Skip to content

Commit bae80b0

Browse files
Byronpascalkuthe
authored andcommitted
improve baseline tests to be more practical
- don't run them unless it's a release build - make the existence of atomic tests more obvious with `make` - share code between both baseline variants - some output to learn about progress
1 parent b42ac1e commit bae80b0

File tree

4 files changed

+128
-182
lines changed

4 files changed

+128
-182
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ CARGO = $(shell command -v cargo)
66

77
##@ Development
88

9+
baseline-atomic: ## run very slow tests that single-step through all commits
10+
RUST_BACKTRACE=1 cargo test --test baseline_atomic --release -- --nocapture
11+
912
test: ## run all tests with cargo
1013
RUST_BACKTRACE=1 cargo test --test crates-index-diff
1114
RUST_BACKTRACE=1 cargo test --test baseline --release

tests/baseline.rs

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,8 @@
1-
use std::collections::HashSet;
2-
3-
use crates_index_diff::Change::*;
1+
use crate::shared::Step;
2+
mod shared;
43

4+
#[cfg_attr(debug_assertions, ignore)]
55
#[test]
6-
fn all_aggregrated_diffs_equal_latest_version(
7-
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
8-
let ((expected, baseline_duration), (actual, diff_duration)) = std::thread::scope(
9-
|scope| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
10-
let baseline = scope.spawn(|| -> Result<_, crates_index::Error> {
11-
let index = crates_index::Index::new_cargo_default()?;
12-
let start = std::time::Instant::now();
13-
let mut versions = HashSet::new();
14-
for krate in index.crates() {
15-
for version in krate.versions() {
16-
versions.insert(version.checksum().to_owned());
17-
}
18-
}
19-
Ok((versions, start.elapsed()))
20-
});
21-
let actual = scope.spawn(|| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
22-
use crates_index_diff::git;
23-
24-
let start = std::time::Instant::now();
25-
let repo_path = crates_index::Index::new_cargo_default()?.path().to_owned();
26-
let index = crates_index_diff::Index::from_path_or_cloned(repo_path)?;
27-
let repo = index.repository();
28-
let head = repo
29-
.find_reference("FETCH_HEAD")
30-
.or_else(|_| repo.find_reference("HEAD"))?
31-
.id();
32-
let mut commits = head
33-
.ancestors()
34-
.first_parent_only()
35-
.all()?
36-
.map(|id| id.map(|id| id.detach()))
37-
.collect::<Result<Vec<_>, _>>()?;
38-
commits.push(head.detach());
39-
40-
// This could be more complex, like jumping to landmarks like 'Delete crate(s)' and so forth.
41-
let partitions = 4;
42-
let chunk_size = (commits.len() / partitions).max(1);
43-
let mut steps = (0..commits.len()).step_by(chunk_size).collect::<Vec<_>>();
44-
if *steps.last().expect("at least 1") != commits.len() - 1 {
45-
steps.push(commits.len() - 1);
46-
}
47-
48-
let mut versions = HashSet::default();
49-
let mut previous = None;
50-
for current in steps.into_iter().map(|idx| commits[idx].to_owned()) {
51-
let old = previous
52-
.unwrap_or_else(|| git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1));
53-
previous = Some(current);
54-
55-
let changes = index.changes_between_commits(old, current)?;
56-
for change in changes {
57-
match change {
58-
Added(v) | AddedAndYanked(v) => {
59-
// found a new crate, add it to the index
60-
versions.insert(v.checksum.to_owned());
61-
}
62-
Unyanked(v) | Yanked(v) => {
63-
// yanked/unyanked crates must be part of the index
64-
assert!(versions.contains(&v.checksum))
65-
}
66-
Deleted {
67-
versions: deleted, ..
68-
} => {
69-
// delete a yanked crate
70-
for deleted_version in deleted {
71-
versions.remove(&deleted_version.checksum);
72-
}
73-
}
74-
}
75-
}
76-
}
77-
Ok((versions, start.elapsed()))
78-
});
79-
80-
Ok((
81-
baseline.join().expect("no panic")?,
82-
actual.join().expect("no panic")?,
83-
))
84-
},
85-
)?;
86-
87-
dbg!(baseline_duration, expected.len());
88-
dbg!(diff_duration, actual.len());
89-
assert_eq!(
90-
actual.len(),
91-
expected.len(),
92-
"aggregated of all changes produces the final result"
93-
);
94-
assert!(actual.eq(&expected), "actual should be exactly the same");
95-
96-
Ok(())
6+
fn big_steps() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
7+
shared::baseline(Step::Partitioned { size: 4 })
978
}

