Skip to content

Testing ​

Running the suite ​

The runner is a plain script, not a Pkg.test target:

bash
JULIA_NUM_THREADS=4 julia --project=. --heap-size-hint=3G tests/run_tests.jl

With 4 threads it takes four to five minutes and stays below 4 GB, and reports 1404 passing assertions, no failures and no errors.

Assertions per file, counted on one run with every file wrapped in its own test set (in-process group, blue) or from the summary table each subprocess prints (orange). The runner itself scores each subprocess file as one assertion on its exit code, which is why it reports 1404 rather than 1594; two subprocess files check by exit code only.

tests/run_tests.jl has two groups:

  • In-process files (test_files). The runner includes them one after another inside one @testset "MILEVA unit tests", in one Julia process and one namespace. The first file, test_grid.jl, does using Plots and include("include/mileva.jl"); most later files only do using Test and rely on that, so they run on their own only if you load MILEVA first (julia --project=. -e 'using Plots; include("include/mileva.jl"); include("tests/test_annulus_operator.jl")'). Files run in list order, so put a new file after anything it depends on.

  • Subprocess files (subprocess_files). These build their own dependency set instead of including include/mileva.jl, and re-including config/SimConfig.jl with a different set of definitions would redefine a struct, which Julia refuses. The runner launches each in a fresh julia --project process, streams its output, and records a failure if the exit code is non-zero.

Tests that write files put them under output/ (for example tests/test_2d_vcycle_scgs.jl writes its figure to output/tests/), which is git-ignored.

What a test should check ​

For an incompressible flow

∇⋅(2ηε˙)=η∇2v+2(∇η)⋅ε˙,

so a test run at constant viscosity cannot distinguish the two operators. Two rules for new tests follow from examples like this one:

  1. Test a property that the correct answer must have. The work-dissipation identity below holds for any Stokes solution at any viscosity field. Agreement between two solvers on one assembled matrix checks the solvers, not the matrix.

  2. Make sure the property responds to what you want to check. Rigid rotation has ε˙≡0, so the 2(∇η)⋅ε˙ term vanishes on it; the null-mode test is useful, but the viscosity-gradient term is checked by the work-dissipation identity at variable viscosity. Where it distinguishes truncation from inconsistency, require the error to shrink under refinement rather than sit below a fixed tolerance.

The invariants the suite guards ​

These are the tests that carry weight. Any change to the Stokes operator, the temperature solver or the marker transport must keep them green.

PropertyFileAssertion
Work equals dissipation, ∫f⋅vdV=∫4ηε˙II2dV, 2-D spherical annulustest_annulus_operator.jlW>0, D>0 and W/D within 0.05 of 1 at contrasts 1, 1e2, 1e4, 1e6, 1e8 (Nr = 32); at contrast 1e8 the error at Nr = 64 is less than half that at Nr = 32
Work equals dissipation, 3-D, radial viscosity ramptest_stokes_3d_variable_eta.jlW/D within 0.05 of 1, and not worse on the finer grid
Work equals dissipation under production-like conditionstest_energy_consistency.jlpositive work, identity converging under refinement; the two O(Di) terms (adiabatic and dissipative) balance on the flow they describe
Rigid rotation is an exact null mode of ∇⋅(2ηε˙)test_annulus_operator.jlresidual < 1e-12 at Nr = 12, 24, 48
Strain rate is exact for Vr=C/r on a stretched gridtest_annulus_operator.jlmax relative error < 0.02 on a grid with stretch ratio > 3
Nu = 1 on a conducting state, both geometriestest_nusselt_conduction.jlNu within 5e-3 of 1 at Nr = 16, 32, 64, error decreasing; Nu_top == Nu_bot to 1e-9
Discrete energy budget closes, with and without internal heatingtest_nusselt_conduction.jlresidual < 1e-10
One marker step conserves energytest_marker_energy.jlenergy delivered to the markers equals the energy the grid solver computed, < 1e-10, in 2-D and 3-D
A model given nothing to do does nothingtest_quiescence.jlan isothermal model stays isothermal and still; uniform heating warms at the prescribed rate without overshoot; a laterally uniform source makes no lateral structure
The answer stops moving under refinementtest_solution_convergence.jlsuccessive differences shrink, peak velocity not amplified by refinement
Marker projection does not drive the flowtest_projection_noise.jlprojection noise falls with markers per cell and with refinement

What each in-process file covers ​

