From 395bab657a650a5f46fbbb465a50566f2f70226e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 19 Dec 2022 19:09:12 +0100 Subject: [PATCH 1/7] Yoloimplement support for appsv2 --- lib/default_install_args.py | 54 +++++++++++++++++++++++++++++++++++ lib/manifest_parsing.py | 56 ------------------------------------- lib/tests.sh | 42 +++++++++++++++++++++------- lib/tests_coordination.sh | 14 +++++++--- 4 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 lib/default_install_args.py delete mode 100644 lib/manifest_parsing.py diff --git a/lib/default_install_args.py b/lib/default_install_args.py new file mode 100644 index 0000000..2c1e89b --- /dev/null +++ b/lib/default_install_args.py @@ -0,0 +1,54 @@ +#!/usr/bin/python3 + +import sys +import json +import toml + +def get_default_values_for_questions(manifest): + + base_default_value_per_arg_type = { + ("domain", "domain"): "domain.tld", + ("path", "path"): "/" + manifest["id"], + ("user", "admin"): "package_checker", + ("group", "init_main_permission"): "visitors", + ("group", "init_admin_permission"): "admins", + ("password", "password"): "MySuperComplexPassword" + } + + if manifest.get("packaging_format", 1) <= 1: + questions = {q["name"]:q for q in manifest["arguments"]["install"]} + else: + questions = manifest["install"] + + for name, question in questions.items(): + type_and_name = (question["type"], name) + base_default = base_default_value_per_arg_type.get(type_and_name) + if base_default: + yield (name, base_default) + elif question.get("default"): + if isinstance(question.get("default"), bool): + yield (name, str(int(question.get("default")))) + else: + yield (name, str(question.get("default"))) + elif question["type"] == "boolean": + yield (name, "1") + elif question.get("choices"): + if isinstance(question["choices"]): + choices = str(question["choices"]) + else: + choices = list(question["choices"].keys()) + yield (name, question["choices"][0]) + else: + raise Exception("No default value could be computed for arg " + name) + + +if __name__ == '__main__': + manifest_path = sys.argv[1:][0] + + if manifest_path.endswith(".json"): + manifest = json.load(open(manifest_path, "r")) + else: + manifest = toml.load(open(manifest_path, "r")) + + querystring = '&'.join([k + "=" + v for k, v in get_default_values_for_questions(manifest)]) + print(querystring) diff --git a/lib/manifest_parsing.py b/lib/manifest_parsing.py deleted file mode 100644 index 079e188..0000000 --- a/lib/manifest_parsing.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/python3 - -import sys -import json - -def argument_for_question(question, all_choices=False): - question_type = question.get("type") - - if question_type is None and question.get("choices"): - question_type = "boolean" - elif question_type in [None, "string"] and question.get("default"): - question_type = "with_default" - elif question_type is None and question["name"] == "admin": - question_type = "user" - elif question_type is None and question["name"] == "domain": - question_type = "domain" - - if question_type == "domain": - return (question["name"], "ynh.local") - elif question_type == "path": - if all_choices: - return (question["name"], question["default"], "/") - else: - return (question["name"], question["default"]) - elif question_type == "with_default": - return (question["name"], question["default"]) - elif question_type == "boolean": - if not all_choices: - if isinstance(question["default"], bool): - if question["default"]: - question["default"] = "1" - else: - question["default"] = "0" - - return (question["name"], question["default"]) - else: - if isinstance(question["default"], bool) : - return (question["name"], "1", "0") - - if question.get("choices"): - return (question["name"],) + tuple(question["choices"]) - - return (question["name"], question["default"]) - elif question_type == "password": - return (question["name"], "ynh") - elif question_type == "user": - return (question["name"], "johndoe") - else: - raise Exception("Unknow question type: %s\n" % question_type, question) - -if __name__ == '__main__': - manifest_path = sys.argv[1:][0] - manifest = json.load(open(manifest_path, "r")) - - for question in manifest["arguments"]["install"]: - print(":".join(argument_for_question(question, all_choices=True))) diff --git a/lib/tests.sh b/lib/tests.sh index 08a36c7..eecb737 100644 --- a/lib/tests.sh +++ b/lib/tests.sh @@ -76,13 +76,14 @@ _INSTALL_APP () { # We have default values for domain, admin and is_public, but these # may still be overwritten by the args ($@) - for arg_override in "domain=$SUBDOMAIN" "admin=$TEST_USER" "is_public=1" "$@" + for arg_override in "domain=$SUBDOMAIN" "admin=$TEST_USER" "is_public=1" "init_main_permission=visitors" "$@" do key="$(echo $arg_override | cut -d '=' -f 1)" value="$(echo $arg_override | cut -d '=' -f 2-)" # (Legacy stuff ... We don't override is_public if its type is not boolean) - [[ "$key" == "is_public" ]] \ + [[ -e $package_path/manifest.json ]] \ + && [[ "$key" == "is_public" ]] \ && [[ "$(jq -r '.arguments.install[] | select(.name=="is_public") | .type' $package_path/manifest.json)" != "boolean" ]] \ && continue @@ -90,14 +91,27 @@ _INSTALL_APP () { done # Note : we do this at this stage and not during the parsing of check_process - # because this also applies to upgrades ... - # For all manifest arg - for ARG in $(jq -r '.arguments.install[].name' $package_path/manifest.json) + # because this also applies to upgrades ... ie older version may have different args and default values + + # Fetch and loop over all manifest arg + if [[ -e $package_path/manifest.json ]] + then + local manifest_args="$(jq -r '.arguments.install[].name' $package_path/manifest.json)" + else + local manifest_args="$(grep '^\s*\[install\.' $package_path/manifest.toml | tr -d '[]' | awk -F. '{print $2}')" + fi + + for ARG in $manifest_args do # If the argument is not yet in install args, add its default value if ! echo "$install_args" | grep -q -E "\<$ARG=" then - local default_value=$(jq -e -r --arg ARG $ARG '.arguments.install[] | select(.name==$ARG) | .default' $package_path/manifest.json) + if [[ -e $package_path/manifest.json ]] + then + local default_value=$(jq -e -r --arg ARG $ARG '.arguments.install[] | select(.name==$ARG) | .default' $package_path/manifest.json) + else + local default_value=$(python3 -c "import toml, sys; t = toml.loads(sys.stdin.read()); d = t['install']['$ARG'].get('default'); assert d is not None, 'Missing default value'; print(d)" < manifest.toml) + fi [[ $? -eq 0 ]] || { log_error "Missing install arg $ARG ?"; return 1; } [[ ${install_args: -1} == '&' ]] || install_args+="&" install_args+="$ARG=$default_value" @@ -177,7 +191,14 @@ _VALIDATE_THAT_APP_CAN_BE_ACCESSED () { # private by default For "regular" apps (with a is_public arg) they are # installed as public, and we precisely want to check they are publicly # accessible *without* tweaking skipped_uris... - if [ "$install_type" != 'private' ] && [[ -z "$(jq -r '.arguments.install[] | select(.name=="is_public")' $package_path/manifest.json)" ]] + if [[ -e $package_path/manifest.json ]] + then + local has_public_arg=$([[ -n "$(jq -r '.arguments.install[] | select(.name=="is_public")' $package_path/manifest.json)" ]] && echo true || echo false) + else + local has_public_arg=$(grep -q '\[install.init_main_permission\]' manifest.toml && echo true || echo false) + fi + + if [ "$install_type" != 'private' ] && [[ $has_public_arg == "false" ]] then log_debug "Forcing public access using a skipped_uris setting" # Add a skipped_uris on / for the app @@ -350,10 +371,11 @@ TEST_INSTALL () { local check_path="/" local is_public="1" + local init_main_permission="visitors" [ "$install_type" = "subdir" ] && { start_test "Installation in a sub path"; local check_path=/path; } [ "$install_type" = "root" ] && { start_test "Installation on the root"; } [ "$install_type" = "nourl" ] && { start_test "Installation without URL access"; local check_path=""; } - [ "$install_type" = "private" ] && { start_test "Installation in private mode"; local is_public="0"; } + [ "$install_type" = "private" ] && { start_test "Installation in private mode"; local is_public="0"; local init_main_permission="all_users"; } local snapname=snap_${install_type}install LOAD_LXC_SNAPSHOT snap0 @@ -361,7 +383,7 @@ TEST_INSTALL () { _PREINSTALL # Install the application in a LXC container - _INSTALL_APP "path=$check_path" "is_public=$is_public" \ + _INSTALL_APP "path=$check_path" "is_public=$is_public" "init_main_permission=$init_main_permission" \ && _VALIDATE_THAT_APP_CAN_BE_ACCESSED "$SUBDOMAIN" "$check_path" "$install_type" \ local install=$? @@ -377,7 +399,7 @@ TEST_INSTALL () { # Remove and reinstall the application _REMOVE_APP \ && log_small_title "Reinstalling after removal." \ - && _INSTALL_APP "path=$check_path" "is_public=$is_public" \ + && _INSTALL_APP "path=$check_path" "is_public=$is_public" "init_main_permission=$init_main_permission" \ && _VALIDATE_THAT_APP_CAN_BE_ACCESSED "$SUBDOMAIN" "$check_path" "$install_type" return $? diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index 1548a75..6c4271e 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -87,6 +87,7 @@ parse_check_process() { echo "$install_args" | tr '&' '\n' | grep -q "^path=" ||install_args+="path=&" echo "$install_args" | tr '&' '\n' | grep -q "^admin=" ||install_args+="admin=&" echo "$install_args" | tr '&' '\n' | grep -q "^is_public=" ||install_args+="is_public=&" + echo "$install_args" | tr '&' '\n' | grep -q "^init_main_permission=" ||install_args+="init_main_permission=&" extract_check_process_section "^; Checks" "^; " $test_serie_rawconf > $TEST_CONTEXT/check_process.tests_infos @@ -193,16 +194,16 @@ guess_test_configuration() { > "$TEST_CONTEXT/tests/$test_id.json" } - local install_args=$(python3 "./lib/manifest_parsing.py" "$package_path/manifest.json" | cut -d ':' -f1,2 | tr ':' '=' | tr '\n' '&') + local install_args=$(python3 "./lib/default_install_args.py" "$package_path"/manifest.*) add_test "PACKAGE_LINTER" add_test "TEST_INSTALL" "root" add_test "TEST_INSTALL" "subdir" - if echo $install_args | grep -q "is_public=" + if echo $install_args | grep -q "is_public=\|init_main_permission=" then add_test "TEST_INSTALL" "private" fi - if grep multi_instance "$package_path/manifest.json" | grep -q true + if grep multi_instance "$package_path"/manifest.* | grep -q true then add_test "TEST_INSTALL" "multi" fi @@ -220,7 +221,12 @@ run_all_tests() { mkdir -p $TEST_CONTEXT/results mkdir -p $TEST_CONTEXT/logs - readonly app_id="$(jq -r .id $package_path/manifest.json)" + if [ -e $package_path/manifest.json ] + then + readonly app_id="$(jq -r .id $package_path/manifest.json)" + else + readonly app_id="$(grep '^id = ' $package_path/manifest.toml | tr -d '" ' | awk -F= '{print $2}')" + fi # Parse the check_process only if it's exist check_process="$package_path/check_process" From 5e821aee236b1ba0d272f62be9b805777b1ab162 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Wed, 11 Jan 2023 09:55:39 +0100 Subject: [PATCH 2/7] Fixes /o\ --- lib/tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tests.sh b/lib/tests.sh index eecb737..2ed58fe 100644 --- a/lib/tests.sh +++ b/lib/tests.sh @@ -82,7 +82,7 @@ _INSTALL_APP () { value="$(echo $arg_override | cut -d '=' -f 2-)" # (Legacy stuff ... We don't override is_public if its type is not boolean) - [[ -e $package_path/manifest.json ]] \ + [[ -e $package_path/manifest.json ]] \ && [[ "$key" == "is_public" ]] \ && [[ "$(jq -r '.arguments.install[] | select(.name=="is_public") | .type' $package_path/manifest.json)" != "boolean" ]] \ && continue @@ -110,7 +110,7 @@ _INSTALL_APP () { then local default_value=$(jq -e -r --arg ARG $ARG '.arguments.install[] | select(.name==$ARG) | .default' $package_path/manifest.json) else - local default_value=$(python3 -c "import toml, sys; t = toml.loads(sys.stdin.read()); d = t['install']['$ARG'].get('default'); assert d is not None, 'Missing default value'; print(d)" < manifest.toml) + local default_value=$(python3 -c "import toml, sys; t = toml.loads(sys.stdin.read()); d = t['install']['$ARG'].get('default'); assert d is not None, 'Missing default value'; print(d)" < $package_path/manifest.toml) fi [[ $? -eq 0 ]] || { log_error "Missing install arg $ARG ?"; return 1; } [[ ${install_args: -1} == '&' ]] || install_args+="&" @@ -195,7 +195,7 @@ _VALIDATE_THAT_APP_CAN_BE_ACCESSED () { then local has_public_arg=$([[ -n "$(jq -r '.arguments.install[] | select(.name=="is_public")' $package_path/manifest.json)" ]] && echo true || echo false) else - local has_public_arg=$(grep -q '\[install.init_main_permission\]' manifest.toml && echo true || echo false) + local has_public_arg=$(grep -q '\[install.init_main_permission\]' $package_path/manifest.toml && echo true || echo false) fi if [ "$install_type" != 'private' ] && [[ $has_public_arg == "false" ]] From fe2e0048adae4987ab9a64aacfa1644ee62d0a0e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Jan 2023 20:38:56 +0100 Subject: [PATCH 3/7] Introduce a parse_tests_toml.py to interface with a modern replacement for horrendous check_process files --- lib/analyze_test_results.py | 18 ++-- lib/default_install_args.py | 1 - lib/parse_tests_toml.py | 163 ++++++++++++++++++++++++++++++++++++ lib/tests.sh | 2 +- lib/tests_coordination.sh | 4 +- 5 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 lib/parse_tests_toml.py diff --git a/lib/analyze_test_results.py b/lib/analyze_test_results.py index 0da8c34..cc06dd7 100644 --- a/lib/analyze_test_results.py +++ b/lib/analyze_test_results.py @@ -25,10 +25,10 @@ def test_notes(test): if test["test_type"] == "TEST_UPGRADE" and test["test_arg"]: return - if test["test_type"] == "PACKAGE_LINTER" and test['results']['main_result'] == 'success' and test['results'].get("warning"): + if test["test_type"] == "TEST_PACKAGE_LINTER" and test['results']['main_result'] == 'success' and test['results'].get("warning"): yield '%s warnings' % len(test['results'].get("warning")) - if test["test_type"] == "PACKAGE_LINTER" and test['results']['main_result'] == 'success' and test['results'].get("info"): + if test["test_type"] == "TEST_PACKAGE_LINTER" and test['results']['main_result'] == 'success' and test['results'].get("info"): yield '%s possible improvements' % len(set(test['results'].get("info"))) if test['results'].get("witness"): @@ -71,7 +71,7 @@ def level_1(tests): And there are no critical issues in the linter """ - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] install_tests = [t for t in tests if t["test_type"] == "TEST_INSTALL"] witness_missing_detected = any(t["results"].get("witness") for t in tests) @@ -126,7 +126,7 @@ def level_5(tests): """ alias_traversal_detected = any(t["results"].get("alias_traversal") for t in tests) - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] return not alias_traversal_detected \ and linter_tests != [] \ @@ -140,7 +140,7 @@ def level_6(tests): (the linter will report a warning named "is_in_github_org" if it's not) """ - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] return linter_tests != [] \ and "is_in_github_org" not in linter_tests[0]["results"]["warning"] @@ -153,7 +153,7 @@ def level_7(tests): linter which will report a "qualify_for_level_7" in successes) """ - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] # For runtime warnings, ignore stuff happening during upgrades from previous versions tests_on_which_to_check_for_runtime_warnings = [t for t in tests if not (t["test_type"] == "TEST_UPGRADE" and t["test_arg"])] @@ -178,7 +178,7 @@ def level_8(tests): which will report a "qualify_for_level_8") """ - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] return linter_tests != [] \ and "App.qualify_for_level_8" in linter_tests[0]["results"]["success"] @@ -190,7 +190,7 @@ def level_9(tests): App is flagged high-quality in the app catalog (this is tested by the linter which will rpeort a "qualify_for_level_9") """ - linter_tests = [t for t in tests if t["test_type"] == "PACKAGE_LINTER"] + linter_tests = [t for t in tests if t["test_type"] == "TEST_PACKAGE_LINTER"] return linter_tests != [] \ and "App.qualify_for_level_9" in linter_tests[0]["results"]["success"] @@ -199,7 +199,7 @@ def level_9(tests): def make_summary(): test_types = { - "PACKAGE_LINTER": "Package linter", + "TEST_PACKAGE_LINTER": "Package linter", "TEST_INSTALL": "Install", "TEST_UPGRADE": "Upgrade", "TEST_BACKUP_RESTORE": "Backup/restore", diff --git a/lib/default_install_args.py b/lib/default_install_args.py index 2c1e89b..8d6f1e6 100644 --- a/lib/default_install_args.py +++ b/lib/default_install_args.py @@ -41,7 +41,6 @@ def get_default_values_for_questions(manifest): else: raise Exception("No default value could be computed for arg " + name) - if __name__ == '__main__': manifest_path = sys.argv[1:][0] diff --git a/lib/parse_tests_toml.py b/lib/parse_tests_toml.py new file mode 100644 index 0000000..88e550b --- /dev/null +++ b/lib/parse_tests_toml.py @@ -0,0 +1,163 @@ +import sys +import os +import toml +import copy +import json + + +def generate_test_list_base(test_manifest, default_install_args, is_webapp, is_multi_instance): + + assert test_manifest["test_format"] == 1.0, "Only test_format 1.0 is supported for now" + + assert isinstance(test_manifest["default"], dict), "You should at least defined the 'default' test suite" + + is_full_domain_app = "domain" in default_install_args and "path" not in default_install_args + + for test_suite_id, test_suite in test_manifest.items(): + + # Ignore non-testsuite stuff like "test_format" + if not isinstance(test_suite, dict): + continue + + install_args = copy.copy(default_install_args) + install_args.update(test_suite.get("args")) + + default_meta = { + "preinstall_template": test_suite.get("preinstall_template", ""), + "preupgrade_template": test_suite.get("preupgrade_template", ""), + "install_args": install_args, + } + + yield test_suite_id, "package_linter", default_meta + + if is_webapp: + yield test_suite_id, "install.root", default_meta + if not is_full_domain_app: + yield test_suite_id, "install.subdir", default_meta + else: + yield test_suite_id, "install.nourl", default_meta + + if is_webapp and ("is_public" in install_args or "init_main_permission" in install_args): + yield test_suite_id, "install.private", default_meta + + if is_multi_instance: + yield test_suite_id, "install.multi", default_meta + + yield test_suite_id, "backup_restore", default_meta + + yield test_suite_id, "upgrade", default_meta + for commit, infos in test_suite.get("test_upgrade_from", {}).items(): + upgrade_meta = copy.copy(default_meta) + upgrade_meta.update(infos) + yield test_suite_id, "upgrade." + commit, upgrade_meta + + if is_webapp: + yield test_suite_id, "change_url", default_meta + + +def filter_test_list(test_manifest, base_test_list): + + for test_suite_id, test_suite in test_manifest.items(): + + # Ignore non-testsuite stuff like "test_format" + if not isinstance(test_suite, dict): + continue + + exclude = test_suite.get("exclude", []) + only = test_suite.get("only") + + if test_suite_id == "default" and only: + raise Exception("'only' is not allowed on the default test suite") + + if only: + tests_for_this_suite = {test_id: meta + for suite_id, test_id, meta in base_test_list + if suite_id == test_suite_id and test_id in only} + elif exclude: + tests_for_this_suite = {test_id: meta + for suite_id, test_id, meta in base_test_list + if suite_id == test_suite_id and test_id not in exclude} + else: + tests_for_this_suite = {test_id: meta + for suite_id, test_id, meta in base_test_list + if suite_id == test_suite_id} + + yield test_suite_id, tests_for_this_suite + + +def dump_for_package_check(test_list, package_check_tests_dir): + + test_suite_i = 0 + + for test_suite_id, subtest_list in test_list.items(): + + test_suite_i += 1 + + subtest_i = 0 + + for test, meta in subtest_list.items(): + + meta = copy.copy(meta) + + subtest_i += 1 + + if "." in test: + test_type, test_arg = test.split(".") + else: + test_type = test + test_arg = "" + + J = { + "test_serie": test_suite_id, + "test_type": "TEST_" + test_type.upper(), + "test_arg": test_arg, + "preinstall_template": meta.pop("preinstall_template", ""), + "preupgrade_template": meta.pop("preupgrade_template", ""), + "install_args": '&'.join([k + "=" + str(v) for k, v in meta.pop("install_args").items()]), + "extra": meta # Boring logic just to ship the upgrade-from-commit's name ... + } + + test_file_id = test_suite_i * 100 + subtest_i + + json.dump(J, open(package_check_tests_dir + f"/{test_file_id}.json", "w")) + + +def build_test_list(basedir): + + test_manifest = toml.load(open(basedir + "/tests.toml", "r")) + + if os.path.exists(basedir + "/manifest.json"): + manifest = json.load(open(basedir + "/manifest.json", "r")) + is_multi_instance = manifest.get("multi_instance") is True + else: + manifest = toml.load(open(basedir + "/manifest.toml", "r")) + is_multi_instance = manifest.get("integration").get("multi_instance") is True + + is_webapp = os.system(f"grep -q '^ynh_add_nginx_config' '{basedir}/scripts/install'") == 0 + + from default_install_args import get_default_values_for_questions + default_install_args = {k: v for k, v in get_default_values_for_questions(manifest)} + + base_test_list = list(generate_test_list_base(test_manifest, default_install_args, is_webapp, is_multi_instance)) + test_list = {test_suite_id: tests for test_suite_id, tests in filter_test_list(test_manifest, base_test_list)} + + return test_list + +if __name__ == '__main__': + + test_list = build_test_list(sys.argv[1]) + + if len(sys.argv) <= 1 or sys.argv[1] in ["-h", "--help"]: + print("""Usage: + +Display generated test list: + python3 parse_tests_toml.py /path/to/app/folder/ | jq + +Dump the list (only relevant from inside package_checker's code ...) + python3 parse_tests_toml.py /path/to/app/folder/ /tmp/dir/for/package/check/.... + +""") + elif len(sys.argv) == 2: + print(json.dumps(test_list, indent=4)) + else: + dump_for_package_check(test_list, sys.argv[2]) diff --git a/lib/tests.sh b/lib/tests.sh index 2ed58fe..ecd16a9 100644 --- a/lib/tests.sh +++ b/lib/tests.sh @@ -350,7 +350,7 @@ Page extract:\n$page_extract" > $TEST_CONTEXT/curl_result # Tests #================================================= -PACKAGE_LINTER () { +TEST_PACKAGE_LINTER () { start_test "Package linter" diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index 6c4271e..c216b60 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -133,7 +133,7 @@ parse_check_process() { test_serie=${tests_serie//;; } - is_test_enabled pkg_linter && add_test "PACKAGE_LINTER" + is_test_enabled pkg_linter && add_test "TEST_PACKAGE_LINTER" is_test_enabled setup_root && add_test "TEST_INSTALL" "root" is_test_enabled setup_sub_dir && add_test "TEST_INSTALL" "subdir" is_test_enabled setup_nourl && add_test "TEST_INSTALL" "nourl" @@ -196,7 +196,7 @@ guess_test_configuration() { local install_args=$(python3 "./lib/default_install_args.py" "$package_path"/manifest.*) - add_test "PACKAGE_LINTER" + add_test "TEST_PACKAGE_LINTER" add_test "TEST_INSTALL" "root" add_test "TEST_INSTALL" "subdir" if echo $install_args | grep -q "is_public=\|init_main_permission=" From 1c0cda8d085765805b6a77063f4a63a4e0af252e Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Jan 2023 22:15:55 +0100 Subject: [PATCH 4/7] Parse tests.toml if it exists --- lib/tests_coordination.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index c216b60..864fccf 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -228,12 +228,18 @@ run_all_tests() { readonly app_id="$(grep '^id = ' $package_path/manifest.toml | tr -d '" ' | awk -F= '{print $2}')" fi - # Parse the check_process only if it's exist - check_process="$package_path/check_process" + tests_toml="$package_path/tests.toml" + if [ -e "$tests_toml"] + then + python3 "./lib/parse_tests_toml.py" "$package_path" "$TEST_CONTEXT" + else + # Parse the check_process only if it's exist + check_process="$package_path/check_process" - [ -e "$check_process" ] \ - && parse_check_process \ - || guess_test_configuration + [ -e "$check_process" ] \ + && parse_check_process \ + || guess_test_configuration + fi # Start the timer for this test start_timer From 4aee9832dbb72fe1fc15afe274575d045cde86ee Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Mon, 16 Jan 2023 22:18:13 +0100 Subject: [PATCH 5/7] Split the horendous check_process parsing in legacy.sh --- lib/legacy.sh | 198 +++++++++++++++++++++++++++++++++++++ lib/tests_coordination.sh | 200 +------------------------------------- 2 files changed, 199 insertions(+), 199 deletions(-) create mode 100644 lib/legacy.sh diff --git a/lib/legacy.sh b/lib/legacy.sh new file mode 100644 index 0000000..c13d063 --- /dev/null +++ b/lib/legacy.sh @@ -0,0 +1,198 @@ +#======================================================================= +# Parse the check_process and generate jsons that describe tests to run +#======================================================================= + +# Extract a section found between $1 and $2 from the file $3 +extract_check_process_section () { + local source_file="${3:-$check_process}" + local extract=0 + local line="" + while read line + do + # Extract the line + if [ $extract -eq 1 ] + then + # Check if the line is the second line to found + if echo $line | grep -q "$2"; then + # Break the loop to finish the extract process + break; + fi + # Copy the line in the partial check_process + echo "$line" + fi + + # Search for the first line + if echo $line | grep -q "$1"; then + # Activate the extract process + extract=1 + fi + done < "$source_file" +} + + +parse_check_process() { + + log_info "Parsing check_process file" + + # Remove all commented lines in the check_process + sed --in-place '/^#/d' "$check_process" + # Remove all spaces at the beginning of the lines + sed --in-place 's/^[ \t]*//g' "$check_process" + + # Extract the Upgrade infos + extract_check_process_section "^;;; Upgrade options" ";; " > $TEST_CONTEXT/check_process.upgrade_options + mkdir -p $TEST_CONTEXT/upgrades + local commit + for commit in $(cat $TEST_CONTEXT/check_process.upgrade_options | grep "^; commit=.*" | awk -F= '{print $2}') + do + cat $TEST_CONTEXT/check_process.upgrade_options | sed -n -e "/^; commit=$commit/,/^;/ p" | grep -v "^;;" > $TEST_CONTEXT/upgrades/$commit + done + rm $TEST_CONTEXT/check_process.upgrade_options + + local test_serie_id="0" + + # Parse each tests serie + while read <&3 tests_serie + do + test_serie_id=$((test_serie_id+1)) + local test_id=$((test_serie_id * 100)) + local test_serie_rawconf=$TEST_CONTEXT/raw_test_serie_config + + # Extract the section of the current tests serie + extract_check_process_section "^$tests_serie" "^;;" > $test_serie_rawconf + # This is the arg list to be later fed to "yunohost app install" + # Looking like domain=foo.com&path=/bar&password=stuff + # "Standard" arguments like domain/path will later be overwritten + # during tests + local install_args=$( extract_check_process_section "^; Manifest" "^; " $test_serie_rawconf | sed 's/\s*(.*)$//g' | tr -d '"' | tr '\n' '&') + local preinstall_template=$(extract_check_process_section "^; pre-install" "^; " $test_serie_rawconf) + local preupgrade_template=$(extract_check_process_section "^; pre-upgrade" "^; " $test_serie_rawconf) + + # Add (empty) special args if they ain't provided in check_process + echo "$install_args" | tr '&' '\n' | grep -q "^domain=" ||install_args+="domain=&" + echo "$install_args" | tr '&' '\n' | grep -q "^path=" ||install_args+="path=&" + echo "$install_args" | tr '&' '\n' | grep -q "^admin=" ||install_args+="admin=&" + echo "$install_args" | tr '&' '\n' | grep -q "^is_public=" ||install_args+="is_public=&" + echo "$install_args" | tr '&' '\n' | grep -q "^init_main_permission=" ||install_args+="init_main_permission=&" + + extract_check_process_section "^; Checks" "^; " $test_serie_rawconf > $TEST_CONTEXT/check_process.tests_infos + + is_test_enabled () { + # Find the line for the given check option + local value=$(grep -m1 -o "^$1=." "$TEST_CONTEXT/check_process.tests_infos" | awk -F= '{print $2}') + # And return this value + [ "${value:0:1}" = "1" ] + } + + add_test() { + local test_type="$1" + local test_arg="$2" + test_id="$((test_id+1))" + local extra="{}" + local _install_args="$install_args" + + # Upgrades with a specific commit + if [[ "$test_type" == "TEST_UPGRADE" ]] && [[ -n "$test_arg" ]] + then + if [ -f "$TEST_CONTEXT/upgrades/$test_arg" ]; then + local specific_upgrade_install_args="$(grep "^manifest_arg=" "$TEST_CONTEXT/upgrades/$test_arg" | cut -d'=' -f2-)" + [[ -n "$specific_upgrade_install_args" ]] && _install_args="$specific_upgrade_install_args" + + local upgrade_name="$(grep "^name=" "$TEST_CONTEXT/upgrades/$test_arg" | cut -d'=' -f2)" + else + local upgrade_name="$test_arg" + fi + extra="$(jq -n --arg upgrade_name "$upgrade_name" '{ $upgrade_name }')" + fi + + jq -n \ + --arg test_serie "$test_serie" \ + --arg test_type "$test_type" \ + --arg test_arg "$test_arg" \ + --arg preinstall_template "$preinstall_template" \ + --arg preupgrade_template "$preupgrade_template" \ + --arg install_args "${_install_args//\"}" \ + --argjson extra "$extra" \ + '{ $test_serie, $test_type, $test_arg, $preinstall_template, $preupgrade_template, $install_args, $extra }' \ + > "$TEST_CONTEXT/tests/$test_id.json" + } + + test_serie=${tests_serie//;; } + + is_test_enabled pkg_linter && add_test "TEST_PACKAGE_LINTER" + is_test_enabled setup_root && add_test "TEST_INSTALL" "root" + is_test_enabled setup_sub_dir && add_test "TEST_INSTALL" "subdir" + is_test_enabled setup_nourl && add_test "TEST_INSTALL" "nourl" + is_test_enabled setup_private && add_test "TEST_INSTALL" "private" + is_test_enabled multi_instance && add_test "TEST_INSTALL" "multi" + is_test_enabled backup_restore && add_test "TEST_BACKUP_RESTORE" + + # Upgrades + while IFS= read -r LINE; + do + commit="$(echo $LINE | grep -o "from_commit=.*" | awk -F= '{print $2}')" + add_test "TEST_UPGRADE" "$commit" + done < <(grep "^upgrade=1" "$TEST_CONTEXT/check_process.tests_infos") + + # "Advanced" features + + is_test_enabled change_url && add_test "TEST_CHANGE_URL" + + # Port already used ... do we really need this ... + + if grep -q -m1 "port_already_use=1" "$TEST_CONTEXT/check_process.tests_infos" + then + local check_port=$(grep -m1 "port_already_use=1" "$TEST_CONTEXT/check_process.tests_infos" | grep -o -E "\([0-9]+\)" | tr -d '()') + else + local check_port=6660 + fi + + is_test_enabled port_already_use && add_test "TEST_PORT_ALREADY_USED" "$check_port" + + done 3<<< "$(grep "^;; " "$check_process")" + + return 0 +} + +guess_test_configuration() { + + log_error "No tests.toml file found." + log_warning "Package check will attempt to automatically guess what tests to run." + + local test_id=100 + + add_test() { + local test_type="$1" + local test_arg="$2" + test_id="$((test_id+1))" + local extra="{}" + local preupgrade_template="" + + jq -n \ + --arg test_serie "default" \ + --arg test_type "$test_type" \ + --arg test_arg "$test_arg" \ + --arg preinstall_template "" \ + --arg preupgrade_template "$preupgrade_template" \ + --arg install_args "$install_args" \ + --argjson extra "$extra" \ + '{ $test_serie, $test_type, $test_arg, $preinstall_template, $preupgrade_template, $install_args, $extra }' \ + > "$TEST_CONTEXT/tests/$test_id.json" + } + + local install_args=$(python3 "./lib/default_install_args.py" "$package_path"/manifest.*) + + add_test "TEST_PACKAGE_LINTER" + add_test "TEST_INSTALL" "root" + add_test "TEST_INSTALL" "subdir" + if echo $install_args | grep -q "is_public=\|init_main_permission=" + then + add_test "TEST_INSTALL" "private" + fi + if grep multi_instance "$package_path"/manifest.* | grep -q true + then + add_test "TEST_INSTALL" "multi" + fi + add_test "TEST_BACKUP_RESTORE" + add_test "TEST_UPGRADE" +} diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index 864fccf..780e8c0 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -3,6 +3,7 @@ source lib/lxc.sh source lib/tests.sh source lib/witness.sh +source lib/legacy.sh readonly complete_log="./Complete-${WORKER_ID}.log" @@ -12,205 +13,6 @@ rm -f "$complete_log" && touch "$complete_log" # Redirect fd 3 (=debug steam) to complete log exec 3>>$complete_log -#======================================================================= -# Parse the check_process and generate jsons that describe tests to run -#======================================================================= - -# Extract a section found between $1 and $2 from the file $3 -extract_check_process_section () { - local source_file="${3:-$check_process}" - local extract=0 - local line="" - while read line - do - # Extract the line - if [ $extract -eq 1 ] - then - # Check if the line is the second line to found - if echo $line | grep -q "$2"; then - # Break the loop to finish the extract process - break; - fi - # Copy the line in the partial check_process - echo "$line" - fi - - # Search for the first line - if echo $line | grep -q "$1"; then - # Activate the extract process - extract=1 - fi - done < "$source_file" -} - - -parse_check_process() { - - log_info "Parsing check_process file" - - # Remove all commented lines in the check_process - sed --in-place '/^#/d' "$check_process" - # Remove all spaces at the beginning of the lines - sed --in-place 's/^[ \t]*//g' "$check_process" - - # Extract the Upgrade infos - extract_check_process_section "^;;; Upgrade options" ";; " > $TEST_CONTEXT/check_process.upgrade_options - mkdir -p $TEST_CONTEXT/upgrades - local commit - for commit in $(cat $TEST_CONTEXT/check_process.upgrade_options | grep "^; commit=.*" | awk -F= '{print $2}') - do - cat $TEST_CONTEXT/check_process.upgrade_options | sed -n -e "/^; commit=$commit/,/^;/ p" | grep -v "^;;" > $TEST_CONTEXT/upgrades/$commit - done - rm $TEST_CONTEXT/check_process.upgrade_options - - local test_serie_id="0" - - # Parse each tests serie - while read <&3 tests_serie - do - test_serie_id=$((test_serie_id+1)) - local test_id=$((test_serie_id * 100)) - local test_serie_rawconf=$TEST_CONTEXT/raw_test_serie_config - - # Extract the section of the current tests serie - extract_check_process_section "^$tests_serie" "^;;" > $test_serie_rawconf - # This is the arg list to be later fed to "yunohost app install" - # Looking like domain=foo.com&path=/bar&password=stuff - # "Standard" arguments like domain/path will later be overwritten - # during tests - local install_args=$( extract_check_process_section "^; Manifest" "^; " $test_serie_rawconf | sed 's/\s*(.*)$//g' | tr -d '"' | tr '\n' '&') - local preinstall_template=$(extract_check_process_section "^; pre-install" "^; " $test_serie_rawconf) - local preupgrade_template=$(extract_check_process_section "^; pre-upgrade" "^; " $test_serie_rawconf) - - # Add (empty) special args if they ain't provided in check_process - echo "$install_args" | tr '&' '\n' | grep -q "^domain=" ||install_args+="domain=&" - echo "$install_args" | tr '&' '\n' | grep -q "^path=" ||install_args+="path=&" - echo "$install_args" | tr '&' '\n' | grep -q "^admin=" ||install_args+="admin=&" - echo "$install_args" | tr '&' '\n' | grep -q "^is_public=" ||install_args+="is_public=&" - echo "$install_args" | tr '&' '\n' | grep -q "^init_main_permission=" ||install_args+="init_main_permission=&" - - extract_check_process_section "^; Checks" "^; " $test_serie_rawconf > $TEST_CONTEXT/check_process.tests_infos - - is_test_enabled () { - # Find the line for the given check option - local value=$(grep -m1 -o "^$1=." "$TEST_CONTEXT/check_process.tests_infos" | awk -F= '{print $2}') - # And return this value - [ "${value:0:1}" = "1" ] - } - - add_test() { - local test_type="$1" - local test_arg="$2" - test_id="$((test_id+1))" - local extra="{}" - local _install_args="$install_args" - - # Upgrades with a specific commit - if [[ "$test_type" == "TEST_UPGRADE" ]] && [[ -n "$test_arg" ]] - then - if [ -f "$TEST_CONTEXT/upgrades/$test_arg" ]; then - local specific_upgrade_install_args="$(grep "^manifest_arg=" "$TEST_CONTEXT/upgrades/$test_arg" | cut -d'=' -f2-)" - [[ -n "$specific_upgrade_install_args" ]] && _install_args="$specific_upgrade_install_args" - - local upgrade_name="$(grep "^name=" "$TEST_CONTEXT/upgrades/$test_arg" | cut -d'=' -f2)" - else - local upgrade_name="$test_arg" - fi - extra="$(jq -n --arg upgrade_name "$upgrade_name" '{ $upgrade_name }')" - fi - - jq -n \ - --arg test_serie "$test_serie" \ - --arg test_type "$test_type" \ - --arg test_arg "$test_arg" \ - --arg preinstall_template "$preinstall_template" \ - --arg preupgrade_template "$preupgrade_template" \ - --arg install_args "${_install_args//\"}" \ - --argjson extra "$extra" \ - '{ $test_serie, $test_type, $test_arg, $preinstall_template, $preupgrade_template, $install_args, $extra }' \ - > "$TEST_CONTEXT/tests/$test_id.json" - } - - test_serie=${tests_serie//;; } - - is_test_enabled pkg_linter && add_test "TEST_PACKAGE_LINTER" - is_test_enabled setup_root && add_test "TEST_INSTALL" "root" - is_test_enabled setup_sub_dir && add_test "TEST_INSTALL" "subdir" - is_test_enabled setup_nourl && add_test "TEST_INSTALL" "nourl" - is_test_enabled setup_private && add_test "TEST_INSTALL" "private" - is_test_enabled multi_instance && add_test "TEST_INSTALL" "multi" - is_test_enabled backup_restore && add_test "TEST_BACKUP_RESTORE" - - # Upgrades - while IFS= read -r LINE; - do - commit="$(echo $LINE | grep -o "from_commit=.*" | awk -F= '{print $2}')" - add_test "TEST_UPGRADE" "$commit" - done < <(grep "^upgrade=1" "$TEST_CONTEXT/check_process.tests_infos") - - # "Advanced" features - - is_test_enabled change_url && add_test "TEST_CHANGE_URL" - - # Port already used ... do we really need this ... - - if grep -q -m1 "port_already_use=1" "$TEST_CONTEXT/check_process.tests_infos" - then - local check_port=$(grep -m1 "port_already_use=1" "$TEST_CONTEXT/check_process.tests_infos" | grep -o -E "\([0-9]+\)" | tr -d '()') - else - local check_port=6660 - fi - - is_test_enabled port_already_use && add_test "TEST_PORT_ALREADY_USED" "$check_port" - - done 3<<< "$(grep "^;; " "$check_process")" - - return 0 -} - -guess_test_configuration() { - - log_error "Not check_process file found." - log_warning "Package check will attempt to automatically guess what tests to run." - - local test_id=100 - - add_test() { - local test_type="$1" - local test_arg="$2" - test_id="$((test_id+1))" - local extra="{}" - local preupgrade_template="" - - jq -n \ - --arg test_serie "default" \ - --arg test_type "$test_type" \ - --arg test_arg "$test_arg" \ - --arg preinstall_template "" \ - --arg preupgrade_template "$preupgrade_template" \ - --arg install_args "$install_args" \ - --argjson extra "$extra" \ - '{ $test_serie, $test_type, $test_arg, $preinstall_template, $preupgrade_template, $install_args, $extra }' \ - > "$TEST_CONTEXT/tests/$test_id.json" - } - - local install_args=$(python3 "./lib/default_install_args.py" "$package_path"/manifest.*) - - add_test "TEST_PACKAGE_LINTER" - add_test "TEST_INSTALL" "root" - add_test "TEST_INSTALL" "subdir" - if echo $install_args | grep -q "is_public=\|init_main_permission=" - then - add_test "TEST_INSTALL" "private" - fi - if grep multi_instance "$package_path"/manifest.* | grep -q true - then - add_test "TEST_INSTALL" "multi" - fi - add_test "TEST_BACKUP_RESTORE" - add_test "TEST_UPGRADE" -} - #================================================= # Misc test helpers & coordination #================================================= From 1a204c659383a76d2f9d4fb15b66ec4b4d52b753 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 17 Jan 2023 01:40:35 +0100 Subject: [PATCH 6/7] Typo >_> --- lib/tests_coordination.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index 780e8c0..809088c 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -31,7 +31,7 @@ run_all_tests() { fi tests_toml="$package_path/tests.toml" - if [ -e "$tests_toml"] + if [ -e "$tests_toml" ] then python3 "./lib/parse_tests_toml.py" "$package_path" "$TEST_CONTEXT" else From 8cd12dab76a789e3b84fbaddf511cade8caf1629 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Tue, 17 Jan 2023 02:03:35 +0100 Subject: [PATCH 7/7] Fixes from the battlefield --- lib/parse_tests_toml.py | 2 +- lib/tests_coordination.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/parse_tests_toml.py b/lib/parse_tests_toml.py index 88e550b..41280d6 100644 --- a/lib/parse_tests_toml.py +++ b/lib/parse_tests_toml.py @@ -20,7 +20,7 @@ def generate_test_list_base(test_manifest, default_install_args, is_webapp, is_m continue install_args = copy.copy(default_install_args) - install_args.update(test_suite.get("args")) + install_args.update(test_suite.get("args", {})) default_meta = { "preinstall_template": test_suite.get("preinstall_template", ""), diff --git a/lib/tests_coordination.sh b/lib/tests_coordination.sh index 809088c..eaa63a6 100644 --- a/lib/tests_coordination.sh +++ b/lib/tests_coordination.sh @@ -33,7 +33,7 @@ run_all_tests() { tests_toml="$package_path/tests.toml" if [ -e "$tests_toml" ] then - python3 "./lib/parse_tests_toml.py" "$package_path" "$TEST_CONTEXT" + python3 "./lib/parse_tests_toml.py" "$package_path" "$TEST_CONTEXT/tests" else # Parse the check_process only if it's exist check_process="$package_path/check_process"