4. Tests
A test is a program that stops with an error when a check fails. quad gets two, in test/:
!< Each rule integrates exactly the polynomials of its degree: trapezoid lines, Simpson cubics.
program test_exactness
!< Each rule integrates exactly the polynomials of its degree: trapezoid lines, Simpson cubics.
use quad_rules, only : R8P, trapezoid, simpson
use polynomials, only : line, cubic
implicit none
real(R8P), parameter :: tolerance = 1.e-14_R8P !< Rounding allowance.
if (abs(trapezoid(line, 0._R8P, 2._R8P, 3) - 8._R8P) > tolerance) error stop 'trapezoid is not exact on a line'
if (abs(simpson(cubic, 0._R8P, 2._R8P, 2) - 4._R8P) > tolerance) error stop 'simpson is not exact on a cubic'
print '(A)', 'exact on polynomials of its degree'
endprogram test_exactness!< Halving the interval width divides the error by 2**p: p = 2 for the trapezoid rule, 4 for Simpson's.
program test_order
!< Halving the interval width divides the error by 2**p: p = 2 for the trapezoid rule, 4 for Simpson's.
use quad_rules, only : R8P, trapezoid, simpson
use integrands, only : sine
implicit none
real(R8P), parameter :: pi = acos(-1._R8P) !< Pi.
real(R8P) :: p_trapezoid !< Observed order, trapezoid.
real(R8P) :: p_simpson !< Observed order, Simpson.
p_trapezoid = log(abs(trapezoid(sine, 0._R8P, pi, 16) - 2._R8P) / abs(trapezoid(sine, 0._R8P, pi, 32) - 2._R8P)) / log(2._R8P)
p_simpson = log(abs(simpson(sine, 0._R8P, pi, 16) - 2._R8P) / abs(simpson(sine, 0._R8P, pi, 32) - 2._R8P)) / log(2._R8P)
print '(A, F6.3, A, F6.3)', 'observed orders: trapezoid ', p_trapezoid, ', simpson ', p_simpson
if (abs(p_trapezoid - 2._R8P) > 0.01_R8P) error stop 'trapezoid is not second order'
if (abs(p_simpson - 4._R8P) > 0.01_R8P) error stop 'simpson is not fourth order'
endprogram test_order!< Polynomials whose integrals are known exactly.
module polynomials
!< Polynomials whose integrals are known exactly.
use quad_rules, only : R8P
implicit none
private
public :: line, cubic
contains
pure function line(x) result(y)
!< 3x + 1, integral on [0, 2] = 8.
real(R8P), intent(in) :: x !< Abscissa.
real(R8P) :: y !< Value.
y = 3._R8P * x + 1._R8P
endfunction line
pure function cubic(x) result(y)
!< x**3, integral on [0, 2] = 4.
real(R8P), intent(in) :: x !< Abscissa.
real(R8P) :: y !< Value.
y = x ** 3
endfunction cubic
endmodule polynomialstest_exactnesschecks what the rules must get exactly right: the trapezoid rule integrates a line, Simpson's a cubic.test_orderchecks how fast the error falls: halving the intervals divides it by 4 (trapezoid, second order) and by 16 (Simpson, fourth order).error stopends the program with a non-zero exit status: that is what makes a test fail.
The tests are built like any program, so the debug mode now lists test/ among its sources:
[modes]
modes = debug release static shared coverage
[debug]
help = Checks on, no optimisation
compiler = gnu
cflags = -c -O0 -g -fcheck=all -Wall
src = ./src/ ./test/
build_dir = ./build/debug/
target = src/integrate.F90
[release]
help = Optimised
compiler = gnu
cflags = -c -O2
src = ./src/
build_dir = ./build/release/
target = src/integrate.F90
[static]
help = The library, static
compiler = gnu
cflags = -c -O2
src = ./src/
build_dir = ./build/static/
target = src/quad_rules.F90
mklib = static
output = libquad.a
track_build = True
[shared]
help = The library, shared
compiler = gnu
cflags = -c -O2 -fPIC
src = ./src/
build_dir = ./build/shared/
target = src/quad_rules.F90
mklib = shared
output = libquad.so
track_build = True
[coverage]
help = Instrumented for coverage
compiler = gnu
cflags = -c -O0 -g
coverage = True
src = ./src/ ./test/
build_dir = ./build/coverage/
target = src/integrate.F90Running the tests
Every program in test/ (the default test directory; test_dir in a [test] section changes it) is a test:
$ fobis test --list
Discovered tests in 'test':
test_exactness test/test_exactness.F90
test_order test/test_order.F90$ fobis test
Building test suite (2 target(s))...
Builder options
Directories
Root of source files directory: "src test"
Building directory: "build/debug"
Compiled-objects .o directory: "build/debug/obj"
Compiled-objects .mod directory: "build/debug/mod"
Compiler options
Vendor: "gnu"
Compiler command: "gfortran"
Module directory switch: "-J"
Compiling flags: "-c -O0 -g -fcheck=all -Wall"
Linking flags: ""
Preprocessing flags: ""
Coverage: False
Profile: False
Archiver: ar -rcs
Ranlib: ranlib
Preprocessor used: None
Preprocessor args:
Preprocessor output directory: None
Preprocessor extensions processed: []
Building test/test_exactness.F90
Compiling src/quad_rules.F90 serially
Compiling test/polynomials.F90 serially
Compiling test/test_exactness.F90 serially
Linking ~/quad/build/test/test_exactness
Target test/test_exactness.F90 has been successfully built
Builder options
Directories
Root of source files directory: "src test"
Building directory: "build/debug"
Compiled-objects .o directory: "build/debug/obj"
Compiled-objects .mod directory: "build/debug/mod"
Compiler options
Vendor: "gnu"
Compiler command: "gfortran"
Module directory switch: "-J"
Compiling flags: "-c -O0 -g -fcheck=all -Wall"
Linking flags: ""
Preprocessing flags: ""
Coverage: False
Profile: False
Archiver: ar -rcs
Ranlib: ranlib
Preprocessor used: None
Preprocessor args:
Preprocessor output directory: None
Preprocessor extensions processed: []
Building test/test_order.F90
Compiling src/integrands.F90 serially
Compiling test/test_order.F90 serially
Linking ~/quad/build/test/test_order
Target test/test_order.F90 has been successfully built
PASS test_exactness (0.00 s)
PASS test_order (0.00 s)
Results: 2 passed, 0 failed, 0 skipped
Total: 0.00 sfobis test builds each test with the default mode (here debug, with run-time checks: a test should run in the build that catches the most), runs them, and exits with status 1 when any fails. --filter 'test_ord*' selects tests by name, --suite by a ! fobis: suite=NAME comment in the file.
Doctests
A doctest is a check written in a procedure's documentation, next to the code it describes. quad_rules gets one per rule: the code between the fences runs, and its output must be the value after =>:
pure function trapezoid(f, a, b, n) result(s)
!< Composite trapezoidal rule with n intervals.
!<```fortran
!< use integrands, only : sine
!< print '(F8.5)', trapezoid(sine, 0._R8P, acos(-1._R8P), 64)
!<```
!=> 1.99960 <<<$ fobis doctests --quiet
executing doctest quad_rules-doctest-1
doctest passed
executing doctest quad_rules-doctest-2
doctest passedFoBiS writes each snippet into a small program that uses the module, builds it, runs it and compares its output with the expected one. The snippet doubles as an example in the documentation the comments generate.
Coverage
The coverage mode instruments the build (coverage = True); the tests run in it write the counts that fobis coverage reads, through gcovr (or lcov):
fobis test --mode coverage$ fobis coverage --mode coverage --format text --exclude "test/.*" && cat coverage/coverage.txt
Collecting coverage data from 5 .gcda file(s)...
Using backend: gcovr
(INFO) Reading coverage data...
(INFO) Writing coverage report...
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
src/integrands.F90 3 3 100%
src/quad_rules.F90 14 14 100%
------------------------------------------------------------------------------
TOTAL 17 17 100%
--------------------------------------------------------------------------------format html (the default) writes a browsable report in coverage/, --fail-under 90 fails a CI job below 90% of the lines.
What you learned
Test programs and fobis test, doctests and fobis doctests, a coverage mode and fobis coverage. Reference: test, doctests, coverage.
Next: 5. Dependencies.