fuchsia/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Macros for creating Fuchsia components and tests.
6//!
7//! These macros work on Fuchsia, and also on host with some limitations (that are called out
8//! where they exist).
9
10// Features from those macros are expected to be implemented by exactly one function in this
11// module. We strive for simple, independent, single purpose functions as building blocks to allow
12// dead code elimination to have the very best chance of removing unused code that might be
13// otherwise pulled in here.
14
15#![deny(missing_docs)]
16
17pub use fuchsia_macro::{main, test};
18use libc as _;
19#[doc(hidden)]
20pub use log::error;
21use std::future::Future;
22
23#[cfg(fuchsia_api_level_less_than = "27")]
24pub use fidl_fuchsia_diagnostics::{Interest, Severity};
25#[cfg(fuchsia_api_level_at_least = "27")]
26pub use fidl_fuchsia_diagnostics_types::{Interest, Severity};
27
28#[cfg(target_os = "fuchsia")]
29pub use fuchsia_async_inspect;
30
31//
32// LOGGING INITIALIZATION
33//
34
35/// Options used when initializing logging.
36#[derive(Default, Clone)]
37pub struct LoggingOptions<'a> {
38    /// Tags with which to initialize the logging system. All logs will carry the tags configured
39    /// here.
40    pub tags: &'a [&'static str],
41
42    /// Allows to configure the minimum severity of the logs being emitted. Logs of lower severity
43    /// won't be emitted.
44    pub interest: Interest,
45
46    /// String to include in logged panic messages.
47    pub panic_prefix: &'static str,
48
49    /// True to always log file/line information, false to only log
50    /// when severity is ERROR or above.
51    pub always_log_file_line: bool,
52}
53
54#[cfg(not(target_os = "fuchsia"))]
55impl<'a> From<LoggingOptions<'a>> for diagnostics_log::PublishOptions<'a> {
56    fn from(logging: LoggingOptions<'a>) -> Self {
57        let mut options = diagnostics_log::PublishOptions::default().tags(logging.tags);
58        if let Some(severity) = logging.interest.min_severity {
59            options = options.minimum_severity(severity);
60        }
61        options = options.panic_prefix(logging.panic_prefix);
62        options
63    }
64}
65
66#[cfg(target_os = "fuchsia")]
67impl<'a> From<LoggingOptions<'a>> for diagnostics_log::PublishOptions<'a> {
68    fn from(logging: LoggingOptions<'a>) -> Self {
69        let mut options = diagnostics_log::PublishOptions::default().tags(logging.tags);
70        if let Some(severity) = logging.interest.min_severity {
71            options = options.minimum_severity(severity);
72        }
73        if logging.always_log_file_line {
74            options = options.always_log_file_line();
75        }
76        options = options.panic_prefix(logging.panic_prefix);
77        options
78    }
79}
80
81/// Initialize logging
82#[doc(hidden)]
83pub fn init_logging_for_component_with_executor<'a, R>(
84    func: impl FnOnce() -> R + 'a,
85    logging: LoggingOptions<'a>,
86) -> impl FnOnce() -> R + 'a {
87    move || {
88        diagnostics_log::initialize(logging.into()).expect("initialize_logging");
89        func()
90    }
91}
92
93#[doc(hidden)]
94pub fn init_default_logging_for_component_with_executor<'a, R>(
95    func: impl FnOnce() -> R + 'a,
96    logging: LoggingOptions<'a>,
97) -> impl FnOnce() -> R + 'a {
98    init_logging_for_component_with_executor(func, logging)
99}
100
101#[doc(hidden)]
102pub fn init_noop_logging_for_component_with_executor<'a, R>(
103    func: impl FnOnce() -> R + 'a,
104    _logging: LoggingOptions<'a>,
105) -> impl FnOnce() -> R + 'a {
106    func
107}
108
109/// Initialize logging
110#[doc(hidden)]
111pub fn init_logging_for_component_with_threads<'a, R>(
112    func: impl FnOnce() -> R + 'a,
113    logging: LoggingOptions<'a>,
114) -> impl FnOnce() -> R + 'a {
115    move || {
116        let _guard = init_logging_with_threads(logging);
117        func()
118    }
119}
120
121#[doc(hidden)]
122pub fn init_default_logging_for_component_with_threads<'a, R>(
123    func: impl FnOnce() -> R + 'a,
124    logging: LoggingOptions<'a>,
125) -> impl FnOnce() -> R + 'a {
126    init_logging_for_component_with_threads(func, logging)
127}
128
129#[doc(hidden)]
130pub fn init_noop_logging_for_component_with_threads<'a, R>(
131    func: impl FnOnce() -> R + 'a,
132    _logging: LoggingOptions<'a>,
133) -> impl FnOnce() -> R + 'a {
134    func
135}
136
137/// Initialize logging
138#[doc(hidden)]
139pub fn init_logging_for_test_with_executor<'a, R>(
140    func: impl Fn(usize) -> R + 'a,
141    logging: LoggingOptions<'a>,
142) -> impl Fn(usize) -> R + 'a {
143    move |n| {
144        diagnostics_log::initialize(logging.clone().into()).expect("initalize logging");
145        func(n)
146    }
147}
148
149#[doc(hidden)]
150pub fn init_default_logging_for_test_with_executor<'a, R>(
151    func: impl Fn(usize) -> R + 'a,
152    logging: LoggingOptions<'a>,
153) -> impl Fn(usize) -> R + 'a {
154    #[cfg(target_os = "fuchsia")]
155    {
156        init_logging_for_test_with_executor(func, logging)
157    }
158    #[cfg(not(target_os = "fuchsia"))]
159    {
160        init_noop_logging_for_test_with_executor(func, logging)
161    }
162}
163
164#[doc(hidden)]
165pub fn init_noop_logging_for_test_with_executor<'a, R>(
166    func: impl Fn(usize) -> R + 'a,
167    _logging: LoggingOptions<'a>,
168) -> impl Fn(usize) -> R + 'a {
169    func
170}
171
172/// Initialize logging
173#[doc(hidden)]
174pub fn init_logging_for_test_with_threads<'a, R>(
175    func: impl Fn(usize) -> R + 'a,
176    logging: LoggingOptions<'a>,
177) -> impl Fn(usize) -> R + 'a {
178    move |n| {
179        init_logging_with_threads(logging.clone());
180        func(n)
181    }
182}
183
184#[doc(hidden)]
185pub fn init_default_logging_for_test_with_threads<'a, R>(
186    func: impl Fn(usize) -> R + 'a,
187    logging: LoggingOptions<'a>,
188) -> impl Fn(usize) -> R + 'a {
189    #[cfg(target_os = "fuchsia")]
190    {
191        init_logging_for_test_with_threads(func, logging)
192    }
193    #[cfg(not(target_os = "fuchsia"))]
194    {
195        // On non-Fuchsia targets, we don't initialize logging by default for tests. Host tests
196        // don't have any process isolation between each other, so one test can say logging=true
197        // and another in the same binary can say logging=false, and who wins will be determined
198        // by execution order. In order to avoid confusion, we default to not enabling logging. If
199        // a user understands the implications, they can still opt back in to enabling it.
200        init_noop_logging_for_test_with_threads(func, logging)
201    }
202}
203
204#[doc(hidden)]
205pub fn init_noop_logging_for_test_with_threads<'a, R>(
206    func: impl Fn(usize) -> R + 'a,
207    _logging: LoggingOptions<'a>,
208) -> impl Fn(usize) -> R + 'a {
209    func
210}
211
212/// Initializes logging on a background thread, returning a guard which cancels interest listening
213/// when dropped.
214#[cfg(target_os = "fuchsia")]
215fn init_logging_with_threads(logging: LoggingOptions<'_>) {
216    diagnostics_log::initialize_sync(logging.into());
217}
218
219#[cfg(not(target_os = "fuchsia"))]
220fn init_logging_with_threads(logging: LoggingOptions<'_>) {
221    diagnostics_log::initialize(logging.into()).expect("initialize_logging");
222}
223
224#[cfg(target_os = "fuchsia")]
225fn set_thread_role(role_name: &str) {
226    if let Err(e) = fuchsia_scheduler::set_role_for_this_thread(role_name) {
227        log::warn!(e:%, role_name:%; "Couldn't apply thread role.");
228    }
229}
230
231//
232// MAIN FUNCTION WRAPPERS
233//
234
235/// Run a non-async main function.
236#[doc(hidden)]
237pub fn main_not_async<F, R>(f: F) -> R
238where
239    F: FnOnce() -> R,
240{
241    f()
242}
243
244/// Run a non-async main function, applying `role_name` as the thread role.
245#[doc(hidden)]
246pub fn main_not_async_with_role<F, R>(f: F, _role_name: &'static str) -> R
247where
248    F: FnOnce() -> R,
249{
250    #[cfg(target_os = "fuchsia")]
251    set_thread_role(_role_name);
252    f()
253}
254
255/// Run an async main function with a single threaded executor.
256#[doc(hidden)]
257#[cfg(target_os = "fuchsia")]
258pub fn main_singlethreaded<F, Fut, R>(
259    f: F,
260    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
261) -> R
262where
263    F: FnOnce() -> Fut,
264    Fut: Future<Output = R> + 'static,
265{
266    fuchsia_async::LocalExecutorBuilder::new()
267        .instrument(instrument)
268        .build()
269        .run_singlethreaded(f())
270}
271
272/// Run an async main function with a single threaded executor.
273#[doc(hidden)]
274#[cfg(not(target_os = "fuchsia"))]
275pub fn main_singlethreaded<F, Fut, R>(f: F, _instrument: Option<()>) -> R
276where
277    F: FnOnce() -> Fut,
278    Fut: Future<Output = R> + 'static,
279{
280    fuchsia_async::LocalExecutorBuilder::new().build().run_singlethreaded(f())
281}
282
283/// Run an async main function with a single threaded executor, applying `role_name`.
284#[doc(hidden)]
285#[cfg(target_os = "fuchsia")]
286pub fn main_singlethreaded_with_role<F, Fut, R>(
287    f: F,
288    _role_name: &'static str,
289    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
290) -> R
291where
292    F: FnOnce() -> Fut,
293    Fut: Future<Output = R> + 'static,
294{
295    #[cfg(target_os = "fuchsia")]
296    set_thread_role(_role_name);
297    fuchsia_async::LocalExecutorBuilder::new()
298        .instrument(instrument)
299        .build()
300        .run_singlethreaded(f())
301}
302
303/// Run an async main function with a single threaded executor, applying `role_name`.
304#[doc(hidden)]
305#[cfg(not(target_os = "fuchsia"))]
306pub fn main_singlethreaded_with_role<F, Fut, R>(
307    f: F,
308    _role_name: &'static str,
309    _instrument: Option<()>,
310) -> R
311where
312    F: FnOnce() -> Fut,
313    Fut: Future<Output = R> + 'static,
314{
315    fuchsia_async::LocalExecutorBuilder::new().build().run_singlethreaded(f())
316}
317
318/// Run an async main function with a multi threaded executor (containing `num_threads`).
319#[doc(hidden)]
320#[cfg(target_os = "fuchsia")]
321pub fn main_multithreaded<F, Fut, R>(
322    f: F,
323    num_threads: u8,
324    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
325) -> R
326where
327    F: FnOnce() -> Fut,
328    Fut: Future<Output = R> + Send + 'static,
329    R: Send + 'static,
330{
331    fuchsia_async::SendExecutorBuilder::new()
332        .num_threads(num_threads)
333        .instrument(instrument)
334        .build()
335        .run(f())
336}
337
338/// Run an async main function with a multi threaded executor (containing `num_threads`).
339#[doc(hidden)]
340#[cfg(not(target_os = "fuchsia"))]
341pub fn main_multithreaded<F, Fut, R>(f: F, num_threads: u8, _instrument: Option<()>) -> R
342where
343    F: FnOnce() -> Fut,
344    Fut: Future<Output = R> + Send + 'static,
345    R: Send + 'static,
346{
347    fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads).build().run(f())
348}
349
350/// Run an async main function with a multi threaded executor (containing `num_threads`) and apply
351/// `role_name` to all of the threads.
352#[doc(hidden)]
353#[cfg(target_os = "fuchsia")]
354pub fn main_multithreaded_with_role<F, Fut, R>(
355    f: F,
356    num_threads: u8,
357    _role_name: &'static str,
358    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
359) -> R
360where
361    F: FnOnce() -> Fut,
362    Fut: Future<Output = R> + Send + 'static,
363    R: Send + 'static,
364{
365    #[cfg(target_os = "fuchsia")]
366    set_thread_role(_role_name);
367
368    let builder =
369        fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads).instrument(instrument);
370
371    #[cfg(target_os = "fuchsia")]
372    let builder = builder.worker_init(move || set_thread_role(_role_name));
373
374    builder.build().run(f())
375}
376
377/// Run an async main function with a multi threaded executor (containing `num_threads`) and apply
378/// `role_name` to all of the threads.
379#[doc(hidden)]
380#[cfg(not(target_os = "fuchsia"))]
381pub fn main_multithreaded_with_role<F, Fut, R>(
382    f: F,
383    num_threads: u8,
384    _role_name: &'static str,
385    _instrument: Option<()>,
386) -> R
387where
388    F: FnOnce() -> Fut,
389    Fut: Future<Output = R> + Send + 'static,
390    R: Send + 'static,
391{
392    let builder = fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads);
393
394    #[cfg(target_os = "fuchsia")]
395    let builder = builder.worker_init(move || set_thread_role(_role_name));
396
397    builder.build().run(f())
398}
399
400//
401// TEST FUNCTION WRAPPERS
402//
403
404/// Run a non-async test function.
405#[doc(hidden)]
406pub fn test_not_async<F, R>(f: F) -> R
407where
408    F: FnOnce(usize) -> R,
409    R: fuchsia_async::test_support::TestResult,
410{
411    let result = f(0);
412    if result.is_ok() {
413        install_lsan_hook();
414    }
415    result
416}
417
418/// Run an async test function with a single threaded executor.
419#[doc(hidden)]
420pub fn test_singlethreaded<F, Fut, R>(f: F) -> R
421where
422    F: Fn(usize) -> Fut + Sync + 'static,
423    Fut: Future<Output = R> + 'static,
424    R: fuchsia_async::test_support::TestResult,
425{
426    let result = fuchsia_async::test_support::run_singlethreaded_test(f);
427    if result.is_ok() {
428        install_lsan_hook();
429    }
430    result
431}
432
433/// Run an async test function with a multi threaded executor (containing `num_threads`).
434#[doc(hidden)]
435pub fn test_multithreaded<F, Fut, R>(f: F, num_threads: u8) -> R
436where
437    F: Fn(usize) -> Fut + Sync + 'static,
438    Fut: Future<Output = R> + Send + 'static,
439    R: fuchsia_async::test_support::MultithreadedTestResult,
440{
441    let result = fuchsia_async::test_support::run_test(f, num_threads);
442    if result.is_ok() {
443        install_lsan_hook();
444    }
445    result
446}
447
448/// Run an async test function until it stalls. The executor will also use fake time.
449#[doc(hidden)]
450#[cfg(target_os = "fuchsia")]
451pub fn test_until_stalled<F, Fut, R>(f: F) -> R
452where
453    F: 'static + Sync + Fn(usize) -> Fut,
454    Fut: 'static + Future<Output = R>,
455    R: fuchsia_async::test_support::TestResult,
456{
457    let result = fuchsia_async::test_support::run_until_stalled_test(true, f);
458    if result.is_ok() {
459        install_lsan_hook();
460    }
461    result
462}
463
464//
465// FUNCTION ARGUMENT ADAPTERS
466//
467
468/// Take a main function `f` that takes an argument and return a function that takes none but calls
469/// `f` with the arguments parsed via argh.
470#[doc(hidden)]
471pub fn adapt_to_parse_arguments<A, R>(f: impl FnOnce(A) -> R) -> impl FnOnce() -> R
472where
473    A: argh::TopLevelCommand,
474{
475    move || f(argh::from_env())
476}
477
478/// Take a test function `f` that takes no parameters and return a function that takes the run
479/// number as required by our test runners.
480#[doc(hidden)]
481pub fn adapt_to_take_test_run_number<R>(f: impl Fn() -> R) -> impl Fn(usize) -> R {
482    move |_| f()
483}
484
485//
486// LEAK SANITIZER SUPPORT
487//
488
489// Note that the variant is named variant_asan (for AddressSanitizer), but the specific sanitizer we
490// are targeting is lsan (LeakSanitizer), which is enabled as part of the asan variant.
491
492#[doc(hidden)]
493#[cfg(not(any(feature = "variant_asan", feature = "variant_hwasan")))]
494pub fn disable_lsan_for_should_panic() {}
495
496#[doc(hidden)]
497#[cfg(any(feature = "variant_asan", feature = "variant_hwasan"))]
498pub fn disable_lsan_for_should_panic() {
499    unsafe extern "C" {
500        fn __lsan_disable();
501    }
502    unsafe {
503        __lsan_disable();
504    }
505}
506
507#[cfg(not(any(feature = "variant_asan", feature = "variant_hwasan")))]
508fn install_lsan_hook() {}
509
510#[cfg(any(feature = "variant_asan", feature = "variant_hwasan"))]
511fn install_lsan_hook() {
512    unsafe extern "C" {
513        fn __lsan_do_leak_check();
514    }
515    // Wrap the call because atexit requires a safe function pointer.
516    extern "C" fn lsan_do_leak_check() {
517        unsafe { __lsan_do_leak_check() }
518    }
519    // Wait until atexit hooks are called, in case there is more cleanup left to do.
520    let err = unsafe { libc::atexit(lsan_do_leak_check) };
521    if err != 0 {
522        panic!("Failed to install atexit hook for LeakSanitizer: atexit returned {err}");
523    }
524}