diff options
| -rw-r--r-- | docs/markdown/Unit-tests.md | 5 | ||||
| -rw-r--r-- | docs/markdown/snippets/test-slicing.md | 6 | ||||
| -rw-r--r-- | man/meson.1 | 5 | ||||
| -rw-r--r-- | mesonbuild/mtest.py | 31 | ||||
| -rw-r--r-- | test cases/unit/124 test slice/meson.build | 12 | ||||
| -rw-r--r-- | test cases/unit/124 test slice/test.py | 0 | ||||
| -rw-r--r-- | unittests/allplatformstests.py | 29 |
7 files changed, 86 insertions, 2 deletions
diff --git a/docs/markdown/Unit-tests.md b/docs/markdown/Unit-tests.md index 13f6093f2..5a7b19940 100644 --- a/docs/markdown/Unit-tests.md +++ b/docs/markdown/Unit-tests.md @@ -206,6 +206,11 @@ name(s), the test name(s) must be contained in the suite(s). This however is redundant-- it would be more useful to specify either specific test names or suite(s). +Since version *1.8.0*, you can pass `--slice i/n` to split up the set of tests +into `n` slices and execute the `ith` such slice. This allows you to distribute +a set of long-running tests across multiple machines to decrease the overall +runtime of tests. + ### Other test options Sometimes you need to run the tests multiple times, which is done like this: diff --git a/docs/markdown/snippets/test-slicing.md b/docs/markdown/snippets/test-slicing.md new file mode 100644 index 000000000..180b9ace5 --- /dev/null +++ b/docs/markdown/snippets/test-slicing.md @@ -0,0 +1,6 @@ +## New option to execute a slice of tests + +When tests take a long time to run a common strategy is to slice up the tests +into multiple sets, where each set is executed on a separate machine. You can +now use the `--slice i/n` argument for `meson test` to create `n` slices and +execute the `ith` slice. diff --git a/man/meson.1 b/man/meson.1 index 01b9abd89..deb320dbc 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -328,6 +328,9 @@ a multiplier to use for test timeout values (usually something like 100 for Valg .TP \fB\-\-setup\fR use the specified test setup +.Tp +\fB\-\-slice SLICE/NUM_SLICES\fR +Split tests into NUM_SLICES slices and execute slice number SLICE. (Since 1.8.0) .SH The wrap command @@ -410,7 +413,7 @@ Manage the packagefiles overlay .B meson rewrite modifies the project definition. - + .B meson rewrite [ .I options .B ] [ diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index bb58f617c..58cfef30e 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -99,6 +99,29 @@ def uniwidth(s: str) -> int: result += UNIWIDTH_MAPPING[w] return result +def test_slice(arg: str) -> T.Tuple[int, int]: + values = arg.split('/') + if len(values) != 2: + raise argparse.ArgumentTypeError("value does not conform to format 'SLICE/NUM_SLICES'") + + try: + nrslices = int(values[1]) + except ValueError: + raise argparse.ArgumentTypeError('NUM_SLICES is not an integer') + if nrslices <= 0: + raise argparse.ArgumentTypeError('NUM_SLICES is not a positive integer') + + try: + subslice = int(values[0]) + except ValueError: + raise argparse.ArgumentTypeError('SLICE is not an integer') + if subslice <= 0: + raise argparse.ArgumentTypeError('SLICE is not a positive integer') + if subslice > nrslices: + raise argparse.ArgumentTypeError('SLICE exceeds NUM_SLICES') + + return subslice, nrslices + # Note: when adding arguments, please also add them to the completion # scripts in $MESONSRC/data/shell-completions/ def add_arguments(parser: argparse.ArgumentParser) -> None: @@ -149,12 +172,13 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: help='Arguments to pass to the specified test(s) or all tests') parser.add_argument('--max-lines', default=100, dest='max_lines', type=int, help='Maximum number of lines to show from a long test log. Since 1.5.0.') + parser.add_argument('--slice', default=None, type=test_slice, metavar='SLICE/NUM_SLICES', + help='Split tests into NUM_SLICES slices and execute slice SLICE. Since 1.8.0.') parser.add_argument('args', nargs='*', help='Optional list of test names to run. "testname" to run all tests with that name, ' '"subprojname:testname" to specifically run "testname" from "subprojname", ' '"subprojname:" to run all tests defined by "subprojname".') - def print_safe(s: str) -> None: end = '' if s[-1] == '\n' else '\n' try: @@ -1977,6 +2001,11 @@ class TestHarness: tests = [t for t in self.tests if self.test_suitable(t)] if self.options.args: tests = list(self.tests_from_args(tests)) + if self.options.slice: + our_slice, nslices = self.options.slice + if nslices > len(tests): + raise MesonException(f'number of slices ({nslices}) exceeds number of tests ({len(tests)})') + tests = tests[our_slice - 1::nslices] if not tests: print('No suitable tests defined.', file=errorfile) diff --git a/test cases/unit/124 test slice/meson.build b/test cases/unit/124 test slice/meson.build new file mode 100644 index 000000000..a41c2f62d --- /dev/null +++ b/test cases/unit/124 test slice/meson.build @@ -0,0 +1,12 @@ +project('test_slice') + +python = import('python').find_installation('python3') + +foreach i : range(10) + test('test-' + (i + 1).to_string(), + python, + args: [ + meson.current_source_dir() / 'test.py' + ], + ) +endforeach diff --git a/test cases/unit/124 test slice/test.py b/test cases/unit/124 test slice/test.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test cases/unit/124 test slice/test.py diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 634a860ad..8bbe7c644 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -5130,6 +5130,35 @@ class AllPlatformTests(BasePlatformTests): def test_c_cpp_objc_objcpp_stds(self) -> None: self.__test_multi_stds(test_objc=True) + def test_slice(self): + testdir = os.path.join(self.unit_test_dir, '124 test slice') + self.init(testdir) + self.build() + + for arg, expectation in {'1/1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + '1/2': [1, 3, 5, 7, 9], + '2/2': [2, 4, 6, 8, 10], + '1/10': [1], + '2/10': [2], + '10/10': [10], + }.items(): + output = self._run(self.mtest_command + ['--slice=' + arg]) + tests = sorted([ int(x[5:]) for x in re.findall(r'test-[0-9]*', output) ]) + self.assertEqual(tests, expectation) + + for arg, expectation in {'': 'error: argument --slice: value does not conform to format \'SLICE/NUM_SLICES\'', + '0': 'error: argument --slice: value does not conform to format \'SLICE/NUM_SLICES\'', + '0/1': 'error: argument --slice: SLICE is not a positive integer', + 'a/1': 'error: argument --slice: SLICE is not an integer', + '1/0': 'error: argument --slice: NUM_SLICES is not a positive integer', + '1/a': 'error: argument --slice: NUM_SLICES is not an integer', + '2/1': 'error: argument --slice: SLICE exceeds NUM_SLICES', + '1/11': 'ERROR: number of slices (11) exceeds number of tests (10)', + }.items(): + with self.assertRaises(subprocess.CalledProcessError) as cm: + self._run(self.mtest_command + ['--slice=' + arg]) + self.assertIn(expectation, cm.exception.output) + def test_rsp_support(self): env = get_fake_env() cc = detect_c_compiler(env, MachineChoice.HOST) |
