Skip to content

Commit ad23a58

Browse files
authored
feature: Macro for fixture testing (#1226)
testing_macros: - Add `#[fixture]` for easier fixture testing
1 parent 512153f commit ad23a58

File tree

16 files changed

+292
-4
lines changed

16 files changed

+292
-4
lines changed

.github/workflows/integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Set platform name
2929
run: |
3030
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())")
31-
echo "name=PLATFORM_NAME::$NODE_PLATFORM_NAME" >> $GITHUB_ENV
31+
echo "PLATFORM_NAME=$NODE_PLATFORM_NAME" >> $GITHUB_ENV
3232
shell: bash
3333

3434
- name: Prepare

.github/workflows/publish-node.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ jobs:
129129
node-version: 12
130130

131131
- name: Set release name
132-
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}"" >> $GITHUB_ENV
132+
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
133133

134134
# Do not cache node_modules, or yarn workspace links broken
135135
- name: Install dependencies

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ script:
4343
- true
4444

4545
before_deploy:
46-
- CARGO_TARGET_DIR=$HOME/cargo-target cargo doc --color always
46+
- CARGO_TARGET_DIR=$HOME/cargo-target cargo doc --all --exclude node --color always
4747

4848
deploy:
4949
local_dir: $HOME/cargo-target/doc

testing/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2018"
66
license = "Apache-2.0/MIT"
77
name = "testing"
88
repository = "https://github.com/swc-project/swc.git"
9-
version = "0.10.1"
9+
version = "0.10.2"
1010

1111
[dependencies]
1212
ansi_term = "0.12.1"
@@ -17,3 +17,4 @@ once_cell = "1"
1717
pretty_assertions = "0.6.1"
1818
regex = "1"
1919
swc_common = {version = "0.10.0", path = "../common", features = ["tty-emitter"]}
20+
testing_macros = {version = "0.1", path = "./macros"}

testing/macros/Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
3+
description = "General purpose testing macros"
4+
documentation = "https://swc.rs/rustdoc/testing_macros/"
5+
edition = "2018"
6+
license = "Apache-2.0/MIT"
7+
name = "testing_macros"
8+
repository = "https://github.com/swc-project/swc.git"
9+
version = "0.1.0"
10+
11+
[lib]
12+
proc-macro = true
13+
14+
[dependencies]
15+
anyhow = "1"
16+
glob = "0.3"
17+
pmutil = "0.5.1"
18+
proc-macro2 = "1.0.24"
19+
quote = "1"
20+
regex = "1"
21+
relative-path = "1.3.2"
22+
syn = {version = "1", features = ["fold", "parsing", "full", "extra-traits"]}

