The main Spglib test-suite is a standalone CMake project that can be executed on a pre-installed Spglib package or as
part of the main Spglib project. On top of this, there are language-dependent test-suites, e.g. pytest for the Python
bindings, and a tmt test layer that orchestrates all tests across each test-suite.
- CMake >= 3.11
Googletest(downloaded viaFetchContentif not available)- Optional:
tmt: to execute the tmt tests locally. Currently only Fedora-like systems are supported[test]python optional dependency: to execute python tests
We organize the test-suite as follows:
unit: Unit tests that test small individual components of the code-baseintegration: Integration tests that test the interplay between the units and the external dependencies. (Currently the api binding tests are not covered here, but infunctional)functional: Functional tests that test the physical correctness and how the user would actually interact with the libraryexample: Standalone example project tests. These are symlinked to../examplefolderpackage: Packaging tests that check the supported methods that the project can be imported
All tests are either implemented within the ctest test-suite layer or they are indirectly called by it. There can be
discrepancy in what and how the tests are executed, e.g. the project source being tested in package tests, and these
differences are being covered by the tmt test layer.
Tests on the ctest layer are implemented in a few methods:
- as
Googletestindividual tests:unit,functional,integration - as standalone CMake projects invoked by
ctest --build-and-test:example,package - as standalone test-suite invoked directly by ctest:
pytest
There are two ways of building and executing the ctest test-suite:
- Build as part of the main Spglib project with
SPGLIB_WITH_TESTS=ON. This tests the main Spglib project that is being built - Build as standalone project, i.e. running the typical cmake workflow in this test folder
This tests the Spglib project that is already installed on the system via
$ cmake -B ./build $ cmake --build ./build $ ctest --test-dir ./build
find_package, or thatFetchContentpoints to
You can find a full list of available tests by running:
$ ctest --test-dir ./build -NThe -N can also be used to check the effect of the test filtering by regex/labels.
The tests are also organized by labels which you can find a list of using:
$ ctest --test-dir ./build --print-labelsTo select specific tests, you can either use regex -R (simplest being just the name of the test), or the labels -L:
$ ctest --test-dir ./build -R "SymmetrySearch.test_spg_get_symmetry"
$ ctest --test-dir ./build -L unit_testsFirst you have to consider what is being tested and if it is a unit/functional/integration etc. test. After that,
depending on the type of test you either need to:
- Add or expand a
<gtest_testsuite_name>.cppfile with appropriate link in the folder'sCMakeLists.txtfile. The content of the<gtest_testsuite_name>.cpplooks like:#include <gtest/gtest.h> extern "C" { #include "spglib.h" } TEST(<gtest_testsuite_name>, <test_name>) { // ASSERT_EQ(a, b) // ASSERT_NE(a, b) // ASSERT_TRUE(a) }
- Add an example CMake project. It should be buildable by itself with optional/minimal integrated tests. Use the
tmttest layer to do more complex test checks. - Add or expand a language-specific test. Make sure the test-suite is included as an
add_testinsidectest
Although Googletest is automatically prepared, you may need to install it locally to highlight structs and functions.
Below is an example of c_cpp_properties.json for Mac.
{
"configurations": [
{
...
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/cmake-build-release-gcc/_deps/googletest-src/googletest/include",
"${workspaceFolder}/cmake-build-release-llvm/_deps/googletest-src/googletest/include"
],
...
}
]
}The launch.json below runs a single test with a debugger:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Launch(lldb): #381",
"type": "lldb",
"request": "launch",
"program": "${workspaceFolder}/build/test/test_suite",
"args": ["--gtest_filter=MagneticDataset.test_with_slightly_distorted_positions"],
"cwd": "${workspaceFolder}/build/test",
},
]
}When you find a doubtful cell with Python API, the following python snippets will be useful to prepare a test case for Python testing and C testing.
def print_python_cell(cell):
if len(cell) == 3:
lattice, positions, numbers = cell
magmoms = None
elif len(cell) == 4:
lattice, positions, numbers, magmoms = cell
contents = []
# lattice
contents.append("lattice = np.array([")
for i in range(3):
contents.append(f" [{lattice[i][0]:.8f}, {lattice[i][1]:.8f}, {lattice[i][2]:.8f}],")
contents.append("])")
# positions
contents.append("positions = np.array([")
for i in range(len(positions)):
contents.append(
f" [{positions[i][0]:.8f}, {positions[i][1]:.8f}, {positions[i][2]:.8f}],"
)
contents.append("])")
# numbers
contents.append("numbers = np.array([" + ", ".join(map(str, numbers)) + "])")
# magmoms
if magmoms:
contents.append("magmoms = np.array([")
for i in range(len(positions)):
contents.append(f" [{magmoms[i][0]:.8f}, {magmoms[i][1]:.8f}, {magmoms[i][2]:.8f}],")
contents.append("])")
print("\n".join(contents))def print_cpp_cell(cell):
if len(cell) == 3:
lattice, positions, numbers = cell
magmoms = None
elif len(cell) == 4:
lattice, positions, numbers, magmoms = cell
contents = []
# lattice
lattice_col = lattice.T
contents.append("double lattice[3][3] = {")
for i in range(3):
contents.append(
f" {{ {lattice_col[i][0]:.8f}, {lattice_col[i][1]:.8f}, {lattice_col[i][2]:.8f} }},"
)
contents.append("};")
# positions
contents.append("double positions[][3] = {")
for i in range(len(positions)):
contents.append(
f" {{ {positions[i][0]:.8f}, {positions[i][1]:.8f}, {positions[i][2]:.8f} }},"
)
contents.append("};")
# numbers
contents.append("int types[] = {" + ", ".join(map(str, numbers)) + "};")
# magmoms
if magmoms:
contents.append("double spins[] = {")
for i in range(len(positions)):
contents.append(f" {magmoms[i][0]:.8f}, {magmoms[i][1]:.8f}, {magmoms[i][2]:.8f},")
contents.append("};")
contents.append(f"int num_atoms = {len(positions)};")
print("\n".join(contents))Normally all functions outside of the exported spglib.h are not exposed, but for the purposes of unit testing, we need
access to these. Currently, we don't know a clear way of resolving this issue, but we know two ways of addressing this:
- Building Spglib as a static library for linking to the test-suite
- Use a macro
SPG_API_TESTthat expands toSPG_APIwhen building unit tests
We have opted for the latter approach for now, so keep in mind when writing unit tests to include SPG_API_TEST
directive.
The tmt test layer is primarily for orchestrating and running all the tests in arbitrary environments, e.g. in the
CI, post packaging, in the Fedora-CI, etc. Although this test-suite is currently limited to fedora-like environments,
there are plans to expand this to arbitrary systems soon.
The tmt tests-suite is composed of plans (a collection of tests with a common preparation step) and individual
tests, all of which are defined in the .fmf files.
After installing tmt from PyPI or other sources, you can do basic inspection of the tests/plans, even if the OS does
not support actual execution of the tests.
The .fmf files are simply yaml files with folder structure inheritance, and to view the actual
generated yaml-like content use:
$ fmf show --path ./:::{note}
The --path in fmf is equivalent to the --root in tmt and it points to the fmf tree root (the folder that
contains .fmf/version file) that you wish to introspect.
:::
To view the actual plans/tests use:
$ tmt --root ./ tests show
$ tmt --root ../.distro plans showThe tmt tests are primarily executed within the testing-farm environment through packit or other downstream like Fedora-CI. But you can also interact with it directly, e.g.
$ tmt --root ../.distro run --all plans --name full:::{note} The tests are currently assuming Spglib is already installed on the system. Standalone plans that compile Spglib as a preparation step is yet to be designed. :::
You can alter these executions by adding individual steps, e.g. if you want to change the provision step so that you run
in a container you can add a provision step in the middle like:
$ tmt --root ../.distro run --all provision --how container --image=fedora:rawhide plans --name fullAt the bare minimum, you can simply design a test that executes the test-suite, see pytest.fmf for example. For more
complex testing we use beakerlib, which basically provides a structured way of executing bash commands and check the
results and stdout. These tests are found as <test>.sh, the simplest reference being that of ctest.sh.