Skip to content

Commit

Permalink
starting on stable support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Dubrov committed Aug 17, 2019
1 parent 2ad10a4 commit 1763ffe
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 58 deletions.
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "datatest"
version = "0.3.5"
version = "0.4.0"
authors = ["Ivan Dubrov <[email protected]>"]
edition = "2018"
repository = "https://github.com/commure/datatest"
Expand All @@ -10,13 +10,17 @@ description = """
Data-driven tests in Rust
"""

[build-dependencies]
version_check = "0.9.1"

[dependencies]
datatest-derive = { path = "datatest-derive", version = "=0.3.5" }
datatest-derive = { path = "datatest-derive", version = "=0.4.0" }
regex = "1.0.0"
walkdir = "2.1.4"
serde = "1.0.84"
serde_yaml = "0.8.7"
yaml-rust = "0.4.2"
ctor = "0.1.10"

[dev-dependencies]
serde = { version = "1.0.84", features = ["derive"] }
Expand Down
11 changes: 11 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use version_check::Channel;

fn main() {
let is_nightly = Channel::read().map_or(false, |ch| ch.is_nightly());
if is_nightly {
println!("cargo:rustc-cfg=feature=\"nightly\"");
} else {
println!("cargo:rustc-cfg=feature=\"stable\"");
}
println!("cargo:rustc-env=RUSTC_BOOTSTRAP=1");
}
2 changes: 1 addition & 1 deletion datatest-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "datatest-derive"
version = "0.3.5"
version = "0.4.0"
authors = ["Ivan Dubrov <[email protected]>"]
edition = "2018"
repository = "https://github.com/commure/datatest"
Expand Down
87 changes: 68 additions & 19 deletions datatest-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ impl Parse for FilesTestArgs {
}
}

enum Channel {
Stable,
Nightly,
}

/// Wrapper that turns on behavior that works on stable Rust.
#[proc_macro_attribute]
pub fn files_stable(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
files_internal(args, func, Channel::Stable)
}

/// Wrapper that turns on behavior that works only on nightly Rust.
#[proc_macro_attribute]
pub fn files_nightly(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
files_internal(args, func, Channel::Nightly)
}

