Skip to content

Commit 618d12d

Browse files
authored
[agent] Inject test env and test command to test.xml (#1696)
Previously, when looking at flaky tests in Test Analytics UI, it's hard to tell which platform the test was run on or which test it is in the testsuite. For example: <img width="1218" alt="Screenshot 2023-06-15 at 15 22 22" src="https://github.com/bazelbuild/continuous-integration/assets/5947531/6477c1eb-f754-4bdd-8213-ace8ab5cbea8"> This PR updates the uploader to inject some information about the test run into the `test.xml`, e.g. platform, test name and test command. Now it test log displayed in Test Analytics UI becomes: <img width="1254" alt="Screenshot 2023-06-15 at 15 24 14" src="https://github.com/bazelbuild/continuous-integration/assets/5947531/8bd0b6de-59c0-4237-8f70-25c40cfdffbb">
1 parent e5c791f commit 618d12d

File tree

1 file changed

+84
-9
lines changed

1 file changed

+84
-9
lines changed

agent/src/artifact/upload.rs

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::{anyhow, Context, Result};
2-
use quick_xml::events::BytesStart;
2+
use quick_xml::events::{BytesCData, BytesEnd, BytesStart};
33
use serde_json::Value;
44
use sha1::{Digest, Sha1};
55
use std::{
@@ -213,7 +213,19 @@ fn upload_bep_json_file(
213213
uploader.upload_artifact(dry, None, build_event_json_file, mode)
214214
}
215215

216-
fn parse_test_xml(path: &Path, label: &str) -> Result<Option<Vec<u8>>> {
216+
fn gen_error_content(bazelci_task: &str, label: &str, name: &str, test_log: &str) -> String {
217+
let mut buf = String::new();
218+
buf.push_str(&format!("BAZELCI_TASK={}\n", bazelci_task));
219+
buf.push_str(&format!("TEST_TARGET={}\n", label));
220+
buf.push_str(&format!("TEST_NAME={}\n", name));
221+
buf.push_str("\n");
222+
buf.push_str(&format!("bazel test {} --test_filter={}\n", &label, &name));
223+
buf.push_str("\n\n");
224+
buf.push_str(test_log);
225+
buf
226+
}
227+
228+
fn parse_test_xml(path: &Path, bazelci_task: &str, label: &str) -> Result<Option<Vec<u8>>> {
217229
use quick_xml::{events::Event, reader::Reader, writer::Writer};
218230
use std::io::Cursor;
219231

@@ -222,10 +234,17 @@ fn parse_test_xml(path: &Path, label: &str) -> Result<Option<Vec<u8>>> {
222234
let mut buf = Vec::new();
223235
let mut reader = Reader::from_file(&path)?;
224236
let fallback_classname = label.replace("//", "").replace("/", ".").replace(":", ".");
237+
let mut in_error_tag = false;
238+
let mut error_tag_stack = 0;
239+
let mut name = String::new();
225240
loop {
226241
match reader.read_event_into(&mut buf)? {
227242
Event::Eof => break,
228243
Event::Start(tag) => {
244+
if in_error_tag {
245+
error_tag_stack += 1;
246+
}
247+
229248
let tag = match tag.name().as_ref() {
230249
b"testcase" => {
231250
has_testcase = true;
@@ -241,6 +260,8 @@ fn parse_test_xml(path: &Path, label: &str) -> Result<Option<Vec<u8>>> {
241260
if attr.value.len() == 0 {
242261
attr.value = fallback_classname.clone().into_bytes().into();
243262
}
263+
} else if attr.key.as_ref() == b"name" {
264+
name = String::from_utf8_lossy(&attr.value).to_string();
244265
}
245266
new_tag.push_attribute(attr);
246267
}
@@ -251,10 +272,52 @@ fn parse_test_xml(path: &Path, label: &str) -> Result<Option<Vec<u8>>> {
251272

252273
new_tag
253274
}
275+
b"failure" => {
276+
in_error_tag = true;
277+
// replace failure with error
278+
let mut new_tag = BytesStart::new("error");
279+
new_tag.push_attribute(("message", ""));
280+
new_tag
281+
}
282+
b"error" => {
283+
in_error_tag = true;
284+
tag
285+
}
254286
_ => tag,
255287
};
256288
writer.write_event(Event::Start(tag))?;
257289
}
290+
Event::CData(mut cdata) => {
291+
if in_error_tag {
292+
let test_log = String::from_utf8_lossy(&*cdata);
293+
let new_content = gen_error_content(bazelci_task, label, &name, &test_log);
294+
cdata = BytesCData::new(new_content);
295+
}
296+
297+
writer.write_event(Event::CData(cdata))?;
298+
}
299+
Event::Text(text) => {
300+
if in_error_tag {
301+
let test_log = String::from_utf8_lossy(&*text);
302+
let new_content = gen_error_content(bazelci_task, label, &name, &test_log);
303+
let cdata = BytesCData::new(new_content);
304+
writer.write_event(Event::CData(cdata))?;
305+
} else {
306+
writer.write_event(Event::Text(text))?;
307+
}
308+
}
309+
Event::End(mut tag) => {
310+
if in_error_tag {
311+
if error_tag_stack > 0 {
312+
error_tag_stack -= 1;
313+
} else {
314+
in_error_tag = false;
315+
tag = BytesEnd::new("error");
316+
}
317+
}
318+
319+
writer.write_event(Event::End(tag))?;
320+
}
258321
e => writer.write_event(e)?,
259322
}
260323
}
@@ -366,18 +429,19 @@ impl Uploader {
366429
dry: bool,
367430
cwd: Option<&Path>,
368431
token: &str,
369-
data: &Path,
432+
bazelci_task: &str,
433+
test_xml: &Path,
370434
label: &str,
371435
format: &str,
372436
forms: &[(impl AsRef<str>, impl AsRef<str>)],
373437
) -> Result<()> {
374438
let full_path = if let Some(cwd) = cwd {
375-
cwd.join(data)
439+
cwd.join(test_xml)
376440
} else {
377-
data.to_path_buf()
441+
test_xml.to_path_buf()
378442
};
379443

380-
let content = parse_test_xml(&full_path, label)?;
444+
let content = parse_test_xml(&full_path, &bazelci_task, label)?;
381445
if content.is_none() {
382446
return Ok(());
383447
}
@@ -441,18 +505,21 @@ impl Uploader {
441505
let token = maybe_get_env("BUILDKITE_ANALYTICS_TOKEN");
442506
let build_id = maybe_get_env("BUILDKITE_BUILD_ID");
443507
let step_id = maybe_get_env("BUILDKITE_STEP_ID");
444-
if token.is_none() || build_id.is_none() || step_id.is_none() {
508+
let bazelci_task = maybe_get_env("BAZELCI_TASK");
509+
if token.is_none() || build_id.is_none() || step_id.is_none() || bazelci_task.is_none() {
445510
return Ok(());
446511
}
447512

448513
let token = token.unwrap();
449514
let build_id = build_id.unwrap();
450515
let step_id = step_id.unwrap();
516+
let bazelci_task = bazelci_task.unwrap();
451517

452518
let mut forms = vec![
453519
("run_env[CI]", "buildkite".to_string()),
454520
("run_env[key]", format!("{}/{}", &build_id, &step_id)),
455521
("run_env[build_id]", build_id),
522+
("run_env[tags][]", bazelci_task.clone()),
456523
];
457524

458525
for (name, env) in [
@@ -462,14 +529,22 @@ impl Uploader {
462529
("run_env[number]", "BUILDKITE_BUILD_NUMBER"),
463530
("run_env[job_id]", "BUILDKITE_JOB_ID"),
464531
("run_env[message]", "BUILDKITE_MESSAGE"),
465-
("run_env[tags][]", "BAZELCI_TASK"),
466532
] {
467533
if let Some(value) = maybe_get_env(env) {
468534
forms.push((name, value));
469535
}
470536
}
471537

472-
self.upload_test_analytics(dry, cwd, &token, test_xml, label, "junit", &forms)
538+
self.upload_test_analytics(
539+
dry,
540+
cwd,
541+
&token,
542+
&bazelci_task,
543+
test_xml,
544+
label,
545+
"junit",
546+
&forms,
547+
)
473548
}
474549
}
475550

0 commit comments

Comments
 (0)