1 /*
2  * This file is partially derived from src/lib.rs in the Rust test library, used
3  * under the Apache License, Version 2.0. The following is the original
4  * copyright information from the Rust project:
5  *
6  * Copyrights in the Rust project are retained by their contributors. No
7  * copyright assignment is required to contribute to the Rust project.
8  *
9  * Some files include explicit copyright notices and/or license notices.
10  * For full authorship information, see the version control history or
11  * https://thanks.rust-lang.org
12  *
13  * Except as otherwise noted (below and/or in individual files), Rust is
14  * licensed under the Apache License, Version 2.0 <LICENSE-APACHE> or
15  * <http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
16  * <LICENSE-MIT> or <http://opensource.org/licenses/MIT>, at your option.
17  *
18  *
19  * Licensed under the Apache License, Version 2.0 (the "License");
20  * you may not use this file except in compliance with the License.
21  * You may obtain a copy of the License at
22  *
23  *      http://www.apache.org/licenses/LICENSE-2.0
24  *
25  * Unless required by applicable law or agreed to in writing, software
26  * distributed under the License is distributed on an "AS IS" BASIS,
27  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28  * See the License for the specific language governing permissions and
29  * limitations under the License.
30  */
31 
32 //! # Trusty Rust Testing Framework
33 
34 use core::cell::RefCell;
35 use log::{Log, Metadata, Record};
36 use tipc::{
37     ConnectResult, Handle, Manager, MessageResult, PortCfg, Serialize, Serializer, Service, Uuid,
38 };
39 use trusty_log::{TrustyLogger, TrustyLoggerConfig};
40 use trusty_std::alloc::Vec;
41 
42 // Public reexports
43 pub use self::bench::Bencher;
44 pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
45 pub use self::types::TestName::*;
46 pub use self::types::*;
47 
48 pub mod asserts;
49 mod bench;
50 mod context;
51 mod macros;
52 mod options;
53 mod types;
54 
55 use context::CONTEXT;
56 
57 extern "Rust" {
58     static TEST_PORT: &'static str;
59 }
60 
61 /// Initialize a test service for this crate.
62 ///
63 /// Including an invocation of this macro exactly once is required to configure a
64 /// crate to set up the Trusty Rust test framework.
65 ///
66 /// # Examples
67 ///
68 /// ```
69 /// #[cfg(test)]
70 /// mod test {
71 ///     // Initialize the test framework
72 ///     test::init!();
73 ///
74 ///     #[test]
75 ///     fn test() {}
76 /// }
77 /// ```
78 #[macro_export]
79 macro_rules! init {
80     () => {
81         #[cfg(test)]
82         #[used]
83         #[no_mangle]
84         pub static TEST_PORT: &'static str = env!(
85             "TRUSTY_TEST_PORT",
86             "Expected TRUSTY_TEST_PORT environment variable to be set during compilation",
87         );
88     };
89 }
90 
91 // TestMessage::Message doesn't have a use yet
92 #[allow(dead_code)]
93 enum TestMessage<'m> {
94     Passed,
95     Failed,
96     Message(&'m str),
97 }
98 
99 impl<'m, 's> Serialize<'s> for TestMessage<'m> {
serialize<'a: 's, S: Serializer<'s>>( &'a self, serializer: &mut S, ) -> Result<S::Ok, S::Error>100     fn serialize<'a: 's, S: Serializer<'s>>(
101         &'a self,
102         serializer: &mut S,
103     ) -> Result<S::Ok, S::Error> {
104         match self {
105             TestMessage::Passed => serializer.serialize_bytes(&[0u8]),
106             TestMessage::Failed => serializer.serialize_bytes(&[1u8]),
107             TestMessage::Message(msg) => {
108                 serializer.serialize_bytes(&[2u8])?;
109                 serializer.serialize_bytes(msg.as_bytes())
110             }
111         }
112     }
113 }
114 
115 pub struct TrustyTestLogger {
116     stderr_logger: TrustyLogger,
117     client_connection: RefCell<Option<Handle>>,
118 }
119 
120 // SAFETY: This is not actually thread-safe, but we don't implement Mutex in
121 // Trusty's std yet.
122 unsafe impl Sync for TrustyTestLogger {}
123 
124 impl TrustyTestLogger {
new() -> Self125     const fn new() -> Self {
126         Self {
127             stderr_logger: TrustyLogger::new(TrustyLoggerConfig::new()),
128             client_connection: RefCell::new(None),
129         }
130     }
131 
132     /// Connect a new client to this logger, disconnecting the existing client,
133     /// if any.
connect(&self, handle: &Handle) -> tipc::Result<()>134     fn connect(&self, handle: &Handle) -> tipc::Result<()> {
135         let _ = self.client_connection.replace(Some(handle.try_clone()?));
136         Ok(())
137     }
138 
139     /// Disconnect the current client, if connected.
140     ///
141     /// If there is not a current client, this method does nothing.
disconnect(&self)142     fn disconnect(&self) {
143         let _ = self.client_connection.take();
144     }
145 }
146 
147 impl Log for TrustyTestLogger {
enabled(&self, metadata: &Metadata) -> bool148     fn enabled(&self, metadata: &Metadata) -> bool {
149         self.stderr_logger.enabled(metadata)
150     }
151 
log(&self, record: &Record)152     fn log(&self, record: &Record) {
153         if !self.enabled(record.metadata()) {
154             return;
155         }
156         self.stderr_logger.log(record);
157         if let Some(client) = self.client_connection.borrow().as_ref() {
158             let err = if let Some(msg) = record.args().as_str() {
159                 // avoid an allocation if message is a static str
160                 client.send(&TestMessage::Message(msg))
161             } else {
162                 let msg = format!("{}\n", record.args());
163                 client.send(&TestMessage::Message(&msg))
164             };
165             if let Err(e) = err {
166                 eprintln!("Could not send log message to test client: {:?}", e);
167             }
168         }
169     }
170 
flush(&self)171     fn flush(&self) {
172         self.stderr_logger.flush()
173     }
174 }
175 
176 static LOGGER: TrustyTestLogger = TrustyTestLogger::new();
177 
print_status(test: &TestDesc, msg: &str)178 fn print_status(test: &TestDesc, msg: &str) {
179     log::info!("[ {} ] {}", msg, test.name);
180 }
181 
182 struct TestService {
183     tests: Vec<TestDescAndFn>,
184 }
185 
186 impl Service for TestService {
187     type Connection = ();
188     type Message = ();
189 
on_connect( &self, _port: &PortCfg, handle: &Handle, _peer: &Uuid, ) -> tipc::Result<ConnectResult<Self::Connection>>190     fn on_connect(
191         &self,
192         _port: &PortCfg,
193         handle: &Handle,
194         _peer: &Uuid,
195     ) -> tipc::Result<ConnectResult<Self::Connection>> {
196         LOGGER.connect(handle)?;
197 
198         let mut passed_tests = 0;
199         let mut failed_tests = 0;
200         let mut skipped_tests = 0;
201         let mut total_ran = 0;
202         for test in &self.tests {
203             CONTEXT.reset();
204             total_ran += 1;
205             print_status(&test.desc, "RUN     ");
206             match test.testfn {
207                 StaticTestFn(f) => f(),
208                 StaticBenchFn(_f) => panic!("Test harness does not support benchmarking"),
209                 _ => panic!("non-static tests passed to test::test_main_static"),
210             }
211             if CONTEXT.skipped() {
212                 print_status(&test.desc, " SKIPPED");
213                 skipped_tests += 1;
214             } else if CONTEXT.all_ok() {
215                 print_status(&test.desc, "      OK");
216                 passed_tests += 1;
217             } else {
218                 print_status(&test.desc, " FAILED ");
219                 failed_tests += 1;
220             }
221             if CONTEXT.hard_fail() {
222                 break;
223             }
224         }
225 
226         log::info!("[==========] {} tests ran.", total_ran);
227         if passed_tests > 0 {
228             log::info!("[  PASSED  ] {} tests.", passed_tests);
229         }
230         if skipped_tests > 0 {
231             log::info!("[  SKIPPED ] {} tests.", skipped_tests);
232         }
233         if failed_tests > 0 {
234             log::info!("[  FAILED  ] {} tests.", failed_tests);
235         }
236 
237         let response = if failed_tests == 0 { TestMessage::Passed } else { TestMessage::Failed };
238         handle.send(&response)?;
239 
240         LOGGER.disconnect();
241         Ok(ConnectResult::CloseConnection)
242     }
243 
on_message( &self, _connection: &Self::Connection, _handle: &Handle, _msg: Self::Message, ) -> tipc::Result<MessageResult>244     fn on_message(
245         &self,
246         _connection: &Self::Connection,
247         _handle: &Handle,
248         _msg: Self::Message,
249     ) -> tipc::Result<MessageResult> {
250         Ok(MessageResult::CloseConnection)
251     }
252 
on_disconnect(&self, _connection: &Self::Connection)253     fn on_disconnect(&self, _connection: &Self::Connection) {
254         LOGGER.disconnect();
255     }
256 }
257 
258 /// A variant optimized for invocation with a static test vector.
259 /// This will panic (intentionally) when fed any dynamic tests.
260 ///
261 /// This is the entry point for the main function generated by `rustc --test`
262 /// when panic=abort.
test_main_static_abort(tests: &[&TestDescAndFn])263 pub fn test_main_static_abort(tests: &[&TestDescAndFn]) {
264     log::set_logger(&LOGGER).expect("Could not set global logger");
265     log::set_max_level(log::LevelFilter::Info);
266 
267     let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
268 
269     // SAFETY: This static is declared in the crate being tested, so must be
270     // external. This static should only ever be defined by the macro above.
271     let port_str = unsafe { TEST_PORT };
272 
273     let cfg = PortCfg::new(port_str)
274         .expect("Could not create port config")
275         .allow_ta_connect()
276         .allow_ns_connect();
277 
278     let test_service = TestService { tests: owned_tests };
279 
280     let buffer = [0u8; 4096];
281     Manager::<_, _, 1, 4>::new(test_service, cfg, buffer)
282         .expect("Could not create service manager")
283         .run_event_loop()
284         .expect("Test event loop failed");
285 }
286 
287 /// Clones static values for putting into a dynamic vector, which test_main()
288 /// needs to hand out ownership of tests to parallel test runners.
289 ///
290 /// This will panic when fed any dynamic tests, because they cannot be cloned.
make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn291 fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
292     match test.testfn {
293         StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: test.desc.clone() },
294         StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: test.desc.clone() },
295         _ => panic!("non-static tests passed to test::test_main_static"),
296     }
297 }
298 
299 /// Invoked when unit tests terminate. The normal Rust test harness supports
300 /// tests which return values, we don't, so we require the test to return unit.
assert_test_result(_result: ())301 pub fn assert_test_result(_result: ()) {}
302 
303 /// Skip the current test case.
skip()304 pub fn skip() {
305     CONTEXT.skip();
306 }
307