Skip to content

Commit 7920ff8

Browse files
committed
feat: add parse_raw_upload
we want to support parsing raw upload files instead of individual JUnit XML files the input of this new function is the raw upload in byte form the output is a messagepacked binary payload containing the results of the parsing and the raw upload in readable format in byte form
1 parent 0aff0d2 commit 7920ff8

File tree

4 files changed

+468
-7
lines changed

4 files changed

+468
-7
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use pyo3::prelude::*;
44
mod compute_name;
55
mod failure_message;
66
mod junit;
7+
mod raw_upload;
78
mod testrun;
89

910
pyo3::create_exception!(test_results_parser, ParserError, PyException);
@@ -18,6 +19,7 @@ fn test_results_parser(py: Python, m: &Bound<PyModule>) -> PyResult<()> {
1819
m.add_class::<testrun::Framework>()?;
1920
m.add_class::<testrun::ParsingInfo>()?;
2021

22+
m.add_function(wrap_pyfunction!(raw_upload::parse_raw_upload, m)?)?;
2123
m.add_function(wrap_pyfunction!(junit::parse_junit_xml, m)?)?;
2224
m.add_function(wrap_pyfunction!(failure_message::build_message, m)?)?;
2325
m.add_function(wrap_pyfunction!(failure_message::escape_message, m)?)?;

src/raw_upload.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use base64::prelude::*;
2+
use pyo3::prelude::*;
3+
use std::collections::HashSet;
4+
use std::io::prelude::*;
5+
6+
use flate2::bufread::ZlibDecoder;
7+
8+
use quick_xml::reader::Reader;
9+
use serde::Deserialize;
10+
11+
use crate::junit::{get_position_info, use_reader};
12+
use crate::testrun::ParsingInfo;
13+
use crate::ParserError;
14+
15+
#[derive(Deserialize, Debug, Clone)]
16+
struct TestResultFile {
17+
filename: String,
18+
#[serde(skip_deserializing)]
19+
_format: String,
20+
data: String,
21+
#[serde(skip_deserializing)]
22+
_labels: Vec<String>,
23+
}
24+
#[derive(Deserialize, Debug, Clone)]
25+
struct RawTestResultUpload {
26+
#[serde(default)]
27+
network: Option<Vec<String>>,
28+
test_results_files: Vec<TestResultFile>,
29+
}
30+
31+
#[derive(Debug, Clone)]
32+
struct ReadableFile {
33+
filename: Vec<u8>,
34+
data: Vec<u8>,
35+
}
36+
37+
const LEGACY_FORMAT_PREFIX: &[u8] = b"# path=";
38+
const LEGACY_FORMAT_SUFFIX: &[u8] = b"<<<<<< EOF";
39+
40+
fn serialize_to_legacy_format(readable_files: Vec<ReadableFile>) -> Vec<u8> {
41+
let mut res = Vec::new();
42+
for file in readable_files {
43+
res.extend_from_slice(LEGACY_FORMAT_PREFIX);
44+
res.extend_from_slice(&file.filename);
45+
res.extend_from_slice(b"\n");
46+
res.extend_from_slice(&file.data);
47+
res.extend_from_slice(b"\n");
48+
res.extend_from_slice(LEGACY_FORMAT_SUFFIX);
49+
res.extend_from_slice(b"\n");
50+
}
51+
res
52+
}
53+
54+
#[pyfunction]
55+
#[pyo3(signature = (raw_upload_bytes))]
56+
pub fn parse_raw_upload(raw_upload_bytes: &[u8]) -> PyResult<(Vec<u8>, Vec<u8>)> {
57+
let upload: RawTestResultUpload = serde_json::from_slice(raw_upload_bytes)
58+
.map_err(|e| ParserError::new_err(format!("Error deserializing json: {}", e)))?;
59+
let network: Option<HashSet<String>> = upload.network.map(|v| v.into_iter().collect());
60+
61+
let mut results: Vec<ParsingInfo> = Vec::new();
62+
let mut readable_files: Vec<ReadableFile> = Vec::new();
63+
64+
for file in upload.test_results_files {
65+
let decoded_file_bytes = BASE64_STANDARD
66+
.decode(file.data)
67+
.map_err(|e| ParserError::new_err(format!("Error decoding base64: {}", e)))?;
68+
69+
let mut decoder = ZlibDecoder::new(&decoded_file_bytes[..]);
70+
71+
let mut decompressed_file_bytes = Vec::new();
72+
decoder
73+
.read_to_end(&mut decompressed_file_bytes)
74+
.map_err(|e| ParserError::new_err(format!("Error decompressing file: {}", e)))?;
75+
76+
let mut reader = Reader::from_reader(&decompressed_file_bytes[..]);
77+
reader.config_mut().trim_text(true);
78+
let reader_result = use_reader(&mut reader, network.as_ref()).map_err(|e| {
79+
let pos = reader.buffer_position();
80+
let (line, col) = get_position_info(&decompressed_file_bytes, pos.try_into().unwrap());
81+
ParserError::new_err(format!(
82+
"Error parsing JUnit XML at {}:{}: {}",
83+
line, col, e
84+
))
85+
})?;
86+
results.push(reader_result);
87+
88+
let readable_file = ReadableFile {
89+
data: decompressed_file_bytes,
90+
filename: file.filename.into_bytes(),
91+
};
92+
readable_files.push(readable_file);
93+
}
94+
95+
let results_bytes = rmp_serde::to_vec_named(&results)
96+
.map_err(|_| ParserError::new_err("Error serializing pr comment summary"))?;
97+
98+
let readable_file = serialize_to_legacy_format(readable_files);
99+
100+
Ok((results_bytes, readable_file))
101+
}

src/testrun.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::fmt::Display;
33
use pyo3::class::basic::CompareOp;
44
use pyo3::{prelude::*, pyclass};
55

6+
use serde::Serialize;
7+
68
#[derive(Clone, Copy, Debug, PartialEq)]
79
// See https://github.com/PyO3/pyo3/issues/4723
810
#[allow(ambiguous_associated_items)]
@@ -37,13 +39,22 @@ impl Outcome {
3739
}
3840
}
3941

