Skip to content

Commit

Permalink
refactor(coverage): add driver struct for adding common checks later (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Aug 14, 2024
1 parent ea1e64a commit 117ae36
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 195 deletions.
167 changes: 167 additions & 0 deletions tasks/coverage/src/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::collections::HashSet;
use std::path::PathBuf;

use oxc_allocator::Allocator;
use oxc_ast::Trivias;
use oxc_codegen::{CodeGenerator, CommentOptions, WhitespaceRemover};
use oxc_diagnostics::OxcDiagnostic;
use oxc_minifier::{CompressOptions, Compressor};
use oxc_parser::{Parser, ParserReturn};
use oxc_semantic::{Semantic, SemanticBuilder};
use oxc_span::{SourceType, Span};
use oxc_transformer::{TransformOptions, Transformer};

use crate::suite::TestResult;

#[allow(clippy::struct_excessive_bools)]
#[derive(Default)]
pub struct Driver {
pub path: PathBuf,
// options
pub transform: Option<TransformOptions>,
pub compress: bool,
pub remove_whitespace: bool,
pub codegen: bool,
pub allow_return_outside_function: bool,
// results
pub panicked: bool,
pub errors: Vec<OxcDiagnostic>,
pub printed: String,
}

impl Driver {
pub fn errors(&mut self) -> Vec<OxcDiagnostic> {
std::mem::take(&mut self.errors)
}

pub fn idempotency(
mut self,
case: &'static str,
source_text: &str,
source_type: SourceType,
) -> TestResult {
self.run(source_text, source_type);
let printed1 = self.printed.clone();
self.run(&printed1, source_type);
let printed2 = self.printed.clone();
if printed1 == printed2 {
TestResult::Passed
} else {
TestResult::Mismatch(case, printed1, printed2)
}
}

pub fn run(&mut self, source_text: &str, source_type: SourceType) {
let allocator = Allocator::default();
let ParserReturn { mut program, errors, trivias, panicked } =
Parser::new(&allocator, source_text, source_type)
.allow_return_outside_function(self.allow_return_outside_function)
.parse();
self.panicked = panicked;

if self.check_comments(&trivias) {
return;
}

// Make sure serialization doesn't crash; also for code coverage.
let _serializer = program.serializer();

if !errors.is_empty() {
self.errors.extend(errors);
}

let semantic_ret = SemanticBuilder::new(source_text, source_type)
.with_trivias(trivias.clone())
.with_check_syntax_error(true)
.build_module_record(self.path.clone(), &program)
.build(&program);

if !semantic_ret.errors.is_empty() {
self.errors.extend(semantic_ret.errors);
return;
}

// TODO
// if self.check_semantic(&semantic_ret.semantic) {
// return;
// }

if let Some(options) = self.transform.clone() {
Transformer::new(
&allocator,
&self.path,
source_type,
source_text,
trivias.clone(),
options,
)
.build(&mut program);
}

if self.compress {
Compressor::new(&allocator, CompressOptions::all_true()).build(&mut program);
}

if self.codegen {
let comment_options = CommentOptions { preserve_annotate_comments: true };

let printed = if self.remove_whitespace {
WhitespaceRemover::new().build(&program).source_text
} else {
CodeGenerator::new()
.enable_comment(source_text, trivias, comment_options)
.build(&program)
.source_text
};

self.printed = printed;
}
}

fn check_comments(&mut self, trivias: &Trivias) -> bool {
let mut uniq: HashSet<Span> = HashSet::new();
for comment in trivias.comments() {
if !uniq.insert(comment.span) {
self.errors
.push(OxcDiagnostic::error("Duplicate Comment").with_label(comment.span));
return true;
}
}
false
}

#[allow(unused)]
fn check_semantic(&mut self, semantic: &Semantic<'_>) -> bool {
if are_all_identifiers_resolved(semantic) {
return false;
}
self.errors.push(OxcDiagnostic::error("symbol or reference is not set"));
false
}
}

#[allow(unused)]
fn are_all_identifiers_resolved(semantic: &Semantic<'_>) -> bool {
use oxc_ast::AstKind;
use oxc_semantic::AstNode;

let ast_nodes = semantic.nodes();
let has_non_resolved = ast_nodes.iter().any(|node| {
match node.kind() {
AstKind::BindingIdentifier(id) => {
let mut parents = ast_nodes.iter_parents(node.id()).map(AstNode::kind);
parents.next(); // Exclude BindingIdentifier itself
if let (Some(AstKind::Function(_)), Some(AstKind::IfStatement(_))) =
(parents.next(), parents.next())
{
return false;
}
id.symbol_id.get().is_none()
}
AstKind::IdentifierReference(ref_id) => ref_id.reference_id.get().is_none(),
_ => false,
}
});

!has_non_resolved
}
2 changes: 2 additions & 0 deletions tasks/coverage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod misc;
mod test262;
mod typescript;

mod driver;
mod tools;

use std::{fs, path::PathBuf, process::Command, time::Duration};
Expand All @@ -18,6 +19,7 @@ use similar::DiffableStr;

use crate::{
babel::{BabelCase, BabelSuite},
driver::Driver,
misc::{MiscCase, MiscSuite},
suite::Suite,
test262::{Test262Case, Test262Suite},
Expand Down
88 changes: 18 additions & 70 deletions tasks/coverage/src/suite.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
collections::HashSet,
fs,
io::{stdout, Read, Write},
panic::UnwindSafe,
Expand All @@ -11,33 +10,25 @@ use console::Style;
use encoding_rs::UTF_16LE;
use encoding_rs_io::DecodeReaderBytesBuilder;
use futures::future::join_all;
use oxc_allocator::Allocator;
use oxc_ast::Trivias;
use oxc_diagnostics::{GraphicalReportHandler, GraphicalTheme, NamedSource};
use oxc_parser::Parser;
use oxc_semantic::SemanticBuilder;
use oxc_span::{SourceType, Span};
use oxc_span::SourceType;
use oxc_tasks_common::{normalize_path, Snapshot};
use rayon::prelude::*;
use similar::{ChangeTag, TextDiff};
use tokio::runtime::Runtime;
use walkdir::WalkDir;

use crate::{project_root, AppArgs};
use crate::{project_root, AppArgs, Driver};

#[derive(Debug, PartialEq)]
pub enum TestResult {
ToBeRun,
Passed,
IncorrectlyPassed,
#[allow(unused)]
// (actual, expected)
Mismatch(String, String),
Mismatch(/* case */ &'static str, /* actual */ String, /* expected */ String),
ParseError(String, /* panicked */ bool),
CorrectError(String, /* panicked */ bool),
RuntimeError(String),
CodegenError(/* reason */ &'static str),
DuplicatedComments(String),
Snapshot(String),
}

Expand Down Expand Up @@ -318,28 +309,16 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {

/// Execute the parser once and get the test result
fn execute(&mut self, source_type: SourceType) -> TestResult {
let allocator = Allocator::default();
let source_text = self.code();
let parser_ret = Parser::new(&allocator, source_text, source_type)
.allow_return_outside_function(self.allow_return_outside_function())
.parse();
if let Some(res) = self.check_comments(&parser_ret.trivias) {
return res;
}
let path = self.path();

// Make sure serialization doesn't crash; also for code coverage.
let _serializer = parser_ret.program.serializer();

let program = allocator.alloc(parser_ret.program);
let semantic_ret = SemanticBuilder::new(source_text, source_type)
.with_trivias(parser_ret.trivias)
.with_check_syntax_error(true)
.build_module_record(PathBuf::new(), program)
.build(program);
if let Some(res) = self.check_semantic(&semantic_ret.semantic) {
return res;
}
let errors = parser_ret.errors.into_iter().chain(semantic_ret.errors).collect::<Vec<_>>();
let mut driver = Driver {
path: path.to_path_buf(),
allow_return_outside_function: self.allow_return_outside_function(),
..Driver::default()
};
driver.run(source_text, source_type);
let errors = driver.errors();

let result = if errors.is_empty() {
Ok(String::new())
Expand All @@ -349,7 +328,7 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {
let mut output = String::new();
for error in errors {
let error = error.with_source_code(NamedSource::new(
normalize_path(self.path()),
normalize_path(path),
source_text.to_string(),
));
handler.render_report(&mut output, error.as_ref()).unwrap();
Expand All @@ -359,8 +338,8 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {

let should_fail = self.should_fail();
match result {
Err(err) if should_fail => TestResult::CorrectError(err, parser_ret.panicked),
Err(err) if !should_fail => TestResult::ParseError(err, parser_ret.panicked),
Err(err) if should_fail => TestResult::CorrectError(err, driver.panicked),
Err(err) if !should_fail => TestResult::ParseError(err, driver.panicked),
Ok(_) if should_fail => TestResult::IncorrectlyPassed,
Ok(_) if !should_fail => TestResult::Passed,
_ => unreachable!(),
Expand All @@ -375,13 +354,12 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {
)?;
writer.write_all(error.as_bytes())?;
}
TestResult::Mismatch(ast_string, expected_ast_string) => {
writer.write_all(
format!("Mismatch: {:?}\n", normalize_path(self.path())).as_bytes(),
)?;
TestResult::Mismatch(case, ast_string, expected_ast_string) => {
writer
.write_all(format!("{case}: {:?}\n", normalize_path(self.path())).as_bytes())?;
if args.diff {
self.print_diff(writer, ast_string.as_str(), expected_ast_string.as_str())?;
println!("Mismatch: {:?}", normalize_path(self.path()));
println!("{case}: {:?}", normalize_path(self.path()));
}
}
TestResult::RuntimeError(error) => {
Expand All @@ -396,23 +374,9 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {
format!("Expect Syntax Error: {:?}\n", normalize_path(self.path())).as_bytes(),
)?;
}
TestResult::CodegenError(reason) => {
writer.write_all(
format!("{reason} failed: {:?}\n", normalize_path(self.path())).as_bytes(),
)?;
}
TestResult::Snapshot(snapshot) => {
writer.write_all(snapshot.as_bytes())?;
}
TestResult::DuplicatedComments(comment) => {
writer.write_all(
format!(
"Duplicated comments \"{comment}\": {:?}\n",
normalize_path(self.path())
)
.as_bytes(),
)?;
}
TestResult::Passed | TestResult::ToBeRun | TestResult::CorrectError(..) => {}
}
Ok(())
Expand All @@ -437,20 +401,4 @@ pub trait Case: Sized + Sync + Send + UnwindSafe {
}
Ok(())
}

fn check_semantic(&self, _semantic: &oxc_semantic::Semantic<'_>) -> Option<TestResult> {
None
}

fn check_comments(&self, trivias: &Trivias) -> Option<TestResult> {
let mut uniq: HashSet<Span> = HashSet::new();
for comment in trivias.comments() {
if !uniq.insert(comment.span) {
return Some(TestResult::DuplicatedComments(
comment.span.source_text(self.code()).to_string(),
));
}
}
None
}
}
33 changes: 0 additions & 33 deletions tasks/coverage/src/test262/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,37 +137,4 @@ impl Case for Test262Case {
}
};
}

fn check_semantic(&self, semantic: &oxc_semantic::Semantic<'_>) -> Option<TestResult> {
if are_all_identifiers_resolved(semantic) {
None
} else {
Some(TestResult::ParseError("Unset symbol / reference".to_string(), true))
}
}
}

fn are_all_identifiers_resolved(semantic: &oxc_semantic::Semantic<'_>) -> bool {
use oxc_ast::AstKind;
use oxc_semantic::AstNode;

let ast_nodes = semantic.nodes();
let has_non_resolved = ast_nodes.iter().any(|node| {
match node.kind() {
AstKind::BindingIdentifier(id) => {
let mut parents = ast_nodes.iter_parents(node.id()).map(AstNode::kind);
parents.next(); // Exclude BindingIdentifier itself
if let (Some(AstKind::Function(_)), Some(AstKind::IfStatement(_))) =
(parents.next(), parents.next())
{
return false;
}
id.symbol_id.get().is_none()
}
AstKind::IdentifierReference(ref_id) => ref_id.reference_id.get().is_none(),
_ => false,
}
});

!has_non_resolved
}
Loading

0 comments on commit 117ae36

Please sign in to comment.