testing/macros/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# testing_macros
2+
3+
Please refer to [rustdoc](https://swc.rs/rustdoc/testing_macros)

testing/macros/src/fixture.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
use anyhow::{Context, Error};
2+
use glob::glob;
3+
use pmutil::q;
4+
use proc_macro2::{SourceFile, Span};
5+
use regex::Regex;
6+
use relative_path::RelativePath;
7+
use syn::{
8+
parse::{Parse, ParseStream},
9+
Ident, ItemFn, Lit, LitStr, Meta, NestedMeta, Token,
10+
};
11+
12+
pub struct Config {
13+
pattern: String,
14+
exclude_patterns: Vec<Regex>,
15+
}
16+
17+
impl Parse for Config {
18+
fn parse(input: ParseStream) -> syn::Result<Self> {
19+
fn update(c: &mut Config, meta: Meta) {
20+
match &meta {
21+
Meta::List(list) => {
22+
if list
23+
.path
24+
.get_ident()
25+
.map(|i| i.to_string() == "exclude")
26+
.unwrap_or(false)
27+
{
28+
//
29+
macro_rules! fail {
30+
() => {{
31+
fail!("invalid input to the attribute")
32+
}};
33+
($inner:expr) => {{
34+
panic!(
35+
"{}\nnote: exclude() expectes one or more comma-separated \
36+
regular expressions, like exclude(\".*\\\\.d\\\\.ts\") or \
37+
exclude(\".*\\\\.d\\\\.ts\", \".*\\\\.tsx\")",
38+
$inner
39+
)
40+
}};
41+
}
42+
43+
if list.nested.is_empty() {
44+
fail!("empty exlclude()")
45+
}
46+
47+
for token in list.nested.iter() {
48+
match token {
49+
NestedMeta::Meta(_) => fail!(),
50+
NestedMeta::Lit(lit) => {
51+
let lit = match lit {
52+
Lit::Str(v) => v.value(),
53+
_ => fail!(),
54+
};
55+
c.exclude_patterns.push(Regex::new(&lit).unwrap_or_else(
56+
|err| {
57+
fail!(format!(
58+
"failed to parse regex: {}\n{}",
59+
lit, err
60+
))
61+
},
62+
));
63+
}
64+
}
65+
}
66+
67+
return;
68+
}
69+
}
70+
_ => {}
71+
}
72+
73+
let expected = r#"#[fixture("fixture/**/*.ts", exclude("*\.d\.ts"))]"#;
74+
75+
unimplemented!(
76+
"Exected something like {}\nGot wrong meta tag: {:?}",
77+
expected,
78+
meta,
79+
)
80+
}
81+
82+
let pattern: LitStr = input.parse()?;
83+
let pattern = pattern.value();
84+
85+
let mut config = Self {
86+
pattern,
87+
exclude_patterns: vec![],
88+
};
89+
90+
let comma: Option<Token![,]> = input.parse()?;
91+
if comma.is_some() {
92+
let meta: Meta = input.parse()?;
93+
update(&mut config, meta);
94+
}
95+
96+
Ok(config)
97+
}
98+
}
99+
100+
pub fn expand(test_file: &SourceFile, callee: &Ident, attr: Config) -> Result<Vec<ItemFn>, Error> {
101+
let base_dir = test_file.path().parent().unwrap().to_path_buf();
102+
let resolved_path = RelativePath::new(&attr.pattern).to_path(&base_dir);
103+
let pattern = resolved_path.to_string_lossy();
104+
105+
let paths =
106+
glob(&pattern).with_context(|| format!("glob failed for whole path: `{}`", pattern))?;
107+
let mut test_fns = vec![];
108+
109+
'add: for path in paths {
110+
let path = path.with_context(|| format!("glob failed for file"))?;
111+
let path = path.strip_prefix(&base_dir).with_context(|| {
112+
format!(
113+
"Failed to strip prefix `{}` from `{}`",
114+
base_dir.display(),
115+
path.display()
116+
)
117+
})?;
118+
let path_str = path.to_string_lossy();
119+
// Skip excluded files
120+
for pattern in &attr.exclude_patterns {
121+
if pattern.is_match(&path_str) {
122+
continue 'add;
123+
}
124+
}
125+
126+
let ignored = path_str.starts_with(".") || path_str.contains("/.");
127+
let test_name = format!(
128+
"{}_{}",
129+
callee,
130+
path_str
131+
.replace("/", "__")
132+
.replace(".", "_")
133+
.replace("-", "_")
134+
)
135+
.replace("___", "__");
136+
let test_ident = Ident::new(&test_name, Span::call_site());
137+
138+
let mut f = q!(
139+
Vars {
140+
test_ident,
141+
path_str,
142+
callee
143+
},
144+
{
145+
#[test]
146+
#[ignore]
147+
fn test_ident() {
148+
callee(::std::path::PathBuf::from(path_str));
149+
}
150+
}
151+
)
152+
.parse::<ItemFn>();
153+
154+
if !ignored {
155+
f.attrs.retain(|attr| {
156+
match attr.path.get_ident() {
157+
Some(name) => {
158+
if name == "ignore" {
159+
return false;
160+
}
161+
}
162+
None => {}
163+
}
164+
165+
true
166+
});
167+
//
168+
}
169+
170+
test_fns.push(f);
171+
}
172+
173+
Ok(test_fns)
174+
}

testing/macros/src/lib.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::Span;
3+
use quote::ToTokens;
4+
use syn::ItemFn;
5+
6+
mod fixture;
7+
8+
/// Create tests from files.
9+
///
10+
/// # Why
11+
///
12+
/// If you create test dynamically, running a specific test become very
13+
/// cumbersome.
14+
///
15+
/// For example, if you use `test` crate with nightly, you can't use `cargo test
16+
/// foo` to run tests with name containing `foo`. Instead, you have to implement
17+
/// your own ignoring logic
18+
///
19+
///
20+
/// # Usage
21+
///
22+
/// If you want to load all typescript files from `pass`
23+
///
24+
/// ```rust,ignore
25+
///
26+
/// #[fixture("pass/**/*.{ts,tsx}")]
27+
/// fn pass(file: PathBuf) {
28+
/// // test by reading file
29+
/// }
30+
/// ```
31+
///
32+
/// # Return value
33+
///
34+
/// The function is allowed to return `Result` on error. If it's the case
35+
///
36+
///
37+
/// ## Ignoreing a test
38+
///
39+
/// If the path to the file contains a component starts with `.` (dot), it will
40+
/// be ignore. This convention is widely used in many projects (including other
41+
/// languages), as a file or a directory starting with `.` means hidden file in
42+
/// unix system.
43+
///
44+
/// Note that they are added as a test `#[ignore]`, so you can use
45+
/// `cargo test -- --ignored` or `cargo test -- --include-ignored` to run those
46+
/// tests.
47+
///
48+
///
49+
/// # Roadmap
50+
///
51+
/// - Support async function
52+
#[proc_macro_attribute]
53+
pub fn fixture(attr: TokenStream, item: TokenStream) -> TokenStream {
54+
let item: ItemFn = syn::parse(item).expect("failed to parse input as a function item");
55+
let config: self::fixture::Config =
56+
syn::parse(attr).expect("failed to parse input passed to #[fixture]");
57+
58+
let file = Span::call_site().source_file();
59+
60+
let cases = self::fixture::expand(&file, &item.sig.ident, config).unwrap();
61+
62+
let mut output = proc_macro2::TokenStream::new();
63+
64+
for case in cases {
65+
case.to_tokens(&mut output);
66+
}
67+
68+
item.to_tokens(&mut output);
69+
70+
output.into()
71+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

testing/macros/tests/ignore/a.ts

Whitespace-only changes.

testing/macros/tests/ignore/b.ts

Whitespace-only changes.

testing/macros/tests/ignore/nested/.ignored.ts

Whitespace-only changes.

testing/macros/tests/simple/a.ts

Whitespace-only changes.

testing/macros/tests/simple/b.tsx

Whitespace-only changes.

testing/macros/tests/test.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use std::path::PathBuf;
2+
use testing_macros::fixture;
3+
4+
#[fixture("simple/*.ts")]
5+
fn simple(_path: PathBuf) {}
6+
7+
#[fixture("ignore/**/*.ts")]
8+
fn ignored(_path: PathBuf) {}
9+
10+
#[fixture("simple/**/*.ts")]
11+
#[fixture("simple/**/*.tsx")]
12+
fn multiple(_path: PathBuf) {}
13+
14+
#[fixture("simple/**/*", exclude(".*\\.tsx", ".*.d\\.ts"))]
15+
fn exlucde(_path: PathBuf) {}

testing/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use swc_common::{
1818
sync::Lrc,
1919
FilePathMapping, SourceMap,
2020
};
21+
pub use testing_macros::fixture;
2122

2223
#[macro_use]
2324
mod macros;

0 commit comments

Comments
 (0)