FileCovers
test_grid.jl2-D and 3-D grid construction, cell counts and spacings
test_geometry_metrics.jlthe radial measure A(r)∝rm and that every site that uses it agrees on the geometry
test_config_matrix.jlevery geometry and compressibility mode runs and is distinguishable; unimplemented flags refuse; solver backends agree; no major physics option is on by default
test_markers.jlmarker initialisation, marker-grid-marker round trip, advection stays in the domain
test_diffusion.jlexplicit and Crank-Nicolson diffusion converge and agree; Dirichlet walls; radiogenic heating
test_advection_consistency.jlspherical metric factors in 3-D temperature advection; a uniform field is stationary; no overshoot; TVD less diffusive than upwind
test_conduction_conservation.jlradial conduction in conservative flux form
test_annulus_operator.jlsee the invariants table
test_nusselt_conduction.jlsee the invariants table
test_marker_energy.jlsee the invariants table; the volatile inventory survives reseeding
test_composite_rheology.jldiffusion and dislocation creep against their formulas, the composite, the yield cap, the clamps
test_stokes_3d_variable_eta.jlsee the invariants table
test_energy_consistency.jlsee the invariants table
test_solution_convergence.jlsee the invariants table
test_projection_noise.jlsee the invariants table
test_phase_transition_resolution.jla phase boundary spans the requested number of cells at every resolution; 2-D and 3-D agree; each transition counted once
test_config_timeloop.jlevery 2-D configuration, a combined one and 3-D survive the real time loop
test_quiescence.jlsee the invariants table
test_spherical_annulus.jlthe r2 geometry and its properties
test_stokes.jldirect, AMG and 2-D multigrid solvers: free-slip walls, divergence, agreement between backends, high viscosity contrast
test_melt_conservation.jlmelt mass, composition and energy bookkeeping; the backlog never discards; extraction rate independent of the time step; 3-D deposition
test_rheology_switches.jlyield law, depletion stiffening, lower-mantle offset
test_hpe_partition.jlheat-producing-element partitioning and the conserved marker field
test_melt_frame.jlmelting compares the solidus with the real temperature
test_crust_material.jlcrust density and viscosity as a material
test_lid_mobility.jlno-net-rotation frame, lid mobility, the resurfacing detector
test_2d_vcycle_scgs.jl2-D multigrid against the direct solver, at 2, 3 and 4 levels
test_diagnostics_csv_vrms.jldiagnostics.csv V_rms in the geometry's own measure
test_depletion_projection.jla painted depletion reaches the grid without melting

Subprocess group: test_stretched_radial_3d.jl, test_stokes_3d_operator_consistency.jl, test_spherical_operator_nullmode.jl, test_assembly_3d_mirror.jl (serial against distributed 3-D assembly on one rank), test_3d_physics.jl, test_compressible_stokes_ab.jl, test_yinyang_phi_coupling_ab.jl, test_vcycle_var_eta.jl, test_yy_rotation_vs_ks2004.jl (Yin-Yang vector rotation against Kageyama and Sato 2004), test_amr_indicators.jl and test_full_feature_smoke.jl (a 3-D smoke run with every physics flag for two steps).

Outside the runner ​

These files live in tests/ but run_tests.jl does not run them:

FileStatus
test_mpi_*.jlneed mpiexec; launch as in the file header, e.g. mpiexec -n 2 julia --project=. tests/test_mpi_2rank_smoke.jl with the mpiexec that MPI.jl returns from MPI.mpiexec()
test_amr_full.jlcompares the uniform-grid FGMRES iteration count with a stored count of 40 at η=1 (tolerance 5); the current solver takes 31
test_mdpi_eta_contrast.jl, test_vcycle_visc_contrast_sweep.jl3-D multigrid sweeps over viscosity contrast; they print PASS/FAIL and exit 0 either way, so read the output
test_arrhenius_viscosity.jl, test_fk_viscosity.jlprint their results rather than asserting them
smoke_stokes_3d_mg.jl, smoke_chunk_fk_lid.jl, smoke_chunk_arrhenius_lid.jlcoarse 3-D smoke runs (the first is the default of bin/mileva test)

Tests and benchmarks ​

The suite guards identities. Agreement with published numbers is checked separately, for the 2-D spherical annulus, by benchmarks/hernlund_tackley_annulus.jl against Hernlund and Tackley (2008) and the annulus column of Fleury et al. (2024); see Benchmarks and verification.

Adding a test ​

  1. Create tests/test_<topic>.jl. Everything in include/mileva.jl is already in scope when the runner reaches it; to make it runnable on its own as well, guard the load:
julia
using Test
if !isdefined(Main, :SimConfig)
    using Plots; gr()
    include(joinpath(@__DIR__, "..", "include", "mileva.jl"))
end

Add it to test_files in tests/run_tests.jl with a one-line comment saying what property it defends. A file that builds its own include set goes in subprocess_files instead and must exit non-zero on failure.

  1. Build configurations from SimConfig() plus field overrides, never positionally, and use cfg.geometry = "spherical_annulus" for anything 2-D. A planetary configuration that a test needs lives in tests/fixtures/ (for example tests/fixtures/Venus_BMO.jl).

  2. Keep it small: a few seconds, coarse grids, no output beyond output/tests/. Coarse is fine for an identity (Nu = 1, W/D=1, energy closure); it is not fine for a claim about a resolved physical state.

  3. Prefer an identity, a conservation law or an exact solution to a golden number. When you compare against a stored value, store the reason with it and expect to update it.

  4. Vary the input that the property depends on: a viscosity test needs a varying viscosity, an energy test a heat source.

  5. Check that the new test fails on code it is meant to protect against (for example by reverting the change locally and running it).