Skip to content

Commit

Permalink
feat(coverage): run multi-file typescript tests (#4256)
Browse files Browse the repository at this point in the history
The code is really ugly because I didn't anticipate multi-test files from typescript when I started writing the runner :-(
  • Loading branch information
Boshen committed Jul 16, 2024
1 parent b5a8f3c commit 107e570
Show file tree
Hide file tree
Showing 10 changed files with 1,849 additions and 241 deletions.
4 changes: 2 additions & 2 deletions tasks/coverage/codegen_typescript.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
commit: d8086f14

codegen_typescript Summary:
AST Parsed : 5283/5283 (100.00%)
Positive Passed: 5283/5283 (100.00%)
AST Parsed : 6456/6456 (100.00%)
Positive Passed: 6456/6456 (100.00%)
1,894 changes: 1,741 additions & 153 deletions tasks/coverage/parser_typescript.snap

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions tasks/coverage/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,15 @@ impl Case for CodegenTypeScriptCase {
}

fn run(&mut self) {
let source_text = self.base.code();
let source_type = self.base.source_type();
let result = get_result(source_text, source_type);
self.base.set_result(result);
let units = self.base.units.clone();
for unit in units {
let result = get_result(&unit.content, unit.source_type);
if result != TestResult::Passed {
self.base.result = result;
return;
}
}
self.base.result = TestResult::Passed;
}
}

Expand Down
14 changes: 10 additions & 4 deletions tasks/coverage/src/prettier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,16 @@ impl Case for PrettierTypeScriptCase {
}

fn run(&mut self) {
let source_text = self.base.code();
let source_type = self.base.source_type();
let result = get_result(source_text, source_type);
self.base.set_result(result);
let units = self.base.units.clone();
for unit in units {
self.base.code = unit.content.to_string();
let result = get_result(&unit.content, unit.source_type);
if result != TestResult::Passed {
self.base.result = result;
return;
}
}
self.base.result = TestResult::Passed;
}
}

Expand Down
2 changes: 1 addition & 1 deletion tasks/coverage/src/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use walkdir::WalkDir;

use crate::{project_root, AppArgs};

#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum TestResult {
ToBeRun,
Passed,
Expand Down
22 changes: 17 additions & 5 deletions tasks/coverage/src/transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,28 @@ impl Case for TransformerTypeScriptCase {
self.base.skip_test_case() || self.base.should_fail()
}

