diff options
Diffstat (limited to 'test')
162 files changed, 2937 insertions, 788 deletions
diff --git a/test/conftest.py b/test/conftest.py index 8ffa3bd..35115b9 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,4 +1,5 @@ """PyTest fixtures for testing the project.""" + from __future__ import annotations import shutil @@ -83,7 +84,7 @@ def regenerate_formatting_fixtures() -> None: # Writing fixtures with ansiblelint.yaml_utils.FormattedYAML() for fixture in fixtures_dir_after.glob("fmt-[0-9].yml"): - data = yaml.loads(fixture.read_text()) + data = yaml.load(fixture.read_text()) output = yaml.dumps(data) fixture.write_text(output) diff --git a/test/fixtures/broken-ansible.cfg/ansible.cfg b/test/fixtures/broken-ansible.cfg/ansible.cfg new file mode 100644 index 0000000..06c1b92 --- /dev/null +++ b/test/fixtures/broken-ansible.cfg/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +fact_caching_timeout=invalid-value diff --git a/test/fixtures/formatting-after/fmt-2.yml b/test/fixtures/formatting-after/fmt-2.yml index a162721..3601acc 100644 --- a/test/fixtures/formatting-after/fmt-2.yml +++ b/test/fixtures/formatting-after/fmt-2.yml @@ -22,3 +22,10 @@ - 10 - 9999 zero: 0 # Not an octal. See #2071 + +- string: + - 0steps + - 9steps + - 0.0.0.0 + - "0" + - "01234" diff --git a/test/fixtures/formatting-after/fmt-4.yml b/test/fixtures/formatting-after/fmt-4.yml new file mode 100644 index 0000000..5ded596 --- /dev/null +++ b/test/fixtures/formatting-after/fmt-4.yml @@ -0,0 +1,22 @@ +--- +- name: Gather all legacy facts + cisco.ios.ios_facts: + +- name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + +- name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + +- name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-after/fmt-5.yml b/test/fixtures/formatting-after/fmt-5.yml new file mode 100644 index 0000000..b259e0e --- /dev/null +++ b/test/fixtures/formatting-after/fmt-5.yml @@ -0,0 +1,25 @@ +--- +- name: Test this playbook + hosts: all + tasks: + - name: Gather all legacy facts + cisco.ios.ios_facts: + + - name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + + - name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + + - name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-after/fmt-hex.yml b/test/fixtures/formatting-after/fmt-hex.yml new file mode 100644 index 0000000..7f09cb9 --- /dev/null +++ b/test/fixtures/formatting-after/fmt-hex.yml @@ -0,0 +1,3 @@ +--- +d: 0x123 # <-- hex +e: 0x0123 diff --git a/test/fixtures/formatting-before/fmt-2.yml b/test/fixtures/formatting-before/fmt-2.yml index 2941663..dbfb777 100644 --- a/test/fixtures/formatting-before/fmt-2.yml +++ b/test/fixtures/formatting-before/fmt-2.yml @@ -22,3 +22,10 @@ - 10 - 9999 zero: 0 # Not an octal. See #2071 + + - string: + - 0steps + - 9steps + - 0.0.0.0 + - "0" + - "01234" diff --git a/test/fixtures/formatting-before/fmt-4.yml b/test/fixtures/formatting-before/fmt-4.yml new file mode 100644 index 0000000..579231f --- /dev/null +++ b/test/fixtures/formatting-before/fmt-4.yml @@ -0,0 +1,25 @@ +--- +- name: Gather all legacy facts + cisco.ios.ios_facts: + +- name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + + +- name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + + + +- name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-before/fmt-5.yml b/test/fixtures/formatting-before/fmt-5.yml new file mode 100644 index 0000000..a9145e6 --- /dev/null +++ b/test/fixtures/formatting-before/fmt-5.yml @@ -0,0 +1,28 @@ +--- +- name: Test this playbook + hosts: all + tasks: + - name: Gather all legacy facts + cisco.ios.ios_facts: + + - name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + + + - name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + + + + - name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-before/fmt-hex.yml b/test/fixtures/formatting-before/fmt-hex.yml new file mode 100644 index 0000000..3bc15a7 --- /dev/null +++ b/test/fixtures/formatting-before/fmt-hex.yml @@ -0,0 +1,3 @@ +--- +d: 0x123 # <-- hex +e: 0x0123 diff --git a/test/fixtures/formatting-prettier/fmt-2.yml b/test/fixtures/formatting-prettier/fmt-2.yml index 90ac484..037c9dd 100644 --- a/test/fixtures/formatting-prettier/fmt-2.yml +++ b/test/fixtures/formatting-prettier/fmt-2.yml @@ -22,3 +22,10 @@ - 10 - 9999 zero: 0 # Not an octal. See #2071 + +- string: + - 0steps + - 9steps + - 0.0.0.0 + - "0" + - "01234" diff --git a/test/fixtures/formatting-prettier/fmt-4.yml b/test/fixtures/formatting-prettier/fmt-4.yml new file mode 100644 index 0000000..5ded596 --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-4.yml @@ -0,0 +1,22 @@ +--- +- name: Gather all legacy facts + cisco.ios.ios_facts: + +- name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + +- name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + +- name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-prettier/fmt-5.yml b/test/fixtures/formatting-prettier/fmt-5.yml new file mode 100644 index 0000000..b259e0e --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-5.yml @@ -0,0 +1,25 @@ +--- +- name: Test this playbook + hosts: all + tasks: + - name: Gather all legacy facts + cisco.ios.ios_facts: + + - name: Update modification and access time of given file + ansible.builtin.file: + path: /etc/some_file + state: file + modification_time: now + access_time: now + + - name: Disable ufw service + ansible.builtin.service: + name: ufw + enabled: false + state: stopped + when: '"ufw" in services' + + - name: Remove file (delete file) + ansible.builtin.file: + path: /etc/foo.txt + state: absent diff --git a/test/fixtures/formatting-prettier/fmt-hex.yml b/test/fixtures/formatting-prettier/fmt-hex.yml new file mode 100644 index 0000000..7f09cb9 --- /dev/null +++ b/test/fixtures/formatting-prettier/fmt-hex.yml @@ -0,0 +1,3 @@ +--- +d: 0x123 # <-- hex +e: 0x0123 diff --git a/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py index 58bc269..6244329 100644 --- a/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py +++ b/test/local-content/collections/ansible_collections/testns/test_collection/plugins/filter/test_filter.py @@ -1,5 +1,4 @@ """A filter plugin.""" -# pylint: disable=invalid-name def a_test_filter(a, b): diff --git a/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py index 92bd6e7..63f4532 100644 --- a/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py +++ b/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/b_failed_complete.py @@ -1,5 +1,4 @@ """A test plugin.""" -# pylint: disable=invalid-name def compatibility_in_test(a, b): diff --git a/test/rules/fixtures/ematcher.py b/test/rules/fixtures/ematcher.py index 1b04b6b..b034064 100644 --- a/test/rules/fixtures/ematcher.py +++ b/test/rules/fixtures/ematcher.py @@ -1,4 +1,5 @@ """Custom rule used as fixture.""" + from ansiblelint.rules import AnsibleLintRule diff --git a/test/rules/fixtures/raw_task.py b/test/rules/fixtures/raw_task.py index 0d5b023..6dfd7d9 100644 --- a/test/rules/fixtures/raw_task.py +++ b/test/rules/fixtures/raw_task.py @@ -1,4 +1,5 @@ """Test Rule that needs_raw_task.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/test/rules/fixtures/unset_variable_matcher.py b/test/rules/fixtures/unset_variable_matcher.py index 8486009..ea8b0c0 100644 --- a/test/rules/fixtures/unset_variable_matcher.py +++ b/test/rules/fixtures/unset_variable_matcher.py @@ -1,4 +1,5 @@ """Custom linting rule used as test fixture.""" + from ansiblelint.rules import AnsibleLintRule diff --git a/test/rules/test_args.py b/test/rules/test_args.py new file mode 100644 index 0000000..30d83f1 --- /dev/null +++ b/test/rules/test_args.py @@ -0,0 +1,19 @@ +"""Tests for args rule.""" + +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_args_module_relative_import(default_rules_collection: RulesCollection) -> None: + """Validate args check of a module with a relative import.""" + lintable = Lintable( + "examples/playbooks/module_relative_import.yml", + kind="playbook", + ) + result = Runner(lintable, rules=default_rules_collection).run() + assert len(result) == 1, result + assert result[0].lineno == 5 + assert result[0].filename == "examples/playbooks/module_relative_import.yml" + assert result[0].tag == "args[module]" + assert result[0].message == "missing required arguments: name" diff --git a/test/rules/test_deprecated_module.py b/test/rules/test_deprecated_module.py index a57d8db..6346b80 100644 --- a/test/rules/test_deprecated_module.py +++ b/test/rules/test_deprecated_module.py @@ -1,4 +1,5 @@ """Tests for deprecated-module rule.""" + from pathlib import Path from ansiblelint.rules import RulesCollection diff --git a/test/rules/test_inline_env_var.py b/test/rules/test_inline_env_var.py index 98f337e..aa833ec 100644 --- a/test/rules/test_inline_env_var.py +++ b/test/rules/test_inline_env_var.py @@ -1,4 +1,5 @@ """Tests for inline-env-var rule.""" + from ansiblelint.rules import RulesCollection from ansiblelint.rules.inline_env_var import EnvVarsInCommandRule from ansiblelint.testing import RunFromText @@ -13,7 +14,7 @@ SUCCESS_PLAY_TASKS = """ HELLO: hello - name: Use some key-value pairs - command: chdir=/tmp creates=/tmp/bobbins warn=no touch bobbins + command: chdir=/tmp creates=/tmp/bobbins touch bobbins - name: Commands can have flags command: abc --xyz=def blah @@ -68,7 +69,7 @@ FAIL_PLAY_TASKS = """ command: HELLO=hello echo $HELLO - name: Typo some stuff - command: cerates=/tmp/blah warn=no touch /tmp/blah + command: crates=/tmp/blah touch /tmp/blah """ diff --git a/test/rules/test_no_changed_when.py b/test/rules/test_no_changed_when.py index c89d8f4..3316e12 100644 --- a/test/rules/test_no_changed_when.py +++ b/test/rules/test_no_changed_when.py @@ -1,4 +1,5 @@ """Tests for no-change-when rule.""" + from ansiblelint.rules import RulesCollection from ansiblelint.rules.no_changed_when import CommandHasChangesCheckRule from ansiblelint.runner import Runner diff --git a/test/rules/test_package_latest.py b/test/rules/test_package_latest.py index 5631f02..972fced 100644 --- a/test/rules/test_package_latest.py +++ b/test/rules/test_package_latest.py @@ -1,4 +1,5 @@ """Tests for package-latest rule.""" + from ansiblelint.rules import RulesCollection from ansiblelint.rules.package_latest import PackageIsNotLatestRule from ansiblelint.runner import Runner @@ -20,4 +21,4 @@ def test_package_not_latest_negative() -> None: failure = "examples/playbooks/package-check-failure.yml" bad_runner = Runner(failure, rules=collection) errs = bad_runner.run() - assert len(errs) == 4 + assert len(errs) == 5 diff --git a/test/rules/test_role_names.py b/test/rules/test_role_names.py index 491cf14..e13e56a 100644 --- a/test/rules/test_role_names.py +++ b/test/rules/test_role_names.py @@ -1,4 +1,5 @@ """Test the RoleNames rule.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any diff --git a/test/rules/test_syntax_check.py b/test/rules/test_syntax_check.py index 2fe36a3..6ec111d 100644 --- a/test/rules/test_syntax_check.py +++ b/test/rules/test_syntax_check.py @@ -1,32 +1,71 @@ """Tests for syntax-check rule.""" + from typing import Any +import pytest + from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner +@pytest.mark.parametrize( + ("filename", "expected_results"), + ( + pytest.param( + "examples/playbooks/conflicting_action.yml", + [ + ( + "syntax-check[specific]", + 4, + 7, + "conflicting action statements: ansible.builtin.debug, ansible.builtin.command", + ), + ], + id="0", + ), + pytest.param( + "examples/playbooks/conflicting_action2.yml", + [ + ( + "parser-error", + 1, + None, + "conflicting action statements: block, include_role", + ), + ( + "syntax-check[specific]", + 5, + 7, + "'include_role' is not a valid attribute for a Block", + ), + ], + id="1", + ), + ), +) def test_get_ansible_syntax_check_matches( default_rules_collection: RulesCollection, + filename: str, + expected_results: list[tuple[str, int, int, str]], ) -> None: """Validate parsing of ansible output.""" lintable = Lintable( - "examples/playbooks/conflicting_action.yml", + filename, kind="playbook", ) - result = Runner(lintable, rules=default_rules_collection).run() + result = sorted(Runner(lintable, rules=default_rules_collection).run()) - assert result[0].lineno == 4 - assert result[0].column == 7 - assert ( - result[0].message - == "conflicting action statements: ansible.builtin.debug, ansible.builtin.command" - ) - # We internally convert absolute paths returned by ansible into paths - # relative to current directory. - assert result[0].filename.endswith("/conflicting_action.yml") - assert len(result) == 1 + assert len(result) == len(expected_results) + for index, expected in enumerate(expected_results): + assert result[index].tag == expected[0] + assert result[index].lineno == expected[1] + assert result[index].column == expected[2] + assert str(expected[3]) in result[index].message + # We internally convert absolute paths returned by ansible into paths + # relative to current directory. + # assert result[index].filename.endswith("/conflicting_action.yml") def test_empty_playbook(default_rules_collection: RulesCollection) -> None: @@ -58,11 +97,10 @@ def test_extra_vars_passed_to_command( assert not result -def test_syntax_check_role() -> None: +def test_syntax_check_role(default_rules_collection: RulesCollection) -> None: """Validate syntax check of a broken role.""" lintable = Lintable("examples/playbooks/roles/invalid_due_syntax", kind="role") - rules = RulesCollection() - result = Runner(lintable, rules=rules).run() + result = Runner(lintable, rules=default_rules_collection).run() assert len(result) == 1, result assert result[0].lineno == 2 assert result[0].filename == "examples/roles/invalid_due_syntax/tasks/main.yml" diff --git a/test/schemas/.mocharc.json b/test/schemas/.mocharc.json index 0148197..c3b1d46 100644 --- a/test/schemas/.mocharc.json +++ b/test/schemas/.mocharc.json @@ -1,7 +1,11 @@ { "colors": true, - "extension": ["ts"], + "extensions": ["ts"], "require": "ts-node/register", + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ], "slow": "500", "spec": "src/**/*.spec.ts" } diff --git a/test/schemas/negative_test/.ansible-lint.md b/test/schemas/negative_test/.ansible-lint.md index f1f2308..7746f3c 100644 --- a/test/schemas/negative_test/.ansible-lint.md +++ b/test/schemas/negative_test/.ansible-lint.md @@ -128,6 +128,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [], "parse_errors": [ { diff --git a/test/schemas/negative_test/.config/ansible-lint.yml.md b/test/schemas/negative_test/.config/ansible-lint.yml.md index 4fe331e..8d055af 100644 --- a/test/schemas/negative_test/.config/ansible-lint.yml.md +++ b/test/schemas/negative_test/.config/ansible-lint.yml.md @@ -29,6 +29,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/.config/ansible-lint.yml", diff --git a/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md index 72b4f96..82b2601 100644 --- a/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md +++ b/test/schemas/negative_test/changelogs/invalid-date/changelogs/changelog.yaml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/changelogs/invalid-date/changelogs/changelog.yaml", diff --git a/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md index ef847c3..f9cbc03 100644 --- a/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md +++ b/test/schemas/negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/changelogs/invalid-plugin-namespace/changelogs/changelog.yaml", diff --git a/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md index 5938944..c21eca7 100644 --- a/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md +++ b/test/schemas/negative_test/changelogs/list/changelogs/changelog.yaml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/changelogs/list/changelogs/changelog.yaml", diff --git a/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md index 64c4665..9acc793 100644 --- a/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md +++ b/test/schemas/negative_test/changelogs/no-semver/changelogs/changelog.yaml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/changelogs/no-semver/changelogs/changelog.yaml", diff --git a/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md index 490bdbe..35cf572 100644 --- a/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md +++ b/test/schemas/negative_test/changelogs/unknown-keys/changelogs/changelog.yaml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/changelogs/unknown-keys/changelogs/changelog.yaml", diff --git a/test/schemas/negative_test/galaxy_1/galaxy.yml.md b/test/schemas/negative_test/galaxy_1/galaxy.yml.md index bbb79ec..0119fbe 100644 --- a/test/schemas/negative_test/galaxy_1/galaxy.yml.md +++ b/test/schemas/negative_test/galaxy_1/galaxy.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/galaxy_1/galaxy.yml", diff --git a/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md b/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md index d4fefaf..1979297 100644 --- a/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md +++ b/test/schemas/negative_test/inventory/broken_dev_inventory.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/inventory/broken_dev_inventory.yml", diff --git a/test/schemas/negative_test/meta/runtime.yml.md b/test/schemas/negative_test/meta/runtime.yml.md index 761fa6f..45dfc74 100644 --- a/test/schemas/negative_test/meta/runtime.yml.md +++ b/test/schemas/negative_test/meta/runtime.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/meta/runtime.yml", diff --git a/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md b/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md index 68e09eb..5c0320e 100644 --- a/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md +++ b/test/schemas/negative_test/molecule/platforms_children/molecule.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/molecule/platforms_children/molecule.yml", diff --git a/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md index 74b8de7..8ecbddf 100644 --- a/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md +++ b/test/schemas/negative_test/molecule/platforms_networks/molecule.yml.md @@ -30,6 +30,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/molecule/platforms_networks/molecule.yml", diff --git a/test/schemas/negative_test/playbooks/environment.yml.md b/test/schemas/negative_test/playbooks/environment.yml.md index 8923cb3..b34d039 100644 --- a/test/schemas/negative_test/playbooks/environment.yml.md +++ b/test/schemas/negative_test/playbooks/environment.yml.md @@ -91,6 +91,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/environment.yml", @@ -101,6 +102,11 @@ stdout: "path": "$[0]", "message": "'environment', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].environment", + "message": "'{{ foo }}-123' is not of type 'object'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/failed_when.yml.md b/test/schemas/negative_test/playbooks/failed_when.yml.md index e843e1f..c1c6e6c 100644 --- a/test/schemas/negative_test/playbooks/failed_when.yml.md +++ b/test/schemas/negative_test/playbooks/failed_when.yml.md @@ -118,6 +118,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/failed_when.yml", @@ -128,6 +129,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'boolean'" + }, + "num_sub_errors": 9, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/gather_facts.yml.md b/test/schemas/negative_test/playbooks/gather_facts.yml.md index 0eb3a4b..6b8d90a 100644 --- a/test/schemas/negative_test/playbooks/gather_facts.yml.md +++ b/test/schemas/negative_test/playbooks/gather_facts.yml.md @@ -63,7 +63,25 @@ "params": { "type": "boolean" }, - "schemaPath": "#/properties/gather_facts/type" + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/gather_facts", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/gather_facts", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" }, { "instancePath": "/0", @@ -84,6 +102,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/gather_facts.yml", @@ -94,6 +113,11 @@ stdout: "path": "$[0]", "message": "'gather_facts', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].gather_facts", + "message": "'non' is not of type 'boolean'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", @@ -113,7 +137,15 @@ stdout: }, { "path": "$[0].gather_facts", + "message": "'non' is not valid under any of the given schemas" + }, + { + "path": "$[0].gather_facts", "message": "'non' is not of type 'boolean'" + }, + { + "path": "$[0].gather_facts", + "message": "'non' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" } ] } diff --git a/test/schemas/negative_test/playbooks/gather_subset.yml.md b/test/schemas/negative_test/playbooks/gather_subset.yml.md index b426a23..5ee372b 100644 --- a/test/schemas/negative_test/playbooks/gather_subset.yml.md +++ b/test/schemas/negative_test/playbooks/gather_subset.yml.md @@ -84,6 +84,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/gather_subset.yml", @@ -94,6 +95,11 @@ stdout: "path": "$[0]", "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].gather_subset", + "message": "'all' is not of type 'array'" + }, + "num_sub_errors": 4, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/gather_subset2.yml.md b/test/schemas/negative_test/playbooks/gather_subset2.yml.md index 8d6be68..d5ec667 100644 --- a/test/schemas/negative_test/playbooks/gather_subset2.yml.md +++ b/test/schemas/negative_test/playbooks/gather_subset2.yml.md @@ -230,6 +230,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/gather_subset2.yml", @@ -240,6 +241,11 @@ stdout: "path": "$[0]", "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].gather_subset[0]", + "message": "'invalid' is not one of ['all', 'min', 'all_ipv4_addresses', 'all_ipv6_addresses', 'apparmor', 'architecture', 'caps', 'chroot,cmdline', 'date_time', 'default_ipv4', 'default_ipv6', 'devices', 'distribution', 'distribution_major_version', 'distribution_release', 'distribution_version', 'dns', 'effective_group_ids', 'effective_user_id', 'env', 'facter', 'fips', 'hardware', 'interfaces', 'is_chroot', 'iscsi', 'kernel', 'local', 'lsb', 'machine', 'machine_id', 'mounts', 'network', 'ohai', 'os_family', 'pkg_mgr', 'platform', 'processor', 'processor_cores', 'processor_count', 'python', 'python_version', 'real_user_id', 'selinux', 'service_mgr', 'ssh_host_key_dsa_public', 'ssh_host_key_ecdsa_public', 'ssh_host_key_ed25519_public', 'ssh_host_key_rsa_public', 'ssh_host_pub_keys', 'ssh_pub_keys', 'system', 'system_capabilities', 'system_capabilities_enforced', 'user', 'user_dir', 'user_gecos', 'user_gid', 'user_id', 'user_shell', 'user_uid', 'virtual', 'virtualization_role', 'virtualization_type']" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/gather_subset3.yml.md b/test/schemas/negative_test/playbooks/gather_subset3.yml.md index 7dc1b13..c2ed681 100644 --- a/test/schemas/negative_test/playbooks/gather_subset3.yml.md +++ b/test/schemas/negative_test/playbooks/gather_subset3.yml.md @@ -248,6 +248,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/gather_subset3.yml", @@ -258,6 +259,11 @@ stdout: "path": "$[0]", "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].gather_subset[0]", + "message": "1 is not one of ['all', 'min', 'all_ipv4_addresses', 'all_ipv6_addresses', 'apparmor', 'architecture', 'caps', 'chroot,cmdline', 'date_time', 'default_ipv4', 'default_ipv6', 'devices', 'distribution', 'distribution_major_version', 'distribution_release', 'distribution_version', 'dns', 'effective_group_ids', 'effective_user_id', 'env', 'facter', 'fips', 'hardware', 'interfaces', 'is_chroot', 'iscsi', 'kernel', 'local', 'lsb', 'machine', 'machine_id', 'mounts', 'network', 'ohai', 'os_family', 'pkg_mgr', 'platform', 'processor', 'processor_cores', 'processor_count', 'python', 'python_version', 'real_user_id', 'selinux', 'service_mgr', 'ssh_host_key_dsa_public', 'ssh_host_key_ecdsa_public', 'ssh_host_key_ed25519_public', 'ssh_host_key_rsa_public', 'ssh_host_pub_keys', 'ssh_pub_keys', 'system', 'system_capabilities', 'system_capabilities_enforced', 'user', 'user_dir', 'user_gecos', 'user_gid', 'user_id', 'user_shell', 'user_uid', 'virtual', 'virtualization_role', 'virtualization_type']" + }, + "num_sub_errors": 8, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/gather_subset4.yml.md b/test/schemas/negative_test/playbooks/gather_subset4.yml.md index ada01cb..6372c84 100644 --- a/test/schemas/negative_test/playbooks/gather_subset4.yml.md +++ b/test/schemas/negative_test/playbooks/gather_subset4.yml.md @@ -84,6 +84,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/gather_subset4.yml", @@ -94,6 +95,11 @@ stdout: "path": "$[0]", "message": "'gather_subset', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].gather_subset", + "message": "1 is not of type 'array'" + }, + "num_sub_errors": 4, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/ignore-unreachable.yml b/test/schemas/negative_test/playbooks/ignore-unreachable.yml new file mode 100644 index 0000000..0934936 --- /dev/null +++ b/test/schemas/negative_test/playbooks/ignore-unreachable.yml @@ -0,0 +1,13 @@ +--- +- name: Test + hosts: localhost + tasks: + - name: Debug + ansible.builtin.debug: + msg: ignore_unreachable should not be a string + ignore_unreachable: "yes" + + - name: Debug + ansible.builtin.debug: + msg: jinja evaluation should not be a string + ignore_unreachable: 123 diff --git a/test/schemas/negative_test/playbooks/ignore-unreachable.yml.md b/test/schemas/negative_test/playbooks/ignore-unreachable.yml.md new file mode 100644 index 0000000..b12403d --- /dev/null +++ b/test/schemas/negative_test/playbooks/ignore-unreachable.yml.md @@ -0,0 +1,329 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "pattern", + "message": "must match pattern \"^\\{[\\{%](.|[\r\n])*[\\}%]\\}$\"", + "params": { + "pattern": "^\\{[\\{%](.|[\r\n])*[\\}%]\\}$" + }, + "schemaPath": "#/$defs/full-jinja/pattern" + }, + { + "instancePath": "/0/tasks/0/ignore_unreachable", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0/tasks/1", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be boolean", + "params": { + "type": "boolean" + }, + "schemaPath": "#/oneOf/0/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/oneOf/1/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/$defs/full-jinja/type" + }, + { + "instancePath": "/0/tasks/1/ignore_unreachable", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0/tasks/1", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "successes": [], + "errors": [ + { + "filename": "negative_test/playbooks/ignore-unreachable.yml", + "path": "$[0]", + "message": "{'name': 'Test', 'hosts': 'localhost', 'tasks': [{'name': 'Debug', 'ansible.builtin.debug': {'msg': 'ignore_unreachable should not be a string'}, 'ignore_unreachable': 'yes'}, {'name': 'Debug', 'ansible.builtin.debug': {'msg': 'jinja evaluation should not be a string'}, 'ignore_unreachable': 123}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "best_deep_match": { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' is not of type 'boolean'" + }, + "num_sub_errors": 19, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'name': 'Test', 'hosts': 'localhost', 'tasks': [{'name': 'Debug', 'ansible.builtin.debug': {'msg': 'ignore_unreachable should not be a string'}, 'ignore_unreachable': 'yes'}, {'name': 'Debug', 'ansible.builtin.debug': {'msg': 'jinja evaluation should not be a string'}, 'ignore_unreachable': 123}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'name': 'Debug', 'ansible.builtin.debug': {'msg': 'ignore_unreachable should not be a string'}, 'ignore_unreachable': 'yes'} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' is not of type 'boolean'" + }, + { + "path": "$[0].tasks[0].ignore_unreachable", + "message": "'yes' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + { + "path": "$[0].tasks[1]", + "message": "{'name': 'Debug', 'ansible.builtin.debug': {'msg': 'jinja evaluation should not be a string'}, 'ignore_unreachable': 123} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not of type 'string'" + }, + { + "path": "$[0].tasks[1]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not of type 'boolean'" + }, + { + "path": "$[0].tasks[1].ignore_unreachable", + "message": "123 is not of type 'string'" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/ignore_errors.yml.md b/test/schemas/negative_test/playbooks/ignore_errors.yml.md index 61c3116..c76c098 100644 --- a/test/schemas/negative_test/playbooks/ignore_errors.yml.md +++ b/test/schemas/negative_test/playbooks/ignore_errors.yml.md @@ -136,6 +136,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/ignore_errors.yml", @@ -146,6 +147,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + "num_sub_errors": 11, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/import_playbook.yml.md b/test/schemas/negative_test/playbooks/import_playbook.yml.md index def3dce..a04a1b8 100644 --- a/test/schemas/negative_test/playbooks/import_playbook.yml.md +++ b/test/schemas/negative_test/playbooks/import_playbook.yml.md @@ -55,6 +55,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/import_playbook.yml", @@ -65,6 +66,11 @@ stdout: "path": "$[0]", "message": "{'ansible.builtin.import_playbook': {}} should not be valid under {'required': ['ansible.builtin.import_playbook']}" }, + "best_deep_match": { + "path": "$[0].ansible.builtin.import_playbook", + "message": "{} is not of type 'string'" + }, + "num_sub_errors": 3, "sub_errors": [ { "path": "$[0].ansible.builtin.import_playbook", diff --git a/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md index 184a434..143165f 100644 --- a/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md +++ b/test/schemas/negative_test/playbooks/import_playbook_exclusive.yml.md @@ -85,6 +85,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/import_playbook_exclusive.yml", @@ -95,6 +96,11 @@ stdout: "path": "$[0]", "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['ansible.builtin.import_playbook']}" }, + "best_deep_match": { + "path": "$[0]", + "message": "{'ansible.builtin.import_playbook': 'foo.yml', 'import_playbook': 'other.yml'} should not be valid under {'required': ['import_playbook']}" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/include.yml b/test/schemas/negative_test/playbooks/include.yml new file mode 100644 index 0000000..5504e13 --- /dev/null +++ b/test/schemas/negative_test/playbooks/include.yml @@ -0,0 +1,3 @@ +- hosts: localhost + tasks: + - include: foo.yml # <-- removed in Ansible 2.16 diff --git a/test/schemas/negative_test/playbooks/include.yml.md b/test/schemas/negative_test/playbooks/include.yml.md new file mode 100644 index 0000000..c577d84 --- /dev/null +++ b/test/schemas/negative_test/playbooks/include.yml.md @@ -0,0 +1,142 @@ +# ajv errors + +```json +[ + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'ansible.builtin.import_playbook'", + "params": { + "missingProperty": "ansible.builtin.import_playbook" + }, + "schemaPath": "#/oneOf/0/required" + }, + { + "instancePath": "/0", + "keyword": "required", + "message": "must have required property 'import_playbook'", + "params": { + "missingProperty": "import_playbook" + }, + "schemaPath": "#/oneOf/1/required" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/oneOf" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "hosts" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0", + "keyword": "additionalProperties", + "message": "must NOT have additional properties", + "params": { + "additionalProperty": "tasks" + }, + "schemaPath": "#/additionalProperties" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "required", + "message": "must have required property 'block'", + "params": { + "missingProperty": "block" + }, + "schemaPath": "#/required" + }, + { + "instancePath": "/0/tasks/0/include", + "keyword": "not", + "message": "must NOT be valid", + "params": {}, + "schemaPath": "#/$defs/removed-include-module/not" + }, + { + "instancePath": "/0/tasks/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/items/anyOf" + }, + { + "instancePath": "/0", + "keyword": "oneOf", + "message": "must match exactly one schema in oneOf", + "params": { + "passingSchemas": null + }, + "schemaPath": "#/items/oneOf" + } +] +``` + +# check-jsonschema + +stdout: + +```json +{ + "status": "fail", + "successes": [], + "errors": [ + { + "filename": "negative_test/playbooks/include.yml", + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'include': 'foo.yml'}]} is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "best_deep_match": { + "path": "$[0].tasks[0].include", + "message": "'foo.yml' should not be valid under {}" + }, + "num_sub_errors": 6, + "sub_errors": [ + { + "path": "$[0]", + "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + { + "path": "$[0]", + "message": "{'hosts': 'localhost', 'tasks': [{'include': 'foo.yml'}]} is not valid under any of the given schemas" + }, + { + "path": "$[0]", + "message": "'ansible.builtin.import_playbook' is a required property" + }, + { + "path": "$[0]", + "message": "'import_playbook' is a required property" + }, + { + "path": "$[0].tasks[0]", + "message": "{'include': 'foo.yml'} is not valid under any of the given schemas" + }, + { + "path": "$[0].tasks[0]", + "message": "'block' is a required property" + }, + { + "path": "$[0].tasks[0].include", + "message": "'foo.yml' should not be valid under {}" + } + ] + } + ], + "parse_errors": [] +} +``` diff --git a/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md b/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md index 3a41059..e2d1a0d 100644 --- a/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md +++ b/test/schemas/negative_test/playbooks/invalid-failed-when.yml.md @@ -170,6 +170,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/invalid-failed-when.yml", @@ -180,6 +181,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].failed_when", + "message": "123 is not of type 'boolean'" + }, + "num_sub_errors": 15, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/invalid-serial.yml.md b/test/schemas/negative_test/playbooks/invalid-serial.yml.md index 5c48b21..80785f0 100644 --- a/test/schemas/negative_test/playbooks/invalid-serial.yml.md +++ b/test/schemas/negative_test/playbooks/invalid-serial.yml.md @@ -118,6 +118,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/invalid-serial.yml", @@ -128,6 +129,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'serial' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].serial", + "message": "'10%BAD' is not of type 'integer'" + }, + "num_sub_errors": 9, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/invalid.yml.md b/test/schemas/negative_test/playbooks/invalid.yml.md index c3435dd..6a48a92 100644 --- a/test/schemas/negative_test/playbooks/invalid.yml.md +++ b/test/schemas/negative_test/playbooks/invalid.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/invalid.yml", @@ -56,6 +57,11 @@ stdout: "path": "$[0]", "message": "{'name': 'foo', 'hosts': 'localhost', 'import_playbook': 'included.yml'} should not be valid under {'required': ['import_playbook']}" }, + "best_deep_match": { + "path": "$[0]", + "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/invalid_become.yml.md b/test/schemas/negative_test/playbooks/invalid_become.yml.md index 37d730d..e4fd6d5 100644 --- a/test/schemas/negative_test/playbooks/invalid_become.yml.md +++ b/test/schemas/negative_test/playbooks/invalid_become.yml.md @@ -93,6 +93,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/invalid_become.yml", @@ -103,6 +104,11 @@ stdout: "path": "$[0]", "message": "'become', 'hosts' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].become", + "message": "'yes' is not of type 'boolean'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/local_action.yml.md b/test/schemas/negative_test/playbooks/local_action.yml.md index 17f6244..d41de95 100644 --- a/test/schemas/negative_test/playbooks/local_action.yml.md +++ b/test/schemas/negative_test/playbooks/local_action.yml.md @@ -94,6 +94,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/local_action.yml", @@ -104,6 +105,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].local_action", + "message": "[] is not of type 'string', 'object'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/loop.yml.md b/test/schemas/negative_test/playbooks/loop.yml.md index 88df838..c7b3e45 100644 --- a/test/schemas/negative_test/playbooks/loop.yml.md +++ b/test/schemas/negative_test/playbooks/loop.yml.md @@ -94,6 +94,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/loop.yml", @@ -104,6 +105,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].loop", + "message": "123 is not of type 'string', 'array'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/loop2.yml.md b/test/schemas/negative_test/playbooks/loop2.yml.md index df60a41..cf77f85 100644 --- a/test/schemas/negative_test/playbooks/loop2.yml.md +++ b/test/schemas/negative_test/playbooks/loop2.yml.md @@ -94,6 +94,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/loop2.yml", @@ -104,6 +105,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].loop", + "message": "{} is not of type 'string', 'array'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md b/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md index ee73686..c7fd4fd 100644 --- a/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md +++ b/test/schemas/negative_test/playbooks/no_log_partial_template.yml.md @@ -136,6 +136,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/no_log_partial_template.yml", @@ -146,6 +147,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].no_log", + "message": "'foo-{{ some_var }}' is not of type 'boolean'" + }, + "num_sub_errors": 11, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/no_log_string.yml.md b/test/schemas/negative_test/playbooks/no_log_string.yml.md index c8213c0..98b4bc2 100644 --- a/test/schemas/negative_test/playbooks/no_log_string.yml.md +++ b/test/schemas/negative_test/playbooks/no_log_string.yml.md @@ -136,6 +136,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/no_log_string.yml", @@ -146,6 +147,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + "num_sub_errors": 11, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/roles.yml.md b/test/schemas/negative_test/playbooks/roles.yml.md index 9b4e25a..72d7b85 100644 --- a/test/schemas/negative_test/playbooks/roles.yml.md +++ b/test/schemas/negative_test/playbooks/roles.yml.md @@ -75,6 +75,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/roles.yml", @@ -85,6 +86,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'roles' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].roles", + "message": "'xxx' is not of type 'array'" + }, + "num_sub_errors": 4, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/run_once_list.yml.md b/test/schemas/negative_test/playbooks/run_once_list.yml.md index 84b7dc1..63424ff 100644 --- a/test/schemas/negative_test/playbooks/run_once_list.yml.md +++ b/test/schemas/negative_test/playbooks/run_once_list.yml.md @@ -154,6 +154,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/run_once_list.yml", @@ -164,6 +165,11 @@ stdout: "path": "$[0]", "message": "'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].run_once", + "message": "['{{ true }}', 'xxx'] is not of type 'boolean'" + }, + "num_sub_errors": 11, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tags-mapping.yml.md b/test/schemas/negative_test/playbooks/tags-mapping.yml.md index aada0c6..10cdb9b 100644 --- a/test/schemas/negative_test/playbooks/tags-mapping.yml.md +++ b/test/schemas/negative_test/playbooks/tags-mapping.yml.md @@ -107,6 +107,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tags-mapping.yml", @@ -117,6 +118,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + "num_sub_errors": 9, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tags-number.yml.md b/test/schemas/negative_test/playbooks/tags-number.yml.md index 3d32737..48a264a 100644 --- a/test/schemas/negative_test/playbooks/tags-number.yml.md +++ b/test/schemas/negative_test/playbooks/tags-number.yml.md @@ -107,6 +107,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tags-number.yml", @@ -117,6 +118,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + "num_sub_errors": 9, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks.yml.md b/test/schemas/negative_test/playbooks/tasks.yml.md index 309912b..08d7fe4 100644 --- a/test/schemas/negative_test/playbooks/tasks.yml.md +++ b/test/schemas/negative_test/playbooks/tasks.yml.md @@ -141,6 +141,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks.yml", @@ -151,6 +152,11 @@ stdout: "path": "$[0]", "message": "'handlers', 'hosts', 'post_tasks', 'pre_tasks', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].handlers", + "message": "1.0 is not of type 'array', 'null'" + }, + "num_sub_errors": 7, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md b/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md index 8820251..25000f8 100644 --- a/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/args_integer.yml.md @@ -64,6 +64,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/args_integer.yml", @@ -74,6 +75,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].args", + "message": "123 is not of type 'object'" + }, + "num_sub_errors": 3, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/args_string.yml.md b/test/schemas/negative_test/playbooks/tasks/args_string.yml.md index 6359a14..b1bf502 100644 --- a/test/schemas/negative_test/playbooks/tasks/args_string.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/args_string.yml.md @@ -55,6 +55,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/args_string.yml", @@ -65,6 +66,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].args", + "message": "'{{ }}123' is not of type 'object'" + }, + "num_sub_errors": 3, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/become_method_invalid.yml.md b/test/schemas/negative_test/playbooks/tasks/become_method_invalid.yml.md index fc1e692..b94527a 100644 --- a/test/schemas/negative_test/playbooks/tasks/become_method_invalid.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/become_method_invalid.yml.md @@ -140,6 +140,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/become_method_invalid.yml", @@ -150,6 +151,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].become_method", + "message": "True is not one of ['ansible.builtin.sudo', 'ansible.builtin.su', 'community.general.pbrun', 'community.general.pfexec', 'ansible.builtin.runas', 'community.general.dzdo', 'community.general.ksu', 'community.general.doas', 'community.general.machinectl', 'community.general.pmrun', 'community.general.sesu', 'community.general.sudosu']" + }, + "num_sub_errors": 10, "sub_errors": [ { "path": "$[0].become_method", diff --git a/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md index 559a200..abd8968 100644 --- a/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/ignore_errors.yml.md @@ -82,6 +82,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/ignore_errors.yml", @@ -92,6 +93,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].ignore_errors", + "message": "'should_ignore_errors' is not of type 'boolean'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0].ignore_errors", diff --git a/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md index bf4b30e..f952161 100644 --- a/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/invalid_block.yml.md @@ -35,6 +35,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/invalid_block.yml", @@ -45,6 +46,11 @@ stdout: "path": "$[0]", "message": "{'block': {}} should not be valid under {'required': ['block']}" }, + "best_deep_match": { + "path": "$[0].block", + "message": "{} is not of type 'array'" + }, + "num_sub_errors": 1, "sub_errors": [ { "path": "$[0].block", diff --git a/test/schemas/negative_test/playbooks/tasks/local_action.yml.md b/test/schemas/negative_test/playbooks/tasks/local_action.yml.md index cf67e7b..0b1151b 100644 --- a/test/schemas/negative_test/playbooks/tasks/local_action.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/local_action.yml.md @@ -40,6 +40,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/local_action.yml", @@ -50,6 +51,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].local_action", + "message": "[] is not of type 'string', 'object'" + }, + "num_sub_errors": 1, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/loop.yml.md b/test/schemas/negative_test/playbooks/tasks/loop.yml.md index de8277f..45a1908 100644 --- a/test/schemas/negative_test/playbooks/tasks/loop.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/loop.yml.md @@ -40,6 +40,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/loop.yml", @@ -50,6 +51,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].loop", + "message": "{} is not of type 'string', 'array'" + }, + "num_sub_errors": 1, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/loop2.yml.md b/test/schemas/negative_test/playbooks/tasks/loop2.yml.md index c36d7c9..e29af19 100644 --- a/test/schemas/negative_test/playbooks/tasks/loop2.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/loop2.yml.md @@ -40,6 +40,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/loop2.yml", @@ -50,6 +51,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].loop", + "message": "123 is not of type 'string', 'array'" + }, + "num_sub_errors": 1, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md index 4b9516c..bc85fd7 100644 --- a/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/no_log_number.yml.md @@ -100,6 +100,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/no_log_number.yml", @@ -110,6 +111,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].no_log", + "message": "123 is not of type 'boolean'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0].no_log", diff --git a/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md index 6742175..ec88c66 100644 --- a/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/no_log_string.yml.md @@ -82,6 +82,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/no_log_string.yml", @@ -92,6 +93,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].no_log", + "message": "'some_var' is not of type 'boolean'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0].no_log", diff --git a/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md index d860605..998d783 100644 --- a/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/tags-mapping.yml.md @@ -78,6 +78,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/tags-mapping.yml", @@ -88,6 +89,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].tags", + "message": "{} is not of type 'string'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0].tags", diff --git a/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md b/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md index 0bb7ed0..cdf421d 100644 --- a/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/tags-string.yml.md @@ -78,6 +78,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/tags-string.yml", @@ -88,6 +89,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].tags", + "message": "123 is not of type 'string'" + }, + "num_sub_errors": 6, "sub_errors": [ { "path": "$[0].tags", diff --git a/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md b/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md index bc59cc4..8acb890 100644 --- a/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/when_integer.yml.md @@ -100,6 +100,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/when_integer.yml", @@ -110,6 +111,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].when", + "message": "123 is not of type 'boolean'" + }, + "num_sub_errors": 8, "sub_errors": [ { "path": "$[0].when", diff --git a/test/schemas/negative_test/playbooks/tasks/when_object.yml.md b/test/schemas/negative_test/playbooks/tasks/when_object.yml.md index 6c28d0c..4ea653b 100644 --- a/test/schemas/negative_test/playbooks/tasks/when_object.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/when_object.yml.md @@ -100,6 +100,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/when_object.yml", @@ -110,6 +111,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].when", + "message": "{} is not of type 'boolean'" + }, + "num_sub_errors": 8, "sub_errors": [ { "path": "$[0].when", diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md index ffc8ef8..92340d2 100644 --- a/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/with_items_boolean.yml.md @@ -53,6 +53,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/with_items_boolean.yml", @@ -63,6 +64,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].with_items", + "message": "True is not of type 'string'" + }, + "num_sub_errors": 3, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md index 158b0ee..8ecd7bf 100644 --- a/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md +++ b/test/schemas/negative_test/playbooks/tasks/with_items_untemplated_string.yml.md @@ -53,6 +53,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/tasks/with_items_untemplated_string.yml", @@ -63,6 +64,11 @@ stdout: "path": "$[0]", "message": "'block' is a required property" }, + "best_deep_match": { + "path": "$[0].with_items", + "message": "'foobar' does not match '^\\\\{[\\\\{%](.|[\\r\\n])*[\\\\}%]\\\\}$'" + }, + "num_sub_errors": 3, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/var_files_list_number.yml.md b/test/schemas/negative_test/playbooks/var_files_list_number.yml.md index e915593..c47cc1b 100644 --- a/test/schemas/negative_test/playbooks/var_files_list_number.yml.md +++ b/test/schemas/negative_test/playbooks/var_files_list_number.yml.md @@ -93,6 +93,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/var_files_list_number.yml", @@ -103,6 +104,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].vars_files[0]", + "message": "0 is not of type 'string'" + }, + "num_sub_errors": 7, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/var_files_list_of_list_number.yml.md b/test/schemas/negative_test/playbooks/var_files_list_of_list_number.yml.md index 3494498..2f9b9cb 100644 --- a/test/schemas/negative_test/playbooks/var_files_list_of_list_number.yml.md +++ b/test/schemas/negative_test/playbooks/var_files_list_of_list_number.yml.md @@ -102,6 +102,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/var_files_list_of_list_number.yml", @@ -112,6 +113,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].vars_files[0][0]", + "message": "0 is not of type 'string'" + }, + "num_sub_errors": 8, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/var_files_number.yml.md b/test/schemas/negative_test/playbooks/var_files_number.yml.md index fa97e7e..f121b09 100644 --- a/test/schemas/negative_test/playbooks/var_files_number.yml.md +++ b/test/schemas/negative_test/playbooks/var_files_number.yml.md @@ -79,6 +79,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/var_files_number.yml", @@ -89,6 +90,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].vars_files", + "message": "0 is not of type 'object'" + }, + "num_sub_errors": 5, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/vars/asterisk.yml.md b/test/schemas/negative_test/playbooks/vars/asterisk.yml.md index 1ea9a98..9204de1 100644 --- a/test/schemas/negative_test/playbooks/vars/asterisk.yml.md +++ b/test/schemas/negative_test/playbooks/vars/asterisk.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/asterisk.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "{'*foo': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'*foo' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md index b862e69..6e0a83b 100644 --- a/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md +++ b/test/schemas/negative_test/playbooks/vars/dash-in-var-name.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/dash-in-var-name.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "{'foo-bar': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'foo-bar' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/list.yml.md b/test/schemas/negative_test/playbooks/vars/list.yml.md index e2c9bf5..82f599a 100644 --- a/test/schemas/negative_test/playbooks/vars/list.yml.md +++ b/test/schemas/negative_test/playbooks/vars/list.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/list.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "['foo', 'bar'] is not of type 'object'" }, + "best_deep_match": { + "path": "$", + "message": "['foo', 'bar'] is not of type 'object'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md index 7ddcff6..9f15015 100644 --- a/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md +++ b/test/schemas/negative_test/playbooks/vars/numeric-var-name.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/numeric-var-name.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "{'12': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'12' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md b/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md index 6b88b2a..d463c1c 100644 --- a/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md +++ b/test/schemas/negative_test/playbooks/vars/play-keyword.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/play-keyword.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "{'environment': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'environment' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md b/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md index ca42f74..667364c 100644 --- a/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md +++ b/test/schemas/negative_test/playbooks/vars/python-keyword.yml.md @@ -55,6 +55,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/python-keyword.yml", @@ -65,6 +66,11 @@ stdout: "path": "$", "message": "{'async': '...', 'lambda': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'async', 'lambda' do not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md index 8b73b0a..620a03c 100644 --- a/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md +++ b/test/schemas/negative_test/playbooks/vars/varname-numeric-prefix.yml.md @@ -46,6 +46,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vars/varname-numeric-prefix.yml", @@ -56,6 +57,11 @@ stdout: "path": "$", "message": "{'5foo': '...'} is not of type 'string'" }, + "best_deep_match": { + "path": "$", + "message": "'5foo' does not match any of the regexes: '^(?!(False|None|True|and|any_errors_fatal|as|assert|async|await|become|become_exe|become_flags|become_method|become_user|break|check_mode|class|collections|connection|continue|debugger|def|del|diff|elif|else|environment|except|fact_path|finally|for|force_handlers|from|gather_facts|gather_subset|gather_timeout|global|handlers|hosts|if|ignore_errors|ignore_unreachable|import|in|is|lambda|max_fail_percentage|module_defaults|name|no_log|nonlocal|not|or|order|pass|port|post_tasks|pre_tasks|raise|remote_user|return|roles|run_once|serial|strategy|tags|tasks|throttle|timeout|try|vars|vars_files|vars_prompt|while|with|yield)$)[a-zA-Z_][\\\\w]*$'" + }, + "num_sub_errors": 2, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/playbooks/vas_prompt.yml.md b/test/schemas/negative_test/playbooks/vas_prompt.yml.md index d2d809d..ca1863f 100644 --- a/test/schemas/negative_test/playbooks/vas_prompt.yml.md +++ b/test/schemas/negative_test/playbooks/vas_prompt.yml.md @@ -75,6 +75,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/vas_prompt.yml", @@ -85,6 +86,11 @@ stdout: "path": "$[0]", "message": "'hosts' does not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].vars_prompt[0]", + "message": "Additional properties are not allowed ('tags' was unexpected)" + }, + "num_sub_errors": 5, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/playbooks/when.yml.md b/test/schemas/negative_test/playbooks/when.yml.md index 4c23dcb..125e9d6 100644 --- a/test/schemas/negative_test/playbooks/when.yml.md +++ b/test/schemas/negative_test/playbooks/when.yml.md @@ -195,6 +195,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/playbooks/when.yml", @@ -205,6 +206,11 @@ stdout: "path": "$[0]", "message": "'gather_facts', 'hosts', 'tasks' do not match any of the regexes: '^(ansible\\\\.builtin\\\\.)?import_playbook$', 'name', 'tags', 'vars', 'when'" }, + "best_deep_match": { + "path": "$[0].tasks[0].when[1]", + "message": "123 is not of type 'boolean'" + }, + "num_sub_errors": 17, "sub_errors": [ { "path": "$[0]", diff --git a/test/schemas/negative_test/reqs3/meta/requirements.yml.md b/test/schemas/negative_test/reqs3/meta/requirements.yml.md index 5de6643..d0d7623 100644 --- a/test/schemas/negative_test/reqs3/meta/requirements.yml.md +++ b/test/schemas/negative_test/reqs3/meta/requirements.yml.md @@ -62,6 +62,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/reqs3/meta/requirements.yml", @@ -72,6 +73,11 @@ stdout: "path": "$", "message": "{'foo': 'bar'} is not of type 'array'" }, + "best_deep_match": { + "path": "$", + "message": "{'foo': 'bar'} is not of type 'array'" + }, + "num_sub_errors": 4, "sub_errors": [ { "path": "$", diff --git a/test/schemas/negative_test/roles/meta/argument_specs.yml.md b/test/schemas/negative_test/roles/meta/argument_specs.yml.md index 34da932..e06b00d 100644 --- a/test/schemas/negative_test/roles/meta/argument_specs.yml.md +++ b/test/schemas/negative_test/roles/meta/argument_specs.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/meta/argument_specs.yml", diff --git a/test/schemas/negative_test/roles/meta/main.yml.md b/test/schemas/negative_test/roles/meta/main.yml.md index 2c9e99b..5ed52bf 100644 --- a/test/schemas/negative_test/roles/meta/main.yml.md +++ b/test/schemas/negative_test/roles/meta/main.yml.md @@ -39,6 +39,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/meta/main.yml", diff --git a/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md index 1b8dcd0..bea80be 100644 --- a/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md +++ b/test/schemas/negative_test/roles/meta_invalid_collection/meta/main.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/meta_invalid_collection/meta/main.yml", diff --git a/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md index 5d775f0..722b549 100644 --- a/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md +++ b/test/schemas/negative_test/roles/meta_invalid_collections/meta/main.yml.md @@ -21,6 +21,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/meta_invalid_collections/meta/main.yml", diff --git a/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md index ad7e9d3..73369a2 100644 --- a/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md +++ b/test/schemas/negative_test/roles/meta_invalid_role_namespace/meta/main.yml.md @@ -19,15 +19,6 @@ "failingKeyword": "then" }, "schemaPath": "#/allOf/0/if" - }, - { - "instancePath": "/galaxy_info/namespace", - "keyword": "pattern", - "message": "must match pattern \"^[a-z][a-z0-9_]+$\"", - "params": { - "pattern": "^[a-z][a-z0-9_]+$" - }, - "schemaPath": "#/properties/namespace/pattern" } ] ``` @@ -39,18 +30,13 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/meta_invalid_role_namespace/meta/main.yml", "path": "$.galaxy_info", "message": "'author' is a required property", "has_sub_errors": false - }, - { - "filename": "negative_test/roles/meta_invalid_role_namespace/meta/main.yml", - "path": "$.galaxy_info.namespace", - "message": "'foo-bar' does not match '^[a-z][a-z0-9_]+$'", - "has_sub_errors": false } ], "parse_errors": [] diff --git a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml index 81d4d3d..0e94325 100644 --- a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml +++ b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml @@ -11,3 +11,4 @@ galaxy_info: dependencies: - version: foo # invalid, should have at least name, role or src properties + - 1234 # invalid, needs to be a string diff --git a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md index f09b1ac..a518b18 100644 --- a/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md +++ b/test/schemas/negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml.md @@ -4,6 +4,15 @@ [ { "instancePath": "/dependencies/0", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/properties/dependencies/items/anyOf/0/type" + }, + { + "instancePath": "/dependencies/0", "keyword": "required", "message": "must have required property 'role'", "params": { @@ -37,6 +46,38 @@ "schemaPath": "#/anyOf" }, { + "instancePath": "/dependencies/0", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/dependencies/items/anyOf" + }, + { + "instancePath": "/dependencies/1", + "keyword": "type", + "message": "must be string", + "params": { + "type": "string" + }, + "schemaPath": "#/properties/dependencies/items/anyOf/0/type" + }, + { + "instancePath": "/dependencies/1", + "keyword": "type", + "message": "must be object", + "params": { + "type": "object" + }, + "schemaPath": "#/type" + }, + { + "instancePath": "/dependencies/1", + "keyword": "anyOf", + "message": "must match a schema in anyOf", + "params": {}, + "schemaPath": "#/properties/dependencies/items/anyOf" + }, + { "instancePath": "/galaxy_info", "keyword": "required", "message": "must have required property 'author'", @@ -64,6 +105,7 @@ stdout: ```json { "status": "fail", + "successes": [], "errors": [ { "filename": "negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml", @@ -72,11 +114,24 @@ stdout: "has_sub_errors": true, "best_match": { "path": "$.dependencies[0]", - "message": "'role' is a required property" + "message": "{'version': 'foo'} is not of type 'string'" + }, + "best_deep_match": { + "path": "$.dependencies[0]", + "message": "{'version': 'foo'} is not of type 'string'" }, + "num_sub_errors": 4, "sub_errors": [ { "path": "$.dependencies[0]", + "message": "{'version': 'foo'} is not of type 'string'" + }, + { + "path": "$.dependencies[0]", + "message": "{'version': 'foo'} is not valid under any of the given schemas" + }, + { + "path": "$.dependencies[0]", "message": "'role' is a required property" }, { @@ -91,6 +146,31 @@ stdout: }, { "filename": "negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml", + "path": "$.dependencies[1]", + "message": "1234 is not valid under any of the given schemas", + "has_sub_errors": true, + "best_match": { + "path": "$.dependencies[1]", + "message": "1234 is not of type 'string'" + }, + "best_deep_match": { + "path": "$.dependencies[1]", + "message": "1234 is not of type 'string'" + }, + "num_sub_errors": 1, + "sub_errors": [ + { + "path": "$.dependencies[1]", + "message": "1234 is not of type 'string'" + }, + { + "path": "$.dependencies[1]", + "message": "1234 is not of type 'object'" + } + ] + }, + { + "filename": "negative_test/roles/role_with_bad_deps_in_meta/meta/main.yml", "path": "$.galaxy_info", "message": "'author' is a required property", "has_sub_errors": false diff --git a/test/schemas/package-lock.json b/test/schemas/package-lock.json index 3745a97..52bee86 100644 --- a/test/schemas/package-lock.json +++ b/test/schemas/package-lock.json @@ -5,22 +5,22 @@ "packages": { "": { "dependencies": { - "ajv-formats": "^2.1.1", + "ajv-formats": "^3.0.1", "js-yaml": "^4.1.0", "safe-stable-stringify": "^2.4.3", - "ts-node": "^10.9.1", - "vscode-json-languageservice": "^5.3.5" + "ts-node": "^10.9.2", + "vscode-json-languageservice": "^5.3.11" }, "devDependencies": { - "@types/chai": "^4.3.5", - "@types/js-yaml": "^4.0.5", + "@types/chai": "^4.3.16", + "@types/js-yaml": "^4.0.9", "@types/minimatch": "^5.1.2", - "@types/mocha": "^10.0.1", - "@types/node": "^20.3.1", - "chai": "^4.3.7", - "minimatch": "^9.0.1", - "mocha": "^10.2.0", - "typescript": "^5.1.3" + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.13", + "chai": "^5.1.1", + "minimatch": "^9.0.4", + "mocha": "^10.4.0", + "typescript": "^5.4.5" } }, "node_modules/@cspotcode/source-map-support": { @@ -77,15 +77,15 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" }, "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", + "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, "node_modules/@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "node_modules/@types/minimatch": { @@ -95,20 +95,23 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "node_modules/@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + "version": "20.12.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz", + "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@vscode/l10n": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.13.tgz", - "integrity": "sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==" + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==" }, "node_modules/acorn": { "version": "8.6.0", @@ -145,9 +148,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dependencies": { "ajv": "^8.0.0" }, @@ -217,12 +220,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/balanced-match": { @@ -241,22 +244,21 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -281,21 +283,19 @@ } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" }, "engines": { - "node": ">=4" + "node": ">=12" } }, "node_modules/chalk": { @@ -327,12 +327,12 @@ } }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "engines": { - "node": "*" + "node": ">= 16" } }, "node_modules/chokidar": { @@ -391,12 +391,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -438,13 +432,10 @@ } }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, "engines": { "node": ">=6" } @@ -491,9 +482,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -530,7 +521,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "node_modules/fsevents": { @@ -557,29 +548,28 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -598,15 +588,15 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", - "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" } }, "node_modules/has-flag": { @@ -630,7 +620,7 @@ "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "dependencies": { "once": "^1.3.0", @@ -732,9 +722,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "node_modules/locate-path": { "version": "6.0.0", @@ -768,12 +758,12 @@ } }, "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", "dev": true, "dependencies": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "node_modules/make-error": { @@ -782,9 +772,9 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -796,19 +786,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "dependencies": { "ansi-colors": "4.1.1", @@ -818,13 +799,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -839,19 +819,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" } }, "node_modules/mocha/node_modules/minimatch": { @@ -872,18 +839,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -896,7 +851,7 @@ "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "dependencies": { "wrappy": "1" @@ -941,22 +896,13 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "engines": { - "node": "*" + "node": ">= 14.16" } }, "node_modules/picomatch": { @@ -1120,9 +1066,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -1169,19 +1115,10 @@ "node": ">=0.3.1" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1190,6 +1127,11 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1204,31 +1146,31 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/vscode-json-languageservice": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.3.5.tgz", - "integrity": "sha512-DasT+bKtpaS2rTPEB4VMROnvO1WES2KD8RZZxXbumnk9sk5wco10VdB6sJgTlsKQN14tHQLZDXuHnSoSAlE8LQ==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.3.11.tgz", + "integrity": "sha512-WYS72Ymria3dn8ZbjtBbt5K71m05wY1Q6hpXV5JxUT0q75Ts0ljLmnZJAVpx8DjPgYbFD+Z8KHpWh2laKLUCtQ==", "dependencies": { - "@vscode/l10n": "^0.0.13", - "jsonc-parser": "^3.2.0", - "vscode-languageserver-textdocument": "^1.0.8", - "vscode-languageserver-types": "^3.17.3", - "vscode-uri": "^3.0.7" + "@vscode/l10n": "^0.0.18", + "jsonc-parser": "^3.2.1", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" } }, "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", - "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" }, "node_modules/vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/vscode-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", - "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" }, "node_modules/workerpool": { "version": "6.2.1", @@ -1256,7 +1198,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "node_modules/y18n": { @@ -1380,15 +1322,15 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" }, "@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", + "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, "@types/js-yaml": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", - "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "@types/minimatch": { @@ -1398,20 +1340,23 @@ "dev": true }, "@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "@types/node": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", - "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==" + "version": "20.12.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz", + "integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==", + "requires": { + "undici-types": "~5.26.4" + } }, "@vscode/l10n": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.13.tgz", - "integrity": "sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ==" + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==" }, "acorn": { "version": "8.6.0", @@ -1435,9 +1380,9 @@ } }, "ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "requires": { "ajv": "^8.0.0" } @@ -1484,9 +1429,9 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true }, "balanced-match": { @@ -1502,22 +1447,21 @@ "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -1533,18 +1477,16 @@ "dev": true }, "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", "dev": true, "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" } }, "chalk": { @@ -1569,9 +1511,9 @@ } }, "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true }, "chokidar": { @@ -1616,12 +1558,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1651,13 +1587,10 @@ "dev": true }, "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "dev": true }, "diff": { "version": "5.0.0", @@ -1689,9 +1622,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -1716,7 +1649,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, "fsevents": { @@ -1733,32 +1666,31 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "dependencies": { "minimatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz", - "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } } } @@ -1787,7 +1719,7 @@ "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -1862,9 +1794,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "locate-path": { "version": "6.0.0", @@ -1886,12 +1818,12 @@ } }, "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", "dev": true, "requires": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "make-error": { @@ -1900,29 +1832,18 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "minimatch": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", - "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } } }, "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", + "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", "dev": true, "requires": { "ansi-colors": "4.1.1", @@ -1932,13 +1853,12 @@ "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.2.0", + "glob": "8.1.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", @@ -1948,15 +1868,6 @@ "yargs-unparser": "2.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "minimatch": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", @@ -1974,12 +1885,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1989,7 +1894,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" @@ -2019,16 +1924,10 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true }, "picomatch": { @@ -2136,9 +2035,9 @@ } }, "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -2162,16 +2061,15 @@ } } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, "typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==" + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==" + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "uri-js": { "version": "4.4.1", @@ -2187,31 +2085,31 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "vscode-json-languageservice": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.3.5.tgz", - "integrity": "sha512-DasT+bKtpaS2rTPEB4VMROnvO1WES2KD8RZZxXbumnk9sk5wco10VdB6sJgTlsKQN14tHQLZDXuHnSoSAlE8LQ==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.3.11.tgz", + "integrity": "sha512-WYS72Ymria3dn8ZbjtBbt5K71m05wY1Q6hpXV5JxUT0q75Ts0ljLmnZJAVpx8DjPgYbFD+Z8KHpWh2laKLUCtQ==", "requires": { - "@vscode/l10n": "^0.0.13", - "jsonc-parser": "^3.2.0", - "vscode-languageserver-textdocument": "^1.0.8", - "vscode-languageserver-types": "^3.17.3", - "vscode-uri": "^3.0.7" + "@vscode/l10n": "^0.0.18", + "jsonc-parser": "^3.2.1", + "vscode-languageserver-textdocument": "^1.0.11", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.0.8" } }, "vscode-languageserver-textdocument": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", - "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==" + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==" }, "vscode-languageserver-types": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", - "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "vscode-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", - "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==" }, "workerpool": { "version": "6.2.1", @@ -2233,7 +2131,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, "y18n": { diff --git a/test/schemas/package.json b/test/schemas/package.json index c318ca0..bc7d264 100644 --- a/test/schemas/package.json +++ b/test/schemas/package.json @@ -1,28 +1,29 @@ { "dependencies": { - "ajv-formats": "^2.1.1", + "ajv-formats": "^3.0.1", "js-yaml": "^4.1.0", "safe-stable-stringify": "^2.4.3", - "ts-node": "^10.9.1", - "vscode-json-languageservice": "^5.3.5" + "ts-node": "^10.9.2", + "vscode-json-languageservice": "^5.3.11" }, "scripts": { - "compile": "tsc -p ./src", + "compile": "tsc", "deps": "npx --yes npm-check-updates -u && npm install --ignore-scripts", "test": "python3 src/rebuild.py && mocha" }, "devDependencies": { - "@types/chai": "^4.3.5", - "@types/js-yaml": "^4.0.5", + "@types/chai": "^4.3.16", + "@types/js-yaml": "^4.0.9", "@types/minimatch": "^5.1.2", - "@types/mocha": "^10.0.1", - "@types/node": "^20.3.1", - "chai": "^4.3.7", - "minimatch": "^9.0.1", - "mocha": "^10.2.0", - "typescript": "^5.1.3" + "@types/mocha": "^10.0.6", + "@types/node": "^20.12.13", + "chai": "^5.1.1", + "minimatch": "^9.0.4", + "mocha": "^10.4.0", + "typescript": "^5.4.5" }, "directories": { "test": "./src" - } + }, + "type": "module" } diff --git a/test/schemas/src/rebuild.py b/test/schemas/src/rebuild.py index 2fab8c0..5eb4807 100644 --- a/test/schemas/src/rebuild.py +++ b/test/schemas/src/rebuild.py @@ -1,4 +1,5 @@ """Utility to generate some complex patterns.""" + import copy import json import keyword @@ -63,11 +64,11 @@ def is_ref_used(obj: Any, ref: str) -> bool: if obj.get("$ref", None) == ref_use: return True for _ in obj.values(): - if isinstance(_, (dict, list)) and is_ref_used(_, ref): + if isinstance(_, dict | list) and is_ref_used(_, ref): return True elif isinstance(obj, list): for _ in obj: - if isinstance(_, (dict, list)) and is_ref_used(_, ref): + if isinstance(_, dict | list) and is_ref_used(_, ref): return True return False @@ -119,16 +120,13 @@ if __name__ == "__main__": for key, value in combined_json["$defs"][subschema].items(): sub_json[key] = value sub_json["$comment"] = "Generated from ansible.json, do not edit." - sub_json[ - "$id" - ] = f"https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/{subschema}.json" + sub_json["$id"] = ( + f"https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/{subschema}.json" + ) # Remove all unreferenced ($ref) definitions ($defs) recursively while True: - spare = [] - for k in sub_json["$defs"]: - if not is_ref_used(sub_json, k): - spare.append(k) + spare = [k for k in sub_json["$defs"] if not is_ref_used(sub_json, k)] for k in spare: print(f"{subschema}: deleting unused '{k}' definition") # noqa: T201 del sub_json["$defs"][k] diff --git a/test/schemas/src/schema.spec.ts b/test/schemas/src/schema.spec.ts index b826461..beb6ee2 100644 --- a/test/schemas/src/schema.spec.ts +++ b/test/schemas/src/schema.spec.ts @@ -5,9 +5,7 @@ import { minimatch } from "minimatch"; import yaml from "js-yaml"; import { assert } from "chai"; import stringify from "safe-stable-stringify"; -import { integer } from "vscode-languageserver-types"; -import { exec } from "child_process"; -const spawnSync = require("child_process").spawnSync; +import { spawnSync } from "child_process"; function ansiRegex({ onlyFirst = false } = {}) { const pattern = [ @@ -21,7 +19,7 @@ function ansiRegex({ onlyFirst = false } = {}) { function stripAnsi(data: string) { if (typeof data !== "string") { throw new TypeError( - `Expected a \`string\`, got \`${typeof data}\ = ${data}` + `Expected a \`string\`, got \`${typeof data}\ = ${data}`, ); } return data.replace(ansiRegex(), ""); @@ -57,7 +55,7 @@ describe("schemas under f/", function () { const validator = ajv.compile(schema_json); if (schema_json.examples == undefined) { console.error( - `Schema file ${schema_file} is missing an examples key that we need for documenting file matching patterns.` + `Schema file ${schema_file} is missing an examples key that we need for documenting file matching patterns.`, ); return process.exit(1); } @@ -67,7 +65,7 @@ describe("schemas under f/", function () { it(`linting ${test_file} using ${schema_file}`, function () { var errors_md = ""; const result = validator( - yaml.load(fs.readFileSync(test_file, "utf8")) + yaml.load(fs.readFileSync(test_file, "utf8")), ); if (validator.errors) { errors_md += "# ajv errors\n\n```json\n"; @@ -76,17 +74,17 @@ describe("schemas under f/", function () { } // validate using check-jsonschema (python-jsonschema): // const py = exec(); - // Do not use python -m ... calling notation because for some + // Do not use python3 -m ... calling notation because for some // reason, nodejs environment lacks some env variables needed // and breaks usage from inside virtualenvs. const proc = spawnSync( `${process.env.VIRTUAL_ENV}/bin/check-jsonschema -v -o json --schemafile f/${schema_file} ${test_file}`, - { shell: true, encoding: "utf-8", stdio: "pipe" } + { shell: true, encoding: "utf-8", stdio: "pipe" }, ); if (proc.status != 0) { // real errors are sent to stderr due to https://github.com/python-jsonschema/check-jsonschema/issues/88 errors_md += "# check-jsonschema\n\nstdout:\n\n```json\n"; - errors_md += stripAnsi(proc.output[1]); + errors_md += stripAnsi(proc.output[1] || ""); errors_md += "```\n"; if (proc.output[2]) { errors_md += "\nstderr:\n\n```\n"; @@ -110,10 +108,10 @@ describe("schemas under f/", function () { assert.equal( result, !expect_fail, - `${JSON.stringify(validator.errors)}` + `${JSON.stringify(validator.errors)}`, ); }); - } + }, ); // All /$defs/ that have examples property are assumed to be // subschemas, "tasks" being the primary such case, which is also used @@ -130,15 +128,15 @@ describe("schemas under f/", function () { ({ file: test_file, expect_fail }) => { it(`linting ${test_file} using ${subschema_uri}`, function () { const result = subschema_validator( - yaml.load(fs.readFileSync(test_file, "utf8")) + yaml.load(fs.readFileSync(test_file, "utf8")), ); assert.equal( result, !expect_fail, - `${JSON.stringify(validator.errors)}` + `${JSON.stringify(validator.errors)}`, ); }); - } + }, ); } } @@ -148,29 +146,29 @@ describe("schemas under f/", function () { // find all tests for each schema file function getTestFiles( - globs: string[] + globs: string[], ): { file: string; expect_fail: boolean }[] { const files = Array.from( new Set( globs .map((glob: any) => minimatch.match(test_files, path.join("**", glob))) - .flat() - ) + .flat(), + ), ); const negative_files = Array.from( new Set( globs .map((glob: any) => - minimatch.match(negative_test_files, path.join("**", glob)) + minimatch.match(negative_test_files, path.join("**", glob)), ) - .flat() - ) + .flat(), + ), ); // All fails ending with fail, like `foo.fail.yml` are expected to fail validation let result = files.map((f) => ({ file: f, expect_fail: false })); result = result.concat( - negative_files.map((f) => ({ file: f, expect_fail: true })) + negative_files.map((f) => ({ file: f, expect_fail: true })), ); return result; } diff --git a/test/schemas/test/playbooks/gather_facts.yml b/test/schemas/test/playbooks/gather_facts.yml index 598188d..bdba790 100644 --- a/test/schemas/test/playbooks/gather_facts.yml +++ b/test/schemas/test/playbooks/gather_facts.yml @@ -4,3 +4,9 @@ tasks: - ansible.builtin.debug: msg: foo + +- hosts: localhost + gather_facts: "{{ facts_var_bool | default(false) }}" + tasks: + - ansible.builtin.debug: + msg: bar diff --git a/test/schemas/test/playbooks/ignore-unreachable.yml b/test/schemas/test/playbooks/ignore-unreachable.yml new file mode 100644 index 0000000..8dfdc21 --- /dev/null +++ b/test/schemas/test/playbooks/ignore-unreachable.yml @@ -0,0 +1,13 @@ +--- +- name: Test + hosts: localhost + tasks: + - name: Debug + ansible.builtin.debug: + msg: ignore_unreachable should be a boolean + ignore_unreachable: true + + - name: Debug + ansible.builtin.debug: + msg: "foo" + ignore_unreachable: '{{ "yes" | bool }}' diff --git a/test/schemas/test/playbooks/order.yml b/test/schemas/test/playbooks/order.yml new file mode 100644 index 0000000..08534de --- /dev/null +++ b/test/schemas/test/playbooks/order.yml @@ -0,0 +1,15 @@ +--- +- name: Test + hosts: localhost + order: "{{ host_order | default('shuffle') }}" + gather_facts: false + serial: 1 + tasks: + - name: ABC + ansible.builtin.debug: + msg: "hello" +- name: Test 2 + hosts: localhost + order: inventory + gather_facts: false + tasks: [] diff --git a/test/schemas/test/roles/foo/meta/argument_specs.yml b/test/schemas/test/roles/foo/meta/argument_specs.yml index c8d8c68..a83b82c 100644 --- a/test/schemas/test/roles/foo/meta/argument_specs.yml +++ b/test/schemas/test/roles/foo/meta/argument_specs.yml @@ -40,6 +40,35 @@ argument_specs: - 3 - 123 + complex_required_options: + type: dict + description: Contains sub-options with interacting requirements + options: + foo: + type: str + bar: + type: str + baz: + type: str + + mutually_exclusive: + - ["foo", "bar"] + + required_together: + - ["bar", "baz"] + + required_one_of: + - ["foo", "bar", "baz"] + + required_if: + - ["foo", "must_have_bar_and_baz_default", ["bar", "baz"]] + - ["foo", "must_have_bar_and_baz_explicit", ["bar", "baz"], false] + - ["foo", "must_have_one_of_bar_or_baz", ["bar", "baz"], true] + + required_by: + foo: "bar" + bar: ["foo", "baz"] + seealso: - module: community.foo.bar - module: community.foo.baz @@ -55,11 +84,31 @@ argument_specs: name: The Ansible documentation. description: A link to the Ansible documentation. + examples: |- + - name: Use role + include_role: foo.bar.baz + alternate: short_description: The alternate entry point for the my_app role. author: - Foobar Baz - Bert Foo + attributes: + idempotent: + description: Whether the role is idempotent. + support: full + check_mode: + description: + - Whether the role supports check mode. + support: partial + details: + - Does not work if O(my_app_int=5). + version_added: 1.2.0 + action_group: + description: + - Use C(group/foo.bar.baz) in C(module_defaults) to set authentication options for the C(foo.bar) modules used by this role. + support: full + membership: foo.bar.baz options: my_app_int: type: "int" diff --git a/test/schemas/test/roles/foo/meta/main.yml b/test/schemas/test/roles/foo/meta/main.yml index b84b10c..2536c22 100644 --- a/test/schemas/test/roles/foo/meta/main.yml +++ b/test/schemas/test/roles/foo/meta/main.yml @@ -5,6 +5,7 @@ dependencies: version: "1.0" - name: ansible-role-bar version: "1.0" + - ansible-role-baz # from Bitbucket - src: git+http://bitbucket.org/willthames/git-ansible-galaxy version: v1.4 diff --git a/test/schemas/tsconfig.json b/test/schemas/tsconfig.json index fe51c68..b2291af 100644 --- a/test/schemas/tsconfig.json +++ b/test/schemas/tsconfig.json @@ -2,16 +2,16 @@ "compilerOptions": { "declaration": true, "esModuleInterop": true, - "lib": ["es5", "es2015.promise"], - "module": "commonjs", + "lib": ["ESNext"], + "module": "esnext", "moduleResolution": "node", - "outDir": "../lib/umd", + "outDir": "../../.tox/out", "resolveJsonModule": true, "sourceMap": true, "strict": true, "stripInternal": true, - "target": "es5" + "target": "ESNext", }, "exclude": ["node_modules"], - "include": ["src/**/*"] + "include": ["src/**/*"], } diff --git a/test/test_adjacent_plugins.py b/test/test_adjacent_plugins.py new file mode 100644 index 0000000..3e642ce --- /dev/null +++ b/test/test_adjacent_plugins.py @@ -0,0 +1,25 @@ +"""Test ability to recognize adjacent modules/plugins.""" + +import logging + +import pytest + +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner + + +def test_adj_action( + default_rules_collection: RulesCollection, + caplog: pytest.LogCaptureFixture, +) -> None: + """Assures local collections are found.""" + playbook_path = "examples/playbooks/adj_action.yml" + + with caplog.at_level(logging.DEBUG): + runner = Runner(playbook_path, rules=default_rules_collection, verbosity=1) + results = runner.run() + assert "Unable to load module" not in caplog.text + assert "Unable to resolve FQCN" not in caplog.text + + assert len(runner.lintables) == 1 + assert len(results) == 0 diff --git a/test/test_ansiblelintrule.py b/test/test_ansiblelintrule.py index c576e0f..dcce2ea 100644 --- a/test/test_ansiblelintrule.py +++ b/test/test_ansiblelintrule.py @@ -1,15 +1,15 @@ """Generic tests for AnsibleLintRule class.""" + from __future__ import annotations from typing import TYPE_CHECKING, Any import pytest -from ansiblelint.config import options -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules import AnsibleLintRule, RulesCollection if TYPE_CHECKING: - from _pytest.monkeypatch import MonkeyPatch + from ansiblelint.config import Options def test_unjinja() -> None: @@ -20,12 +20,14 @@ def test_unjinja() -> None: @pytest.mark.parametrize("rule_config", ({}, {"foo": True, "bar": 1})) -def test_rule_config(rule_config: dict[str, Any], monkeypatch: MonkeyPatch) -> None: - """Check that a rule config is inherited from options.""" - rule_id = "rule-0" - monkeypatch.setattr(AnsibleLintRule, "id", rule_id) - monkeypatch.setitem(options.rules, rule_id, rule_config) - - rule = AnsibleLintRule() - assert set(rule.rule_config.items()) == set(rule_config.items()) - assert all(rule.get_config(k) == v for k, v in rule_config.items()) +def test_rule_config( + rule_config: dict[str, Any], + config_options: Options, +) -> None: + """Check that a rule config can be accessed.""" + config_options.rules["load-failure"] = rule_config + rules = RulesCollection(options=config_options) + for rule in rules: + if rule.id == "load-failure": + assert rule._collection # noqa: SLF001 + assert rule.rule_config == rule_config diff --git a/test/test_ansiblesyntax.py b/test/test_ansiblesyntax.py index f71a525..649833e 100644 --- a/test/test_ansiblesyntax.py +++ b/test/test_ansiblesyntax.py @@ -3,6 +3,7 @@ This module contains tests that validate that linter does not produce errors when encountering what counts as valid Ansible syntax. """ + from ansiblelint.testing import RunFromText PB_WITH_NULL_TASKS = """\ diff --git a/test/test_app.py b/test/test_app.py index 140f5f6..cbeae3d 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -1,4 +1,5 @@ """Test for app module.""" + from pathlib import Path from ansiblelint.constants import RC diff --git a/test/test_cli.py b/test/test_cli.py index a37a43d..b2c3320 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -1,4 +1,5 @@ """Test cli arguments and config.""" + from __future__ import annotations import os @@ -66,37 +67,77 @@ def test_ensure_config_are_equal( @pytest.mark.parametrize( - ("with_base", "args", "config"), + ("with_base", "args", "config", "expected"), ( - (True, ["--write"], "test/fixtures/config-with-write-all.yml"), - (True, ["--write=all"], "test/fixtures/config-with-write-all.yml"), - (True, ["--write", "all"], "test/fixtures/config-with-write-all.yml"), - (True, ["--write=none"], "test/fixtures/config-with-write-none.yml"), - (True, ["--write", "none"], "test/fixtures/config-with-write-none.yml"), - ( + pytest.param( + True, + ["--fix"], + "test/fixtures/config-with-write-all.yml", + ["all"], + id="1", + ), + pytest.param( + True, + ["--fix=all"], + "test/fixtures/config-with-write-all.yml", + ["all"], + id="2", + ), + pytest.param( + True, + ["--fix", "all"], + "test/fixtures/config-with-write-all.yml", + ["all"], + id="3", + ), + pytest.param( True, - ["--write=rule-tag,rule-id"], + ["--fix=none"], + "test/fixtures/config-with-write-none.yml", + [], + id="4", + ), + pytest.param( + True, + ["--fix", "none"], + "test/fixtures/config-with-write-none.yml", + [], + id="5", + ), + pytest.param( + True, + ["--fix=rule-tag,rule-id"], "test/fixtures/config-with-write-subset.yml", + ["rule-tag", "rule-id"], + id="6", ), - ( + pytest.param( True, - ["--write", "rule-tag,rule-id"], + ["--fix", "rule-tag,rule-id"], "test/fixtures/config-with-write-subset.yml", + ["rule-tag", "rule-id"], + id="7", ), - ( + pytest.param( True, - ["--write", "rule-tag", "--write", "rule-id"], + ["--fix", "rule-tag", "--fix", "rule-id"], "test/fixtures/config-with-write-subset.yml", + ["rule-tag", "rule-id"], + id="8", ), - ( + pytest.param( False, - ["--write", "examples/playbooks/example.yml"], + ["--fix", "examples/playbooks/example.yml"], "test/fixtures/config-with-write-all.yml", + ["all"], + id="9", ), - ( + pytest.param( False, - ["--write", "examples/playbooks/example.yml", "non-existent.yml"], + ["--fix", "examples/playbooks/example.yml", "non-existent.yml"], "test/fixtures/config-with-write-all.yml", + ["all"], + id="10", ), ), ) @@ -105,21 +146,22 @@ def test_ensure_write_cli_does_not_consume_lintables( with_base: bool, args: list[str], config: str, + expected: list[str], ) -> None: - """Check equality of the CLI --write options to config files.""" + """Check equality of the CLI --fix options to config files.""" cli_parser = cli.get_cli_parser() command = base_arguments + args if with_base else args options = cli_parser.parse_args(command) file_config = cli.load_config(config)[0] - file_value = file_config.get("write_list") + file_config.get("write_list") orig_cli_value = options.write_list - cli_value = cli.WriteArgAction.merge_write_list_config( + cli_value = cli.WriteArgAction.merge_fix_list_config( from_file=[], from_cli=orig_cli_value, ) - assert file_value == cli_value + assert cli_value == expected def test_config_can_be_overridden(base_arguments: list[str]) -> None: diff --git a/test/test_cli_role_paths.py b/test/test_cli_role_paths.py index 148e1ed..131c3b5 100644 --- a/test/test_cli_role_paths.py +++ b/test/test_cli_role_paths.py @@ -1,4 +1,5 @@ """Tests related to role paths.""" + from __future__ import annotations import os @@ -174,7 +175,10 @@ def test_run_single_role_path_with_roles_path_env(local_test_dir: Path) -> None: @pytest.mark.parametrize( ("result", "env"), - ((True, {"GITHUB_ACTIONS": "true", "GITHUB_WORKFLOW": "foo"}), (False, None)), + ( + (True, {"GITHUB_ACTIONS": "true", "GITHUB_WORKFLOW": "foo", "NO_COLOR": "1"}), + (False, None), + ), ids=("on", "off"), ) def test_run_playbook_github(result: bool, env: dict[str, str]) -> None: @@ -192,3 +196,41 @@ def test_run_playbook_github(result: bool, env: dict[str, str]) -> None: "Package installs should not use latest" ) assert (expected in result_gh.stderr) is result + + +def test_run_role_identified(local_test_dir: Path) -> None: + """Test that role name is identified correctly.""" + cwd = local_test_dir + + env = os.environ.copy() + env["ANSIBLE_ROLES_PATH"] = os.path.realpath( + (cwd / "../examples/roles/role_detection").resolve(), + ) + result = run_ansible_lint( + Path("roles/role_detection/foo/defaults/main.yml"), + cwd=cwd, + env=env, + ) + assert result.returncode == RC.SUCCESS + + +def test_run_role_identified_prefix_missing(local_test_dir: Path) -> None: + """Test that role name is identified correctly, with prefix violations.""" + cwd = local_test_dir + + env = os.environ.copy() + env["ANSIBLE_ROLES_PATH"] = os.path.realpath( + (cwd / "../examples/roles/role_detection/base").resolve(), + ) + result = run_ansible_lint( + Path("roles/role_detection/base/bar/defaults/main.yml"), + cwd=cwd, + env=env, + ) + assert result.returncode == RC.VIOLATIONS_FOUND + assert ( + "Variables names from within roles should use bar_ as a prefix" in result.stdout + ) + assert ( + "Variables names from within roles should use bar_ as a prefix" in result.stdout + ) diff --git a/test/test_config.py b/test/test_config.py index 51a09b0..4c4ff5a 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,4 +1,5 @@ """Tests for config module.""" + from ansiblelint.config import PROFILES from ansiblelint.rules import RulesCollection diff --git a/test/test_constants.py b/test/test_constants.py index 52b297a..ad957e2 100644 --- a/test/test_constants.py +++ b/test/test_constants.py @@ -1,4 +1,5 @@ """Tests for constants module.""" + from ansiblelint.constants import States diff --git a/test/test_dependencies_in_meta.py b/test/test_dependencies_in_meta.py index 44007b7..0206164 100644 --- a/test/test_dependencies_in_meta.py +++ b/test/test_dependencies_in_meta.py @@ -1,4 +1,5 @@ """Tests about dependencies in meta.""" + from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner diff --git a/test/test_errors.py b/test/test_errors.py new file mode 100644 index 0000000..69b7fe8 --- /dev/null +++ b/test/test_errors.py @@ -0,0 +1,25 @@ +"""Test ansiblelint.errors.""" + +import pytest + +from ansiblelint.errors import MatchError + + +def test_matcherror() -> None: + """.""" + match = MatchError("foo", lineno=1, column=2) + with pytest.raises(TypeError): + assert match <= 0 + + assert match != 0 + + assert match.position == "1:2" + + match2 = MatchError("foo", lineno=1) + assert match2.position == "1" + + # str and repr are for the moment the same + assert str(match) == repr(match) + + # tests implicit level + assert match.level == "warning" diff --git a/test/test_examples.py b/test/test_examples.py index 2842930..7840360 100644 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -1,4 +1,5 @@ """Assure samples produced desire outcomes.""" + import pytest from ansiblelint.app import get_app @@ -17,31 +18,37 @@ def test_example(default_rules_collection: RulesCollection) -> None: @pytest.mark.parametrize( - ("filename", "line", "column"), + ("filename", "expected_results"), ( pytest.param( "examples/playbooks/syntax-error-string.yml", - 6, - 7, - id="syntax-error", + [("syntax-check[unknown-module]", 6, 7)], + id="0", + ), + pytest.param( + "examples/playbooks/syntax-error.yml", + [("syntax-check[specific]", 2, 3)], + id="1", ), - pytest.param("examples/playbooks/syntax-error.yml", 2, 3, id="syntax-error"), ), ) def test_example_syntax_error( default_rules_collection: RulesCollection, filename: str, - line: int, - column: int, + expected_results: list[tuple[str, int | None, int | None]], ) -> None: """Validates that loading valid YAML string produce error.""" result = Runner(filename, rules=default_rules_collection).run() - assert len(result) == 1 - assert result[0].rule.id == "syntax-check" - # This also ensures that line and column numbers start at 1, so they - # match what editors will show (or output from other linters) - assert result[0].lineno == line - assert result[0].column == column + assert len(result) == len(expected_results) + for i, expected in enumerate(expected_results): + if expected[0] is not None: + assert result[i].tag == expected[0] + # This also ensures that line and column numbers start at 1, so they + # match what editors will show (or output from other linters) + if expected[1] is not None: + assert result[i].lineno == expected[1] + if expected[2] is not None: + assert result[i].column == expected[2] def test_example_custom_module(default_rules_collection: RulesCollection) -> None: @@ -67,7 +74,7 @@ def test_vault_partial( default_rules_collection: RulesCollection, caplog: pytest.LogCaptureFixture, ) -> None: - """Check ability to precess files that container !vault inside.""" + """Check ability to process files that container !vault inside.""" result = Runner( "examples/playbooks/vars/vault_partial.yml", rules=default_rules_collection, diff --git a/test/test_file_path_evaluation.py b/test/test_file_path_evaluation.py index b31f923..69f02bb 100644 --- a/test/test_file_path_evaluation.py +++ b/test/test_file_path_evaluation.py @@ -1,4 +1,5 @@ """Testing file path evaluation when using import_tasks / include_tasks.""" + from __future__ import annotations import textwrap @@ -42,14 +43,14 @@ LAYOUT_IMPORTS: dict[str, str] = { "tasks/subtasks/subtask_1.yml": textwrap.dedent( """\ --- - - name: subtask_1 | From subtask 1 import subtask 2 + - name: subtasks | subtask_1 | From subtask 1 import subtask 2 ansible.builtin.import_tasks: tasks/subtasks/subtask_2.yml """, ), "tasks/subtasks/subtask_2.yml": textwrap.dedent( """\ --- - - name: subtask_2 | From subtask 2 do something + - name: subtasks | subtask_2 | From subtask 2 do something debug: # <-- expected to raise fqcn[action-core] msg: | Something... @@ -86,14 +87,14 @@ LAYOUT_INCLUDES: dict[str, str] = { "tasks/subtasks/subtask_1.yml": textwrap.dedent( """\ --- - - name: subtask_1 | From subtask 1 import subtask 2 + - name: subtasks | subtask_1 | From subtask 1 import subtask 2 ansible.builtin.include_tasks: tasks/subtasks/subtask_2.yml """, ), "tasks/subtasks/subtask_2.yml": textwrap.dedent( """\ --- - - name: subtask_2 | From subtask 2 do something + - name: subtasks | subtask_2 | From subtask 2 do something debug: # <-- expected to raise fqcn[action-core] msg: | Something... @@ -105,8 +106,8 @@ LAYOUT_INCLUDES: dict[str, str] = { @pytest.mark.parametrize( "ansible_project_layout", ( - pytest.param(LAYOUT_IMPORTS, id="using only import_tasks"), - pytest.param(LAYOUT_INCLUDES, id="using only include_tasks"), + pytest.param(LAYOUT_IMPORTS, id="using-only-import_tasks"), + pytest.param(LAYOUT_INCLUDES, id="using-only-include_tasks"), ), ) def test_file_path_evaluation( diff --git a/test/test_file_utils.py b/test/test_file_utils.py index b7b9115..74f1934 100644 --- a/test/test_file_utils.py +++ b/test/test_file_utils.py @@ -1,4 +1,5 @@ """Tests for file utility functions.""" + from __future__ import annotations import copy @@ -59,7 +60,7 @@ def test_expand_path_vars(monkeypatch: MonkeyPatch) -> None: pytest.param(Path("$TEST_PATH"), "/test/path", id="pathlib.Path"), pytest.param("$TEST_PATH", "/test/path", id="str"), pytest.param(" $TEST_PATH ", "/test/path", id="stripped-str"), - pytest.param("~", os.path.expanduser("~"), id="home"), # noqa: PTH:111 + pytest.param("~", os.path.expanduser("~"), id="home"), # noqa: PTH111 ), ) def test_expand_paths_vars( @@ -236,7 +237,7 @@ def test_discover_lintables_umlaut(monkeypatch: MonkeyPatch) -> None: "tasks", id="33", ), # content should determine is tasks - pytest.param("examples/collection/galaxy.yml", "galaxy", id="34"), + pytest.param("examples/.collection/galaxy.yml", "galaxy", id="34"), pytest.param("examples/meta/runtime.yml", "meta-runtime", id="35"), pytest.param("examples/meta/changelogs/changelog.yaml", "changelog", id="36"), pytest.param("examples/inventory/inventory.yml", "inventory", id="37"), @@ -258,6 +259,12 @@ def test_discover_lintables_umlaut(monkeypatch: MonkeyPatch) -> None: "playbook", id="43", ), # content should determine it as a play + pytest.param( + "plugins/modules/fake_module.py", + "plugin", + id="44", + ), + pytest.param("examples/meta/changelogs/changelog.yml", "changelog", id="45"), ), ) def test_kinds(path: str, kind: FileType) -> None: diff --git a/test/test_formatter.py b/test/test_formatter.py index 68f0508..c41f673 100644 --- a/test/test_formatter.py +++ b/test/test_formatter.py @@ -1,4 +1,5 @@ """Test for output formatter.""" + # Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,10 +24,12 @@ import pathlib from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.formatters import Formatter -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules import AnsibleLintRule, RulesCollection +collection = RulesCollection() rule = AnsibleLintRule() rule.id = "TCF0001" +collection.register(rule) formatter = Formatter(pathlib.Path.cwd(), display_relative_path=True) # These details would generate a rich rendering error if not escaped: DETAILS = "Some [/tmp/foo] details." diff --git a/test/test_formatter_base.py b/test/test_formatter_base.py index 5cc86b8..462fc62 100644 --- a/test/test_formatter_base.py +++ b/test/test_formatter_base.py @@ -1,4 +1,5 @@ """Tests related to base formatter.""" + from __future__ import annotations from pathlib import Path @@ -12,12 +13,18 @@ from ansiblelint.formatters import BaseFormatter @pytest.mark.parametrize( ("base_dir", "relative_path"), ( - (None, True), - ("/whatever", False), - (Path("/whatever"), False), + pytest.param(None, True, id="0"), + pytest.param("/whatever", False, id="1"), + pytest.param(Path("/whatever"), False, id="2"), + ), +) +@pytest.mark.parametrize( + "path", + ( + pytest.param("/whatever/string", id="a"), + pytest.param(Path("/whatever/string"), id="b"), ), ) -@pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) def test_base_formatter_when_base_dir( base_dir: Any, relative_path: bool, @@ -28,18 +35,15 @@ def test_base_formatter_when_base_dir( base_formatter = BaseFormatter(base_dir, relative_path) # type: ignore[var-annotated] # When - output_path = ( - base_formatter._format_path( # pylint: disable=protected-access # noqa: SLF001 - path, - ) + output_path = base_formatter._format_path( # noqa: SLF001 + path, ) # Then - assert isinstance(output_path, (str, Path)) - # pylint: disable=protected-access + assert isinstance(output_path, str | Path) assert base_formatter.base_dir is None or isinstance( base_formatter.base_dir, - (str, Path), + str | Path, ) assert output_path == path @@ -47,11 +51,17 @@ def test_base_formatter_when_base_dir( @pytest.mark.parametrize( "base_dir", ( - Path("/whatever"), - "/whatever", + pytest.param(Path("/whatever"), id="0"), + pytest.param("/whatever", id="1"), + ), +) +@pytest.mark.parametrize( + "path", + ( + pytest.param("/whatever/string", id="a"), + pytest.param(Path("/whatever/string"), id="b"), ), ) -@pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) def test_base_formatter_when_base_dir_is_given_and_relative_is_true( path: str | Path, base_dir: str | Path, @@ -61,13 +71,11 @@ def test_base_formatter_when_base_dir_is_given_and_relative_is_true( base_formatter = BaseFormatter(base_dir, True) # type: ignore[var-annotated] # When - # pylint: disable=protected-access output_path = base_formatter._format_path(path) # noqa: SLF001 # Then - assert isinstance(output_path, (str, Path)) - # pylint: disable=protected-access - assert isinstance(base_formatter.base_dir, (str, Path)) + assert isinstance(output_path, str | Path) + assert isinstance(base_formatter.base_dir, str | Path) assert output_path == Path(path).name diff --git a/test/test_formatter_json.py b/test/test_formatter_json.py index 25aa5f5..763c843 100644 --- a/test/test_formatter_json.py +++ b/test/test_formatter_json.py @@ -1,4 +1,5 @@ """Test the codeclimate JSON formatter.""" + from __future__ import annotations import json @@ -11,7 +12,7 @@ import pytest from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.formatters import CodeclimateJSONFormatter -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules import AnsibleLintRule, RulesCollection class TestCodeclimateJSONFormatter: @@ -20,12 +21,14 @@ class TestCodeclimateJSONFormatter: rule = AnsibleLintRule() matches: list[MatchError] = [] formatter: CodeclimateJSONFormatter | None = None + collection = RulesCollection() def setup_class(self) -> None: """Set up few MatchError objects.""" self.rule = AnsibleLintRule() self.rule.id = "TCF0001" self.rule.severity = "VERY_HIGH" + self.collection.register(self.rule) self.matches = [] self.matches.append( MatchError( @@ -51,7 +54,7 @@ class TestCodeclimateJSONFormatter: display_relative_path=True, ) - def test_format_list(self) -> None: + def test_json_format_list(self) -> None: """Test if the return value is a string.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) assert isinstance(self.formatter.format_result(self.matches), str) @@ -64,10 +67,10 @@ class TestCodeclimateJSONFormatter: # https://github.com/ansible/ansible-navigator/issues/1490 assert "\n" not in output - def test_single_match(self) -> None: + def test_json_single_match(self) -> None: """Test negative case. Only lists are allowed. Otherwise a RuntimeError will be raised.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) - with pytest.raises(RuntimeError): + with pytest.raises(TypeError): self.formatter.format_result(self.matches[0]) # type: ignore[arg-type] def test_result_is_list(self) -> None: diff --git a/test/test_formatter_sarif.py b/test/test_formatter_sarif.py index 026d336..982bb6e 100644 --- a/test/test_formatter_sarif.py +++ b/test/test_formatter_sarif.py @@ -1,4 +1,5 @@ """Test the codeclimate JSON formatter.""" + from __future__ import annotations import json @@ -13,54 +14,75 @@ import pytest from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.formatters import SarifFormatter -from ansiblelint.rules import AnsibleLintRule +from ansiblelint.rules import AnsibleLintRule, RulesCollection class TestSarifFormatter: """Unit test for SarifFormatter.""" - rule = AnsibleLintRule() + rule1 = AnsibleLintRule() + rule2 = AnsibleLintRule() matches: list[MatchError] = [] formatter: SarifFormatter | None = None + collection = RulesCollection() + collection.register(rule1) + collection.register(rule2) def setup_class(self) -> None: """Set up few MatchError objects.""" - self.rule = AnsibleLintRule() - self.rule.id = "TCF0001" - self.rule.severity = "VERY_HIGH" - self.rule.description = "This is the rule description." - self.rule.link = "https://rules/help#TCF0001" - self.rule.tags = ["tag1", "tag2"] - self.matches = [] - self.matches.append( - MatchError( - message="message", - lineno=1, - column=10, - details="details", - lintable=Lintable("filename.yml", content=""), - rule=self.rule, - tag="yaml[test]", - ), - ) - self.matches.append( - MatchError( - message="message", - lineno=2, - details="", - lintable=Lintable("filename.yml", content=""), - rule=self.rule, - tag="yaml[test]", - ), + self.rule1.id = "TCF0001" + self.rule1.severity = "VERY_HIGH" + self.rule1.description = "This is the rule description." + self.rule1.link = "https://rules/help#TCF0001" + self.rule1.tags = ["tag1", "tag2"] + + self.rule2.id = "TCF0002" + self.rule2.severity = "MEDIUM" + self.rule2.link = "https://rules/help#TCF0002" + self.rule2.tags = ["tag3", "tag4"] + + self.matches.extend( + [ + MatchError( + message="message1", + lineno=1, + column=10, + details="details1", + lintable=Lintable("filename1.yml", content=""), + rule=self.rule1, + tag="yaml[test1]", + ignored=False, + ), + MatchError( + message="message2", + lineno=2, + details="", + lintable=Lintable("filename2.yml", content=""), + rule=self.rule1, + tag="yaml[test2]", + ignored=True, + ), + MatchError( + message="message3", + lineno=666, + column=667, + details="details3", + lintable=Lintable("filename3.yml", content=""), + rule=self.rule2, + tag="yaml[test3]", + ignored=False, + ), + ], ) + self.formatter = SarifFormatter(pathlib.Path.cwd(), display_relative_path=True) - def test_format_list(self) -> None: + def test_sarif_format_list(self) -> None: """Test if the return value is a string.""" assert isinstance(self.formatter, SarifFormatter) assert isinstance(self.formatter.format_result(self.matches), str) - def test_result_is_json(self) -> None: + def test_sarif_result_is_json(self) -> None: """Test if returned string value is a JSON.""" assert isinstance(self.formatter, SarifFormatter) output = self.formatter.format_result(self.matches) @@ -68,17 +90,22 @@ class TestSarifFormatter: # https://github.com/ansible/ansible-navigator/issues/1490 assert "\n" not in output - def test_single_match(self) -> None: + def test_sarif_single_match(self) -> None: """Test negative case. Only lists are allowed. Otherwise, a RuntimeError will be raised.""" assert isinstance(self.formatter, SarifFormatter) - with pytest.raises(RuntimeError): + with pytest.raises(TypeError): self.formatter.format_result(self.matches[0]) # type: ignore[arg-type] - def test_result_is_list(self) -> None: - """Test if the return SARIF object contains the results with length of 2.""" + def test_sarif_format(self) -> None: + """Test if the return SARIF object contains the expected results.""" assert isinstance(self.formatter, SarifFormatter) sarif = json.loads(self.formatter.format_result(self.matches)) - assert len(sarif["runs"][0]["results"]) == 2 + assert len(sarif["runs"][0]["results"]) == 3 + for result in sarif["runs"][0]["results"]: + # Ensure all reported entries have a level + assert "level" in result + # Ensure reported levels are either error or warning + assert result["level"] in ("error", "warning") def test_validate_sarif_schema(self) -> None: """Test if the returned JSON is a valid SARIF report.""" @@ -90,16 +117,18 @@ class TestSarifFormatter: assert driver["name"] == SarifFormatter.TOOL_NAME assert driver["informationUri"] == SarifFormatter.TOOL_URL rules = driver["rules"] - assert len(rules) == 1 + assert len(rules) == 3 assert rules[0]["id"] == self.matches[0].tag assert rules[0]["name"] == self.matches[0].tag assert rules[0]["shortDescription"]["text"] == self.matches[0].message - assert rules[0]["defaultConfiguration"]["level"] == "error" + assert rules[0]["defaultConfiguration"][ + "level" + ] == SarifFormatter.get_sarif_rule_severity_level(self.matches[0].rule) assert rules[0]["help"]["text"] == self.matches[0].rule.description assert rules[0]["properties"]["tags"] == self.matches[0].rule.tags assert rules[0]["helpUri"] == self.matches[0].rule.url results = sarif["runs"][0]["results"] - assert len(results) == 2 + assert len(results) == 3 for i, result in enumerate(results): assert result["ruleId"] == self.matches[i].tag assert ( @@ -126,6 +155,9 @@ class TestSarifFormatter: "startColumn" not in result["locations"][0]["physicalLocation"]["region"] ) + assert result["level"] == SarifFormatter.get_sarif_result_severity_level( + self.matches[i], + ) assert sarif["runs"][0]["originalUriBaseIds"][SarifFormatter.BASE_URI_ID]["uri"] assert results[0]["message"]["text"] == self.matches[0].details assert results[1]["message"]["text"] == self.matches[1].message @@ -151,8 +183,8 @@ def test_sarif_parsable_ignored() -> None: @pytest.mark.parametrize( ("file", "return_code"), ( - pytest.param("examples/playbooks/valid.yml", 0), - pytest.param("playbook.yml", 2), + pytest.param("examples/playbooks/valid.yml", 0, id="0"), + pytest.param("playbook.yml", 2, id="1"), ), ) def test_sarif_file(file: str, return_code: int) -> None: @@ -168,12 +200,12 @@ def test_sarif_file(file: str, return_code: int) -> None: result = subprocess.run([*cmd, file], check=False, capture_output=True) assert result.returncode == return_code assert os.path.exists(output_file.name) # noqa: PTH110 - assert os.path.getsize(output_file.name) > 0 + assert pathlib.Path(output_file.name).stat().st_size > 0 @pytest.mark.parametrize( ("file", "return_code"), - (pytest.param("examples/playbooks/valid.yml", 0),), + (pytest.param("examples/playbooks/valid.yml", 0, id="0"),), ) def test_sarif_file_creates_it_if_none_exists(file: str, return_code: int) -> None: """Test ability to create sarif file if none exists and dump output to it (--sarif-file).""" @@ -188,5 +220,5 @@ def test_sarif_file_creates_it_if_none_exists(file: str, return_code: int) -> No result = subprocess.run([*cmd, file], check=False, capture_output=True) assert result.returncode == return_code assert os.path.exists(sarif_file_name) # noqa: PTH110 - assert os.path.getsize(sarif_file_name) > 0 + assert pathlib.Path(sarif_file_name).stat().st_size > 0 pathlib.Path.unlink(pathlib.Path(sarif_file_name)) diff --git a/test/test_import_include_role.py b/test/test_import_include_role.py index bc3fdbe..b221646 100644 --- a/test/test_import_include_role.py +++ b/test/test_import_include_role.py @@ -1,4 +1,5 @@ """Tests related to role imports.""" + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/test/test_import_playbook.py b/test/test_import_playbook.py index 66d8763..63c91d2 100644 --- a/test/test_import_playbook.py +++ b/test/test_import_playbook.py @@ -1,4 +1,5 @@ """Test ability to import playbooks.""" + from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner @@ -16,3 +17,29 @@ def test_task_hook_import_playbook(default_rules_collection: RulesCollection) -> assert "Commands should not change things" in results_text assert "[name]" in results_text assert "All tasks should be named" in results_text + + +def test_import_playbook_from_collection( + default_rules_collection: RulesCollection, +) -> None: + """Assures import_playbook from collection.""" + playbook_path = "examples/playbooks/test_import_playbook.yml" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + assert len(runner.lintables) == 1 + assert len(results) == 0 + + +def test_import_playbook_invalid( + default_rules_collection: RulesCollection, +) -> None: + """Assures import_playbook from collection.""" + playbook_path = "examples/playbooks/test_import_playbook_invalid.yml" + runner = Runner(playbook_path, rules=default_rules_collection) + results = runner.run() + + assert len(runner.lintables) == 1 + assert len(results) == 1 + assert results[0].tag == "syntax-check[specific]" + assert results[0].lineno == 2 diff --git a/test/test_import_tasks.py b/test/test_import_tasks.py index aec1c25..ceb5c28 100644 --- a/test/test_import_tasks.py +++ b/test/test_import_tasks.py @@ -1,4 +1,5 @@ """Test related to import of invalid files.""" + import pytest from ansiblelint.rules import RulesCollection @@ -6,24 +7,28 @@ from ansiblelint.runner import Runner @pytest.mark.parametrize( - "playbook_path", + ("playbook_path", "lintable_count", "match_count"), ( pytest.param( "examples/playbooks/test_import_with_conflicting_action_statements.yml", + 2, + 4, id="0", ), - pytest.param("examples/playbooks/test_import_with_malformed.yml", id="1"), + pytest.param("examples/playbooks/test_import_with_malformed.yml", 2, 2, id="1"), ), ) def test_import_tasks( default_rules_collection: RulesCollection, playbook_path: str, + lintable_count: int, + match_count: int, ) -> None: """Assures import_playbook includes are recognized.""" runner = Runner(playbook_path, rules=default_rules_collection) results = runner.run() - assert len(runner.lintables) == 1 - assert len(results) == 1 + assert len(runner.lintables) == lintable_count + assert len(results) == match_count # Assures we detected the issues from imported file - assert results[0].rule.id == "syntax-check" + assert results[0].rule.id in ("syntax-check", "load-failure") diff --git a/test/test_include_miss_file_with_role.py b/test/test_include_miss_file_with_role.py index 6834758..599928e 100644 --- a/test/test_include_miss_file_with_role.py +++ b/test/test_include_miss_file_with_role.py @@ -1,4 +1,5 @@ """Tests related to inclusions.""" + import pytest from _pytest.logging import LogCaptureFixture diff --git a/test/test_internal_rules.py b/test/test_internal_rules.py index b949238..e1cc69e 100644 --- a/test/test_internal_rules.py +++ b/test/test_internal_rules.py @@ -1,8 +1,35 @@ """Tests for internal rules.""" + +import pytest + from ansiblelint._internal.rules import BaseRule +from ansiblelint.rules import RulesCollection +from ansiblelint.runner import Runner def test_base_rule_url() -> None: """Test that rule URL is set to expected value.""" rule = BaseRule() - assert rule.url == "https://ansible-lint.readthedocs.io/rules/" + assert rule.url == "https://ansible.readthedocs.io/projects/lint/rules/" + + +@pytest.mark.parametrize( + ("path"), + ( + pytest.param( + "examples/playbooks/incorrect_module_args.yml", + id="playbook", + ), + ), +) +def test_incorrect_module_args( + path: str, + default_rules_collection: RulesCollection, +) -> None: + """Check that we fail when file encoding is wrong.""" + runner = Runner(path, rules=default_rules_collection) + matches = runner.run() + assert len(matches) == 1, matches + assert matches[0].rule.id == "load-failure" + assert "Failed to find required 'name' key in include_role" in matches[0].message + assert matches[0].tag == "internal-error" diff --git a/test/test_lint_rule.py b/test/test_lint_rule.py index 2e13aa2..d0edbe9 100644 --- a/test/test_lint_rule.py +++ b/test/test_lint_rule.py @@ -1,4 +1,5 @@ """Tests for lintable.""" + # Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -18,13 +19,12 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. - -from test.rules.fixtures import ematcher, raw_task - import pytest from ansiblelint.file_utils import Lintable +from .rules.fixtures import ematcher, raw_task + @pytest.fixture(name="lintable") def fixture_lintable() -> Lintable: diff --git a/test/test_list_rules.py b/test/test_list_rules.py index dab16e3..85ef53f 100644 --- a/test/test_list_rules.py +++ b/test/test_list_rules.py @@ -18,6 +18,13 @@ def test_list_rules_includes_opt_in_rules(project_path: Path) -> None: assert ("opt-in" in result_list_rules.stdout) is True +def test_list_rules_includes_autofix() -> None: + """Checks that listing rules also includes the autofix label for applicable rules.""" + result_list_rules = run_ansible_lint("--list-rules") + + assert ("autofix" in result_list_rules.stdout) is True + + @pytest.mark.parametrize( ("result", "returncode", "format_string"), ( diff --git a/test/test_load_failure.py b/test/test_load_failure.py index 98d178f..72112d6 100644 --- a/test/test_load_failure.py +++ b/test/test_load_failure.py @@ -1,4 +1,5 @@ """Tests for LoadFailureRule.""" + import pytest from ansiblelint.rules import RulesCollection diff --git a/test/test_loaders.py b/test/test_loaders.py index be12cfd..6e8d66b 100644 --- a/test/test_loaders.py +++ b/test/test_loaders.py @@ -1,4 +1,5 @@ """Tests for loaders submodule.""" + import os import tempfile import uuid @@ -31,7 +32,7 @@ def test_load_ignore_txt_default_success() -> None: _ignore_file.write( dedent( """ - # See https://ansible-lint.readthedocs.io/configuring/#ignoring-rules-for-entire-files + # See https://ansible.readthedocs.io/projects/lint/configuring/#ignoring-rules-for-entire-files playbook2.yml package-latest # comment playbook2.yml foo-bar """, diff --git a/test/test_local_content.py b/test/test_local_content.py index 8455aaf..63472c2 100644 --- a/test/test_local_content.py +++ b/test/test_local_content.py @@ -1,4 +1,5 @@ """Test playbooks with local content.""" + from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner diff --git a/test/test_main.py b/test/test_main.py index 870926f..e7258ee 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -1,4 +1,5 @@ """Tests related to ansiblelint.__main__ module.""" + import os import shutil import subprocess @@ -10,6 +11,7 @@ import pytest from pytest_mock import MockerFixture from ansiblelint.config import get_version_warning +from ansiblelint.constants import RC @pytest.mark.parametrize( @@ -52,9 +54,9 @@ def test_call_from_outside_venv(expected_warning: bool) -> None: @pytest.mark.parametrize( ("ver_diff", "found", "check", "outlen"), ( - ("v1.2.2", True, "pre-release", 1), - ("v1.2.3", False, "", 1), - ("v1.2.4", True, "new release", 2), + pytest.param("v1.2.2", True, "pre-release", 1, id="0"), + pytest.param("v1.2.3", False, "", 1, id="1"), + pytest.param("v1.2.4", True, "new release", 2, id="2"), ), ) def test_get_version_warning( @@ -82,3 +84,48 @@ def test_get_version_warning( else: assert check in msg assert len(msg.split("\n")) == outlen + + +def test_get_version_warning_no_pip(mocker: MockerFixture) -> None: + """Test that we do not display any message if install method is not pip.""" + mocker.patch("ansiblelint.config.guess_install_method", return_value="") + assert get_version_warning() == "" + + +@pytest.mark.parametrize( + ("lintable"), + ( + pytest.param("examples/playbooks/nodeps.yml", id="1"), + pytest.param("examples/playbooks/nodeps2.yml", id="2"), + ), +) +def test_nodeps(lintable: str) -> None: + """Asserts ability to be called w/ or w/o venv activation.""" + env = os.environ.copy() + env["ANSIBLE_LINT_NODEPS"] = "1" + py_path = Path(sys.executable).parent + proc = subprocess.run( + [str(py_path / "ansible-lint"), lintable], + check=False, + capture_output=True, + text=True, + env=env, + ) + assert proc.returncode == 0, proc + + +def test_broken_ansible_cfg() -> None: + """Asserts behavior when encountering broken ansible.cfg files.""" + py_path = Path(sys.executable).parent + proc = subprocess.run( + [str(py_path / "ansible-lint"), "--version"], + check=False, + capture_output=True, + text=True, + cwd="test/fixtures/broken-ansible.cfg", + ) + assert proc.returncode == RC.INVALID_CONFIG, proc + assert ( + "Invalid type for configuration option setting: CACHE_PLUGIN_TIMEOUT" + in proc.stderr + ) diff --git a/test/test_matcherrror.py b/test/test_matcherrror.py index 03d9cbd..5b67e23 100644 --- a/test/test_matcherrror.py +++ b/test/test_matcherrror.py @@ -1,7 +1,8 @@ """Tests for MatchError.""" import operator -from typing import Any, Callable +from collections.abc import Callable +from typing import Any import pytest @@ -121,18 +122,18 @@ class TestMatchErrorCompare: @pytest.mark.parametrize( "other", ( - None, - "foo", - 42, - Exception("foo"), + pytest.param(None, id="none"), + pytest.param("foo", id="str"), + pytest.param(42, id="int"), + pytest.param(Exception("foo"), id="exc"), ), ids=repr, ) @pytest.mark.parametrize( ("operation", "operator_char"), ( - pytest.param(operator.le, "<=", id="<="), - pytest.param(operator.gt, ">", id=">"), + pytest.param(operator.le, "<=", id="le"), + pytest.param(operator.gt, ">", id="gt"), ), ) def test_matcherror_compare_no_other_fallback( @@ -143,12 +144,9 @@ def test_matcherror_compare_no_other_fallback( """Check that MatchError comparison with other types causes TypeError.""" expected_error = ( r"^(" - r"unsupported operand type\(s\) for {operator!s}:|" - r"'{operator!s}' not supported between instances of" - r") 'MatchError' and '{other_type!s}'$".format( - other_type=type(other).__name__, - operator=operator_char, - ) + rf"unsupported operand type\(s\) for {operator_char!s}:|" + rf"'{operator_char!s}' not supported between instances of" + rf") 'MatchError' and '{type(other).__name__!s}'$" ) with pytest.raises(TypeError, match=expected_error): operation(MatchError("foo"), other) @@ -157,21 +155,20 @@ def test_matcherror_compare_no_other_fallback( @pytest.mark.parametrize( "other", ( - None, - "foo", - 42, - Exception("foo"), - DummyTestObject(), + pytest.param(None, id="none"), + pytest.param("foo", id="str"), + pytest.param(42, id="int"), + pytest.param(Exception("foo"), id="exception"), + pytest.param(DummyTestObject(), id="obj"), ), ids=repr, ) @pytest.mark.parametrize( ("operation", "expected_value"), ( - (operator.eq, False), - (operator.ne, True), + pytest.param(operator.eq, False, id="eq"), + pytest.param(operator.ne, True, id="ne"), ), - ids=("==", "!="), ) def test_matcherror_compare_with_other_fallback( other: object, @@ -185,16 +182,15 @@ def test_matcherror_compare_with_other_fallback( @pytest.mark.parametrize( ("operation", "expected_value"), ( - (operator.eq, "EQ_SENTINEL"), - (operator.ne, "NE_SENTINEL"), + pytest.param(operator.eq, "EQ_SENTINEL", id="eq"), + pytest.param(operator.ne, "NE_SENTINEL", id="ne"), # NOTE: these are swapped because when we do `x < y`, and `x.__lt__(y)` # NOTE: returns `NotImplemented`, Python will reverse the check into # NOTE: `y > x`, and so `y.__gt__(x) is called. # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__ - (operator.lt, "GT_SENTINEL"), - (operator.gt, "LT_SENTINEL"), + pytest.param(operator.lt, "GT_SENTINEL", id="gt"), + pytest.param(operator.gt, "LT_SENTINEL", id="lt"), ), - ids=("==", "!=", "<", ">"), ) def test_matcherror_compare_with_dummy_sentinel( operation: Callable[..., bool], diff --git a/test/test_mockings.py b/test/test_mockings.py index 0e8d77a..417d5d5 100644 --- a/test/test_mockings.py +++ b/test/test_mockings.py @@ -1,18 +1,18 @@ """Test mockings module.""" -from typing import Any + +from pathlib import Path import pytest from ansiblelint._mockings import _make_module_stub -from ansiblelint.config import options +from ansiblelint.config import Options from ansiblelint.constants import RC -def test_make_module_stub(mocker: Any) -> None: +def test_make_module_stub(config_options: Options) -> None: """Test make module stub.""" - mocker.patch("ansiblelint.config.options.cache_dir", return_value=".") - assert options.cache_dir is not None + config_options.cache_dir = Path() # current directory with pytest.raises(SystemExit) as exc: - _make_module_stub(module_name="", options=options) + _make_module_stub(module_name="", options=config_options) assert exc.type == SystemExit assert exc.value.code == RC.INVALID_CONFIG diff --git a/test/test_profiles.py b/test/test_profiles.py index a40382c..a1d9865 100644 --- a/test/test_profiles.py +++ b/test/test_profiles.py @@ -1,4 +1,5 @@ """Tests for the --profile feature.""" + import platform import subprocess import sys @@ -21,7 +22,7 @@ def test_profile_min() -> None: filter_rules_with_profile(collection.rules, "min") assert ( - len(collection.rules) == 3 + len(collection.rules) == 4 ), "Failed to unload rule that is not part of 'min' profile." diff --git a/test/test_requirements.py b/test/test_requirements.py new file mode 100644 index 0000000..0703d34 --- /dev/null +++ b/test/test_requirements.py @@ -0,0 +1,18 @@ +"""Tests requirements module.""" + +from ansible_compat.runtime import Runtime + +from ansiblelint.requirements import Reqs + + +def test_reqs() -> None: + """Performs basic testing of Reqs class.""" + reqs = Reqs() + runtime = Runtime() + assert "ansible-core" in reqs + # checks that this ansible core version is not supported: + assert reqs.matches("ansible-core", "0.0") is False + # assert that invalid package name + assert reqs.matches("this-package-does-not-exist", "0.0") is False + # check the current ansible core version is supported: + assert reqs.matches("ansible-core", runtime.version) diff --git a/test/test_rule_properties.py b/test/test_rule_properties.py index 7db3afd..3e5eb3e 100644 --- a/test/test_rule_properties.py +++ b/test/test_rule_properties.py @@ -1,4 +1,5 @@ """Tests related to rule properties.""" + from ansiblelint.rules import RulesCollection diff --git a/test/test_rules_collection.py b/test/test_rules_collection.py index 66c69ec..44317fe 100644 --- a/test/test_rules_collection.py +++ b/test/test_rules_collection.py @@ -1,4 +1,5 @@ """Tests for rule collection class.""" + # Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,14 +24,17 @@ from __future__ import annotations import collections import re from pathlib import Path +from typing import TYPE_CHECKING import pytest -from ansiblelint.config import options from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection from ansiblelint.testing import run_ansible_lint +if TYPE_CHECKING: + from ansiblelint.config import Options + @pytest.fixture(name="test_rules_collection") def fixture_test_rules_collection() -> RulesCollection: @@ -153,12 +157,12 @@ def test_rich_rule_listing() -> None: assert rule.description[:30] in result.stdout -def test_rules_id_format() -> None: +def test_rules_id_format(config_options: Options) -> None: """Assure all our rules have consistent format.""" rule_id_re = re.compile("^[a-z-]{4,30}$") rules = RulesCollection( [Path("./src/ansiblelint/rules").resolve()], - options=options, + options=config_options, conditional=False, ) keys: set[str] = set() @@ -171,5 +175,5 @@ def test_rules_id_format() -> None: rule.help or rule.description or rule.__doc__ ), f"Rule {rule.id} must have at least one of: .help, .description, .__doc__" assert "yaml" in keys, "yaml rule is missing" - assert len(rules) == 49 # update this number when adding new rules! + assert len(rules) == 50 # update this number when adding new rules! assert len(keys) == len(rules), "Duplicate rule ids?" diff --git a/test/test_runner.py b/test/test_runner.py index e89cee1..aa76b65 100644 --- a/test/test_runner.py +++ b/test/test_runner.py @@ -1,4 +1,5 @@ """Tests for runner submodule.""" + # Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -48,7 +49,7 @@ LOTS_OF_WARNINGS_PLAYBOOK = Path("examples/playbooks/lots_of_warnings.yml").reso pytest.param( LOTS_OF_WARNINGS_PLAYBOOK, [LOTS_OF_WARNINGS_PLAYBOOK], - 992, + 993, id="lots_of_warnings", ), pytest.param(Path("examples/playbooks/become.yml"), [], 0, id="become"), @@ -86,21 +87,23 @@ def test_runner_exclude_paths(default_rules_collection: RulesCollection) -> None assert len(matches) == 0 -@pytest.mark.parametrize(("exclude_path"), ("**/playbooks/*.yml",)) +@pytest.mark.parametrize( + ("exclude_path"), + (pytest.param("**/playbooks_globs/*b.yml", id="1"),), +) def test_runner_exclude_globs( default_rules_collection: RulesCollection, exclude_path: str, ) -> None: """Test that globs work.""" runner = Runner( - "examples/playbooks", + "examples/playbooks_globs", rules=default_rules_collection, exclude_paths=[exclude_path], ) matches = runner.run() - # we expect to find one match from the very few .yaml file we have there (most of them have .yml extension) - assert len(matches) == 1 + assert len(matches) == 0 @pytest.mark.parametrize( @@ -175,6 +178,52 @@ def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> N assert len(run2) == 0 +@pytest.mark.parametrize( + ("filename", "failures", "checked_files_no"), + ( + pytest.param( + "examples/playbooks/common-include-wrong-syntax.yml", + 1, + 1, + id="1", + ), + pytest.param( + "examples/playbooks/common-include-wrong-syntax2.yml", + 1, + 1, + id="2", + ), + pytest.param( + "examples/playbooks/common-include-wrong-syntax3.yml", + 0, + 2, + id="3", + ), + ), +) +def test_include_wrong_syntax( + filename: str, + failures: int, + checked_files_no: int, + default_rules_collection: RulesCollection, +) -> None: + """Ensure that lintables aren't double-checked.""" + checked_files: set[Lintable] = set() + + path = Path(filename).resolve() + runner = Runner( + path, + rules=default_rules_collection, + verbosity=0, + checked_files=checked_files, + ) + result = runner.run() + assert len(runner.checked_files) == checked_files_no + assert len(result) == failures, result + for item in result: + assert item.tag == "syntax-check[no-file]" + + def test_runner_not_found(default_rules_collection: RulesCollection) -> None: """Ensure that lintables aren't double-checked.""" checked_files: set[Lintable] = set() @@ -208,3 +257,16 @@ def test_runner_tmp_file( result = runner.run() assert len(result) == 1 assert result[0].tag == "syntax-check[empty-playbook]" + + +def test_with_full_path(default_rules_collection: RulesCollection) -> None: + """Ensure that lintables include file path starting from home directory.""" + filename = Path("examples/playbooks/deep").absolute() + runner = Runner( + filename, + rules=default_rules_collection, + verbosity=0, + ) + result = runner.run() + assert len(result) == 1 + assert result[0].tag == "name[casing]" diff --git a/test/test_schemas.py b/test/test_schemas.py index 6392241..646a283 100644 --- a/test/test_schemas.py +++ b/test/test_schemas.py @@ -1,16 +1,18 @@ """Test schemas modules.""" + import json import logging +import os import subprocess import sys import urllib +import warnings from pathlib import Path -from time import sleep from typing import Any from unittest.mock import DEFAULT, MagicMock, patch +import license_expression import pytest -import spdx.config from ansiblelint.file_utils import Lintable from ansiblelint.schemas import __file__ as schema_module @@ -18,21 +20,9 @@ from ansiblelint.schemas.__main__ import refresh_schemas from ansiblelint.schemas.main import validate_file_schema schema_path = Path(schema_module).parent -spdx_config_path = Path(spdx.config.__file__).parent - - -def test_refresh_schemas() -> None: - """Test for schema update skip.""" - # This is written as a single test in order to avoid concurrency issues, - # which caused random issues on CI when the two tests run in parallel - # and or in different order. - assert refresh_schemas(min_age_seconds=3600 * 24 * 365 * 10) == 0 - sleep(1) - # this should disable the cache and force an update - assert refresh_schemas(min_age_seconds=0) == 1 - sleep(1) - # should be cached now - assert refresh_schemas(min_age_seconds=10) == 0 +spdx_config_path = ( + Path(license_expression.__file__).parent / "data" / "scancode-licensedb-index.json" +) def urlopen_side_effect(*_args: Any, **kwargs: Any) -> DEFAULT: @@ -59,7 +49,7 @@ def test_request_timeouterror_handling( error_msg = "Simulating handshake operation time out." mock_request.urlopen.side_effect = urllib.error.URLError(TimeoutError(error_msg)) with caplog.at_level(logging.DEBUG): - assert refresh_schemas(min_age_seconds=0) == 1 + assert refresh_schemas(min_age_seconds=0) == 0 mock_request.urlopen.assert_called() assert "Skipped schema refresh due to unexpected exception: " in caplog.text assert error_msg in caplog.text @@ -73,7 +63,7 @@ def test_schema_refresh_cli() -> None: capture_output=True, text=True, ) - assert proc.returncode == 0 + assert proc.returncode == 0, proc def test_validate_file_schema() -> None: @@ -86,24 +76,33 @@ def test_validate_file_schema() -> None: def test_spdx() -> None: """Test that SPDX license identifiers are in sync.""" - _licenses = spdx_config_path / "licenses.json" - license_ids = set() - with _licenses.open(encoding="utf-8") as license_fh: + with spdx_config_path.open(encoding="utf-8") as license_fh: licenses = json.load(license_fh) - for lic in licenses["licenses"]: - if lic.get("isDeprecatedLicenseId"): + for lic in licenses: + if lic.get("is_deprecated"): + continue + lic_id = lic["spdx_license_key"] + if lic_id.startswith("LicenseRef"): continue - license_ids.add(lic["licenseId"]) + license_ids.add(lic_id) galaxy_json = schema_path / "galaxy.json" with galaxy_json.open(encoding="utf-8") as f: schema = json.load(f) spx_enum = schema["$defs"]["SPDXLicenseEnum"]["enum"] if set(spx_enum) != license_ids: - with galaxy_json.open("w", encoding="utf-8") as f: - schema["$defs"]["SPDXLicenseEnum"]["enum"] = sorted(license_ids) - json.dump(schema, f, indent=2) - pytest.fail( - "SPDX license list inside galaxy.json JSON Schema file was updated.", - ) + # In absence of a + if os.environ.get("PIP_CONSTRAINT", "/dev/null") == "/dev/null": + with galaxy_json.open("w", encoding="utf-8") as f: + schema["$defs"]["SPDXLicenseEnum"]["enum"] = sorted(license_ids) + json.dump(schema, f, indent=2) + pytest.fail( + "SPDX license list inside galaxy.json JSON Schema file was updated.", + ) + else: + warnings.warn( + "test_spdx failure was ignored because constraints were not pinned (PIP_CONSTRAINTS). This is expected for py310 and py-devel jobs.", + category=pytest.PytestWarning, + stacklevel=1, + ) diff --git a/test/test_skip_import_playbook.py b/test/test_skip_import_playbook.py index 777fec6..8674c16 100644 --- a/test/test_skip_import_playbook.py +++ b/test/test_skip_import_playbook.py @@ -1,4 +1,5 @@ """Test related to skipping import_playbook.""" + from pathlib import Path import pytest diff --git a/test/test_skip_inside_yaml.py b/test/test_skip_inside_yaml.py index 363734e..8050f13 100644 --- a/test/test_skip_inside_yaml.py +++ b/test/test_skip_inside_yaml.py @@ -1,4 +1,5 @@ """Tests related to use of inline noqa.""" + import pytest from ansiblelint.rules import RulesCollection diff --git a/test/test_skip_playbook_items.py b/test/test_skip_playbook_items.py index 2861c6a..2fc05ea 100644 --- a/test/test_skip_playbook_items.py +++ b/test/test_skip_playbook_items.py @@ -1,4 +1,5 @@ """Tests related to use of noqa inside playbooks.""" + import pytest from ansiblelint.testing import RunFromText diff --git a/test/test_skiputils.py b/test/test_skiputils.py index 7e736e7..2975945 100644 --- a/test/test_skiputils.py +++ b/test/test_skiputils.py @@ -1,4 +1,5 @@ """Validate ansiblelint.skip_utils.""" + from __future__ import annotations from pathlib import Path @@ -39,8 +40,8 @@ PLAYBOOK_WITH_NOQA = """\ @pytest.mark.parametrize( ("line", "expected"), ( - ("foo # noqa: bar", "bar"), - ("foo # noqa bar", "bar"), + pytest.param("foo # noqa: bar", "bar", id="0"), + pytest.param("foo # noqa bar", "bar", id="1"), ), ) def test_get_rule_skips_from_line(line: str, expected: str) -> None: diff --git a/test/test_strict.py b/test/test_strict.py index ba93d7c..5994ffd 100644 --- a/test/test_strict.py +++ b/test/test_strict.py @@ -1,4 +1,5 @@ """Test strict mode.""" + import os import pytest diff --git a/test/test_task_includes.py b/test/test_task_includes.py index 3b02d00..80b5856 100644 --- a/test/test_task_includes.py +++ b/test/test_task_includes.py @@ -1,4 +1,5 @@ """Tests related to task inclusions.""" + import pytest from ansiblelint.file_utils import Lintable @@ -9,7 +10,12 @@ from ansiblelint.runner import Runner @pytest.mark.parametrize( ("filename", "file_count", "match_count"), ( - pytest.param("examples/playbooks/blockincludes.yml", 4, 3, id="blockincludes"), + pytest.param( + "examples/playbooks/blockincludes.yml", + 4, + 3, + id="blockincludes", + ), pytest.param( "examples/playbooks/blockincludes2.yml", 4, diff --git a/test/test_text.py b/test/test_text.py index fa91fee..22214c7 100644 --- a/test/test_text.py +++ b/test/test_text.py @@ -1,4 +1,5 @@ """Tests for text module.""" + from typing import Any import pytest diff --git a/test/test_transform_mixin.py b/test/test_transform_mixin.py index d639bff..44b851b 100644 --- a/test/test_transform_mixin.py +++ b/test/test_transform_mixin.py @@ -1,4 +1,5 @@ """Tests for TransformMixin.""" + from __future__ import annotations from typing import TYPE_CHECKING @@ -55,72 +56,91 @@ def test_seek_with_bad_path( @pytest.mark.parametrize( ("yaml_path", "data", "expected"), ( - ([], DUMMY_MAP, DUMMY_MAP), - (["foo"], DUMMY_MAP, DUMMY_MAP["foo"]), - (["bar"], DUMMY_MAP, DUMMY_MAP["bar"]), - (["bar", "some"], DUMMY_MAP, DUMMY_MAP["bar"]["some"]), - (["fruits"], DUMMY_MAP, DUMMY_MAP["fruits"]), - (["fruits", 0], DUMMY_MAP, DUMMY_MAP["fruits"][0]), - (["fruits", 1], DUMMY_MAP, DUMMY_MAP["fruits"][1]), - (["answer"], DUMMY_MAP, DUMMY_MAP["answer"]), - (["answer", 0], DUMMY_MAP, DUMMY_MAP["answer"][0]), - (["answer", 0, "forty-two"], DUMMY_MAP, DUMMY_MAP["answer"][0]["forty-two"]), - ( + pytest.param([], DUMMY_MAP, DUMMY_MAP, id="0"), + pytest.param(["foo"], DUMMY_MAP, DUMMY_MAP["foo"], id="1"), + pytest.param(["bar"], DUMMY_MAP, DUMMY_MAP["bar"], id="2"), + pytest.param(["bar", "some"], DUMMY_MAP, DUMMY_MAP["bar"]["some"], id="3"), + pytest.param(["fruits"], DUMMY_MAP, DUMMY_MAP["fruits"], id="4"), + pytest.param(["fruits", 0], DUMMY_MAP, DUMMY_MAP["fruits"][0], id="5"), + pytest.param(["fruits", 1], DUMMY_MAP, DUMMY_MAP["fruits"][1], id="6"), + pytest.param(["answer"], DUMMY_MAP, DUMMY_MAP["answer"], id="7"), + pytest.param(["answer", 0], DUMMY_MAP, DUMMY_MAP["answer"][0], id="8"), + pytest.param( + ["answer", 0, "forty-two"], + DUMMY_MAP, + DUMMY_MAP["answer"][0]["forty-two"], + id="9", + ), + pytest.param( ["answer", 0, "forty-two", 0], DUMMY_MAP, DUMMY_MAP["answer"][0]["forty-two"][0], + id="10", ), - ( + pytest.param( ["answer", 0, "forty-two", 1], DUMMY_MAP, DUMMY_MAP["answer"][0]["forty-two"][1], + id="11", ), - ( + pytest.param( ["answer", 0, "forty-two", 2], DUMMY_MAP, DUMMY_MAP["answer"][0]["forty-two"][2], + id="12", + ), + pytest.param([], DUMMY_LIST, DUMMY_LIST, id="13"), + pytest.param([0], DUMMY_LIST, DUMMY_LIST[0], id="14"), + pytest.param([0, "foo"], DUMMY_LIST, DUMMY_LIST[0]["foo"], id="15"), + pytest.param([1], DUMMY_LIST, DUMMY_LIST[1], id="16"), + pytest.param([1, "bar"], DUMMY_LIST, DUMMY_LIST[1]["bar"], id="17"), + pytest.param( + [1, "bar", "some"], + DUMMY_LIST, + DUMMY_LIST[1]["bar"]["some"], + id="18", ), - ([], DUMMY_LIST, DUMMY_LIST), - ([0], DUMMY_LIST, DUMMY_LIST[0]), - ([0, "foo"], DUMMY_LIST, DUMMY_LIST[0]["foo"]), - ([1], DUMMY_LIST, DUMMY_LIST[1]), - ([1, "bar"], DUMMY_LIST, DUMMY_LIST[1]["bar"]), - ([1, "bar", "some"], DUMMY_LIST, DUMMY_LIST[1]["bar"]["some"]), - ([1, "fruits"], DUMMY_LIST, DUMMY_LIST[1]["fruits"]), - ([1, "fruits", 0], DUMMY_LIST, DUMMY_LIST[1]["fruits"][0]), - ([1, "fruits", 1], DUMMY_LIST, DUMMY_LIST[1]["fruits"][1]), - ([2], DUMMY_LIST, DUMMY_LIST[2]), - ([2, "answer"], DUMMY_LIST, DUMMY_LIST[2]["answer"]), - ([2, "answer", 0], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]), - ( + pytest.param([1, "fruits"], DUMMY_LIST, DUMMY_LIST[1]["fruits"], id="19"), + pytest.param([1, "fruits", 0], DUMMY_LIST, DUMMY_LIST[1]["fruits"][0], id="20"), + pytest.param([1, "fruits", 1], DUMMY_LIST, DUMMY_LIST[1]["fruits"][1], id="21"), + pytest.param([2], DUMMY_LIST, DUMMY_LIST[2], id="22"), + pytest.param([2, "answer"], DUMMY_LIST, DUMMY_LIST[2]["answer"], id="23"), + pytest.param([2, "answer", 0], DUMMY_LIST, DUMMY_LIST[2]["answer"][0], id="24"), + pytest.param( [2, "answer", 0, "forty-two"], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]["forty-two"], + id="25", ), - ( + pytest.param( [2, "answer", 0, "forty-two", 0], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]["forty-two"][0], + id="26", ), - ( + pytest.param( [2, "answer", 0, "forty-two", 1], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]["forty-two"][1], + id="27", ), - ( + pytest.param( [2, "answer", 0, "forty-two", 2], DUMMY_LIST, DUMMY_LIST[2]["answer"][0]["forty-two"][2], + id="28", ), - ( + pytest.param( [], "this is a string that should be returned as is, ignoring path.", "this is a string that should be returned as is, ignoring path.", + id="29", ), - ( + pytest.param( [2, "answer", 0, "forty-two", 2], "this is a string that should be returned as is, ignoring path.", "this is a string that should be returned as is, ignoring path.", + id="30", ), ), ) diff --git a/test/test_transformer.py b/test/test_transformer.py index 78dd121..51e97d5 100644 --- a/test/test_transformer.py +++ b/test/test_transformer.py @@ -1,125 +1,247 @@ +# cspell:ignore classinfo """Tests for Transformer.""" + from __future__ import annotations +import builtins import os import shutil from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any +from unittest import mock import pytest +import ansiblelint.__main__ as main +from ansiblelint.app import App +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import TransformMixin + # noinspection PyProtectedMember -from ansiblelint.runner import LintResult, _get_matches +from ansiblelint.runner import LintResult, get_matches from ansiblelint.transformer import Transformer if TYPE_CHECKING: - from argparse import Namespace - from collections.abc import Iterator - from ansiblelint.config import Options + from ansiblelint.errors import MatchError from ansiblelint.rules import RulesCollection -@pytest.fixture(name="copy_examples_dir") -def fixture_copy_examples_dir( - tmp_path: Path, - config_options: Namespace, -) -> Iterator[tuple[Path, Path]]: - """Fixture that copies the examples/ dir into a tmpdir.""" - examples_dir = Path("examples") - - shutil.copytree(examples_dir, tmp_path / "examples") - old_cwd = Path.cwd() - try: - os.chdir(tmp_path) - config_options.cwd = tmp_path - yield old_cwd, tmp_path - finally: - os.chdir(old_cwd) - - @pytest.fixture(name="runner_result") def fixture_runner_result( config_options: Options, default_rules_collection: RulesCollection, - playbook: str, + playbook_str: str, + monkeypatch: pytest.MonkeyPatch, ) -> LintResult: """Fixture that runs the Runner to populate a LintResult for a given file.""" - config_options.lintables = [playbook] - result = _get_matches(rules=default_rules_collection, options=config_options) + # needed for testing transformer when roles/modules are missing: + monkeypatch.setenv("ANSIBLE_LINT_NODEPS", "1") + config_options.lintables = [playbook_str] + result = get_matches(rules=default_rules_collection, options=config_options) return result @pytest.mark.parametrize( - ("playbook", "matches_count", "transformed"), + ("playbook_str", "matches_count", "transformed", "is_owned_by_ansible"), ( # reuse TestRunner::test_runner test cases to ensure transformer does not mangle matches pytest.param( "examples/playbooks/nomatchestest.yml", 0, False, + True, id="nomatchestest", ), - pytest.param("examples/playbooks/unicode.yml", 1, False, id="unicode"), + pytest.param("examples/playbooks/unicode.yml", 1, False, True, id="unicode"), pytest.param( "examples/playbooks/lots_of_warnings.yml", - 992, + 993, False, + True, id="lots_of_warnings", ), - pytest.param("examples/playbooks/become.yml", 0, False, id="become"), + pytest.param("examples/playbooks/become.yml", 0, False, True, id="become"), pytest.param( "examples/playbooks/contains_secrets.yml", 0, False, + True, id="contains_secrets", ), pytest.param( "examples/playbooks/vars/empty_vars.yml", 0, False, + True, id="empty_vars", ), - pytest.param("examples/playbooks/vars/strings.yml", 0, True, id="strings"), - pytest.param("examples/playbooks/vars/empty.yml", 1, False, id="empty"), - pytest.param("examples/playbooks/name-case.yml", 1, True, id="name_case"), - pytest.param("examples/playbooks/fqcn.yml", 3, True, id="fqcn"), + pytest.param( + "examples/playbooks/vars/strings.yml", + 0, + True, + True, + id="strings", + ), + pytest.param("examples/playbooks/vars/empty.yml", 1, False, True, id="empty"), + pytest.param("examples/playbooks/fqcn.yml", 3, True, True, id="fqcn"), + pytest.param( + "examples/playbooks/multi_yaml_doc.yml", + 1, + False, + True, + id="multi_yaml_doc", + ), + pytest.param( + "examples/playbooks/transform_command_instead_of_shell.yml", + 3, + True, + True, + id="cmd_instead_of_shell", + ), + pytest.param( + "examples/playbooks/transform-deprecated-local-action.yml", + 1, + True, + True, + id="dep_local_action", + ), + pytest.param( + "examples/playbooks/transform-block-indentation-indicator.yml", + 0, + True, + True, + id="multiline_msg_with_indent_indicator", + ), + pytest.param( + "examples/playbooks/transform-jinja.yml", + 7, + True, + True, + id="jinja_spacing", + ), + pytest.param( + "examples/playbooks/transform-no-jinja-when.yml", + 3, + True, + True, + id="no_jinja_when", + ), + pytest.param( + "examples/playbooks/vars/transform_nested_data.yml", + 3, + True, + True, + id="nested", + ), + pytest.param( + "examples/playbooks/transform-key-order.yml", + 6, + True, + True, + id="key_order_transform", + ), + pytest.param( + "examples/playbooks/transform-no-free-form.yml", + 5, + True, + True, + id="no_free_form_transform", + ), + pytest.param( + "examples/playbooks/transform-partial-become.yml", + 4, + True, + True, + id="partial_become", + ), + pytest.param( + "examples/playbooks/transform-key-order-play.yml", + 1, + True, + True, + id="key_order_play_transform", + ), + pytest.param( + "examples/playbooks/transform-key-order-block.yml", + 1, + True, + True, + id="key_order_block_transform", + ), + pytest.param( + "examples/.github/workflows/sample.yml", + 0, + False, + False, + id="github-workflow", + ), + pytest.param( + "examples/playbooks/invalid-transform.yml", + 1, + False, + True, + id="invalid_transform", + ), + pytest.param( + "examples/roles/name_prefix/tasks/test.yml", + 1, + True, + True, + id="name_casing_prefix", + ), + pytest.param( + "examples/roles/name_casing/tasks/main.yml", + 2, + True, + True, + id="name_case_roles", + ), + pytest.param( + "examples/playbooks/4114/transform-with-missing-role-and-modules.yml", + 1, + True, + True, + id="4114", + ), ), ) -def test_transformer( # pylint: disable=too-many-arguments, too-many-locals +@mock.patch.dict(os.environ, {"ANSIBLE_LINT_WRITE_TMP": "1"}, clear=True) +def test_transformer( # pylint: disable=too-many-arguments config_options: Options, - copy_examples_dir: tuple[Path, Path], - playbook: str, + playbook_str: str, runner_result: LintResult, transformed: bool, + is_owned_by_ansible: bool, matches_count: int, ) -> None: """Test that transformer can go through any corner cases. Based on TestRunner::test_runner """ + # test ability to detect is_owned_by_ansible + assert Lintable(playbook_str).is_owned_by_ansible() == is_owned_by_ansible + playbook = Path(playbook_str) config_options.write_list = ["all"] - transformer = Transformer(result=runner_result, options=config_options) - transformer.run() matches = runner_result.matches assert len(matches) == matches_count - orig_dir, tmp_dir = copy_examples_dir - orig_playbook = orig_dir / playbook - expected_playbook = orig_dir / playbook.replace(".yml", ".transformed.yml") - transformed_playbook = tmp_dir / playbook - - orig_playbook_content = orig_playbook.read_text() - expected_playbook_content = expected_playbook.read_text() - transformed_playbook_content = transformed_playbook.read_text() + transformer = Transformer(result=runner_result, options=config_options) + transformer.run() + orig_content = playbook.read_text(encoding="utf-8") if transformed: - assert orig_playbook_content != transformed_playbook_content - else: - assert orig_playbook_content == transformed_playbook_content + expected_content = playbook.with_suffix( + f".transformed{playbook.suffix}", + ).read_text(encoding="utf-8") + transformed_content = playbook.with_suffix(f".tmp{playbook.suffix}").read_text( + encoding="utf-8", + ) - assert transformed_playbook_content == expected_playbook_content + assert orig_content != transformed_content + assert expected_content == transformed_content + playbook.with_suffix(f".tmp{playbook.suffix}").unlink() @pytest.mark.parametrize( @@ -173,3 +295,341 @@ def test_effective_write_set(write_list: list[str], expected: set[str]) -> None: """Make sure effective_write_set handles all/none keywords correctly.""" actual = Transformer.effective_write_set(write_list) assert actual == expected + + +def test_pruned_err_after_fix(monkeypatch: pytest.MonkeyPatch, tmpdir: Path) -> None: + """Test that pruned errors are not reported after fixing. + + :param monkeypatch: Monkeypatch + :param tmpdir: Temporary directory + """ + file = Path("examples/playbooks/transform-jinja.yml") + source = Path.cwd() / file + dest = tmpdir / source.name + shutil.copyfile(source, dest) + + monkeypatch.setattr("sys.argv", ["ansible-lint", str(dest), "--fix=all"]) + + fix_called = False + orig_fix = main.fix + + def test_fix( + runtime_options: Options, + result: LintResult, + rules: RulesCollection, + ) -> None: + """Wrap main.fix to check if it was called and match count is correct. + + :param runtime_options: Runtime options + :param result: Lint result + :param rules: Rules collection + """ + nonlocal fix_called + fix_called = True + assert len(result.matches) == 7 + orig_fix(runtime_options, result, rules) + + report_called = False + + class TestApp(App): + """Wrap App to check if it was called and match count is correct.""" + + def report_outcome( + self: TestApp, + result: LintResult, + *, + mark_as_success: bool = False, + ) -> int: + """Wrap App.report_outcome to check if it was called and match count is correct. + + :param result: Lint result + :param mark_as_success: Mark as success + :returns: Exit code + """ + nonlocal report_called + report_called = True + assert len(result.matches) == 1 + return super().report_outcome(result, mark_as_success=mark_as_success) + + monkeypatch.setattr("ansiblelint.__main__.fix", test_fix) + monkeypatch.setattr("ansiblelint.app.App", TestApp) + + main.main() + assert fix_called + assert report_called + + +class TransformTests: + """A carrier for some common test constants.""" + + FILE_NAME = "examples/playbooks/transform-no-free-form.yml" + FILE_TYPE = "playbook" + LINENO = 5 + ID = "no-free-form" + MATCH_TYPE = "task" + VERSION_PART = "version=(1, 1)" + + @classmethod + def match_id(cls) -> str: + """Generate a match id. + + :returns: Match id string + """ + return f"{cls.ID}/{cls.MATCH_TYPE} {cls.FILE_NAME}:{cls.LINENO}" + + @classmethod + def rewrite_part(cls) -> str: + """Generate a rewrite part. + + :returns: Rewrite part string + """ + return f"{cls.FILE_NAME} ({cls.FILE_TYPE}), {cls.VERSION_PART}" + + +@pytest.fixture(name="test_result") +def fixture_test_result( + config_options: Options, + default_rules_collection: RulesCollection, +) -> tuple[LintResult, Options]: + """Fixture that runs the Runner to populate a LintResult for a given file. + + The results are confirmed and a limited to a single match. + + :param config_options: Configuration options + :param default_rules_collection: Default rules collection + :returns: Tuple of LintResult and Options + """ + config_options.write_list = [TransformTests.ID] + config_options.lintables = [TransformTests.FILE_NAME] + + result = get_matches(rules=default_rules_collection, options=config_options) + match = result.matches[0] + + def write(*_args: Any, **_kwargs: Any) -> None: + """Don't rewrite the test fixture. + + :param _args: Arguments + :param _kwargs: Keyword arguments + """ + + setattr(match.lintable, "write", write) # noqa: B010 + + assert match.rule.id == TransformTests.ID + assert match.filename == TransformTests.FILE_NAME + assert match.lineno == TransformTests.LINENO + assert match.match_type == TransformTests.MATCH_TYPE + result.matches = [match] + + return result, config_options + + +def test_transform_na( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, + test_result: tuple[LintResult, Options], +) -> None: + """Test the transformer is not available. + + :param caplog: Log capture fixture + :param monkeypatch: Monkeypatch + :param test_result: Test result fixture + """ + result = test_result[0] + options = test_result[1] + + _isinstance = builtins.isinstance + called = False + + def mp_isinstance(t_object: Any, classinfo: type) -> bool: + if classinfo is TransformMixin: + nonlocal called + called = True + return False + return _isinstance(t_object, classinfo) + + monkeypatch.setattr(builtins, "isinstance", mp_isinstance) + + transformer = Transformer(result=result, options=options) + with caplog.at_level(10): + transformer.run() + + assert called + logs = [record for record in caplog.records if record.module == "transformer"] + assert len(logs) == 2 + + log_0 = f"{transformer.FIX_NA_MSG} {TransformTests.match_id()}" + assert logs[0].message == log_0 + assert logs[0].levelname == "DEBUG" + + log_1 = f"{transformer.DUMP_MSG} {TransformTests.rewrite_part()}" + assert logs[1].message == log_1 + assert logs[1].levelname == "DEBUG" + + +def test_transform_no_tb( + caplog: pytest.LogCaptureFixture, + test_result: tuple[LintResult, Options], +) -> None: + """Test the transformer does not traceback. + + :param caplog: Log capture fixture + :param test_result: Test result fixture + :raises RuntimeError: If the rule is not a TransformMixin + """ + result = test_result[0] + options = test_result[1] + exception_msg = "FixFailure" + + def transform(*_args: Any, **_kwargs: Any) -> None: + """Raise an exception for the transform call. + + :raises RuntimeError: Always + """ + raise RuntimeError(exception_msg) + + if isinstance(result.matches[0].rule, TransformMixin): + setattr(result.matches[0].rule, "transform", transform) # noqa: B010 + else: + err = "Rule is not a TransformMixin" + raise TypeError(err) + + transformer = Transformer(result=result, options=options) + with caplog.at_level(10): + transformer.run() + + logs = [record for record in caplog.records if record.module == "transformer"] + assert len(logs) == 5 + + log_0 = f"{transformer.FIX_APPLY_MSG} {TransformTests.match_id()}" + assert logs[0].message == log_0 + assert logs[0].levelname == "DEBUG" + + log_1 = f"{transformer.FIX_FAILED_MSG} {TransformTests.match_id()}" + assert logs[1].message == log_1 + assert logs[1].levelname == "ERROR" + + log_2 = exception_msg + assert logs[2].message == log_2 + assert logs[2].levelname == "ERROR" + + log_3 = f"{transformer.FIX_ISSUE_MSG}" + assert logs[3].message == log_3 + assert logs[3].levelname == "ERROR" + + log_4 = f"{transformer.DUMP_MSG} {TransformTests.rewrite_part()}" + assert logs[4].message == log_4 + assert logs[4].levelname == "DEBUG" + + +def test_transform_applied( + caplog: pytest.LogCaptureFixture, + test_result: tuple[LintResult, Options], +) -> None: + """Test the transformer is applied. + + :param caplog: Log capture fixture + :param test_result: Test result fixture + """ + result = test_result[0] + options = test_result[1] + + transformer = Transformer(result=result, options=options) + with caplog.at_level(10): + transformer.run() + + logs = [record for record in caplog.records if record.module == "transformer"] + assert len(logs) == 3 + + log_0 = f"{transformer.FIX_APPLY_MSG} {TransformTests.match_id()}" + assert logs[0].message == log_0 + assert logs[0].levelname == "DEBUG" + + log_1 = f"{transformer.FIX_APPLIED_MSG} {TransformTests.match_id()}" + assert logs[1].message == log_1 + assert logs[1].levelname == "DEBUG" + + log_2 = f"{transformer.DUMP_MSG} {TransformTests.rewrite_part()}" + assert logs[2].message == log_2 + assert logs[2].levelname == "DEBUG" + + +def test_transform_not_enabled( + caplog: pytest.LogCaptureFixture, + test_result: tuple[LintResult, Options], +) -> None: + """Test the transformer is not enabled. + + :param caplog: Log capture fixture + :param test_result: Test result fixture + """ + result = test_result[0] + options = test_result[1] + options.write_list = [] + + transformer = Transformer(result=result, options=options) + with caplog.at_level(10): + transformer.run() + + logs = [record for record in caplog.records if record.module == "transformer"] + assert len(logs) == 2 + + log_0 = f"{transformer.FIX_NE_MSG} {TransformTests.match_id()}" + assert logs[0].message == log_0 + assert logs[0].levelname == "DEBUG" + + log_1 = f"{transformer.DUMP_MSG} {TransformTests.rewrite_part()}" + assert logs[1].message == log_1 + assert logs[1].levelname == "DEBUG" + + +def test_transform_not_applied( + caplog: pytest.LogCaptureFixture, + test_result: tuple[LintResult, Options], +) -> None: + """Test the transformer is not applied. + + :param caplog: Log capture fixture + :param test_result: Test result fixture + :raises RuntimeError: If the rule is not a TransformMixin + """ + result = test_result[0] + options = test_result[1] + + called = False + + def transform(match: MatchError, *_args: Any, **_kwargs: Any) -> None: + """Do not apply the transform. + + :param match: Match object + :param _args: Arguments + :param _kwargs: Keyword arguments + """ + nonlocal called + called = True + match.fixed = False + + if isinstance(result.matches[0].rule, TransformMixin): + setattr(result.matches[0].rule, "transform", transform) # noqa: B010 + else: + err = "Rule is not a TransformMixin" + raise TypeError(err) + + transformer = Transformer(result=result, options=options) + with caplog.at_level(10): + transformer.run() + + assert called + logs = [record for record in caplog.records if record.module == "transformer"] + assert len(logs) == 3 + + log_0 = f"{transformer.FIX_APPLY_MSG} {TransformTests.match_id()}" + assert logs[0].message == log_0 + assert logs[0].levelname == "DEBUG" + + log_1 = f"{transformer.FIX_NOT_APPLIED_MSG} {TransformTests.match_id()}" + assert logs[1].message == log_1 + assert logs[1].levelname == "ERROR" + + log_2 = f"{transformer.DUMP_MSG} {TransformTests.rewrite_part()}" + assert logs[2].message == log_2 + assert logs[2].levelname == "DEBUG" diff --git a/test/test_utils.py b/test/test_utils.py index 1b9a2dc..6f728dc 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -51,37 +51,44 @@ runtime = Runtime(require_module=True) @pytest.mark.parametrize( - ("string", "expected_cmd", "expected_args", "expected_kwargs"), + ("string", "expected_args", "expected_kwargs"), ( - pytest.param("", "", [], {}, id="blank"), - pytest.param("vars:", "vars", [], {}, id="single_word"), - pytest.param("hello: a=1", "hello", [], {"a": "1"}, id="string_module_and_arg"), - pytest.param("action: hello a=1", "hello", [], {"a": "1"}, id="strips_action"), + pytest.param("", [], {}, id="a"), + pytest.param("a=1", [], {"a": "1"}, id="b"), + pytest.param("hello a=1", ["hello"], {"a": "1"}, id="c"), pytest.param( - "action: whatever bobbins x=y z=x c=3", - "whatever", - ["bobbins", "x=y", "z=x", "c=3"], - {}, + "whatever bobbins x=y z=x c=3", + ["whatever", "bobbins"], + {"x": "y", "z": "x", "c": "3"}, id="more_than_one_arg", ), pytest.param( - "action: command chdir=wxy creates=zyx tar xzf zyx.tgz", - "command", - ["tar", "xzf", "zyx.tgz"], + "command chdir=wxy creates=zyx tar xzf zyx.tgz", + ["command", "tar", "xzf", "zyx.tgz"], {"chdir": "wxy", "creates": "zyx"}, id="command_with_args", ), + pytest.param( + "{{ varset }}.yml", + ["{{ varset }}.yml"], + {}, + id="x", + ), + pytest.param( + "foo bar.yml", + ["foo bar.yml"], + {}, + id="path-with-spaces", + ), ), ) def test_tokenize( string: str, - expected_cmd: str, expected_args: Sequence[str], expected_kwargs: dict[str, Any], ) -> None: """Test that tokenize works for different input types.""" - (cmd, args, kwargs) = utils.tokenize(string) - assert cmd == expected_cmd + (args, kwargs) = utils.tokenize(string) assert args == expected_args assert kwargs == expected_kwargs @@ -113,37 +120,42 @@ def test_normalize( alternate_forms: tuple[dict[str, Any]], ) -> None: """Test that tasks specified differently are normalized same way.""" - normal_form = utils.normalize_task(reference_form, "tasks.yml") + task = utils.Task(reference_form, filename="tasks.yml") + normal_form = task._normalize_task() # noqa: SLF001 for form in alternate_forms: - assert normal_form == utils.normalize_task(form, "tasks.yml") + task2 = utils.Task(form, filename="tasks.yml") + assert normal_form == task2._normalize_task() # noqa: SLF001 def test_normalize_complex_command() -> None: """Test that tasks specified differently are normalized same way.""" - task1 = { - "name": "hello", - "action": {"module": "pip", "name": "df", "editable": "false"}, - } - task2 = {"name": "hello", "pip": {"name": "df", "editable": "false"}} - task3 = {"name": "hello", "pip": "name=df editable=false"} - task4 = {"name": "hello", "action": "pip name=df editable=false"} - assert utils.normalize_task(task1, "tasks.yml") == utils.normalize_task( - task2, - "tasks.yml", + task1 = utils.Task( + { + "name": "hello", + "action": {"module": "pip", "name": "df", "editable": "false"}, + }, + filename="tasks.yml", ) - assert utils.normalize_task(task2, "tasks.yml") == utils.normalize_task( - task3, - "tasks.yml", + task2 = utils.Task( + {"name": "hello", "pip": {"name": "df", "editable": "false"}}, + filename="tasks.yml", ) - assert utils.normalize_task(task3, "tasks.yml") == utils.normalize_task( - task4, - "tasks.yml", + task3 = utils.Task( + {"name": "hello", "pip": "name=df editable=false"}, + filename="tasks.yml", ) + task4 = utils.Task( + {"name": "hello", "action": "pip name=df editable=false"}, + filename="tasks.yml", + ) + assert task1._normalize_task() == task2._normalize_task() # noqa: SLF001 + assert task2._normalize_task() == task3._normalize_task() # noqa: SLF001 + assert task3._normalize_task() == task4._normalize_task() # noqa: SLF001 @pytest.mark.parametrize( - ("task", "expected_form"), + ("task_raw", "expected_form"), ( pytest.param( { @@ -191,8 +203,12 @@ def test_normalize_complex_command() -> None: ), ), ) -def test_normalize_task_v2(task: dict[str, Any], expected_form: dict[str, Any]) -> None: +def test_normalize_task_v2( + task_raw: dict[str, Any], + expected_form: dict[str, Any], +) -> None: """Check that it normalizes task and returns the expected form.""" + task = utils.Task(task_raw) assert utils.normalize_task_v2(task) == expected_form @@ -262,8 +278,8 @@ def test_template(template: str, output: str) -> None: def test_task_to_str_unicode() -> None: """Ensure that extracting messages from tasks preserves Unicode.""" - task = {"fail": {"msg": "unicode é ô à"}} - result = utils.task_to_str(utils.normalize_task(task, "filename.yml")) + task = utils.Task({"fail": {"msg": "unicode é ô à"}}, filename="filename.yml") + result = utils.task_to_str(task._normalize_task()) # noqa: SLF001 assert result == "fail msg=unicode é ô à" @@ -447,3 +463,20 @@ def test_task_in_list(file: str, names: list[str], positions: list[str]) -> None for index, task in enumerate(tasks): assert task.name == names[index] assert task.position == positions[index] + + +def test_find_children_in_module(default_rules_collection: RulesCollection) -> None: + """Verify correct function of find_children() in tasks.""" + lintable = Lintable("plugins/modules/fake_module.py") + children = Runner( + rules=default_rules_collection, + ).find_children(lintable) + assert len(children) == 1 + child = children[0] + + # Parent is a python file + assert lintable.base_kind == "text/python" + + # Child correctly looks like a YAML file + assert child.base_kind == "text/yaml" + assert child.content.startswith("---") diff --git a/test/test_verbosity.py b/test/test_verbosity.py index d3ddb3c..38df170 100644 --- a/test/test_verbosity.py +++ b/test/test_verbosity.py @@ -1,4 +1,5 @@ """Tests related to our logging/verbosity setup.""" + from __future__ import annotations from pathlib import Path @@ -6,6 +7,7 @@ from pathlib import Path import pytest from ansiblelint.testing import run_ansible_lint +from ansiblelint.text import strip_ansi_escape # substrs is a list of tuples, where: @@ -83,6 +85,9 @@ def test_verbosity( else: result = run_ansible_lint(str(fakerole), cwd=project_path) + result.stderr = strip_ansi_escape(result.stderr) + result.stdout = strip_ansi_escape(result.stdout) + assert result.returncode == 2, result for substr, invert in substrs: if invert: assert substr not in result.stderr, result.stderr diff --git a/test/test_with_skip_tagid.py b/test/test_with_skip_tagid.py index 5fbea8f..a2a46c3 100644 --- a/test/test_with_skip_tagid.py +++ b/test/test_with_skip_tagid.py @@ -1,4 +1,5 @@ """Tests related to skip tag id.""" + from ansiblelint.rules import RulesCollection from ansiblelint.rules.yaml_rule import YamllintRule from ansiblelint.runner import Runner @@ -26,7 +27,7 @@ def test_negative_with_id() -> None: def test_negative_with_tag() -> None: """Negative test with_tag.""" - with_tag = "trailing-spaces" + with_tag = "yaml[trailing-spaces]" bad_runner = Runner(FILE, rules=collection, tags=frozenset([with_tag])) errs = bad_runner.run() assert len(errs) == 1 @@ -39,6 +40,13 @@ def test_positive_skip_id() -> None: assert [] == good_runner.run() +def test_positive_skip_id_2() -> None: + """Positive test skip_id.""" + skip_id = "key-order" + good_runner = Runner(FILE, rules=collection, tags=frozenset([skip_id])) + assert [] == good_runner.run() + + def test_positive_skip_tag() -> None: """Positive test skip_tag.""" skip_tag = "yaml[trailing-spaces]" diff --git a/test/test_yaml_utils.py b/test/test_yaml_utils.py index 5546e58..f4d9b46 100644 --- a/test/test_yaml_utils.py +++ b/test/test_yaml_utils.py @@ -1,16 +1,18 @@ """Tests for yaml-related utility functions.""" + +# pylint: disable=too-many-lines from __future__ import annotations from io import StringIO from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import pytest from ruamel.yaml.main import YAML from yamllint.linter import run as run_yamllint import ansiblelint.yaml_utils -from ansiblelint.file_utils import Lintable +from ansiblelint.file_utils import Lintable, cwd from ansiblelint.utils import task_in_list if TYPE_CHECKING: @@ -202,8 +204,7 @@ def test_custom_ruamel_yaml_emitter( assert output == expected_output -@pytest.fixture(name="yaml_formatting_fixtures") -def fixture_yaml_formatting_fixtures(fixture_filename: str) -> tuple[str, str, str]: +def load_yaml_formatting_fixtures(fixture_filename: str) -> tuple[str, str, str]: """Get the contents for the formatting fixture files. To regenerate these fixtures, please run ``pytest --regenerate-formatting-fixtures``. @@ -220,30 +221,67 @@ def fixture_yaml_formatting_fixtures(fixture_filename: str) -> tuple[str, str, s @pytest.mark.parametrize( - "fixture_filename", + ("before", "after", "version"), + ( + pytest.param("---\nfoo: bar\n", "---\nfoo: bar\n", None, id="1"), + # verify that 'on' is not translated to bool (1.2 behavior) + pytest.param("---\nfoo: on\n", "---\nfoo: on\n", None, id="2"), + # When version is manually mentioned by us, we expect to output without version directive + pytest.param("---\nfoo: on\n", "---\nfoo: on\n", (1, 2), id="3"), + pytest.param("---\nfoo: on\n", "---\nfoo: true\n", (1, 1), id="4"), + pytest.param("%YAML 1.1\n---\nfoo: on\n", "---\nfoo: true\n", (1, 1), id="5"), + # verify that in-line directive takes precedence but dumping strips if we mention a specific version + pytest.param("%YAML 1.1\n---\nfoo: on\n", "---\nfoo: true\n", (1, 2), id="6"), + # verify that version directive are kept if present + pytest.param("%YAML 1.1\n---\nfoo: on\n", "---\nfoo: true\n", None, id="7"), + pytest.param( + "%YAML 1.2\n---\nfoo: on\n", + "%YAML 1.2\n---\nfoo: on\n", + None, + id="8", + ), + pytest.param("---\nfoo: YES\n", "---\nfoo: true\n", (1, 1), id="9"), + pytest.param("---\nfoo: YES\n", "---\nfoo: YES\n", (1, 2), id="10"), + pytest.param("---\nfoo: YES\n", "---\nfoo: YES\n", None, id="11"), + ), +) +def test_fmt(before: str, after: str, version: tuple[int, int] | None) -> None: + """Tests behavior of formatter in regards to different YAML versions, specified or not.""" + yaml = ansiblelint.yaml_utils.FormattedYAML(version=version) + data = yaml.load(before) + result = yaml.dumps(data) + assert result == after + + +@pytest.mark.parametrize( + ("fixture_filename", "version"), ( - "fmt-1.yml", - "fmt-2.yml", - "fmt-3.yml", + pytest.param("fmt-1.yml", (1, 1), id="1"), + pytest.param("fmt-2.yml", (1, 1), id="2"), + pytest.param("fmt-3.yml", (1, 1), id="3"), + pytest.param("fmt-4.yml", (1, 1), id="4"), + pytest.param("fmt-5.yml", (1, 1), id="5"), + pytest.param("fmt-hex.yml", (1, 1), id="hex"), ), ) def test_formatted_yaml_loader_dumper( - yaml_formatting_fixtures: tuple[str, str, str], - fixture_filename: str, # noqa: ARG001 + fixture_filename: str, + version: tuple[int, int], ) -> None: """Ensure that FormattedYAML loads/dumps formatting fixtures consistently.""" - # pylint: disable=unused-argument - before_content, prettier_content, after_content = yaml_formatting_fixtures + before_content, prettier_content, after_content = load_yaml_formatting_fixtures( + fixture_filename, + ) assert before_content != prettier_content assert before_content != after_content - yaml = ansiblelint.yaml_utils.FormattedYAML() + yaml = ansiblelint.yaml_utils.FormattedYAML(version=version) - data_before = yaml.loads(before_content) + data_before = yaml.load(before_content) dump_from_before = yaml.dumps(data_before) - data_prettier = yaml.loads(prettier_content) + data_prettier = yaml.load(prettier_content) dump_from_prettier = yaml.dumps(data_prettier) - data_after = yaml.loads(after_content) + data_after = yaml.load(after_content) dump_from_after = yaml.dumps(data_after) # comparing data does not work because the Comment objects @@ -274,7 +312,7 @@ def fixture_lintable(file_path: str) -> Lintable: def fixture_ruamel_data(lintable: Lintable) -> CommentedMap | CommentedSeq: """Return the loaded YAML data for the Lintable.""" yaml = ansiblelint.yaml_utils.FormattedYAML() - data: CommentedMap | CommentedSeq = yaml.loads(lintable.content) + data: CommentedMap | CommentedSeq = yaml.load(lintable.content) return data @@ -384,7 +422,7 @@ def fixture_ruamel_data(lintable: Lintable) -> CommentedMap | CommentedSeq: ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 10, + 12, [1], id="4_play_playbook-first_line_in_play_2", ), @@ -402,7 +440,7 @@ def fixture_ruamel_data(lintable: Lintable) -> CommentedMap | CommentedSeq: ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 19, + 21, [2], id="4_play_playbook-first_line_in_play_3", ), @@ -420,7 +458,7 @@ def fixture_ruamel_data(lintable: Lintable) -> CommentedMap | CommentedSeq: ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 28, + 31, [3], id="4_play_playbook-first_line_in_play_4", ), @@ -601,7 +639,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 7, + 8, [0, "tasks", 0], id="4_play_playbook-play_1_first_line_task_1", ), @@ -613,7 +651,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 10, + 13, [], id="4_play_playbook-play_2_line_before_tasks", ), @@ -625,7 +663,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 13, + 15, [1, "tasks", 0], id="4_play_playbook-play_2_first_line_task_1", ), @@ -643,7 +681,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 19, + 23, [], id="4_play_playbook-play_3_line_before_tasks", ), @@ -655,7 +693,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 23, + 25, [2, "tasks", 0], id="4_play_playbook-play_3_first_line_task_1", ), @@ -673,7 +711,7 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 28, + 33, [], id="4_play_playbook-play_4_line_before_tasks", ), @@ -685,13 +723,13 @@ def test_get_path_to_play( ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 32, + 35, [3, "tasks", 0], id="4_play_playbook-play_4_first_line_task_1", ), pytest.param( "examples/playbooks/rule-partial-become-without-become-pass.yml", - 33, + 39, [3, "tasks", 0], id="4_play_playbook-play_4_middle_line_task_1", ), @@ -730,12 +768,12 @@ def test_get_path_to_play( pytest.param( "examples/playbooks/include.yml", 14, - [0, "tasks", 1], + [0, "tasks", 2], id="playbook-multi_tasks_blocks-tasks_last_task_before_handlers", ), pytest.param( "examples/playbooks/include.yml", - 16, + 17, [0, "handlers", 0], id="playbook-multi_tasks_blocks-handlers_task", ), @@ -953,3 +991,35 @@ def test_deannotate( ) -> None: """Ensure deannotate works as intended.""" assert ansiblelint.yaml_utils.deannotate(before) == after + + +def test_yamllint_incompatible_config() -> None: + """Ensure we can detect incompatible yamllint settings.""" + with (cwd(Path("examples/yamllint/incompatible-config")),): + config = ansiblelint.yaml_utils.load_yamllint_config() + assert config.incompatible + + +@pytest.mark.parametrize( + ("yaml_version", "explicit_start"), + ( + pytest.param((1, 1), True), + pytest.param((1, 1), False), + ), +) +def test_document_start( + yaml_version: tuple[int, int] | None, + explicit_start: bool, +) -> None: + """Ensure the explicit_start config option from .yamllint is applied correctly.""" + config = ansiblelint.yaml_utils.FormattedYAML.default_config + config["explicit_start"] = explicit_start + + yaml = ansiblelint.yaml_utils.FormattedYAML( + version=yaml_version, + config=cast(dict[str, bool | int | str], config), + ) + assert ( + yaml.dumps(yaml.load(_SINGLE_QUOTE_WITHOUT_INDENTS)).startswith("---") + == explicit_start + ) |