42+
impl Serialize for Outcome {
43+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44+
where
45+
S: serde::Serializer,
46+
{
47+
serializer.serialize_str(&self.to_string())
48+
}
49+
}
50+
4051
impl Display for Outcome {
4152
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4253
match &self {
43-
Outcome::Pass => write!(f, "Pass"),
44-
Outcome::Failure => write!(f, "Failure"),
45-
Outcome::Error => write!(f, "Error"),
46-
Outcome::Skip => write!(f, "Skip"),
54+
Outcome::Pass => write!(f, "pass"),
55+
Outcome::Failure => write!(f, "failure"),
56+
Outcome::Error => write!(f, "error"),
57+
Outcome::Skip => write!(f, "skip"),
4758
}
4859
}
4960
}
@@ -77,7 +88,7 @@ pub fn check_testsuites_name(testsuites_name: &str) -> Option<Framework> {
7788
.next()
7889
}
7990

80-
#[derive(Clone, Debug, PartialEq)]
91+
#[derive(Clone, Debug, PartialEq, Serialize)]
8192
#[pyclass]
8293
pub struct Testrun {
8394
#[pyo3(get, set)]
@@ -189,7 +200,7 @@ impl Testrun {
189200
}
190201
}
191202

192-
#[derive(Clone, Copy, Debug, PartialEq)]
203+
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
193204
#[pyclass(eq, eq_int)]
194205
pub enum Framework {
195206
Pytest,
@@ -221,7 +232,7 @@ impl Display for Framework {
221232
}
222233
}
223234

224-
#[derive(Clone, Debug)]
235+
#[derive(Clone, Debug, Serialize)]
225236
#[pyclass]
226237
pub struct ParsingInfo {
227238
#[pyo3(get, set)]

0 commit comments

Comments
 (0)