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    /// Whether or not logs will be blocking. By default logs are dropped when they can't be
47    /// written to the socket. However, when this is set, the log statement will block until the
48    /// log can be written to the socket or the socket is closed.
49    ///
50    /// NOTE: this is ignored on `host`.
51    pub blocking: bool,
52
53    /// String to include in logged panic messages.
54    pub panic_prefix: &'static str,
55
56    /// True to always log file/line information, false to only log
57    /// when severity is ERROR or above.
58    pub always_log_file_line: bool,
59}
60
61#[cfg(not(target_os = "fuchsia"))]
62impl<'a> From<LoggingOptions<'a>> for diagnostics_log::PublishOptions<'a> {
63    fn from(logging: LoggingOptions<'a>) -> Self {
64        let mut options = diagnostics_log::PublishOptions::default().tags(logging.tags);
65        if let Some(severity) = logging.interest.min_severity {
66            options = options.minimum_severity(severity);
67        }
68        options = options.panic_prefix(logging.panic_prefix);
69        options
70    }
71}
72
73#[cfg(target_os = "fuchsia")]
74impl<'a> From<LoggingOptions<'a>> for diagnostics_log::PublishOptions<'a> {
75    fn from(logging: LoggingOptions<'a>) -> Self {
76        let mut options = diagnostics_log::PublishOptions::default().tags(logging.tags);
77        options = options.blocking(logging.blocking);
78        if let Some(severity) = logging.interest.min_severity {
79            options = options.minimum_severity(severity);
80        }
81        if logging.always_log_file_line {
82            options = options.always_log_file_line();
83        }
84        options = options.panic_prefix(logging.panic_prefix);
85        options
86    }
87}
88
89/// Initialize logging
90#[doc(hidden)]
91pub fn init_logging_for_component_with_executor<'a, R>(
92    func: impl FnOnce() -> R + 'a,
93    logging: LoggingOptions<'a>,
94) -> impl FnOnce() -> R + 'a {
95    move || {
96        diagnostics_log::initialize(logging.into()).expect("initialize_logging");
97        func()
98    }
99}
100
101/// Initialize logging
102#[doc(hidden)]
103pub fn init_logging_for_component_with_threads<'a, R>(
104    func: impl FnOnce() -> R + 'a,
105    logging: LoggingOptions<'a>,
106) -> impl FnOnce() -> R + 'a {
107    move || {
108        let _guard = init_logging_with_threads(logging);
109        func()
110    }
111}
112
113/// Initialize logging
114#[doc(hidden)]
115#[cfg(target_os = "fuchsia")]
116pub fn init_logging_for_test_with_executor<'a, R>(
117    func: impl Fn(usize) -> R + 'a,
118    logging: LoggingOptions<'a>,
119) -> impl Fn(usize) -> R + 'a {
120    move |n| {
121        diagnostics_log::initialize(logging.clone().into()).expect("initalize logging");
122        func(n)
123    }
124}
125
126/// Initialize logging
127#[doc(hidden)]
128#[cfg(target_os = "fuchsia")]
129pub fn init_logging_for_test_with_threads<'a, R>(
130    func: impl Fn(usize) -> R + 'a,
131    logging: LoggingOptions<'a>,
132) -> impl Fn(usize) -> R + 'a {
133    move |n| {
134        init_logging_with_threads(logging.clone());
135        func(n)
136    }
137}
138
139/// Initializes logging on a background thread.
140#[cfg(target_os = "fuchsia")]
141fn init_logging_with_threads(logging: LoggingOptions<'_>) {
142    diagnostics_log::initialize_sync(logging.into());
143}
144
145#[cfg(not(target_os = "fuchsia"))]
146fn init_logging_with_threads(logging: LoggingOptions<'_>) {
147    diagnostics_log::initialize(logging.into()).expect("initialize_logging");
148}
149
150/// Initialize logging
151#[doc(hidden)]
152#[cfg(not(target_os = "fuchsia"))]
153pub fn init_logging_for_test_with_executor<'a, R>(
154    func: impl Fn(usize) -> R + 'a,
155    _logging: LoggingOptions<'a>,
156) -> impl Fn(usize) -> R + 'a {
157    move |n| func(n)
158}
159
160/// Initialize logging
161#[doc(hidden)]
162#[cfg(not(target_os = "fuchsia"))]
163pub fn init_logging_for_test_with_threads<'a, R>(
164    func: impl Fn(usize) -> R + 'a,
165    _logging: LoggingOptions<'a>,
166) -> impl Fn(usize) -> R + 'a {
167    move |n| func(n)
168}
169
170#[cfg(target_os = "fuchsia")]
171fn set_thread_role(role_name: &str) {
172    if let Err(e) = fuchsia_scheduler::set_role_for_this_thread(role_name) {
173        log::warn!(e:%, role_name:%; "Couldn't apply thread role.");
174    }
175}
176
177//
178// MAIN FUNCTION WRAPPERS
179//
180
181/// Run a non-async main function.
182#[doc(hidden)]
183pub fn main_not_async<F, R>(f: F) -> R
184where
185    F: FnOnce() -> R,
186{
187    f()
188}
189
190/// Run a non-async main function, applying `role_name` as the thread role.
191#[doc(hidden)]
192pub fn main_not_async_with_role<F, R>(f: F, _role_name: &'static str) -> R
193where
194    F: FnOnce() -> R,
195{
196    #[cfg(target_os = "fuchsia")]
197    set_thread_role(_role_name);
198    f()
199}
200
201/// Run an async main function with a single threaded executor.
202#[doc(hidden)]
203#[cfg(target_os = "fuchsia")]
204pub fn main_singlethreaded<F, Fut, R>(
205    f: F,
206    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
207) -> R
208where
209    F: FnOnce() -> Fut,
210    Fut: Future<Output = R> + 'static,
211{
212    fuchsia_async::LocalExecutorBuilder::new()
213        .instrument(instrument)
214        .build()
215        .run_singlethreaded(f())
216}
217
218/// Run an async main function with a single threaded executor.
219#[doc(hidden)]
220#[cfg(not(target_os = "fuchsia"))]
221pub fn main_singlethreaded<F, Fut, R>(f: F, _instrument: Option<()>) -> R
222where
223    F: FnOnce() -> Fut,
224    Fut: Future<Output = R> + 'static,
225{
226    fuchsia_async::LocalExecutorBuilder::new().build().run_singlethreaded(f())
227}
228
229/// Run an async main function with a single threaded executor, applying `role_name`.
230#[doc(hidden)]
231#[cfg(target_os = "fuchsia")]
232pub fn main_singlethreaded_with_role<F, Fut, R>(
233    f: F,
234    _role_name: &'static str,
235    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
236) -> R
237where
238    F: FnOnce() -> Fut,
239    Fut: Future<Output = R> + 'static,
240{
241    #[cfg(target_os = "fuchsia")]
242    set_thread_role(_role_name);
243    fuchsia_async::LocalExecutorBuilder::new()
244        .instrument(instrument)
245        .build()
246        .run_singlethreaded(f())
247}
248
249/// Run an async main function with a single threaded executor, applying `role_name`.
250#[doc(hidden)]
251#[cfg(not(target_os = "fuchsia"))]
252pub fn main_singlethreaded_with_role<F, Fut, R>(
253    f: F,
254    _role_name: &'static str,
255    _instrument: Option<()>,
256) -> R
257where
258    F: FnOnce() -> Fut,
259    Fut: Future<Output = R> + 'static,
260{
261    fuchsia_async::LocalExecutorBuilder::new().build().run_singlethreaded(f())
262}
263
264/// Run an async main function with a multi threaded executor (containing `num_threads`).
265#[doc(hidden)]
266#[cfg(target_os = "fuchsia")]
267pub fn main_multithreaded<F, Fut, R>(
268    f: F,
269    num_threads: u8,
270    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
271) -> R
272where
273    F: FnOnce() -> Fut,
274    Fut: Future<Output = R> + Send + 'static,
275    R: Send + 'static,
276{
277    fuchsia_async::SendExecutorBuilder::new()
278        .num_threads(num_threads)
279        .instrument(instrument)
280        .build()
281        .run(f())
282}
283
284/// Run an async main function with a multi threaded executor (containing `num_threads`).
285#[doc(hidden)]
286#[cfg(not(target_os = "fuchsia"))]
287pub fn main_multithreaded<F, Fut, R>(f: F, num_threads: u8, _instrument: Option<()>) -> R
288where
289    F: FnOnce() -> Fut,
290    Fut: Future<Output = R> + Send + 'static,
291    R: Send + 'static,
292{
293    fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads).build().run(f())
294}
295
296/// Run an async main function with a multi threaded executor (containing `num_threads`) and apply
297/// `role_name` to all of the threads.
298#[doc(hidden)]
299#[cfg(target_os = "fuchsia")]
300pub fn main_multithreaded_with_role<F, Fut, R>(
301    f: F,
302    num_threads: u8,
303    _role_name: &'static str,
304    instrument: Option<std::sync::Arc<dyn fuchsia_async::instrument::TaskInstrument>>,
305) -> R
306where
307    F: FnOnce() -> Fut,
308    Fut: Future<Output = R> + Send + 'static,
309    R: Send + 'static,
310{
311    #[cfg(target_os = "fuchsia")]
312    set_thread_role(_role_name);
313
314    let builder =
315        fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads).instrument(instrument);
316
317    #[cfg(target_os = "fuchsia")]
318    let builder = builder.worker_init(move || set_thread_role(_role_name));
319
320    builder.build().run(f())
321}
322
323/// Run an async main function with a multi threaded executor (containing `num_threads`) and apply
324/// `role_name` to all of the threads.
325#[doc(hidden)]
326#[cfg(not(target_os = "fuchsia"))]
327pub fn main_multithreaded_with_role<F, Fut, R>(
328    f: F,
329    num_threads: u8,
330    _role_name: &'static str,
331    _instrument: Option<()>,
332) -> R
333where
334    F: FnOnce() -> Fut,
335    Fut: Future<Output = R> + Send + 'static,
336    R: Send + 'static,
337{
338    let builder = fuchsia_async::SendExecutorBuilder::new().num_threads(num_threads);
339
340    #[cfg(target_os = "fuchsia")]
341    let builder = builder.worker_init(move || set_thread_role(_role_name));
342
343    builder.build().run(f())
344}
345
346//
347// TEST FUNCTION WRAPPERS
348//
349
350/// Run a non-async test function.
351#[doc(hidden)]
352pub fn test_not_async<F, R>(f: F) -> R
353where
354    F: FnOnce(usize) -> R,
355    R: fuchsia_async::test_support::TestResult,
356{
357    let result = f(0);
358    if result.is_ok() {
359        install_lsan_hook();
360    }
361    result
362}
363
364/// Run an async test function with a single threaded executor.
365#[doc(hidden)]
366pub fn test_singlethreaded<F, Fut, R>(f: F) -> R
367where
368    F: Fn(usize) -> Fut + Sync + 'static,
369    Fut: Future<Output = R> + 'static,
370    R: fuchsia_async::test_support::TestResult,
371{
372    let result = fuchsia_async::test_support::run_singlethreaded_test(f);
373    if result.is_ok() {
374        install_lsan_hook();
375    }
376    result
377}
378
379/// Run an async test function with a multi threaded executor (containing `num_threads`).
380#[doc(hidden)]
381pub fn test_multithreaded<F, Fut, R>(f: F, num_threads: u8) -> R
382where
383    F: Fn(usize) -> Fut + Sync + 'static,
384    Fut: Future<Output = R> + Send + 'static,
385    R: fuchsia_async::test_support::MultithreadedTestResult,
386{
387    let result = fuchsia_async::test_support::run_test(f, num_threads);
388    if result.is_ok() {
389        install_lsan_hook();
390    }
391    result
392}
393
394/// Run an async test function until it stalls. The executor will also use fake time.
395#[doc(hidden)]
396#[cfg(target_os = "fuchsia")]
397pub fn test_until_stalled<F, Fut, R>(f: F) -> R
398where
399    F: 'static + Sync + Fn(usize) -> Fut,
400    Fut: 'static + Future<Output = R>,
401    R: fuchsia_async::test_support::TestResult,
402{
403    let result = fuchsia_async::test_support::run_until_stalled_test(true, f);
404    if result.is_ok() {
405        install_lsan_hook();
406    }
407    result
408}
409
410//
411// FUNCTION ARGUMENT ADAPTERS
412//
413
414/// Take a main function `f` that takes an argument and return a function that takes none but calls
415/// `f` with the arguments parsed via argh.
416#[doc(hidden)]
417pub fn adapt_to_parse_arguments<A, R>(f: impl FnOnce(A) -> R) -> impl FnOnce() -> R
418where
419    A: argh::TopLevelCommand,
420{
421    move || f(argh::from_env())
422}
423
424/// Take a test function `f` that takes no parameters and return a function that takes the run
425/// number as required by our test runners.
426#[doc(hidden)]
427pub fn adapt_to_take_test_run_number<R>(f: impl Fn() -> R) -> impl Fn(usize) -> R {
428    move |_| f()
429}
430
431//
432// LEAK SANITIZER SUPPORT
433//
434
435// Note that the variant is named variant_asan (for AddressSanitizer), but the specific sanitizer we
436// are targeting is lsan (LeakSanitizer), which is enabled as part of the asan variant.
437
438#[doc(hidden)]
439#[cfg(not(any(feature = "variant_asan", feature = "variant_hwasan")))]
440pub fn disable_lsan_for_should_panic() {}
441
442#[doc(hidden)]
443#[cfg(any(feature = "variant_asan", feature = "variant_hwasan"))]
444pub fn disable_lsan_for_should_panic() {
445    extern "C" {
446        fn __lsan_disable();
447    }
448    unsafe {
449        __lsan_disable();
450    }
451}
452
453#[cfg(not(any(feature = "variant_asan", feature = "variant_hwasan")))]
454fn install_lsan_hook() {}
455
456#[cfg(any(feature = "variant_asan", feature = "variant_hwasan"))]
457fn install_lsan_hook() {
458    extern "C" {
459        fn __lsan_do_leak_check();
460    }
461    // Wrap the call because atexit requires a safe function pointer.
462    extern "C" fn lsan_do_leak_check() {
463        unsafe { __lsan_do_leak_check() }
464    }
465    // Wait until atexit hooks are called, in case there is more cleanup left to do.
466    let err = unsafe { libc::atexit(lsan_do_leak_check) };
467    if err != 0 {
468        panic!("Failed to install atexit hook for LeakSanitizer: atexit returned {err}");
469    }
470}