Skip to content
This repository was archived by the owner on Nov 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
run: cargo install --locked --path .
# partially covered by tests/cli.rs, but this checks the shebang
- name: Check output of hello example
run: examples/hello.qn | git diff --no-index examples/run/out/hello.txt -
run: diff <(examples/hello.qn) <(echo 'Hello, world!')
- name: Upload quench for next job
uses: actions/upload-artifact@v2
with:
Expand Down
21 changes: 20 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ goldenfile = "1"
itertools = "0.10"
pretty_assertions = "0.7"
regex = "1"
serde = "1"
serde_yaml = "0.8"
toml = "0.5"
yaml-rust = "0.4"
Empty file.
Empty file removed examples/compile/err/hello.txt
Empty file.
3 changes: 0 additions & 3 deletions examples/compile/err/hello_syntax_errors.txt

This file was deleted.

Empty file.
File renamed without changes.
File renamed without changes.
Empty file removed examples/run/err/complicated.txt
Empty file.
Empty file removed examples/run/err/hello.txt
Empty file.
3 changes: 0 additions & 3 deletions examples/run/err/hello_syntax_errors.txt

This file was deleted.

5 changes: 0 additions & 5 deletions examples/run/out/complicated.txt

This file was deleted.

1 change: 0 additions & 1 deletion examples/run/out/hello.txt

This file was deleted.

