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