/// Proc macro handling `#[files(...)]` syntax. This attribute defines rules for deriving
/// test function arguments from file paths. There are two types of rules:
/// 1. Pattern rule, `<arg_name> in "<regexp>"`
Expand Down Expand Up @@ -131,11 +154,10 @@ impl Parse for FilesTestArgs {
/// I could have made this proc macro to handle these cases explicitly and generate a different
/// code, but I decided to not add a complexity of type analysis to the proc macro and use traits
/// instead. See `datatest::TakeArg` and `datatest::DeriveArg` to see how this mechanism works.
#[proc_macro_attribute]
#[allow(clippy::needless_pass_by_value)]
pub fn files(
fn files_internal(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
channel: Channel,
) -> proc_macro::TokenStream {
let mut func_item = parse_macro_input!(func as ItemFn);
let args: FilesTestArgs = parse_macro_input!(args as FilesTestArgs);
Expand Down Expand Up @@ -195,7 +217,7 @@ pub fn files(

params.push(arg.value.value());
invoke_args.push(quote! {
::datatest::TakeArg::take(&mut <#ty as ::datatest::DeriveArg>::derive(&paths_arg[#idx]))
::datatest::__internal::TakeArg::take(&mut <#ty as ::datatest::__internal::DeriveArg>::derive(&paths_arg[#idx]))
})
} else {
return Error::new(pat_ident.span(), "mapping is not defined for the argument")
Expand Down Expand Up @@ -231,31 +253,33 @@ pub fn files(
let orig_func_name = &func_item.ident;

let (kind, bencher_param) = if info.bench {
(quote!(BenchFn), quote!(bencher: &mut ::datatest::Bencher,))
(quote!(BenchFn), quote!(bencher: &mut ::datatest::__internal::Bencher,))
} else {
(quote!(TestFn), quote!())
};

let registration = test_registration(channel);

// Adding `#[allow(unused_attributes)]` to `#orig_func` to allow `#[ignore]` attribute
let output = quote! {
#[test_case]
#registration
#[automatically_derived]
#[allow(non_upper_case_globals)]
static #desc_ident: ::datatest::FilesTestDesc = ::datatest::FilesTestDesc {
static #desc_ident: ::datatest::__internal::FilesTestDesc = ::datatest::__internal::FilesTestDesc {
name: concat!(module_path!(), "::", #func_name_str),
ignore: #ignore,
root: #root,
params: &[#(#params),*],
pattern: #pattern_idx,
ignorefn: #ignore_func_ref,
testfn: ::datatest::FilesTestFn::#kind(#trampoline_func_ident),
testfn: ::datatest::__internal::FilesTestFn::#kind(#trampoline_func_ident),
};

#[automatically_derived]
#[allow(non_snake_case)]
fn #trampoline_func_ident(#bencher_param paths_arg: &[::std::path::PathBuf]) {
let result = #orig_func_name(#(#invoke_args),*);
datatest::assert_test_result(result);
::datatest::__internal::assert_test_result(result);
}

#func_item
Expand Down Expand Up @@ -323,16 +347,34 @@ impl Parse for DataTestArgs {
}
}


/// Wrapper that turns on behavior that works on stable Rust.
#[proc_macro_attribute]
pub fn data_stable(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
files_internal(args, func, Channel::Stable)
}

/// Wrapper that turns on behavior that works only on nightly Rust.
#[proc_macro_attribute]
#[allow(clippy::needless_pass_by_value)]
pub fn data(
pub fn data_nightly(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
data_internal(args, func, Channel::Nightly)
}

fn data_internal(
args: proc_macro::TokenStream,
func: proc_macro::TokenStream,
channel: Channel,
) -> proc_macro::TokenStream {
let mut func_item = parse_macro_input!(func as ItemFn);
let cases: DataTestArgs = parse_macro_input!(args as DataTestArgs);
let cases = match cases {
DataTestArgs::Literal(path) => quote!(datatest::yaml(#path)),
DataTestArgs::Literal(path) => quote!(::datatest::yaml(#path)),
DataTestArgs::Expression(expr) => quote!(#expr),
};

Expand Down Expand Up @@ -376,23 +418,25 @@ pub fn data(

let (case_ctor, bencher_param, bencher_arg) = if info.bench {
(
quote!(::datatest::DataTestFn::BenchFn(Box::new(::datatest::DataBenchFn(#trampoline_func_ident, case)))),
quote!(bencher: &mut ::datatest::Bencher,),
quote!(::datatest::__internal::DataTestFn::BenchFn(Box::new(::datatest::__internal::DataBenchFn(#trampoline_func_ident, case)))),
quote!(bencher: &mut ::datatest::__internal::Bencher,),
quote!(bencher,),
)
} else {
(
quote!(::datatest::DataTestFn::TestFn(Box::new(move || #trampoline_func_ident(case)))),
quote!(::datatest::__internal::DataTestFn::TestFn(Box::new(move || #trampoline_func_ident(case)))),
quote!(),
quote!(),
)
};

let registration = test_registration(channel);

let output = quote! {
#[test_case]
#registration
#[automatically_derived]
#[allow(non_upper_case_globals)]
static #desc_ident: ::datatest::DataTestDesc = ::datatest::DataTestDesc {
static #desc_ident: ::datatest::__internal::DataTestDesc = ::datatest::__internal::DataTestDesc {
name: concat!(module_path!(), "::", #func_name_str),
ignore: #ignore,
describefn: #describe_func_ident,
Expand All @@ -402,12 +446,12 @@ pub fn data(
#[allow(non_snake_case)]
fn #trampoline_func_ident(#bencher_param arg: #ty) {
let result = #orig_func_ident(#bencher_arg #ref_token arg);
datatest::assert_test_result(result);
::datatest::__internal::assert_test_result(result);
}

#[automatically_derived]
#[allow(non_snake_case)]
fn #describe_func_ident() -> Vec<::datatest::DataTestCaseDesc<::datatest::DataTestFn>> {
fn #describe_func_ident() -> Vec<::datatest::DataTestCaseDesc<::datatest::__internal::DataTestFn>> {
let result = #cases
.into_iter()
.map(|input| {
Expand All @@ -427,3 +471,8 @@ pub fn data(
};
output.into()
}


fn test_registration(channel: Channel) -> TokenStream {
quote!(#[test_case])
}
20 changes: 13 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,22 @@ mod files;
mod runner;

#[doc(hidden)]
pub use crate::data::{DataBenchFn, DataTestDesc, DataTestFn};
#[doc(hidden)]
pub use crate::files::{DeriveArg, FilesTestDesc, FilesTestFn, TakeArg};
#[doc(hidden)]
pub use crate::runner::{assert_test_result, runner};
pub mod __internal {
pub use crate::data::{DataBenchFn, DataTestDesc, DataTestFn};
pub use crate::files::{DeriveArg, FilesTestDesc, FilesTestFn, TakeArg};
pub use crate::runner::assert_test_result;
pub use crate::test::Bencher;
}

pub use crate::runner::runner;

#[doc(hidden)]
pub use crate::test::Bencher;
#[cfg(feature = "stable")]
pub use datatest_derive::{data_stable as data, files_stable as files};

#[doc(hidden)]
pub use datatest_derive::{data, files};
#[cfg(feature = "nightly")]
pub use datatest_derive::{data_nightly as data, files_nightly as files};

/// Experimental functionality.
#[doc(hidden)]
Expand Down
30 changes: 22 additions & 8 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ fn render_data_test(desc: &DataTestDesc, rendered: &mut Vec<TestDescAndFn>) {
};

let testfn = match case.case {
DataTestFn::TestFn(testfn) => TestFn::DynTestFn(Box::new(|| testfn())),
DataTestFn::TestFn(testfn) => TestFn::DynTestFn(testfn),
DataTestFn::BenchFn(benchfn) => TestFn::DynBenchFn(benchfn),
};

Expand Down Expand Up @@ -286,13 +286,27 @@ pub fn runner(tests: &[&dyn TestDescriptor]) {
}
}

pub trait Termination {
fn is_success(&self) -> bool;
}

impl Termination for () {
fn is_success(&self) -> bool {
true
}
}

impl <T, E> Termination for Result<T, E> {
fn is_success(&self) -> bool {
self.is_ok()
}
}

#[doc(hidden)]
pub fn assert_test_result<T: std::process::Termination>(result: T) {
let code = result.report();
assert_eq!(
code, 0,
"the test returned a termination value with a non-zero status code ({}) \
which indicates a failure",
code
pub fn assert_test_result<T: Termination>(result: T) {
assert!(
result.is_success(),
"the test returned a termination value with a non-zero status code (255) \
which indicates a failure"
);
}
29 changes: 8 additions & 21 deletions tests/datatest.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![feature(custom_test_frameworks)]
#![test_runner(datatest::runner)]
#![cfg_attr(feature = "nightly", feature(custom_test_frameworks))]
#![cfg_attr(feature = "nightly", test_runner(datatest::runner))]

use serde::Deserialize;
use std::fmt;
Expand Down Expand Up @@ -109,6 +109,12 @@ struct GreeterTestCase {
expected: String,
}

impl fmt::Display for GreeterTestCase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.name)
}
}

/// Data-driven tests are defined via `#[datatest::data(..)]` attribute.
///
/// This attribute specifies a test file with test cases. Currently, the test file have to be in
Expand Down Expand Up @@ -141,25 +147,6 @@ fn data_test_line_only_hoplessly_broken(_data: &GreeterTestCase) {
panic!("this test always fails, but this is okay because we marked it as ignored!")
}

/// This test case item implements [`std::fmt::Display`], which is used to generate test name
#[derive(Deserialize)]
struct GreeterTestCaseNamed {
name: String,
expected: String,
}

impl fmt::Display for GreeterTestCaseNamed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.name)
}
}

#[datatest::data("tests/tests.yaml")]
#[test]
fn data_test_name_and_line(data: &GreeterTestCaseNamed) {
assert_eq!(data.expected, format!("Hi, {}!", data.name));
}

/// Can also take string inputs
#[datatest::data("tests/strings.yaml")]
#[test]
Expand Down
1 change: 1 addition & 0 deletions tests/unicode.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![cfg(feature = "nightly")]
#![feature(non_ascii_idents)]
#![feature(custom_test_frameworks)]
#![test_runner(datatest::runner)]
Expand Down

0 comments on commit 1763ffe

Please sign in to comment.