Empty file.
3 changes: 1 addition & 2 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,7 @@ mod tests {

#[test]
fn test_diagnostics_hello_world() {
let (db, uri) =
foo_db(slurp::read_all_to_string("examples/hello_syntax_errors.qn").unwrap());
let (db, uri) = foo_db(slurp::read_all_to_string("examples/errors.qn").unwrap());
let diagnostics = db.diagnostics(uri);
assert_eq!(
diagnostics,
Expand Down
180 changes: 152 additions & 28 deletions tests/cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
use assert_cmd::{assert::OutputAssertExt, prelude::CommandCargoExt};
use goldenfile::Mint;
use pretty_assertions::assert_eq;
use std::{
collections::BTreeMap,
ffi::OsStr,
path::Path,
fs::File,
io,
path::{Path, PathBuf},
process::{Command, Output},
str,
};
use std::{fs, io::Write};

Expand All @@ -17,41 +22,160 @@ fn test_help() {
file.write_all(&assert.get_output().stdout).unwrap();
}

fn assert_expected(subcmd: &str, src: &Path) {
let mut cmd = Command::cargo_bin("quench").unwrap();
let assert = cmd.env("NO_COLOR", "1").arg(subcmd).arg(&src).assert();
let Output {
status,
stdout,
stderr,
} = assert.get_output();
fn subcmd<I, S>(name: &str, args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::cargo_bin("quench")
.unwrap()
.env("NO_COLOR", "1")
.arg(name)
.args(args)
.output()
.unwrap()
}

if stderr.is_empty() {
assert!(status.success());
fn to_nonempty_string(bytes: Vec<u8>) -> Option<String> {
if bytes.is_empty() {
None
} else {
assert!(!status.success());
Some(str::from_utf8(&bytes).unwrap().to_string())
}
}

#[derive(Debug, serde::Deserialize, PartialEq)]
struct Example {
compile: Option<String>,
status: Option<i32>,
out: Option<String>,
err: Option<String>,
}

let dir = src.parent().unwrap().join(subcmd);
let txt_path = src.with_extension("txt");
let txt_name = Path::new(txt_path.file_name().unwrap());
fn try_example(path: PathBuf) -> (String, Example) {
let stem = path.file_stem().unwrap().to_str().unwrap().to_string();
let example = {
let Output {
status,
stdout,
stderr,
} = subcmd("compile", &[&path]);
if status.success() {
assert!(stderr.is_empty());
{
let mut mint = Mint::new("examples");
let mut file = mint
.new_goldenfile(Path::new(&stem).with_extension("js"))
.unwrap();
file.write_all(&stdout).unwrap();
}

let mut out_mint = Mint::new(dir.join("out"));
let mut out = out_mint.new_goldenfile(txt_name).unwrap();
out.write_all(stdout).unwrap();
let Output {
status,
stdout,
stderr,
} = subcmd("run", &[path]);
Example {
compile: None,
// we don't just use status.code() here, because we assume there was an exit code
status: {
let code = status.code().unwrap();
if code == 0 {
None
} else {
Some(code)
}
},
out: to_nonempty_string(stdout),
err: to_nonempty_string(stderr),
}
} else {
assert!(stdout.is_empty());
Example {
compile: to_nonempty_string(stderr),
status: None,
out: None,
err: None,
}
}
};
(stem, example)
}

let mut err_mint = Mint::new(dir.join("err"));
let mut err = err_mint.new_goldenfile(txt_name).unwrap();
err.write_all(stderr).unwrap();
fn write_literal(writer: &mut impl Write, key: &str, value: &Option<String>) -> io::Result<()> {
if let Some(string) = value {
write!(writer, " {}: |\n", key)?;
for line in string.lines() {
write!(writer, " {}\n", line)?;
}
}
Ok(())
}

#[test]
fn test_examples() {
for entry in fs::read_dir("examples").unwrap() {
let path = entry.unwrap().path();
if path.extension() == Some(OsStr::new("qn")) {
assert_expected("compile", &path);
assert_expected("run", &path);
fn write_example(writer: &mut impl Write, name: &str, example: &Example) -> io::Result<()> {
write!(writer, "{}:\n", name)?;
let Example {
compile,
status,
out,
err,
} = example;
write_literal(writer, "compile", compile)?;
if let Some(code) = status {
write!(writer, " status: {}\n", code)?;
}
write_literal(writer, "out", out)?;
write_literal(writer, "err", err)?;
Ok(())
}

type Examples = BTreeMap<String, Example>;

fn write_examples(writer: &mut impl Write, examples: &Examples) -> io::Result<()> {
let mut it = examples.iter();
if let Some((name, example)) = it.next() {
write_example(writer, name, example)?;
for (name, example) in it {
write!(writer, "\n")?;
write_example(writer, name, example)?;
}
}
Ok(())
}

#[test]
fn test_examples() {
let examples: Examples = fs::read_dir("examples")
.unwrap()
.filter_map(|entry| {
let path = entry.unwrap().path();
if path.extension() == Some(OsStr::new("qn")) {
Some(try_example(path))
} else {
None
}
})
.collect();

let goldenfiles = "tests/goldenfiles";
let filename = "examples.yml";

// assert that the actual YAML file matches what we generate from running the examples; the
// goldenfile paradigm provides a nice testing UX via its REGENERATE_GOLDENFILES env var, but we
// need to use a custom write_examples function because yaml-rust doesn't emit literal scalars
// https://github.com/chyh1990/yaml-rust/pull/132 https://github.com/chyh1990/yaml-rust/pull/137
write_examples(
&mut Mint::new(goldenfiles).new_goldenfile(filename).unwrap(),
&examples,
)
.unwrap();

// redundant check, to ensure that our write_examples function works correctly
assert_eq!(
examples,
serde_yaml::from_reader::<File, Examples>(
File::open(Path::new(goldenfiles).join(filename)).unwrap()
)
.unwrap(),
);
}
17 changes: 17 additions & 0 deletions tests/goldenfiles/examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
complicated:
out: |
foo
bar
undefined
👻 ba # not a comment
z 🙃

errors:
compile: |
0:6 to 0:14 Error: syntax (ERROR (string))
0:24 to 0:24 Error: syntax (MISSING ";")
Error: Failed to parse.

hello:
out: |
Hello, world!
51 changes: 20 additions & 31 deletions tests/version.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use comrak::ComrakOptions;
use regex::Regex;
use std::{collections::HashSet, path::PathBuf, str};
use yaml_rust::{Yaml, YamlLoader};

#[test]
fn test_version() {
Expand Down Expand Up @@ -76,36 +75,26 @@ fn test_minimum_rustc() {

let ci_versions: HashSet<String> = {
let re = Regex::new(r"^(\d+\.\d+)\.\d+$").unwrap();
YamlLoader::load_from_str(&slurp::read_all_to_string(".github/workflows/ci.yml").unwrap())
.unwrap()
.get(0)
.unwrap()
.as_hash()
.unwrap()
.get(&Yaml::String(String::from("jobs")))
.unwrap()
.as_hash()
.unwrap()
.get(&Yaml::String(String::from("rust")))
.unwrap()
.as_hash()
.unwrap()
.get(&Yaml::String(String::from("strategy")))
.unwrap()
.as_hash()
.unwrap()
.get(&Yaml::String(String::from("matrix")))
.unwrap()
.as_hash()
.unwrap()
.get(&Yaml::String(String::from("rust")))
.unwrap()
.as_vec()
.unwrap()
.iter()
.filter_map(|version| re.captures(version.as_str().unwrap()))
.map(|m| String::from(&m[1]))
.collect()
serde_yaml::from_str::<serde_yaml::Value>(
&slurp::read_all_to_string(".github/workflows/ci.yml").unwrap(),
)
.unwrap()
.get("jobs")
.unwrap()
.get("rust")
.unwrap()
.get("strategy")
.unwrap()
.get("matrix")
.unwrap()
.get("rust")
.unwrap()
.as_sequence()
.unwrap()
.iter()
.filter_map(|version| re.captures(version.as_str().unwrap()))
.map(|m| String::from(&m[1]))
.collect()
};
assert!(
readme_versions.is_subset(&ci_versions),
Expand Down