tracel_xtask/commands/
test.rs

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