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
56pub 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 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 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}