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