tests/baseline_atomic.rs

Lines changed: 5 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,9 @@
1-
use std::collections::HashSet;
1+
use crate::shared::Step;
22

3-
use crates_index_diff::Change::*;
3+
mod shared;
44

5+
#[cfg_attr(debug_assertions, ignore)]
56
#[test]
6-
#[ignore]
7-
fn all_aggregrated_diffs_equal_latest_version(
8-
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
9-
let ((expected, baseline_duration), (actual, diff_duration)) = std::thread::scope(
10-
|scope| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
11-
let baseline = scope.spawn(|| -> Result<_, crates_index::Error> {
12-
let index = crates_index::Index::new_cargo_default()?;
13-
let start = std::time::Instant::now();
14-
let mut versions = HashSet::new();
15-
for krate in index.crates() {
16-
for version in krate.versions() {
17-
versions.insert(version.checksum().to_owned());
18-
}
19-
}
20-
Ok((versions, start.elapsed()))
21-
});
22-
let actual = scope.spawn(|| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
23-
use crates_index_diff::git;
24-
25-
let start = std::time::Instant::now();
26-
let repo_path = crates_index::Index::new_cargo_default()?.path().to_owned();
27-
let index = crates_index_diff::Index::from_path_or_cloned(repo_path)?;
28-
let repo = index.repository();
29-
let head = repo
30-
.find_reference("FETCH_HEAD")
31-
.or_else(|_| repo.find_reference("HEAD"))?
32-
.id();
33-
let mut commits = head
34-
.ancestors()
35-
.first_parent_only()
36-
.all()?
37-
.map(|id| id.map(|id| id.detach()))
38-
.collect::<Result<Vec<_>, _>>()?;
39-
commits.push(head.detach());
40-
41-
// This could be more complex, like jumping to landmarks like 'Delete crate(s)' and so forth.
42-
43-
let mut versions = HashSet::default();
44-
let mut previous = None;
45-
for current in commits.into_iter() {
46-
let old = previous
47-
.unwrap_or_else(|| git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1));
48-
previous = Some(current);
49-
50-
let changes = index.changes_between_commits(old, current)?;
51-
for change in changes {
52-
match change {
53-
Added(v) | AddedAndYanked(v) => {
54-
// found a new crate, add it to the index
55-
versions.insert(v.checksum.to_owned());
56-
}
57-
Unyanked(v) | Yanked(v) => {
58-
// yanked/unyanked crates must be part of the index
59-
assert!(versions.contains(&v.checksum))
60-
}
61-
Deleted {
62-
versions: deleted, ..
63-
} => {
64-
// delete a yanked crate
65-
for deleted_version in deleted {
66-
versions.remove(&deleted_version.checksum);
67-
}
68-
}
69-
}
70-
}
71-
}
72-
Ok((versions, start.elapsed()))
73-
});
74-
75-
Ok((
76-
baseline.join().expect("no panic")?,
77-
actual.join().expect("no panic")?,
78-
))
79-
},
80-
)?;
81-
82-
dbg!(baseline_duration, expected.len());
83-
dbg!(diff_duration, actual.len());
84-
assert_eq!(
85-
actual.len(),
86-
expected.len(),
87-
"aggregated of all changes produces the final result"
88-
);
89-
assert!(actual.eq(&expected), "actual should be exactly the same");
90-
91-
Ok(())
7+
fn one_per_commit() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
8+
shared::baseline(Step::OnePerCommit)
929
}

