compiletest/
executor.rs

1//! This module encapsulates all of the code that interacts directly with
2//! libtest, to execute the collected tests.
3//!
4//! This will hopefully make it easier to migrate away from libtest someday.
5
6use std::borrow::Cow;
7use std::io;
8use std::sync::Arc;
9
10use crate::common::{Config, TestPaths};
11
12/// Delegates to libtest to run the list of collected tests.
13///
14/// Returns `Ok(true)` if all tests passed, or `Ok(false)` if one or more tests failed.
15pub(crate) fn execute_tests(config: &Config, tests: Vec<CollectedTest>) -> io::Result<bool> {
16    let opts = test_opts(config);
17    let tests = tests.into_iter().map(|t| t.into_libtest()).collect::<Vec<_>>();
18
19    test::run_tests_console(&opts, tests)
20}
21
22/// Information needed to create a `test::TestDescAndFn`.
23pub(crate) struct CollectedTest {
24    pub(crate) desc: CollectedTestDesc,
25    pub(crate) config: Arc<Config>,
26    pub(crate) testpaths: TestPaths,
27    pub(crate) revision: Option<String>,
28}
29
30/// Information needed to create a `test::TestDesc`.
31pub(crate) struct CollectedTestDesc {
32    pub(crate) name: String,
33    pub(crate) ignore: bool,
34    pub(crate) ignore_message: Option<Cow<'static, str>>,
35    pub(crate) should_panic: ShouldPanic,
36}
37
38impl CollectedTest {
39    fn into_libtest(self) -> test::TestDescAndFn {
40        let Self { desc, config, testpaths, revision } = self;
41        let CollectedTestDesc { name, ignore, ignore_message, should_panic } = desc;
42
43        // Libtest requires the ignore message to be a &'static str, so we might
44        // have to leak memory to create it. This is fine, as we only do so once
45        // per test, so the leak won't grow indefinitely.
46        let ignore_message = ignore_message.map(|msg| match msg {
47            Cow::Borrowed(s) => s,
48            Cow::Owned(s) => &*String::leak(s),
49        });
50
51        let desc = test::TestDesc {
52            name: test::DynTestName(name),
53            ignore,
54            ignore_message,
55            source_file: "",
56            start_line: 0,
57            start_col: 0,
58            end_line: 0,
59            end_col: 0,
60            should_panic: should_panic.to_libtest(),
61            compile_fail: false,
62            no_run: false,
63            test_type: test::TestType::Unknown,
64        };
65
66        // This closure is invoked when libtest returns control to compiletest
67        // to execute the test.
68        let testfn = test::DynTestFn(Box::new(move || {
69            crate::runtest::run(config, &testpaths, revision.as_deref());
70            Ok(())
71        }));
72
73        test::TestDescAndFn { desc, testfn }
74    }
75}
76
77/// Whether console output should be colored or not.
78#[derive(Copy, Clone, Default, Debug)]
79pub enum ColorConfig {
80    #[default]
81    AutoColor,
82    AlwaysColor,
83    NeverColor,
84}
85
86impl ColorConfig {
87    fn to_libtest(self) -> test::ColorConfig {
88        match self {
89            Self::AutoColor => test::ColorConfig::AutoColor,
90            Self::AlwaysColor => test::ColorConfig::AlwaysColor,
91            Self::NeverColor => test::ColorConfig::NeverColor,
92        }
93    }
94}
95
96/// Format of the test results output.
97#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
98pub enum OutputFormat {
99    /// Verbose output
100    Pretty,
101    /// Quiet output
102    #[default]
103    Terse,
104    /// JSON output
105    Json,
106}
107
108impl OutputFormat {
109    fn to_libtest(self) -> test::OutputFormat {
110        match self {
111            Self::Pretty => test::OutputFormat::Pretty,
112            Self::Terse => test::OutputFormat::Terse,
113            Self::Json => test::OutputFormat::Json,
114        }
115    }
116}
117
118/// Whether test is expected to panic or not.
119#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
120pub(crate) enum ShouldPanic {
121    No,
122    Yes,
123}
124
125impl ShouldPanic {
126    fn to_libtest(self) -> test::ShouldPanic {
127        match self {
128            Self::No => test::ShouldPanic::No,
129            Self::Yes => test::ShouldPanic::Yes,
130        }
131    }
132}
133
134fn test_opts(config: &Config) -> test::TestOpts {
135    test::TestOpts {
136        exclude_should_panic: false,
137        filters: config.filters.clone(),
138        filter_exact: config.filter_exact,
139        run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
140        format: config.format.to_libtest(),
141        logfile: None,
142        run_tests: true,
143        bench_benchmarks: true,
144        nocapture: config.nocapture,
145        color: config.color.to_libtest(),
146        shuffle: false,
147        shuffle_seed: None,
148        test_threads: None,
149        skip: config.skip.clone(),
150        list: false,
151        options: test::Options::new(),
152        time_options: None,
153        force_run_in_process: false,
154        fail_fast: config.fail_fast,
155    }
156}