Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

chore: test infra for transformations #4719

Merged
merged 4 commits into from
Jul 23, 2023
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
36 changes: 27 additions & 9 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ rome_suppression = { version = "0.0.1", path = "./crates/rome_suppres
rome_text_edit = { version = "0.0.1", path = "./crates/rome_text_edit" }
rome_text_size = { version = "0.0.1", path = "./crates/rome_text_size" }
tests_macros = { path = "./crates/tests_macros" }
rome_test_utils = { path = "./crates/rome_test_utils" }

# Crates needed in the workspace
bitflags = "2.3.1"
Expand Down
4 changes: 3 additions & 1 deletion crates/rome_analyze/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ pub use crate::rule::{
RuleMeta, RuleMetadata, SuppressAction,
};
pub use crate::services::{FromServices, MissingServicesDiagnostic, ServiceBag};
pub use crate::signals::{AnalyzerAction, AnalyzerSignal, DiagnosticSignal};
pub use crate::signals::{
AnalyzerAction, AnalyzerSignal, AnalyzerTransformation, DiagnosticSignal,
};
pub use crate::syntax::{Ast, SyntaxVisitor};
pub use crate::visitor::{NodeVisitor, Visitor, VisitorContext, VisitorFinishContext};

Expand Down
5 changes: 1 addition & 4 deletions crates/rome_js_analyze/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,10 @@ smallvec = { workspace = true }
[dev-dependencies]
countme = { workspace = true, features = ["enable"] }
insta = { workspace = true, features = ["glob"] }
json_comments = "0.2.1"
rome_js_parser = { workspace = true, features = ["tests"] }
rome_json_parser = { path = "../rome_json_parser" }
rome_service = { path = "../rome_service" }
rome_text_edit = { workspace = true }
similar = "2.1.0"
tests_macros = { workspace = true }
rome_test_utils = { workspace = true }

[features]
schemars = ["dep:schemars"]
199 changes: 21 additions & 178 deletions crates/rome_js_analyze/tests/spec_tests.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,19 @@
use json_comments::StripComments;
use rome_analyze::{
AnalysisFilter, AnalyzerAction, AnalyzerOptions, ControlFlow, Never, RuleFilter,
};
use rome_console::{
fmt::{Formatter, Termcolor},
markup, Markup,
};
use rome_deserialize::json::deserialize_from_json_str;
use rome_analyze::{AnalysisFilter, AnalyzerAction, ControlFlow, Never, RuleFilter};
use rome_diagnostics::advice::CodeSuggestionAdvice;
use rome_diagnostics::termcolor::NoColor;
use rome_diagnostics::{DiagnosticExt, Error, PrintDiagnostic, Severity};
use rome_js_parser::{
parse,
test_utils::{assert_errors_are_absent, has_bogus_nodes_or_empty_slots},
JsParserOptions,
};
use rome_diagnostics::{DiagnosticExt, Severity};
use rome_js_parser::{parse, JsParserOptions};
use rome_js_syntax::{JsFileSource, JsLanguage};
use rome_service::configuration::to_analyzer_configuration;
use rome_service::settings::WorkspaceSettings;
use rome_service::Configuration;
use similar::TextDiff;
use std::{
ffi::OsStr, fmt::Write, fs::read_to_string, os::raw::c_int, path::Path, slice, sync::Once,
use rome_rowan::AstNode;
use rome_test_utils::{
assert_errors_are_absent, code_fix_to_string, create_analyzer_options, diagnostic_to_string,
has_bogus_nodes_or_empty_slots, parse_test_path, register_leak_checker, scripts_from_json,
write_analyzer_snapshot, CheckActionType,
};
use std::{ffi::OsStr, fs::read_to_string, path::Path, slice};

tests_macros::gen_tests! {"tests/specs/**/*.{cjs,js,jsx,tsx,ts,json,jsonc}", crate::run_test, "module"}
tests_macros::gen_tests! {"tests/suppression/**/*.{cjs,js,jsx,tsx,ts,json,jsonc}", crate::run_suppression_test, "module"}

fn scripts_from_json(extension: &OsStr, input_code: &str) -> Option<Vec<String>> {
if extension == "json" || extension == "jsonc" {
let input_code = StripComments::new(input_code.as_bytes());
let scripts: Vec<String> = serde_json::from_reader(input_code).ok()?;
Some(scripts)
} else {
None
}
}

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

Expand Down Expand Up @@ -67,7 +44,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
.unwrap_or_else(|err| panic!("failed to read {:?}: {:?}", input_file, err));
let quantity_diagnostics = if let Some(scripts) = scripts_from_json(extension, &input_code) {
for script in scripts {
write_analysis_to_snapshot(
analyze_and_snap(
&mut snapshot,
&script,
JsFileSource::js_script(),
Expand All @@ -84,7 +61,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
let Ok(source_type) = input_file.try_into() else {
return;
};
write_analysis_to_snapshot(
analyze_and_snap(
&mut snapshot,
&input_code,
source_type,
Expand All @@ -108,19 +85,8 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
}
}

enum CheckActionType {
Suppression,
Lint,
}

impl CheckActionType {
const fn is_suppression(&self) -> bool {
matches!(self, Self::Suppression)
}
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn write_analysis_to_snapshot(
pub(crate) fn analyze_and_snap(
snapshot: &mut String,
input_code: &str,
source_type: JsFileSource,
Expand All @@ -135,44 +101,7 @@ pub(crate) fn write_analysis_to_snapshot(

let mut diagnostics = Vec::new();
let mut code_fixes = Vec::new();
let mut options = AnalyzerOptions::default();
// We allow a test file to configure its rule using a special
// file with the same name as the test but with extension ".options.json"
// that configures that specific rule.
let options_file = input_file.with_extension("options.json");
if let Ok(json) = std::fs::read_to_string(options_file.clone()) {
let deserialized = deserialize_from_json_str::<Configuration>(json.as_str());
if deserialized.has_errors() {
diagnostics.extend(
deserialized
.into_diagnostics()
.into_iter()
.map(|diagnostic| {
diagnostic_to_string(
options_file.file_stem().unwrap().to_str().unwrap(),
&json,
diagnostic,
)
})
.collect::<Vec<_>>(),
);
None
} else {
let configuration = deserialized.into_deserialized();
let mut settings = WorkspaceSettings::default();
settings.merge_with_configuration(configuration).unwrap();
let configuration =
to_analyzer_configuration(&settings.linter, &settings.languages, |_| vec![]);
options = AnalyzerOptions {
configuration,
..AnalyzerOptions::default()
};

Some(json)
}
} else {
None
};
let options = create_analyzer_options(input_file, &mut diagnostics);

let (_, errors) = rome_js_analyze::analyze(&root, filter, &options, source_type, |event| {
if let Some(mut diag) = event.diagnostic() {
Expand Down Expand Up @@ -236,68 +165,16 @@ pub(crate) fn write_analysis_to_snapshot(
diagnostics.push(diagnostic_to_string(file_name, input_code, error));
}

writeln!(snapshot, "# Input").unwrap();
writeln!(snapshot, "```js").unwrap();
writeln!(snapshot, "{}", input_code).unwrap();
writeln!(snapshot, "```").unwrap();
writeln!(snapshot).unwrap();

if !diagnostics.is_empty() {
writeln!(snapshot, "# Diagnostics").unwrap();
for diagnostic in &diagnostics {
writeln!(snapshot, "```").unwrap();
writeln!(snapshot, "{}", diagnostic).unwrap();
writeln!(snapshot, "```").unwrap();
writeln!(snapshot).unwrap();
}
}

if !code_fixes.is_empty() {
writeln!(snapshot, "# Actions").unwrap();
for action in code_fixes {
writeln!(snapshot, "```diff").unwrap();
writeln!(snapshot, "{}", action).unwrap();
writeln!(snapshot, "```").unwrap();
writeln!(snapshot).unwrap();
}
}
write_analyzer_snapshot(
snapshot,
input_code,
diagnostics.as_slice(),
code_fixes.as_slice(),
);

diagnostics.len()
}

/// The test runner for the analyzer is currently designed to have a
/// one-to-one mapping between test case and analyzer rules.
/// So each testing file will be run through the analyzer with only the rule
/// corresponding to the directory name. E.g., `style/useWhile/test.js`
/// will be analyzed with just the `style/useWhile` rule.
fn parse_test_path(file: &Path) -> (&str, &str) {
let rule_folder = file.parent().unwrap();
let rule_name = rule_folder.file_name().unwrap();

let group_folder = rule_folder.parent().unwrap();
let group_name = group_folder.file_name().unwrap();

(group_name.to_str().unwrap(), rule_name.to_str().unwrap())
}

fn markup_to_string(markup: Markup) -> String {
let mut buffer = Vec::new();
let mut write = Termcolor(NoColor::new(&mut buffer));
let mut fmt = Formatter::new(&mut write);
fmt.write_markup(markup).unwrap();

String::from_utf8(buffer).unwrap()
}
#[allow(clippy::let_and_return)]
fn diagnostic_to_string(name: &str, source: &str, diag: Error) -> String {
let error = diag.with_file_path(name).with_file_source_code(source);
let text = markup_to_string(markup! {
{PrintDiagnostic::verbose(&error)}
});

text
}

fn check_code_action(
path: &Path,
source: &str,
Expand Down Expand Up @@ -329,41 +206,7 @@ fn check_code_action(

// Re-parse the modified code and panic if the resulting tree has syntax errors
let re_parse = parse(&output, source_type, options);
assert_errors_are_absent(&re_parse, path);
}

fn code_fix_to_string(source: &str, action: AnalyzerAction<JsLanguage>) -> String {
let (_, text_edit) = action.mutation.as_text_edits().unwrap_or_default();

let output = text_edit.new_string(source);

let diff = TextDiff::from_lines(source, &output);

let mut diff = diff.unified_diff();
diff.context_radius(3);

diff.to_string()
}

// Check that all red / green nodes have correctly been released on exit
extern "C" fn check_leaks() {
if let Some(report) = rome_rowan::check_live() {
panic!("\n{report}")
}
}
pub(crate) fn register_leak_checker() {
// Import the atexit function from libc
extern "C" {
fn atexit(f: extern "C" fn()) -> c_int;
}

// Use an atomic Once to register the check_leaks function to be called
// when the process exits
static ONCE: Once = Once::new();
ONCE.call_once(|| unsafe {
countme::enable(true);
atexit(check_leaks);
});
assert_errors_are_absent(re_parse.tree().syntax(), re_parse.diagnostics(), path);
}

pub(crate) fn run_suppression_test(input: &'static str, _: &str, _: &str, _: &str) {
Expand All @@ -383,7 +226,7 @@ pub(crate) fn run_suppression_test(input: &'static str, _: &str, _: &str, _: &st
};

let mut snapshot = String::new();
write_analysis_to_snapshot(
analyze_and_snap(
&mut snapshot,
&input_code,
JsFileSource::jsx(),
Expand Down
Loading
Loading