Skip to main content

tracel_xtask/commands/
test.rs

1use anyhow::Result;
2use strum::IntoEnumIterator;
3
4use crate::{
5    commands::WARN_IGNORED_ONLY_ARGS,
6    endgroup,
7    environment::EnvironmentName,
8    group,
9    prelude::{Context, Environment},
10    utils::{
11        process::{run_process_for_package, run_process_for_workspace},
12        workspace::{WorkspaceMember, WorkspaceMemberType, get_workspace_members},
13    },
14};
15
16use super::Target;
17
18#[tracel_xtask_macros::declare_command_args(Target, TestSubCommand)]
19pub struct TestCmdArgs {}
20
21pub fn handle_command(args: TestCmdArgs, env: Environment, _ctx: Context) -> anyhow::Result<()> {
22    if args.target == Target::Workspace && !args.only.is_empty() {
23        warn!("{WARN_IGNORED_ONLY_ARGS}");
24    }
25    if !check_environment(&args, &env) {
26        std::process::exit(1);
27    }
28    match args.get_command() {
29        TestSubCommand::Unit => run_unit(&args.target, &args),
30        TestSubCommand::Integration => run_integration(&args.target, &args),
31        TestSubCommand::All => TestSubCommand::iter()
32            .filter(|c| *c != TestSubCommand::All)
33            .try_for_each(|c| {
34                handle_command(
35                    TestCmdArgs {
36                        command: Some(c),
37                        target: args.target.clone(),
38                        exclude: args.exclude.clone(),
39                        only: args.only.clone(),
40                        threads: args.threads,
41                        test: args.test.clone(),
42                        jobs: args.jobs,
43                        force: args.force,
44                        features: args.features.clone(),
45                        no_default_features: args.no_default_features,
46                        no_capture: args.no_capture,
47                        release: args.release,
48                    },
49                    env.clone(),
50                    _ctx.clone(),
51                )
52            }),
53    }
54}
55
56/// Return true if the environment is OK.
57/// Prevents from running test in production unless the `force` flag is set
58pub fn check_environment(args: &TestCmdArgs, env: &Environment) -> bool {
59    if env.name == EnvironmentName::Production {
60        if args.force {
61            warn!("Force running tests in production (--force argument is set)");
62            return true;
63        } else {
64            info!("Abort tests to avoid running them in production!");
65            return false;
66        }
67    }
68    true
69}
70
71fn push_optional_args(cmd_args: &mut Vec<String>, args: &TestCmdArgs) {
72    // cargo options
73    if let Some(jobs) = &args.jobs {
74        cmd_args.extend(vec!["--jobs".to_string(), jobs.to_string()]);
75    };
76    if let Some(features) = &args.features {
77        if !features.is_empty() {
78            cmd_args.extend(vec!["--features".to_string(), features.join(",")]);
79        }
80    }
81    if args.release {
82        cmd_args.push("--release".to_string());
83    }
84    if args.no_default_features {
85        cmd_args.push("--no-default-features".to_string());
86    }
87    // test harness options
88    cmd_args.extend(vec!["--".to_string(), "--color=always".to_string()]);
89    if let Some(threads) = &args.threads {
90        cmd_args.extend(vec!["--test-threads".to_string(), threads.to_string()]);
91    };
92    if args.no_capture {
93        cmd_args.push("--nocapture".to_string());
94    }
95}
96
97pub fn run_unit(target: &Target, args: &TestCmdArgs) -> Result<()> {
98    match target {
99        Target::Workspace => {
100            info!("Workspace Unit Tests");
101            let test = args.test.as_deref().unwrap_or("");
102            let mut cmd_args = vec![
103                "test",
104                "--workspace",
105                "--lib",
106                "--bins",
107                "--examples",
108                test,
109                "--color",
110                "always",
111            ]
112            .into_iter()
113            .map(|s| s.to_string())
114            .collect::<Vec<String>>();
115            push_optional_args(&mut cmd_args, args);
116            run_process_for_workspace(
117                "cargo",
118                &cmd_args.iter().map(String::as_str).collect::<Vec<&str>>(),
119                &args.exclude,
120                Some(r".*target/[^/]+/deps/([^-\s]+)"),
121                Some("Unit Tests"),
122                "Workspace Unit Tests failed",
123                Some("no library targets found"),
124                Some("No library found to test for in workspace."),
125            )?;
126        }
127        Target::Crates | Target::Examples => {
128            let members = match target {
129                Target::Crates => get_workspace_members(WorkspaceMemberType::Crate),
130                Target::Examples => get_workspace_members(WorkspaceMemberType::Example),
131                _ => unreachable!(),
132            };
133
134            for member in members {
135                run_unit_test(&member, args)?;
136            }
137        }
138        Target::AllPackages => {
139            Target::iter()
140                .filter(|t| *t != Target::AllPackages && *t != Target::Workspace)
141                .try_for_each(|t| run_unit(&t, args))?;
142        }
143    }
144    anyhow::Ok(())
145}
146
147pub fn run_unit_test(member: &WorkspaceMember, args: &TestCmdArgs) -> Result<(), anyhow::Error> {
148    group!("Unit Tests: {}", member.name);
149    let test = args.test.as_deref().unwrap_or("");
150    let mut cmd_args = vec![
151        "test",
152        test,
153        "--lib",
154        "--bins",
155        "--examples",
156        "-p",
157        &member.name,
158        "--color=always",
159    ]
160    .into_iter()
161    .map(|s| s.to_string())
162    .collect::<Vec<String>>();
163    push_optional_args(&mut cmd_args, args);
164    run_process_for_package(
165        "cargo",
166        &member.name,
167        &cmd_args.iter().map(String::as_str).collect::<Vec<&str>>(),
168        &args.exclude,
169        &args.only,
170        &format!("Failed to execute unit test for '{}'", &member.name),
171        Some("no library targets found"),
172        Some(&format!(
173            "No library found to test for in the crate '{}'.",
174            &member.name
175        )),
176    )?;
177    endgroup!();
178    anyhow::Ok(())
179}
180
181pub fn run_integration(target: &Target, args: &TestCmdArgs) -> anyhow::Result<()> {
182    match target {
183        Target::Workspace => {
184            info!("Workspace Integration Tests");
185            let test = args.test.as_deref().unwrap_or("*");
186            let mut cmd_args = vec!["test", "--workspace", "--test", test, "--color", "always"]
187                .into_iter()
188                .map(|s| s.to_string())
189                .collect::<Vec<String>>();
190            push_optional_args(&mut cmd_args, args);
191            run_process_for_workspace(
192                "cargo",
193                &cmd_args.iter().map(String::as_str).collect::<Vec<&str>>(),
194                &args.exclude,
195                Some(r".*target/[^/]+/deps/([^-\s]+)"),
196                Some("Integration Tests"),
197                "Workspace Integration Tests failed",
198                Some("no test target matches pattern"),
199                Some("No tests found matching the pattern `test_*` in workspace."),
200            )?;
201        }
202        Target::Crates | Target::Examples => {
203            let members = match target {
204                Target::Crates => get_workspace_members(WorkspaceMemberType::Crate),
205                Target::Examples => get_workspace_members(WorkspaceMemberType::Example),
206                _ => unreachable!(),
207            };
208
209            for member in members {
210                run_integration_test(&member, args)?;
211            }
212        }
213        Target::AllPackages => {
214            Target::iter()
215                .filter(|t| *t != Target::AllPackages && *t != Target::Workspace)
216                .try_for_each(|t| run_integration(&t, args))?;
217        }
218    }
219    anyhow::Ok(())
220}
221
222fn run_integration_test(member: &WorkspaceMember, args: &TestCmdArgs) -> Result<()> {
223    group!("Integration Tests: {}", &member.name);
224    let mut cmd_args = vec![
225        "test",
226        "--test",
227        "*",
228        "-p",
229        &member.name,
230        "--color",
231        "always",
232    ]
233    .into_iter()
234    .map(|s| s.to_string())
235    .collect::<Vec<String>>();
236    push_optional_args(&mut cmd_args, args);
237    run_process_for_package(
238        "cargo",
239        &member.name,
240        &cmd_args.iter().map(String::as_str).collect::<Vec<&str>>(),
241        &args.exclude,
242        &args.only,
243        &format!("Failed to execute integration test for '{}'", &member.name),
244        Some("no test target matches pattern"),
245        Some(&format!(
246            "No integration tests found for '{}'.",
247            &member.name
248        )),
249    )?;
250    endgroup!();
251    anyhow::Ok(())
252}