Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(coverage): add driver struct for adding common checks later #4893

Merged
merged 1 commit into from
Aug 14, 2024
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
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
Loading