From 434373d828e575996cc4cd5e0bae29b455d42a38 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 14:41:00 +0200 Subject: [PATCH 01/19] backport yaml parsing --- jenkins/helper/test_launch_controller.py | 156 +++++++++++++++++++++-- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 8c833e96..fbcf75d2 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -3,7 +3,9 @@ import argparse import copy import sys +import json from traceback import print_exc +import yaml from site_config import IS_ARM, IS_WINDOWS, IS_MAC, IS_COVERAGE @@ -110,7 +112,10 @@ def list_generator(cluster): "buckets": "number of buckets to use for this test", "suffix": "suffix that is appended to the tests folder name", "priority": "priority that controls execution order. Testsuites with lower priority are executed later", - "parallelity": "parallelity how many resources will the job use in the SUT? Default: 1 in Single server, 4 in Clusters" + "parallelity": "parallelity how many resources will the job use in the SUT? Default: 1 in Single server, 4 in Clusters", + "type": "single or cluster flag", + "full": "whether to spare from a single or full run", + "sniff": "whether to enable sniffing", } @@ -232,22 +237,149 @@ def read_definition_line(line): "params": params } +def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, yaml_struct): + """ convert yaml representation into the internal one """ + if not 'options' in definition: + definition['options'] = {} + flags = [] + params = {} + arangosh_args = [] + args = [] + if 'args' in definition: + if not isinstance(definition['args'], dict): + raise Exception(f"expected args to be a key value list! have: {definition['args']}") + for key, val in definition['args'].items(): + if key == 'moreArgv': + args.append(val) + else: + args.append(f"--{key}") + if isinstance(val, bool): + args.append("true" if val else "false") + else: + args.append(val) + if 'arangosh_args' in definition: + if not isinstance(definition['arangosh_args'], dict): + raise Exception(f"expected arangosh_args to be a key value list! have: {definition['arangosh_args']}") + for key, val in definition['arangosh_args'].items(): + arangosh_args.append(f"--{key}") + if isinstance(val, bool): + arangosh_args.append("true" if val else "false") + else: + arangosh_args.append(val) + + is_cluster = False + if 'type' in params: + if params['type'] == "cluster": + is_cluster = True + flags.append('cluster') + else: + flags.append('single') + size = "medium" if is_cluster else "small" + size = size if not "size" in params else params['size'] + params = validate_params(definition['options'], is_cluster) + + if 'full' in params: + flags.append("full" if params["full"] else "!full") + if 'coverage' in params: + flags.append("coverage" if params["coverage"] else "!coverage") + if 'sniff' in params: + flags.append("sniff" if params["sniff"] else "!sniff") + if yaml_struct != {}: + run_job = yaml_struct['add-yaml']['derives-to'] + else: + run_job = 'run-linux-tests' + return { + "bucket": bucket_name, + "name": name if not "name" in params else params['name'], + "suites": suite, + "size": size, + "flags": flags, + "args": args.copy(), + "arangosh_args": arangosh_args.copy(), + "params": params.copy(), + "testfile_definitions": testfile_definitions, + "run_job": run_job, + } + +def read_yaml_serial_suite(name, definition, testfile_definitions, bucket_name, yaml_struct): + """ convert yaml representation into the internal one """ + generated_definition = { + } + if 'options' in definition: + generated_definition['options'] = definition['options'] + args = {} + generated_name = "" + if 'args' in definition: + args = definition['args'].copy() + if isinstance(definition['suites'][0], str): + generated_name = ','.join(definition['suites']) + else: + suite_strs = [] + options_json = [] + for suite in definition['suites']: + suite_name = list(suite.keys())[0] + if 'args' in suite[suite_name]: + options_json.append(suite[suite_name]['args']) + suite_strs.append(suite_name) + generated_name = ','.join(suite_strs) + args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) + if args != {}: + generated_definition['args'] = args + name = generated_name + if 'name' in definition: + name = definition['name'] + return read_yaml_suite(name, generated_name, generated_definition, testfile_definitions, bucket_name, yaml_struct) + +def read_yaml_bucket_suite(name, definition, testfile_definitions, bucket_name, yaml_struct): + """ convert yaml representation into the internal one """ + bucket_name = definition['name'] + ret = [] + for suite in definition['suites']: + suite_name = list(suite.keys())[0] + ret.append(read_yaml_suite(suite_name, suite_name, suite[suite_name], testfile_definitions, bucket_name, yaml_struct)) + return ret def read_definitions(filename): """ read test definitions txt """ tests = [] has_error = False - with open(filename, "r", encoding="utf-8") as filep: - for line_no, line in enumerate(filep): - line = line.strip() - if line.startswith("#") or len(line) == 0: - continue # ignore comments - try: - test = read_definition_line(line) - tests.append(test) - except Exception as exc: - print(f"{filename}:{line_no + 1}: \n`{line}`\n {exc}", file=sys.stderr) - has_error = True + testfile_definitions = {} + yaml_text = "" + have_yaml = False + parsed_yaml = {} + if filename.endswith(".yml"): + with open(filename, "r", encoding="utf-8") as filep: + config = yaml.safe_load(filep) + filtered_config = [] + for testcase in config: + suite_name = list(testcase.keys())[0] + if suite_name == "add-yaml": + parsed_yaml = {"add-yaml": copy.deepcopy(testcase["add-yaml"])} + elif suite_name == "jobProperties": + testfile_definitions = copy.deepcopy(testcase["jobProperties"]) + else: + filtered_config.append(testcase) + for testcase in filtered_config: + suite_name = list(testcase.keys())[0] + if suite_name == "serial": + tests.append(read_yaml_serial_suite(suite_name, testcase, testfile_definitions, None, parsed_yaml)) + elif suite_name == "bucket": + tests += read_yaml_bucket_suite(suite_name, testcase, testfile_definitions, None, parsed_yaml) + None + else: + tests.append(read_yaml_suite(suite_name, suite_name, testcase[suite_name], testfile_definitions, None, parsed_yaml)) + else: + with open(filename, "r", encoding="utf-8") as filep: + for line_no, line in enumerate(filep): + line = line.strip() + if line.startswith("#") or len(line) == 0: + continue # ignore comments + try: + test = read_definition_line(line) + tests.append(test) + except Exception as exc: + print(f"{filename}:{line_no + 1}: \n`{line}`\n {exc}", file=sys.stderr) + has_error = True if has_error: raise Exception("abort due to errors") return tests From ca7dbdfdb1a3447e6462c0c0fe088afe90eb6e45 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 15:03:12 +0200 Subject: [PATCH 02/19] choose yaml depending on the main branch --- jenkins/runCoverageRocksDB.fish | 6 +++++- scripts/runTests.fish | 13 +++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/jenkins/runCoverageRocksDB.fish b/jenkins/runCoverageRocksDB.fish index 0ea9eac0..1957618d 100755 --- a/jenkins/runCoverageRocksDB.fish +++ b/jenkins/runCoverageRocksDB.fish @@ -1,6 +1,10 @@ #!/usr/bin/env fish -set -xg TEST_DEFINITIONS test-definitions.txt +if test "$BASE_VERSION" == "devel" + set -xg TEST_DEFINITIONS test-definitions.yml +else + set -xg TEST_DEFINITIONS test-definitions.txt +end if test (count $argv) -gt 0 set -xg TEST_DEFINITIONS $argv[1] end diff --git a/scripts/runTests.fish b/scripts/runTests.fish index ae83463e..0157e6a8 100755 --- a/scripts/runTests.fish +++ b/scripts/runTests.fish @@ -6,6 +6,11 @@ set ENTERPRISE_ARG "--no-enterprise" if test "$ENTERPRISEEDITION" = "On" set ENTERPRISE_ARG "--enterprise" end +if test "$BASE_VERSION" == "devel" + set -xg TD_TYPE yml +else + set -xg TD_TYPE txt +end ################################################################################ ## Single tests: runtime,command @@ -13,7 +18,7 @@ end function launchSingleTests echo "Using test definitions from arangodb repo" - python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.txt" -f launch "$ENTERPRISE_ARG" + python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.$TD_TYPE" -f launch "$ENTERPRISE_ARG" set x $status if test "$x" = "0" -a -f $INNERWORKDIR/testRuns.html set -xg result "GOOD" @@ -28,7 +33,7 @@ end ################################################################################ function launchGTest - python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.txt" -f launch --gtest "$ENTERPRISE_ARG" + python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.$TD_TYPE" -f launch --gtest "$ENTERPRISE_ARG" set x $status if test "$x" = "0" -a -f $INNERWORKDIR/testRuns.html set -xg result "GOOD" @@ -44,7 +49,7 @@ end function launchClusterTests echo "Using test definitions from arangodb repo" - python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.txt" -f launch --cluster "$ENTERPRISE_ARG" + python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.$TD_TYPE" -f launch --cluster "$ENTERPRISE_ARG" set x $status if test "$x" = "0" -a -f $INNERWORKDIR/testRuns.html set -xg result "GOOD" @@ -60,7 +65,7 @@ end function launchSingleClusterTests echo "Using test definitions from arangodb repo" - python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.txt" -f launch --single_cluster "$ENTERPRISE_ARG" + python3 -u "$WORKSPACE/jenkins/helper/test_launch_controller.py" "$INNERWORKDIR/ArangoDB/tests/test-definitions.$TD_TYPE" -f launch --single_cluster "$ENTERPRISE_ARG" set x $status if test "$x" = "0" -a -f $INNERWORKDIR/testRuns.html set -xg result "GOOD" From 963f35448df72f59389d987572dc6a4dd030f0f5 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 15:29:55 +0200 Subject: [PATCH 03/19] choose yaml depending on the main branch --- jenkins/runCoverageRocksDB.fish | 2 +- scripts/runTests.fish | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkins/runCoverageRocksDB.fish b/jenkins/runCoverageRocksDB.fish index 1957618d..cfa448a4 100755 --- a/jenkins/runCoverageRocksDB.fish +++ b/jenkins/runCoverageRocksDB.fish @@ -1,6 +1,6 @@ #!/usr/bin/env fish -if test "$BASE_VERSION" == "devel" +if test "$BASE_VERSION" = "devel" set -xg TEST_DEFINITIONS test-definitions.yml else set -xg TEST_DEFINITIONS test-definitions.txt diff --git a/scripts/runTests.fish b/scripts/runTests.fish index 0157e6a8..f29452b9 100755 --- a/scripts/runTests.fish +++ b/scripts/runTests.fish @@ -6,7 +6,7 @@ set ENTERPRISE_ARG "--no-enterprise" if test "$ENTERPRISEEDITION" = "On" set ENTERPRISE_ARG "--enterprise" end -if test "$BASE_VERSION" == "devel" +if test "$BASE_VERSION" = "devel" set -xg TD_TYPE yml else set -xg TD_TYPE txt From 3de395c6269ba6dc1a72d8658cb6a91af9fc5992 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 16:00:20 +0200 Subject: [PATCH 04/19] debug --- scripts/runTests.fish | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/runTests.fish b/scripts/runTests.fish index f29452b9..3daa5cf0 100755 --- a/scripts/runTests.fish +++ b/scripts/runTests.fish @@ -6,7 +6,11 @@ set ENTERPRISE_ARG "--no-enterprise" if test "$ENTERPRISEEDITION" = "On" set ENTERPRISE_ARG "--enterprise" end + +echo "--------------------------------------------------------------------------------" +echo $BASE_VERSION if test "$BASE_VERSION" = "devel" + echo yaml set -xg TD_TYPE yml else set -xg TD_TYPE txt From d8c7a236f983140d6ae53a92e88fe327e2c9bbd5 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 17:00:20 +0200 Subject: [PATCH 05/19] search for the yaml --- jenkins/runCoverageRocksDB.fish | 2 +- scripts/runTests.fish | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jenkins/runCoverageRocksDB.fish b/jenkins/runCoverageRocksDB.fish index cfa448a4..c4cfca3c 100755 --- a/jenkins/runCoverageRocksDB.fish +++ b/jenkins/runCoverageRocksDB.fish @@ -1,6 +1,6 @@ #!/usr/bin/env fish -if test "$BASE_VERSION" = "devel" +if test -f "$INNERWORKDIR/ArangoDB/tests/test-definitions.yml" set -xg TEST_DEFINITIONS test-definitions.yml else set -xg TEST_DEFINITIONS test-definitions.txt diff --git a/scripts/runTests.fish b/scripts/runTests.fish index 3daa5cf0..c79d0010 100755 --- a/scripts/runTests.fish +++ b/scripts/runTests.fish @@ -9,7 +9,8 @@ end echo "--------------------------------------------------------------------------------" echo $BASE_VERSION -if test "$BASE_VERSION" = "devel" +set +if test -f "$INNERWORKDIR/ArangoDB/tests/test-definitions.yml" echo yaml set -xg TD_TYPE yml else From 9688f5a216e7cc83d587ddcb9a48068b757a9208 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 17:28:03 +0200 Subject: [PATCH 06/19] check for already integer --- jenkins/helper/test_launch_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index fbcf75d2..45594d9b 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -166,7 +166,7 @@ def parse_number(value): def parse_number_or_default(key, default_value=None): """ check number """ - if key in params: + if key in params and not isinstance(params[key], int): if params[key][0] == '*': # factor the default params[key] = default_value * parse_number(params[key][1:]) else: From f6a381ddc4afe3dffd3da00c662826e8efd05446 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 17:55:20 +0200 Subject: [PATCH 07/19] add missing parallelity --- jenkins/helper/test_launch_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 45594d9b..4c69e08f 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -298,7 +298,8 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, "arangosh_args": arangosh_args.copy(), "params": params.copy(), "testfile_definitions": testfile_definitions, - "run_job": run_job, + "run_job": run_job,, + "parallelity": params["parallelity"], } def read_yaml_serial_suite(name, definition, testfile_definitions, bucket_name, yaml_struct): From 62c93ad7ec2818c690b8c196171c527f38c70403 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 18:14:42 +0200 Subject: [PATCH 08/19] make sure all is passed on correctly --- jenkins/helper/test_launch_controller.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 4c69e08f..59bd7993 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -268,8 +268,8 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, arangosh_args.append(val) is_cluster = False - if 'type' in params: - if params['type'] == "cluster": + if 'type' in definition['options']: + if definition['options']['type'] == "cluster": is_cluster = True flags.append('cluster') else: @@ -290,15 +290,17 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, run_job = 'run-linux-tests' return { "bucket": bucket_name, - "name": name if not "name" in params else params['name'], + "name": params.get("name", suite), "suites": suite, + "suite": suite, "size": size, "flags": flags, "args": args.copy(), + "priority": params["priority"], "arangosh_args": arangosh_args.copy(), "params": params.copy(), "testfile_definitions": testfile_definitions, - "run_job": run_job,, + "run_job": run_job, "parallelity": params["parallelity"], } From 1408d7bdd5b1d372111ee74f96016e365d178b3a Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 18:28:01 +0200 Subject: [PATCH 09/19] make sure all is passed on correctly --- jenkins/helper/test_launch_controller.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 59bd7993..b61baaf0 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -166,13 +166,14 @@ def parse_number(value): def parse_number_or_default(key, default_value=None): """ check number """ - if key in params and not isinstance(params[key], int): - if params[key][0] == '*': # factor the default - params[key] = default_value * parse_number(params[key][1:]) - else: - params[key] = parse_number(params[key]) - elif default_value is not None: - params[key] = default_value + if not isinstance(params[key], int): + if key in params: + if params[key][0] == '*': # factor the default + params[key] = default_value * parse_number(params[key][1:]) + else: + params[key] = parse_number(params[key]) + elif default_value is not None: + params[key] = default_value parse_number_or_default("priority", 250) parse_number_or_default("parallelity", 4 if is_cluster else 1) From d1f195c6d60f7847adf9f5792d493d4944323ca1 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 18:29:05 +0200 Subject: [PATCH 10/19] make sure all is passed on correctly --- jenkins/helper/test_launch_controller.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index b61baaf0..c299e2ae 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -166,14 +166,15 @@ def parse_number(value): def parse_number_or_default(key, default_value=None): """ check number """ - if not isinstance(params[key], int): - if key in params: - if params[key][0] == '*': # factor the default - params[key] = default_value * parse_number(params[key][1:]) - else: - params[key] = parse_number(params[key]) - elif default_value is not None: - params[key] = default_value + if key in params: + if isinstance(params[key], int): + return + if params[key][0] == '*': # factor the default + params[key] = default_value * parse_number(params[key][1:]) + else: + params[key] = parse_number(params[key]) + elif default_value is not None: + params[key] = default_value parse_number_or_default("priority", 250) parse_number_or_default("parallelity", 4 if is_cluster else 1) From a187a37ab1e3368917de8e41c119269fe4e34fda Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 18:31:38 +0200 Subject: [PATCH 11/19] make sure all is passed on correctly --- jenkins/helper/test_launch_controller.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index c299e2ae..59bd7993 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -166,9 +166,7 @@ def parse_number(value): def parse_number_or_default(key, default_value=None): """ check number """ - if key in params: - if isinstance(params[key], int): - return + if key in params and not isinstance(params[key], int): if params[key][0] == '*': # factor the default params[key] = default_value * parse_number(params[key][1:]) else: From 917ddc73cfdaada31ff8944b1756e58c8daf2698 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 13 Oct 2025 18:32:14 +0200 Subject: [PATCH 12/19] dedebug --- scripts/runTests.fish | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/runTests.fish b/scripts/runTests.fish index c79d0010..48c88c6a 100755 --- a/scripts/runTests.fish +++ b/scripts/runTests.fish @@ -7,9 +7,6 @@ if test "$ENTERPRISEEDITION" = "On" set ENTERPRISE_ARG "--enterprise" end -echo "--------------------------------------------------------------------------------" -echo $BASE_VERSION -set if test -f "$INNERWORKDIR/ArangoDB/tests/test-definitions.yml" echo yaml set -xg TD_TYPE yml From 3d59f663c22067a8d1492a3646893c10cefa2f09 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Thu, 16 Oct 2025 15:09:23 +0200 Subject: [PATCH 13/19] backport changes --- jenkins/helper/test_launch_controller.py | 190 ++++++++++++++++------- 1 file changed, 136 insertions(+), 54 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 59bd7993..001c36ba 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -83,6 +83,23 @@ def list_generator(cluster): return res_sg + res_cl return list_generator(args.cluster) +def filter_one_test(args, test): + """filter testcase by operations target Single/Cluster/full""" + if args.all: + return False + if IS_COVERAGE: + if 'coverage' in test: + return True + full = args.full or args.nightly + filters = [] + + if 'full' in test: + if full and not test['full']: + return True + if not full and test['full']: + return True + return False + formats = { "dump": generate_dump_output, "launch": launch, @@ -91,6 +108,7 @@ def list_generator(cluster): known_flags = { "cluster": "this test requires a cluster", "single": "this test requires a single server", + "mixed": "some buckets will run cluster, some not.", "full": "this test is only executed in full tests", "!full": "this test is only executed in non-full tests", "gtest": "only the testsuites starting with 'gtest' are to be executed", @@ -165,7 +183,7 @@ def parse_number(value): raise Exception(f"invalid numeric value: {value}") from exc def parse_number_or_default(key, default_value=None): - """ check number """ + """check number""" if key in params and not isinstance(params[key], int): if params[key][0] == '*': # factor the default params[key] = default_value * parse_number(params[key][1:]) @@ -182,7 +200,7 @@ def parse_number_or_default(key, default_value=None): def validate_flags(flags): - """ check whether target flags are valid """ + """check whether target flags are valid""" if "cluster" in flags and "single" in flags: raise Exception("`cluster` and `single` specified for the same test") if "full" in flags and "!full" in flags: @@ -190,7 +208,7 @@ def validate_flags(flags): def read_definition_line(line): - """ parse one test definition line """ + """parse one test definition line""" bits = line.split() if len(bits) < 1: raise Exception("expected at least one argument: ") @@ -226,18 +244,23 @@ def read_definition_line(line): validate_flags(flags) params = validate_params(params, 'cluster' in flags) + if len(arangosh_args) == 0: + arangosh_args = "" + run_job = 'run-linux-tests' return { "name": params.get("name", suites), - "suite": suites, + "suites": suites, "priority": params["priority"], "parallelity": params["parallelity"], "flags": flags, "args": args, "arangosh_args": arangosh_args, - "params": params + "params": params, + "testfile_definitions": testfile_definitions, + "run_job": run_job, } -def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, yaml_struct): +def read_yaml_suite(name, suite, definition, testfile_definitions): """ convert yaml representation into the internal one """ if not 'options' in definition: definition['options'] = {} @@ -267,16 +290,20 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, else: arangosh_args.append(val) + medium_size = False is_cluster = False - if 'type' in definition['options']: - if definition['options']['type'] == "cluster": - is_cluster = True + if 'type' in params: + if params['type'] == "cluster": + medium_size = True flags.append('cluster') + elif params['type'] == "mixed": + medium_size = True + flags.append('mixed') else: flags.append('single') - size = "medium" if is_cluster else "small" - size = size if not "size" in params else params['size'] params = validate_params(definition['options'], is_cluster) + size = "medium" if medium_size else "small" + size = size if not "size" in params else params['size'] if 'full' in params: flags.append("full" if params["full"] else "!full") @@ -284,15 +311,10 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, flags.append("coverage" if params["coverage"] else "!coverage") if 'sniff' in params: flags.append("sniff" if params["sniff"] else "!sniff") - if yaml_struct != {}: - run_job = yaml_struct['add-yaml']['derives-to'] - else: - run_job = 'run-linux-tests' + run_job = 'run-linux-tests' return { - "bucket": bucket_name, - "name": params.get("name", suite), + "name": name if not "name" in params else params['name'], "suites": suite, - "suite": suite, "size": size, "flags": flags, "args": args.copy(), @@ -304,7 +326,27 @@ def read_yaml_suite(name, suite, definition, testfile_definitions, bucket_name, "parallelity": params["parallelity"], } -def read_yaml_serial_suite(name, definition, testfile_definitions, bucket_name, yaml_struct): +def get_args(args): + """ serialize args into json similar to fromArgv in testing.js """ + sub_args = {} + for key in args.keys(): + value = args[key] + if ":" in key: + keyparts = key.split(":") + if not keyparts[0] in sub_args: + sub_args[keyparts[0]] = {} + sub_args[keyparts[0]][keyparts[1]] = value + elif key in sub_args: + if isinstance(sub_args[key], list): + sub_args[key].append(value) + else: + sub_args[key] = [value] + else: + sub_args[key] = value + return sub_args + + +def read_yaml_multi_suite(name, definition, testfile_definitions, cli_args): """ convert yaml representation into the internal one """ generated_definition = { } @@ -320,57 +362,97 @@ def read_yaml_serial_suite(name, definition, testfile_definitions, bucket_name, suite_strs = [] options_json = [] for suite in definition['suites']: - suite_name = list(suite.keys())[0] - if 'args' in suite[suite_name]: - options_json.append(suite[suite_name]['args']) + if isinstance(suite, str): + options_json.append({}) + suite_name = suite + else: + suite_name = list(suite.keys())[0] + if not isinstance(suite, dict): + raise Exception(f"suite should be a dict, it is {type(suite)}") + if 'options' in suite[suite_name]: + if filter_one_test(cli_args, suite[suite_name]['options']): + print(f"skipping {suite}") + continue + if 'args' in suite[suite_name]: + options_json.append(get_args(suite[suite_name]['args'])) + else: + options_json.append({}) suite_strs.append(suite_name) generated_name = ','.join(suite_strs) args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) if args != {}: generated_definition['args'] = args - name = generated_name - if 'name' in definition: - name = definition['name'] - return read_yaml_suite(name, generated_name, generated_definition, testfile_definitions, bucket_name, yaml_struct) + return read_yaml_suite(name, generated_name, generated_definition, testfile_definitions) -def read_yaml_bucket_suite(name, definition, testfile_definitions, bucket_name, yaml_struct): +def read_yaml_bucket_suite(bucket_name, definition, testfile_definitions, cli_args): """ convert yaml representation into the internal one """ - bucket_name = definition['name'] - ret = [] + args = {} + if 'args' in definition: + args = definition['args'] + suite_names = [] + sub_suites = [] + options_json = [] for suite in definition['suites']: - suite_name = list(suite.keys())[0] - ret.append(read_yaml_suite(suite_name, suite_name, suite[suite_name], testfile_definitions, bucket_name, yaml_struct)) - return ret - -def read_definitions(filename): - """ read test definitions txt """ + if isinstance(suite, str): + options_json.append({}) + suite_names.append(suite) + else: + suite_name = list(suite.keys())[0] + if 'options' in suite[suite_name]: + if filter_one_test(cli_args, suite[suite_name]['options']): + print(f"skipping {suite}") + continue + suite_names.append(suite_name) + sub_suites.append(suite[suite_name]) + if 'args' in suite[suite_name]: + options_json.append(get_args(suite[suite_name]['args'])) + else: + options_json.append({}) + args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) + joint_suite_name = ','.join(suite_names) + definition['options']['buckets'] = len(suite_names) + definition['options']['args'] = args + + return read_yaml_suite(bucket_name, + joint_suite_name, + { + 'options': definition['options'], + 'name': bucket_name, + 'args': args, + 'suites': definition['suites'] + }, + testfile_definitions) + +def read_definitions(filename, override_branch, args): + """read test definitions txt""" tests = [] has_error = False testfile_definitions = {} yaml_text = "" - have_yaml = False - parsed_yaml = {} if filename.endswith(".yml"): with open(filename, "r", encoding="utf-8") as filep: config = yaml.safe_load(filep) - filtered_config = [] + if isinstance(config, dict): + if "add-yaml" in config: + parsed_yaml = {"add-yaml": copy.deepcopy(config["add-yaml"])} + if "jobProperties" in config: + testfile_definitions = copy.deepcopy(config["jobProperties"]) + config = config['tests'] for testcase in config: suite_name = list(testcase.keys())[0] - if suite_name == "add-yaml": - parsed_yaml = {"add-yaml": copy.deepcopy(testcase["add-yaml"])} - elif suite_name == "jobProperties": - testfile_definitions = copy.deepcopy(testcase["jobProperties"]) - else: - filtered_config.append(testcase) - for testcase in filtered_config: - suite_name = list(testcase.keys())[0] - if suite_name == "serial": - tests.append(read_yaml_serial_suite(suite_name, testcase, testfile_definitions, None, parsed_yaml)) - elif suite_name == "bucket": - tests += read_yaml_bucket_suite(suite_name, testcase, testfile_definitions, None, parsed_yaml) - None - else: - tests.append(read_yaml_suite(suite_name, suite_name, testcase[suite_name], testfile_definitions, None, parsed_yaml)) + try: + suite = testcase[suite_name] + if "suites" in suite: + if 'options' in suite and 'bucket' in suite['options']: + tests.append(read_yaml_bucket_suite(suite_name, suite, testfile_definitions, args)) + else: + tests.append(read_yaml_multi_suite(suite_name, suite, testfile_definitions, args)) + else: + tests.append(read_yaml_suite(suite_name, suite_name, + suite, testfile_definitions)) + except Exception as ex: + print(f"while parsing {suite_name} {testcase}") + raise ex else: with open(filename, "r", encoding="utf-8") as filep: for line_no, line in enumerate(filep): @@ -398,7 +480,7 @@ def main(): """ entrypoint """ try: args = parse_arguments() - tests = read_definitions(args.definitions) + tests = read_definitions(args.definitions, "", args) if args.validate_only: return # nothing left to do tests = filter_tests(args, tests) From 65fb713b4e63312fb160e55937d38b3383396d21 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Thu, 16 Oct 2025 15:20:40 +0200 Subject: [PATCH 14/19] flat buckets --- jenkins/helper/test_launch_controller.py | 53 +++++++++++++----------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 001c36ba..822d0d9a 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -386,42 +386,47 @@ def read_yaml_multi_suite(name, definition, testfile_definitions, cli_args): def read_yaml_bucket_suite(bucket_name, definition, testfile_definitions, cli_args): """ convert yaml representation into the internal one """ + ret = [] args = {} if 'args' in definition: args = definition['args'] - suite_names = [] - sub_suites = [] - options_json = [] + options = [] + if 'options' in definition: + options = definition['options'] for suite in definition['suites']: if isinstance(suite, str): - options_json.append({}) - suite_names.append(suite) + ret.append( + read_yaml_suite(suite, + suite, + { + 'options': definition['options'], + 'name': bucket_name, + 'args': args, + 'suites': suite + }, + testfile_definitions) + ) else: suite_name = list(suite.keys())[0] + local_options = options.copy() if 'options' in suite[suite_name]: - if filter_one_test(cli_args, suite[suite_name]['options']): - print(f"skipping {suite}") - continue - suite_names.append(suite_name) - sub_suites.append(suite[suite_name]) + local_options = local_options | suite[suite_name]['options'] + local_args = args.copy() if 'args' in suite[suite_name]: - options_json.append(get_args(suite[suite_name]['args'])) - else: - options_json.append({}) - args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) - joint_suite_name = ','.join(suite_names) - definition['options']['buckets'] = len(suite_names) - definition['options']['args'] = args - - return read_yaml_suite(bucket_name, - joint_suite_name, + local_args = local_args | suite[suite_name]['args'] + ret.append( + read_yaml_suite(suite_name, + suite_name, { - 'options': definition['options'], + 'options': local_options, 'name': bucket_name, - 'args': args, - 'suites': definition['suites'] + 'args': local_args, + 'suites': suite_name }, testfile_definitions) + ) + + return ret def read_definitions(filename, override_branch, args): """read test definitions txt""" @@ -444,7 +449,7 @@ def read_definitions(filename, override_branch, args): suite = testcase[suite_name] if "suites" in suite: if 'options' in suite and 'bucket' in suite['options']: - tests.append(read_yaml_bucket_suite(suite_name, suite, testfile_definitions, args)) + tests += read_yaml_bucket_suite(suite_name, suite, testfile_definitions, args) else: tests.append(read_yaml_multi_suite(suite_name, suite, testfile_definitions, args)) else: From ab2e71a54a4873143075efcf0d404de7b484328b Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Thu, 16 Oct 2025 15:53:28 +0200 Subject: [PATCH 15/19] flat buckets --- jenkins/helper/test_launch_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 822d0d9a..69fe0aef 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -90,7 +90,7 @@ def filter_one_test(args, test): if IS_COVERAGE: if 'coverage' in test: return True - full = args.full or args.nightly + full = args.full filters = [] if 'full' in test: From 93b145a993dd6f924429fbf82a01172846c160f8 Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Thu, 16 Oct 2025 16:22:47 +0200 Subject: [PATCH 16/19] fix struct --- jenkins/helper/test_launch_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index 69fe0aef..ef05958a 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -314,7 +314,7 @@ def read_yaml_suite(name, suite, definition, testfile_definitions): run_job = 'run-linux-tests' return { "name": name if not "name" in params else params['name'], - "suites": suite, + "suite": suite, "size": size, "flags": flags, "args": args.copy(), @@ -402,7 +402,7 @@ def read_yaml_bucket_suite(bucket_name, definition, testfile_definitions, cli_ar 'options': definition['options'], 'name': bucket_name, 'args': args, - 'suites': suite + 'suite': suite }, testfile_definitions) ) @@ -421,7 +421,7 @@ def read_yaml_bucket_suite(bucket_name, definition, testfile_definitions, cli_ar 'options': local_options, 'name': bucket_name, 'args': local_args, - 'suites': suite_name + 'suite': suite_name }, testfile_definitions) ) From 5d8cccd4bb385059fa14c61e1e72608f7c5428ad Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Fri, 17 Oct 2025 13:15:54 +0200 Subject: [PATCH 17/19] need to know cluster before... --- jenkins/helper/test_launch_controller.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index ef05958a..fd3067c4 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -291,7 +291,10 @@ def read_yaml_suite(name, suite, definition, testfile_definitions): arangosh_args.append(val) medium_size = False - is_cluster = False + is_cluster = (definition['options'] and + 'type' in definition['options'] and + definition['options']['type'] == 'cluster') + params = validate_params(definition['options'], is_cluster) if 'type' in params: if params['type'] == "cluster": medium_size = True @@ -301,7 +304,6 @@ def read_yaml_suite(name, suite, definition, testfile_definitions): flags.append('mixed') else: flags.append('single') - params = validate_params(definition['options'], is_cluster) size = "medium" if medium_size else "small" size = size if not "size" in params else params['size'] From 7f30b2fcda51e0c422d24d9003fd974e5238eddd Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 20 Oct 2025 12:25:34 +0200 Subject: [PATCH 18/19] follow syntax change --- jenkins/helper/test_launch_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index fd3067c4..be3da10b 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -450,7 +450,10 @@ def read_definitions(filename, override_branch, args): try: suite = testcase[suite_name] if "suites" in suite: - if 'options' in suite and 'bucket' in suite['options']: + if ('options' in suite and + 'buckets' in suite['options'] and + suite['options']['buckets'] == "auto"): + del suite['options']['buckets'] tests += read_yaml_bucket_suite(suite_name, suite, testfile_definitions, args) else: tests.append(read_yaml_multi_suite(suite_name, suite, testfile_definitions, args)) From 2617dc77b2dfd9ee22e6c2aed8d0b4890a8d38ca Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Mon, 20 Oct 2025 14:39:54 +0200 Subject: [PATCH 19/19] simplify --- jenkins/helper/test_launch_controller.py | 50 +++++++++++------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/jenkins/helper/test_launch_controller.py b/jenkins/helper/test_launch_controller.py index be3da10b..b2846db8 100644 --- a/jenkins/helper/test_launch_controller.py +++ b/jenkins/helper/test_launch_controller.py @@ -352,36 +352,32 @@ def read_yaml_multi_suite(name, definition, testfile_definitions, cli_args): """ convert yaml representation into the internal one """ generated_definition = { } + args = {} if 'options' in definition: generated_definition['options'] = definition['options'] - args = {} - generated_name = "" if 'args' in definition: args = definition['args'].copy() - if isinstance(definition['suites'][0], str): - generated_name = ','.join(definition['suites']) - else: - suite_strs = [] - options_json = [] - for suite in definition['suites']: - if isinstance(suite, str): - options_json.append({}) - suite_name = suite + suite_strs = [] + options_json = [] + for suite in definition['suites']: + if isinstance(suite, str): + options_json.append({}) + suite_name = suite + else: + suite_name = list(suite.keys())[0] + if not isinstance(suite, dict): + raise Exception(f"suite should be a dict, it is {type(suite)}") + if 'options' in suite[suite_name]: + if filter_one_test(cli_args, suite[suite_name]['options']): + print(f"skipping {suite}") + continue + if 'args' in suite[suite_name]: + options_json.append(get_args(suite[suite_name]['args'])) else: - suite_name = list(suite.keys())[0] - if not isinstance(suite, dict): - raise Exception(f"suite should be a dict, it is {type(suite)}") - if 'options' in suite[suite_name]: - if filter_one_test(cli_args, suite[suite_name]['options']): - print(f"skipping {suite}") - continue - if 'args' in suite[suite_name]: - options_json.append(get_args(suite[suite_name]['args'])) - else: - options_json.append({}) - suite_strs.append(suite_name) - generated_name = ','.join(suite_strs) - args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) + options_json.append({}) + suite_strs.append(suite_name) + generated_name = ','.join(suite_strs) + args['optionsJson'] = json.dumps(options_json, separators=(',', ':')) if args != {}: generated_definition['args'] = args return read_yaml_suite(name, generated_name, generated_definition, testfile_definitions) @@ -390,11 +386,11 @@ def read_yaml_bucket_suite(bucket_name, definition, testfile_definitions, cli_ar """ convert yaml representation into the internal one """ ret = [] args = {} - if 'args' in definition: - args = definition['args'] options = [] if 'options' in definition: options = definition['options'] + if 'args' in definition: + args = definition['args'] for suite in definition['suites']: if isinstance(suite, str): ret.append(