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