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