Skip to content

Commit

Permalink
Use rstest parameterized tests in some cases.
Browse files Browse the repository at this point in the history
The advantage of this type of test (macro-generated test functions)
over the previous style is that each one can succeed or fail
independently. `all_is_cubes::util::MultiFailure` handles multiple
failures but doesn’t list them as separate tests. I’m not sure if
this is a good idea overall, though; it might be worse for build time
since there are more functions to compile, or it might be better than
`MultiFailure` since there is no closure.
  • Loading branch information
kpreid committed Nov 5, 2024
1 parent 25c6514 commit 5872bc9
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 254 deletions.
131 changes: 83 additions & 48 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ re_sdk = { version = "0.19.0", default-features = false }
re_types = { version = "0.19.0", default-features = false }
rectangle-pack = { version = "0.4.2" } # currently only used in all-is-cubes-port which is currently std only
rendiff = { version = "0.2.0", default-features = false }
# We use rstest for parameterized tests where each case can fail or succeed independently.
# If there are too many cases to write literally, use `all_is_cubes::util::MultiFailure` instead.
rstest = { version = "0.23.0", default-features = false }
scopeguard = { version = "1.2.0", default-features = false }
# "futures" feature is used by all-is-cubes-gpu, but it's small so we enable it everywhere
send_wrapper = { version = "0.6.0", default-features = false, features = ["futures"] }
Expand Down
3 changes: 2 additions & 1 deletion all-is-cubes-mesh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ rayon = { workspace = true, optional = true }
[dev-dependencies]
# used for visualize-block-mesh example
all-is-cubes-content = { workspace = true }
pollster = { workspace = true }
criterion = { workspace = true }
pollster = { workspace = true }
pretty_assertions = { workspace = true }
rstest = { workspace = true }

[lints]
workspace = true
102 changes: 47 additions & 55 deletions all-is-cubes-mesh/src/block_mesh/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,63 +306,55 @@ mod tests {
}

