Skip to main content

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