Skip to content

Commit

Permalink
chore: Grit testing infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr committed Jul 11, 2024
1 parent 08f0c8f commit 8347a69
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crates/biome_grit_patterns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ rustc-hash = { workspace = true }
serde = { workspace = true, features = ["derive"] }

[dev-dependencies]
insta = { workspace = true }
biome_test_utils = { path = "../biome_test_utils" }
insta = { workspace = true }
tests_macros = { path = "../tests_macros" }

[lints]
workspace = true
4 changes: 2 additions & 2 deletions crates/biome_grit_patterns/src/grit_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ impl Rewrite {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CreateFile {
pub rewritten: OutputFile,
range: Option<Vec<Range>>,
pub ranges: Option<Vec<Range>>,
}

impl From<CreateFile> for GritQueryResult {
Expand All @@ -294,7 +294,7 @@ impl CreateFile {
fn new(path: &Path, body: &str) -> Self {
CreateFile {
rewritten: OutputFile::new(path, body, None),
range: None,
ranges: None,
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions crates/biome_grit_patterns/src/grit_target_language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ generate_target_language! {
}

impl GritTargetLanguage {
/// Returns the target language to use for the given file extension.
pub fn from_extension(extension: &str) -> Option<Self> {
match extension {
"cjs" | "js" | "jsx" | "mjs" | "ts" | "tsx" => {
Some(Self::JsTargetLanguage(JsTargetLanguage))
}
_ => None,
}
}

/// Returns `true` when the text `content` contains an identifier for a
/// metavariable using bracket syntax.
///
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_grit_patterns/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod variables;

pub use errors::*;
pub use grit_context::GritTargetFile;
pub use grit_query::{GritQuery, GritQueryResult};
pub use grit_query::{CreateFile, GritQuery, GritQueryResult, Message, OutputFile};
pub use grit_target_language::{GritTargetLanguage, JsTargetLanguage};

use biome_grit_parser::parse_grit;
Expand Down
5 changes: 1 addition & 4 deletions crates/biome_grit_patterns/tests/quick_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ use biome_js_syntax::JsFileSource;
fn test_query() {
let parse_grit_result = parse_grit("`hello`");
if !parse_grit_result.diagnostics().is_empty() {
println!(
"Diagnostics from parsing query:\n{:?}",
parse_grit_result.diagnostics()
);
panic!("Cannot parse query:\n{:?}", parse_grit_result.diagnostics());
}

let query = GritQuery::from_node(
Expand Down
128 changes: 128 additions & 0 deletions crates/biome_grit_patterns/tests/spec_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use biome_grit_parser::parse_grit;
use biome_grit_patterns::{
GritQuery, GritQueryResult, GritTargetFile, GritTargetLanguage, Message, OutputFile,
};
use biome_js_parser::{parse, JsParserOptions};
use biome_js_syntax::JsFileSource;
use biome_test_utils::register_leak_checker;
use grit_util::Range;
use std::{fs::read_to_string, path::Path};

tests_macros::gen_tests! {"tests/specs/**/*.grit", crate::run_test, "module"}

fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
register_leak_checker();

let query_path = Path::new(input);

let (test_name, target_lang_ext) = parse_test_path(query_path);
if target_lang_ext == "specs" {
panic!("the test file must be placed in the specs/<target-lang-ext>/ directory");
}

let Some(target_lang) = GritTargetLanguage::from_extension(target_lang_ext) else {
panic!("the test file must be placed in the specs/<target-lang-ext>/ directory, unrecognized extension: {target_lang_ext}");
};

let query = {
let query = read_to_string(query_path)
.unwrap_or_else(|err| panic!("cannot read query from {query_path:?}: {err:?}"));

let parse_grit_result = parse_grit(&query);
if !parse_grit_result.diagnostics().is_empty() {
panic!(
"cannot parse query from {query_path:?}:\n{:?}",
parse_grit_result.diagnostics()
);
}

GritQuery::from_node(parse_grit_result.tree(), None, target_lang)
.unwrap_or_else(|err| panic!("cannot compile query from {query_path:?}: {err:?}"))
};

let target_file = {
let target_path = format!("tests/specs/{target_lang_ext}/{test_name}.{target_lang_ext}");
let target_path = Path::new(&target_path);
let target_code = read_to_string(target_path)
.unwrap_or_else(|err| panic!("failed to read code from {target_path:?}: {err:?}"));
// TODO: We should generalize this when we have more target languages.
let target_parse = parse(
&target_code,
JsFileSource::tsx(),
JsParserOptions::default(),
);

GritTargetFile {
path: target_path.into(),
parse: target_parse.into(),
}
};

let results = query
.execute(target_file)
.unwrap_or_else(|err| panic!("cannot execute query from {query_path:?}: {err:?}"));
let snapshot_result = SnapshotResult::from_query_results(results);

let snapshot = format!("{snapshot_result:#?}");

insta::with_settings!({
prepend_module_to_snapshot => false,
snapshot_path => query_path.parent().unwrap(),
}, {
insta::assert_snapshot!(test_name, snapshot, test_name);
});
}

/// Tests should be in a `specs/<target-lang-extension>` directory, and each
/// test should have a `.grit` file and a matching `.<target-lang-extension>`
/// file.
///
/// Returns a `("<test-name>", "<target-lang-extension>")` tuple.
fn parse_test_path(file: &Path) -> (&str, &str) {
let test_name = file.file_stem().unwrap();

let target_lang_extension = file.parent().unwrap();
let target_lang_extension = target_lang_extension.file_name().unwrap();

(
test_name.to_str().unwrap(),
target_lang_extension.to_str().unwrap(),
)
}

#[derive(Debug, Default)]
struct SnapshotResult {
messages: Vec<Message>,
matched_ranges: Vec<Range>,
rewritten_files: Vec<OutputFile>,
created_files: Vec<OutputFile>,
}

impl SnapshotResult {
fn from_query_results(results: Vec<GritQueryResult>) -> Self {
let mut snapshot_result = Self::default();
for result in results {
match result {
GritQueryResult::Match(m) => {
snapshot_result.messages.extend(m.messages);
snapshot_result.matched_ranges.extend(m.ranges);
}
GritQueryResult::Rewrite(rewrite) => {
snapshot_result.messages.extend(rewrite.original.messages);
snapshot_result
.matched_ranges
.extend(rewrite.original.ranges);
snapshot_result.rewritten_files.push(rewrite.rewritten);
}
GritQueryResult::CreateFile(create_file) => {
if let Some(ranges) = create_file.ranges {
snapshot_result.matched_ranges.extend(ranges);
}
snapshot_result.created_files.push(create_file.rewritten);
}
}
}

snapshot_result
}
}
1 change: 1 addition & 0 deletions crates/biome_grit_patterns/tests/specs/ts/identifier.grit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`bar`
47 changes: 47 additions & 0 deletions crates/biome_grit_patterns/tests/specs/ts/identifier.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
source: crates/biome_grit_patterns/tests/spec_tests.rs
expression: identifier
---
SnapshotResult {
messages: [],
matched_ranges: [
Range {
start: Position {
line: 1,
column: 4,
},
end: Position {
line: 1,
column: 4,
},
start_byte: 25,
end_byte: 28,
},
Range {
start: Position {
line: 1,
column: 4,
},
end: Position {
line: 1,
column: 4,
},
start_byte: 52,
end_byte: 55,
},
Range {
start: Position {
line: 1,
column: 4,
},
end: Position {
line: 1,
column: 4,
},
start_byte: 99,
end_byte: 102,
},
],
rewritten_files: [],
created_files: [],
}
12 changes: 12 additions & 0 deletions crates/biome_grit_patterns/tests/specs/ts/identifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

function foo() {
const bar = 1;

const baz = {
bar: "another one"
};
}

export type Foo = {
bar: number;
};

0 comments on commit 8347a69

Please sign in to comment.