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 libc::{clock_gettime, CLOCK_BOOTTIME};
36 use log::{Log, Metadata, Record};
37 use tipc::{
38     ConnectResult, Handle, Manager, MessageResult, PortCfg, Serialize, Serializer, Service, Uuid,
39 };
40 use trusty_log::{TrustyLogger, TrustyLoggerConfig};
41 use trusty_std::alloc::Vec;
42 
43 // Public reexports
44 pub use self::bench::Bencher;
45 pub use self::options::{ColorConfig, Options, OutputFormat, RunIgnored, ShouldPanic};
46 pub use self::types::TestName::*;
47 pub use self::types::*;
48 
49 pub mod asserts;
50 mod bench;
51 mod context;
52 mod macros;
53 mod options;
54 mod stats;
55 mod types;
56 
57 use context::CONTEXT;
58 
59 extern "Rust" {
60     static TEST_PORT: &'static str;
61 }
62 
get_time_ns() -> u6463 fn get_time_ns() -> u64 {
64     let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
65 
66     // Safety: Passing valid pointer to variable ts which lives past end of call
67     unsafe { clock_gettime(CLOCK_BOOTTIME, &mut ts) };
68 
69     ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
70 }
71 
72 /// Initialize a test service for this crate.
73 ///
74 /// Including an invocation of this macro exactly once is required to configure a
75 /// crate to set up the Trusty Rust test framework.
76 ///
77 /// # Examples
78 ///
79 /// ```
80 /// #[cfg(test)]
81 /// mod test {
82 ///     // Initialize the test framework
83 ///     test::init!();
84 ///
85 ///     #[test]
86 ///     fn test() {}
87 /// }
88 /// ```
89 #[macro_export]
90 macro_rules! init {
91     () => {
92         #[cfg(test)]
93         #[used]
94         #[no_mangle]
95         pub static TEST_PORT: &'static str = env!(
96             "TRUSTY_TEST_PORT",
97             "Expected TRUSTY_TEST_PORT environment variable to be set during compilation",
98         );
99     };
100 }
101 
102 // TestMessage::Message doesn't have a use yet
103 #[allow(dead_code)]
104 enum TestMessage<'m> {
105     Passed,
106     Failed,
107     Message(&'m str),
108 }
109 
110 impl<'m, 's> Serialize<'s> for TestMessage<'m> {
serialize<'a: 's, S: Serializer<'s>>( &'a self, serializer: &mut S, ) -> Result<S::Ok, S::Error>111     fn serialize<'a: 's, S: Serializer<'s>>(
112         &'a self,
113         serializer: &mut S,
114     ) -> Result<S::Ok, S::Error> {
115         match self {
116             TestMessage::Passed => serializer.serialize_bytes(&[0u8]),
117             TestMessage::Failed => serializer.serialize_bytes(&[1u8]),
118             TestMessage::Message(msg) => {
119                 serializer.serialize_bytes(&[2u8])?;
120                 serializer.serialize_bytes(msg.as_bytes())
121             }
122         }
123     }
124 }
125 
126 pub struct TrustyTestLogger {
127     stderr_logger: TrustyLogger,
128     client_connection: RefCell<Option<Handle>>,
129 }
130 
131 // SAFETY: This is not actually thread-safe, but we don't implement Mutex in
132 // Trusty's std yet.
133 unsafe impl Sync for TrustyTestLogger {}
134 
135 impl TrustyTestLogger {
new() -> Self136     const fn new() -> Self {
137         Self {
138             stderr_logger: TrustyLogger::new(TrustyLoggerConfig::new()),
139             client_connection: RefCell::new(None),
140         }
141     }
142 
143     /// Connect a new client to this logger, disconnecting the existing client,
144     /// if any.
connect(&self, handle: &Handle) -> tipc::Result<()>145     fn connect(&self, handle: &Handle) -> tipc::Result<()> {
146         let _ = self.client_connection.replace(Some(handle.try_clone()?));
147         Ok(())
148     }
149 
150     /// Disconnect the current client, if connected.
151     ///
152     /// If there is not a current client, this method does nothing.
disconnect(&self)153     fn disconnect(&self) {
154         let _ = self.client_connection.take();
155     }
156 }
157 
158 impl Log for TrustyTestLogger {
enabled(&self, metadata: &Metadata) -> bool159     fn enabled(&self, metadata: &Metadata) -> bool {
160         self.stderr_logger.enabled(metadata)
161     }
162 
log(&self, record: &Record)163     fn log(&self, record: &Record) {
164         if !self.enabled(record.metadata()) {
165             return;
166         }
167         self.stderr_logger.log(record);
168         if let Some(client) = self.client_connection.borrow().as_ref() {
169             let err = if let Some(msg) = record.args().as_str() {
170                 // avoid an allocation if message is a static str
171                 client.send(&TestMessage::Message(msg))
172             } else {
173                 let msg = format!("{}\n", record.args());
174                 client.send(&TestMessage::Message(&msg))
175             };
176             if let Err(e) = err {
177                 eprintln!("Could not send log message to test client: {:?}", e);
178             }
179         }
180     }
181 
flush(&self)182     fn flush(&self) {
183         self.stderr_logger.flush()
184     }
185 }
186 
187 static LOGGER: TrustyTestLogger = TrustyTestLogger::new();
188 
print_status(test: &TestDesc, msg: &str)189 fn print_status(test: &TestDesc, msg: &str) {
190     log::info!("[ {} ] {}", msg, test.name);
191 }
192 
print_status_with_duration(test: &TestDesc, msg: &str, duration_ms: u64)193 fn print_status_with_duration(test: &TestDesc, msg: &str, duration_ms: u64) {
194     log::info!("[ {} ] {} ({} ms)", msg, test.name, duration_ms);
195 }
196 
197 struct TestService {
198     tests: Vec<TestDescAndFn>,
199 }
200 
201 #[cfg(not(feature = "machine_readable"))]
print_samples(_test: &TestDesc, bs: &bench::BenchSamples)202 fn print_samples(_test: &TestDesc, bs: &bench::BenchSamples) {
203     use core::fmt::Write;
204 
205     struct FmtCounter {
206         chars: usize,
207     }
208 
209     impl FmtCounter {
210         fn new() -> FmtCounter {
211             FmtCounter { chars: 0 }
212         }
213     }
214 
215     impl core::fmt::Write for FmtCounter {
216         fn write_str(&mut self, s: &str) -> core::fmt::Result {
217             self.chars += s.chars().count();
218             Ok(())
219         }
220     }
221 
222     let min = bs.ns_iter_summ.min as u64;
223     let avg = bs.ns_iter_summ.mean as u64;
224     let max = bs.ns_iter_summ.max as u64;
225     let cold = bs.ns_iter_summ.cold as u64;
226 
227     let mut min_fc = FmtCounter::new();
228     if let Err(_) = core::write!(min_fc, "{}", min) {
229         return;
230     }
231     let mut avg_fc = FmtCounter::new();
232     if let Err(_) = core::write!(avg_fc, "{}", avg) {
233         return;
234     }
235     let mut max_fc = FmtCounter::new();
236     if let Err(_) = core::write!(max_fc, "{}", max) {
237         return;
238     }
239     let mut cold_fc = FmtCounter::new();
240     if let Err(_) = core::write!(cold_fc, "{}", cold) {
241         return;
242     }
243     log::info!(
244         "{:-<width$}",
245         "-",
246         width = min_fc.chars + avg_fc.chars + max_fc.chars + cold_fc.chars + 16
247     );
248     log::info!(
249         "|Metric    |{:minw$}|{:avgw$}|{:maxw$}|{:coldw$}|",
250         "Min",
251         "Avg",
252         "Max",
253         "Cold",
254         minw = min_fc.chars,
255         avgw = avg_fc.chars,
256         maxw = max_fc.chars,
257         coldw = cold_fc.chars
258     );
259     log::info!(
260         "{:-<width$}",
261         "-",
262         width = min_fc.chars + avg_fc.chars + max_fc.chars + cold_fc.chars + 16
263     );
264     log::info!("|time_nanos|{:3}|{:3}|{:3}|{:4}|", min, avg, max, cold);
265     log::info!(
266         "{:-<width$}",
267         "-",
268         width = min_fc.chars + avg_fc.chars + max_fc.chars + cold_fc.chars + 16
269     );
270 }
271 
272 #[cfg(feature = "machine_readable")]
print_samples(test: &TestDesc, bs: &bench::BenchSamples)273 fn print_samples(test: &TestDesc, bs: &bench::BenchSamples) {
274     let min = bs.ns_iter_summ.min as u64;
275     let avg = bs.ns_iter_summ.mean as u64;
276     let max = bs.ns_iter_summ.max as u64;
277     let cold = bs.ns_iter_summ.cold as u64;
278 
279     let (suite, bench) =
280         test.name.as_slice().rsplit_once("::").unwrap_or((test.name.as_slice(), ""));
281     log::info!("{{\"schema_version\": 3,");
282     log::info!("\"suite_name\": \"{}\",", suite);
283     log::info!("\"bench_name\": \"{}\",", bench);
284     log::info!(
285         "\"results\": \
286         [{{\"metric_name\": \"time_nanos\", \
287         \"min\": \"{}\", \
288         \"max\": \"{}\", \
289         \"avg\": \"{}\", \
290         \"cold\": \"{}\", \
291         \"raw_min\": {}, \
292         \"raw_max\": {}, \
293         \"raw_avg\": {}, \
294         \"raw_cold\": {}}}",
295         min,
296         max,
297         avg,
298         cold,
299         min,
300         max,
301         avg,
302         cold,
303     );
304     log::info!("]}}");
305 }
306 
307 impl Service for TestService {
308     type Connection = ();
309     type Message = ();
310 
on_connect( &self, _port: &PortCfg, handle: &Handle, _peer: &Uuid, ) -> tipc::Result<ConnectResult<Self::Connection>>311     fn on_connect(
312         &self,
313         _port: &PortCfg,
314         handle: &Handle,
315         _peer: &Uuid,
316     ) -> tipc::Result<ConnectResult<Self::Connection>> {
317         LOGGER.connect(handle)?;
318 
319         log::info!("[==========] Running {} tests from 1 test suite.\n", self.tests.len());
320 
321         let mut passed_tests = 0;
322         let mut failed_tests = 0;
323         let mut skipped_tests = 0;
324         let mut total_ran = 0;
325         let mut total_duration_ms = 0;
326         for test in &self.tests {
327             CONTEXT.reset();
328             total_ran += 1;
329             print_status(&test.desc, "RUN     ");
330             let start_time_ns = get_time_ns();
331             match test.testfn {
332                 StaticTestFn(f) => f(),
333                 StaticBenchFn(f) => {
334                     let bs = bench::benchmark(|harness| f(harness));
335                     print_samples(&test.desc, &bs);
336                 }
337                 _ => panic!("non-static tests passed to test::test_main_static"),
338             }
339             let duration_ms = (get_time_ns() - start_time_ns) / 1_000_000u64;
340             total_duration_ms += duration_ms;
341             if CONTEXT.skipped() {
342                 print_status_with_duration(&test.desc, " SKIPPED", duration_ms);
343                 skipped_tests += 1;
344             } else if CONTEXT.all_ok() {
345                 print_status_with_duration(&test.desc, "      OK", duration_ms);
346                 passed_tests += 1;
347             } else {
348                 print_status_with_duration(&test.desc, " FAILED ", duration_ms);
349                 failed_tests += 1;
350             }
351             if CONTEXT.hard_fail() {
352                 break;
353             }
354         }
355 
356         log::info!("[==========] {} tests ran ({} ms total).", total_ran, total_duration_ms);
357         if passed_tests > 0 {
358             log::info!("[  PASSED  ] {} tests.", passed_tests);
359         }
360         if skipped_tests > 0 {
361             log::info!("[  SKIPPED ] {} tests.", skipped_tests);
362         }
363         if failed_tests > 0 {
364             log::info!("[  FAILED  ] {} tests.", failed_tests);
365         }
366 
367         let response = if failed_tests == 0 { TestMessage::Passed } else { TestMessage::Failed };
368         handle.send(&response)?;
369 
370         LOGGER.disconnect();
371         Ok(ConnectResult::CloseConnection)
372     }
373 
on_message( &self, _connection: &Self::Connection, _handle: &Handle, _msg: Self::Message, ) -> tipc::Result<MessageResult>374     fn on_message(
375         &self,
376         _connection: &Self::Connection,
377         _handle: &Handle,
378         _msg: Self::Message,
379     ) -> tipc::Result<MessageResult> {
380         Ok(MessageResult::CloseConnection)
381     }
382 
on_disconnect(&self, _connection: &Self::Connection)383     fn on_disconnect(&self, _connection: &Self::Connection) {
384         LOGGER.disconnect();
385     }
386 }
387 
388 /// A variant optimized for invocation with a static test vector.
389 /// This will panic (intentionally) when fed any dynamic tests.
390 ///
391 /// This is the entry point for the main function generated by `rustc --test`
392 /// when panic=abort.
test_main_static_abort(tests: &[&TestDescAndFn])393 pub fn test_main_static_abort(tests: &[&TestDescAndFn]) {
394     log::set_logger(&LOGGER).expect("Could not set global logger");
395     log::set_max_level(log::LevelFilter::Info);
396 
397     let owned_tests: Vec<_> = tests.iter().map(make_owned_test).collect();
398 
399     // SAFETY: This static is declared in the crate being tested, so must be
400     // external. This static should only ever be defined by the macro above.
401     let port_str = unsafe { TEST_PORT };
402 
403     let cfg = PortCfg::new(port_str)
404         .expect("Could not create port config")
405         .allow_ta_connect()
406         .allow_ns_connect();
407 
408     let test_service = TestService { tests: owned_tests };
409 
410     let buffer = [0u8; 4096];
411     Manager::<_, _, 1, 4>::new(test_service, cfg, buffer)
412         .expect("Could not create service manager")
413         .run_event_loop()
414         .expect("Test event loop failed");
415 }
416 
417 /// Clones static values for putting into a dynamic vector, which test_main()
418 /// needs to hand out ownership of tests to parallel test runners.
419 ///
420 /// This will panic when fed any dynamic tests, because they cannot be cloned.
make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn421 fn make_owned_test(test: &&TestDescAndFn) -> TestDescAndFn {
422     match test.testfn {
423         StaticTestFn(f) => TestDescAndFn { testfn: StaticTestFn(f), desc: test.desc.clone() },
424         StaticBenchFn(f) => TestDescAndFn { testfn: StaticBenchFn(f), desc: test.desc.clone() },
425         _ => panic!("non-static tests passed to test::test_main_static"),
426     }
427 }
428 
429 /// Invoked when unit tests terminate. The normal Rust test harness supports
430 /// tests which return values, we don't, so we require the test to return unit.
assert_test_result(_result: ())431 pub fn assert_test_result(_result: ()) {}
432 
433 /// Skip the current test case.
skip()434 pub fn skip() {
435     CONTEXT.skip();
436 }
437