fn run(&mut self) {
fn execute(&mut self, source_type: SourceType) -> TestResult {
let mut options = get_default_transformer_options();
let mut source_type = self.base.source_type();
let mut source_type = source_type;
// handle @jsx: react, `react` of behavior is match babel following options
if self.base.meta().settings.jsx.last().is_some_and(|jsx| jsx == "react") {
if self.base.settings.jsx.last().is_some_and(|jsx| jsx == "react") {
source_type = source_type.with_module(true);
options.react.runtime = ReactJsxRuntime::Classic;
}
let result = get_result(self.base.code(), source_type, self.path(), Some(options));
self.base.set_result(result);
get_result(self.base.code(), source_type, self.path(), Some(options))
}

fn run(&mut self) {
let units = self.base.units.clone();
for unit in units {
self.base.code = unit.content.to_string();
let result = self.execute(unit.source_type);
if result != TestResult::Passed {
self.base.result = result;
return;
}
}
self.base.result = TestResult::Passed;
}
}

Expand Down
30 changes: 29 additions & 1 deletion tasks/coverage/src/typescript/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ impl CompilerSettings {
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct TestUnitData {
pub name: String,
pub content: String,
pub source_type: SourceType,
}

#[derive(Debug)]
Expand Down Expand Up @@ -110,6 +111,7 @@ impl TestCaseContent {
test_unit_data.push(TestUnitData {
name: file_name,
content: std::mem::take(&mut current_file_content),
source_type: SourceType::default(),
});
}
current_file_name = Some(meta_value.to_string());
Expand All @@ -131,13 +133,39 @@ impl TestCaseContent {
test_unit_data.push(TestUnitData {
name: file_name,
content: std::mem::take(&mut current_file_content),
source_type: SourceType::default(),
});

let settings = CompilerSettings::new(&current_file_options);

let is_module = test_unit_data.len() > 1;
let test_unit_data = test_unit_data
.into_iter()
.filter_map(|mut unit| {
let source_type = Self::get_source_type(Path::new(&unit.name), &settings)?;
unit.source_type = source_type.with_module(is_module);
Some(unit)
})
.collect::<Vec<_>>();

let error_files = Self::get_error_files(path, &settings);
Self { tests: test_unit_data, settings, error_files }
}

fn get_source_type(path: &Path, options: &CompilerSettings) -> Option<SourceType> {
let is_module = ["esnext", "es2022", "es2020", "es2015"]
.into_iter()
.any(|module| options.modules.contains(&module.to_string()));
Some(
SourceType::from_path(path)
.ok()?
.with_script(true)
.with_module(is_module)
.with_jsx(!options.jsx.is_empty())
.with_typescript_definition(options.declaration),
)
}

// TypeScript error files can be:
// * `filename(module=es2022).errors.txt`
// * `filename(target=esnext).errors.txt`
Expand Down
67 changes: 19 additions & 48 deletions tasks/coverage/src/typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ mod transpile_runner;

use std::path::{Path, PathBuf};

use oxc_span::SourceType;

use self::meta::TestCaseContent;
use self::meta::{CompilerSettings, TestCaseContent, TestUnitData};
pub use self::transpile_runner::{TranspileRunner, TypeScriptTranspileCase};
use crate::{
project_root,
Expand Down Expand Up @@ -62,48 +60,18 @@ impl<T: Case> Suite<T> for TypeScriptSuite<T> {

pub struct TypeScriptCase {
path: PathBuf,
code: String,
source_type: SourceType,
result: TestResult,
meta: TestCaseContent,
}

impl TypeScriptCase {
pub fn source_type(&self) -> SourceType {
self.source_type
}

pub fn set_result(&mut self, result: TestResult) {
self.result = result;
}

pub fn meta(&self) -> &TestCaseContent {
&self.meta
}
pub code: String,
pub units: Vec<TestUnitData>,
pub settings: CompilerSettings,
error_files: Vec<String>,
pub result: TestResult,
}

impl Case for TypeScriptCase {
fn new(path: PathBuf, code: String) -> Self {
let meta = TestCaseContent::make_units_from_test(&path, &code);
let compiler_options = &meta.settings;
let is_module = ["esnext", "es2022", "es2020", "es2015"]
.into_iter()
.any(|module| compiler_options.modules.contains(&module.to_string()));
let source_type = SourceType::from_path(&path)
.unwrap()
.with_script(true)
.with_module(is_module)
.with_jsx(!compiler_options.jsx.is_empty())
.with_typescript_definition(compiler_options.declaration);
Self {
path,
// FIXME: current skip multi-file test cases, if doesn't skip in the future, need to handle multi-file test cases
// Use meta.tests[0].content.clone() instead of code to get without meta options code
code: meta.tests[0].content.clone(),
source_type,
result: TestResult::ToBeRun,
meta,
}
let TestCaseContent { tests, settings, error_files } =
TestCaseContent::make_units_from_test(&path, &code);
Self { path, code, units: tests, settings, error_files, result: TestResult::ToBeRun }
}

fn code(&self) -> &str {
Expand All @@ -119,15 +87,18 @@ impl Case for TypeScriptCase {
}

fn should_fail(&self) -> bool {
!self.meta.error_files.is_empty()
}

fn skip_test_case(&self) -> bool {
// skip multi-file test cases for now
self.meta.tests.len() > 1
!self.error_files.is_empty()
}

fn run(&mut self) {
self.result = self.execute(self.source_type);
let units = self.units.clone();
for unit in units {
self.code.clone_from(&unit.content);
self.result = self.execute(unit.source_type);
if self.result != TestResult::Passed {
return;
}
}
self.result = TestResult::Passed;
}
}
37 changes: 16 additions & 21 deletions tasks/coverage/src/typescript/transpile_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use oxc_parser::Parser;
use oxc_span::SourceType;

use super::{
meta::{Baseline, BaselineFile, CompilerSettings, TestCaseContent, TestUnitData},
TESTS_ROOT,
meta::{Baseline, BaselineFile},
TypeScriptCase, TESTS_ROOT,
};
use crate::{
project_root,
Expand Down Expand Up @@ -61,50 +61,44 @@ enum TranspileKind {
}

pub struct TypeScriptTranspileCase {
path: PathBuf,
code: String,
units: Vec<TestUnitData>,
settings: CompilerSettings,
test_result: TestResult,
base: TypeScriptCase,
}

impl Case for TypeScriptTranspileCase {
fn new(path: PathBuf, code: String) -> Self {
let TestCaseContent { tests, settings, .. } =
TestCaseContent::make_units_from_test(&path, &code);
Self { path, code, units: tests, settings, test_result: TestResult::ToBeRun }
Self { base: TypeScriptCase::new(path, code) }
}

fn code(&self) -> &str {
&self.code
self.base.code()
}

fn path(&self) -> &Path {
&self.path
self.base.path()
}

fn test_result(&self) -> &TestResult {
&self.test_result
self.base.test_result()
}

fn skip_test_case(&self) -> bool {
!self.settings.declaration
!self.base.settings.declaration
}

fn run(&mut self) {
// if !self.settings.emit_declaration_only {
// self.run_kind(TranspileKind::Module);
// }
if self.settings.declaration {
self.test_result = self.compare(TranspileKind::Declaration);
if self.base.settings.declaration {
self.base.result = self.compare(TranspileKind::Declaration);
}
}
}

impl TypeScriptTranspileCase {
fn compare(&self, kind: TranspileKind) -> TestResult {
// get expected text by reading its .d.ts file
let filename = change_extension(self.path.to_str().unwrap());
let filename = change_extension(self.path().to_str().unwrap());
let path =
project_root().join(TESTS_ROOT).join("baselines/reference/transpile").join(filename);
let expected = BaselineFile::parse(&path);
Expand All @@ -130,7 +124,8 @@ impl TypeScriptTranspileCase {
},
);
if !matched {
let snapshot = format!("\n#### {:?} ####\n{}", self.path, baseline.snapshot());
let snapshot =
format!("\n#### {:?} ####\n{}", self.path(), baseline.snapshot());
return TestResult::CorrectError(snapshot, false);
}
}
Expand All @@ -142,7 +137,7 @@ impl TypeScriptTranspileCase {
fn run_kind(&self, _kind: TranspileKind) -> BaselineFile {
let mut files = vec![];

for unit in &self.units {
for unit in &self.base.units {
let mut baseline = Baseline {
name: unit.name.clone(),
original: unit.content.clone(),
Expand All @@ -152,8 +147,8 @@ impl TypeScriptTranspileCase {
files.push(baseline);
}

for unit in &self.units {
let (source_text, errors) = transpile(&self.path, &unit.content);
for unit in &self.base.units {
let (source_text, errors) = transpile(self.path(), &unit.content);
let baseline = Baseline {
name: change_extension(&unit.name),
original: unit.content.clone(),
Expand Down
7 changes: 5 additions & 2 deletions tasks/coverage/transformer_typescript.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
commit: d8086f14

transformer_typescript Summary:
AST Parsed : 5283/5283 (100.00%)
Positive Passed: 5282/5283 (99.98%)
AST Parsed : 6456/6456 (100.00%)
Positive Passed: 6452/6456 (99.94%)
Mismatch: "compiler/constEnumNamespaceReferenceCausesNoImport2.ts"
Mismatch: "compiler/elidedEmbeddedStatementsReplacedWithSemicolon.ts"
Mismatch: "conformance/externalModules/typeOnly/exportDeclaration.ts"
Mismatch: "conformance/jsx/inline/inlineJsxAndJsxFragPragmaOverridesCompilerOptions.tsx"

0 comments on commit 107e570

Please sign in to comment.