rust_analyzer/
test_runner.rs1use crossbeam_channel::Sender;
5use paths::{AbsPath, Utf8Path};
6use project_model::TargetKind;
7use serde::Deserialize as _;
8use serde_derive::Deserialize;
9use toolchain::Tool;
10
11use crate::{
12 command::{CommandHandle, JsonLinesParser},
13 flycheck::CargoOptions,
14};
15
16#[derive(Debug, Deserialize)]
17#[serde(tag = "event", rename_all = "camelCase")]
18pub(crate) enum TestState {
19 Started,
20 Ok,
21 Ignored,
22 Failed {
23 #[serde(skip_serializing_if = "String::is_empty", default)]
25 stdout: String,
26 },
27}
28
29#[derive(Debug)]
30pub(crate) struct CargoTestMessage {
31 pub target: TestTarget,
32 pub output: CargoTestOutput,
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(tag = "type", rename_all = "camelCase")]
37pub(crate) enum CargoTestOutput {
38 Test {
39 name: String,
40 #[serde(flatten)]
41 state: TestState,
42 },
43 Suite,
44 Finished,
45 Custom {
46 text: String,
47 },
48}
49
50pub(crate) struct CargoTestOutputParser {
51 pub target: TestTarget,
52}
53
54impl CargoTestOutputParser {
55 pub(crate) fn new(test_target: &TestTarget) -> Self {
56 Self { target: test_target.clone() }
57 }
58}
59
60impl JsonLinesParser<CargoTestMessage> for CargoTestOutputParser {
61 fn from_line(&self, line: &str, _error: &mut String) -> Option<CargoTestMessage> {
62 let mut deserializer = serde_json::Deserializer::from_str(line);
63 deserializer.disable_recursion_limit();
64
65 Some(CargoTestMessage {
66 target: self.target.clone(),
67 output: if let Ok(message) = CargoTestOutput::deserialize(&mut deserializer) {
68 message
69 } else {
70 CargoTestOutput::Custom { text: line.to_owned() }
71 },
72 })
73 }
74
75 fn from_eof(&self) -> Option<CargoTestMessage> {
76 Some(CargoTestMessage { target: self.target.clone(), output: CargoTestOutput::Finished })
77 }
78}
79
80#[derive(Debug)]
81pub(crate) struct CargoTestHandle {
82 _handle: CommandHandle<CargoTestMessage>,
83}
84
85#[derive(Debug, Clone)]
90pub(crate) struct TestTarget {
91 pub package: String,
92 pub target: String,
93 pub kind: TargetKind,
94}
95
96impl CargoTestHandle {
97 pub(crate) fn new(
98 path: Option<&str>,
99 options: CargoOptions,
100 root: &AbsPath,
101 ws_target_dir: Option<&Utf8Path>,
102 test_target: TestTarget,
103 sender: Sender<CargoTestMessage>,
104 ) -> std::io::Result<Self> {
105 let mut cmd = toolchain::command(Tool::Cargo.path(), root, &options.extra_env);
106 cmd.env("RUSTC_BOOTSTRAP", "1");
107 cmd.arg("--color=always");
108 cmd.arg("test");
109
110 cmd.arg("--package");
111 cmd.arg(&test_target.package);
112
113 if let TargetKind::Lib { .. } = test_target.kind {
114 cmd.arg("--lib");
116 } else if let Some(cargo_target) = test_target.kind.as_cargo_target() {
117 cmd.arg(format!("--{cargo_target}"));
118 cmd.arg(&test_target.target);
119 } else {
120 tracing::warn!("Running test for unknown cargo target {:?}", test_target.kind);
121 }
122
123 cmd.arg("--no-fail-fast");
125 cmd.arg("--manifest-path");
126 cmd.arg(root.join("Cargo.toml"));
127 options.apply_on_command(&mut cmd, ws_target_dir);
128 cmd.arg("--");
129 if let Some(path) = path {
130 cmd.arg(path);
131 }
132 cmd.args(["-Z", "unstable-options"]);
133 cmd.arg("--format=json");
134
135 for extra_arg in options.extra_test_bin_args {
136 cmd.arg(extra_arg);
137 }
138
139 Ok(Self {
140 _handle: CommandHandle::spawn(
141 cmd,
142 CargoTestOutputParser::new(&test_target),
143 sender,
144 None,
145 )?,
146 })
147 }
148}