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