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