blob: 103bc2c417d6ee5c7a5be96555e2e43443b2f32f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License..
//! sgx_tunittest is for performing unit tests in enclaves.
//!
//! To use this crate, import the assertion macros defined in sgx_tstd and
//! this crate like this at first:
//!
//! ```
//! #[macro_use]
//! extern crate sgx_tstd as std;
//! #[macro_use]
//! extern crate sgx_tunittest;
//! ```
//!
//! Similar to `#[test]` in Rust, unit test functions are required
//! to take zero arguments and return nothing. One test is success
//! only when the test function returns without panic.
//!
//! Different from Rust, we don't use features like `#[test]`,
//! `#[should_panic]` for unit test function declaration. Instead,
//! to declare a unit test function, one just need implement it as normal.
//!
//! Here is a sample unit test function:
//!
//! ```
//! fn foo() {
//! assert!(true);
//! assert_eq!(1,1);
//! assert_ne!(1,0);
//! }
//! ```
//!
//! To launch the unit test, one should use the macro `rsgx_unit_test!`.
//! For example, assuming that we have three unit test functions: `foo`,
//! `bar` and `zoo`. To start the test, just write as the following:
//!
//! ```
//! rsgx_unit_tests!(foo, bar, zoo);
//! ```
//!
//! sgx_tunittest supports fail test (something must panic). But it does
//! not provide it in Rust style (#[should_panic]). One should use macro
//! `should_panic!` to assert the statement that would panic. For example:
//!
//! ```
//! fn foo_panic() {
//! let v = vec![]
//! should_panic!(vec[0]); // vec[0] would panic
//! }
//! ```
//!
//! In this way, `vec[0]` would panic. But `should_panic!` catches it. Thus
//! `foo_panic` would pass the unit test.
//!
#![cfg_attr(not(target_env = "sgx"), no_std)]
#![cfg_attr(
all(target_env = "sgx", target_vendor = "mesalock"),
feature(rustc_private)
)]
#![feature(const_fn)]
#[cfg(not(target_env = "sgx"))]
#[macro_use]
extern crate sgx_tstd as std;
use std::string::String;
use std::vec::Vec;
/// This macro implements the fail test.
///
/// For example, in traditional Rust testing, we write
///
/// ```
/// #[test]
/// #[should_panic]
/// fn foo () {
/// assert!(false);
/// }
/// ```
///
/// This test would pass because it would panic and is expected to panic
/// (`#[should_panic]`).
///
/// An equivalent version of Rust SGX unit test is:
///
/// ```
/// fn foo() {
/// should_panic!(assert!(false));
/// }
/// ```
///
/// This requires developer to identify the line which triggers panic exactly.
#[macro_export]
macro_rules! should_panic {
($fmt:expr) => {{
match ::std::panic::catch_unwind(|| $fmt).is_err() {
true => {}
false => ::std::rt::begin_panic($fmt),
}
}};
}
/// This macro works as test case driver.
///
/// `rsgx_unit_tests!` works as a variadic function. It takes a list of test
/// case function as arguments and then execute them sequentially. It prints
/// the statistics on the test result at the end, and returns the amount of
/// failed tests. meaning if everything works the return vlaue will be 0.
///
/// One test fails if and only if it panics. For fail test (similar to
/// `#[should_panic]` in Rust, one should wrap the line which would panic with
/// macro `should_panic!`.
///
/// Here is one sample. For the entire sample, please reference to the sample
/// codes in this project.
///
/// ```
/// #[macro_use]
/// extern crate sgx_tstd as std;
/// #[macro_use]
/// extern crate sgx_unittest;
///
/// #[no_mangle]
/// pub extern "C"
/// fn test_ecall() -> sgx_status_t {
/// rsgx_unit_tests!(foo, bar, zoo);
/// sgx_status_t::SGX_SUCCESS
/// }
/// ```
#[macro_export]
macro_rules! rsgx_unit_tests {
(
$($f : expr),* $(,)?
) => {
{
rsgx_unit_test_start();
let mut ntestcases : u64 = 0u64;
let mut failurecases : Vec<String> = Vec::new();
$(rsgx_unit_test(&mut ntestcases, &mut failurecases, $f,stringify!($f));)*
rsgx_unit_test_end(ntestcases, failurecases)
}
}
}
/// A prologue function for Rust SGX unit testing.
///
/// To initiate the test environment, `rsgx_unit_tests!` macro would trigger
/// `rsgx_unit_test_start` at the very beginning. `rsgx_unit_test_start` inits
/// the test counter and fail test list, and print the prologue message.
pub fn rsgx_unit_test_start() {
println!("\nstart running tests");
}
/// An epilogue function for Rust SGX unit testing.
///
/// `rsgx_unit_test_end` prints the statistics on test result, including
/// a list of failed tests and the statistics.
/// It will return the amount of failed tests. (success == 0)
pub fn rsgx_unit_test_end(ntestcases: u64, failurecases: Vec<String>) -> usize {
let ntotal = ntestcases as usize;
let nsucc = ntestcases as usize - failurecases.len();
if failurecases.len() != 0 {
print!("\nfailures: ");
println!(
" {}",
failurecases
.iter()
.fold(String::new(), |s, per| s + "\n " + per)
);
}
if ntotal == nsucc {
print!("\ntest result \x1B[1;32mok\x1B[0m. ");
} else {
print!("\ntest result \x1B[1;31mFAILED\x1B[0m. ");
}
println!(
"{} tested, {} passed, {} failed",
ntotal,
nsucc,
ntotal - nsucc
);
failurecases.len()
}
/// Perform one test case at a time.
///
/// This is the core function of sgx_tunittest. It runs one test case at a
/// time and saves the result. On test passes, it increases the passed counter
/// and on test fails, it records the failed test.
/// Required test function must be `Fn()`, taking nothing as input and returns
/// nothing.
pub fn rsgx_unit_test<F, R>(ncases: &mut u64, failurecases: &mut Vec<String>, f: F, name: &str)
where
F: FnOnce() -> R + std::panic::UnwindSafe,
{
*ncases = *ncases + 1;
match std::panic::catch_unwind(|| {
f();
})
.is_ok()
{
true => {
println!("{} {} ... {}!", "testing", name, "\x1B[1;32mok\x1B[0m");
}
false => {
println!("{} {} ... {}!", "testing", name, "\x1B[1;31mfailed\x1B[0m");
failurecases.push(String::from(name));
}
}
}