tests/shared/mod.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use crates_index_diff::Change::*;
2+
use std::collections::HashSet;
3+
4+
#[allow(dead_code)]
5+
pub enum Step {
6+
Partitioned { size: usize },
7+
OnePerCommit,
8+
}
9+
10+
pub fn baseline(mode: Step) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
11+
let ((expected, baseline_duration), (actual, diff_duration)) = std::thread::scope(
12+
|scope| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
13+
let baseline = scope.spawn(|| -> Result<_, crates_index::Error> {
14+
let index = crates_index::Index::new_cargo_default()?;
15+
let start = std::time::Instant::now();
16+
let mut versions = HashSet::new();
17+
for krate in index.crates() {
18+
for version in krate.versions() {
19+
versions.insert(version.checksum().to_owned());
20+
}
21+
}
22+
Ok((versions, start.elapsed()))
23+
});
24+
let actual = scope.spawn(|| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
25+
use crates_index_diff::git;
26+
27+
let start = std::time::Instant::now();
28+
let repo_path = crates_index::Index::new_cargo_default()?.path().to_owned();
29+
let index = crates_index_diff::Index::from_path_or_cloned(repo_path)?;
30+
let repo = index.repository();
31+
let head = repo.find_reference("refs/remotes/origin/HEAD")?.id();
32+
let commits = head
33+
.ancestors()
34+
.first_parent_only()
35+
.all()?
36+
.map(|id| id.map(|id| id.detach()))
37+
.collect::<Result<Vec<_>, _>>()?;
38+
39+
// This could be more complex, like jumping to landmarks like 'Delete crate(s)' and so forth.
40+
let partitions = match mode {
41+
Step::Partitioned { size } => size,
42+
Step::OnePerCommit => commits.len(),
43+
};
44+
let chunk_size = (commits.len() / partitions).max(1);
45+
let mut steps = (0..commits.len()).step_by(chunk_size).collect::<Vec<_>>();
46+
if *steps.last().expect("at least 1") != commits.len() - 1 {
47+
steps.push(commits.len() - 1);
48+
}
49+
50+
let mut versions = HashSet::default();
51+
let mut previous = None;
52+
let num_steps = steps.len();
53+
for (step, current) in steps
54+
.into_iter()
55+
.rev()
56+
.map(|idx| commits[idx].to_owned())
57+
.enumerate()
58+
{
59+
let old = previous
60+
.unwrap_or_else(|| git::hash::ObjectId::empty_tree(git::hash::Kind::Sha1));
61+
previous = Some(current);
62+
63+
let start = std::time::Instant::now();
64+
let changes = index.changes_between_commits(old, current)?;
65+
let num_changes = changes.len();
66+
for change in changes {
67+
match change {
68+
Added(v) | AddedAndYanked(v) => {
69+
// found a new crate, add it to the index
70+
versions.insert(v.checksum.to_owned());
71+
}
72+
Unyanked(v) | Yanked(v) => {
73+
// yanked/unyanked crates must be part of the index
74+
assert!(versions.contains(&v.checksum))
75+
}
76+
Deleted {
77+
versions: deleted, ..
78+
} => {
79+
// delete a yanked crate
80+
for deleted_version in deleted {
81+
versions.remove(&deleted_version.checksum);
82+
}
83+
}
84+
}
85+
}
86+
let elapsed = start.elapsed().as_secs_f32();
87+
eprintln!(
88+
"Step {} / {} and {} change(s) took {:.02}s ({:.0} changes/s)",
89+
step,
90+
num_steps,
91+
num_changes,
92+
elapsed,
93+
num_changes as f32 / elapsed
94+
);
95+
}
96+
Ok((versions, start.elapsed()))
97+
});
98+
99+
Ok((
100+
baseline.join().expect("no panic")?,
101+
actual.join().expect("no panic")?,
102+
))
103+
},
104+
)?;
105+
106+
dbg!(baseline_duration, expected.len());
107+
dbg!(diff_duration, actual.len());
108+
assert_eq!(
109+
actual.len(),
110+
expected.len(),
111+
"aggregated of all changes produces the final result"
112+
);
113+
assert!(actual.eq(&expected), "actual should be exactly the same");
114+
Ok(())
115+
}

0 commit comments

Comments
 (0)