From 43c45b3bdda7f5b41693e444a6fa8a582c7d3b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20Gl=C3=A4ser?= <dennis.glaeser@iws.uni-stuttgart.de> Date: Wed, 14 Apr 2021 18:54:02 +0200 Subject: [PATCH] [ci] make test selection part of test pipeline --- .gitlab-ci.yml | 4 +- .gitlab-ci/default.yml.template | 6 +- .gitlab-ci/makepipelineconfig.py | 137 ++++++++++++++++--------------- bin/testing/runselectedtests.py | 0 4 files changed, 76 insertions(+), 71 deletions(-) mode change 100644 => 100755 bin/testing/runselectedtests.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 008eea11a8..a6587d0097 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,9 +27,7 @@ generate-config: script: - | if [ $CI_PIPELINE_SOURCE == "merge_request_event" ]; then - dunecontrol --opts=$DUNE_OPTS_FILE --current all - pushd build-cmake && python3 ../bin/testing/findtests.py -f affectedtests.json -t origin/master && popd - python3 .gitlab-ci/makepipelineconfig.py -c build-cmake/affectedtests.json -o generated-config.yml + python3 .gitlab-ci/makepipelineconfig.py -o generated-config.yml --affectedtestsonly else python3 .gitlab-ci/makepipelineconfig.py -o generated-config.yml fi diff --git a/.gitlab-ci/default.yml.template b/.gitlab-ci/default.yml.template index 29095ff05d..90135ea3f4 100644 --- a/.gitlab-ci/default.yml.template +++ b/.gitlab-ci/default.yml.template @@ -2,13 +2,14 @@ default: image: $$IMAGE stages: - - build - - test +${stages} workflow: rules: - if: $$CI_PIPELINE_SOURCE=="parent_pipeline" +${test_select_job} + build dumux: stage: build script: @@ -17,6 +18,7 @@ ${build_script} paths: - build-cmake expire_in: 3 hours +${build_needs} test dumux: stage: test diff --git a/.gitlab-ci/makepipelineconfig.py b/.gitlab-ci/makepipelineconfig.py index 14e7221a6e..d5ea63bb13 100644 --- a/.gitlab-ci/makepipelineconfig.py +++ b/.gitlab-ci/makepipelineconfig.py @@ -2,7 +2,6 @@ import os import sys -import json import string from argparse import ArgumentParser @@ -11,15 +10,22 @@ if sys.version_info.major < 3: sys.exit('Python 3 required') parser = ArgumentParser(description='Generate dumux test pipeline .yml file') -parser.add_argument('-o', '--outfile', required=True, +parser.add_argument('-o', '--outfile', + required=True, help='Specify the file to write the pipeline definition') -parser.add_argument('-c', '--testconfig', required=False, - help='Specify a test configuration file containing the ' - 'tests that should be run within the test pipeline') -parser.add_argument('-t', '--template', required=False, +parser.add_argument('-a', '--affectedtestsonly', + required=False, + action='store_true', + help='Use this flag to create a pipeline that runs only ' + 'those tests that are affected by changes w.r.t to ' + 'the origin/master branch') +parser.add_argument('-t', '--template', + required=False, default='.gitlab-ci/default.yml.template', help='Specify the template .yml file to be used') -parser.add_argument('-i', '--indentation', required=False, default=4, +parser.add_argument('-i', '--indentation', + required=False, + default=4, help='Specify the indentation for the script commands') args = vars(parser.parse_args()) @@ -37,69 +43,68 @@ def substituteAndWrite(mapping): commandIndentation = ' '*args['indentation'] -duneConfigCommand = 'dunecontrol --opts=$DUNE_OPTS_FILE --current all' with open(args['outfile'], 'w') as ymlFile: - def makeScriptString(commands): - commands = [commandIndentation + '- ' + comm for comm in commands] - return '\n'.join(commands) - - def makeMultiLineCommand(commandParts): + def wrapDuneControl(command): + return 'dunecontrol --opts=$DUNE_OPTS_FILE --current ' + command - # add indentation to each part - result = [commandIndentation + ' ' + cp for cp in commandParts] - - # add line break token at the end of each part - result = ' \\\n'.join(result) + def makeYamlList(commands, indent=commandIndentation): + commands = [indent + '- ' + comm for comm in commands] + return '\n'.join(commands) - # add multiline trigger token at the beginning - return '|\n' + result + # if no configuration is given, build and run all tests (skip select stage) + if not args['affectedtestsonly']: + buildCommand = [wrapDuneControl('all'), + wrapDuneControl('bexec make -k -j4 build_tests')] + testCommand = [wrapDuneControl('bexec dune-ctest' + ' -j4 --output-on-failure')] - # if no configuration is given, build and run all tests - if not args['testconfig']: - buildCommand = [duneConfigCommand, - 'dunecontrol --opts=$DUNE_OPTS_FILE --current ' - 'make -k -j4 build_tests'] - testCommand = ['cd build-cmake', 'dune-ctest -j4 --output-on-failure'] + substituteAndWrite({'build_script': makeYamlList(buildCommand), + 'test_script': makeYamlList(testCommand), + 'stages': makeYamlList(['build', 'test'], ' '), + 'test_select_job': '', + 'build_needs': ''}) - # otherwise, parse data from the given configuration file + # otherwise, add a stage that detects the tests to be run first else: - with open(args['testconfig']) as configFile: - config = json.load(configFile) - - testNames = list(config.keys()) - targetNames = [tc['target'] for tc in config.values()] - - if not targetNames: - buildCommand = [duneConfigCommand, - 'echo "No tests to be built."'] - else: - # The MakeFile generated by cmake contains .NOTPARALLEL, - # as it only allows a single call to `CMakeFiles/Makefile2`. - # Parallelism is taken care of within that latter Makefile. - # We let the script create a small custom makeFile here on top - # of `Makefile2`, defining a new target to be built in parallel - buildCommand = [ - duneConfigCommand, - 'cd build-cmake', - 'rm -f TestMakefile && touch TestMakefile', - 'echo "include CMakeFiles/Makefile2" >> TestMakefile', - 'echo "" >> TestMakefile', - makeMultiLineCommand(['echo "build_selected_tests:"'] - + targetNames - + ['>> TestMakefile']), - 'make -f TestMakefile -j4 build_selected_tests'] - - if not testNames: - testCommand = ['echo "No tests to be run, make empty report."', - 'cd build-cmake', - 'dune-ctest -R NOOP'] - else: - testCommand = ['cd build-cmake', - makeMultiLineCommand(['dune-ctest -j4 ' - '--output-on-failure ' - '-R'] - + testNames)] - - substituteAndWrite({'build_script': makeScriptString(buildCommand), - 'test_script': makeScriptString(testCommand)}) + selectStageName = 'configure' + selectJobName = 'select tests' + + stages = makeYamlList([selectStageName, 'build', 'test'], ' ') + buildNeeds = '\n'.join([' needs:', + ' - job: {}'.format(selectJobName), + ' artifacts: true']) + + selectJob = '\n'.join([selectJobName + ':', + ' stage: {}'.format(selectStageName), + ' script:']) + selectJob += '\n' + selectJob += makeYamlList([wrapDuneControl('all'), + 'pushd build-cmake', + 'python3 ../bin/testing/findtests.py' + ' -f ../affectedtests.json' + ' -t origin/master', + 'popd']) + selectJob += '\n' + selectJob += '\n'.join([' artifacts:', + ' paths:', + ' - affectedtests.json', + ' expire_in: 3 hours']) + + buildCommand = [wrapDuneControl('all'), + 'cp affectedtests.json build-cmake', + 'pushd build-cmake', + 'python3 ../bin/testing/runselectedtests.py ' + ' -c affectedtests.json -b', + 'popd'] + testCommand = [wrapDuneControl('all'), + 'pushd build-cmake', + 'python3 ../bin/testing/runselectedtests.py ' + ' -c affectedtests.json -t', + 'popd'] + + substituteAndWrite({'build_script': makeYamlList(buildCommand), + 'test_script': makeYamlList(testCommand), + 'stages': stages, + 'test_select_job': selectJob, + 'build_needs': buildNeeds}) diff --git a/bin/testing/runselectedtests.py b/bin/testing/runselectedtests.py old mode 100644 new mode 100755 -- GitLab