Unit Testing
Unit testing is integral to the GEOS development process. While not all components naturally lend themselves to unit testing (for example a physics solver) every effort should be made to write comprehensive quality unit tests.
Each sub-directory in coreComponents should have a unitTests directory containing the test sources. Each test consists of a cpp file whose name begins with test followed by a name to describe the test. Please read over the LvArray unit test documentation as it gives an intro to the Google Test framework and a set of best practices.
GEOS Specific Recommendations
An informative example is testSinglePhaseMobilityKernel which tests the single phase flow mobility and accumulation kernels on a variety of inputs.
TEST( SinglePhaseBaseKernels, mobility )
{
int constexpr NTEST = 3;
real64 const dens[NTEST] = { 800.0, 1000.0, 1500.0 };
real64 const dDens_dPres[NTEST] = { 1e-5, 1e-10, 0.0 };
real64 const visc[NTEST] = { 5.0, 2.0, 1.0 };
real64 const dVisc_dPres[NTEST] = { 1e-7, 0.0, 0.0 };
for( int i = 0; i < NTEST; ++i )
{
SCOPED_TRACE( "Input # " + std::to_string( i ) );
real64 mob;
real64 dMob_dPres;
MobilityKernel::compute( dens[i], visc[i], mob );
MobilityKernel::compute_derivative( mob, dDens_dPres[i], visc[i], dVisc_dPres[i], dMob_dPres );
// compute etalon
real64 const mob_et = dens[i] / visc[i];
real64 const dMob_dPres_et = mob_et * (dDens_dPres[i] / dens[i] - dVisc_dPres[i] / visc[i]);
EXPECT_DOUBLE_EQ( mob, mob_et );
EXPECT_DOUBLE_EQ( dMob_dPres, dMob_dPres_et );
}
}
[Source: coreComponents/physicsSolvers/fluidFlow/kernels/singlePhase/unitTests/testSinglePhaseMobilityKernel.cpp]
What makes this such a good test is that it depends on very little other than kernels themselves. There is no need to involve the data repository or parse an XML file. Sometimes however this is not possible, or at least not without a significant duplication of code. In this case it is better to embed the XML file into the test source as a string instead of creating a separate XML file and passing it to the test as a command line argument or hard coding the path. One example of this is testLaplaceFEM which tests the laplacian solver. The embedded XML is shown below.
char const * xmlInput =
R"xml(
<Problem>
<Solvers gravityVector="{ 0.0, 0.0, -9.81 }">
<CompositionalMultiphaseFVM name="compflow"
logLevel="0"
discretization="fluidTPFA"
targetRegions="{region}"
temperature="297.15"
useMass="0">
<NonlinearSolverParameters newtonTol="1.0e-6"
newtonMaxIter="2"/>
<LinearSolverParameters solverType="gmres"
krylovTol="1.0e-10"/>
</CompositionalMultiphaseFVM>
</Solvers>
<Mesh>
<InternalMesh name="mesh"
elementTypes="{C3D8}"
xCoords="{0, 3}"
yCoords="{0, 1}"
zCoords="{0, 1}"
nx="{3}"
ny="{1}"
nz="{1}"
cellBlockNames="{cb1}"/>
</Mesh>
<NumericalMethods>
<FiniteVolume>
<TwoPointFluxApproximation name="fluidTPFA"/>
</FiniteVolume>
</NumericalMethods>
<ElementRegions>
<CellElementRegion name="region" cellBlocks="{*}" materialList="{fluid, rock, relperm, cappressure}" />
</ElementRegions>
<Constitutive>
<CompositionalTwoPhaseFluid name="fluid"
phaseNames="{oil, gas}"
equationsOfState="{PengRobinson, PengRobinson}"
componentNames="{N2, C10, C20, H2O}"
componentCriticalPressure="{34e5, 25.3e5, 14.6e5, 220.5e5}"
componentCriticalTemperature="{126.2, 622.0, 782.0, 647.0}"
componentAcentricFactor="{0.04, 0.443, 0.816, 0.344}"
componentMolarWeight="{28e-3, 134e-3, 275e-3, 18e-3}"
componentVolumeShift="{0, 0, 0, 0}"
constantPhaseViscosity="{ 1.0e-3, 1.0e-3 }" />
<CompressibleSolidConstantPermeability name="rock"
solidModelName="nullSolid"
porosityModelName="rockPorosity"
permeabilityModelName="rockPerm"/>
<NullModel name="nullSolid"/>
<PressurePorosity name="rockPorosity"
defaultReferencePorosity="0.05"
referencePressure = "0.0"
compressibility="1.0e-9"/>
<BrooksCoreyRelativePermeability name="relperm"
phaseNames="{oil, gas}"
phaseMinVolumeFraction="{0.1, 0.15}"
phaseRelPermExponent="{2.0, 2.0}"
phaseRelPermMaxValue="{0.8, 0.9}"/>
<BrooksCoreyCapillaryPressure name="cappressure"
phaseNames="{oil, gas}"
phaseMinVolumeFraction="{0.2, 0.05}"
phaseCapPressureExponentInv="{4.25, 3.5}"
phaseEntryPressure="{0., 1e8}"
capPressureEpsilon="0.0"/>
<ConstantPermeability name="rockPerm"
permeabilityComponents="{2.0e-16, 2.0e-16, 2.0e-16}"/>
</Constitutive>
<FieldSpecifications>
<FieldSpecification name="initialPressure"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="pressure"
functionName="initialPressureFunc"
scale="5e6"/>
<FieldSpecification name="initialComposition_N2"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="0"
scale="0.1"/>
<FieldSpecification name="initialComposition_C10"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="1"
scale="0.2"/>
<FieldSpecification name="initialComposition_C20"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="2"
scale="0.6"/>
<FieldSpecification name="initialComposition_H20"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="3"
scale="0.1"/>
</FieldSpecifications>
<Functions>
<TableFunction name="initialPressureFunc"
inputVarNames="{elementCenter}"
coordinates="{0.0, 3.0}"
values="{1.0, 0.5}"/>
</Functions>
</Problem>
)xml";
[Source: coreComponents/integrationTests/fluidFlowTests/testCompMultiphaseFlow.cpp]
MPI
Often times it makes sense to write a unit test that is meant to be run with multiple MPI ranks. This can be accomplished by simply adding the NUM_MPI_TASKS parameter to geos_add_test in the CMake file. For example
geos_add_test( NAME testWithMPI
COMMAND testWithMPI
NUM_MPI_TASKS ${NUMBER_OF_MPI_TASKS} )
With this addition make test or calling ctest directly will run testWithMPI via something analogous to mpirun -n NUMBER_OF_MPI_TASKS testWithMPI.
Parallel CTest Execution
Generated build-system test targets run CTest in parallel by default. GEOS sets
CMAKE_CTEST_ARGUMENTS to pass --parallel to CTest unless the host-config
or CMake command line defines CMAKE_CTEST_ARGUMENTS explicitly. CTest chooses
the default parallel level and uses the PROCESSORS property from
NUM_MPI_TASKS tests when scheduling MPI tests with serial tests.
The default can be changed when configuring GEOS:
-DGEOS_CTEST_PARALLEL_LEVEL=OFF
-DGEOS_CTEST_PARALLEL_LEVEL=8
OFF keeps the generated test and build_test targets serial. A
nonnegative integer is passed as ctest --parallel <N>. Developers can still
call ctest -j <N> directly from the build directory for one-off runs.