Skip to content

Commit f0d8da5

Browse files
committed
Merge branch 'main' of github.com:codecov/test-results-parser into joseph/parse-raw-upload
2 parents d123ef6 + 0a663e5 commit f0d8da5

File tree

20 files changed

+2095
-69
lines changed

20 files changed

+2095
-69
lines changed

.envrc

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
1-
# using uv-managed python is currently broken because it sets a wrong `libdir`:
2-
# https://github.com/astral-sh/rye/discussions/851
3-
# https://github.com/astral-sh/rye/issues/646
4-
# https://github.com/astral-sh/uv/issues/8879
5-
# https://github.com/astral-sh/uv/issues/8429
6-
# https://github.com/astral-sh/uv/issues/7369
7-
uv sync --no-install-project --python-preference only-system
1+
uv sync --no-install-project
82
source .venv/bin/activate

.github/workflows/bench.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
- run: cargo binstall cargo-codspeed
2020
- uses: Swatinem/rust-cache@v2
2121

22+
- uses: actions/setup-python@v5
23+
2224
- run: cargo codspeed build
2325
- uses: CodSpeedHQ/action@v3
2426
with:

.github/workflows/ci.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ on:
66
- main
77
pull_request:
88

9-
env:
10-
RUSTFLAGS: -Dwarnings
11-
129
jobs:
1310
lint:
1411
name: Lint
1512
runs-on: ubuntu-latest
13+
env:
14+
RUSTFLAGS: -Dwarnings
1615
steps:
1716
- uses: actions/checkout@v4
1817

