Skip to content

Commit

Permalink
Refactor sanctuary test and extract bindings creation for readability
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Nov 25, 2024
1 parent 4dd5895 commit fe89f8d
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 41 deletions.
10 changes: 9 additions & 1 deletion crates/solidity/testing/sanctuary/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl Events {
};
}

pub fn parse_error(&self, message: impl AsRef<str>) {
fn test_error(&self, message: impl AsRef<str>) {
match self.failed.position().cmp(&MAX_PRINTED_FAILURES) {
cmp::Ordering::Less => {
self.reporter.println(message);
Expand All @@ -122,6 +122,14 @@ impl Events {
};
}

pub fn parse_error(&self, message: impl AsRef<str>) {
self.test_error(message);
}

pub fn bindings_error(&self, message: impl AsRef<str>) {
self.test_error(message);
}

pub fn trace(&self, message: impl AsRef<str>) {
self.reporter.println(message);
}
Expand Down
93 changes: 53 additions & 40 deletions crates/solidity/testing/sanctuary/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use infra_utils::paths::PathExtensions;
use itertools::Itertools;
use metaslang_bindings::PathResolver;
use semver::Version;
use slang_solidity::bindings::Bindings;
use slang_solidity::cst::{NonterminalKind, TextIndex};
use slang_solidity::parser::Parser;
use slang_solidity::parser::{ParseOutput, Parser};
use slang_solidity::{bindings, transform_built_ins_node};

use crate::datasets::{DataSet, SourceFile};
Expand Down Expand Up @@ -103,73 +104,50 @@ pub fn run_test(file: &SourceFile, events: &Events) -> Result<()> {

let parser = Parser::create(version.clone())?;
let output = parser.parse(NonterminalKind::SourceUnit, &source);
let source_id = file.path.strip_repo_root()?.unwrap_str();

if output.is_valid() {
let source_id = file.path.strip_repo_root()?.unwrap_str();
let mut bindings = bindings::create_with_resolver(
version.clone(),
Arc::new(SingleFileResolver {
source_id: source_id.into(),
}),
);
let built_ins_tree = parser
.parse(
NonterminalKind::SourceUnit,
bindings::get_built_ins(&version),
)
.tree();
let built_ins_cursor =
transform_built_ins_node(&built_ins_tree).cursor_with_offset(TextIndex::ZERO);

bindings.add_system_file("built_ins.sol", built_ins_cursor);
bindings.add_user_file(source_id, output.create_tree_cursor());
let mut outcome = TestOutcome::Passed;

let bindings = create_bindings(&version, source_id, &output)?;

for reference in bindings.all_references() {
if reference.get_file().is_system() {
// skip built-ins
continue;
}
// We're not interested in the exact definition a reference resolves
// to, so we lookup all of them and fail if we find none.
if reference.definitions().is_empty() {
outcome = TestOutcome::Failed;
let cursor = reference.get_cursor().unwrap();
let report = format!(
"Unresolved reference to {symbol} at {file}:{line}:{col}",
"Unresolved reference to `{symbol}` at {file}:{line}:{col}",
symbol = cursor.node().unparse(),
file = reference.get_file().get_path(),
line = cursor.text_offset().line,
col = cursor.text_offset().column,
);
events.parse_error(format!("[{version}] {report}"));
events.bindings_error(format!("[{version}] {report}"));
}
}

events.test(outcome);
return Ok(());
}

events.test(TestOutcome::Failed);
} else {
events.test(TestOutcome::Failed);

let with_color = true;
let source_id = file.path.strip_repo_root()?.unwrap_str();
let with_color = true;

for error in output.errors() {
let report = slang_solidity::diagnostic::render(error, source_id, &source, with_color);
for error in output.errors() {
let report = slang_solidity::diagnostic::render(error, source_id, &source, with_color);

events.parse_error(format!("[{version}] {report}"));
events.parse_error(format!("[{version}] {report}"));
}
}

Ok(())
}

struct SingleFileResolver {
source_id: String,
}

impl PathResolver for SingleFileResolver {
fn resolve_path(&self, _context_path: &str, _path_to_resolve: &str) -> Option<String> {
Some(self.source_id.clone())
}
}

fn extract_compiler_version(compiler: &str) -> Option<Version> {
if compiler.starts_with("vyper:") {
// Ignore contracts not compiled by "solc":
Expand Down Expand Up @@ -211,3 +189,38 @@ fn uses_exotic_parser_bug(file: &Path) -> bool {
.iter()
.any(|path| file.ends_with(path))
}

fn create_bindings(version: &Version, source_id: &str, output: &ParseOutput) -> Result<Bindings> {
let mut bindings = bindings::create_with_resolver(
version.clone(),
Arc::new(SingleFileResolver {
source_id: source_id.into(),
}),
);
let parser = Parser::create(version.clone())?;
let built_ins_tree = parser
.parse(
NonterminalKind::SourceUnit,
bindings::get_built_ins(version),
)
.tree();
let built_ins_cursor =
transform_built_ins_node(&built_ins_tree).cursor_with_offset(TextIndex::ZERO);

bindings.add_system_file("built_ins.sol", built_ins_cursor);
bindings.add_user_file(source_id, output.create_tree_cursor());
Ok(bindings)
}

/// Bindings `PathResolver` that always resolves to the given `source_id`.
/// This is useful for Sanctuary since all dependencies are concatenated in the
/// same file, but the import directives are retained.
struct SingleFileResolver {
source_id: String,
}

impl PathResolver for SingleFileResolver {
fn resolve_path(&self, _context_path: &str, _path_to_resolve: &str) -> Option<String> {
Some(self.source_id.clone())
}
}

0 comments on commit fe89f8d

Please sign in to comment.