One of the things I missed when I migrated from cmake to meson was the ease at which cmake discovers tests.

1# Tests
2option(PACKAGE_TESTS "Build the tests" OFF)
3if(PACKAGE_TESTS)
4  find_package(GTest REQUIRED)
5  enable_testing()
6  include(GoogleTest)
7  add_subdirectory(gtests)
8endif()

Thankfully, meson can kind of emulate this behavior, even in its restricted syntax. The key concept is arrays and their iterators.

 1test_array = [#
 2  # ['Pretty name', 'binary_name', 'BlahTest.cpp']
 3  ['String parser helpers', 'strparse_run', 'StringHelpersTest.cpp'],
 4  ['Improved Dimer', 'impldim_run', 'ImpDimerTest.cpp'],
 5]
 6foreach test : test_array
 7  test(test.get(0),
 8       executable(test.get(1),
 9          sources : ['gtests/'+test.get(2)],
10          dependencies : [ prog_deps, gtest_dep, gmock_dep ],
11          link_with : proglib,
12          cpp_args : prog_extra_args
13                 ),
14      )
15endforeach

Now this is pretty neat already, but we can go a step further and augment this with a working directory concept.

 1test_array = [#
 2  # ['Pretty name', 'binary_name', 'BlahTest.cpp', 'working_dir']
 3  ['Improved Dimer', 'impldim_run', 'ImpDimerTest.cpp', '/gtests/data/'],
 4  ['Matter converter', 'matter_run', 'MatterTest.cpp', '/gtests/data/systems/sulfolene'],
 5  ['String parser helpers', 'strparse_run', 'StringHelpersTest.cpp', ''],
 6]
 7foreach test : test_array
 8  test(test.get(0),
 9       executable(test.get(1),
10          sources : ['gtests/'+test.get(2)],
11          dependencies : [ prog_deps, gtest_dep, gmock_dep ],
12          link_with : proglib,
13          cpp_args : prog_extra_args
14                 ),
15        workdir : meson.source_root() + test.get(3)
16      )
17endforeach

This is rather satisfying. Combined with some wraps, it is also pretty portable.

Addenum: Tests with arguments

Recently, some openblas work made me rethink how meson handles tests and arguments. It turns out something relatively simple in make:

1xblat2s: sblat2.o $(BLASLIB)
2      $(FC) $(FFLAGS) $(LDFLAGS) -o $@ $^
3run: all
4      ./xblat2s < sblat2.in

Is rather complicated to emulate directly within meson. A wrapper is required, first in C:

 1#include <stdio.h>
 2#include <stdlib.h>
 3#include <string.h>
 4
 5int main(int argc, char *argv[]) {
 6    if (argc != 3) {
 7        fprintf(stderr, "Usage: %s <executable> <input_file>\n", argv[0]);
 8        return EXIT_FAILURE;
 9    }
10
11    char command[1024];
12    snprintf(command, sizeof(command), "%s < %s", argv[1], argv[2]);
13
14    int result = system(command);
15    if (result != 0) {
16        fprintf(stderr, "Error: Command '%s' failed with return code %d.\n", command, result);
17        return EXIT_FAILURE;
18    }
19
20    return EXIT_SUCCESS;
21}

Which can then be used within meson:

 1_blas_input_test_array = [#
 2  # ['Pretty name', 'binary_name', 'BlahTest.cpp', 'inputfile.in']
 3  ['Test REAL Level 2 BLAS', 'xblat2s', 'sblat2.f', 'sblat2.in'],
 4]
 5test_runner = executable('run_test',
 6                         sources: ['run_test.c'],
 7                         install: false)
 8# NOTE: For the tests to pass the executables need to be compiled first
 9foreach _test : _blas_input_test_array
10  executable(_test.get(1),
11             sources : _test.get(2),
12             link_with : netlib_blas,
13            )
14  test_exe = meson.current_build_dir() / _test.get(1)
15  input_file = meson.source_root() + '/lapack-netlib/BLAS/TESTING/' + _test.get(3)
16  test(_test.get(0), fortran_test_runner,
17       args: [test_exe, input_file],
18       workdir: meson.current_build_dir())
19endforeach

However, the main pain point with this is that the executables are not exactly test dependencies so this fails if the entire project hasn’t been compiled before running the tests. Still a pretty minor caveat.