Repository navigation
Test suite consolidation: fewer test IDs, asserts that can fail, and CLAUDE.md test rules - #307
Merged
Merged
Conversation
test_dataset.py (30 -> 16 IDs):
- Drop the array/name/signal_units getter-setter and repr/str tests; the
origin/sampling/units setters and their length validation stay, as one loop.
- test_mean/max/min only checked types and shapes; replaced by one test that
compares each reduction (all axes, both axes, axis 0) against numpy.
- The four 4D/single-axis crop tests become one table that checks shape and
origin, both out of place and in place; test_crop is kept.
- test_bin and TestBinMetadata merged (shape, sampling, origin, in place).
- Down/upsample resample tests merged into one table with their own center
tolerances; odd/even and API-error tests kept.
- Removed the unused 3D fixtures.
test_dataset4dstem.py (15 -> 8 IDs):
- The six dp_mean/max/median property and get_* tests checked only shapes;
one loop now checks each against numpy and checks the attach/cache contract.
- getitem 2D/3D merged; virtual image now checks the summed values.
- Deleted test_show: its monkeypatch targeted visualization.show_2d, which
dataset2d had already imported, so it rendered a real figure and only
checked for a 2-tuple. Dataset4d.show is a one-line forward.
test_dataset3d_show.py (25 -> 3 IDs):
- Parametrized cases become loops that name the failing kwargs. Duplicate
branches dropped (ncols/max -1, step 2). The long dataset is 25 frames
instead of 100, so max=None draws 25 subplots, not 100; the default
max=20 truncation is still exercised.
test_autoserialize.py (16 -> 6 IDs):
- store=zip/dir only changes the container, so only the Dummy test loops
over both stores; the other tests use zip.
- Nested roundtrip and nested skip merged (same class, two saves).
- test_torch_tensor_roundtrip removed; test_torch_tensor_and_optimizer
checks the same tensor. Its optimizer check compared state_dict keys,
which are always {state, param_groups}; it now checks the restored lr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_custom_normalizations.py (37 -> 9 IDs): - Intervals/stretches clip to [0, 1], so the "0 <= result <= 1" checks on the log/inverse-log/asinh/sinh stretches and on every CustomNormalization variant could not fail (the 1..10 input clipped to all ones). Replaced by: one loop asserting f(0)=0, f(1)=1, monotonicity, inverse round trip, the documented log/inverse-log formulas and asinh/sinh symmetry; and one CustomNormalization table checking exact values for the default, manual, centered, power, logarithmic and asinh variants. - Interval limit tests become one table; ManualInterval.__call__ now checks every value. Linear/power exact checks kept in one test. - Invalid-parameter tests become one loop. - NormalizationConfig default/custom tests deleted (dataclass fields). - _resolve_normalization: the preset test only checked isinstance; now one test checks explicit configs for None, the vmin/vmax and quantile kwargs, dict, two presets and pass-through; invalid preset/type merged. - Removed the unused outlier fixture. test_visualization_utils.py (31 -> 12 IDs): - The three "custom origin/sampling/statistic" histogram tests asserted ~isnan(hist[hist > 0]), which cannot fail, and the others only the output shape. One table now places known points and checks bin values for sum, mean, origin and sampling; the column-vector test checks equality with the 1D call and the mismatch error. - array_to_rgba: cmap path compared against matplotlib; angle path checks black at zero amplitude, lightness order, hue change and the chroma boost. combine_arrays_to_rgba checks black/no-signal, distinct hues and boost. - ScalebarConfig default/custom tests deleted; _resolve_scalebar's six tests become one, adding the sampling-kwarg path. - estimate_scalebar_length checks the actual lengths and the non-uniform sampling error instead of isinstance/positivity. - Colorbar tests check the norm and the arg ticks; the chroma-boost variant only asserted "is not None" and now checks the colormap changes. - turbo_black: fade_len was never reflected in any assertion; one loop now checks N, the black start and the unfaded tail. test_visualization.py (20 -> 10 IDs): - isinstance(fig, Figure) could not fail with figax passed in. Smoke cases for real/complex/cmap/chroma (and the combined variants) become loops asserting one image each; scalebar/colorbar/title checks kept in one call. - New value check: manual norm + viridis is what imshow receives. - Grid normalization tests merged; pyplot figures closed. - Deleted three commented-out tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_imaging_utils.py (37 -> 7 IDs): the 4 x 6 upsample/shift product, the 6 torch-parity shifts and the 3 center-tap factors all exercise one code path each. They are now loops inside the same tests, with every case kept and named in the assertion message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Apple silicon the MPS branch left the global device set to "mps", so every later test in the same worker that reads config.get_device() ran on MPS. The test now restores the device it started with in a finally block. Also: - test_check_key_val_known_key_silent and ..._nested_key_silent merged into one loop over both keys. - test_version_exists removed; test_version_is_string implies it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_eds_background.py: the four invalid-input parametrize cases become a loop in one test (6 -> 3 IDs), each case kept. - test_xeds_fitting.py: test_loss_and_tv_helpers_are_finite only checked finiteness; it now checks the exact MSE, Poisson and in-plane TV values for the same inputs, and the unknown-loss error. - test_serialize_bundle.py: the three reserved-name cases become a loop (6 -> 4 IDs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_logger_tomography.py (5 -> 3 IDs): SummaryWriter writes the events file in __init__, so "an events file exists" could not fail, and test_log_iter_without_val_loss asserted nothing. The scalar tests are one test that reads the events back and checks every tag, value and step (including that loss/val is skipped when val_loss is None); the image test checks the logged image tags. test_tomography_opt.py (16 -> 11 IDs): - test_setter_getter_roundtrip and test_current_lrs folded into test_set_optimizers_builds_object_and_pose (same state, all assertions). - test_scheduler_setter_getter folded into test_set_schedulers_builds. - The step/zero_grad once-per-call regressions become one loop. - _get_default_lr checked only isinstance(float); it now checks the object and pose values, which also pins the routing, and the unknown-key error. test_tomography.py (15 -> 9 IDs): - The conventional factory and empty-loss-accessor tests merged. - The three constraints-setter tests and the two wrong-type setter tests merged. - test_to_updates_device deleted (moves cpu to cpu). - TestInrFactory::test_from_models_builds deleted (isinstance only; every INR test here and in test_tomography_opt.py builds through from_models). - Both plot_losses tests asserted nothing; they now check the plotted losses (and learning rates) and close the figure. test_radon.py (19 -> 9 IDs): - Shape tests merged into one radon and one iradon test. - The named-filter parametrize only checked shape/finiteness; one loop now checks each window lies between 0 and the ramp and attenuates Nyquist, and that the ramp peaks there. Odd-size and unknown-name errors merged. - The explicit- and default-theta round trips are one loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The constants, probe, smooth phase, dataset simulation, ptychography builder and shift-tolerant correlation were copied verbatim into test_object_inr.py and test_object_tensor_decomp.py. They now live once, as an INR_-prefixed block appended to diffractive_imaging/conftest.py (nothing above it changed; the prefix avoids the direct-ptychography N/Q_PROBE/... names). The importlib concern in the old docstring does not apply: other modules here already import from .conftest. test_object_inr.py (23 -> 21 IDs): - test_multislice_z_coordinates and test_single_slice_z_is_zero merged. - test_autograd_only_backward_raises and test_complex_obj_type_not_supported merged into one test. test_object_tensor_decomp.py (27 -> 20 IDs). ObjectTensorDecomp overrides only its factories, name, model, optimizer grouping and pretrain, so these re-tested ObjectINR code and are removed (covered in test_object_inr.py): - test_off_object_is_vacuum (ObjectINR.forward masking) - test_multislice_z_coordinates (ObjectINR._z_coords); the 3-slice K-Planes forward shape is now part of test_forward_shape_dtype_and_vacuum - test_autograd_only_backward_raises (ObjectINR.backward) - TestImplicitSync (same assertion as the INR test) - slow test_multislice_runs (INR slow multislice test plus the unit forward) Also: test_is_implicit_and_name folded into the forward test, and the two KPlanesHead tests are one loop over use_hybrid_mlp. Both files pass with --runslow (thresholds unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests test_ptychography.py (18 -> 4 IDs): - TestTargetResidency ran on the 64^4 white-noise fixture (4096 simulated 64x64 patterns, preprocessed with rotation/CoM plots), rebuilt for each of 9 IDs, about 0.8 s each, to test a string property. It now uses an 8x8x16x16 random dataset; default/valid/invalid-setter checks are one test with the six invalid values looped and named; the save/load check is kept. - TestAspectRatioRotatedFOV: the positions-inside-object and obj_cropped checks built the same rotated 32x64 scan separately; one test now builds it once per transpose and runs both checks (4 builds -> 2). - Slow TestPtychographySaveLoadRoundtrip: the function-scoped trained fixture reran the 50-iteration 64^4 reconstruction for each of 5 tests. One test now reconstructs once and checks the reloaded losses, positions and object (exact), the strict 0.3 loss threshold, and the continue-training behaviour (about 24 s once instead of five times). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… case test_constraints.py (24 -> 12 IDs): - test_mask_applied_once[pure_phase] used a constant phase, which the recentering zeroes, so the expected 0 held whether the FOV mask was applied once or twice. It now uses a zero-mean +-0.7 checkerboard: once gives |phase| 0.35, twice would give 0.175. - TestParseDict: the four successful parses are one test, the unknown and missing-name errors another. - TestTypoCatching: the dict and add_constraint paths are one test. - TestRoundtrip: the object/probe/dataset dataclass identity checks are a loop; the dict form is kept. - TestReconstructKwargs: test_dataclass_leaf_applied folded into test_mixed_dataclass_and_dict_leaves (the object dataclass leaf now also sets tv_weight_z=1.5 and checks it); the partial dict update is kept. - TestPurePhaseRealValued: the three storage-dtype tests and the hard-constraints-stay-real test are one loop; the phase-wrap warning presence/absence pair is another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestMultiGPUDeviceRestoration, TestMultiGPULossTracking, TestMultiGPULRTracking and TestMultiGPUOptimizerState each built a fresh model and ran the same reset / continue / reset spawn sequence (9 spawns). One test now runs a single sequence (4 spawns): the first call checks the device list, CUDA placement, loss and lr history and Adam state; then a continuation without device= (the device must persist), a continuation with device=, and a reset, checking that the loss and lr histories extend and reset. The single-GPU and DIP tests are unchanged. 6 -> 3 IDs. This test requires two CUDA devices, so it was linted but not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removed (cannot fail, or implied by another test): - test_one_slice_is_the_single_plane: num_slices=1 is the default, so both calls were identical. - test_no_per_pixel_stack_is_kept, test_variance_loss_has_been_removed: tombstones for removed attributes. - test_astigmatism_breaks_the_radial_symmetry: covered by test_the_shift_fit_returns_the_twin_nearest_its_reference, which recovers C12 from the same shifts. - test_the_two_classes_agree: rms_gradient_loss lives on the base class, and prlx equality is pinned by the montage TestFourierEquivalence. - test_a_spec_built_anywhere_is_accepted_everywhere: implied by the `is` identity in test_every_import_path_is_the_same_class. - test_the_noise_variance (Wiener): asserted the stored value equals the one passed. - test_a_finer_scan_sampling_refines_the_grid: implied by test_a_finer_scan_sampling_recovers_it. - test_upsampling_preserves_the_field_of_view[1,2,3]: same base _obj_sampling as the montage TestVisualization sampling test. - TestDatasetVariants: the prlx residual minimum is covered by test_is_a_fraction_minimized_at_the_true_defocus. - test_upsampling_an_irregular_regrid_warns: the warning is required by test_upsampling_factor_degrades_on_an_irregular_scan and test_a_finer_scan_sampling_recovers_it; its lattice_rms check moved into test_no_unfolding_warning_without_an_irregular_regrid. - test_twisted_layers_separate[honeycomb_scene], test_odd_aberrations_..._sign[C23]: same code paths as the square and C21 cases; twisted_honeycomb_cell is dropped from conftest. Merged (same branch or same setup): - test_aliases_resolve[4], test_the_matched_filter_has_been_removed[2], test_unknown_kernel_raises -> test_kernel_names. - test_is_a_fraction[4], test_is_minimized_at_the_true_defocus[3] -> test_is_a_fraction_minimized_at_the_true_defocus; the two "is None before reconstructing" tests -> test_is_none_before_reconstructing. - test_grid_search, test_grid_search_with_the_rms_gradient, test_grid_search_accepts_a_callable, test_recorded_losses_are_the_requested_objective -> test_grid_search[residual, rms_gradient, callable]. - test_unknown_losses_are_rejected[3] -> one loop on the shared reconstruction. - Six round-trip tests (TestSerialization x2, TestFromDataset3d, TestComplexObject, TestSectioning, TestPartialCoherence) -> TestSerialization: one gridded reconstruction carrying a complex sectioned object, partial coherence and a stored fit, plus one ungridded reconstruction. - TestReconstructAllPermutations (4) and test_permutations_leave_out_only_icom -> one table. - test_lattice_positions_reproduce_from_dataset4d[4 kernels]: regridding does not depend on the kernel, so the pair is built once and the kernels looped. - The four no-warning tests of TestFromDataset3d -> two. - test_lattice_positions_unfold_through_the_ungridded_path -> an absolute check in test_upsampling_factor_degrades_on_an_irregular_scan, which computes the same image. - test_sections_are_single_plane_reconstructions[5 kernels] and test_a_complex_object_has_amplitude_sections -> one complex obf case: _reconstruct_sections is kernel-generic apart from the iCoM refusal, which keeps its own test. - TestSectioning::test_aliases[4], test_rejects_an_unknown_mode -> test_mode_names. - test_kernels_without_a_joint_solve_raise[3] and the identical ssb call in TestSectioning -> one test matching both halves of the message. - test_only_the_normal_equation_kernels_take_it[3], TestWienerKernel's and WDD's ARD refusals -> one case in TestArdPrior::test_rejects_bad_arguments (one branch of _validate_prior). - The empirical-probe reproductions (complex ssb/obf, joint obf, and the pure-phase cases of the montage file) -> TestEmpiricalProbe, sharing one analytic/empirical pair. - The two visualize tests, test_obj_is_the_complex_transmission and test_pure_phase_has_no_amplitude, test_no_aberrations_means_no_shift into test_pure_defocus_matches_the_analytic_shift. - Cheap parametrizations sharing one object become loops naming the case: test_one_mode_is_the_coherent_probe, test_a_double_precision_probe_..., test_one_plane, test_slice_thicknesses_are_validated, test_a_finite_source_leaves_obf_..., test_rejects_a_negative_spread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removed (cannot fail, or exercise no library code): - TestBandLimit::test_deconvolution_kernels_are_untouched[ssb,obf]: band_limit only reaches _resolve_q_lowpass, which returns the cutoff unchanged for ssb/obf either way, so both runs were the same computation. test_an_explicit_cutoff_below_the_band_limit_ still_wins asserts that branch directly. - test_the_icom_kernel_is_the_ricom_kernel (numpy-only identity), test_photon_wavelength_is_not_the_electron_one (core util and literal arithmetic), test_nearest_is_a_roll_of_the_bright_field_images (rounding identity), test_interpolation_defaults_to_nearest (identical call). - isfinite-only or "not allclose" checks: test_accepts_the_deconvolution_kernels (covered by TestRealSpaceKernels::test_converges_to_the_fourier_kernel), test_icom_runs_in_either_mode (test_icom_matches_the_fourier_class, test_truncated_icom_is_ricom, TestSparseEvents::test_icom_matches_dense), test_scattered_positions_reconstruct (test_scattered_positions_match_dense), test_position_axis_order_is_row_col (test_matches_from_dataset4d, test_scan_origin_recovers_the_input_positions), test_rotation_changes_the_reconstruction (replaced by an exact quarter-turn case in test_matches_parallax_kernel). - test_fft_mode_beats_a_truncated_stencil (implied by test_fft_mode_is_exact and the convergence test), test_torch_size_attributes_round_trip_as_tuples (never round-tripped), test_is_analytic_reports_which_path_is_live, test_fft_mode_leaves_no_stencil_info, test_dense_and_sparse_are_mutually_exclusive. - TestIntegerShiftConstruction: its integrality check is now a precondition inside test_matches_parallax_kernel. - Copies of the Fourier file's from_dataset3d tests, which go through the same build_vbf_stack_from_dataset3d: auto scan sampling, Dataset2d positions, wrong units, mismatched count. - test_grid_search_recovers_the_seeded_defocus: same base driver as the Fourier test_grid_search; the montage objective is covered by test_residual_loss_is_minimized_at_the_true_defocus. - TestComplexAndMultislice::test_parallax_rejects_complex: same _validate_reconstruction_mode as the Fourier test. - The Fourier cases of TestFourierProbe::test_empirical_probe_reproduces_the_analytic_one (now TestEmpiricalProbe in the Fourier file) and the montage case of test_prlx_runs_on_a_modelled_empirical_probe (test_fitted_coefficients_drive_parallax). - test_semiangle_cutoff_becomes_optional: its reconstruction moved into test_an_empirical_probe_reads_its_extent_off_the_bf_mask. Merged: - test_matches_parallax_kernel[pixel_shift x U], test_matches_with_phase_flip, test_matches_with_butterworth_filters -> one loop on a shared pair (pixel_shift reaches no other path), plus the quarter turn. - Sampling and field-of-view tests over both classes (scalebar[6], resets, before reconstructing, fov, wrapped canvas, wrap default, zero scan origin) -> test_scalebar_follows_the_upsampling; padded canvas at U=2 only. - TestWavelength and TestSemiangleCutoff: the checks live in the base class, so one class; six parametrized refusals -> two loops. - Eight round-trip tests -> TestSerialization::test_round_trip (an ungridded montage with scan offset, defocus gradient and a stored fit, and an empirical-probe montage), TestSparseEvents::test_survives_a_round_trip, and the wavelength round trip. - TestDefocusGradient: the two rate tests, the delta tests, the update/leave defocus fits and the three defocus_map argument tests each become one; the gradient sweeps keep one oblique gradient (plus the zero control for the fit). - TestRealSpaceKernels: the residual-undefined tests and the auto-radius tests become one each. TestConvolutionModes: conv2d[2x2] and the mode-resolution tests become loops. - TestSparseEvents: the wrap-nearest case was the same call as test_deconvolution_stencils_match_dense[ssb]; the pad-bilinear case reuses the class pair through reconstruct(boundary="pad"), which is bit-identical to building a pad pair; the complex sparse tests move here onto the class pair. The counts are scaled to a mean of 4 rather than 40, which leaves every dense/sparse error unchanged (largest 1.3e-5 against 1e-4) and cuts each from_vector build from 2.2 s to 0.2 s. - TestFrameDrift: the drift is estimated once per class; the four rejection tests share one set of frames. - The three stencil refusals of TestComplexAndMultislice -> one test. - A module fixture shares the Fourier/montage pair among the tests that only reconstruct. GPU: - TestAccelerators moves to test_direct_ptychography_gpu.py and compares every route with the CPU, residual included; the ungridded origin fit now has aberrations, since with none the parallax image was identically zero. test_padded_canvas_shape_is_device_ independent is the pad case of that loop. - test_sections_match_the_cpu[3 kernels] -> obf: sectioning is a CPU-side loop over the single-plane kernels, each compared in test_every_kernel_matches_the_cpu. - conftest: ACCELERATORS is unused; tilted_layers_4dstem replaces the layered-dataset helpers duplicated in the GPU and tilt-series files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_one_untilted_acquisition_is_its_own_reconstruction[3]: each case simulated and built its own acquisition; the three cases now loop over one. - test_upsampling asserted only the shape and finiteness on the five-tilt series. It now runs on two noise-free acquisitions and checks the upsampled slices against the coarse ones on the coarse grid. - test_from_tilt_series_builds_each_acquisition: per-dataset keyword forwarding and the returned type are asserted by test_survives_a_round_trip. - test_rejects_bad_series[3], test_rejects_another_scan_grid, test_only_the_normal_equation_kernels_combine[4] (a single `kernel not in _KERNELS` branch) and test_a_tilt_series_cannot_be_sectioned -> test_rejects_what_it_cannot_combine. - The noise-free acquisitions are a module fixture shared by test_upsampling, the rejections and test_survives_a_round_trip_before_reconstructing; the layered dataset comes from conftest's tilted_layers_4dstem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_scalebar_follows_the_upsampling loops over U = 2 and 3 on one pair, so the odd factor the removed U=3 parametrizations covered is still checked for both classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test suite grew from approximately 2k lines in June to 22k lines in October, mostly from tests added during Claude Code sessions. CLAUDE.md asks future sessions to extend existing tests before adding new ones, to test each code path once, to write asserts that can fail, and to use the smallest problem that exercises the code path. .gitignore previously excluded CLAUDE.md, so we replaced that entry with CLAUDE.local.md, which keeps personal notes out of the repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the default `--dist load`, each xdist worker rebuilds every module- and class-scoped fixture it uses. `--dist loadscope` assigns each test class (or each module's free functions) to a single worker, so the shared direct ptychography fixtures are built once. A local run with 4 workers dropped from approximately 118 s to 97 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TomographyDatasetBase declared DEFAULT_LRS = {"pose_lr": 5e-2}, while
TomographyOpt._get_default_lr reads DEFAULT_LRS.get("pose", 5e-2), so the
declared value was never used and the fallback, which happens to be equal,
always was. The key is now "pose", the plain key every other DEFAULT_LRS
uses.
test_get_default_lr asserted 5e-2, which the fallback also gave, so it could
not fail. It now compares each key with the model's own DEFAULT_LRS, and
fails with a KeyError before this fix.
dataset_models.py already differs from ruff format in lines this commit does
not touch, so the formatter hook was skipped to keep the diff to the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_tomography_cpu_recon.py exists to cover the learned reconstruction loop of Tomography.reconstruct on CPU-only CI, but it was marked slow, which CI skips. It takes under a second, so the slow marker is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No CI job ran the tests marked slow. They now run with --runslow on pull requests into main, once per release, on every OS in the release matrix. Locally they add about 70 s with 4 workers (19 passed, 6 skipped without CUDA). CLAUDE.md's rule for the slow marker says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator
|
lgtmc (looks good to my Claude) 😅 |
gvarnavi
approved these changes
Oct 19, 2026
Collaborator
|
Okay confirmed that this works for multi-gpus. The only issues I found were in diffraction and other areas you deliberately didn't modify, so I don't think there's anything else to add for now. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The quantem test suite grew from approximately 2k lines in June to 22k lines in October, mostly from tests added during Claude Code sessions. This PR consolidates the suite following the approach @TomaSusi used for abTEM in abTEM/abTEM#589: we removed parametrizations that do not change the code path, merged duplicate tests, rewrote tests that could not fail so that they check computed values, and shared expensive inputs between tests. We also added a CLAUDE.md with rules for writing tests, so that future sessions extend existing tests instead of adding new ones. Each commit message lists every removed or merged test and the test that still covers that behavior.
Results
devWe measured all run times on an Apple silicon laptop, where repeated runs with 4 workers varied by approximately 10 s. The diffraction, imaging and ML tests are unchanged (see below).
Changes
Over-parametrization. Many tests were parametrized over values that take the same code path, for example kernel name aliases, both direct ptychography classes for code in their shared base class, both
store="zip"andstore="dir"for every serialization test, and a 4 × 6 grid of upsampling factors and shifts. We kept one case per code path, and where several inexpensive cases share one path, we loop over them inside one test and name the failing case in the assert message.Duplicate tests. The Fourier and montage direct ptychography files contained copies of the tests for their shared helper functions and 17 separate save and load round trips, which are now 5. The INR and tensor decomposition tests contained identical copies of approximately 140 lines of helper functions, which now live in
diffractive_imaging/conftest.py.Tests that could not fail. The normalization stretch tests asserted
0 <= f(x) <= 1for inputs that each stretch first clips to [0, 1], and the histogram tests asserted~isnan(h[h > 0]). The tomography logger tests checked for an events file thatSummaryWriter.__init__always writes, the pure-phase double-mask test used a constant phase that gives zero whether the mask is applied once or twice, and the GPU origin-fit test compared two parallax images that were identically zero. These tests now check computed values.Run time. The direct ptychography tests now share module-scoped fixtures for the Fourier and montage reconstruction pair, the drift frames and the tilt series acquisitions. The sparse event tests use a mean count of 4 instead of 40, which reduces each
from_vectorbuild from 2.2 s to 0.2 s and leaves the largest dense-to-sparse error at 1.3e-5, below the 1e-4 tolerance. The target-residency tests intest_ptychography.pypreviously rebuilt a 64⁴ simulated dataset for each of 9 test IDs, and now use a single 8 × 8 × 16 × 16 dataset.CI scheduling. The CI workflow now runs pytest with
--dist loadscope, which assigns each test class (or each module's free functions) to a single xdist worker, so that each module- and class-scoped fixture is built once instead of once per worker.Device leak. On Apple silicon,
test_config_set_deviceleft the global device set tomps, so later tests in the same worker ran on MPS. The test now restores the original device.CLAUDE.md. The new CLAUDE.md asks Claude Code sessions to extend an existing test before adding a new one, to test each code path once, to write asserts that can fail, and to use the smallest problem that exercises the code path.
.gitignorepreviously excludedCLAUDE.md, so we replaced that entry withCLAUDE.local.md, which keeps personal notes out of the repository.Remaining work
To avoid merge conflicts, we did not modify test files that open PRs also modify. These include
tests/diffraction/, which #305 modifies; we audited it and expect a reduction from 256 to approximately 206 test IDs and from 56 s to approximately 32 s once #305 merges. The others aretest_optimizermixin.pyand four tomography test files (#246),test_vector.py(#273) andtest_lattice.py(#236). Approximately 50 of the 64 test IDs intest_optimizermixin.pycheck dataclass default values and can be removed after #246 merges.Other issues found
CI does not run the 31 tests marked
@pytest.mark.slow, because the workflow does not pass--runslow. These includetest_tomography_cpu_recon.py, whose docstring states that it exists to cover the learned reconstruction loop in CI. We should either run the slow tests in a separate scheduled CI job, or remove the slow marker from the tests that CI must cover.tomography_opt.py:29reads the pose learning rate fromDEFAULT_LRS["pose"], butdataset_models.py:162defines the key as"pose_lr", so the code always uses the fallback value, which is currently equal to the intended value of 5e-2.Testing
The full suite passes both serially and with 4 workers (1,023 passed, 32 skipped). We ran the direct ptychography tests three times with 4 workers to check for flaky tests.
test_multi_gpu.pyrequires two CUDA devices, so we linted it but did not run it.🤖 Generated with Claude Code