/// Exercise the analysis on the outputs of `make_slab()`.
#[test]
fn analyze_slab() {
#[rstest::rstest]
fn analyze_slab(#[values(0, 1, 2, 3, 4)] thickness: i32) {
let mut u = Universe::new();
for thickness in [0, 1, 2, 3, 4] {
eprintln!("thickness {thickness}:");
let slab = all_is_cubes::content::make_slab(&mut u, thickness, Resolution::R4);
let ev = slab.evaluate().unwrap();
let analysis = analyze(
ev.resolution(),
ev.voxels().as_vol_ref(),
&mut Viz::disabled(),
);

let occupied_planes = FaceMap::from_fn(|f| analysis.occupied_planes(f).collect_vec());
if thickness == 0 {
assert_eq!(analysis.needs_texture, false, "needs_texture");
assert_eq!(
occupied_planes,
FaceMap::default(), // all empty
);
} else {
assert_eq!(analysis.needs_texture, true, "needs_texture");
// Note: the orientation of these rects depends on the arbitrary choices of
// Face6::face_transform().
assert_eq!(
occupied_planes,
FaceMap {
nx: vec![(
0,
Rect::from_origin_and_size(point2(0, 0), size2(thickness, 4))
)],
ny: vec![(0, Rect::from_origin_and_size(point2(0, 0), size2(4, 4)))],
nz: vec![(
0,
Rect::from_origin_and_size(point2(0, 0), size2(4, thickness))
)],
px: vec![(
0,
Rect::from_origin_and_size(
point2(4 - thickness, 0),
size2(thickness, 4)
)
)],
py: vec![(
4 - thickness,
Rect::from_origin_and_size(point2(0, 0), size2(4, 4))
)],
pz: vec![(
0,
Rect::from_origin_and_size(
point2(0, 4 - thickness),
size2(4, thickness)
)
)],
}
);
}
let slab = all_is_cubes::content::make_slab(&mut u, thickness, Resolution::R4);
let ev = slab.evaluate().unwrap();
let analysis = analyze(
ev.resolution(),
ev.voxels().as_vol_ref(),
&mut Viz::disabled(),
);

let occupied_planes = FaceMap::from_fn(|f| analysis.occupied_planes(f).collect_vec());
if thickness == 0 {
assert_eq!(analysis.needs_texture, false, "needs_texture");
assert_eq!(
occupied_planes,
FaceMap::default(), // all empty
);
} else {
assert_eq!(analysis.needs_texture, true, "needs_texture");
// Note: the orientation of these rects depends on the arbitrary choices of
// Face6::face_transform().
assert_eq!(
occupied_planes,
FaceMap {
nx: vec![(
0,
Rect::from_origin_and_size(point2(0, 0), size2(thickness, 4))
)],
ny: vec![(0, Rect::from_origin_and_size(point2(0, 0), size2(4, 4)))],
nz: vec![(
0,
Rect::from_origin_and_size(point2(0, 0), size2(4, thickness))
)],
px: vec![(
0,
Rect::from_origin_and_size(point2(4 - thickness, 0), size2(thickness, 4))
)],
py: vec![(
4 - thickness,
Rect::from_origin_and_size(point2(0, 0), size2(4, 4))
)],
pz: vec![(
0,
Rect::from_origin_and_size(point2(0, 4 - thickness), size2(4, thickness))
)],
}
);
}
}

Expand Down
1 change: 1 addition & 0 deletions all-is-cubes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ allocation-counter = { workspace = true }
criterion = { workspace = true }
futures-util = { workspace = true }
pretty_assertions = { workspace = true }
rstest = { workspace = true }
serde_json = { workspace = true }
# Using tokio for async test-running.
tokio = { workspace = true, features = ["macros", "rt", "sync"] }
Expand Down
51 changes: 26 additions & 25 deletions all-is-cubes/src/block/eval/derived.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,34 +433,35 @@ mod tests {
///
/// TODO: A more thorough test would be to double the resolution of a non-uniform block,
/// though that might have rounding error.
#[test]
fn solid_block_equivalent_at_any_resolution() {
let mut attributes = block::BlockAttributes::default();
attributes.display_name = "foo".into();

for color in [
#[rstest::rstest]
fn solid_block_equivalent_at_any_resolution(
#[values(
Rgba::BLACK,
Rgba::WHITE,
Rgba::TRANSPARENT,
Rgba::new(0.0, 0.5, 1.0, 0.5),
] {
let voxel = Evoxel::from_color(color);
let ev_one = compute_derived(&attributes, &Evoxels::from_one(voxel));
let ev_many = compute_derived(
&attributes,
&Evoxels::from_many(R2, Vol::from_fn(GridAab::for_block(R2), |_| voxel)),
);

// Check that the derived attributes are all identical (except for the opacity mask),
assert_eq!(
Derived {
voxel_opacity_mask: ev_one.voxel_opacity_mask.clone(),
..ev_many
},
ev_one,
"Input color {color:?}"
);
}
Rgba::new(0.0, 0.5, 1.0, 0.5)
)]
color: Rgba,
) {
let mut attributes = block::BlockAttributes::default();
attributes.display_name = "foo".into();

let voxel = Evoxel::from_color(color);
let ev_one = compute_derived(&attributes, &Evoxels::from_one(voxel));
let ev_many = compute_derived(
&attributes,
&Evoxels::from_many(R2, Vol::from_fn(GridAab::for_block(R2), |_| voxel)),
);

// Check that the derived attributes are all identical (except for the opacity mask),
assert_eq!(
Derived {
voxel_opacity_mask: ev_one.voxel_opacity_mask.clone(),
..ev_many
},
ev_one,
"Input color {color:?}"
);
}

// Unit tests for `VoxSum`'s math. `VoxSum` is an internal helper type, so if there is reason
Expand Down
52 changes: 27 additions & 25 deletions all-is-cubes/src/block/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use pretty_assertions::assert_eq;
use crate::block::{
self, modifier, AnimationChange, AnimationHint, Atom, Block, BlockAttributes, BlockChange,
BlockCollision, BlockDef, BlockDefTransaction, EvalBlockError, Evoxel, Evoxels, Modifier,
Primitive, Resolution::*, AIR, AIR_EVALUATED,
Primitive,
Resolution::{self, *},
AIR, AIR_EVALUATED,
};
use crate::content::make_some_blocks;
use crate::listen::{self, NullListener, Sink};
Expand Down Expand Up @@ -84,6 +86,8 @@ fn block_debug_with_modifiers() {
}

mod eval {
use rstest::rstest;

use crate::block::{Cost, EvKey, EvaluatedBlock, VoxelOpacityMask};

use super::{assert_eq, *};
Expand Down Expand Up @@ -275,35 +279,33 @@ mod eval {

/// Test that light emission from voxels doesn't depend on resolution, or rather, the emission
/// is taken as an intensive property rather than an extensive property.
#[test]
fn voxels_emission_equivalence() {
#[rstest]
fn voxels_emission_equivalence(
#[values(Rgba::TRANSPARENT, Rgba::new(0.0, 0.5, 1.0, 0.5))] reflectance: Rgba,
#[values(R1, R2, R4, R32)] resolution: Resolution,
) {
let mut universe = Universe::new();

let atom_emission = Rgb::new(1.0, 2.0, 3.0);
for reflectance in [Rgba::TRANSPARENT, Rgba::new(0.0, 0.5, 1.0, 0.5)] {
let atom = Block::builder()
.color(reflectance)
.light_emission(atom_emission)
.build();

for resolution in [R1, R2, R4, R32] {
let voxel_block = Block::builder()
.voxels_fn(resolution, |_| &atom)
.unwrap()
.build_into(&mut universe);

let total_emission: Vector3D<f32, Intensity> =
voxel_block.evaluate().unwrap().light_emission().into();
let difference: Vector3D<f32, Intensity> = total_emission - atom_emission.into();
assert!(
difference.length() < 0.0001,
"reflectance = {reflectance:?}\n\
let atom = Block::builder()
.color(reflectance)
.light_emission(atom_emission)
.build();

let voxel_block = Block::builder()
.voxels_fn(resolution, |_| &atom)
.unwrap()
.build_into(&mut universe);

let total_emission: Vector3D<f32, Intensity> =
voxel_block.evaluate().unwrap().light_emission().into();
let difference: Vector3D<f32, Intensity> = total_emission - atom_emission.into();
assert!(
difference.length() < 0.0001,
"reflectance = {reflectance:?}\n\
resolution = {resolution}\n\
expected = {atom_emission:?}\n\
actual = {total_emission:?}"
);
}
}
);
}

#[test]
Expand Down
Loading

0 comments on commit 5872bc9

Please sign in to comment.