.gitignore

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
/.*_cache
2+
/.venv
3+
/*.xml
14
/target
2-
.venv
35
__pycache__
4-
.coverage
5-
/*.xml
6+
.coverage

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ watto = { git = "https://github.com/getsentry/watto", features = [
2424
"strings",
2525
"offset_set",
2626
] }
27-
anyhow = "1.0.35"
2827

2928
[dev-dependencies]
3029
criterion = { version = "2.7.2", package = "codspeed-criterion-compat" }
@@ -33,3 +32,8 @@ insta = { version = "1.42.0", features = ["glob", "yaml"] }
3332

3433
[profile.release]
3534
debug = 1
35+
36+
37+
[[bench]]
38+
name = "binary"
39+
harness = false

benches/binary.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use std::hint::black_box;
2+
3+
use criterion::{criterion_group, criterion_main, Criterion};
4+
use rand::distributions::{Alphanumeric, DistString, Distribution, Uniform, WeightedIndex};
5+
use rand::rngs::SmallRng;
6+
use rand::seq::SliceRandom as _;
7+
use rand::{Rng, SeedableRng};
8+
use test_results_parser::binary::*;
9+
use test_results_parser::{Outcome, Testrun};
10+
11+
criterion_group!(benches, binary);
12+
criterion_main!(benches);
13+
14+
const NUM_UPLOADS: usize = 10;
15+
const NUM_TESTS_PER_UPLOAD: usize = 10_000;
16+
const DAY: u32 = 24 * 60 * 60;
17+
18+
fn binary(c: &mut Criterion) {
19+
let rng = &mut SmallRng::seed_from_u64(0);
20+
21+
let mut uploads = create_random_testcases(rng, NUM_UPLOADS, NUM_TESTS_PER_UPLOAD);
22+
randomize_test_data(rng, &mut uploads);
23+
24+
let buf = write_tests(&uploads, 60, 0);
25+
let buf_1 = write_tests(&uploads[..NUM_UPLOADS - 1], 60, 0);
26+
randomize_test_data(rng, &mut uploads);
27+
let buf_2 = write_tests(&uploads[1..], 60, 1 * DAY);
28+
29+
c.benchmark_group("binary")
30+
.throughput(criterion::Throughput::Elements(NUM_TESTS_PER_UPLOAD as u64))
31+
.sample_size(10) // because with the configured `NUM_TESTS`, each iteration would run >100ms
32+
.bench_function("create_and_serialize", |b| {
33+
b.iter(|| {
34+
write_tests(&uploads, 60, 0);
35+
})
36+
})
37+
.bench_function("read_aggregation", |b| {
38+
b.iter(|| {
39+
let parsed = TestAnalytics::parse(&buf, 0).unwrap();
40+
for test in parsed.tests(0..60, None).unwrap() {
41+
let test = test.unwrap();
42+
let _name = black_box(test.name().unwrap());
43+
let _aggregates = black_box(test.aggregates());
44+
}
45+
})
46+
})
47+
.bench_function("update_same", |b| {
48+
b.iter(|| {
49+
let parsed = TestAnalytics::parse(&buf, 1).unwrap();
50+
let mut writer = TestAnalyticsWriter::from_existing_format(&parsed).unwrap();
51+
let mut flags = vec![];
52+
for upload in &uploads {
53+
flags.clear();
54+
flags.extend(upload.flags.iter().map(String::as_str));
55+
let mut session = writer.start_session(1, CommitHash::default(), &flags);
56+
for test in &upload.tests {
57+
session.insert(test);
58+
}
59+
}
60+
61+
let mut buf = vec![];
62+
writer.serialize(&mut buf).unwrap();
63+
buf
64+
})
65+
})
66+
.bench_function("update_different", |b| {
67+
b.iter(|| {
68+
let parsed = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap();
69+
let mut writer = TestAnalyticsWriter::from_existing_format(&parsed).unwrap();
70+
let mut flags = vec![];
71+
for upload in uploads.iter().skip(1) {
72+
flags.clear();
73+
flags.extend(upload.flags.iter().map(String::as_str));
74+
let mut session = writer.start_session(1, CommitHash::default(), &flags);
75+
for test in &upload.tests {
76+
session.insert(test);
77+
}
78+
}
79+
80+
let mut buf = vec![];
81+
writer.serialize(&mut buf).unwrap();
82+
buf
83+
})
84+
})
85+
.bench_function("merge", |b| {
86+
b.iter(|| {
87+
let parsed_1 = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap();
88+
let parsed_2 = TestAnalytics::parse(&buf_2, 1 * DAY).unwrap();
89+
let writer = TestAnalyticsWriter::merge(&parsed_1, &parsed_2).unwrap();
90+
91+
let mut buf = vec![];
92+
writer.serialize(&mut buf).unwrap();
93+
buf
94+
})
95+
})
96+
.bench_function("merge_rewrite", |b| {
97+
b.iter(|| {
98+
let parsed_1 = TestAnalytics::parse(&buf_1, 1 * DAY).unwrap();
99+
let parsed_2 = TestAnalytics::parse(&buf_2, 1 * DAY).unwrap();
100+
let mut writer = TestAnalyticsWriter::merge(&parsed_1, &parsed_2).unwrap();
101+
102+
writer.rewrite(60, 1 * DAY, Some(0)).unwrap();
103+
104+
let mut buf = vec![];
105+
writer.serialize(&mut buf).unwrap();
106+
buf
107+
})
108+
});
109+
}
110+
111+
fn write_tests(uploads: &[Upload], num_days: usize, timestamp: u32) -> Vec<u8> {
112+
let mut writer = TestAnalyticsWriter::new(num_days);
113+
let mut flags = vec![];
114+
for upload in uploads {
115+
flags.clear();
116+
flags.extend(upload.flags.iter().map(String::as_str));
117+
let mut session = writer.start_session(timestamp, CommitHash::default(), &flags);
118+
for test in &upload.tests {
119+
session.insert(test);
120+
}
121+
}
122+
123+
let mut buf = vec![];
124+
writer.serialize(&mut buf).unwrap();
125+
buf
126+
}
127+
128+
struct Upload {
129+
flags: Vec<String>,
130+
tests: Vec<Testrun>,
131+
}
132+
133+
/// Generates a random set of `num_flags` flags.
134+
fn create_random_flags(rng: &mut impl Rng, num_flags: usize) -> Vec<String> {
135+
let flag_lens = Uniform::from(5usize..10);
136+
(0..num_flags)
137+
.map(|_| {
138+
let flag_len = flag_lens.sample(rng);
139+
Alphanumeric.sample_string(rng, flag_len)
140+
})
141+
.collect()
142+
}
143+
144+
/// Samples random combinations of flags with length `max_flags_in_set`.
145+
fn sample_flag_sets<'a>(
146+
rng: &'a mut impl Rng,
147+
flags: &'a [String],
148+
max_flags_in_set: usize,
149+
) -> impl Iterator<Item = Vec<String>> + 'a {
150+
let num_flags = Uniform::from(0..max_flags_in_set);
151+
std::iter::from_fn(move || {
152+
let num_flags = num_flags.sample(rng);
153+
let flags: Vec<_> = flags.choose_multiple(rng, num_flags).cloned().collect();
154+
Some(flags)
155+
})
156+
}
157+
158+
fn create_random_testcases(
159+
rng: &mut impl Rng,
160+
num_uploads: usize,
161+
num_tests_per_upload: usize,
162+
) -> Vec<Upload> {
163+
let flags = create_random_flags(rng, 5);
164+
let flag_sets: Vec<_> = sample_flag_sets(rng, &flags, 3)
165+
.take(num_uploads / 3)
166+
.collect();
167+
let name_lens = Uniform::from(5usize..50);
168+
169+
(0..num_uploads)
170+
.map(|_| {
171+
let flags = flag_sets.choose(rng).cloned().unwrap_or_default();
172+
let tests = (0..num_tests_per_upload)
173+
.map(|_| {
174+
let name_len = name_lens.sample(rng);
175+
let name = Alphanumeric.sample_string(rng, name_len);
176+
177+
Testrun {
178+
name,
179+
classname: "".into(),
180+
duration: Some(1.0),
181+
outcome: Outcome::Pass,
182+
testsuite: "".into(),
183+
failure_message: None,
184+
filename: None,
185+
build_url: None,
186+
computed_name: None,
187+
}
188+
})
189+
.collect();
190+
Upload { flags, tests }
191+
})
192+
.collect()
193+
}
194+
195+
fn randomize_test_data(rng: &mut impl Rng, uploads: &mut [Upload]) {
196+
let durations = Uniform::from(0f64..10f64);
197+
let outcomes = WeightedIndex::new([1000, 10, 20]).unwrap();
198+
199+
for upload in uploads {
200+
for test in &mut upload.tests {
201+
test.duration = Some(durations.sample(rng));
202+
test.outcome = match outcomes.sample(rng) {
203+
0 => Outcome::Pass,
204+
1 => Outcome::Skip,
205+
_ => Outcome::Failure,
206+
};
207+
}
208+
}
209+
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ features = ["pyo3/extension-module"]
1818
[tool.uv]
1919
dev-dependencies = [
2020
"pytest>=8.3.3",
21-
"pytest-cov>=5.0.0",
21+
"pytest-cov>=6.0.0",
2222
"pytest-reportlog>=0.4.0",
2323
"maturin>=1.7.4",
2424
"msgpack>=1.1.0",

0 commit comments

Comments
 (0)