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: simplify macro testing infrastructure #1040

Merged
merged 2 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 11 additions & 9 deletions Cargo.lock

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

7 changes: 1 addition & 6 deletions ops/compile_test_runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,16 @@ authors.workspace = true
edition.workspace = true
license.workspace = true
publish = false
readme = "README.md"
repository.workspace = true
description = "Compile-test runner for deno_ops"

[lib]
path = "lib.rs"

[dev-dependencies]
bytes.workspace = true
deno_core.workspace = true
deno_error.workspace = true
deno_ops.workspace = true
pretty_assertions.workspace = true
prettyplease = "0.2.9"
rustversion = "1.0"
serde.workspace = true
syn.workspace = true
testing_macros = "0.2.11"
trybuild = "1.0"
31 changes: 31 additions & 0 deletions ops/compile_test_runner/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2018-2025 the Deno authors. MIT license.

#[macro_export]
macro_rules! prelude {
() => {
#[allow(unused_imports)]
use deno_ops::op2;
#[allow(unused_imports)]
use deno_ops::WebIDL;

pub fn main() {}
};
}

#[cfg(test)]
mod compile_tests {
#[test]
fn op2() {
let t = trybuild::TestCases::new();
t.pass("../op2/test_cases/**/*.rs");
t.pass("../op2/test_cases/compiler_pass/*.rs");
t.compile_fail("../op2/test_cases_fail/**/*.rs");
}

#[test]
fn webidl() {
let t = trybuild::TestCases::new();
t.pass("../webidl/test_cases/*.rs");
t.compile_fail("../webidl/test_cases_fail/*.rs");
}
}
70 changes: 0 additions & 70 deletions ops/compile_test_runner/src/lib.rs

This file was deleted.

52 changes: 52 additions & 0 deletions ops/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,55 @@ pub fn webidl(item: TokenStream) -> TokenStream {
Err(err) => err.into_compile_error().into(),
}
}

#[cfg(test)]
mod infra {
use std::path::PathBuf;
use syn::File;

pub fn run_macro_expansion_test<F, I>(input: PathBuf, expander: F)
where
F: FnOnce(File) -> I,
I: Iterator<Item = proc_macro2::TokenStream>,
{
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();

let source =
std::fs::read_to_string(&input).expect("Failed to read test file");

const PRELUDE: &str = r"// Copyright 2018-2025 the Deno authors. MIT license.

#![deny(warnings)]
deno_ops_compile_test_runner::prelude!();";

if !source.starts_with(PRELUDE) {
panic!("Source does not start with expected prelude:]n{PRELUDE}");
}

let file =
syn::parse_str::<File>(&source).expect("Failed to parse Rust file");

let expected_out = expander(file)
.map(|tokens| {
println!("======== Raw tokens ========:\n{}", tokens.clone());
let tree = syn::parse2(tokens).unwrap();
let actual = prettyplease::unparse(&tree);
println!("======== Generated ========:\n{}", actual);
actual
})
.collect::<Vec<String>>()
.join("\n");

if update_expected {
std::fs::write(input.with_extension("out"), expected_out)
.expect("Failed to write expectation file");
} else {
let expected = std::fs::read_to_string(input.with_extension("out"))
.expect("Failed to read expectation file");
assert_eq!(
expected, expected_out,
"Failed to match expectation. Use UPDATE_EXPECTED=1."
);
}
}
}
79 changes: 22 additions & 57 deletions ops/op2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,6 @@ mod tests {
use pretty_assertions::assert_eq;
use quote::ToTokens;
use std::path::PathBuf;
use syn::File;
use syn::Item;

fn to_attr_input(op2_attr: syn::Attribute) -> TokenStream {
Expand All @@ -385,6 +384,11 @@ mod tests {
.map(|(idx, attr)| (idx, attr.to_owned()))
}

fn expand_op2(op2_attr: syn::Attribute, item: impl ToTokens) -> TokenStream {
op2(to_attr_input(op2_attr), item.to_token_stream())
.expect("Failed to generate op")
}

#[testing_macros::fixture("op2/test_cases/sync/*.rs")]
fn test_proc_macro_sync(input: PathBuf) {
test_proc_macro_output(input)
Expand All @@ -395,67 +399,28 @@ mod tests {
test_proc_macro_output(input)
}

fn expand_op2(op2_attr: syn::Attribute, item: impl ToTokens) -> String {
let tokens = op2(to_attr_input(op2_attr), item.to_token_stream())
.expect("Failed to generate op");
println!("======== Raw tokens ========:\n{}", tokens.clone());
let tree = syn::parse2(tokens).unwrap();
let actual = prettyplease::unparse(&tree);
println!("======== Generated ========:\n{}", actual);
actual
}

fn test_proc_macro_output(input: PathBuf) {
let update_expected = std::env::var("UPDATE_EXPECTED").is_ok();

let source =
std::fs::read_to_string(&input).expect("Failed to read test file");

const PRELUDE: &str = r"// Copyright 2018-2025 the Deno authors. MIT license.

#![deny(warnings)]
deno_ops_compile_test_runner::prelude!();";

if !source.starts_with(PRELUDE) {
panic!("Source does not start with expected prelude:]n{PRELUDE}");
}

let file =
syn::parse_str::<File>(&source).expect("Failed to parse Rust file");
let mut expected_out = vec![];
for item in file.items {
match item {
Item::Fn(mut func) => {
if let Some((idx, op2_attr)) = find_op2_attr(&func.attrs) {
func.attrs.remove(idx);
let actual = expand_op2(op2_attr, func);
expected_out.push(actual);
crate::infra::run_macro_expansion_test(input, |file| {
file.items.into_iter().filter_map(|item| {
match item {
Item::Fn(mut func) => {
if let Some((idx, op2_attr)) = find_op2_attr(&func.attrs) {
func.attrs.remove(idx);
return Some(expand_op2(op2_attr, func));
}
}
}
Item::Impl(mut imp) => {
if let Some((idx, op2_attr)) = find_op2_attr(&imp.attrs) {
imp.attrs.remove(idx);
let actual = expand_op2(op2_attr, imp);
expected_out.push(actual);
Item::Impl(mut imp) => {
if let Some((idx, op2_attr)) = find_op2_attr(&imp.attrs) {
imp.attrs.remove(idx);
return Some(expand_op2(op2_attr, imp));
}
}
_ => {}
}
_ => {}
}
}

let expected_out = expected_out.join("\n");

if update_expected {
std::fs::write(input.with_extension("out"), expected_out)
.expect("Failed to write expectation file");
} else {
let expected = std::fs::read_to_string(input.with_extension("out"))
.expect("Failed to read expectation file");
assert_eq!(
expected, expected_out,
"Failed to match expectation. Use UPDATE_EXPECTED=1."
);
}
None
})
})
}

fn parse_md(md: &str, mut f: impl FnMut(&str, Vec<&str>)) {
Expand Down
6 changes: 2 additions & 4 deletions ops/op2/test_cases/sync/object_wrap.out

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

Loading
Loading