summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorcos <cos>2024-04-20 09:15:12 +0200
committercos <cos>2024-04-20 09:44:47 +0200
commitbbed591285e1882d05198e518f75873af58939f5 (patch)
treec25b33652187f7070d0c2467663c11d6cd4e2326 /test
parentc4f015da4ac75017b97c24ef6601bdd98872e60f (diff)
downloaddebian-ansible-core-bbed591285e1882d05198e518f75873af58939f5.zip
Attempt to recreate upstream branch state from tar filesupstream/failed-recreation-attempt
Unfortunately this was a too naive approach, and the result fails to build. Work around that version control is behind the actual package version in trixie. As is obvious from the lacking commits in the salsa repository and also visible on https://tracker.debian.org/pkg/ansible-core with the report from vcswatch stating: VCS repository is not up to date. This commit contains all changes from ansible-core_2.14.13.orig.tar.gz to ansible-core_2.16.5.orig.tar.gz, which should hopefully be a squashed representation on the same set of changes on the uploader's unpushed git tree.
Diffstat (limited to 'test')
-rw-r--r--test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json2
-rw-r--r--test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py1
-rw-r--r--test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py3
-rw-r--r--test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json2
-rw-r--r--test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py1
-rw-r--r--test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py22
-rw-r--r--test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml19
-rw-r--r--test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json2
-rw-r--r--test/integration/targets/ansible-doc/randommodule-text.output26
-rw-r--r--test/integration/targets/ansible-doc/randommodule.output19
-rwxr-xr-xtest/integration/targets/ansible-doc/runme.sh134
-rw-r--r--test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt6
-rw-r--r--test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml1
-rw-r--r--test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py4
-rw-r--r--test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml2
-rw-r--r--test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml2
-rw-r--r--test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml7
-rw-r--r--test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py57
-rw-r--r--test/integration/targets/ansible-galaxy-collection/library/setup_collections.py15
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/build.yml25
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/download.yml4
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml2
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/init.yml65
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/install.yml203
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml8
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/list.yml41
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/main.yml43
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/publish.yml5
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml8
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml2
-rw-r--r--test/integration/targets/ansible-galaxy-collection/tasks/verify.yml8
-rw-r--r--test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j216
-rw-r--r--test/integration/targets/ansible-galaxy-collection/vars/main.yml43
-rwxr-xr-xtest/integration/targets/ansible-galaxy-role/files/create-role-archive.py21
-rw-r--r--test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml86
-rw-r--r--test/integration/targets/ansible-galaxy-role/tasks/main.yml15
-rw-r--r--test/integration/targets/ansible-galaxy/files/testserver.py19
-rwxr-xr-xtest/integration/targets/ansible-galaxy/runme.sh7
-rw-r--r--test/integration/targets/ansible-inventory/files/valid_sample.yml2
-rw-r--r--test/integration/targets/ansible-inventory/tasks/main.yml7
-rwxr-xr-xtest/integration/targets/ansible-pull/runme.sh5
-rw-r--r--test/integration/targets/ansible-runner/aliases1
-rw-r--r--test/integration/targets/ansible-runner/files/adhoc_example1.py1
-rw-r--r--test/integration/targets/ansible-test-cloud-openshift/aliases2
-rw-r--r--test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml11
-rwxr-xr-xtest/integration/targets/ansible-test-container/runme.py11
-rw-r--r--test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py4
-rw-r--r--test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py4
-rwxr-xr-xtest/integration/targets/ansible-test-sanity-import/runme.sh22
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml3
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md1
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md1
-rw-r--r--test/integration/targets/ansible-test-sanity-validate-modules/expected.txt21
-rwxr-xr-xtest/integration/targets/ansible-test-sanity-validate-modules/runme.sh12
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md1
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml9
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py4
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py2
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py2
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py2
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py4
-rw-r--r--test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt1
-rwxr-xr-xtest/integration/targets/ansible-test-sanity/runme.sh6
-rwxr-xr-xtest/integration/targets/ansible-test/venv-pythons.py10
-rw-r--r--test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml2
-rwxr-xr-xtest/integration/targets/ansible-vault/runme.sh32
-rw-r--r--test/integration/targets/ansible-vault/test_vault.yml2
-rw-r--r--test/integration/targets/ansible-vault/test_vaulted_template.yml2
-rw-r--r--test/integration/targets/ansible/aliases1
-rw-r--r--test/integration/targets/ansible/ansible-testé.cfg2
-rwxr-xr-xtest/integration/targets/ansible/runme.sh6
-rw-r--r--test/integration/targets/apt/aliases1
-rw-r--r--test/integration/targets/apt/tasks/apt.yml51
-rw-r--r--test/integration/targets/apt/tasks/repo.yml2
-rw-r--r--test/integration/targets/apt_key/aliases1
-rw-r--r--test/integration/targets/apt_key/tasks/main.yml2
-rw-r--r--test/integration/targets/apt_repository/aliases1
-rw-r--r--test/integration/targets/apt_repository/tasks/apt.yml18
-rw-r--r--test/integration/targets/apt_repository/tasks/mode_cleanup.yaml2
-rw-r--r--test/integration/targets/argspec/library/argspec.py6
-rw-r--r--test/integration/targets/become/tasks/main.yml4
-rw-r--r--test/integration/targets/blockinfile/tasks/main.yml3
-rw-r--r--test/integration/targets/blocks/unsafe_failed_task.yml2
-rw-r--r--test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout4
-rw-r--r--test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout4
-rw-r--r--test/integration/targets/check_mode/check_mode.yml2
-rw-r--r--test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml8
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py2
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py5
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py5
-rw-r--r--test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py5
-rw-r--r--test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py1
-rw-r--r--test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py1
-rw-r--r--test/integration/targets/command_nonexisting/tasks/main.yml2
-rw-r--r--test/integration/targets/command_shell/tasks/main.yml42
-rw-r--r--test/integration/targets/conditionals/play.yml26
-rw-r--r--test/integration/targets/connection_delegation/aliases1
-rw-r--r--test/integration/targets/connection_paramiko_ssh/test_connection.inventory2
-rw-r--r--test/integration/targets/connection_psrp/tests.yml11
-rw-r--r--test/integration/targets/connection_winrm/tests.yml3
-rw-r--r--test/integration/targets/copy/tasks/main.yml2
-rw-r--r--test/integration/targets/copy/tasks/tests.yml152
-rw-r--r--test/integration/targets/cron/aliases1
-rw-r--r--test/integration/targets/debconf/tasks/main.yml42
-rw-r--r--test/integration/targets/delegate_to/delegate_local_from_root.yml2
-rwxr-xr-xtest/integration/targets/delegate_to/runme.sh4
-rw-r--r--test/integration/targets/delegate_to/test_delegate_to.yml27
-rw-r--r--test/integration/targets/dnf/aliases2
-rw-r--r--test/integration/targets/dnf/tasks/dnf.yml55
-rw-r--r--test/integration/targets/dnf/tasks/main.yml6
-rw-r--r--test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml3
-rw-r--r--test/integration/targets/dnf/tasks/test_sos_removal.yml4
-rw-r--r--test/integration/targets/dpkg_selections/aliases1
-rw-r--r--test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml12
-rw-r--r--test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py2
-rw-r--r--test/integration/targets/environment/test_environment.yml10
-rw-r--r--test/integration/targets/error_from_connection/connection_plugins/dummy.py1
-rw-r--r--test/integration/targets/expect/tasks/main.yml9
-rw-r--r--test/integration/targets/facts_linux_network/aliases1
-rw-r--r--test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml10
-rw-r--r--test/integration/targets/file/tasks/link_rewrite.yml10
-rw-r--r--test/integration/targets/file/tasks/main.yml2
-rw-r--r--test/integration/targets/filter_core/tasks/main.yml32
-rw-r--r--test/integration/targets/filter_encryption/base.yml12
-rw-r--r--test/integration/targets/filter_mathstuff/tasks/main.yml54
-rw-r--r--test/integration/targets/find/tasks/main.yml3
-rw-r--r--test/integration/targets/fork_safe_stdio/aliases2
-rwxr-xr-xtest/integration/targets/fork_safe_stdio/runme.sh2
-rw-r--r--test/integration/targets/gathering_facts/library/file_utils.py3
-rwxr-xr-xtest/integration/targets/gathering_facts/runme.sh14
-rw-r--r--test/integration/targets/get_url/tasks/main.yml2
-rw-r--r--test/integration/targets/get_url/tasks/use_netrc.yml6
-rw-r--r--test/integration/targets/git/tasks/depth.yml9
-rw-r--r--test/integration/targets/git/tasks/forcefully-fetch-tag.yml4
-rw-r--r--test/integration/targets/git/tasks/gpg-verification.yml10
-rw-r--r--test/integration/targets/git/tasks/localmods.yml26
-rw-r--r--test/integration/targets/git/tasks/main.yml50
-rw-r--r--test/integration/targets/git/tasks/missing_hostkey.yml3
-rw-r--r--test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml3
-rw-r--r--test/integration/targets/git/tasks/reset-origin.yml9
-rw-r--r--test/integration/targets/git/tasks/setup-local-repos.yml33
-rw-r--r--test/integration/targets/git/tasks/setup.yml38
-rw-r--r--test/integration/targets/git/tasks/single-branch.yml6
-rw-r--r--test/integration/targets/git/tasks/specific-revision.yml18
-rw-r--r--test/integration/targets/git/vars/main.yml1
-rw-r--r--test/integration/targets/group/tasks/main.yml23
-rw-r--r--test/integration/targets/group/tasks/tests.yml681
-rwxr-xr-xtest/integration/targets/handlers/runme.sh27
-rw-r--r--test/integration/targets/include_vars/tasks/main.yml52
-rw-r--r--test/integration/targets/include_vars/vars/services/service_vars.yml2
-rw-r--r--test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml2
-rw-r--r--test/integration/targets/include_when_parent_is_dynamic/tasks.yml2
-rw-r--r--test/integration/targets/include_when_parent_is_static/tasks.yml2
-rw-r--r--test/integration/targets/includes/include_on_playbook_should_fail.yml2
-rw-r--r--test/integration/targets/includes/roles/test_includes/handlers/main.yml2
-rw-r--r--test/integration/targets/includes/roles/test_includes/tasks/main.yml40
-rw-r--r--test/integration/targets/includes/roles/test_includes_free/tasks/main.yml4
-rw-r--r--test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml2
-rwxr-xr-xtest/integration/targets/includes/runme.sh2
-rw-r--r--test/integration/targets/includes/test_includes2.yml4
-rw-r--r--test/integration/targets/includes/test_includes3.yml2
-rw-r--r--test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py2
-rw-r--r--test/integration/targets/inventory_ini/inventory.ini2
-rwxr-xr-xtest/integration/targets/inventory_ini/runme.sh3
-rw-r--r--test/integration/targets/iptables/aliases1
-rw-r--r--test/integration/targets/iptables/tasks/chain_management.yml21
-rw-r--r--test/integration/targets/known_hosts/defaults/main.yml2
-rw-r--r--test/integration/targets/known_hosts/tasks/main.yml2
-rw-r--r--test/integration/targets/lookup_config/tasks/main.yml2
-rw-r--r--test/integration/targets/lookup_fileglob/issue72873/test.yml6
-rw-r--r--test/integration/targets/lookup_first_found/tasks/main.yml53
-rw-r--r--test/integration/targets/lookup_sequence/tasks/main.yml2
-rw-r--r--test/integration/targets/lookup_together/tasks/main.yml2
-rw-r--r--test/integration/targets/lookup_url/aliases9
-rw-r--r--test/integration/targets/lookup_url/meta/main.yml2
-rw-r--r--test/integration/targets/lookup_url/tasks/main.yml32
-rw-r--r--test/integration/targets/lookup_url/tasks/use_netrc.yml8
-rw-r--r--test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml2
-rw-r--r--test/integration/targets/loop-connection/main.yml2
-rw-r--r--test/integration/targets/missing_required_lib/library/missing_required_lib.py2
-rw-r--r--test/integration/targets/module_defaults/action_plugins/debug.py2
-rw-r--r--test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py1
-rw-r--r--test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py1
-rw-r--r--test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py1
-rw-r--r--test/integration/targets/module_no_log/aliases1
-rw-r--r--test/integration/targets/module_no_log/tasks/main.yml38
-rw-r--r--test/integration/targets/module_utils/library/test.py12
-rw-r--r--test/integration/targets/module_utils/library/test_failure.py4
-rw-r--r--test/integration/targets/module_utils/module_utils_test.yml2
-rw-r--r--test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps12
-rw-r--r--test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps168
-rwxr-xr-xtest/integration/targets/no_log/runme.sh7
-rw-r--r--test/integration/targets/old_style_cache_plugins/aliases1
-rw-r--r--test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py1
-rw-r--r--test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml3
-rw-r--r--test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py2
-rwxr-xr-xtest/integration/targets/old_style_vars_plugins/runme.sh38
-rw-r--r--test/integration/targets/omit/75692.yml2
-rw-r--r--test/integration/targets/package/tasks/main.yml2
-rw-r--r--test/integration/targets/package_facts/aliases1
-rw-r--r--test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml19
-rw-r--r--test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml2
-rwxr-xr-xtest/integration/targets/parsing/runme.sh4
-rw-r--r--test/integration/targets/path_lookups/testplay.yml8
-rwxr-xr-xtest/integration/targets/pause/test-pause.py23
-rw-r--r--test/integration/targets/pip/tasks/main.yml3
-rw-r--r--test/integration/targets/pip/tasks/pip.yml22
-rw-r--r--test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py2
-rw-r--r--test/integration/targets/plugin_filtering/filter_lookup.yml2
-rw-r--r--test/integration/targets/plugin_filtering/filter_modules.yml2
-rw-r--r--test/integration/targets/plugin_filtering/filter_ping.yml2
-rw-r--r--test/integration/targets/plugin_filtering/filter_stat.yml2
-rwxr-xr-xtest/integration/targets/plugin_filtering/runme.sh16
-rw-r--r--test/integration/targets/plugin_loader/override/filters.yml2
-rwxr-xr-xtest/integration/targets/plugin_loader/runme.sh5
-rw-r--r--test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py2
-rw-r--r--test/integration/targets/remote_tmp/playbook.yml43
-rw-r--r--test/integration/targets/replace/tasks/main.yml19
-rwxr-xr-xtest/integration/targets/roles/runme.sh35
-rw-r--r--test/integration/targets/roles_arg_spec/roles/c/meta/main.yml9
-rw-r--r--test/integration/targets/roles_arg_spec/test.yml130
-rw-r--r--test/integration/targets/rpm_key/tasks/rpm_key.yaml26
-rw-r--r--test/integration/targets/script/tasks/main.yml11
-rw-r--r--test/integration/targets/service/aliases1
-rw-r--r--test/integration/targets/service/files/ansible_test_service.py1
-rw-r--r--test/integration/targets/service_facts/aliases1
-rw-r--r--test/integration/targets/setup_deb_repo/tasks/main.yml1
-rw-r--r--test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml9
-rw-r--r--test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml4
-rw-r--r--test/integration/targets/setup_rpm_repo/tasks/main.yml15
-rwxr-xr-xtest/integration/targets/strategy_linear/runme.sh2
-rw-r--r--test/integration/targets/subversion/aliases2
-rw-r--r--test/integration/targets/systemd/tasks/test_indirect_service.yml2
-rw-r--r--test/integration/targets/systemd/vars/Debian.yml2
-rwxr-xr-xtest/integration/targets/tags/runme.sh9
-rw-r--r--test/integration/targets/tasks/playbook.yml5
-rwxr-xr-xtest/integration/targets/tasks/runme.sh2
-rwxr-xr-xtest/integration/targets/template/runme.sh7
-rw-r--r--test/integration/targets/template/tasks/main.yml4
-rw-r--r--test/integration/targets/template/unsafe.yml5
-rw-r--r--test/integration/targets/template_jinja2_non_native/macro_override.yml2
-rw-r--r--test/integration/targets/templating/tasks/main.yml11
-rw-r--r--test/integration/targets/test_core/tasks/main.yml13
-rw-r--r--test/integration/targets/unarchive/tasks/main.yml1
-rw-r--r--test/integration/targets/unarchive/tasks/test_different_language_var.yml4
-rw-r--r--test/integration/targets/unarchive/tasks/test_mode.yml23
-rw-r--r--test/integration/targets/unsafe_writes/aliases1
-rw-r--r--test/integration/targets/until/tasks/main.yml34
-rw-r--r--test/integration/targets/unvault/main.yml1
-rwxr-xr-xtest/integration/targets/unvault/runme.sh2
-rw-r--r--test/integration/targets/uri/tasks/main.yml37
-rw-r--r--test/integration/targets/uri/tasks/redirect-none.yml2
-rw-r--r--test/integration/targets/uri/tasks/redirect-urllib2.yml35
-rw-r--r--test/integration/targets/uri/tasks/return-content.yml2
-rw-r--r--test/integration/targets/uri/tasks/use_netrc.yml2
-rw-r--r--test/integration/targets/user/tasks/main.yml2
-rw-r--r--test/integration/targets/user/tasks/test_create_user.yml12
-rw-r--r--test/integration/targets/user/tasks/test_create_user_home.yml18
-rw-r--r--test/integration/targets/user/tasks/test_local.yml40
-rw-r--r--test/integration/targets/user/vars/main.yml2
-rw-r--r--test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml10
-rwxr-xr-xtest/integration/targets/var_precedence/ansible-var-precedence-check.py5
-rw-r--r--test/integration/targets/var_precedence/test_var_precedence.yml16
-rw-r--r--test/integration/targets/wait_for/tasks/main.yml11
-rw-r--r--test/integration/targets/win_exec_wrapper/tasks/main.yml9
-rw-r--r--test/integration/targets/win_fetch/tasks/main.yml14
-rw-r--r--test/integration/targets/win_script/files/test_script_with_args.ps12
-rw-r--r--test/integration/targets/win_script/files/test_script_with_errors.ps12
-rw-r--r--test/integration/targets/windows-minimal/library/win_ping_set_attr.ps18
-rw-r--r--test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps18
-rw-r--r--test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps18
-rw-r--r--test/integration/targets/windows-minimal/library/win_ping_throw.ps18
-rw-r--r--test/integration/targets/windows-minimal/library/win_ping_throw_string.ps18
-rw-r--r--test/integration/targets/yum/aliases1
-rw-r--r--test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py2
-rw-r--r--test/lib/ansible_test/_data/completion/docker.txt18
-rw-r--r--test/lib/ansible_test/_data/completion/remote.txt14
-rw-r--r--test/lib/ansible_test/_data/completion/windows.txt2
-rw-r--r--test/lib/ansible_test/_data/requirements/ansible-test.txt3
-rw-r--r--test/lib/ansible_test/_data/requirements/ansible.txt2
-rw-r--r--test/lib/ansible_test/_data/requirements/constraints.txt2
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt9
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.changelog.in3
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.changelog.txt15
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt6
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.import.txt4
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt4
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.mypy.in10
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.mypy.txt32
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.pep8.txt2
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.pslint.ps14
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.pylint.in2
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.pylint.txt20
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt4
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.validate-modules.in1
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt7
-rw-r--r--test/lib/ansible_test/_data/requirements/sanity.yamllint.txt8
-rw-r--r--test/lib/ansible_test/_data/requirements/units.txt1
-rw-r--r--test/lib/ansible_test/_internal/ci/azp.py8
-rw-r--r--test/lib/ansible_test/_internal/cli/environments.py13
-rw-r--r--test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py8
-rw-r--r--test/lib/ansible_test/_internal/commands/coverage/combine.py2
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/acme.py14
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/cs.py15
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/galaxy.py199
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/httptester.py3
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/nios.py16
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/openshift.py9
-rw-r--r--test/lib/ansible_test/_internal/commands/integration/cloud/vcenter.py94
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/__init__.py31
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/ansible_doc.py40
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/import.py12
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/mypy.py18
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/pylint.py28
-rw-r--r--test/lib/ansible_test/_internal/commands/sanity/validate_modules.py5
-rw-r--r--test/lib/ansible_test/_internal/commands/units/__init__.py4
-rw-r--r--test/lib/ansible_test/_internal/config.py7
-rw-r--r--test/lib/ansible_test/_internal/containers.py67
-rw-r--r--test/lib/ansible_test/_internal/core_ci.py7
-rw-r--r--test/lib/ansible_test/_internal/coverage_util.py11
-rw-r--r--test/lib/ansible_test/_internal/delegation.py1
-rw-r--r--test/lib/ansible_test/_internal/diff.py2
-rw-r--r--test/lib/ansible_test/_internal/docker_util.py11
-rw-r--r--test/lib/ansible_test/_internal/host_profiles.py12
-rw-r--r--test/lib/ansible_test/_internal/http.py2
-rw-r--r--test/lib/ansible_test/_internal/junit_xml.py2
-rw-r--r--test/lib/ansible_test/_internal/pypi_proxy.py2
-rw-r--r--test/lib/ansible_test/_internal/python_requirements.py11
-rw-r--r--test/lib/ansible_test/_internal/util.py9
-rw-r--r--test/lib/ansible_test/_internal/util_common.py11
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/code-smell/no-get-exception.json4
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json4
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py57
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/code-smell/use-compat-six.json4
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/mypy/ansible-core.ini3
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/mypy/ansible-test.ini8
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pep8/current-ignore.txt5
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pslint/settings.psd13
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg4
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg3
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg1
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/config/collection.cfg9
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/config/default.cfg10
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py185
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py41
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py13
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py197
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py2
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py113
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py2
-rw-r--r--test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py6
-rw-r--r--test/lib/ansible_test/_util/controller/tools/collection_detail.py4
-rw-r--r--test/lib/ansible_test/_util/target/common/constants.py4
-rw-r--r--test/lib/ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py46
-rw-r--r--test/lib/ansible_test/_util/target/sanity/import/importer.py12
-rw-r--r--test/lib/ansible_test/_util/target/setup/bootstrap.sh61
-rw-r--r--test/lib/ansible_test/_util/target/setup/quiet_pip.py4
-rw-r--r--test/lib/ansible_test/config/cloud-config-aws.ini.template4
-rw-r--r--test/lib/ansible_test/config/cloud-config-azure.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-cloudscale.ini.template2
-rw-r--r--test/lib/ansible_test/config/cloud-config-cs.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-gcp.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-hcloud.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-opennebula.ini.template5
-rw-r--r--test/lib/ansible_test/config/cloud-config-openshift.kubeconfig.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-scaleway.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-vcenter.ini.template3
-rw-r--r--test/lib/ansible_test/config/cloud-config-vultr.ini.template3
-rw-r--r--test/lib/ansible_test/config/inventory.networking.template3
-rw-r--r--test/lib/ansible_test/config/inventory.winrm.template3
-rw-r--r--test/sanity/code-smell/ansible-requirements.py1
-rw-r--r--test/sanity/code-smell/deprecated-config.requirements.in2
-rw-r--r--test/sanity/code-smell/deprecated-config.requirements.txt6
-rw-r--r--test/sanity/code-smell/obsolete-files.json2
-rw-r--r--test/sanity/code-smell/package-data.requirements.in8
-rw-r--r--test/sanity/code-smell/package-data.requirements.txt25
-rw-r--r--test/sanity/code-smell/release-names.py7
-rw-r--r--test/sanity/code-smell/test-constraints.py6
-rw-r--r--test/sanity/code-smell/update-bundled.requirements.txt3
-rw-r--r--test/sanity/ignore.txt113
-rw-r--r--test/support/README.md2
-rw-r--r--test/support/integration/plugins/modules/sefcontext.py4
-rw-r--r--test/support/integration/plugins/modules/timezone.py4
-rw-r--r--test/support/integration/plugins/modules/zypper.py5
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_get.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_put.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/network.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/network_cli.py3
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/persistent.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py20
-rw-r--r--test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/modules/cli_config.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py6
-rw-r--r--test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/terminal/ios.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py2
-rw-r--r--test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py6
-rw-r--r--test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py2
-rw-r--r--test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/action/win_copy.py4
-rw-r--r--test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps12
-rw-r--r--test/support/windows-integration/plugins/action/win_copy.py4
-rw-r--r--test/support/windows-integration/plugins/action/win_reboot.py7
-rw-r--r--test/support/windows-integration/plugins/modules/win_stat.ps12
-rw-r--r--test/units/_vendor/test_vendor.py19
-rw-r--r--test/units/cli/arguments/test_optparse_helpers.py5
-rw-r--r--test/units/cli/galaxy/test_execute_list_collection.py152
-rw-r--r--test/units/cli/test_adhoc.py10
-rw-r--r--test/units/cli/test_data/collection_skeleton/README.md2
-rw-r--r--test/units/cli/test_data/collection_skeleton/docs/My Collection.md2
-rw-r--r--test/units/cli/test_doc.py3
-rw-r--r--test/units/cli/test_galaxy.py110
-rw-r--r--test/units/cli/test_vault.py23
-rw-r--r--test/units/compat/mock.py2
-rw-r--r--test/units/config/manager/test_find_ini_config_file.py67
-rw-r--r--test/units/config/test_manager.py30
-rw-r--r--test/units/executor/module_common/test_modify_module.py8
-rw-r--r--test/units/executor/module_common/test_module_common.py39
-rw-r--r--test/units/executor/module_common/test_recursive_finder.py5
-rw-r--r--test/units/executor/test_interpreter_discovery.py8
-rw-r--r--test/units/executor/test_play_iterator.py24
-rw-r--r--test/units/executor/test_task_executor.py55
-rw-r--r--test/units/galaxy/test_api.py37
-rw-r--r--test/units/galaxy/test_collection.py161
-rw-r--r--test/units/galaxy/test_collection_install.py156
-rw-r--r--test/units/galaxy/test_role_install.py21
-rw-r--r--test/units/galaxy/test_token.py2
-rw-r--r--test/units/inventory/test_host.py8
-rw-r--r--test/units/mock/loader.py30
-rw-r--r--test/units/mock/procenv.py27
-rw-r--r--test/units/mock/vault_helper.py2
-rw-r--r--test/units/mock/yaml_helper.py73
-rw-r--r--test/units/module_utils/basic/test__symbolic_mode_to_octal.py8
-rw-r--r--test/units/module_utils/basic/test_argument_spec.py2
-rw-r--r--test/units/module_utils/basic/test_command_nonexisting.py5
-rw-r--r--test/units/module_utils/basic/test_filesystem.py2
-rw-r--r--test/units/module_utils/basic/test_run_command.py10
-rw-r--r--test/units/module_utils/basic/test_safe_eval.py2
-rw-r--r--test/units/module_utils/basic/test_sanitize_keys.py1
-rw-r--r--test/units/module_utils/basic/test_selinux.py82
-rw-r--r--test/units/module_utils/basic/test_set_cwd.py7
-rw-r--r--test/units/module_utils/basic/test_tmpdir.py2
-rw-r--r--test/units/module_utils/common/arg_spec/test_aliases.py1
-rw-r--r--test/units/module_utils/common/parameters/test_handle_aliases.py2
-rw-r--r--test/units/module_utils/common/parameters/test_list_deprecations.py11
-rw-r--r--test/units/module_utils/common/test_collections.py21
-rw-r--r--test/units/module_utils/common/text/converters/test_json_encode_fallback.py6
-rw-r--r--test/units/module_utils/common/validation/test_check_missing_parameters.py8
-rw-r--r--test/units/module_utils/common/validation/test_check_mutually_exclusive.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_required_arguments.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_required_by.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_required_if.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_required_one_of.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_required_together.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bits.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bool.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bytes.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_float.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_int.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_jsonarg.py2
-rw-r--r--test/units/module_utils/common/validation/test_check_type_str.py2
-rw-r--r--test/units/module_utils/conftest.py4
-rw-r--r--test/units/module_utils/facts/base.py4
-rw-r--r--test/units/module_utils/facts/hardware/linux_data.py62
-rw-r--r--test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py4
-rw-r--r--test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py6
-rw-r--r--test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py5
-rw-r--r--test/units/module_utils/facts/test_collectors.py5
-rw-r--r--test/units/module_utils/facts/test_date_time.py15
-rw-r--r--test/units/module_utils/facts/test_sysctl.py6
-rw-r--r--test/units/module_utils/facts/test_timeout.py2
-rw-r--r--test/units/module_utils/urls/test_Request.py14
-rw-r--r--test/units/module_utils/urls/test_fetch_file.py1
-rw-r--r--test/units/module_utils/urls/test_prepare_multipart.py2
-rw-r--r--test/units/module_utils/urls/test_urls.py2
-rw-r--r--test/units/modules/conftest.py21
-rw-r--r--test/units/modules/test_apt.py29
-rw-r--r--test/units/modules/test_async_wrapper.py9
-rw-r--r--test/units/modules/test_copy.py23
-rw-r--r--test/units/modules/test_hostname.py10
-rw-r--r--test/units/modules/test_iptables.py40
-rw-r--r--test/units/modules/test_known_hosts.py2
-rw-r--r--test/units/modules/test_unarchive.py20
-rw-r--r--test/units/modules/utils.py10
-rw-r--r--test/units/parsing/test_ajson.py6
-rw-r--r--test/units/parsing/test_dataloader.py13
-rw-r--r--test/units/parsing/test_mod_args.py10
-rw-r--r--test/units/parsing/test_splitter.py75
-rw-r--r--test/units/parsing/vault/test_vault.py43
-rw-r--r--test/units/parsing/vault/test_vault_editor.py79
-rw-r--r--test/units/parsing/yaml/test_dumper.py21
-rw-r--r--test/units/parsing/yaml/test_objects.py7
-rw-r--r--test/units/playbook/role/test_include_role.py6
-rw-r--r--test/units/playbook/role/test_role.py77
-rw-r--r--test/units/playbook/test_base.py20
-rw-r--r--test/units/playbook/test_collectionsearch.py1
-rw-r--r--test/units/playbook/test_helpers.py62
-rw-r--r--test/units/playbook/test_included_file.py14
-rw-r--r--test/units/playbook/test_play_context.py2
-rw-r--r--test/units/playbook/test_taggable.py1
-rw-r--r--test/units/playbook/test_task.py2
-rw-r--r--test/units/plugins/action/test_action.py57
-rw-r--r--test/units/plugins/action/test_raw.py6
-rw-r--r--test/units/plugins/cache/test_cache.py5
-rw-r--r--test/units/plugins/connection/test_connection.py75
-rw-r--r--test/units/plugins/connection/test_local.py1
-rw-r--r--test/units/plugins/connection/test_ssh.py18
-rw-r--r--test/units/plugins/connection/test_winrm.py104
-rw-r--r--test/units/plugins/filter/test_core.py4
-rw-r--r--test/units/plugins/filter/test_mathstuff.py85
-rw-r--r--test/units/plugins/inventory/test_constructed.py10
-rw-r--r--test/units/plugins/inventory/test_inventory.py2
-rw-r--r--test/units/plugins/inventory/test_script.py10
-rw-r--r--test/units/plugins/lookup/test_password.py30
-rw-r--r--test/units/plugins/test_plugins.py10
-rw-r--r--test/units/requirements.txt8
-rw-r--r--test/units/template/test_templar.py14
-rw-r--r--test/units/template/test_vars.py23
-rw-r--r--test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py2
-rw-r--r--test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_other_util.py2
-rw-r--r--test/units/utils/collection_loader/test_collection_loader.py71
-rw-r--r--test/units/utils/display/test_broken_cowsay.py7
-rw-r--r--test/units/utils/test_cleanup_tmp_file.py25
-rw-r--r--test/units/utils/test_display.py35
-rw-r--r--test/units/utils/test_encrypt.py13
-rw-r--r--test/units/utils/test_unsafe_proxy.py28
-rw-r--r--test/units/vars/test_module_response_deepcopy.py11
-rw-r--r--test/units/vars/test_variable_manager.py6
533 files changed, 5466 insertions, 3242 deletions
diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json
index 243a5e43..36f402fc 100644
--- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json
+++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json
@@ -17,7 +17,7 @@
"version": "0.1.1231",
"readme": "README.md",
"license_file": "COPYING",
- "homepage": "",
+ "homepage": ""
},
"file_manifest_file": {
"format": 1,
diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
index caec2ed6..dfc12710 100644
--- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
+++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
@@ -20,7 +20,6 @@ DOCUMENTATION = '''
required: True
'''
-from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable
diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py
index d4569869..639d3c6b 100644
--- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py
+++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py
@@ -32,7 +32,8 @@ RETURN = """
version_added: 1.0.0
"""
-from ansible.module_utils.common._collections_compat import Sequence
+from collections.abc import Sequence
+
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json
index 243a5e43..36f402fc 100644
--- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json
+++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json
@@ -17,7 +17,7 @@
"version": "0.1.1231",
"readme": "README.md",
"license_file": "COPYING",
- "homepage": "",
+ "homepage": ""
},
"file_manifest_file": {
"format": 1,
diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
index cbb8f0fb..1870b8ea 100644
--- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
+++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py
@@ -19,7 +19,6 @@ DOCUMENTATION = '''
required: True
'''
-from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable
diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py
index 79b7a704..aaaecb80 100644
--- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py
+++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py
@@ -3,12 +3,17 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-DOCUMENTATION = '''
+DOCUMENTATION = r'''
---
module: randommodule
short_description: A random module
description:
- A random module.
+ - See O(foo.bar.baz#role:main:foo=bar) for how this is used in the P(foo.bar.baz#role)'s C(main) entrypoint.
+ - See L(the docsite,https://docs.ansible.com/ansible-core/devel/) for more information on ansible-core.
+ - This module is not related to the M(ansible.builtin.copy) module. HORIZONTALLINE You might also be interested
+ in R(ansible_python_interpreter, ansible_python_interpreter).
+ - Sometimes you have M(broken markup) that will result in error messages.
author:
- Ansible Core Team
version_added: 1.0.0
@@ -18,22 +23,22 @@ deprecated:
removed_in: '3.0.0'
options:
test:
- description: Some text.
+ description: Some text. Consider not using O(ignore:foo=bar).
type: str
version_added: 1.2.0
sub:
- description: Suboptions.
+ description: Suboptions. Contains O(sub.subtest), which can be set to V(123). You can use E(TEST_ENV) to set this.
type: dict
suboptions:
subtest:
- description: A suboption.
+ description: A suboption. Not compatible to O(ansible.builtin.copy#module:path=c:\\foo\(1\).txt).
type: int
version_added: 1.1.0
# The following is the wrong syntax, and should not get processed
# by add_collection_to_versions_and_dates()
options:
subtest2:
- description: Another suboption.
+ description: Another suboption. Useful when P(ansible.builtin.shuffle#filter) is used with value V([a,b,\),d\\]).
type: float
version_added: 1.1.0
# The following is not supported in modules, and should not get processed
@@ -65,7 +70,7 @@ seealso:
EXAMPLES = '''
'''
-RETURN = '''
+RETURN = r'''
z_last:
description: A last result.
type: str
@@ -75,7 +80,8 @@ z_last:
m_middle:
description:
- This should be in the middle.
- - Has some more data
+ - Has some more data.
+ - Check out RV(m_middle.suboption) and compare it to RV(a_first=foo) and RV(community.general.foo#lookup:value).
type: dict
returned: success and 1st of month
contains:
@@ -86,7 +92,7 @@ m_middle:
version_added: 1.4.0
a_first:
- description: A first result.
+ description: A first result. Use RV(a_first=foo\(bar\\baz\)bam).
type: str
returned: success
'''
diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml
index cc60945e..ebfea2af 100644
--- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml
+++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml
@@ -8,6 +8,25 @@ DOCUMENTATION:
description: does not matter
type: raw
required: true
+ seealso:
+ - module: ansible.builtin.test
+ - module: testns.testcol.fakemodule
+ description: A fake module
+ - plugin: testns.testcol.noop
+ plugin_type: lookup
+ - plugin: testns.testcol.grouped
+ plugin_type: filter
+ description: A grouped filter.
+ - plugin: ansible.builtin.combine
+ plugin_type: filter
+ - plugin: ansible.builtin.file
+ plugin_type: lookup
+ description: Read a file on the controller.
+ - link: https://docs.ansible.com
+ name: Ansible docsite
+ description: See also the Ansible docsite.
+ - ref: foo_bar
+ description: Some foo bar.
EXAMPLES: |
{{ 'anything' is yolo }}
diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json
index 02ec289f..e930d7d8 100644
--- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json
+++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json
@@ -17,7 +17,7 @@
"version": "1.2.0",
"readme": "README.md",
"license_file": "COPYING",
- "homepage": "",
+ "homepage": ""
},
"file_manifest_file": {
"format": 1,
diff --git a/test/integration/targets/ansible-doc/randommodule-text.output b/test/integration/targets/ansible-doc/randommodule-text.output
index 602d66ec..ca361346 100644
--- a/test/integration/targets/ansible-doc/randommodule-text.output
+++ b/test/integration/targets/ansible-doc/randommodule-text.output
@@ -1,6 +1,13 @@
> TESTNS.TESTCOL.RANDOMMODULE (./collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py)
- A random module.
+ A random module. See `foo=bar' (of role foo.bar.baz, main
+ entrypoint) for how this is used in the [foo.bar.baz]'s `main'
+ entrypoint. See the docsite <https://docs.ansible.com/ansible-
+ core/devel/> for more information on ansible-core. This module
+ is not related to the [ansible.builtin.copy] module.
+ ------------- You might also be interested in
+ ansible_python_interpreter. Sometimes you have [broken markup]
+ that will result in error messages.
ADDED IN: version 1.0.0 of testns.testcol
@@ -14,7 +21,8 @@ DEPRECATED:
OPTIONS (= is mandatory):
- sub
- Suboptions.
+ Suboptions. Contains `sub.subtest', which can be set to `123'.
+ You can use `TEST_ENV' to set this.
set_via:
env:
- deprecated:
@@ -29,7 +37,8 @@ OPTIONS (= is mandatory):
OPTIONS:
- subtest2
- Another suboption.
+ Another suboption. Useful when [ansible.builtin.shuffle]
+ is used with value `[a,b,),d\]'.
default: null
type: float
added in: version 1.1.0
@@ -39,14 +48,15 @@ OPTIONS (= is mandatory):
SUBOPTIONS:
- subtest
- A suboption.
+ A suboption. Not compatible to `path=c:\foo(1).txt' (of
+ module ansible.builtin.copy).
default: null
type: int
added in: version 1.1.0 of testns.testcol
- test
- Some text.
+ Some text. Consider not using `foo=bar'.
default: null
type: str
added in: version 1.2.0 of testns.testcol
@@ -93,13 +103,15 @@ EXAMPLES:
RETURN VALUES:
- a_first
- A first result.
+ A first result. Use `a_first=foo(bar\baz)bam'.
returned: success
type: str
- m_middle
This should be in the middle.
- Has some more data
+ Has some more data.
+ Check out `m_middle.suboption' and compare it to `a_first=foo'
+ and `value' (of lookup plugin community.general.foo).
returned: success and 1st of month
type: dict
diff --git a/test/integration/targets/ansible-doc/randommodule.output b/test/integration/targets/ansible-doc/randommodule.output
index cf036000..f40202a8 100644
--- a/test/integration/targets/ansible-doc/randommodule.output
+++ b/test/integration/targets/ansible-doc/randommodule.output
@@ -12,14 +12,18 @@
"why": "Test deprecation"
},
"description": [
- "A random module."
+ "A random module.",
+ "See O(foo.bar.baz#role:main:foo=bar) for how this is used in the P(foo.bar.baz#role)'s C(main) entrypoint.",
+ "See L(the docsite,https://docs.ansible.com/ansible-core/devel/) for more information on ansible-core.",
+ "This module is not related to the M(ansible.builtin.copy) module. HORIZONTALLINE You might also be interested in R(ansible_python_interpreter, ansible_python_interpreter).",
+ "Sometimes you have M(broken markup) that will result in error messages."
],
"filename": "./collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py",
"has_action": false,
"module": "randommodule",
"options": {
"sub": {
- "description": "Suboptions.",
+ "description": "Suboptions. Contains O(sub.subtest), which can be set to V(123). You can use E(TEST_ENV) to set this.",
"env": [
{
"deprecated": {
@@ -34,14 +38,14 @@
],
"options": {
"subtest2": {
- "description": "Another suboption.",
+ "description": "Another suboption. Useful when P(ansible.builtin.shuffle#filter) is used with value V([a,b,\\),d\\\\]).",
"type": "float",
"version_added": "1.1.0"
}
},
"suboptions": {
"subtest": {
- "description": "A suboption.",
+ "description": "A suboption. Not compatible to O(ansible.builtin.copy#module:path=c:\\\\foo\\(1\\).txt).",
"type": "int",
"version_added": "1.1.0",
"version_added_collection": "testns.testcol"
@@ -50,7 +54,7 @@
"type": "dict"
},
"test": {
- "description": "Some text.",
+ "description": "Some text. Consider not using O(ignore:foo=bar).",
"type": "str",
"version_added": "1.2.0",
"version_added_collection": "testns.testcol"
@@ -103,7 +107,7 @@
"metadata": null,
"return": {
"a_first": {
- "description": "A first result.",
+ "description": "A first result. Use RV(a_first=foo\\(bar\\\\baz\\)bam).",
"returned": "success",
"type": "str"
},
@@ -123,7 +127,8 @@
},
"description": [
"This should be in the middle.",
- "Has some more data"
+ "Has some more data.",
+ "Check out RV(m_middle.suboption) and compare it to RV(a_first=foo) and RV(community.general.foo#lookup:value)."
],
"returned": "success and 1st of month",
"type": "dict"
diff --git a/test/integration/targets/ansible-doc/runme.sh b/test/integration/targets/ansible-doc/runme.sh
index f51fa8a4..b525766c 100755
--- a/test/integration/targets/ansible-doc/runme.sh
+++ b/test/integration/targets/ansible-doc/runme.sh
@@ -1,36 +1,74 @@
#!/usr/bin/env bash
-set -eux
+# always set sane error behaviors, enable execution tracing later if sufficient verbosity requested
+set -eu
+
+verbosity=0
+
+# default to silent output for naked grep; -vvv+ will adjust this
+export GREP_OPTS=-q
+
+# shell tracing output is very large from this script; only enable if >= -vvv was passed
+while getopts :v opt
+do case "$opt" in
+ v) ((verbosity+=1)) ;;
+ *) ;;
+ esac
+done
+
+if (( verbosity >= 3 ));
+then
+ set -x;
+ export GREP_OPTS= ;
+fi
+
+echo "running playbook-backed docs tests"
ansible-playbook test.yml -i inventory "$@"
# test keyword docs
-ansible-doc -t keyword -l | grep 'vars_prompt: list of variables to prompt for.'
-ansible-doc -t keyword vars_prompt | grep 'description: list of variables to prompt for.'
-ansible-doc -t keyword asldkfjaslidfhals 2>&1 | grep 'Skipping Invalid keyword'
+ansible-doc -t keyword -l | grep $GREP_OPTS 'vars_prompt: list of variables to prompt for.'
+ansible-doc -t keyword vars_prompt | grep $GREP_OPTS 'description: list of variables to prompt for.'
+ansible-doc -t keyword asldkfjaslidfhals 2>&1 | grep $GREP_OPTS 'Skipping Invalid keyword'
# collections testing
(
unset ANSIBLE_PLAYBOOK_DIR
cd "$(dirname "$0")"
-# test module docs from collection
+
+echo "test fakemodule docs from collection"
# we use sed to strip the module path from the first line
current_out="$(ansible-doc --playbook-dir ./ testns.testcol.fakemodule | sed '1 s/\(^> TESTNS\.TESTCOL\.FAKEMODULE\).*(.*)$/\1/')"
expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.FAKEMODULE\).*(.*)$/\1/' fakemodule.output)"
test "$current_out" == "$expected_out"
+echo "test randommodule docs from collection"
# we use sed to strip the plugin path from the first line, and fix-urls.py to unbreak and replace URLs from stable-X branches
current_out="$(ansible-doc --playbook-dir ./ testns.testcol.randommodule | sed '1 s/\(^> TESTNS\.TESTCOL\.RANDOMMODULE\).*(.*)$/\1/' | python fix-urls.py)"
expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.RANDOMMODULE\).*(.*)$/\1/' randommodule-text.output)"
test "$current_out" == "$expected_out"
-# ensure we do work with valid collection name for list
-ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep -v "Invalid collection name"
+echo "test yolo filter docs from collection"
+# we use sed to strip the plugin path from the first line, and fix-urls.py to unbreak and replace URLs from stable-X branches
+current_out="$(ansible-doc --playbook-dir ./ testns.testcol.yolo --type test | sed '1 s/\(^> TESTNS\.TESTCOL\.YOLO\).*(.*)$/\1/' | python fix-urls.py)"
+expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.YOLO\).*(.*)$/\1/' yolo-text.output)"
+test "$current_out" == "$expected_out"
+
+echo "ensure we do work with valid collection name for list"
+ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep $GREP_OPTS -v "Invalid collection name"
-# ensure we dont break on invalid collection name for list
-ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep "Invalid collection name"
+echo "ensure we dont break on invalid collection name for list"
+ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep $GREP_OPTS "Invalid collection name"
-# test listing diff plugin types from collection
+echo "filter list with more than one collection (1/2)"
+output=$(ansible-doc --list testns.testcol3 testns.testcol4 --playbook-dir ./ 2>&1 | wc -l)
+test "$output" -eq 2
+
+echo "filter list with more than one collection (2/2)"
+output=$(ansible-doc --list testns.testcol testns.testcol4 --playbook-dir ./ 2>&1 | wc -l)
+test "$output" -eq 5
+
+echo "testing ansible-doc output for various plugin types"
for ptype in cache inventory lookup vars filter module
do
# each plugin type adds 1 from collection
@@ -50,20 +88,20 @@ do
elif [ "${ptype}" == "lookup" ]; then expected_names=("noop");
elif [ "${ptype}" == "vars" ]; then expected_names=("noop_vars_plugin"); fi
fi
- # ensure we ONLY list from the collection
+ echo "testing collection-filtered list for plugin ${ptype}"
justcol=$(ansible-doc -l -t ${ptype} --playbook-dir ./ testns.testcol|wc -l)
test "$justcol" -eq "$expected"
- # ensure the right names are displayed
+ echo "validate collection plugin name display for plugin ${ptype}"
list_result=$(ansible-doc -l -t ${ptype} --playbook-dir ./ testns.testcol)
metadata_result=$(ansible-doc --metadata-dump --no-fail-on-errors -t ${ptype} --playbook-dir ./ testns.testcol)
for name in "${expected_names[@]}"; do
- echo "${list_result}" | grep "testns.testcol.${name}"
- echo "${metadata_result}" | grep "testns.testcol.${name}"
+ echo "${list_result}" | grep $GREP_OPTS "testns.testcol.${name}"
+ echo "${metadata_result}" | grep $GREP_OPTS "testns.testcol.${name}"
done
- # ensure we get error if passinginvalid collection, much less any plugins
- ansible-doc -l -t ${ptype} testns.testcol 2>&1 | grep "unable to locate collection"
+ # ensure we get error if passing invalid collection, much less any plugins
+ ansible-doc -l -t ${ptype} bogus.boguscoll 2>&1 | grep $GREP_OPTS "unable to locate collection"
# TODO: do we want per namespace?
# ensure we get 1 plugins when restricting namespace
@@ -73,20 +111,28 @@ done
#### test role functionality
-# Test role text output
+echo "testing role text output"
# we use sed to strip the role path from the first line
current_role_out="$(ansible-doc -t role -r ./roles test_role1 | sed '1 s/\(^> TEST_ROLE1\).*(.*)$/\1/')"
expected_role_out="$(sed '1 s/\(^> TEST_ROLE1\).*(.*)$/\1/' fakerole.output)"
test "$current_role_out" == "$expected_role_out"
+echo "testing multiple role entrypoints"
# Two collection roles are defined, but only 1 has a role arg spec with 2 entry points
output=$(ansible-doc -t role -l --playbook-dir . testns.testcol | wc -l)
test "$output" -eq 2
+echo "test listing roles with multiple collection filters"
+# Two collection roles are defined, but only 1 has a role arg spec with 2 entry points
+output=$(ansible-doc -t role -l --playbook-dir . testns.testcol2 testns.testcol | wc -l)
+test "$output" -eq 2
+
+echo "testing standalone roles"
# Include normal roles (no collection filter)
output=$(ansible-doc -t role -l --playbook-dir . | wc -l)
test "$output" -eq 3
+echo "testing role precedence"
# Test that a role in the playbook dir with the same name as a role in the
# 'roles' subdir of the playbook dir does not appear (lower precedence).
output=$(ansible-doc -t role -l --playbook-dir . | grep -c "test_role1 from roles subdir")
@@ -94,7 +140,7 @@ test "$output" -eq 1
output=$(ansible-doc -t role -l --playbook-dir . | grep -c "test_role1 from playbook dir" || true)
test "$output" -eq 0
-# Test entry point filter
+echo "testing role entrypoint filter"
current_role_out="$(ansible-doc -t role --playbook-dir . testns.testcol.testrole -e alternate| sed '1 s/\(^> TESTNS\.TESTCOL\.TESTROLE\).*(.*)$/\1/')"
expected_role_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.TESTROLE\).*(.*)$/\1/' fakecollrole.output)"
test "$current_role_out" == "$expected_role_out"
@@ -103,10 +149,16 @@ test "$current_role_out" == "$expected_role_out"
#### test add_collection_to_versions_and_dates()
+echo "testing json output"
current_out="$(ansible-doc --json --playbook-dir ./ testns.testcol.randommodule | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')"
expected_out="$(sed 's/ *"filename": "[^"]*",$//' randommodule.output)"
test "$current_out" == "$expected_out"
+echo "testing json output 2"
+current_out="$(ansible-doc --json --playbook-dir ./ testns.testcol.yolo --type test | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')"
+expected_out="$(sed 's/ *"filename": "[^"]*",$//' yolo.output)"
+test "$current_out" == "$expected_out"
+
current_out="$(ansible-doc --json --playbook-dir ./ -t cache testns.testcol.notjsonfile | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')"
expected_out="$(sed 's/ *"filename": "[^"]*",$//' notjsonfile.output)"
test "$current_out" == "$expected_out"
@@ -119,8 +171,9 @@ current_out="$(ansible-doc --json --playbook-dir ./ -t vars testns.testcol.noop_
expected_out="$(sed 's/ *"filename": "[^"]*",$//' noop_vars_plugin.output)"
test "$current_out" == "$expected_out"
+echo "testing metadata dump"
# just ensure it runs
-ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir /dev/null >/dev/null
+ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir /dev/null 1>/dev/null 2>&1
# create broken role argument spec
mkdir -p broken-docs/collections/ansible_collections/testns/testcol/roles/testrole/meta
@@ -144,71 +197,72 @@ argument_specs:
EOF
# ensure that --metadata-dump does not fail when --no-fail-on-errors is supplied
-ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --no-fail-on-errors --playbook-dir broken-docs testns.testcol >/dev/null
+ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --no-fail-on-errors --playbook-dir broken-docs testns.testcol 1>/dev/null 2>&1
# ensure that --metadata-dump does fail when --no-fail-on-errors is not supplied
output=$(ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir broken-docs testns.testcol 2>&1 | grep -c 'ERROR!' || true)
test "${output}" -eq 1
-# ensure we list the 'legacy plugins'
+
+echo "testing legacy plugin listing"
[ "$(ansible-doc -M ./library -l ansible.legacy |wc -l)" -gt "0" ]
-# playbook dir should work the same
+echo "testing legacy plugin list via --playbook-dir"
[ "$(ansible-doc -l ansible.legacy --playbook-dir ./|wc -l)" -gt "0" ]
-# see that we show undocumented when missing docs
+echo "testing undocumented plugin output"
[ "$(ansible-doc -M ./library -l ansible.legacy |grep -c UNDOCUMENTED)" == "6" ]
-# ensure filtering works and does not include any 'test_' modules
+echo "testing filtering does not include any 'test_' modules"
[ "$(ansible-doc -M ./library -l ansible.builtin |grep -c test_)" == 0 ]
[ "$(ansible-doc --playbook-dir ./ -l ansible.builtin |grep -c test_)" == 0 ]
-# ensure filtering still shows modules
+echo "testing filtering still shows modules"
count=$(ANSIBLE_LIBRARY='./nolibrary' ansible-doc -l ansible.builtin |wc -l)
[ "${count}" -gt "0" ]
[ "$(ansible-doc -M ./library -l ansible.builtin |wc -l)" == "${count}" ]
[ "$(ansible-doc --playbook-dir ./ -l ansible.builtin |wc -l)" == "${count}" ]
-# produce 'sidecar' docs for test
+echo "testing sidecar docs for jinja plugins"
[ "$(ansible-doc -t test --playbook-dir ./ testns.testcol.yolo| wc -l)" -gt "0" ]
[ "$(ansible-doc -t filter --playbook-dir ./ donothing| wc -l)" -gt "0" ]
[ "$(ansible-doc -t filter --playbook-dir ./ ansible.legacy.donothing| wc -l)" -gt "0" ]
-# no docs and no sidecar
-ansible-doc -t filter --playbook-dir ./ nodocs 2>&1| grep -c 'missing documentation' || true
+echo "testing no docs and no sidecar"
+ansible-doc -t filter --playbook-dir ./ nodocs 2>&1| grep $GREP_OPTS -c 'missing documentation' || true
-# produce 'sidecar' docs for module
+echo "testing sidecar docs for module"
[ "$(ansible-doc -M ./library test_win_module| wc -l)" -gt "0" ]
[ "$(ansible-doc --playbook-dir ./ test_win_module| wc -l)" -gt "0" ]
-# test 'double DOCUMENTATION' use
+echo "testing duplicate DOCUMENTATION"
[ "$(ansible-doc --playbook-dir ./ double_doc| wc -l)" -gt "0" ]
-# don't break on module dir
+echo "testing don't break on module dir"
ansible-doc --list --module-path ./modules > /dev/null
-# ensure we dedupe by fqcn and not base name
+echo "testing dedupe by fqcn and not base name"
[ "$(ansible-doc -l -t filter --playbook-dir ./ |grep -c 'b64decode')" -eq "3" ]
-# ensure we don't show duplicates for plugins that only exist in ansible.builtin when listing ansible.legacy plugins
+echo "testing no duplicates for plugins that only exist in ansible.builtin when listing ansible.legacy plugins"
[ "$(ansible-doc -l -t filter --playbook-dir ./ |grep -c 'b64encode')" -eq "1" ]
-# with playbook dir, legacy should override
-ansible-doc -t filter split --playbook-dir ./ |grep histerical
+echo "testing with playbook dir, legacy should override"
+ansible-doc -t filter split --playbook-dir ./ |grep $GREP_OPTS histerical
pyc_src="$(pwd)/filter_plugins/other.py"
pyc_1="$(pwd)/filter_plugins/split.pyc"
pyc_2="$(pwd)/library/notaplugin.pyc"
trap 'rm -rf "$pyc_1" "$pyc_2"' EXIT
-# test pyc files are not used as adjacent documentation
+echo "testing pyc files are not used as adjacent documentation"
python -c "import py_compile; py_compile.compile('$pyc_src', cfile='$pyc_1')"
-ansible-doc -t filter split --playbook-dir ./ |grep histerical
+ansible-doc -t filter split --playbook-dir ./ |grep $GREP_OPTS histerical
-# test pyc files are not listed as plugins
+echo "testing pyc files are not listed as plugins"
python -c "import py_compile; py_compile.compile('$pyc_src', cfile='$pyc_2')"
test "$(ansible-doc -l -t module --playbook-dir ./ 2>&1 1>/dev/null |grep -c "notaplugin")" == 0
-# without playbook dir, builtin should return
-ansible-doc -t filter split |grep -v histerical
+echo "testing without playbook dir, builtin should return"
+ansible-doc -t filter split 2>&1 |grep $GREP_OPTS -v histerical
diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt b/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt
index 110009e3..69218290 100644
--- a/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt
+++ b/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt
@@ -1,6 +1,11 @@
MANIFEST.json
FILES.json
README.rst
+GPL
+LICENSES/
+LICENSES/MIT.txt
+.reuse/
+.reuse/dep5
changelogs/
docs/
playbooks/
@@ -88,6 +93,7 @@ plugins/test/bar.yml
plugins/test/baz.yaml
plugins/test/test.py
plugins/vars/bar.yml
+plugins/vars/bar.yml.license
plugins/vars/baz.yaml
plugins/vars/test.py
roles/foo/
diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml b/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml
index 8f0ada0b..140bf2a7 100644
--- a/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml
+++ b/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml
@@ -2,6 +2,7 @@ namespace: ns
name: col
version: 1.0.0
readme: README.rst
+license_file: GPL
authors:
- Ansible
manifest:
diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py b/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py
index 913a6f79..60c43cc7 100644
--- a/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py
+++ b/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py
@@ -5,8 +5,12 @@ paths = [
'ns-col-1.0.0.tar.gz',
'foo.txt',
'README.rst',
+ 'GPL',
+ 'LICENSES/MIT.txt',
+ '.reuse/dep5',
'artifacts/.gitkeep',
'plugins/vars/bar.yml',
+ 'plugins/vars/bar.yml.license',
'plugins/vars/baz.yaml',
'plugins/vars/test.py',
'plugins/vars/docs.md',
diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml
index dab599b1..f0e78ca0 100644
--- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml
+++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml
@@ -5,7 +5,7 @@
- name: Test installing collections from git repositories
environment:
- ANSIBLE_COLLECTIONS_PATHS: "{{ galaxy_dir }}/collections"
+ ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/collections"
vars:
cleanup: True
galaxy_dir: "{{ galaxy_dir }}"
diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml
index f22f9844..91ed9124 100644
--- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml
+++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml
@@ -14,6 +14,8 @@
command: 'ansible-galaxy collection install {{ artifact_path }} -p {{ alt_install_path }} --no-deps'
vars:
artifact_path: "{{ galaxy_dir }}/ansible_test-collection_1-1.0.0.tar.gz"
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: ""
- name: check if the files and folders in build_ignore were respected
stat:
diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml
index dd307d72..520dbe5c 100644
--- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml
+++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml
@@ -22,7 +22,12 @@
lineinfile:
path: '{{ scm_path }}/namespace_2/collection_2/galaxy.yml'
regexp: '^dependencies'
- line: "dependencies: {'git+file://{{ scm_path }}/namespace_1/.git#collection_1/': 'master'}"
+ # NOTE: The committish is set to `HEAD` here because Git's default has
+ # NOTE: changed to `main` and it behaves differently in
+ # NOTE: different envs with different Git versions.
+ line: >-
+ dependencies:
+ {'git+file://{{ scm_path }}/namespace_1/.git#collection_1/': 'HEAD'}
- name: Commit the changes
shell: git add ./; git commit -m 'add collection'
diff --git a/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py b/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py
index 53c29f77..c1f5e1d7 100644
--- a/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py
+++ b/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py
@@ -84,7 +84,8 @@ def invoke_api(module, url, method='GET', data=None, status_codes=None):
resp, info = fetch_url(module, url, method=method, data=data, headers=headers)
if info['status'] not in status_codes:
- module.fail_json(url=url, **info)
+ info['url'] = url
+ module.fail_json(**info)
data = to_text(resp.read())
if data:
@@ -105,7 +106,7 @@ def delete_pulp_distribution(distribution, module):
def delete_pulp_orphans(module):
""" Deletes any orphaned pulp objects. """
- orphan_uri = module.params['pulp_api'] + '/pulp/api/v3/orphans/'
+ orphan_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/orphans/'
task_info = invoke_api(module, orphan_uri, method='DELETE', status_codes=[202])
wait_pulp_task(task_info['task'], module)
@@ -125,25 +126,39 @@ def get_galaxy_namespaces(module):
return [n['name'] for n in ns_info['data']]
-def get_pulp_distributions(module):
+def get_pulp_distributions(module, distribution):
""" Gets a list of all the pulp distributions. """
- distro_uri = module.params['pulp_api'] + '/pulp/api/v3/distributions/ansible/ansible/'
- distro_info = invoke_api(module, distro_uri)
+ distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/'
+ distro_info = invoke_api(module, distro_uri + '?name=' + distribution)
return [module.params['pulp_api'] + r['pulp_href'] for r in distro_info['results']]
-def get_pulp_repositories(module):
+def get_pulp_repositories(module, repository):
""" Gets a list of all the pulp repositories. """
- repo_uri = module.params['pulp_api'] + '/pulp/api/v3/repositories/ansible/ansible/'
- repo_info = invoke_api(module, repo_uri)
+ repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/'
+ repo_info = invoke_api(module, repo_uri + '?name=' + repository)
return [module.params['pulp_api'] + r['pulp_href'] for r in repo_info['results']]
+def get_repo_collections(repository, module):
+ collections_uri = module.params['galaxy_ng_server'] + 'v3/plugin/ansible/content/' + repository + '/collections/index/'
+ # status code 500 isn't really expected, an unhandled exception is causing this instead of a 404
+ # See https://issues.redhat.com/browse/AAH-2329
+ info = invoke_api(module, collections_uri + '?limit=100&offset=0', status_codes=[200, 500])
+ if not info:
+ return []
+ return [module.params['pulp_api'] + c['href'] for c in info['data']]
+
+
+def delete_repo_collection(collection, module):
+ task_info = invoke_api(module, collection, method='DELETE', status_codes=[202])
+ wait_pulp_task(task_info['task'], module)
+
+
def new_galaxy_namespace(name, module):
""" Creates a new namespace in Galaxy NG. """
- ns_uri = module.params['galaxy_ng_server'] + 'v3/_ui/namespaces/'
- data = {'name': name, 'groups': [{'name': 'system:partner-engineers', 'object_permissions':
- ['add_namespace', 'change_namespace', 'upload_to_namespace']}]}
+ ns_uri = module.params['galaxy_ng_server'] + 'v3/namespaces/ '
+ data = {'name': name, 'groups': []}
ns_info = invoke_api(module, ns_uri, method='POST', data=data, status_codes=[201])
return ns_info['id']
@@ -151,16 +166,17 @@ def new_galaxy_namespace(name, module):
def new_pulp_repository(name, module):
""" Creates a new pulp repository. """
- repo_uri = module.params['pulp_api'] + '/pulp/api/v3/repositories/ansible/ansible/'
- data = {'name': name}
+ repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/'
+ # retain_repo_versions to work around https://issues.redhat.com/browse/AAH-2332
+ data = {'name': name, 'retain_repo_versions': '1024'}
repo_info = invoke_api(module, repo_uri, method='POST', data=data, status_codes=[201])
- return module.params['pulp_api'] + repo_info['pulp_href']
+ return repo_info['pulp_href']
def new_pulp_distribution(name, base_path, repository, module):
""" Creates a new pulp distribution for a repository. """
- distro_uri = module.params['pulp_api'] + '/pulp/api/v3/distributions/ansible/ansible/'
+ distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/'
data = {'name': name, 'base_path': base_path, 'repository': repository}
task_info = invoke_api(module, distro_uri, method='POST', data=data, status_codes=[202])
task_info = wait_pulp_task(task_info['task'], module)
@@ -194,8 +210,15 @@ def main():
)
module.params['force_basic_auth'] = True
- [delete_pulp_distribution(d, module) for d in get_pulp_distributions(module)]
- [delete_pulp_repository(r, module) for r in get_pulp_repositories(module)]
+ # It may be due to the process of cleaning up orphans, but we cannot delete the namespace
+ # while a collection still exists, so this is just a new safety to nuke all collections
+ # first
+ for repository in module.params['repositories']:
+ [delete_repo_collection(c, module) for c in get_repo_collections(repository, module)]
+
+ for repository in module.params['repositories']:
+ [delete_pulp_distribution(d, module) for d in get_pulp_distributions(module, repository)]
+ [delete_pulp_repository(r, module) for r in get_pulp_repositories(module, repository)]
delete_pulp_orphans(module)
[delete_galaxy_namespace(n, module) for n in get_galaxy_namespaces(module)]
diff --git a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py
index f4a51c4b..423edd9e 100644
--- a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py
+++ b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py
@@ -77,6 +77,7 @@ RETURN = '''
#
'''
+import datetime
import os
import subprocess
import tarfile
@@ -84,13 +85,13 @@ import tempfile
import yaml
from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from functools import partial
from multiprocessing import dummy as threading
from multiprocessing import TimeoutError
-COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT = 120
+COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT = 180
def publish_collection(module, collection):
@@ -104,6 +105,7 @@ def publish_collection(module, collection):
collection_dir = os.path.join(module.tmpdir, "%s-%s-%s" % (namespace, name, version))
b_collection_dir = to_bytes(collection_dir, errors='surrogate_or_strict')
os.mkdir(b_collection_dir)
+ os.mkdir(os.path.join(b_collection_dir, b'meta'))
with open(os.path.join(b_collection_dir, b'README.md'), mode='wb') as fd:
fd.write(b"Collection readme")
@@ -120,6 +122,8 @@ def publish_collection(module, collection):
}
with open(os.path.join(b_collection_dir, b'galaxy.yml'), mode='wb') as fd:
fd.write(to_bytes(yaml.safe_dump(galaxy_meta), errors='surrogate_or_strict'))
+ with open(os.path.join(b_collection_dir, b'meta/runtime.yml'), mode='wb') as fd:
+ fd.write(b'requires_ansible: ">=1.0.0"')
with tempfile.NamedTemporaryFile(mode='wb') as temp_fd:
temp_fd.write(b"data")
@@ -246,7 +250,8 @@ def run_module():
supports_check_mode=False
)
- result = dict(changed=True, results=[])
+ start = datetime.datetime.now()
+ result = dict(changed=True, results=[], start=str(start))
pool = threading.Pool(4)
publish_func = partial(publish_collection, module)
@@ -263,7 +268,9 @@ def run_module():
r['build']['rc'] + r['publish']['rc'] for r in result['results']
))
- module.exit_json(failed=failed, **result)
+ end = datetime.datetime.now()
+ delta = end - start
+ module.exit_json(failed=failed, end=str(end), delta=str(delta), **result)
def main():
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/build.yml b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml
index 8140d468..83e9acc9 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/build.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml
@@ -1,4 +1,29 @@
---
+- name: create a dangling symbolic link inside collection directory
+ ansible.builtin.file:
+ src: '/non-existent-path/README.md'
+ dest: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/docs/README.md'
+ state: link
+ force: yes
+
+- name: build basic collection based on current directory with dangling symlink
+ command: ansible-galaxy collection build {{ galaxy_verbosity }}
+ args:
+ chdir: '{{ galaxy_dir }}/scratch/ansible_test/my_collection'
+ register: fail_symlink_build
+ ignore_errors: yes
+
+- name: assert that build fails due to dangling symlink
+ assert:
+ that:
+ - fail_symlink_build.failed
+ - '"Failed to find the target path" in fail_symlink_build.stderr'
+
+- name: remove dangling symbolic link
+ ansible.builtin.file:
+ path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/docs/README.md'
+ state: absent
+
- name: build basic collection based on current directory
command: ansible-galaxy collection build {{ galaxy_verbosity }}
args:
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/download.yml b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml
index b651a73e..a554c277 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/download.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml
@@ -5,7 +5,7 @@
state: directory
- name: download collection with multiple dependencies with --no-deps
- command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 --no-deps -s pulp_v2 {{ galaxy_verbosity }}
+ command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 --no-deps -s galaxy_ng {{ galaxy_verbosity }}
register: download_collection
args:
chdir: '{{ galaxy_dir }}/download'
@@ -34,7 +34,7 @@
- (download_collection_actual.files[1].path | basename) in ['requirements.yml', 'parent_dep-parent_collection-1.0.0.tar.gz']
- name: download collection with multiple dependencies
- command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 -s pulp_v2 {{ galaxy_verbosity }}
+ command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 -s galaxy_ng {{ galaxy_verbosity }}
register: download_collection
args:
chdir: '{{ galaxy_dir }}/download'
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml
index eb471f8e..d861cb4d 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml
@@ -1,5 +1,5 @@
# resolvelib>=0.6.0 added an 'incompatibilities' parameter to find_matches
-# If incompatibilities aren't removed from the viable candidates, this example causes infinite resursion
+# If incompatibilities aren't removed from the viable candidates, this example causes infinite recursion
- name: test resolvelib removes incompatibilites in find_matches and errors quickly (prevent infinite recursion)
block:
- name: create collection dir
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/init.yml b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml
index 17a000db..46198fef 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/init.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml
@@ -5,6 +5,12 @@
chdir: '{{ galaxy_dir }}/scratch'
register: init_relative
+- name: create required runtime.yml
+ copy:
+ content: |
+ requires_ansible: '>=1.0.0'
+ dest: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/meta/runtime.yml'
+
- name: get result of create default skeleton
find:
path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection'
@@ -92,6 +98,65 @@
- (init_custom_path_actual.files | map(attribute='path') | list)[2] | basename in ['docs', 'plugins', 'roles', 'meta']
- (init_custom_path_actual.files | map(attribute='path') | list)[3] | basename in ['docs', 'plugins', 'roles', 'meta']
+- name: test using a custom skeleton for collection init
+ block:
+ - name: create skeleton directories
+ file:
+ path: "{{ galaxy_dir }}/scratch/skeleton/{{ item }}"
+ state: directory
+ loop:
+ - custom_skeleton
+ - custom_skeleton/plugins
+ - inventory
+
+ - name: create files
+ file:
+ path: "{{ galaxy_dir }}/scratch/skeleton/{{ item }}"
+ state: touch
+ loop:
+ - inventory/foo.py
+ - galaxy.yml
+
+ - name: create symlinks
+ file:
+ path: "{{ galaxy_dir }}/scratch/skeleton/{{ item.link }}"
+ src: "{{ galaxy_dir }}/scratch/skeleton/{{ item.source }}"
+ state: link
+ loop:
+ - link: custom_skeleton/plugins/inventory
+ source: inventory
+ - link: custom_skeleton/galaxy.yml
+ source: galaxy.yml
+
+ - name: initialize a collection using the skeleton
+ command: ansible-galaxy collection init ansible_test.my_collection {{ init_path }} {{ skeleton }}
+ vars:
+ init_path: '--init-path {{ galaxy_dir }}/scratch/skeleton'
+ skeleton: '--collection-skeleton {{ galaxy_dir }}/scratch/skeleton/custom_skeleton'
+
+ - name: stat expected collection contents
+ stat:
+ path: "{{ galaxy_dir }}/scratch/skeleton/ansible_test/my_collection/{{ item }}"
+ register: stat_result
+ loop:
+ - plugins
+ - plugins/inventory
+ - galaxy.yml
+ - plugins/inventory/foo.py
+
+ - assert:
+ that:
+ - stat_result.results[0].stat.isdir
+ - stat_result.results[1].stat.islnk
+ - stat_result.results[2].stat.islnk
+ - stat_result.results[3].stat.isreg
+
+ always:
+ - name: cleanup
+ file:
+ path: "{{ galaxy_dir }}/scratch/skeleton"
+ state: absent
+
- name: create collection for ignored files and folders
command: ansible-galaxy collection init ansible_test.ignore
args:
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml
index cca83c7b..92378266 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml
@@ -165,10 +165,13 @@
failed_when:
- '"Could not satisfy the following requirements" not in fail_dep_mismatch.stderr'
- '" fail_dep2.name:<0.0.5 (dependency of fail_namespace.fail_collection:2.1.2)" not in fail_dep_mismatch.stderr'
+ - 'pre_release_hint not in fail_dep_mismatch.stderr'
+ vars:
+ pre_release_hint: 'Hint: Pre-releases are not installed by default unless the specific version is given. To enable pre-releases, use --pre.'
- name: Find artifact url for namespace3.name
uri:
- url: '{{ test_server }}{{ vX }}collections/namespace3/name/versions/1.0.0/'
+ url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace3/name/versions/1.0.0/'
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
force_basic_auth: true
@@ -218,7 +221,7 @@
state: absent
- assert:
- that: error == expected_error
+ that: expected_error in error
vars:
error: "{{ result.stderr | regex_replace('\\n', ' ') }}"
expected_error: >-
@@ -258,12 +261,14 @@
ignore_errors: yes
register: result
- - debug: msg="Actual - {{ error }}"
+ - debug:
+ msg: "Actual - {{ error }}"
- - debug: msg="Expected - {{ expected_error }}"
+ - debug:
+ msg: "Expected - {{ expected_error }}"
- assert:
- that: error == expected_error
+ that: expected_error in error
always:
- name: clean up collection skeleton and artifact
file:
@@ -295,7 +300,7 @@
- name: Find artifact url for namespace4.name
uri:
- url: '{{ test_server }}{{ vX }}collections/namespace4/name/versions/1.0.0/'
+ url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace4/name/versions/1.0.0/'
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
force_basic_auth: true
@@ -325,10 +330,11 @@
environment:
ANSIBLE_GALAXY_SERVER_LIST: undefined
-- when: not requires_auth
+# pulp_v2 doesn't require auth
+- when: v2|default(false)
block:
- name: install a collection with an empty server list - {{ test_id }}
- command: ansible-galaxy collection install namespace5.name -s '{{ test_server }}' {{ galaxy_verbosity }}
+ command: ansible-galaxy collection install namespace5.name -s '{{ test_server }}' --api-version 2 {{ galaxy_verbosity }}
register: install_empty_server_list
environment:
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
@@ -571,7 +577,6 @@
- namespace8
- namespace9
-# SIVEL
- name: assert invalid signature is not fatal with ansible-galaxy install --ignore-errors - {{ test_id }}
assert:
that:
@@ -646,6 +651,7 @@
- namespace8
- namespace9
+# test --ignore-signature-status-code extends ANSIBLE_GALAXY_IGNORE_SIGNATURE_STATUS_CODES env var
- name: install collections with only one valid signature by ignoring the other errors
command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} --ignore-signature-status-code FAILURE
register: install_req
@@ -686,6 +692,60 @@
vars:
install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}"
+# test --ignore-signature-status-code passed multiple times
+- name: reinstall collections with only one valid signature by ignoring the other errors
+ command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} {{ ignore_errors }}
+ register: install_req
+ vars:
+ req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml"
+ cli_opts: "-s {{ test_name }} --keyring {{ keyring }} --force"
+ keyring: "{{ gpg_homedir }}/pubring.kbx"
+ ignore_errors: "--ignore-signature-status-code BADSIG --ignore-signature-status-code FAILURE"
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all
+ ANSIBLE_NOCOLOR: True
+ ANSIBLE_FORCE_COLOR: False
+
+- name: assert invalid signature is not fatal with ansible-galaxy install - {{ test_name }}
+ assert:
+ that:
+ - install_req is success
+ - '"Installing ''namespace7.name:1.0.0'' to" in install_req.stdout'
+ - '"Signature verification failed for ''namespace7.name'' (return code 1)" not in install_req.stdout'
+ - '"Not installing namespace7.name because GnuPG signature verification failed." not in install_stderr'
+ - '"Installing ''namespace8.name:1.0.0'' to" in install_req.stdout'
+ - '"Installing ''namespace9.name:1.0.0'' to" in install_req.stdout'
+ vars:
+ install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}"
+
+# test --ignore-signature-status-code passed once with a list
+- name: reinstall collections with only one valid signature by ignoring the other errors
+ command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} {{ ignore_errors }}
+ register: install_req
+ vars:
+ req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml"
+ cli_opts: "-s {{ test_name }} --keyring {{ keyring }} --force"
+ keyring: "{{ gpg_homedir }}/pubring.kbx"
+ ignore_errors: "--ignore-signature-status-codes BADSIG FAILURE"
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all
+ ANSIBLE_NOCOLOR: True
+ ANSIBLE_FORCE_COLOR: False
+
+- name: assert invalid signature is not fatal with ansible-galaxy install - {{ test_name }}
+ assert:
+ that:
+ - install_req is success
+ - '"Installing ''namespace7.name:1.0.0'' to" in install_req.stdout'
+ - '"Signature verification failed for ''namespace7.name'' (return code 1)" not in install_req.stdout'
+ - '"Not installing namespace7.name because GnuPG signature verification failed." not in install_stderr'
+ - '"Installing ''namespace8.name:1.0.0'' to" in install_req.stdout'
+ - '"Installing ''namespace9.name:1.0.0'' to" in install_req.stdout'
+ vars:
+ install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}"
+
- name: clean up collections from last test
file:
path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name'
@@ -697,44 +757,45 @@
- namespace8
- namespace9
-# Uncomment once pulp container is at pulp>=0.5.0
-#- name: install cache.cache at the current latest version
-# command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' -vvv
-# environment:
-# ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
-#
-#- set_fact:
-# cache_version_build: '{{ (cache_version_build | int) + 1 }}'
-#
-#- name: publish update for cache.cache test
-# setup_collections:
-# server: galaxy_ng
-# collections:
-# - namespace: cache
-# name: cache
-# version: 1.0.{{ cache_version_build }}
-#
-#- name: make sure the cache version list is ignored on a collection version change - {{ test_id }}
-# command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' --force -vvv
-# register: install_cached_update
-# environment:
-# ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
-#
-#- name: get result of cache version list is ignored on a collection version change - {{ test_id }}
-# slurp:
-# path: '{{ galaxy_dir }}/ansible_collections/cache/cache/MANIFEST.json'
-# register: install_cached_update_actual
-#
-#- name: assert cache version list is ignored on a collection version change - {{ test_id }}
-# assert:
-# that:
-# - '"Installing ''cache.cache:1.0.{{ cache_version_build }}'' to" in install_cached_update.stdout'
-# - (install_cached_update_actual.content | b64decode | from_json).collection_info.version == '1.0.' ~ cache_version_build
+- when: not v2|default(false)
+ block:
+ - name: install cache.cache at the current latest version
+ command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' -vvv
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
+
+ - set_fact:
+ cache_version_build: '{{ (cache_version_build | int) + 1 }}'
+
+ - name: publish update for cache.cache test
+ setup_collections:
+ server: galaxy_ng
+ collections:
+ - namespace: cache
+ name: cache
+ version: 1.0.{{ cache_version_build }}
+
+ - name: make sure the cache version list is ignored on a collection version change - {{ test_id }}
+ command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' --force -vvv
+ register: install_cached_update
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
+
+ - name: get result of cache version list is ignored on a collection version change - {{ test_id }}
+ slurp:
+ path: '{{ galaxy_dir }}/ansible_collections/cache/cache/MANIFEST.json'
+ register: install_cached_update_actual
+
+ - name: assert cache version list is ignored on a collection version change - {{ test_id }}
+ assert:
+ that:
+ - '"Installing ''cache.cache:1.0.{{ cache_version_build }}'' to" in install_cached_update.stdout'
+ - (install_cached_update_actual.content | b64decode | from_json).collection_info.version == '1.0.' ~ cache_version_build
- name: install collection with symlink - {{ test_id }}
command: ansible-galaxy collection install symlink.symlink -s '{{ test_name }}' {{ galaxy_verbosity }}
environment:
- ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
register: install_symlink
- find:
@@ -772,6 +833,56 @@
- install_symlink_actual.results[5].stat.islnk
- install_symlink_actual.results[5].stat.lnk_target == '../REÅDMÈ.md'
+
+# Testing an install from source to check that symlinks to directories
+# are preserved (see issue https://github.com/ansible/ansible/issues/78442)
+- name: symlink_dirs collection install from source test
+ block:
+
+ - name: create symlink_dirs collection
+ command: ansible-galaxy collection init symlink_dirs.symlink_dirs --init-path "{{ galaxy_dir }}/scratch"
+
+ - name: create directory in collection
+ file:
+ path: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderA"
+ state: directory
+
+ - name: create symlink to folderA
+ file:
+ dest: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderB"
+ src: ./folderA
+ state: link
+ force: yes
+
+ - name: install symlink_dirs collection from source
+ command: ansible-galaxy collection install {{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/
+ environment:
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
+ register: install_symlink_dirs
+
+ - name: get result of install collection with symlink_dirs - {{ test_id }}
+ stat:
+ path: '{{ galaxy_dir }}/ansible_collections/symlink_dirs/symlink_dirs/{{ path }}'
+ register: install_symlink_dirs_actual
+ loop_control:
+ loop_var: path
+ loop:
+ - folderA
+ - folderB
+
+ - name: assert install collection with symlink_dirs - {{ test_id }}
+ assert:
+ that:
+ - '"Installing ''symlink_dirs.symlink_dirs:1.0.0'' to" in install_symlink_dirs.stdout'
+ - install_symlink_dirs_actual.results[0].stat.isdir
+ - install_symlink_dirs_actual.results[1].stat.islnk
+ - install_symlink_dirs_actual.results[1].stat.lnk_target == './folderA'
+ always:
+ - name: clean up symlink_dirs collection directory
+ file:
+ path: "{{ galaxy_dir }}/scratch/symlink_dirs"
+ state: absent
+
- name: remove install directory for the next test because parent_dep.parent_collection was installed - {{ test_id }}
file:
path: '{{ galaxy_dir }}/ansible_collections'
@@ -780,7 +891,7 @@
- name: install collection and dep compatible with multiple requirements - {{ test_id }}
command: ansible-galaxy collection install parent_dep.parent_collection parent_dep2.parent_collection
environment:
- ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
register: install_req
- name: assert install collections with ansible-galaxy install - {{ test_id }}
@@ -802,7 +913,7 @@
- name: install a collection to the same installation directory - {{ test_id }}
command: ansible-galaxy collection install namespace1.name1
environment:
- ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
register: install_req
- name: assert installed collections with ansible-galaxy install - {{ test_id }}
@@ -1009,7 +1120,7 @@
args:
chdir: '{{ galaxy_dir }}/scratch'
environment:
- ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections'
+ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections'
register: install_concrete_pre
- name: get result of install collections with concrete pre-release dep - {{ test_id }}
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml
index 74c99838..f3b9777c 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml
@@ -25,6 +25,14 @@
regexp: "^dependencies:*"
line: "dependencies: {'ns.coll2': '>=1.0.0'}"
+ - name: create required runtime.yml
+ copy:
+ dest: "{{ galaxy_dir }}/offline/setup/ns/{{ item }}/meta/runtime.yml"
+ content: "requires_ansible: '>=1.0.0'"
+ loop:
+ - coll1
+ - coll2
+
- name: build both collections
command: ansible-galaxy collection build {{ init_dir }}/ns/{{ item }}
args:
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml
index b8d63492..1c93d54b 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml
@@ -1,4 +1,4 @@
-- name: initialize collection structure
+- name: initialize dev collection structure
command: ansible-galaxy collection init {{ item }} --init-path "{{ galaxy_dir }}/dev/ansible_collections" {{ galaxy_verbosity }}
loop:
- 'dev.collection1'
@@ -8,6 +8,13 @@
- 'dev.collection5'
- 'dev.collection6'
+- name: initialize prod collection structure
+ command: ansible-galaxy collection init {{ item }} --init-path "{{ galaxy_dir }}/prod/ansible_collections" {{ galaxy_verbosity }}
+ loop:
+ - 'prod.collection1'
+ - 'prod.collection2'
+ - 'prod.collection3'
+
- name: replace the default version of the collections
lineinfile:
path: "{{ galaxy_dir }}/dev/ansible_collections/dev/{{ item.name }}/galaxy.yml"
@@ -53,13 +60,13 @@
- assert:
that:
- - "'dev.collection1 *' in list_result.stdout"
+ - "'dev.collection1 *' in list_result.stdout"
# Note the version displayed is the 'placeholder' string rather than "*" since it is not falsey
- - "'dev.collection2 placeholder' in list_result.stdout"
- - "'dev.collection3 *' in list_result.stdout"
- - "'dev.collection4 *' in list_result.stdout"
- - "'dev.collection5 *' in list_result.stdout"
- - "'dev.collection6 *' in list_result.stdout"
+ - "'dev.collection2 placeholder' in list_result.stdout"
+ - "'dev.collection3 *' in list_result.stdout"
+ - "'dev.collection4 *' in list_result.stdout"
+ - "'dev.collection5 *' in list_result.stdout"
+ - "'dev.collection6 *' in list_result.stdout"
- name: list collections in human format
command: ansible-galaxy collection list --format human
@@ -69,12 +76,12 @@
- assert:
that:
- - "'dev.collection1 *' in list_result_human.stdout"
+ - "'dev.collection1 *' in list_result_human.stdout"
# Note the version displayed is the 'placeholder' string rather than "*" since it is not falsey
- - "'dev.collection2 placeholder' in list_result_human.stdout"
- - "'dev.collection3 *' in list_result_human.stdout"
- - "'dev.collection5 *' in list_result.stdout"
- - "'dev.collection6 *' in list_result.stdout"
+ - "'dev.collection2 placeholder' in list_result_human.stdout"
+ - "'dev.collection3 *' in list_result_human.stdout"
+ - "'dev.collection5 *' in list_result.stdout"
+ - "'dev.collection6 *' in list_result.stdout"
- name: list collections in yaml format
command: ansible-galaxy collection list --format yaml
@@ -84,6 +91,12 @@
- assert:
that:
+ - yaml_result[galaxy_dir ~ '/dev/ansible_collections'] != yaml_result[galaxy_dir ~ '/prod/ansible_collections']
+ vars:
+ yaml_result: '{{ list_result_yaml.stdout | from_yaml }}'
+
+- assert:
+ that:
- "item.value | length == 6"
- "item.value['dev.collection1'].version == '*'"
- "item.value['dev.collection2'].version == 'placeholder'"
@@ -91,6 +104,7 @@
- "item.value['dev.collection5'].version == '*'"
- "item.value['dev.collection6'].version == '*'"
with_dict: "{{ list_result_yaml.stdout | from_yaml }}"
+ when: "'dev' in item.key"
- name: list collections in json format
command: ansible-galaxy collection list --format json
@@ -107,6 +121,7 @@
- "item.value['dev.collection5'].version == '*'"
- "item.value['dev.collection6'].version == '*'"
with_dict: "{{ list_result_json.stdout | from_json }}"
+ when: "'dev' in item.key"
- name: list single collection in json format
command: "ansible-galaxy collection list {{ item.key }} --format json"
@@ -137,7 +152,7 @@
register: list_result_error
ignore_errors: True
environment:
- ANSIBLE_COLLECTIONS_PATH: ""
+ ANSIBLE_COLLECTIONS_PATH: "i_dont_exist"
- assert:
that:
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml
index 724c861e..e17d6aa1 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml
@@ -72,13 +72,12 @@
vars:
test_name: '{{ item.name }}'
test_server: '{{ item.server }}'
- vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}'
+ test_api_server: '{{ item.api_server|default(item.server) }}'
loop:
- name: pulp_v2
- server: '{{ pulp_server }}published/api/'
- - name: pulp_v3
- server: '{{ pulp_server }}published/api/'
- v3: true
+ api_server: '{{ galaxy_ng_server }}'
+ server: '{{ pulp_server }}primary/api/'
+ v2: true
- name: galaxy_ng
server: '{{ galaxy_ng_server }}'
v3: true
@@ -108,8 +107,9 @@
test_id: '{{ item.name }}'
test_name: '{{ item.name }}'
test_server: '{{ item.server }}'
- vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}'
+ test_api_server: '{{ item.api_server|default(item.server) }}'
requires_auth: '{{ item.requires_auth|default(false) }}'
+ v2: '{{ item.v2|default(false) }}'
args:
apply:
environment:
@@ -120,10 +120,9 @@
v3: true
requires_auth: true
- name: pulp_v2
- server: '{{ pulp_server }}published/api/'
- - name: pulp_v3
- server: '{{ pulp_server }}published/api/'
- v3: true
+ server: '{{ pulp_server }}primary/api/'
+ api_server: '{{ galaxy_ng_server }}'
+ v2: true
- name: test installing and downloading collections with the range of supported resolvelib versions
include_tasks: supported_resolvelib.yml
@@ -135,6 +134,17 @@
loop_control:
loop_var: resolvelib_version
+- name: test choosing pinned pre-releases anywhere in the dependency tree
+ # This is a regression test for the case when the end-user does not
+ # explicitly allow installing pre-release collection versions, but their
+ # precise pins are still selected if met among the dependencies, either
+ # direct or transitive.
+ include_tasks: pinned_pre_releases_in_deptree.yml
+
+- name: test installing prereleases via scm direct requests
+ # In this test suite because the bug relies on the dep having versions on a Galaxy server
+ include_tasks: virtual_direct_requests.yml
+
- name: publish collection with a dep on another server
setup_collections:
server: secondary
@@ -176,13 +186,13 @@
in install_cross_dep.stdout
# pulp_v2 is highest in the list so it will find it there first
- >-
- "'parent_dep.parent_collection:1.0.0' obtained from server pulp_v2"
+ "'parent_dep.parent_collection:1.0.0' obtained from server galaxy_ng"
in install_cross_dep.stdout
- >-
- "'child_dep.child_collection:0.9.9' obtained from server pulp_v2"
+ "'child_dep.child_collection:0.9.9' obtained from server galaxy_ng"
in install_cross_dep.stdout
- >-
- "'child_dep.child_dep2:1.2.2' obtained from server pulp_v2"
+ "'child_dep.child_dep2:1.2.2' obtained from server galaxy_ng"
in install_cross_dep.stdout
- (install_cross_dep_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0'
- (install_cross_dep_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0'
@@ -204,10 +214,9 @@
ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}'
ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg'
vars:
- test_api_fallback: 'pulp_v2'
- test_api_fallback_versions: 'v1, v2'
- test_name: 'galaxy_ng'
- test_server: '{{ galaxy_ng_server }}'
+ test_api_fallback: 'galaxy_ng'
+ test_api_fallback_versions: 'v3, pulp-v3, v1'
+ test_name: 'pulp_v2'
- name: run ansible-galaxy collection list tests
include_tasks: list.yml
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml
index 241eae60..1be16ae9 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml
@@ -5,9 +5,12 @@
chdir: '{{ galaxy_dir }}'
register: publish_collection
+- name: ensure we can download the published collection - {{ test_name }}
+ command: ansible-galaxy collection install -s {{ test_name }} -p "{{ remote_tmp_dir }}/publish/{{ test_name }}" ansible_test.my_collection==1.0.0 {{ galaxy_verbosity }}
+
- name: get result of publish collection - {{ test_name }}
uri:
- url: '{{ test_server }}{{ vX }}collections/ansible_test/my_collection/versions/1.0.0/'
+ url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/ansible_test/my_collection/versions/1.0.0/'
return_content: yes
user: '{{ pulp_user }}'
password: '{{ pulp_password }}'
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml
index 763c5a19..bff36892 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml
@@ -20,11 +20,11 @@
- include_tasks: install.yml
vars:
- test_name: pulp_v3
+ test_name: galaxy_ng
test_id: '{{ test_name }} (resolvelib {{ resolvelib_version }})'
- test_server: '{{ pulp_server }}published/api/'
- vX: "v3/"
- requires_auth: false
+ test_server: '{{ galaxy_ng_server }}'
+ test_api_server: '{{ galaxy_ng_server }}'
+ requires_auth: true
args:
apply:
environment:
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml
index 893ea803..debd70bc 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml
@@ -142,7 +142,7 @@
- directory
- name: install a collection
- command: ansible-galaxy collection install namespace1.name1:0.0.1 {{ galaxy_verbosity }}
+ command: ansible-galaxy collection install namespace1.name1==0.0.1 {{ galaxy_verbosity }}
register: result
failed_when:
- '"namespace1.name1:0.0.1 was installed successfully" not in result.stdout_lines'
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml
index dfe3d0f7..0fe2f82d 100644
--- a/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml
+++ b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml
@@ -3,6 +3,11 @@
args:
chdir: '{{ galaxy_dir }}/scratch'
+- name: created required runtime.yml
+ copy:
+ content: 'requires_ansible: ">=1.0.0"'
+ dest: '{{ galaxy_dir }}/scratch/ansible_test/verify/meta/runtime.yml'
+
- name: build the collection
command: ansible-galaxy collection build scratch/ansible_test/verify
args:
@@ -31,6 +36,9 @@
- name: verify the collection against the first valid server
command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -vvvv {{ galaxy_verbosity }}
register: verify
+ vars:
+ # This sets a specific precedence that the tests are expecting
+ ANSIBLE_GALAXY_SERVER_LIST: offline,secondary,pulp_v2,galaxy_ng
- assert:
that:
diff --git a/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2 b/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2
index 9bff527b..a242979d 100644
--- a/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2
+++ b/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2
@@ -1,28 +1,22 @@
[galaxy]
# Ensures subsequent unstable reruns don't use the cached information causing another failure
cache_dir={{ remote_tmp_dir }}/galaxy_cache
-server_list=offline,pulp_v2,pulp_v3,galaxy_ng,secondary
+server_list=offline,galaxy_ng,secondary,pulp_v2
[galaxy_server.offline]
url={{ offline_server }}
[galaxy_server.pulp_v2]
-url={{ pulp_server }}published/api/
-username={{ pulp_user }}
-password={{ pulp_password }}
-
-[galaxy_server.pulp_v3]
-url={{ pulp_server }}published/api/
-v3=true
+url={{ pulp_server }}primary/api/
username={{ pulp_user }}
password={{ pulp_password }}
+api_version=2
[galaxy_server.galaxy_ng]
-url={{ galaxy_ng_server }}
+url={{ galaxy_ng_server }}content/primary/
token={{ galaxy_ng_token.json.token }}
[galaxy_server.secondary]
-url={{ pulp_server }}secondary/api/
-v3=true
+url={{ galaxy_ng_server }}content/secondary/
username={{ pulp_user }}
password={{ pulp_password }}
diff --git a/test/integration/targets/ansible-galaxy-collection/vars/main.yml b/test/integration/targets/ansible-galaxy-collection/vars/main.yml
index 175d6696..066d2678 100644
--- a/test/integration/targets/ansible-galaxy-collection/vars/main.yml
+++ b/test/integration/targets/ansible-galaxy-collection/vars/main.yml
@@ -9,17 +9,20 @@ supported_resolvelib_versions:
- "0.6.0"
- "0.7.0"
- "0.8.0"
+ - "0.9.0"
+ - "1.0.1"
unsupported_resolvelib_versions:
- "0.2.0" # Fails on import
- "0.5.1"
pulp_repositories:
- - published
+ - primary
- secondary
publish_namespaces:
- ansible_test
+ - secondary
collection_list:
# Scenario to test out pre-release being ignored unless explicitly set and version pagination.
@@ -162,3 +165,41 @@ collection_list:
name: parent
dependencies:
namespace1.name1: '*'
+
+ # non-prerelease is published to test that installing
+ # the pre-release from SCM doesn't accidentally prefer indirect
+ # dependencies from Galaxy
+ - namespace: test_prereleases
+ name: collection2
+ version: 1.0.0
+
+ - namespace: dev_and_stables_ns
+ name: dev_and_stables_name
+ version: 1.2.3-dev0
+ - namespace: dev_and_stables_ns
+ name: dev_and_stables_name
+ version: 1.2.4
+
+ - namespace: ns_with_wildcard_dep
+ name: name_with_wildcard_dep
+ version: 5.6.7-beta.3
+ dependencies:
+ dev_and_stables_ns.dev_and_stables_name: >-
+ *
+ - namespace: ns_with_dev_dep
+ name: name_with_dev_dep
+ version: 6.7.8
+ dependencies:
+ dev_and_stables_ns.dev_and_stables_name: 1.2.3-dev0
+
+ - namespace: rc_meta_ns_with_transitive_dev_dep
+ name: rc_meta_name_with_transitive_dev_dep
+ version: 2.4.5-rc5
+ dependencies:
+ ns_with_dev_dep.name_with_dev_dep: >-
+ *
+ - namespace: meta_ns_with_transitive_wildcard_dep
+ name: meta_name_with_transitive_wildcard_dep
+ version: 4.5.6
+ dependencies:
+ ns_with_wildcard_dep.name_with_wildcard_dep: 5.6.7-beta.3
diff --git a/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
index cfd908c1..48766638 100755
--- a/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
+++ b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
@@ -2,6 +2,7 @@
"""Create a role archive which overwrites an arbitrary file."""
import argparse
+import os
import pathlib
import tarfile
import tempfile
@@ -18,6 +19,15 @@ def main() -> None:
create_archive(args.archive, args.content, args.target)
+def generate_files_from_path(path):
+ if os.path.isdir(path):
+ for subpath in os.listdir(path):
+ _path = os.path.join(path, subpath)
+ yield from generate_files_from_path(_path)
+ elif os.path.isfile(path):
+ yield pathlib.Path(path)
+
+
def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, target_path: pathlib.Path) -> None:
with (
tarfile.open(name=archive_path, mode='w') as role_archive,
@@ -35,10 +45,15 @@ def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, targe
role_archive.add(meta_main_path)
role_archive.add(symlink_path)
- content_tarinfo = role_archive.gettarinfo(content_path, str(symlink_path))
+ for path in generate_files_from_path(content_path):
+ if path == content_path:
+ arcname = str(symlink_path)
+ else:
+ arcname = os.path.join(temp_dir_path, path)
- with content_path.open('rb') as content_file:
- role_archive.addfile(content_tarinfo, content_file)
+ content_tarinfo = role_archive.gettarinfo(path, arcname)
+ with path.open('rb') as file_content:
+ role_archive.addfile(content_tarinfo, file_content)
if __name__ == '__main__':
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
index c70e8998..1c17daf7 100644
--- a/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
+++ b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
@@ -23,6 +23,9 @@
command:
cmd: ansible-galaxy role install --roles-path '{{ remote_tmp_dir }}/dir-traversal/roles' dangerous.tar
chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
+ environment:
+ ANSIBLE_NOCOLOR: True
+ ANSIBLE_FORCE_COLOR: False
ignore_errors: true
register: galaxy_install_dangerous
@@ -42,3 +45,86 @@
- dangerous_overwrite_content.content|default('')|b64decode == ''
- not dangerous_overwrite_stat.stat.exists
- galaxy_install_dangerous is failed
+ - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))"
+
+- name: remove tarfile for next test
+ file:
+ path: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ remote_tmp_dir }}/dir-traversal/source/dangerous.tar'
+ - '{{ remote_tmp_dir }}/dir-traversal/roles/dangerous.tar'
+
+- name: build dangerous dir traversal role that includes .. in the symlink path
+ script:
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
+ cmd: create-role-archive.py dangerous.tar content.txt {{ remote_tmp_dir }}/dir-traversal/source/../target/target-file-to-overwrite.txt
+ executable: '{{ ansible_playbook_python }}'
+
+- name: install dangerous role
+ command:
+ cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles dangerous.tar'
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
+ environment:
+ ANSIBLE_NOCOLOR: True
+ ANSIBLE_FORCE_COLOR: False
+ ignore_errors: true
+ register: galaxy_install_dangerous
+
+- name: check for overwritten file
+ stat:
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
+ register: dangerous_overwrite_stat
+
+- name: get overwritten content
+ slurp:
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
+ register: dangerous_overwrite_content
+ when: dangerous_overwrite_stat.stat.exists
+
+- assert:
+ that:
+ - dangerous_overwrite_content.content|default('')|b64decode == ''
+ - not dangerous_overwrite_stat.stat.exists
+ - galaxy_install_dangerous is failed
+ - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))"
+
+- name: remove tarfile for next test
+ file:
+ path: '{{ remote_tmp_dir }}/dir-traversal/source/dangerous.tar'
+ state: absent
+
+- name: build dangerous dir traversal role that includes .. in the relative symlink path
+ script:
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
+ cmd: create-role-archive.py dangerous_rel.tar content.txt ../context.txt
+
+- name: install dangerous role with relative symlink
+ command:
+ cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles dangerous_rel.tar'
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
+ environment:
+ ANSIBLE_NOCOLOR: True
+ ANSIBLE_FORCE_COLOR: False
+ ignore_errors: true
+ register: galaxy_install_dangerous
+
+- name: check for symlink outside role
+ stat:
+ path: "{{ remote_tmp_dir | realpath }}/dir-traversal/roles/symlink"
+ register: symlink_outside_role
+
+- assert:
+ that:
+ - not symlink_outside_role.stat.exists
+ - galaxy_install_dangerous is failed
+ - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))"
+
+- name: remove test directories
+ file:
+ path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}'
+ state: absent
+ loop:
+ - source
+ - target
+ - roles
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/main.yml b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
index b39df11c..5f88a557 100644
--- a/test/integration/targets/ansible-galaxy-role/tasks/main.yml
+++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
@@ -25,10 +25,18 @@
- name: Valid role archive
command: "tar cf {{ remote_tmp_dir }}/valid-role.tar {{ remote_tmp_dir }}/role.d"
-- name: Invalid file
- copy:
- content: ""
+- name: Add invalid symlink
+ file:
+ state: link
+ src: "~/invalid"
dest: "{{ remote_tmp_dir }}/role.d/tasks/~invalid.yml"
+ force: yes
+
+- name: Add another invalid symlink
+ file:
+ state: link
+ src: "/"
+ dest: "{{ remote_tmp_dir }}/role.d/tasks/invalid$name.yml"
- name: Valid requirements file
copy:
@@ -61,3 +69,4 @@
command: ansible-galaxy role remove invalid-testrole
- import_tasks: dir-traversal.yml
+- import_tasks: valid-role-symlinks.yml
diff --git a/test/integration/targets/ansible-galaxy/files/testserver.py b/test/integration/targets/ansible-galaxy/files/testserver.py
index 13598507..8cca6a83 100644
--- a/test/integration/targets/ansible-galaxy/files/testserver.py
+++ b/test/integration/targets/ansible-galaxy/files/testserver.py
@@ -1,20 +1,15 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import sys
+import http.server
+import socketserver
import ssl
if __name__ == '__main__':
- if sys.version_info[0] >= 3:
- import http.server
- import socketserver
- Handler = http.server.SimpleHTTPRequestHandler
- httpd = socketserver.TCPServer(("", 4443), Handler)
- else:
- import BaseHTTPServer
- import SimpleHTTPServer
- Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
- httpd = BaseHTTPServer.HTTPServer(("", 4443), Handler)
+ Handler = http.server.SimpleHTTPRequestHandler
+ context = ssl.SSLContext()
+ context.load_cert_chain(certfile='./cert.pem', keyfile='./key.pem')
+ httpd = socketserver.TCPServer(("", 4443), Handler)
+ httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
- httpd.socket = ssl.wrap_socket(httpd.socket, certfile='./cert.pem', keyfile='./key.pem', server_side=True)
httpd.serve_forever()
diff --git a/test/integration/targets/ansible-galaxy/runme.sh b/test/integration/targets/ansible-galaxy/runme.sh
index 7d966e29..fcd826c3 100755
--- a/test/integration/targets/ansible-galaxy/runme.sh
+++ b/test/integration/targets/ansible-galaxy/runme.sh
@@ -61,10 +61,13 @@ f_ansible_galaxy_create_role_repo_post()
git add .
git commit -m "local testing ansible galaxy role"
+ # NOTE: `HEAD` is used because the newer Git versions create
+ # NOTE: `main` by default and the older ones differ. We
+ # NOTE: want to avoid hardcoding them.
git archive \
--format=tar \
--prefix="${repo_name}/" \
- master > "${repo_tar}"
+ HEAD > "${repo_tar}"
# Configure basic (insecure) HTTPS-accessible repository
galaxy_local_test_role_http_repo="${galaxy_webserver_root}/${galaxy_local_test_role}.git"
if [[ ! -d "${galaxy_local_test_role_http_repo}" ]]; then
@@ -354,7 +357,7 @@ pushd "${galaxy_testdir}"
popd # ${galaxy_testdir}
f_ansible_galaxy_status \
- "role info non-existant role"
+ "role info non-existent role"
mkdir -p "${role_testdir}"
pushd "${role_testdir}"
diff --git a/test/integration/targets/ansible-inventory/files/valid_sample.yml b/test/integration/targets/ansible-inventory/files/valid_sample.yml
index 477f82f2..b8e7b882 100644
--- a/test/integration/targets/ansible-inventory/files/valid_sample.yml
+++ b/test/integration/targets/ansible-inventory/files/valid_sample.yml
@@ -4,4 +4,4 @@ all:
hosts:
something:
foo: bar
- ungrouped: {} \ No newline at end of file
+ ungrouped: {}
diff --git a/test/integration/targets/ansible-inventory/tasks/main.yml b/test/integration/targets/ansible-inventory/tasks/main.yml
index 84ac2c3c..c3459c12 100644
--- a/test/integration/targets/ansible-inventory/tasks/main.yml
+++ b/test/integration/targets/ansible-inventory/tasks/main.yml
@@ -145,3 +145,10 @@
loop_control:
loop_var: toml_package
when: toml_package is not contains 'tomllib' or (toml_package is contains 'tomllib' and ansible_facts.python.version_info >= [3, 11])
+
+
+- include_tasks: "{{item}}_output.yml"
+ loop:
+ - json
+ - yaml
+ - toml
diff --git a/test/integration/targets/ansible-pull/runme.sh b/test/integration/targets/ansible-pull/runme.sh
index 582e8099..b591b283 100755
--- a/test/integration/targets/ansible-pull/runme.sh
+++ b/test/integration/targets/ansible-pull/runme.sh
@@ -36,7 +36,8 @@ function pass_tests {
fi
# test for https://github.com/ansible/ansible/issues/13681
- if grep -E '127\.0\.0\.1.*ok' "${temp_log}"; then
+ # match play default output stats, was matching limit + docker
+ if grep -E '127\.0\.0\.1\s*: ok=' "${temp_log}"; then
cat "${temp_log}"
echo "Found host 127.0.0.1 in output. Only localhost should be present."
exit 1
@@ -86,5 +87,7 @@ ANSIBLE_CONFIG='' ansible-pull -d "${pull_dir}" -U "${repo_dir}" "$@" multi_play
pass_tests_multi
+ANSIBLE_CONFIG='' ansible-pull -d "${pull_dir}" -U "${repo_dir}" conn_secret.yml --connection-password-file "${repo_dir}/secret_connection_password" "$@"
+
# fail if we try do delete /var/tmp
ANSIBLE_CONFIG='' ansible-pull -d var/tmp -U "${repo_dir}" --purge "$@"
diff --git a/test/integration/targets/ansible-runner/aliases b/test/integration/targets/ansible-runner/aliases
index 13e7d785..f4caffd1 100644
--- a/test/integration/targets/ansible-runner/aliases
+++ b/test/integration/targets/ansible-runner/aliases
@@ -1,5 +1,4 @@
shippable/posix/group5
context/controller
-skip/osx
skip/macos
skip/freebsd
diff --git a/test/integration/targets/ansible-runner/files/adhoc_example1.py b/test/integration/targets/ansible-runner/files/adhoc_example1.py
index ab24bcad..fe7f9446 100644
--- a/test/integration/targets/ansible-runner/files/adhoc_example1.py
+++ b/test/integration/targets/ansible-runner/files/adhoc_example1.py
@@ -2,7 +2,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
-import os
import sys
import ansible_runner
diff --git a/test/integration/targets/ansible-test-cloud-openshift/aliases b/test/integration/targets/ansible-test-cloud-openshift/aliases
index 6e32db7b..b714e82c 100644
--- a/test/integration/targets/ansible-test-cloud-openshift/aliases
+++ b/test/integration/targets/ansible-test-cloud-openshift/aliases
@@ -1,4 +1,4 @@
cloud/openshift
shippable/generic/group1
-disabled # disabled due to requirements conflict: botocore 1.20.6 has requirement urllib3<1.27,>=1.25.4, but you have urllib3 1.24.3.
+disabled # the container crashes when using a non-default network on some docker hosts (such as Ubuntu 20.04)
context/controller
diff --git a/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml b/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml
index c3b51904..6acb67dc 100644
--- a/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml
+++ b/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml
@@ -1,6 +1,13 @@
+- name: Load kubeconfig
+ include_vars: "{{ lookup('env', 'K8S_AUTH_KUBECONFIG') }}"
+
+- name: Verify endpoints exist
+ assert:
+ that: clusters
+
- name: Verify endpoints respond
uri:
- url: "{{ item }}"
+ url: "{{ item.cluster.server }}"
validate_certs: no
with_items:
- - https://openshift-origin:8443/
+ - "{{ clusters }}"
diff --git a/test/integration/targets/ansible-test-container/runme.py b/test/integration/targets/ansible-test-container/runme.py
index 8ff48e0d..3c86b6dd 100755
--- a/test/integration/targets/ansible-test-container/runme.py
+++ b/test/integration/targets/ansible-test-container/runme.py
@@ -996,7 +996,7 @@ class AptBootstrapper(Bootstrapper):
@classmethod
def install_podman(cls) -> bool:
"""Return True if podman will be installed."""
- return not (os_release.id == 'ubuntu' and os_release.version_id == '20.04')
+ return not (os_release.id == 'ubuntu' and os_release.version_id in {'20.04', '22.04'})
@classmethod
def install_docker(cls) -> bool:
@@ -1053,13 +1053,14 @@ class ApkBootstrapper(Bootstrapper):
# crun added as podman won't install it as dep if runc is present
# but we don't want runc as it fails
# The edge `crun` package installed below requires ip6tables, and in
- # edge, the `iptables` package includes `ip6tables`, but in 3.16 they
- # are separate.
+ # edge, the `iptables` package includes `ip6tables`, but in 3.18 they
+ # are separate. Remove `ip6tables` once we update to 3.19.
packages = ['docker', 'podman', 'openssl', 'crun', 'ip6tables']
run_command('apk', 'add', *packages)
- # 3.16 only contains crun 1.4.5, to get 1.9.2 to resolve the run/shm issue, install crun from edge
- run_command('apk', 'upgrade', '-U', '--repository=http://dl-cdn.alpinelinux.org/alpine/edge/community', 'crun')
+ # 3.18 only contains crun 1.8.4, to get 1.9.2 to resolve the run/shm issue, install crun from 3.19
+ # Remove once we update to 3.19
+ run_command('apk', 'upgrade', '-U', '--repository=http://dl-cdn.alpinelinux.org/alpine/v3.19/community', 'crun')
run_command('service', 'docker', 'start')
run_command('modprobe', 'tun')
diff --git a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py
index f59b9091..f662b97d 100644
--- a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py
+++ b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py
@@ -16,10 +16,10 @@ RETURN = '''#'''
from ansible.plugins.lookup import LookupBase
# noinspection PyUnresolvedReferences
-from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded
+from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded # pylint: disable=unused-import
try:
- import demo
+ import demo # pylint: disable=unused-import
except ImportError:
pass
else:
diff --git a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py
index 22b4236a..38860b03 100644
--- a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py
+++ b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py
@@ -16,10 +16,10 @@ RETURN = '''#'''
from ansible.plugins.lookup import LookupBase
# noinspection PyUnresolvedReferences
-from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded
+from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded # pylint: disable=unused-import
try:
- import demo
+ import demo # pylint: disable=unused-import
except ImportError:
pass
else:
diff --git a/test/integration/targets/ansible-test-sanity-import/runme.sh b/test/integration/targets/ansible-test-sanity-import/runme.sh
index a12e3e3f..a49a71a0 100755
--- a/test/integration/targets/ansible-test-sanity-import/runme.sh
+++ b/test/integration/targets/ansible-test-sanity-import/runme.sh
@@ -1,7 +1,29 @@
#!/usr/bin/env bash
+set -eu
+
+# Create test scenarios at runtime that do not pass sanity tests.
+# This avoids the need to create ignore entries for the tests.
+
+mkdir -p ansible_collections/ns/col/plugins/lookup
+
+(
+ cd ansible_collections/ns/col/plugins/lookup
+
+ echo "import sys; sys.stdout.write('unwanted stdout')" > stdout.py # stdout: unwanted stdout
+ echo "import sys; sys.stderr.write('unwanted stderr')" > stderr.py # stderr: unwanted stderr
+)
+
source ../collection/setup.sh
+# Run regular import sanity tests.
+
+ansible-test sanity --test import --color --failure-ok --lint --python "${ANSIBLE_TEST_PYTHON_VERSION}" "${@}" 1> actual-stdout.txt 2> actual-stderr.txt
+diff -u "${TEST_DIR}/expected.txt" actual-stdout.txt
+grep -f "${TEST_DIR}/expected.txt" actual-stderr.txt
+
+# Run import sanity tests which require modifications to the source directory.
+
vendor_dir="$(python -c 'import pathlib, ansible._vendor; print(pathlib.Path(ansible._vendor.__file__).parent)')"
cleanup() {
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml
index c2575422..4ca20efb 100644
--- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml
@@ -17,6 +17,9 @@ DOCUMENTATION:
default: foo
author:
- Ansible Core Team
+ seealso:
+ - plugin: ns.col.import_order_lookup
+ plugin_type: lookup
EXAMPLES: |
- name: example for sidecar
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md
index bf1003fa..d158a987 100644
--- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md
@@ -1,3 +1,4 @@
README
------
+
This is a simple collection used to test failures with ``ansible-test sanity --test validate-modules``.
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md
index bbdd5138..9c1c1c34 100644
--- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md
@@ -1,3 +1,4 @@
README
------
+
This is a simple PowerShell-only collection used to verify that ``ansible-test`` works on a collection.
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
index 95f12f39..ca6e52a3 100644
--- a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt
@@ -1,5 +1,26 @@
+plugins/lookup/import_order_lookup.py:5:0: import-before-documentation: Import found before documentation variables. All imports must appear below DOCUMENTATION/EXAMPLES/RETURN.
+plugins/modules/check_mode_attribute_1.py:0:0: attributes-check-mode: The module does not declare support for check mode, but the check_mode attribute's support value is 'full' and not 'none'
+plugins/modules/check_mode_attribute_2.py:0:0: attributes-check-mode: The module does not declare support for check mode, but the check_mode attribute's support value is 'partial' and not 'none'
+plugins/modules/check_mode_attribute_3.py:0:0: attributes-check-mode: The module does declare support for check mode, but the check_mode attribute's support value is 'none'
+plugins/modules/check_mode_attribute_4.py:0:0: attributes-check-mode-details: The module declares it does not fully support check mode, but has no details on what exactly that means
+plugins/modules/import_order.py:8:0: import-before-documentation: Import found before documentation variables. All imports must appear below DOCUMENTATION/EXAMPLES/RETURN.
plugins/modules/invalid_yaml_syntax.py:0:0: deprecation-mismatch: "meta/runtime.yml" and DOCUMENTATION.deprecation do not agree.
plugins/modules/invalid_yaml_syntax.py:0:0: missing-documentation: No DOCUMENTATION provided
plugins/modules/invalid_yaml_syntax.py:8:15: documentation-syntax-error: DOCUMENTATION is not valid YAML
plugins/modules/invalid_yaml_syntax.py:12:15: invalid-examples: EXAMPLES is not valid YAML
plugins/modules/invalid_yaml_syntax.py:16:15: return-syntax-error: RETURN is not valid YAML
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.0: While parsing "V(C\(" at index 1: Unnecessarily escaped "(" @ data['options']['a11']['suboptions']['b1']['description'][0]. Got 'V(C\\(foo\\)).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.2: While parsing "P(foo.bar#baz)" at index 1: Plugin name "foo.bar" is not a FQCN @ data['options']['a11']['suboptions']['b1']['description'][2]. Got 'P(foo.bar#baz).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.3: While parsing "P(foo.bar.baz)" at index 1: Parameter "foo.bar.baz" is not of the form FQCN#type @ data['options']['a11']['suboptions']['b1']['description'][3]. Got 'P(foo.bar.baz).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.4: Directive "P(foo.bar.baz#woof)" must contain a valid plugin type; found "woof" @ data['options']['a11']['suboptions']['b1']['description'][4]. Got 'P(foo.bar.baz#woof).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.5: While parsing "E(foo\(" at index 1: Unnecessarily escaped "(" @ data['options']['a11']['suboptions']['b1']['description'][5]. Got 'E(foo\\(bar).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a2.description: While parsing "V(C\(" at index 1: Unnecessarily escaped "(" for dictionary value @ data['options']['a2']['description']. Got 'V(C\\(foo\\)).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a4.description: While parsing "P(foo.bar#baz)" at index 1: Plugin name "foo.bar" is not a FQCN for dictionary value @ data['options']['a4']['description']. Got 'P(foo.bar#baz).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a5.description: While parsing "P(foo.bar.baz)" at index 1: Parameter "foo.bar.baz" is not of the form FQCN#type for dictionary value @ data['options']['a5']['description']. Got 'P(foo.bar.baz).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a6.description: Directive "P(foo.bar.baz#woof)" must contain a valid plugin type; found "woof" for dictionary value @ data['options']['a6']['description']. Got 'P(foo.bar.baz#woof).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a7.description: While parsing "E(foo\(" at index 1: Unnecessarily escaped "(" for dictionary value @ data['options']['a7']['description']. Got 'E(foo\\(bar).'
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(bar)" contains a non-existing option "bar"
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(bar=bam)" contains a non-existing option "bar"
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(foo.bar=1)" contains a non-existing option "foo.bar"
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "RV(bam)" contains a non-existing return value "bam"
+plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "RV(does.not.exist=true)" contains a non-existing return value "does.not.exist"
diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh b/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh
index e0299969..5e2365ab 100755
--- a/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh
+++ b/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh
@@ -6,7 +6,17 @@ set -eux
ansible-test sanity --test validate-modules --color --truncate 0 --failure-ok --lint "${@}" 1> actual-stdout.txt 2> actual-stderr.txt
diff -u "${TEST_DIR}/expected.txt" actual-stdout.txt
-grep -f "${TEST_DIR}/expected.txt" actual-stderr.txt
+grep -F -f "${TEST_DIR}/expected.txt" actual-stderr.txt
+
+cd ../col
+ansible-test sanity --test runtime-metadata
+
+cd ../failure
+if ansible-test sanity --test runtime-metadata 2>&1 | tee out.txt; then
+ echo "runtime-metadata in failure should be invalid"
+ exit 1
+fi
+grep out.txt -e 'extra keys not allowed'
cd ../ps_only
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md
index d8138d3b..67b8a83b 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md
@@ -1,3 +1,4 @@
README
------
+
This is a simple collection used to verify that ``ansible-test`` works on a collection.
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml
index fee22ad8..76ead137 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml
@@ -2,4 +2,11 @@ requires_ansible: '>=2.11' # force ansible-doc to check the Ansible version (re
plugin_routing:
modules:
hi:
- redirect: hello
+ redirect: ns.col2.hello
+ hiya:
+ redirect: ns.col2.package.subdir.hiya
+ module_utils:
+ hi:
+ redirect: ansible_collections.ns.col2.plugins.module_utils
+ hello:
+ redirect: ns.col2.hiya
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py
index 580f9d87..16e0bc88 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py
@@ -19,9 +19,9 @@ EXAMPLES = '''
RETURN = ''' # '''
from ansible.plugins.lookup import LookupBase
-from ansible import constants
+from ansible import constants # pylint: disable=unused-import
-import lxml
+import lxml # pylint: disable=unused-import
class LookupModule(LookupBase):
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py
index dbb479a7..5cdd0966 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py
@@ -19,7 +19,7 @@ EXAMPLES = '''
RETURN = ''' # '''
from ansible.plugins.lookup import LookupBase
-from ansible import constants
+from ansible import constants # pylint: disable=unused-import
class LookupModule(LookupBase):
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py
index e79613bb..8780e356 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py
@@ -19,7 +19,7 @@ EXAMPLES = '''
RETURN = ''''''
from ansible.module_utils.basic import AnsibleModule
-from ansible import constants # intentionally trigger pylint ansible-bad-module-import error
+from ansible import constants # intentionally trigger pylint ansible-bad-module-import error # pylint: disable=unused-import
def main():
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py
index 2e35cf85..e34d1c37 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py
@@ -5,4 +5,4 @@ __metaclass__ = type
# This is not an allowed import, but since this file is in a plugins/ subdirectory that is not checked,
# the import sanity test will not complain.
-import lxml
+import lxml # pylint: disable=unused-import
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py
index 82215438..a5d896f7 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py
@@ -4,12 +4,12 @@ __metaclass__ = type
import tempfile
try:
- import urllib2 # intentionally trigger pylint ansible-bad-import error
+ import urllib2 # intentionally trigger pylint ansible-bad-import error # pylint: disable=unused-import
except ImportError:
urllib2 = None
try:
- from urllib2 import Request # intentionally trigger pylint ansible-bad-import-from error
+ from urllib2 import Request # intentionally trigger pylint ansible-bad-import-from error # pylint: disable=unused-import
except ImportError:
Request = None
diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt
index e1b3f4ca..dcbe827c 100644
--- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt
+++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt
@@ -1,6 +1,7 @@
plugins/modules/bad.py import
plugins/modules/bad.py pylint:ansible-bad-module-import
plugins/lookup/bad.py import
+plugins/plugin_utils/check_pylint.py pylint:disallowed-name
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import
tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from
diff --git a/test/integration/targets/ansible-test-sanity/runme.sh b/test/integration/targets/ansible-test-sanity/runme.sh
index 233db741..92584958 100755
--- a/test/integration/targets/ansible-test-sanity/runme.sh
+++ b/test/integration/targets/ansible-test-sanity/runme.sh
@@ -1,5 +1,11 @@
#!/usr/bin/env bash
+set -eux
+
+ansible-test sanity --color --allow-disabled -e "${@}"
+
+set +x
+
source ../collection/setup.sh
set -x
diff --git a/test/integration/targets/ansible-test/venv-pythons.py b/test/integration/targets/ansible-test/venv-pythons.py
index b380f147..97998bcd 100755
--- a/test/integration/targets/ansible-test/venv-pythons.py
+++ b/test/integration/targets/ansible-test/venv-pythons.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
"""Return target Python options for use with ansible-test."""
+import argparse
import os
import shutil
import subprocess
@@ -10,6 +11,11 @@ from ansible import release
def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--only-versions', action='store_true')
+
+ options = parser.parse_args()
+
ansible_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(release.__file__))))
source_root = os.path.join(ansible_root, 'test', 'lib')
@@ -33,6 +39,10 @@ def main():
print(f'{executable} - {"fail" if process.returncode else "pass"}', file=sys.stderr)
if not process.returncode:
+ if options.only_versions:
+ args.append(python_version)
+ continue
+
args.extend(['--target-python', f'venv/{python_version}'])
print(' '.join(args))
diff --git a/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml b/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml
index 71dbacc0..2365d47d 100644
--- a/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml
+++ b/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml
@@ -20,4 +20,4 @@
# 3366323866663763660a323766383531396433663861656532373663373134376263383263316261
# 3137
-# $ ansible-playbook -i inventory --vault-password-file=vault-secret tasks.yml
+# $ ansible-playbook -i inventory --vault-password-file=vault-secret tasks.yml
diff --git a/test/integration/targets/ansible-vault/runme.sh b/test/integration/targets/ansible-vault/runme.sh
index 50720ea9..98399eca 100755
--- a/test/integration/targets/ansible-vault/runme.sh
+++ b/test/integration/targets/ansible-vault/runme.sh
@@ -47,6 +47,18 @@ echo $?
# view the vault encrypted password file
ansible-vault view "$@" --vault-id vault-password encrypted-vault-password
+# check if ansible-vault fails when destination is not writable
+NOT_WRITABLE_DIR="${MYTMPDIR}/not_writable"
+TEST_FILE_EDIT4="${NOT_WRITABLE_DIR}/testfile"
+mkdir "${NOT_WRITABLE_DIR}"
+touch "${TEST_FILE_EDIT4}"
+chmod ugo-w "${NOT_WRITABLE_DIR}"
+ansible-vault encrypt "$@" --vault-password-file vault-password "${TEST_FILE_EDIT4}" < /dev/null > log 2>&1 && :
+grep "not writable" log && :
+WRONG_RC=$?
+echo "rc was $WRONG_RC (1 is expected)"
+[ $WRONG_RC -eq 1 ]
+
# encrypt with a password from a vault encrypted password file and multiple vault-ids
# should fail because we dont know which vault id to use to encrypt with
ansible-vault encrypt "$@" --vault-id vault-password --vault-id encrypted-vault-password "${TEST_FILE_ENC_PASSWORD}" && :
@@ -574,3 +586,23 @@ ansible-playbook realpath.yml "$@" --vault-password-file symlink/get-password-sy
# using symlink
ansible-playbook symlink.yml "$@" --vault-password-file script/vault-secret.sh 2>&1 |grep "${ER}"
+
+### SALT TESTING ###
+# prep files for encryption
+for salted in test1 test2 test3
+do
+ echo 'this is salty' > "salted_${salted}"
+done
+
+# encrypt files
+ANSIBLE_VAULT_ENCRYPT_SALT=salty ansible-vault encrypt salted_test1 --vault-password-file example1_password "$@"
+ANSIBLE_VAULT_ENCRYPT_SALT=salty ansible-vault encrypt salted_test2 --vault-password-file example1_password "$@"
+ansible-vault encrypt salted_test3 --vault-password-file example1_password "$@"
+
+# should be the same
+out=$(diff salted_test1 salted_test2)
+[ "${out}" == "" ]
+
+# shoudl be diff
+out=$(diff salted_test1 salted_test3 || true)
+[ "${out}" != "" ]
diff --git a/test/integration/targets/ansible-vault/test_vault.yml b/test/integration/targets/ansible-vault/test_vault.yml
index 7f8ed115..c21d49a6 100644
--- a/test/integration/targets/ansible-vault/test_vault.yml
+++ b/test/integration/targets/ansible-vault/test_vault.yml
@@ -1,6 +1,6 @@
- hosts: testhost
gather_facts: False
vars:
- - output_dir: .
+ output_dir: .
roles:
- { role: test_vault, tags: test_vault}
diff --git a/test/integration/targets/ansible-vault/test_vaulted_template.yml b/test/integration/targets/ansible-vault/test_vaulted_template.yml
index b495211d..6a16ec86 100644
--- a/test/integration/targets/ansible-vault/test_vaulted_template.yml
+++ b/test/integration/targets/ansible-vault/test_vaulted_template.yml
@@ -1,6 +1,6 @@
- hosts: testhost
gather_facts: False
vars:
- - output_dir: .
+ output_dir: .
roles:
- { role: test_vaulted_template, tags: test_vaulted_template}
diff --git a/test/integration/targets/ansible/aliases b/test/integration/targets/ansible/aliases
index 8278ec8b..c7f2050a 100644
--- a/test/integration/targets/ansible/aliases
+++ b/test/integration/targets/ansible/aliases
@@ -1,2 +1,3 @@
shippable/posix/group3
context/controller
+needs/target/support-callback_plugins
diff --git a/test/integration/targets/ansible/ansible-testé.cfg b/test/integration/targets/ansible/ansible-testé.cfg
index 09af947f..a0e4e8d7 100644
--- a/test/integration/targets/ansible/ansible-testé.cfg
+++ b/test/integration/targets/ansible/ansible-testé.cfg
@@ -1,3 +1,3 @@
[defaults]
remote_user = admin
-collections_paths = /tmp/collections
+collections_path = /tmp/collections
diff --git a/test/integration/targets/ansible/runme.sh b/test/integration/targets/ansible/runme.sh
index e9e72a9f..d6780214 100755
--- a/test/integration/targets/ansible/runme.sh
+++ b/test/integration/targets/ansible/runme.sh
@@ -14,9 +14,9 @@ ANSIBLE_REMOTE_USER=administrator ansible-config dump| grep 'DEFAULT_REMOTE_USER
ansible-config list | grep 'DEFAULT_REMOTE_USER'
# Collection
-ansible-config view -c ./ansible-testé.cfg | grep 'collections_paths = /tmp/collections'
+ansible-config view -c ./ansible-testé.cfg | grep 'collections_path = /tmp/collections'
ansible-config dump -c ./ansible-testé.cfg | grep 'COLLECTIONS_PATHS([^)]*) ='
-ANSIBLE_COLLECTIONS_PATHS=/tmp/collections ansible-config dump| grep 'COLLECTIONS_PATHS([^)]*) ='
+ANSIBLE_COLLECTIONS_PATH=/tmp/collections ansible-config dump| grep 'COLLECTIONS_PATHS([^)]*) ='
ansible-config list | grep 'COLLECTIONS_PATHS'
# 'view' command must fail when config file is missing or has an invalid file extension
@@ -34,7 +34,7 @@ ansible localhost -m debug -a var=playbook_dir --playbook-dir=/doesnotexist/tmp
env -u ANSIBLE_PLAYBOOK_DIR ANSIBLE_CONFIG=./playbookdir_cfg.ini ansible localhost -m debug -a var=playbook_dir | grep '"playbook_dir": "/doesnotexist/tmp"'
# test adhoc callback triggers
-ANSIBLE_STDOUT_CALLBACK=callback_debug ANSIBLE_LOAD_CALLBACK_PLUGINS=1 ansible --playbook-dir . testhost -i ../../inventory -m ping | grep -E '^v2_' | diff -u adhoc-callback.stdout -
+ANSIBLE_CALLBACK_PLUGINS=../support-callback_plugins/callback_plugins ANSIBLE_STDOUT_CALLBACK=callback_debug ANSIBLE_LOAD_CALLBACK_PLUGINS=1 ansible --playbook-dir . testhost -i ../../inventory -m ping | grep -E '^v2_' | diff -u adhoc-callback.stdout -
# CB_WANTS_IMPLICIT isn't anything in Ansible itself.
# Our test cb plugin just accepts it. It lets us avoid copypasting the whole
diff --git a/test/integration/targets/apt/aliases b/test/integration/targets/apt/aliases
index 5f892f9c..20c87093 100644
--- a/test/integration/targets/apt/aliases
+++ b/test/integration/targets/apt/aliases
@@ -1,6 +1,5 @@
shippable/posix/group2
destructive
skip/freebsd
-skip/osx
skip/macos
skip/rhel
diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml
index d273eda7..a0bc1992 100644
--- a/test/integration/targets/apt/tasks/apt.yml
+++ b/test/integration/targets/apt/tasks/apt.yml
@@ -372,7 +372,7 @@
- libcaca-dev
- libslang2-dev
-# https://github.com/ansible/ansible/issues/38995
+# # https://github.com/ansible/ansible/issues/38995
- name: build-dep for a package
apt:
name: tree
@@ -524,6 +524,55 @@
- "allow_change_held_packages_no_update is not changed"
- "allow_change_held_packages_hello_version.stdout == allow_change_held_packages_hello_version_again.stdout"
+# Remove pkg on hold
+- name: Put hello on hold
+ shell: apt-mark hold hello
+
+- name: Get hold list
+ shell: apt-mark showhold
+ register: hello_hold
+
+- name: Check that the package hello is on the hold list
+ assert:
+ that:
+ - "'hello' in hello_hold.stdout"
+
+- name: Try removing package hello
+ apt:
+ name: hello
+ state: absent
+ register: package_removed
+ ignore_errors: true
+
+- name: verify the package is not removed with dpkg
+ shell: dpkg -l hello
+ register: dpkg_result
+
+- name: Verify that package was not removed
+ assert:
+ that:
+ - package_removed is failed
+ - dpkg_result is success
+
+- name: Try removing package (allow_change_held_packages=yes)
+ apt:
+ name: hello
+ state: absent
+ allow_change_held_packages: yes
+ register: package_removed
+
+- name: verify the package is removed with dpkg
+ shell: dpkg -l hello
+ register: dpkg_result
+ ignore_errors: true
+
+- name: Verify that package removal was succesfull
+ assert:
+ that:
+ - package_removed is success
+ - dpkg_result is failed
+ - package_removed.changed
+
# Virtual package
- name: Install a virtual package
apt:
diff --git a/test/integration/targets/apt/tasks/repo.yml b/test/integration/targets/apt/tasks/repo.yml
index d4cce78a..b1d08afa 100644
--- a/test/integration/targets/apt/tasks/repo.yml
+++ b/test/integration/targets/apt/tasks/repo.yml
@@ -400,6 +400,8 @@
- { upgrade_type: safe, force_apt_get: True }
- { upgrade_type: full, force_apt_get: True }
+ - include_tasks: "upgrade_scenarios.yml"
+
- name: Remove aptitude if not originally present
apt:
pkg: aptitude
diff --git a/test/integration/targets/apt_key/aliases b/test/integration/targets/apt_key/aliases
index a820ec90..97f534a8 100644
--- a/test/integration/targets/apt_key/aliases
+++ b/test/integration/targets/apt_key/aliases
@@ -1,5 +1,4 @@
shippable/posix/group1
skip/freebsd
-skip/osx
skip/macos
skip/rhel
diff --git a/test/integration/targets/apt_key/tasks/main.yml b/test/integration/targets/apt_key/tasks/main.yml
index ffb89b22..7aee56a7 100644
--- a/test/integration/targets/apt_key/tasks/main.yml
+++ b/test/integration/targets/apt_key/tasks/main.yml
@@ -21,7 +21,7 @@
- import_tasks: 'apt_key_inline_data.yml'
when: ansible_distribution in ('Ubuntu', 'Debian')
-
+
- import_tasks: 'file.yml'
when: ansible_distribution in ('Ubuntu', 'Debian')
diff --git a/test/integration/targets/apt_repository/aliases b/test/integration/targets/apt_repository/aliases
index 34e2b540..b4fe8dba 100644
--- a/test/integration/targets/apt_repository/aliases
+++ b/test/integration/targets/apt_repository/aliases
@@ -1,6 +1,5 @@
destructive
shippable/posix/group1
skip/freebsd
-skip/osx
skip/macos
skip/rhel
diff --git a/test/integration/targets/apt_repository/tasks/apt.yml b/test/integration/targets/apt_repository/tasks/apt.yml
index 9c15e647..2ddf4140 100644
--- a/test/integration/targets/apt_repository/tasks/apt.yml
+++ b/test/integration/targets/apt_repository/tasks/apt.yml
@@ -152,6 +152,11 @@
- 'result.changed'
- 'result.state == "present"'
- 'result.repo == test_ppa_spec'
+ - '"sources_added" in result'
+ - 'result.sources_added | length == 1'
+ - '"git" in result.sources_added[0]'
+ - '"sources_removed" in result'
+ - 'result.sources_removed | length == 0'
- result_cache is not changed
- name: 'examine apt cache mtime'
@@ -167,6 +172,17 @@
apt_repository: repo='{{test_ppa_spec}}' state=absent
register: result
+- assert:
+ that:
+ - 'result.changed'
+ - 'result.state == "absent"'
+ - 'result.repo == test_ppa_spec'
+ - '"sources_added" in result'
+ - 'result.sources_added | length == 0'
+ - '"sources_removed" in result'
+ - 'result.sources_removed | length == 1'
+ - '"git" in result.sources_removed[0]'
+
# When installing a repo with the spec, the key is *NOT* added
- name: 'ensure ppa key is absent (expect: pass)'
apt_key: id='{{test_ppa_key}}' state=absent
@@ -224,7 +240,7 @@
- assert:
that:
- result is failed
- - result.msg == 'Please set argument \'repo\' to a non-empty value'
+ - result.msg.startswith("argument 'repo' is of type <class 'NoneType'> and we were unable to convert to str")
- name: Test apt_repository with an empty value for repo
apt_repository:
diff --git a/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml b/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml
index 726de111..62960ccd 100644
--- a/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml
+++ b/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml
@@ -4,4 +4,4 @@
- name: Delete existing repo
file:
path: "{{ test_repo_path }}"
- state: absent \ No newline at end of file
+ state: absent
diff --git a/test/integration/targets/argspec/library/argspec.py b/test/integration/targets/argspec/library/argspec.py
index b6d6d110..2d86d77b 100644
--- a/test/integration/targets/argspec/library/argspec.py
+++ b/test/integration/targets/argspec/library/argspec.py
@@ -23,6 +23,10 @@ def main():
'type': 'str',
'choices': ['absent', 'present'],
},
+ 'default_value': {
+ 'type': 'bool',
+ 'default': True,
+ },
'path': {},
'content': {},
'mapping': {
@@ -246,7 +250,7 @@ def main():
('state', 'present', ('path', 'content'), True),
),
mutually_exclusive=(
- ('path', 'content'),
+ ('path', 'content', 'default_value',),
),
required_one_of=(
('required_one_of_one', 'required_one_of_two'),
diff --git a/test/integration/targets/become/tasks/main.yml b/test/integration/targets/become/tasks/main.yml
index 4a2ce64b..c05824d7 100644
--- a/test/integration/targets/become/tasks/main.yml
+++ b/test/integration/targets/become/tasks/main.yml
@@ -11,8 +11,8 @@
ansible_become_user: "{{ become_test_config.user }}"
ansible_become_method: "{{ become_test_config.method }}"
ansible_become_password: "{{ become_test_config.password | default(None) }}"
- loop: "{{
- (become_methods | selectattr('skip', 'undefined') | list)+
+ loop: "{{
+ (become_methods | selectattr('skip', 'undefined') | list)+
(become_methods | selectattr('skip', 'defined') | rejectattr('skip') | list)
}}"
loop_control:
diff --git a/test/integration/targets/blockinfile/tasks/main.yml b/test/integration/targets/blockinfile/tasks/main.yml
index 054e5549..f26cb165 100644
--- a/test/integration/targets/blockinfile/tasks/main.yml
+++ b/test/integration/targets/blockinfile/tasks/main.yml
@@ -31,6 +31,7 @@
- import_tasks: add_block_to_existing_file.yml
- import_tasks: create_file.yml
+- import_tasks: create_dir.yml
- import_tasks: preserve_line_endings.yml
- import_tasks: block_without_trailing_newline.yml
- import_tasks: file_without_trailing_newline.yml
@@ -39,3 +40,5 @@
- import_tasks: insertafter.yml
- import_tasks: insertbefore.yml
- import_tasks: multiline_search.yml
+- import_tasks: append_newline.yml
+- import_tasks: prepend_newline.yml
diff --git a/test/integration/targets/blocks/unsafe_failed_task.yml b/test/integration/targets/blocks/unsafe_failed_task.yml
index adfa492a..e74327b9 100644
--- a/test/integration/targets/blocks/unsafe_failed_task.yml
+++ b/test/integration/targets/blocks/unsafe_failed_task.yml
@@ -1,7 +1,7 @@
- hosts: localhost
gather_facts: false
vars:
- - data: {}
+ data: {}
tasks:
- block:
- name: template error
diff --git a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout
index 71a4ef9e..ed455756 100644
--- a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout
+++ b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout
@@ -43,6 +43,7 @@ fatal: [testhost]: FAILED! =>
TASK [Skipped task] ************************************************************
skipping: [testhost] =>
changed: false
+ false_condition: false
skip_reason: Conditional result was False
TASK [Task with var in name (foo bar)] *****************************************
@@ -120,6 +121,7 @@ ok: [testhost] => (item=debug-3) =>
msg: debug-3
skipping: [testhost] => (item=debug-4) =>
ansible_loop_var: item
+ false_condition: item != 4
item: 4
fatal: [testhost]: FAILED! =>
msg: One or more items failed
@@ -199,9 +201,11 @@ skipping: [testhost] =>
TASK [debug] *******************************************************************
skipping: [testhost] => (item=1) =>
ansible_loop_var: item
+ false_condition: false
item: 1
skipping: [testhost] => (item=2) =>
ansible_loop_var: item
+ false_condition: false
item: 2
skipping: [testhost] =>
msg: All items skipped
diff --git a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout
index 7a99cc74..3a121a5f 100644
--- a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout
+++ b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout
@@ -45,6 +45,7 @@ fatal: [testhost]: FAILED! =>
TASK [Skipped task] ************************************************************
skipping: [testhost] =>
changed: false
+ false_condition: false
skip_reason: Conditional result was False
TASK [Task with var in name (foo bar)] *****************************************
@@ -126,6 +127,7 @@ ok: [testhost] => (item=debug-3) =>
msg: debug-3
skipping: [testhost] => (item=debug-4) =>
ansible_loop_var: item
+ false_condition: item != 4
item: 4
fatal: [testhost]: FAILED! =>
msg: One or more items failed
@@ -206,9 +208,11 @@ skipping: [testhost] =>
TASK [debug] *******************************************************************
skipping: [testhost] => (item=1) =>
ansible_loop_var: item
+ false_condition: false
item: 1
skipping: [testhost] => (item=2) =>
ansible_loop_var: item
+ false_condition: false
item: 2
skipping: [testhost] =>
msg: All items skipped
diff --git a/test/integration/targets/check_mode/check_mode.yml b/test/integration/targets/check_mode/check_mode.yml
index a5777506..ebf1c5b5 100644
--- a/test/integration/targets/check_mode/check_mode.yml
+++ b/test/integration/targets/check_mode/check_mode.yml
@@ -1,7 +1,7 @@
- name: Test that check works with check_mode specified in roles
hosts: testhost
vars:
- - output_dir: .
+ output_dir: .
roles:
- { role: test_always_run, tags: test_always_run }
- { role: test_check_mode, tags: test_check_mode }
diff --git a/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml b/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml
index f926d144..ce9ecbf4 100644
--- a/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml
+++ b/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml
@@ -25,8 +25,8 @@
register: foo
- name: verify that the file was marked as changed in check mode
- assert:
- that:
+ assert:
+ that:
- "template_result is changed"
- "not foo.stat.exists"
@@ -44,7 +44,7 @@
check_mode: no
- name: verify that the file was not changed
- assert:
- that:
+ assert:
+ that:
- "checkmode_disabled is changed"
- "template_result2 is not changed"
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py
index fc19a99d..77f80502 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py
@@ -1,7 +1,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.connection import ConnectionBase
DOCUMENTATION = """
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py
index b945eb68..6f3a19d7 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py
@@ -2,10 +2,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-import sys
-
-from ..module_utils import bogusmu # pylint: disable=relative-beyond-top-level
+from ..module_utils import bogusmu # pylint: disable=relative-beyond-top-level,unused-import
def main():
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py
index 59cb3c5e..6f2320d3 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py
@@ -2,10 +2,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-import sys
-
-from ..module_utils import missing_redirect_target_collection # pylint: disable=relative-beyond-top-level
+from ..module_utils import missing_redirect_target_collection # pylint: disable=relative-beyond-top-level,unused-import
def main():
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py
index 31ffd17c..de5c2e58 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py
@@ -2,10 +2,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-import sys
-
-from ..module_utils import missing_redirect_target_module # pylint: disable=relative-beyond-top-level
+from ..module_utils import missing_redirect_target_module # pylint: disable=relative-beyond-top-level,unused-import
def main():
diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py
index ae6941f3..92696481 100644
--- a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py
+++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py
@@ -19,7 +19,6 @@ DOCUMENTATION = '''
required: True
'''
-from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable
diff --git a/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py
index 23cce104..f1242e14 100644
--- a/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py
+++ b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py
@@ -14,7 +14,6 @@ DOCUMENTATION = '''
- Enable in configuration.
'''
-from ansible import constants as C
from ansible.plugins.callback import CallbackBase
diff --git a/test/integration/targets/command_nonexisting/tasks/main.yml b/test/integration/targets/command_nonexisting/tasks/main.yml
index d21856e7..e54ecb3f 100644
--- a/test/integration/targets/command_nonexisting/tasks/main.yml
+++ b/test/integration/targets/command_nonexisting/tasks/main.yml
@@ -1,4 +1,4 @@
- command: commandthatdoesnotexist --would-be-awkward
register: res
changed_when: "'changed' in res.stdout"
- failed_when: "res.stdout != '' or res.stderr != ''" \ No newline at end of file
+ failed_when: "res.stdout != '' or res.stderr != ''"
diff --git a/test/integration/targets/command_shell/tasks/main.yml b/test/integration/targets/command_shell/tasks/main.yml
index 1f4aa5d7..2cc365db 100644
--- a/test/integration/targets/command_shell/tasks/main.yml
+++ b/test/integration/targets/command_shell/tasks/main.yml
@@ -284,6 +284,30 @@
that:
- "command_result6.stdout == '9cd0697c6a9ff6689f0afb9136fa62e0b3fee903'"
+- name: check default var expansion
+ command: /bin/sh -c 'echo "\$TEST"'
+ environment:
+ TEST: z
+ register: command_result7
+
+- name: assert vars were expanded
+ assert:
+ that:
+ - command_result7.stdout == '\\z'
+
+- name: check disabled var expansion
+ command: /bin/sh -c 'echo "\$TEST"'
+ args:
+ expand_argument_vars: false
+ environment:
+ TEST: z
+ register: command_result8
+
+- name: assert vars were not expanded
+ assert:
+ that:
+ - command_result8.stdout == '$TEST'
+
##
## shell
##
@@ -546,3 +570,21 @@
- command_strip.stderr == 'hello \n '
- command_no_strip.stdout== 'hello \n \r\n'
- command_no_strip.stderr == 'hello \n \r\n'
+
+- name: run shell with expand_argument_vars
+ shell: echo 'hi'
+ args:
+ expand_argument_vars: false
+ register: shell_expand_failure
+ ignore_errors: true
+
+- name: assert shell with expand_arguments_vars failed
+ assert:
+ that:
+ - shell_expand_failure is failed
+ - "shell_expand_failure.msg == 'Unsupported parameters for (shell) module: expand_argument_vars'"
+
+- name: Run command that backgrounds, to ensure no hang
+ shell: '{{ role_path }}/scripts/yoink.sh &'
+ delegate_to: localhost
+ timeout: 5
diff --git a/test/integration/targets/conditionals/play.yml b/test/integration/targets/conditionals/play.yml
index 455818c9..56ec8438 100644
--- a/test/integration/targets/conditionals/play.yml
+++ b/test/integration/targets/conditionals/play.yml
@@ -665,3 +665,29 @@
- item
loop:
- 1 == 1
+
+ - set_fact:
+ sentinel_file: '{{ lookup("env", "OUTPUT_DIR")}}/LOOKUP_SIDE_EFFECT.txt'
+
+ - name: ensure sentinel file is absent
+ file:
+ path: '{{ sentinel_file }}'
+ state: absent
+ - name: get an untrusted var that's a valid Jinja expression with a side-effect
+ shell: |
+ echo "lookup('pipe', 'echo bang > \"$SENTINEL_FILE\" && cat \"$SENTINEL_FILE\"')"
+ environment:
+ SENTINEL_FILE: '{{ sentinel_file }}'
+ register: untrusted_expr
+ - name: use a conditional with an inline template that refers to the untrusted expression
+ debug:
+ msg: look at some seemingly innocuous stuff
+ when: '"foo" in {{ untrusted_expr.stdout }}'
+ ignore_errors: true
+ - name: ensure the untrusted expression side-effect has not executed
+ stat:
+ path: '{{ sentinel_file }}'
+ register: sentinel_stat
+ - assert:
+ that:
+ - not sentinel_stat.stat.exists
diff --git a/test/integration/targets/connection_delegation/aliases b/test/integration/targets/connection_delegation/aliases
index 6c965663..0ce76011 100644
--- a/test/integration/targets/connection_delegation/aliases
+++ b/test/integration/targets/connection_delegation/aliases
@@ -1,6 +1,5 @@
shippable/posix/group3
context/controller
skip/freebsd # No sshpass
-skip/osx # No sshpass
skip/macos # No sshpass
skip/rhel # No sshpass
diff --git a/test/integration/targets/connection_paramiko_ssh/test_connection.inventory b/test/integration/targets/connection_paramiko_ssh/test_connection.inventory
index a3f34ab7..cd17c090 100644
--- a/test/integration/targets/connection_paramiko_ssh/test_connection.inventory
+++ b/test/integration/targets/connection_paramiko_ssh/test_connection.inventory
@@ -2,6 +2,6 @@
paramiko_ssh-pipelining ansible_ssh_pipelining=true
paramiko_ssh-no-pipelining ansible_ssh_pipelining=false
[paramiko_ssh:vars]
-ansible_host=localhost
+ansible_host={{ 'localhost'|string }}
ansible_connection=paramiko_ssh
ansible_python_interpreter="{{ ansible_playbook_python }}"
diff --git a/test/integration/targets/connection_psrp/tests.yml b/test/integration/targets/connection_psrp/tests.yml
index dabbf407..08832b14 100644
--- a/test/integration/targets/connection_psrp/tests.yml
+++ b/test/integration/targets/connection_psrp/tests.yml
@@ -6,6 +6,9 @@
gather_facts: no
tasks:
+ - name: reboot the host
+ ansible.windows.win_reboot:
+
- name: test complex objects in raw output
# until PyYAML is upgraded to 4.x we need to use the \U escape for a unicode codepoint
# and enclose in a quote to it translates the \U
@@ -29,15 +32,8 @@
- raw_out.stdout_lines[4] == "winrm"
- raw_out.stdout_lines[5] == "string - \U0001F4A9"
- # Become only works on Server 2008 when running with basic auth, skip this host for now as it is too complicated to
- # override the auth protocol in the tests.
- - name: check if we running on Server 2008
- win_shell: '[System.Environment]::OSVersion.Version -ge [Version]"6.1"'
- register: os_version
-
- name: test out become with psrp
win_whoami:
- when: os_version|bool
register: whoami_out
become: yes
become_method: runas
@@ -47,7 +43,6 @@
assert:
that:
- whoami_out.account.sid == "S-1-5-18"
- when: os_version|bool
- name: test out async with psrp
win_shell: Start-Sleep -Seconds 2; Write-Output abc
diff --git a/test/integration/targets/connection_winrm/tests.yml b/test/integration/targets/connection_winrm/tests.yml
index 78f92a49..b086a3ad 100644
--- a/test/integration/targets/connection_winrm/tests.yml
+++ b/test/integration/targets/connection_winrm/tests.yml
@@ -6,6 +6,9 @@
gather_facts: no
tasks:
+ - name: reboot the host
+ ansible.windows.win_reboot:
+
- name: setup remote tmp dir
import_role:
name: ../../setup_remote_tmp_dir
diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml
index b86c56ac..601312fa 100644
--- a/test/integration/targets/copy/tasks/main.yml
+++ b/test/integration/targets/copy/tasks/main.yml
@@ -84,6 +84,7 @@
- import_tasks: check_mode.yml
# https://github.com/ansible/ansible/issues/57618
+ # https://github.com/ansible/ansible/issues/79749
- name: Test diff contents
copy:
content: 'Ansible managed\n'
@@ -95,6 +96,7 @@
that:
- 'diff_output.diff[0].before == ""'
- '"Ansible managed" in diff_output.diff[0].after'
+ - '"file.txt" in diff_output.diff[0].after_header'
- name: tests with remote_src and non files
import_tasks: src_remote_file_is_not_file.yml
diff --git a/test/integration/targets/copy/tasks/tests.yml b/test/integration/targets/copy/tasks/tests.yml
index d6c8e63c..40ea9de3 100644
--- a/test/integration/targets/copy/tasks/tests.yml
+++ b/test/integration/targets/copy/tasks/tests.yml
@@ -420,6 +420,80 @@
- "stat_results2.stat.mode == '0547'"
#
+# test copying an empty dir to a dest dir with remote_src=True
+#
+
+- name: create empty test dir
+ file:
+ path: '{{ remote_dir }}/testcase_empty_dir'
+ state: directory
+
+- name: test copying an empty dir to a dir that does not exist (dest ends with slash)
+ copy:
+ src: '{{ remote_dir }}/testcase_empty_dir/'
+ remote_src: yes
+ dest: '{{ remote_dir }}/testcase_empty_dir_dest/'
+ register: copy_result
+
+- name: get stat of newly created dir
+ stat:
+ path: '{{ remote_dir }}/testcase_empty_dir_dest'
+ register: stat_result
+
+- assert:
+ that:
+ - copy_result.changed
+ - stat_result.stat.exists
+ - stat_result.stat.isdir
+
+- name: test no change is made running the task twice
+ copy:
+ src: '{{ remote_dir }}/testcase_empty_dir/'
+ remote_src: yes
+ dest: '{{ remote_dir }}/testcase_empty_dir_dest/'
+ register: copy_result
+ failed_when: copy_result is changed
+
+- name: remove to test dest with no trailing slash
+ file:
+ path: '{{ remote_dir }}/testcase_empty_dir_dest/'
+ state: absent
+
+- name: test copying an empty dir to a dir that does not exist (both src/dest have no trailing slash)
+ copy:
+ src: '{{ remote_dir }}/testcase_empty_dir'
+ remote_src: yes
+ dest: '{{ remote_dir }}/testcase_empty_dir_dest'
+ register: copy_result
+
+- name: get stat of newly created dir
+ stat:
+ path: '{{ remote_dir }}/testcase_empty_dir_dest'
+ register: stat_result
+
+- assert:
+ that:
+ - copy_result.changed
+ - stat_result.stat.exists
+ - stat_result.stat.isdir
+
+- name: test no change is made running the task twice
+ copy:
+ src: '{{ remote_dir }}/testcase_empty_dir/'
+ remote_src: yes
+ dest: '{{ remote_dir }}/testcase_empty_dir_dest/'
+ register: copy_result
+ failed_when: copy_result is changed
+
+- name: clean up src and dest
+ file:
+ path: "{{ item }}"
+ state: absent
+ loop:
+ - '{{ remote_dir }}/testcase_empty_dir'
+ - '{{ remote_dir }}/testcase_empty_dir_dest'
+
+#
# test recursive copy local_follow=False, no trailing slash
#
@@ -2284,3 +2358,81 @@
that:
- fail_copy_directory_with_enc_file is failed
- fail_copy_directory_with_enc_file.msg == 'A vault password or secret must be specified to decrypt {{role_path}}/files-different/vault/vault-file'
+
+#
+# Test for issue 74536: recursively copy all nested directories with remote_src=yes and src='dir/' when dest exists
+#
+- vars:
+ src: '{{ remote_dir }}/testcase_74536'
+ block:
+ - name: create source dir with 3 nested subdirs
+ file:
+ path: '{{ src }}/a/b1/c1'
+ state: directory
+
+ - name: copy the source dir with a trailing slash
+ copy:
+ src: '{{ src }}/'
+ remote_src: yes
+ dest: '{{ src }}_dest/'
+ register: copy_result
+ failed_when: copy_result is not changed
+
+ - name: remove the source dir to recreate with different subdirs
+ file:
+ path: '{{ src }}'
+ state: absent
+
+ - name: recreate source dir
+ file:
+ path: "{{ item }}"
+ state: directory
+ loop:
+ - '{{ src }}/a/b1/c2'
+ - '{{ src }}/a/b2/c3'
+
+ - name: copy the source dir containing new subdirs into the existing dest dir
+ copy:
+ src: '{{ src }}/'
+ remote_src: yes
+ dest: '{{ src }}_dest/'
+ register: copy_result
+
+ - name: stat each directory that should exist
+ stat:
+ path: '{{ item }}'
+ register: stat_result
+ loop:
+ - '{{ src }}_dest'
+ - '{{ src }}_dest/a'
+ - '{{ src }}_dest/a/b1'
+ - '{{ src }}_dest/a/b2'
+ - '{{ src }}_dest/a/b1/c1'
+ - '{{ src }}_dest/a/b1/c2'
+ - '{{ src }}_dest/a/b2/c3'
+
+ - debug: msg="{{ stat_result }}"
+
+ - assert:
+ that:
+ - copy_result is changed
+ # all paths exist
+ - stat_result.results | map(attribute='stat') | map(attribute='exists') | unique == [true]
+ # all paths are dirs
+ - stat_result.results | map(attribute='stat') | map(attribute='isdir') | unique == [true]
+
+ - name: copy the src again to verify no changes will be made
+ copy:
+ src: '{{ src }}/'
+ remote_src: yes
+ dest: '{{ src }}_dest/'
+ register: copy_result
+ failed_when: copy_result is changed
+
+ - name: clean up src and dest
+ file:
+ path: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ src }}'
+ - '{{ src }}_dest'
diff --git a/test/integration/targets/cron/aliases b/test/integration/targets/cron/aliases
index f2f9ac9d..f3703f85 100644
--- a/test/integration/targets/cron/aliases
+++ b/test/integration/targets/cron/aliases
@@ -1,4 +1,3 @@
destructive
shippable/posix/group1
-skip/osx
skip/macos
diff --git a/test/integration/targets/debconf/tasks/main.yml b/test/integration/targets/debconf/tasks/main.yml
index d3d63cdf..f9236268 100644
--- a/test/integration/targets/debconf/tasks/main.yml
+++ b/test/integration/targets/debconf/tasks/main.yml
@@ -33,4 +33,44 @@
- 'debconf_test0.current is defined'
- '"tzdata/Zones/Etc" in debconf_test0.current'
- 'debconf_test0.current["tzdata/Zones/Etc"] == "UTC"'
- when: ansible_distribution in ('Ubuntu', 'Debian')
+
+ - name: install debconf-utils
+ apt:
+ name: debconf-utils
+ state: present
+ register: debconf_utils_deb_install
+
+ - name: Check if password is set
+ debconf:
+ name: ddclient
+ question: ddclient/password
+ value: "MySecretValue"
+ vtype: password
+ register: debconf_test1
+
+ - name: validate results for test 1
+ assert:
+ that:
+ - debconf_test1.changed
+
+ - name: Change password again
+ debconf:
+ name: ddclient
+ question: ddclient/password
+ value: "MySecretValue"
+ vtype: password
+ no_log: yes
+ register: debconf_test2
+
+ - name: validate results for test 1
+ assert:
+ that:
+ - not debconf_test2.changed
+ always:
+ - name: uninstall debconf-utils
+ apt:
+ name: debconf-utils
+ state: absent
+ when: debconf_utils_deb_install is changed
+
+ when: ansible_distribution in ('Ubuntu', 'Debian') \ No newline at end of file
diff --git a/test/integration/targets/delegate_to/delegate_local_from_root.yml b/test/integration/targets/delegate_to/delegate_local_from_root.yml
index c9be4ff2..b44f83bd 100644
--- a/test/integration/targets/delegate_to/delegate_local_from_root.yml
+++ b/test/integration/targets/delegate_to/delegate_local_from_root.yml
@@ -3,7 +3,7 @@
gather_facts: false
remote_user: root
tasks:
- - name: ensure we copy w/o errors due to remote user not being overriden
+ - name: ensure we copy w/o errors due to remote user not being overridden
copy:
src: testfile
dest: "{{ playbook_dir }}"
diff --git a/test/integration/targets/delegate_to/runme.sh b/test/integration/targets/delegate_to/runme.sh
index 1bdf27cf..e0dcc746 100755
--- a/test/integration/targets/delegate_to/runme.sh
+++ b/test/integration/targets/delegate_to/runme.sh
@@ -76,3 +76,7 @@ ansible-playbook test_delegate_to_lookup_context.yml -i inventory -v "$@"
ansible-playbook delegate_local_from_root.yml -i inventory -v "$@" -e 'ansible_user=root'
ansible-playbook delegate_with_fact_from_delegate_host.yml "$@"
ansible-playbook delegate_facts_loop.yml -i inventory -v "$@"
+ansible-playbook test_random_delegate_to_with_loop.yml -i inventory -v "$@"
+
+# Run playbook multiple times to ensure there are no false-negatives
+for i in $(seq 0 10); do ansible-playbook test_random_delegate_to_without_loop.yml -i inventory -v "$@"; done;
diff --git a/test/integration/targets/delegate_to/test_delegate_to.yml b/test/integration/targets/delegate_to/test_delegate_to.yml
index dcfa9d03..eb601e02 100644
--- a/test/integration/targets/delegate_to/test_delegate_to.yml
+++ b/test/integration/targets/delegate_to/test_delegate_to.yml
@@ -1,9 +1,9 @@
- hosts: testhost3
vars:
- - template_role: ./roles/test_template
- - output_dir: "{{ playbook_dir }}"
- - templated_var: foo
- - templated_dict: { 'hello': 'world' }
+ template_role: ./roles/test_template
+ output_dir: "{{ playbook_dir }}"
+ templated_var: foo
+ templated_dict: { 'hello': 'world' }
tasks:
- name: Test no delegate_to
setup:
@@ -57,6 +57,25 @@
- name: remove test file
file: path={{ output_dir }}/tmp.txt state=absent
+ - name: Use omit to thwart delegation
+ ping:
+ delegate_to: "{{ jenkins_install_key_on|default(omit) }}"
+ register: d_omitted
+
+ - name: Use empty to thwart delegation should fail
+ ping:
+ delegate_to: "{{ jenkins_install_key_on }}"
+ when: jenkins_install_key_on != ""
+ vars:
+ jenkins_install_key_on: ''
+ ignore_errors: true
+ register: d_empty
+
+ - name: Ensure previous 2 tests actually did what was expected
+ assert:
+ that:
+ - d_omitted is success
+ - d_empty is failed
- name: verify delegation with per host vars
hosts: testhost6
diff --git a/test/integration/targets/dnf/aliases b/test/integration/targets/dnf/aliases
index d6f27b8e..b12f3547 100644
--- a/test/integration/targets/dnf/aliases
+++ b/test/integration/targets/dnf/aliases
@@ -1,6 +1,4 @@
destructive
shippable/posix/group1
-skip/power/centos
skip/freebsd
-skip/osx
skip/macos
diff --git a/test/integration/targets/dnf/tasks/dnf.yml b/test/integration/targets/dnf/tasks/dnf.yml
index ec1c36f8..9845f3db 100644
--- a/test/integration/targets/dnf/tasks/dnf.yml
+++ b/test/integration/targets/dnf/tasks/dnf.yml
@@ -224,7 +224,7 @@
- assert:
that:
- dnf_result is success
- - dnf_result.results|length == 2
+ - dnf_result.results|length >= 2
- "dnf_result.results[0].startswith('Removed: ')"
- "dnf_result.results[1].startswith('Removed: ')"
@@ -427,6 +427,10 @@
- shell: 'dnf -y group install "Custom Group" && dnf -y group remove "Custom Group"'
register: shell_dnf_result
+- dnf:
+ name: "@Custom Group"
+ state: absent
+
# GROUP UPGRADE - this will go to the same method as group install
# but through group_update - it is its invocation we're testing here
# see commit 119c9e5d6eb572c4a4800fbe8136095f9063c37b
@@ -446,6 +450,10 @@
# cleanup until https://github.com/ansible/ansible/issues/27377 is resolved
- shell: dnf -y group install "Custom Group" && dnf -y group remove "Custom Group"
+- dnf:
+ name: "@Custom Group"
+ state: absent
+
- name: try to install non existing group
dnf:
name: "@non-existing-group"
@@ -551,30 +559,35 @@
- "'No package non-existent-rpm available' in dnf_result['failures'][0]"
- "'Failed to install some of the specified packages' in dnf_result['msg']"
-- name: use latest to install httpd
+- name: ensure sos isn't installed
dnf:
- name: httpd
+ name: sos
+ state: absent
+
+- name: use latest to install sos
+ dnf:
+ name: sos
state: latest
register: dnf_result
-- name: verify httpd was installed
+- name: verify sos was installed
assert:
that:
- - "'changed' in dnf_result"
+ - dnf_result is changed
-- name: uninstall httpd
+- name: uninstall sos
dnf:
- name: httpd
+ name: sos
state: removed
-- name: update httpd only if it exists
+- name: update sos only if it exists
dnf:
- name: httpd
+ name: sos
state: latest
update_only: yes
register: dnf_result
-- name: verify httpd not installed
+- name: verify sos not installed
assert:
that:
- "not dnf_result is changed"
@@ -655,6 +668,28 @@
- "'changed' in dnf_result"
- "'results' in dnf_result"
+# Install RPM from url with update_only
+- name: install from url with update_only
+ dnf:
+ name: "file://{{ pkg_path }}"
+ state: latest
+ update_only: true
+ disable_gpg_check: true
+ register: dnf_result
+
+- name: verify installation
+ assert:
+ that:
+ - "dnf_result is success"
+ - "not dnf_result is changed"
+ - "dnf_result is not failed"
+
+- name: verify dnf module outputs
+ assert:
+ that:
+ - "'changed' in dnf_result"
+ - "'results' in dnf_result"
+
- name: Create a temp RPM file which does not contain nevra information
file:
name: "/tmp/non_existent_pkg.rpm"
diff --git a/test/integration/targets/dnf/tasks/main.yml b/test/integration/targets/dnf/tasks/main.yml
index 65b77ceb..4941e2c3 100644
--- a/test/integration/targets/dnf/tasks/main.yml
+++ b/test/integration/targets/dnf/tasks/main.yml
@@ -61,6 +61,7 @@
when:
- (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('29', '>=')) or
(ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
+ - not dnf5|default(false)
tags:
- dnf_modularity
@@ -69,5 +70,6 @@
(ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
- include_tasks: cacheonly.yml
- when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
- (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
+ when:
+ - (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or
+ (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>='))
diff --git a/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml b/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml
index 503cb4c3..f54c0a83 100644
--- a/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml
+++ b/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml
@@ -240,7 +240,8 @@
- name: Do an "upgrade" to an older version of broken-a, allow_downgrade=false
dnf:
name:
- - broken-a-1.2.3-1*
+ #- broken-a-1.2.3-1*
+ - broken-a-1.2.3-1.el7.x86_64
state: latest
allow_downgrade: false
check_mode: true
diff --git a/test/integration/targets/dnf/tasks/test_sos_removal.yml b/test/integration/targets/dnf/tasks/test_sos_removal.yml
index 0d70cf78..5e161dbb 100644
--- a/test/integration/targets/dnf/tasks/test_sos_removal.yml
+++ b/test/integration/targets/dnf/tasks/test_sos_removal.yml
@@ -15,5 +15,5 @@
that:
- sos_rm is successful
- sos_rm is changed
- - "'Removed: sos-' ~ sos_version ~ '-' ~ sos_release in sos_rm.results[0]"
- - sos_rm.results|length == 1
+ - sos_rm.results|select("contains", "Removed: sos-{{ sos_version }}-{{ sos_release }}")|length > 0
+ - sos_rm.results|length > 0
diff --git a/test/integration/targets/dpkg_selections/aliases b/test/integration/targets/dpkg_selections/aliases
index c0d5684b..9c44d752 100644
--- a/test/integration/targets/dpkg_selections/aliases
+++ b/test/integration/targets/dpkg_selections/aliases
@@ -1,6 +1,5 @@
shippable/posix/group1
destructive
skip/freebsd
-skip/osx
skip/macos
skip/rhel
diff --git a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
index 080db262..016d7716 100644
--- a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
+++ b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml
@@ -87,3 +87,15 @@
apt:
name: hello
state: absent
+
+- name: Try to select non-existent package
+ dpkg_selections:
+ name: kernel
+ selection: hold
+ ignore_errors: yes
+ register: result
+
+- name: Check that module fails for non-existent package
+ assert:
+ that:
+ - "'Failed to find package' in result.msg"
diff --git a/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py b/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py
index c0c5ccd5..28227fce 100644
--- a/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py
+++ b/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py
@@ -1,7 +1,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import pkg_resources
+import pkg_resources # pylint: disable=unused-import
from ansible.plugins.lookup import LookupBase
diff --git a/test/integration/targets/environment/test_environment.yml b/test/integration/targets/environment/test_environment.yml
index 43f9c74e..f295cf3c 100644
--- a/test/integration/targets/environment/test_environment.yml
+++ b/test/integration/targets/environment/test_environment.yml
@@ -7,8 +7,8 @@
- hosts: testhost
vars:
- - test1:
- key1: val1
+ test1:
+ key1: val1
environment:
PATH: '{{ansible_env.PATH + ":/lola"}}'
lola: 'ido'
@@ -41,9 +41,9 @@
- hosts: testhost
vars:
- - test1:
- key1: val1
- - test2:
+ test1:
+ key1: val1
+ test2:
key1: not1
other1: val2
environment: "{{test1}}"
diff --git a/test/integration/targets/error_from_connection/connection_plugins/dummy.py b/test/integration/targets/error_from_connection/connection_plugins/dummy.py
index 59a81a1b..d322fe0d 100644
--- a/test/integration/targets/error_from_connection/connection_plugins/dummy.py
+++ b/test/integration/targets/error_from_connection/connection_plugins/dummy.py
@@ -11,7 +11,6 @@ DOCUMENTATION = """
version_added: "2.0"
options: {}
"""
-import ansible.constants as C
from ansible.errors import AnsibleError
from ansible.plugins.connection import ConnectionBase
diff --git a/test/integration/targets/expect/tasks/main.yml b/test/integration/targets/expect/tasks/main.yml
index 7bf18c5e..2aef5957 100644
--- a/test/integration/targets/expect/tasks/main.yml
+++ b/test/integration/targets/expect/tasks/main.yml
@@ -148,6 +148,15 @@
- "echo_result.stdout_lines[-2] == 'foobar'"
- "echo_result.stdout_lines[-1] == 'bar'"
+- name: test timeout is valid as null
+ expect:
+ command: "{{ansible_python_interpreter}} {{test_command_file}}"
+ responses:
+ foo: bar
+ echo: true
+ timeout: null # wait indefinitely
+ timeout: 2 # but shouldn't be waiting long
+
- name: test response list
expect:
command: "{{ansible_python_interpreter}} {{test_command_file}} foo foo"
diff --git a/test/integration/targets/facts_linux_network/aliases b/test/integration/targets/facts_linux_network/aliases
index 100ce23a..c9e1dc55 100644
--- a/test/integration/targets/facts_linux_network/aliases
+++ b/test/integration/targets/facts_linux_network/aliases
@@ -1,7 +1,6 @@
needs/privileged
shippable/posix/group1
skip/freebsd
-skip/osx
skip/macos
context/target
destructive
diff --git a/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml b/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml
index 8a6b5b7b..d0bf9bdc 100644
--- a/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml
+++ b/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml
@@ -28,6 +28,15 @@
register: failed_fetch_dest_dir
ignore_errors: true
+- name: Test unreachable
+ fetch:
+ src: "{{ remote_tmp_dir }}/orig"
+ dest: "{{ output_dir }}"
+ register: unreachable_fetch
+ ignore_unreachable: true
+ vars:
+ ansible_user: wrong
+
- name: Ensure fetch failed
assert:
that:
@@ -39,3 +48,4 @@
- failed_fetch_no_access.msg is search('file is not readable')
- failed_fetch_dest_dir is failed
- failed_fetch_dest_dir.msg is search('dest is an existing directory')
+ - unreachable_fetch is unreachable
diff --git a/test/integration/targets/file/tasks/link_rewrite.yml b/test/integration/targets/file/tasks/link_rewrite.yml
index b0e1af3e..2416c2ca 100644
--- a/test/integration/targets/file/tasks/link_rewrite.yml
+++ b/test/integration/targets/file/tasks/link_rewrite.yml
@@ -16,11 +16,11 @@
dest: "{{ tempdir.path }}/somelink"
state: link
-- stat:
+- stat:
path: "{{ tempdir.path }}/somelink"
register: link
-- stat:
+- stat:
path: "{{ tempdir.path }}/somefile"
register: file
@@ -32,12 +32,12 @@
- file:
path: "{{ tempdir.path }}/somelink"
mode: 0644
-
-- stat:
+
+- stat:
path: "{{ tempdir.path }}/somelink"
register: link
-- stat:
+- stat:
path: "{{ tempdir.path }}/somefile"
register: file
diff --git a/test/integration/targets/file/tasks/main.yml b/test/integration/targets/file/tasks/main.yml
index a5bd68d7..c1b4c791 100644
--- a/test/integration/targets/file/tasks/main.yml
+++ b/test/integration/targets/file/tasks/main.yml
@@ -779,7 +779,7 @@
register: touch_result_in_check_mode_fails_not_existing_group
- assert:
- that:
+ that:
- touch_result_in_check_mode_not_existing.changed
- touch_result_in_check_mode_preserve_access_time.changed
- touch_result_in_check_mode_change_only_mode.changed
diff --git a/test/integration/targets/filter_core/tasks/main.yml b/test/integration/targets/filter_core/tasks/main.yml
index 2d084191..9d287a18 100644
--- a/test/integration/targets/filter_core/tasks/main.yml
+++ b/test/integration/targets/filter_core/tasks/main.yml
@@ -454,6 +454,38 @@
- password_hash_2 is failed
- "'not support' in password_hash_2.msg"
+- name: install passlib if needed
+ pip:
+ name: passlib
+ state: present
+ register: installed_passlib
+
+- name: test using passlib with an unsupported hash type
+ set_fact:
+ foo: '{{"hey"|password_hash("msdcc")}}'
+ ignore_errors: yes
+ register: unsupported_hash_type
+
+- name: remove passlib if it was installed
+ pip:
+ name: passlib
+ state: absent
+ when: installed_passlib.changed
+
+- assert:
+ that:
+ - unsupported_hash_type.msg == msg
+ vars:
+ msg: "msdcc is not in the list of supported passlib algorithms: md5, blowfish, sha256, sha512"
+
+- name: test password_hash can work with bcrypt without passlib installed
+ debug:
+ msg: "{{ 'somestring'|password_hash('bcrypt') }}"
+ register: crypt_bcrypt
+ # Some implementations of crypt do not fail outright and return some short value.
+ failed_when: crypt_bcrypt is failed or (crypt_bcrypt.msg|length|int) != 60
+ when: ansible_facts.os_family in ['RedHat', 'Debian']
+
- name: Verify to_uuid throws on weird namespace
set_fact:
foo: '{{"hey"|to_uuid(namespace=22)}}'
diff --git a/test/integration/targets/filter_encryption/base.yml b/test/integration/targets/filter_encryption/base.yml
index 8bf25f77..1479f734 100644
--- a/test/integration/targets/filter_encryption/base.yml
+++ b/test/integration/targets/filter_encryption/base.yml
@@ -2,6 +2,7 @@
gather_facts: true
vars:
data: secret
+ data2: 'foo: bar\n'
dvault: '{{ "secret"|vault("test")}}'
password: test
s_32: '{{(2**31-1)}}'
@@ -21,6 +22,15 @@
is_64: '{{ "64" in ansible_facts["architecture"] }}'
salt: '{{ is_64|bool|ternary(s_64, s_32)|random(seed=inventory_hostname)}}'
vaultedstring: '{{ is_64|bool|ternary(vaultedstring_64, vaultedstring_32) }}'
+ # command line vaulted data2
+ vaulted_id: !vault |
+ $ANSIBLE_VAULT;1.2;AES256;test1
+ 36383733336533656264393332663131613335333332346439356164383935656234663631356430
+ 3533353537343834333538356366376233326364613362640a623832636339363966336238393039
+ 35316562626335306534356162623030613566306235623863373036626531346364626166656134
+ 3063376436656635330a363636376131663362633731313964353061663661376638326461393736
+ 3863
+ vaulted_to_id: "{{data2|vault('test1@secret', vault_id='test1')}}"
tasks:
- name: check vaulting
@@ -35,3 +45,5 @@
that:
- vaultedstring|unvault(password) == data
- vault|unvault(password) == data
+ - vaulted_id|unvault('test1@secret', vault_id='test1')
+ - vaulted_to_id|unvault('test1@secret', vault_id='test1')
diff --git a/test/integration/targets/filter_mathstuff/tasks/main.yml b/test/integration/targets/filter_mathstuff/tasks/main.yml
index 019f00e4..33fcae82 100644
--- a/test/integration/targets/filter_mathstuff/tasks/main.yml
+++ b/test/integration/targets/filter_mathstuff/tasks/main.yml
@@ -64,44 +64,44 @@
that:
- '[1,2,3]|intersect([4,5,6]) == []'
- '[1,2,3]|intersect([3,4,5,6]) == [3]'
- - '[1,2,3]|intersect([3,2,1]) == [1,2,3]'
- - '(1,2,3)|intersect((4,5,6))|list == []'
- - '(1,2,3)|intersect((3,4,5,6))|list == [3]'
+ - '[1,2,3]|intersect([3,2,1]) | sort == [1,2,3]'
+ - '(1,2,3)|intersect((4,5,6)) == []'
+ - '(1,2,3)|intersect((3,4,5,6)) == [3]'
- '["a","A","b"]|intersect(["B","c","C"]) == []'
- '["a","A","b"]|intersect(["b","B","c","C"]) == ["b"]'
- - '["a","A","b"]|intersect(["b","A","a"]) == ["a","A","b"]'
- - '("a","A","b")|intersect(("B","c","C"))|list == []'
- - '("a","A","b")|intersect(("b","B","c","C"))|list == ["b"]'
+ - '["a","A","b"]|intersect(["b","A","a"]) | sort(case_sensitive=True) == ["A","a","b"]'
+ - '("a","A","b")|intersect(("B","c","C")) == []'
+ - '("a","A","b")|intersect(("b","B","c","C")) == ["b"]'
- name: Verify difference
tags: difference
assert:
that:
- - '[1,2,3]|difference([4,5,6]) == [1,2,3]'
- - '[1,2,3]|difference([3,4,5,6]) == [1,2]'
+ - '[1,2,3]|difference([4,5,6]) | sort == [1,2,3]'
+ - '[1,2,3]|difference([3,4,5,6]) | sort == [1,2]'
- '[1,2,3]|difference([3,2,1]) == []'
- - '(1,2,3)|difference((4,5,6))|list == [1,2,3]'
- - '(1,2,3)|difference((3,4,5,6))|list == [1,2]'
- - '["a","A","b"]|difference(["B","c","C"]) == ["a","A","b"]'
- - '["a","A","b"]|difference(["b","B","c","C"]) == ["a","A"]'
+ - '(1,2,3)|difference((4,5,6)) | sort == [1,2,3]'
+ - '(1,2,3)|difference((3,4,5,6)) | sort == [1,2]'
+ - '["a","A","b"]|difference(["B","c","C"]) | sort(case_sensitive=True) == ["A","a","b"]'
+ - '["a","A","b"]|difference(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","a"]'
- '["a","A","b"]|difference(["b","A","a"]) == []'
- - '("a","A","b")|difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","a","b"]'
- - '("a","A","b")|difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","a"]'
+ - '("a","A","b")|difference(("B","c","C")) | sort(case_sensitive=True) == ["A","a","b"]'
+ - '("a","A","b")|difference(("b","B","c","C")) | sort(case_sensitive=True) == ["A","a"]'
- name: Verify symmetric_difference
tags: symmetric_difference
assert:
that:
- - '[1,2,3]|symmetric_difference([4,5,6]) == [1,2,3,4,5,6]'
- - '[1,2,3]|symmetric_difference([3,4,5,6]) == [1,2,4,5,6]'
+ - '[1,2,3]|symmetric_difference([4,5,6]) | sort == [1,2,3,4,5,6]'
+ - '[1,2,3]|symmetric_difference([3,4,5,6]) | sort == [1,2,4,5,6]'
- '[1,2,3]|symmetric_difference([3,2,1]) == []'
- - '(1,2,3)|symmetric_difference((4,5,6))|list == [1,2,3,4,5,6]'
- - '(1,2,3)|symmetric_difference((3,4,5,6))|list == [1,2,4,5,6]'
- - '["a","A","b"]|symmetric_difference(["B","c","C"]) == ["a","A","b","B","c","C"]'
- - '["a","A","b"]|symmetric_difference(["b","B","c","C"]) == ["a","A","B","c","C"]'
+ - '(1,2,3)|symmetric_difference((4,5,6)) | sort == [1,2,3,4,5,6]'
+ - '(1,2,3)|symmetric_difference((3,4,5,6)) | sort == [1,2,4,5,6]'
+ - '["a","A","b"]|symmetric_difference(["B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '["a","A","b"]|symmetric_difference(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","c"]'
- '["a","A","b"]|symmetric_difference(["b","A","a"]) == []'
- - '("a","A","b")|symmetric_difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
- - '("a","A","b")|symmetric_difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","c"]'
+ - '("a","A","b")|symmetric_difference(("B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '("a","A","b")|symmetric_difference(("b","B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","c"]'
- name: Verify union
tags: union
@@ -112,11 +112,11 @@
- '[1,2,3]|union([3,2,1]) == [1,2,3]'
- '(1,2,3)|union((4,5,6))|list == [1,2,3,4,5,6]'
- '(1,2,3)|union((3,4,5,6))|list == [1,2,3,4,5,6]'
- - '["a","A","b"]|union(["B","c","C"]) == ["a","A","b","B","c","C"]'
- - '["a","A","b"]|union(["b","B","c","C"]) == ["a","A","b","B","c","C"]'
- - '["a","A","b"]|union(["b","A","a"]) == ["a","A","b"]'
- - '("a","A","b")|union(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
- - '("a","A","b")|union(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '["a","A","b"]|union(["B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '["a","A","b"]|union(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '["a","A","b"]|union(["b","A","a"]) | sort(case_sensitive=True) == ["A","a","b"]'
+ - '("a","A","b")|union(("B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
+ - '("a","A","b")|union(("b","B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]'
- name: Verify min
tags: min
diff --git a/test/integration/targets/find/tasks/main.yml b/test/integration/targets/find/tasks/main.yml
index 89c62b9b..9c4a960f 100644
--- a/test/integration/targets/find/tasks/main.yml
+++ b/test/integration/targets/find/tasks/main.yml
@@ -374,3 +374,6 @@
- 'remote_tmp_dir_test ~ "/astest/old.txt" in astest_list'
- 'remote_tmp_dir_test ~ "/astest/.hidden.txt" in astest_list'
- '"checksum" in result.files[0]'
+
+- name: Run mode tests
+ import_tasks: mode.yml
diff --git a/test/integration/targets/fork_safe_stdio/aliases b/test/integration/targets/fork_safe_stdio/aliases
index e968db72..7761837e 100644
--- a/test/integration/targets/fork_safe_stdio/aliases
+++ b/test/integration/targets/fork_safe_stdio/aliases
@@ -1,3 +1,3 @@
shippable/posix/group3
context/controller
-skip/macos
+needs/target/test_utils
diff --git a/test/integration/targets/fork_safe_stdio/runme.sh b/test/integration/targets/fork_safe_stdio/runme.sh
index 4438c3fe..863582f3 100755
--- a/test/integration/targets/fork_safe_stdio/runme.sh
+++ b/test/integration/targets/fork_safe_stdio/runme.sh
@@ -7,7 +7,7 @@ echo "testing for stdio deadlock on forked workers (10s timeout)..."
# Enable a callback that trips deadlocks on forked-child stdout, time out after 10s; forces running
# in a pty, since that tends to be much slower than raw file I/O and thus more likely to trigger the deadlock.
# Redirect stdout to /dev/null since it's full of non-printable garbage we don't want to display unless it failed
-ANSIBLE_CALLBACKS_ENABLED=spewstdio SPEWSTDIO_ENABLED=1 python run-with-pty.py timeout 10s ansible-playbook -i hosts -f 5 test.yml > stdout.txt && RC=$? || RC=$?
+ANSIBLE_CALLBACKS_ENABLED=spewstdio SPEWSTDIO_ENABLED=1 python run-with-pty.py ../test_utils/scripts/timeout.py -- 10 ansible-playbook -i hosts -f 5 test.yml > stdout.txt && RC=$? || RC=$?
if [ $RC != 0 ]; then
echo "failed; likely stdout deadlock. dumping raw output (may be very large)"
diff --git a/test/integration/targets/gathering_facts/library/file_utils.py b/test/integration/targets/gathering_facts/library/file_utils.py
index 58538029..38fa9265 100644
--- a/test/integration/targets/gathering_facts/library/file_utils.py
+++ b/test/integration/targets/gathering_facts/library/file_utils.py
@@ -1,9 +1,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-import sys
-
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.facts.utils import (
get_file_content,
diff --git a/test/integration/targets/gathering_facts/runme.sh b/test/integration/targets/gathering_facts/runme.sh
index c1df560c..a90de0f0 100755
--- a/test/integration/targets/gathering_facts/runme.sh
+++ b/test/integration/targets/gathering_facts/runme.sh
@@ -25,3 +25,17 @@ ansible-playbook test_module_defaults.yml "$@" --tags default_fact_module
ANSIBLE_FACTS_MODULES='ansible.legacy.setup' ansible-playbook test_module_defaults.yml "$@" --tags custom_fact_module
ansible-playbook test_module_defaults.yml "$@" --tags networking
+
+# test it works by default
+ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ "$@"
+
+# test that gather_facts will timeout parallel modules that dont support gather_timeout when using gather_Timeout
+ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=1 parallel=true' "$@" 2>&1 |grep 'Timeout exceeded'
+
+# test that gather_facts parallel w/o timing out
+ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=30 parallel=true' "$@" 2>&1 |grep -v 'Timeout exceeded'
+
+
+# test parallelism
+ANSIBLE_FACTS_MODULES='dummy1,dummy2,dummy3' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=30 parallel=true' "$@" 2>&1
+rm "${OUTPUT_DIR}/canary.txt"
diff --git a/test/integration/targets/get_url/tasks/main.yml b/test/integration/targets/get_url/tasks/main.yml
index 09814c70..c26cc08b 100644
--- a/test/integration/targets/get_url/tasks/main.yml
+++ b/test/integration/targets/get_url/tasks/main.yml
@@ -398,6 +398,8 @@
port: '{{ http_port }}'
state: started
+- include_tasks: hashlib.yml
+
- name: download src with sha1 checksum url in check mode
get_url:
url: 'http://localhost:{{ http_port }}/27617.txt'
diff --git a/test/integration/targets/get_url/tasks/use_netrc.yml b/test/integration/targets/get_url/tasks/use_netrc.yml
index e1852a81..234c904a 100644
--- a/test/integration/targets/get_url/tasks/use_netrc.yml
+++ b/test/integration/targets/get_url/tasks/use_netrc.yml
@@ -22,7 +22,7 @@
register: response_failed
- name: Parse token from msg.txt
- set_fact:
+ set_fact:
token: "{{ (response_failed['content'] | b64decode | from_json).token }}"
- name: assert Test Bearer authorization is failed with netrc
@@ -48,7 +48,7 @@
register: response
- name: Parse token from msg.txt
- set_fact:
+ set_fact:
token: "{{ (response['content'] | b64decode | from_json).token }}"
- name: assert Test Bearer authorization is successfull with use_netrc=False
@@ -64,4 +64,4 @@
state: absent
with_items:
- "{{ remote_tmp_dir }}/netrc"
- - "{{ remote_tmp_dir }}/msg.txt" \ No newline at end of file
+ - "{{ remote_tmp_dir }}/msg.txt"
diff --git a/test/integration/targets/git/tasks/depth.yml b/test/integration/targets/git/tasks/depth.yml
index e0585ca3..20f1b4e9 100644
--- a/test/integration/targets/git/tasks/depth.yml
+++ b/test/integration/targets/git/tasks/depth.yml
@@ -95,14 +95,16 @@
repo: 'file://{{ repo_dir|expanduser }}/shallow'
dest: '{{ checkout_dir }}'
depth: 1
- version: master
+ version: >-
+ {{ git_default_branch }}
- name: DEPTH | run a second time (now fetch, not clone)
git:
repo: 'file://{{ repo_dir|expanduser }}/shallow'
dest: '{{ checkout_dir }}'
depth: 1
- version: master
+ version: >-
+ {{ git_default_branch }}
register: git_fetch
- name: DEPTH | ensure the fetch succeeded
@@ -120,7 +122,8 @@
repo: 'file://{{ repo_dir|expanduser }}/shallow'
dest: '{{ checkout_dir }}'
depth: 1
- version: master
+ version: >-
+ {{ git_default_branch }}
- name: DEPTH | switch to older branch with depth=1 (uses fetch)
git:
diff --git a/test/integration/targets/git/tasks/forcefully-fetch-tag.yml b/test/integration/targets/git/tasks/forcefully-fetch-tag.yml
index 47c37478..db35e048 100644
--- a/test/integration/targets/git/tasks/forcefully-fetch-tag.yml
+++ b/test/integration/targets/git/tasks/forcefully-fetch-tag.yml
@@ -11,7 +11,7 @@
git add leet;
git commit -m uh-oh;
git tag -f herewego;
- git push --tags origin master
+ git push --tags origin '{{ git_default_branch }}'
args:
chdir: "{{ repo_dir }}/tag_force_push_clone1"
@@ -26,7 +26,7 @@
git add leet;
git commit -m uh-oh;
git tag -f herewego;
- git push -f --tags origin master
+ git push -f --tags origin '{{ git_default_branch }}'
args:
chdir: "{{ repo_dir }}/tag_force_push_clone1"
diff --git a/test/integration/targets/git/tasks/gpg-verification.yml b/test/integration/targets/git/tasks/gpg-verification.yml
index 8c8834a9..bd57ed1d 100644
--- a/test/integration/targets/git/tasks/gpg-verification.yml
+++ b/test/integration/targets/git/tasks/gpg-verification.yml
@@ -37,8 +37,10 @@
environment:
- GNUPGHOME: "{{ git_gpg_gpghome }}"
shell: |
- set -e
+ set -eEu
+
git init
+
touch an_empty_file
git add an_empty_file
git commit --no-gpg-sign --message "Commit, and don't sign"
@@ -48,11 +50,11 @@
git tag --annotate --message "This is not a signed tag" unsigned_annotated_tag HEAD
git commit --allow-empty --gpg-sign --message "Commit, and sign"
git tag --sign --message "This is a signed tag" signed_annotated_tag HEAD
- git checkout -b some_branch/signed_tip master
+ git checkout -b some_branch/signed_tip '{{ git_default_branch }}'
git commit --allow-empty --gpg-sign --message "Commit, and sign"
- git checkout -b another_branch/unsigned_tip master
+ git checkout -b another_branch/unsigned_tip '{{ git_default_branch }}'
git commit --allow-empty --no-gpg-sign --message "Commit, and don't sign"
- git checkout master
+ git checkout '{{ git_default_branch }}'
args:
chdir: "{{ git_gpg_source }}"
diff --git a/test/integration/targets/git/tasks/localmods.yml b/test/integration/targets/git/tasks/localmods.yml
index 0e0cf684..409bbae2 100644
--- a/test/integration/targets/git/tasks/localmods.yml
+++ b/test/integration/targets/git/tasks/localmods.yml
@@ -1,6 +1,17 @@
# test for https://github.com/ansible/ansible-modules-core/pull/5505
- name: LOCALMODS | prepare old git repo
- shell: rm -rf localmods; mkdir localmods; cd localmods; git init; echo "1" > a; git add a; git commit -m "1"
+ shell: |
+ set -eEu
+
+ rm -rf localmods
+ mkdir localmods
+ cd localmods
+
+ git init
+
+ echo "1" > a
+ git add a
+ git commit -m "1"
args:
chdir: "{{repo_dir}}"
@@ -55,7 +66,18 @@
# localmods and shallow clone
- name: LOCALMODS | prepare old git repo
- shell: rm -rf localmods; mkdir localmods; cd localmods; git init; echo "1" > a; git add a; git commit -m "1"
+ shell: |
+ set -eEu
+
+ rm -rf localmods
+ mkdir localmods
+ cd localmods
+
+ git init
+
+ echo "1" > a
+ git add a
+ git commit -m "1"
args:
chdir: "{{repo_dir}}"
diff --git a/test/integration/targets/git/tasks/main.yml b/test/integration/targets/git/tasks/main.yml
index ed06eab5..c990251f 100644
--- a/test/integration/targets/git/tasks/main.yml
+++ b/test/integration/targets/git/tasks/main.yml
@@ -16,27 +16,37 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-- import_tasks: setup.yml
-- import_tasks: setup-local-repos.yml
+# NOTE: Moving `$HOME` to tmp dir allows this integration test be
+# NOTE: non-destructive. There is no other way to instruct Git use a custom
+# NOTE: config path. There are new `$GIT_CONFIG_KEY_{COUNT,KEY,VALUE}` vars
+# NOTE: for setting specific configuration values but those are only available
+# NOTE: since Git v2.31 which is why we cannot rely on them yet.
-- import_tasks: formats.yml
-- import_tasks: missing_hostkey.yml
-- import_tasks: missing_hostkey_acceptnew.yml
-- import_tasks: no-destination.yml
-- import_tasks: specific-revision.yml
-- import_tasks: submodules.yml
-- import_tasks: change-repo-url.yml
-- import_tasks: depth.yml
-- import_tasks: single-branch.yml
-- import_tasks: checkout-new-tag.yml
-- include_tasks: gpg-verification.yml
- when:
+- block:
+ - import_tasks: setup.yml
+ - import_tasks: setup-local-repos.yml
+
+ - import_tasks: formats.yml
+ - import_tasks: missing_hostkey.yml
+ - import_tasks: missing_hostkey_acceptnew.yml
+ - import_tasks: no-destination.yml
+ - import_tasks: specific-revision.yml
+ - import_tasks: submodules.yml
+ - import_tasks: change-repo-url.yml
+ - import_tasks: depth.yml
+ - import_tasks: single-branch.yml
+ - import_tasks: checkout-new-tag.yml
+ - include_tasks: gpg-verification.yml
+ when:
- not gpg_version.stderr
- gpg_version.stdout
- not (ansible_os_family == 'RedHat' and ansible_distribution_major_version is version('7', '<'))
-- import_tasks: localmods.yml
-- import_tasks: reset-origin.yml
-- import_tasks: ambiguous-ref.yml
-- import_tasks: archive.yml
-- import_tasks: separate-git-dir.yml
-- import_tasks: forcefully-fetch-tag.yml
+ - import_tasks: localmods.yml
+ - import_tasks: reset-origin.yml
+ - import_tasks: ambiguous-ref.yml
+ - import_tasks: archive.yml
+ - import_tasks: separate-git-dir.yml
+ - import_tasks: forcefully-fetch-tag.yml
+ environment:
+ HOME: >-
+ {{ remote_tmp_dir }}
diff --git a/test/integration/targets/git/tasks/missing_hostkey.yml b/test/integration/targets/git/tasks/missing_hostkey.yml
index 136c5d5d..d8a2a818 100644
--- a/test/integration/targets/git/tasks/missing_hostkey.yml
+++ b/test/integration/targets/git/tasks/missing_hostkey.yml
@@ -35,7 +35,8 @@
git:
repo: '{{ repo_format3 }}'
dest: '{{ checkout_dir }}'
- version: 'master'
+ version: >-
+ {{ git_default_branch }}
accept_hostkey: false # should already have been accepted
key_file: '{{ github_ssh_private_key }}'
ssh_opts: '-o UserKnownHostsFile={{ remote_tmp_dir }}/known_hosts'
diff --git a/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml b/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml
index 3fd19067..338ae081 100644
--- a/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml
+++ b/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml
@@ -55,7 +55,8 @@
git:
repo: '{{ repo_format3 }}'
dest: '{{ checkout_dir }}'
- version: 'master'
+ version: >-
+ {{ git_default_branch }}
accept_newhostkey: false # should already have been accepted
key_file: '{{ github_ssh_private_key }}'
ssh_opts: '-o UserKnownHostsFile={{ remote_tmp_dir }}/known_hosts'
diff --git a/test/integration/targets/git/tasks/reset-origin.yml b/test/integration/targets/git/tasks/reset-origin.yml
index 8fddd4b1..cb497c44 100644
--- a/test/integration/targets/git/tasks/reset-origin.yml
+++ b/test/integration/targets/git/tasks/reset-origin.yml
@@ -12,7 +12,14 @@
state: directory
- name: RESET-ORIGIN | Initialise the repo with a file named origin,see github.com/ansible/ansible/pull/22502
- shell: git init; echo "PR 22502" > origin; git add origin; git commit -m "PR 22502"
+ shell: |
+ set -eEu
+
+ git init
+
+ echo "PR 22502" > origin
+ git add origin
+ git commit -m "PR 22502"
args:
chdir: "{{ repo_dir }}/origin"
diff --git a/test/integration/targets/git/tasks/setup-local-repos.yml b/test/integration/targets/git/tasks/setup-local-repos.yml
index 584a1693..4626f102 100644
--- a/test/integration/targets/git/tasks/setup-local-repos.yml
+++ b/test/integration/targets/git/tasks/setup-local-repos.yml
@@ -9,15 +9,32 @@
- "{{ repo_dir }}/tag_force_push"
- name: SETUP-LOCAL-REPOS | prepare minimal git repo
- shell: git init; echo "1" > a; git add a; git commit -m "1"
+ shell: |
+ set -eEu
+
+ git init
+
+ echo "1" > a
+ git add a
+ git commit -m "1"
args:
chdir: "{{ repo_dir }}/minimal"
- name: SETUP-LOCAL-REPOS | prepare git repo for shallow clone
shell: |
- git init;
- echo "1" > a; git add a; git commit -m "1"; git tag earlytag; git branch earlybranch;
- echo "2" > a; git add a; git commit -m "2";
+ set -eEu
+
+ git init
+
+ echo "1" > a
+ git add a
+ git commit -m "1"
+ git tag earlytag
+ git branch earlybranch
+
+ echo "2" > a
+ git add a
+ git commit -m "2"
args:
chdir: "{{ repo_dir }}/shallow"
@@ -29,7 +46,10 @@
- name: SETUP-LOCAL-REPOS | prepare tmp git repo with two branches
shell: |
+ set -eEu
+
git init
+
echo "1" > a; git add a; git commit -m "1"
git checkout -b test_branch; echo "2" > a; git commit -m "2 on branch" a
git checkout -b new_branch; echo "3" > a; git commit -m "3 on new branch" a
@@ -40,6 +60,9 @@
# We make the repo here for consistency with the other repos,
# but we finish setting it up in forcefully-fetch-tag.yml.
- name: SETUP-LOCAL-REPOS | prepare tag_force_push git repo
- shell: git init --bare
+ shell: |
+ set -eEu
+
+ git init --bare
args:
chdir: "{{ repo_dir }}/tag_force_push"
diff --git a/test/integration/targets/git/tasks/setup.yml b/test/integration/targets/git/tasks/setup.yml
index 06511053..982c03ff 100644
--- a/test/integration/targets/git/tasks/setup.yml
+++ b/test/integration/targets/git/tasks/setup.yml
@@ -28,10 +28,44 @@
register: gpg_version
- name: SETUP | set git global user.email if not already set
- shell: git config --global user.email || git config --global user.email "noreply@example.com"
+ shell: git config --global user.email 'noreply@example.com'
- name: SETUP | set git global user.name if not already set
- shell: git config --global user.name || git config --global user.name "Ansible Test Runner"
+ shell: git config --global user.name 'Ansible Test Runner'
+
+- name: SETUP | set git global init.defaultBranch
+ shell: >-
+ git config --global init.defaultBranch '{{ git_default_branch }}'
+
+- name: SETUP | set git global init.templateDir
+ # NOTE: This custom Git repository template emulates the `init.defaultBranch`
+ # NOTE: setting on Git versions below 2.28.
+ # NOTE: Ref: https://superuser.com/a/1559582.
+ # NOTE: Other workarounds mentioned there, like invoking
+ # NOTE: `git symbolic-ref HEAD refs/heads/main` after each `git init` turned
+ # NOTE: out to have mysterious side effects that break the tests in surprising
+ # NOTE: ways.
+ shell: |
+ set -eEu
+
+ git config --global \
+ init.templateDir '{{ remote_tmp_dir }}/git-templates/git.git'
+
+ mkdir -pv '{{ remote_tmp_dir }}/git-templates'
+ set +e
+ GIT_TEMPLATES_DIR=$(\
+ 2>/dev/null \
+ ls -1d \
+ '/Library/Developer/CommandLineTools/usr/share/git-core/templates' \
+ '/usr/local/share/git-core/templates' \
+ '/usr/share/git-core/templates' \
+ )
+ set -e
+ >&2 echo "Found Git's default templates directory: ${GIT_TEMPLATES_DIR}"
+ cp -r "${GIT_TEMPLATES_DIR}" '{{ remote_tmp_dir }}/git-templates/git.git'
+
+ echo 'ref: refs/heads/{{ git_default_branch }}' \
+ > '{{ remote_tmp_dir }}/git-templates/git.git/HEAD'
- name: SETUP | create repo_dir
file:
diff --git a/test/integration/targets/git/tasks/single-branch.yml b/test/integration/targets/git/tasks/single-branch.yml
index 5cfb4d5b..ca8457ac 100644
--- a/test/integration/targets/git/tasks/single-branch.yml
+++ b/test/integration/targets/git/tasks/single-branch.yml
@@ -52,7 +52,8 @@
repo: 'file://{{ repo_dir|expanduser }}/shallow_branches'
dest: '{{ checkout_dir }}'
single_branch: yes
- version: master
+ version: >-
+ {{ git_default_branch }}
register: single_branch_3
- name: SINGLE_BRANCH | Clone example git repo using single_branch with version again
@@ -60,7 +61,8 @@
repo: 'file://{{ repo_dir|expanduser }}/shallow_branches'
dest: '{{ checkout_dir }}'
single_branch: yes
- version: master
+ version: >-
+ {{ git_default_branch }}
register: single_branch_4
- name: SINGLE_BRANCH | List revisions
diff --git a/test/integration/targets/git/tasks/specific-revision.yml b/test/integration/targets/git/tasks/specific-revision.yml
index 26fa7cf3..f1fe41d5 100644
--- a/test/integration/targets/git/tasks/specific-revision.yml
+++ b/test/integration/targets/git/tasks/specific-revision.yml
@@ -162,7 +162,14 @@
path: "{{ checkout_dir }}"
- name: SPECIFIC-REVISION | prepare origina repo
- shell: git init; echo "1" > a; git add a; git commit -m "1"
+ shell: |
+ set -eEu
+
+ git init
+
+ echo "1" > a
+ git add a
+ git commit -m "1"
args:
chdir: "{{ checkout_dir }}"
@@ -191,7 +198,14 @@
force: yes
- name: SPECIFIC-REVISION | create new commit in original
- shell: git init; echo "2" > b; git add b; git commit -m "2"
+ shell: |
+ set -eEu
+
+ git init
+
+ echo "2" > b
+ git add b
+ git commit -m "2"
args:
chdir: "{{ checkout_dir }}"
diff --git a/test/integration/targets/git/vars/main.yml b/test/integration/targets/git/vars/main.yml
index b38531f3..55c7c438 100644
--- a/test/integration/targets/git/vars/main.yml
+++ b/test/integration/targets/git/vars/main.yml
@@ -41,6 +41,7 @@ repo_update_url_2: 'https://github.com/ansible-test-robinro/git-test-new'
known_host_files:
- "{{ lookup('env','HOME') }}/.ssh/known_hosts"
- '/etc/ssh/ssh_known_hosts'
+git_default_branch: main
git_version_supporting_depth: 1.9.1
git_version_supporting_ls_remote: 1.7.5
git_version_supporting_single_branch: 1.7.10
diff --git a/test/integration/targets/group/tasks/main.yml b/test/integration/targets/group/tasks/main.yml
index eb8126dd..21235240 100644
--- a/test/integration/targets/group/tasks/main.yml
+++ b/test/integration/targets/group/tasks/main.yml
@@ -16,25 +16,4 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-- name: ensure test groups are deleted before the test
- group:
- name: '{{ item }}'
- state: absent
- loop:
- - ansibullgroup
- - ansibullgroup2
- - ansibullgroup3
-
-- block:
- - name: run tests
- include_tasks: tests.yml
-
- always:
- - name: remove test groups after test
- group:
- name: '{{ item }}'
- state: absent
- loop:
- - ansibullgroup
- - ansibullgroup2
- - ansibullgroup3 \ No newline at end of file
+- import_tasks: tests.yml
diff --git a/test/integration/targets/group/tasks/tests.yml b/test/integration/targets/group/tasks/tests.yml
index f9a81220..eb92cd1d 100644
--- a/test/integration/targets/group/tasks/tests.yml
+++ b/test/integration/targets/group/tasks/tests.yml
@@ -1,343 +1,412 @@
---
-##
-## group add
-##
-
-- name: create group (check mode)
- group:
- name: ansibullgroup
- state: present
- register: create_group_check
- check_mode: True
-
-- name: get result of create group (check mode)
- script: 'grouplist.sh "{{ ansible_distribution }}"'
- register: create_group_actual_check
-
-- name: assert create group (check mode)
- assert:
- that:
- - create_group_check is changed
- - '"ansibullgroup" not in create_group_actual_check.stdout_lines'
-
-- name: create group
- group:
- name: ansibullgroup
- state: present
- register: create_group
-
-- name: get result of create group
- script: 'grouplist.sh "{{ ansible_distribution }}"'
- register: create_group_actual
-
-- name: assert create group
- assert:
- that:
- - create_group is changed
- - create_group.gid is defined
- - '"ansibullgroup" in create_group_actual.stdout_lines'
-
-- name: create group (idempotent)
+- name: ensure test groups are deleted before the test
group:
- name: ansibullgroup
- state: present
- register: create_group_again
+ name: '{{ item }}'
+ state: absent
+ loop:
+ - ansibullgroup
+ - ansibullgroup2
+ - ansibullgroup3
-- name: assert create group (idempotent)
- assert:
- that:
- - not create_group_again is changed
+- block:
+ ##
+ ## group add
+ ##
-##
-## group check
-##
+ - name: create group (check mode)
+ group:
+ name: ansibullgroup
+ state: present
+ register: create_group_check
+ check_mode: true
-- name: run existing group check tests
- group:
- name: "{{ create_group_actual.stdout_lines|random }}"
- state: present
- with_sequence: start=1 end=5
- register: group_test1
-
-- name: validate results for testcase 1
- assert:
- that:
- - group_test1.results is defined
- - group_test1.results|length == 5
-
-- name: validate change results for testcase 1
- assert:
- that:
- - not group_test1 is changed
-
-##
-## group add with gid
-##
-
-- name: get the next available gid
- script: gidget.py
- args:
- executable: '{{ ansible_python_interpreter }}'
- register: gid
-
-- name: create a group with a gid (check mode)
- group:
- name: ansibullgroup2
- gid: '{{ gid.stdout_lines[0] }}'
- state: present
- register: create_group_gid_check
- check_mode: True
-
-- name: get result of create a group with a gid (check mode)
- script: 'grouplist.sh "{{ ansible_distribution }}"'
- register: create_group_gid_actual_check
-
-- name: assert create group with a gid (check mode)
- assert:
- that:
- - create_group_gid_check is changed
- - '"ansibullgroup2" not in create_group_gid_actual_check.stdout_lines'
-
-- name: create a group with a gid
- group:
- name: ansibullgroup2
- gid: '{{ gid.stdout_lines[0] }}'
- state: present
- register: create_group_gid
-
-- name: get gid of created group
- command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('ansibullgroup2').gr_gid)\""
- register: create_group_gid_actual
-
-- name: assert create group with a gid
- assert:
- that:
- - create_group_gid is changed
- - create_group_gid.gid | int == gid.stdout_lines[0] | int
- - create_group_gid_actual.stdout | trim | int == gid.stdout_lines[0] | int
-
-- name: create a group with a gid (idempotent)
- group:
- name: ansibullgroup2
- gid: '{{ gid.stdout_lines[0] }}'
- state: present
- register: create_group_gid_again
+ - name: get result of create group (check mode)
+ script: 'grouplist.sh "{{ ansible_distribution }}"'
+ register: create_group_actual_check
-- name: assert create group with a gid (idempotent)
- assert:
- that:
- - not create_group_gid_again is changed
- - create_group_gid_again.gid | int == gid.stdout_lines[0] | int
+ - name: assert create group (check mode)
+ assert:
+ that:
+ - create_group_check is changed
+ - '"ansibullgroup" not in create_group_actual_check.stdout_lines'
-- block:
- - name: create a group with a non-unique gid
+ - name: create group
group:
- name: ansibullgroup3
- gid: '{{ gid.stdout_lines[0] }}'
- non_unique: true
+ name: ansibullgroup
state: present
- register: create_group_gid_non_unique
+ register: create_group
- - name: validate gid required with non_unique
+ - name: get result of create group
+ script: 'grouplist.sh "{{ ansible_distribution }}"'
+ register: create_group_actual
+
+ - name: assert create group
+ assert:
+ that:
+ - create_group is changed
+ - create_group.gid is defined
+ - '"ansibullgroup" in create_group_actual.stdout_lines'
+
+ - name: create group (idempotent)
group:
- name: foo
- non_unique: true
- register: missing_gid
- ignore_errors: true
+ name: ansibullgroup
+ state: present
+ register: create_group_again
- - name: assert create group with a non unique gid
+ - name: assert create group (idempotent)
assert:
that:
- - create_group_gid_non_unique is changed
- - create_group_gid_non_unique.gid | int == gid.stdout_lines[0] | int
- - missing_gid is failed
- when: ansible_facts.distribution not in ['MacOSX', 'Alpine']
+ - not create_group_again is changed
-##
-## group remove
-##
+ ##
+ ## group check
+ ##
-- name: delete group (check mode)
- group:
- name: ansibullgroup
- state: absent
- register: delete_group_check
- check_mode: True
+ - name: run existing group check tests
+ group:
+ name: "{{ create_group_actual.stdout_lines|random }}"
+ state: present
+ with_sequence: start=1 end=5
+ register: group_test1
-- name: get result of delete group (check mode)
- script: grouplist.sh "{{ ansible_distribution }}"
- register: delete_group_actual_check
+ - name: validate results for testcase 1
+ assert:
+ that:
+ - group_test1.results is defined
+ - group_test1.results|length == 5
-- name: assert delete group (check mode)
- assert:
- that:
- - delete_group_check is changed
- - '"ansibullgroup" in delete_group_actual_check.stdout_lines'
+ - name: validate change results for testcase 1
+ assert:
+ that:
+ - not group_test1 is changed
-- name: delete group
- group:
- name: ansibullgroup
- state: absent
- register: delete_group
+ ##
+ ## group add with gid
+ ##
-- name: get result of delete group
- script: grouplist.sh "{{ ansible_distribution }}"
- register: delete_group_actual
+ - name: get the next available gid
+ script: get_free_gid.py
+ args:
+ executable: '{{ ansible_python_interpreter }}'
+ register: gid
-- name: assert delete group
- assert:
- that:
- - delete_group is changed
- - '"ansibullgroup" not in delete_group_actual.stdout_lines'
+ - name: create a group with a gid (check mode)
+ group:
+ name: ansibullgroup2
+ gid: '{{ gid.stdout_lines[0] }}'
+ state: present
+ register: create_group_gid_check
+ check_mode: true
-- name: delete group (idempotent)
- group:
- name: ansibullgroup
- state: absent
- register: delete_group_again
-
-- name: assert delete group (idempotent)
- assert:
- that:
- - not delete_group_again is changed
-
-- name: Ensure lgroupadd is present
- action: "{{ ansible_facts.pkg_mgr }}"
- args:
- name: libuser
- state: present
- when: ansible_facts.system in ['Linux'] and ansible_distribution != 'Alpine' and ansible_os_family != 'Suse'
- tags:
- - user_test_local_mode
-
-- name: Ensure lgroupadd is present - Alpine
- command: apk add -U libuser
- when: ansible_distribution == 'Alpine'
- tags:
- - user_test_local_mode
-
-# https://github.com/ansible/ansible/issues/56481
-- block:
- - name: Test duplicate GID with local=yes
- group:
- name: "{{ item }}"
- gid: 1337
- local: yes
- loop:
- - group1_local_test
- - group2_local_test
- ignore_errors: yes
- register: local_duplicate_gid_result
-
- - assert:
- that:
- - local_duplicate_gid_result['results'][0] is success
- - local_duplicate_gid_result['results'][1]['msg'] == "GID '1337' already exists with group 'group1_local_test'"
- always:
- - name: Cleanup
+ - name: get result of create a group with a gid (check mode)
+ script: 'grouplist.sh "{{ ansible_distribution }}"'
+ register: create_group_gid_actual_check
+
+ - name: assert create group with a gid (check mode)
+ assert:
+ that:
+ - create_group_gid_check is changed
+ - '"ansibullgroup2" not in create_group_gid_actual_check.stdout_lines'
+
+ - name: create a group with a gid
group:
- name: group1_local_test
- state: absent
- # only applicable to Linux, limit further to CentOS where 'luseradd' is installed
- when: ansible_distribution == 'CentOS'
+ name: ansibullgroup2
+ gid: '{{ gid.stdout_lines[0] }}'
+ state: present
+ register: create_group_gid
-# https://github.com/ansible/ansible/pull/59769
-- block:
- - name: create a local group with a gid
- group:
- name: group1_local_test
- gid: 1337
- local: yes
- state: present
- register: create_local_group_gid
-
- - name: get gid of created local group
- command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('group1_local_test').gr_gid)\""
- register: create_local_group_gid_actual
-
- - name: assert create local group with a gid
- assert:
+ - name: get gid of created group
+ script: "get_gid_for_group.py ansibullgroup2"
+ args:
+ executable: '{{ ansible_python_interpreter }}'
+ register: create_group_gid_actual
+
+ - name: assert create group with a gid
+ assert:
that:
- - create_local_group_gid is changed
- - create_local_group_gid.gid | int == 1337 | int
- - create_local_group_gid_actual.stdout | trim | int == 1337 | int
-
- - name: create a local group with a gid (idempotent)
- group:
- name: group1_local_test
- gid: 1337
- state: present
- register: create_local_group_gid_again
-
- - name: assert create local group with a gid (idempotent)
- assert:
+ - create_group_gid is changed
+ - create_group_gid.gid | int == gid.stdout_lines[0] | int
+ - create_group_gid_actual.stdout | trim | int == gid.stdout_lines[0] | int
+
+ - name: create a group with a gid (idempotent)
+ group:
+ name: ansibullgroup2
+ gid: '{{ gid.stdout_lines[0] }}'
+ state: present
+ register: create_group_gid_again
+
+ - name: assert create group with a gid (idempotent)
+ assert:
that:
- - not create_local_group_gid_again is changed
- - create_local_group_gid_again.gid | int == 1337 | int
- always:
- - name: Cleanup create local group with a gid
+ - not create_group_gid_again is changed
+ - create_group_gid_again.gid | int == gid.stdout_lines[0] | int
+
+ - block:
+ - name: create a group with a non-unique gid
+ group:
+ name: ansibullgroup3
+ gid: '{{ gid.stdout_lines[0] }}'
+ non_unique: true
+ state: present
+ register: create_group_gid_non_unique
+
+ - name: validate gid required with non_unique
+ group:
+ name: foo
+ non_unique: true
+ register: missing_gid
+ ignore_errors: true
+
+ - name: assert create group with a non unique gid
+ assert:
+ that:
+ - create_group_gid_non_unique is changed
+ - create_group_gid_non_unique.gid | int == gid.stdout_lines[0] | int
+ - missing_gid is failed
+ when: ansible_facts.distribution not in ['MacOSX', 'Alpine']
+
+ ##
+ ## group remove
+ ##
+
+ - name: delete group (check mode)
group:
- name: group1_local_test
+ name: ansibullgroup
state: absent
- # only applicable to Linux, limit further to CentOS where 'luseradd' is installed
- when: ansible_distribution == 'CentOS'
+ register: delete_group_check
+ check_mode: true
-# https://github.com/ansible/ansible/pull/59772
-- block:
- - name: create group with a gid
- group:
- name: group1_test
- gid: 1337
- local: no
- state: present
- register: create_group_gid
-
- - name: get gid of created group
- command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('group1_test').gr_gid)\""
- register: create_group_gid_actual
-
- - name: assert create group with a gid
- assert:
- that:
- - create_group_gid is changed
- - create_group_gid.gid | int == 1337 | int
- - create_group_gid_actual.stdout | trim | int == 1337 | int
-
- - name: create local group with the same gid
- group:
- name: group1_test
- gid: 1337
- local: yes
- state: present
- register: create_local_group_gid
-
- - name: assert create local group with a gid
- assert:
+ - name: get result of delete group (check mode)
+ script: 'grouplist.sh "{{ ansible_distribution }}"'
+ register: delete_group_actual_check
+
+ - name: assert delete group (check mode)
+ assert:
that:
- - create_local_group_gid.gid | int == 1337 | int
- always:
- - name: Cleanup create group with a gid
+ - delete_group_check is changed
+ - '"ansibullgroup" in delete_group_actual_check.stdout_lines'
+
+ - name: delete group
group:
- name: group1_test
- local: no
+ name: ansibullgroup
state: absent
- - name: Cleanup create local group with the same gid
+ register: delete_group
+
+ - name: get result of delete group
+ script: 'grouplist.sh "{{ ansible_distribution }}"'
+ register: delete_group_actual
+
+ - name: assert delete group
+ assert:
+ that:
+ - delete_group is changed
+ - '"ansibullgroup" not in delete_group_actual.stdout_lines'
+
+ - name: delete group (idempotent)
group:
- name: group1_test
- local: yes
+ name: ansibullgroup
state: absent
- # only applicable to Linux, limit further to CentOS where 'lgroupadd' is installed
- when: ansible_distribution == 'CentOS'
+ register: delete_group_again
-# create system group
+ - name: assert delete group (idempotent)
+ assert:
+ that:
+ - not delete_group_again is changed
-- name: remove group
- group:
- name: ansibullgroup
- state: absent
+ - name: Ensure lgroupadd is present
+ action: "{{ ansible_facts.pkg_mgr }}"
+ args:
+ name: libuser
+ state: present
+ when: ansible_facts.system in ['Linux'] and ansible_distribution != 'Alpine' and ansible_os_family != 'Suse'
+ tags:
+ - user_test_local_mode
+
+ - name: Ensure lgroupadd is present - Alpine
+ command: apk add -U libuser
+ when: ansible_distribution == 'Alpine'
+ tags:
+ - user_test_local_mode
+
+ # https://github.com/ansible/ansible/issues/56481
+ - block:
+ - name: Test duplicate GID with local=yes
+ group:
+ name: "{{ item }}"
+ gid: 1337
+ local: true
+ loop:
+ - group1_local_test
+ - group2_local_test
+ ignore_errors: true
+ register: local_duplicate_gid_result
+
+ - assert:
+ that:
+ - local_duplicate_gid_result['results'][0] is success
+ - local_duplicate_gid_result['results'][1]['msg'] == "GID '1337' already exists with group 'group1_local_test'"
+ always:
+ - name: Cleanup
+ group:
+ name: group1_local_test
+ state: absent
+ # only applicable to Linux, limit further to CentOS where 'luseradd' is installed
+ when: ansible_distribution == 'CentOS'
+
+ # https://github.com/ansible/ansible/pull/59769
+ - block:
+ - name: create a local group with a gid
+ group:
+ name: group1_local_test
+ gid: 1337
+ local: true
+ state: present
+ register: create_local_group_gid
+
+ - name: get gid of created local group
+ script: "get_gid_for_group.py group1_local_test"
+ args:
+ executable: '{{ ansible_python_interpreter }}'
+ register: create_local_group_gid_actual
+
+ - name: assert create local group with a gid
+ assert:
+ that:
+ - create_local_group_gid is changed
+ - create_local_group_gid.gid | int == 1337 | int
+ - create_local_group_gid_actual.stdout | trim | int == 1337 | int
+
+ - name: create a local group with a gid (idempotent)
+ group:
+ name: group1_local_test
+ gid: 1337
+ state: present
+ register: create_local_group_gid_again
+
+ - name: assert create local group with a gid (idempotent)
+ assert:
+ that:
+ - not create_local_group_gid_again is changed
+ - create_local_group_gid_again.gid | int == 1337 | int
+ always:
+ - name: Cleanup create local group with a gid
+ group:
+ name: group1_local_test
+ state: absent
+ # only applicable to Linux, limit further to CentOS where 'luseradd' is installed
+ when: ansible_distribution == 'CentOS'
+
+ # https://github.com/ansible/ansible/pull/59772
+ - block:
+ - name: create group with a gid
+ group:
+ name: group1_test
+ gid: 1337
+ local: false
+ state: present
+ register: create_group_gid
+
+ - name: get gid of created group
+ script: "get_gid_for_group.py group1_test"
+ args:
+ executable: '{{ ansible_python_interpreter }}'
+ register: create_group_gid_actual
+
+ - name: assert create group with a gid
+ assert:
+ that:
+ - create_group_gid is changed
+ - create_group_gid.gid | int == 1337 | int
+ - create_group_gid_actual.stdout | trim | int == 1337 | int
+
+ - name: create local group with the same gid
+ group:
+ name: group1_test
+ gid: 1337
+ local: true
+ state: present
+ register: create_local_group_gid
+
+ - name: assert create local group with a gid
+ assert:
+ that:
+ - create_local_group_gid.gid | int == 1337 | int
+ always:
+ - name: Cleanup create group with a gid
+ group:
+ name: group1_test
+ local: false
+ state: absent
+ - name: Cleanup create local group with the same gid
+ group:
+ name: group1_test
+ local: true
+ state: absent
+ # only applicable to Linux, limit further to CentOS where 'lgroupadd' is installed
+ when: ansible_distribution == 'CentOS'
+
+ # https://github.com/ansible/ansible/pull/78172
+ - block:
+ - name: Create a group
+ group:
+ name: groupdeltest
+ state: present
+
+ - name: Create user with primary group of groupdeltest
+ user:
+ name: groupdeluser
+ group: groupdeltest
+ state: present
+
+ - name: Show we can't delete the group usually
+ group:
+ name: groupdeltest
+ state: absent
+ ignore_errors: true
+ register: failed_delete
+
+ - name: assert we couldn't delete the group
+ assert:
+ that:
+ - failed_delete is failed
+
+ - name: force delete the group
+ group:
+ name: groupdeltest
+ force: true
+ state: absent
+
+ always:
+ - name: Cleanup user
+ user:
+ name: groupdeluser
+ state: absent
+
+ - name: Cleanup group
+ group:
+ name: groupdeltest
+ state: absent
+ when: ansible_distribution not in ["MacOSX", "Alpine", "FreeBSD"]
+
+ # create system group
+
+ - name: remove group
+ group:
+ name: ansibullgroup
+ state: absent
-- name: create system group
- group:
- name: ansibullgroup
- state: present
- system: yes
+ - name: create system group
+ group:
+ name: ansibullgroup
+ state: present
+ system: true
+
+ always:
+ - name: remove test groups after test
+ group:
+ name: '{{ item }}'
+ state: absent
+ loop:
+ - ansibullgroup
+ - ansibullgroup2
+ - ansibullgroup3
diff --git a/test/integration/targets/handlers/runme.sh b/test/integration/targets/handlers/runme.sh
index 76fc99d8..368ca44d 100755
--- a/test/integration/targets/handlers/runme.sh
+++ b/test/integration/targets/handlers/runme.sh
@@ -50,6 +50,9 @@ for strategy in linear free; do
[ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_false_in_play --force-handlers \
| grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ]
+ # https://github.com/ansible/ansible/pull/80898
+ [ "$(ansible-playbook 80880.yml -i inventory.handlers -vv "$@" 2>&1)" ]
+
unset ANSIBLE_STRATEGY
done
@@ -66,6 +69,9 @@ done
# Notify handler listen
ansible-playbook test_handlers_listen.yml -i inventory.handlers -v "$@"
+# https://github.com/ansible/ansible/issues/82363
+ansible-playbook test_multiple_handlers_with_recursive_notification.yml -i inventory.handlers -v "$@"
+
# Notify inexistent handlers results in error
set +e
result="$(ansible-playbook test_handlers_inexistent_notify.yml -i inventory.handlers "$@" 2>&1)"
@@ -181,3 +187,24 @@ grep out.txt -e "ERROR! Using a block as a handler is not supported."
ansible-playbook test_block_as_handler-import.yml "$@" 2>&1 | tee out.txt
grep out.txt -e "ERROR! Using a block as a handler is not supported."
+
+ansible-playbook test_include_role_handler_once.yml -i inventory.handlers "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'handler ran')" = "1" ]
+
+ansible-playbook test_listen_role_dedup.yml "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'a handler from a role')" = "1" ]
+
+ansible localhost -m include_role -a "name=r1-dep_chain-vars" "$@"
+
+ansible-playbook test_include_tasks_in_include_role.yml "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'handler ran')" = "1" ]
+
+ansible-playbook test_run_once.yml -i inventory.handlers "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'handler ran once')" = "1" ]
+
+ansible-playbook 82241.yml -i inventory.handlers "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'included_task_from_tasks_dir')" = "1" ]
+
+ansible-playbook nested_flush_handlers_failure_force.yml -i inventory.handlers "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'flush_handlers_rescued')" = "1" ]
+[ "$(grep out.txt -ce 'flush_handlers_always')" = "2" ]
diff --git a/test/integration/targets/include_vars/tasks/main.yml b/test/integration/targets/include_vars/tasks/main.yml
index 6fc4e85a..97636d9d 100644
--- a/test/integration/targets/include_vars/tasks/main.yml
+++ b/test/integration/targets/include_vars/tasks/main.yml
@@ -208,6 +208,21 @@
- "config.key2.b == 22"
- "config.key3 == 3"
+- name: Include a vars dir with hash variables
+ include_vars:
+ dir: "{{ role_path }}/vars2/hashes/"
+ hash_behaviour: merge
+
+- name: Verify that the hash is merged after vars files are accumulated
+ assert:
+ that:
+ - "config | length == 3"
+ - "config.key0 is undefined"
+ - "config.key1 == 1"
+ - "config.key2 | length == 1"
+ - "config.key2.b == 22"
+ - "config.key3 == 3"
+
- include_vars:
file: no_auto_unsafe.yml
register: baz
@@ -215,3 +230,40 @@
- assert:
that:
- baz.ansible_facts.foo|type_debug != "AnsibleUnsafeText"
+
+- name: setup test following symlinks
+ delegate_to: localhost
+ block:
+ - name: create directory to test following symlinks
+ file:
+ path: "{{ role_path }}/test_symlink"
+ state: directory
+
+ - name: create symlink to the vars2 dir
+ file:
+ src: "{{ role_path }}/vars2"
+ dest: "{{ role_path }}/test_symlink/symlink"
+ state: link
+
+- name: include vars by following the symlink
+ include_vars:
+ dir: "{{ role_path }}/test_symlink"
+ register: follow_sym
+
+- assert:
+ that: follow_sym.ansible_included_var_files | sort == [hash1, hash2]
+ vars:
+ hash1: "{{ role_path }}/test_symlink/symlink/hashes/hash1.yml"
+ hash2: "{{ role_path }}/test_symlink/symlink/hashes/hash2.yml"
+
+- name: Test include_vars includes everything to the correct depth
+ ansible.builtin.include_vars:
+ dir: "{{ role_path }}/files/test_depth"
+ depth: 3
+ name: test_depth_var
+ register: test_depth
+
+- assert:
+ that:
+ - "test_depth.ansible_included_var_files|length == 8"
+ - "test_depth_var.keys()|length == 8"
diff --git a/test/integration/targets/include_vars/vars/services/service_vars.yml b/test/integration/targets/include_vars/vars/services/service_vars.yml
index 96b05d6c..bcac7646 100644
--- a/test/integration/targets/include_vars/vars/services/service_vars.yml
+++ b/test/integration/targets/include_vars/vars/services/service_vars.yml
@@ -1,2 +1,2 @@
---
-service_name: 'my_custom_service' \ No newline at end of file
+service_name: 'my_custom_service'
diff --git a/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml
index 2c04fee5..cd82eca5 100644
--- a/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml
+++ b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml
@@ -1,3 +1,3 @@
---
service_name_fqcn: 'my_custom_service'
-service_name_tmpl_fqcn: '{{ service_name_fqcn }}' \ No newline at end of file
+service_name_tmpl_fqcn: '{{ service_name_fqcn }}'
diff --git a/test/integration/targets/include_when_parent_is_dynamic/tasks.yml b/test/integration/targets/include_when_parent_is_dynamic/tasks.yml
index 6831245c..d500f0df 100644
--- a/test/integration/targets/include_when_parent_is_dynamic/tasks.yml
+++ b/test/integration/targets/include_when_parent_is_dynamic/tasks.yml
@@ -9,4 +9,4 @@
# perform an include task which should be static if all of the task's parents are static, otherwise it should be dynamic
# this file was loaded using include_tasks, which is dynamic, so this include should also be dynamic
-- include: syntax_error.yml
+- include_tasks: syntax_error.yml
diff --git a/test/integration/targets/include_when_parent_is_static/tasks.yml b/test/integration/targets/include_when_parent_is_static/tasks.yml
index a234a3dd..50dd2341 100644
--- a/test/integration/targets/include_when_parent_is_static/tasks.yml
+++ b/test/integration/targets/include_when_parent_is_static/tasks.yml
@@ -9,4 +9,4 @@
# perform an include task which should be static if all of the task's parents are static, otherwise it should be dynamic
# this file was loaded using import_tasks, which is static, so this include should also be static
-- include: syntax_error.yml
+- import_tasks: syntax_error.yml
diff --git a/test/integration/targets/includes/include_on_playbook_should_fail.yml b/test/integration/targets/includes/include_on_playbook_should_fail.yml
index 953459dc..c9b1e81a 100644
--- a/test/integration/targets/includes/include_on_playbook_should_fail.yml
+++ b/test/integration/targets/includes/include_on_playbook_should_fail.yml
@@ -1 +1 @@
-- include: test_includes3.yml
+- include_tasks: test_includes3.yml
diff --git a/test/integration/targets/includes/roles/test_includes/handlers/main.yml b/test/integration/targets/includes/roles/test_includes/handlers/main.yml
index 7d3e625f..453fa96d 100644
--- a/test/integration/targets/includes/roles/test_includes/handlers/main.yml
+++ b/test/integration/targets/includes/roles/test_includes/handlers/main.yml
@@ -1 +1 @@
-- include: more_handlers.yml
+- import_tasks: more_handlers.yml
diff --git a/test/integration/targets/includes/roles/test_includes/tasks/main.yml b/test/integration/targets/includes/roles/test_includes/tasks/main.yml
index 83ca468b..2ba1ae63 100644
--- a/test/integration/targets/includes/roles/test_includes/tasks/main.yml
+++ b/test/integration/targets/includes/roles/test_includes/tasks/main.yml
@@ -17,47 +17,9 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
-- include: included_task1.yml a=1 b=2 c=3
-
-- name: verify non-variable include params
- assert:
- that:
- - "ca == '1'"
- - "cb == '2'"
- - "cc == '3'"
-
-- set_fact:
- a: 101
- b: 102
- c: 103
-
-- include: included_task1.yml a={{a}} b={{b}} c=103
-
-- name: verify variable include params
- assert:
- that:
- - "ca == 101"
- - "cb == 102"
- - "cc == 103"
-
-# Test that strings are not turned into numbers
-- set_fact:
- a: "101"
- b: "102"
- c: "103"
-
-- include: included_task1.yml a={{a}} b={{b}} c=103
-
-- name: verify variable include params
- assert:
- that:
- - "ca == '101'"
- - "cb == '102'"
- - "cc == '103'"
-
# now try long form includes
-- include: included_task1.yml
+- include_tasks: included_task1.yml
vars:
a: 201
b: 202
diff --git a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml
index 5ae7882f..d7bcf8eb 100644
--- a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml
+++ b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml
@@ -1,9 +1,9 @@
- name: this needs to be here
debug:
msg: "hello"
-- include: inner.yml
+- include_tasks: inner.yml
with_items:
- '1'
-- ansible.builtin.include: inner_fqcn.yml
+- ansible.builtin.include_tasks: inner_fqcn.yml
with_items:
- '1'
diff --git a/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml b/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml
index 7bc19faa..c06d3feb 100644
--- a/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml
+++ b/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml
@@ -1,6 +1,6 @@
- name: this needs to be here
debug:
msg: "hello"
-- include: inner.yml
+- include_tasks: inner.yml
with_items:
- '1'
diff --git a/test/integration/targets/includes/runme.sh b/test/integration/targets/includes/runme.sh
index e619feaf..8622cf66 100755
--- a/test/integration/targets/includes/runme.sh
+++ b/test/integration/targets/includes/runme.sh
@@ -10,7 +10,7 @@ echo "EXPECTED ERROR: Ensure we fail if using 'include' to include a playbook."
set +e
result="$(ansible-playbook -i ../../inventory include_on_playbook_should_fail.yml -v "$@" 2>&1)"
set -e
-grep -q "ERROR! 'include' is not a valid attribute for a Play" <<< "$result"
+grep -q "ERROR! 'include_tasks' is not a valid attribute for a Play" <<< "$result"
ansible-playbook includes_loop_rescue.yml --extra-vars strategy=linear "$@"
ansible-playbook includes_loop_rescue.yml --extra-vars strategy=free "$@"
diff --git a/test/integration/targets/includes/test_includes2.yml b/test/integration/targets/includes/test_includes2.yml
index a32e8513..da6b914f 100644
--- a/test/integration/targets/includes/test_includes2.yml
+++ b/test/integration/targets/includes/test_includes2.yml
@@ -13,8 +13,8 @@
- role: test_includes
tags: test_includes
tasks:
- - include: roles/test_includes/tasks/not_a_role_task.yml
- - include: roles/test_includes/tasks/empty.yml
+ - include_tasks: roles/test_includes/tasks/not_a_role_task.yml
+ - include_tasks: roles/test_includes/tasks/empty.yml
- assert:
that:
- "ca == 33000"
diff --git a/test/integration/targets/includes/test_includes3.yml b/test/integration/targets/includes/test_includes3.yml
index 0b4c6312..f3c4964e 100644
--- a/test/integration/targets/includes/test_includes3.yml
+++ b/test/integration/targets/includes/test_includes3.yml
@@ -1,6 +1,6 @@
- hosts: testhost
tasks:
- - include: test_includes4.yml
+ - include_tasks: test_includes4.yml
with_items: ["a"]
loop_control:
loop_var: r
diff --git a/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py b/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py
index 7ca445a3..43cad4fc 100644
--- a/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py
+++ b/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py
@@ -14,7 +14,7 @@ DOCUMENTATION = '''
'''
from ansible.errors import AnsibleParserError
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
diff --git a/test/integration/targets/inventory_ini/inventory.ini b/test/integration/targets/inventory_ini/inventory.ini
index a0c99ade..a5de4211 100644
--- a/test/integration/targets/inventory_ini/inventory.ini
+++ b/test/integration/targets/inventory_ini/inventory.ini
@@ -1,3 +1,5 @@
+gitlab-runner-01 ansible_host=gitlab-runner-01.internal.example.net ansible_user=root
+
[local]
testhost ansible_connection=local ansible_become=no ansible_become_user=ansibletest1
diff --git a/test/integration/targets/inventory_ini/runme.sh b/test/integration/targets/inventory_ini/runme.sh
index 81bf1475..919e1884 100755
--- a/test/integration/targets/inventory_ini/runme.sh
+++ b/test/integration/targets/inventory_ini/runme.sh
@@ -3,3 +3,6 @@
set -eux
ansible-playbook -v -i inventory.ini test_ansible_become.yml
+
+ansible-inventory -v -i inventory.ini --list 2> out
+test "$(grep -c 'SyntaxWarning' out)" -eq 0
diff --git a/test/integration/targets/iptables/aliases b/test/integration/targets/iptables/aliases
index 7d66ecf8..73df8aad 100644
--- a/test/integration/targets/iptables/aliases
+++ b/test/integration/targets/iptables/aliases
@@ -1,5 +1,4 @@
shippable/posix/group2
skip/freebsd
-skip/osx
skip/macos
skip/docker
diff --git a/test/integration/targets/iptables/tasks/chain_management.yml b/test/integration/targets/iptables/tasks/chain_management.yml
index 03551228..dae4103a 100644
--- a/test/integration/targets/iptables/tasks/chain_management.yml
+++ b/test/integration/targets/iptables/tasks/chain_management.yml
@@ -45,6 +45,26 @@
- result is not failed
- '"FOOBAR-CHAIN" in result.stdout'
+- name: add rule to foobar chain
+ become: true
+ iptables:
+ chain: FOOBAR-CHAIN
+ source: 0.0.0.0
+ destination: 0.0.0.0
+ jump: DROP
+ comment: "FOOBAR-CHAIN RULE"
+
+- name: get the state of the iptable rules after rule is added to foobar chain
+ become: true
+ shell: "{{ iptables_bin }} -L"
+ register: result
+
+- name: assert rule is present in foobar chain
+ assert:
+ that:
+ - result is not failed
+ - '"FOOBAR-CHAIN RULE" in result.stdout'
+
- name: flush the foobar chain
become: true
iptables:
@@ -68,4 +88,3 @@
that:
- result is not failed
- '"FOOBAR-CHAIN" not in result.stdout'
- - '"FOOBAR-RULE" not in result.stdout'
diff --git a/test/integration/targets/known_hosts/defaults/main.yml b/test/integration/targets/known_hosts/defaults/main.yml
index b1b56ac7..cd438430 100644
--- a/test/integration/targets/known_hosts/defaults/main.yml
+++ b/test/integration/targets/known_hosts/defaults/main.yml
@@ -3,4 +3,4 @@ example_org_rsa_key: >
example.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAglyZmHHWskQ9wkh8LYbIqzvg99/oloneH7BaZ02ripJUy/2Zynv4tgUfm9fdXvAb1XXCEuTRnts9FBer87+voU0FPRgx3CfY9Sgr0FspUjnm4lqs53FIab1psddAaS7/F7lrnjl6VqBtPwMRQZG7qlml5uogGJwYJHxX0PGtsdoTJsM=
example_org_ed25519_key: >
- example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIzlnSq5ESxLgW0avvPk3j7zLV59hcAPkxrMNdnZMKP2 \ No newline at end of file
+ example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIzlnSq5ESxLgW0avvPk3j7zLV59hcAPkxrMNdnZMKP2
diff --git a/test/integration/targets/known_hosts/tasks/main.yml b/test/integration/targets/known_hosts/tasks/main.yml
index dc00dedd..d5ffec4d 100644
--- a/test/integration/targets/known_hosts/tasks/main.yml
+++ b/test/integration/targets/known_hosts/tasks/main.yml
@@ -99,7 +99,7 @@
# https://github.com/ansible/ansible/issues/78598
# test removing nonexistent host key when the other keys exist for the host
- name: remove different key
- known_hosts:
+ known_hosts:
name: example.org
key: "{{ example_org_ed25519_key }}"
state: absent
diff --git a/test/integration/targets/lookup_config/tasks/main.yml b/test/integration/targets/lookup_config/tasks/main.yml
index 356d2f80..e5699d34 100644
--- a/test/integration/targets/lookup_config/tasks/main.yml
+++ b/test/integration/targets/lookup_config/tasks/main.yml
@@ -42,6 +42,7 @@
- name: remote user and port for ssh connection
set_fact:
ssh_user_and_port: '{{q("config", "remote_user", "port", plugin_type="connection", plugin_name="ssh")}}'
+ ssh_user_and_port_and_origin: '{{q("config", "remote_user", "port", plugin_type="connection", plugin_name="ssh", show_origin=True)}}'
vars:
ansible_ssh_user: lola
ansible_ssh_port: 2022
@@ -71,4 +72,5 @@
- lookup_config_7 is failed
- '"Invalid setting" in lookup_config_7.msg'
- ssh_user_and_port == ['lola', 2022]
+ - "ssh_user_and_port_and_origin == [['lola', 'var: ansible_ssh_user'], [2022, 'var: ansible_ssh_port']]"
- yolo_remote == ["yolo"]
diff --git a/test/integration/targets/lookup_fileglob/issue72873/test.yml b/test/integration/targets/lookup_fileglob/issue72873/test.yml
index 218ee58d..92d93d45 100644
--- a/test/integration/targets/lookup_fileglob/issue72873/test.yml
+++ b/test/integration/targets/lookup_fileglob/issue72873/test.yml
@@ -5,7 +5,7 @@
dir: files
tasks:
- file: path='{{ dir }}' state=directory
-
+
- file: path='setvars.bat' state=touch # in current directory!
- file: path='{{ dir }}/{{ item }}' state=touch
@@ -20,11 +20,11 @@
- name: Get working order results and sort them
set_fact:
- working: '{{ query("fileglob", "setvars.bat", "{{ dir }}/*.[ch]") | sort }}'
+ working: '{{ query("fileglob", "setvars.bat", dir ~ "/*.[ch]") | sort }}'
- name: Get broken order results and sort them
set_fact:
- broken: '{{ query("fileglob", "{{ dir }}/*.[ch]", "setvars.bat") | sort }}'
+ broken: '{{ query("fileglob", dir ~ "/*.[ch]", "setvars.bat") | sort }}'
- assert:
that:
diff --git a/test/integration/targets/lookup_first_found/tasks/main.yml b/test/integration/targets/lookup_first_found/tasks/main.yml
index 9aeaf1d1..ba248bd5 100644
--- a/test/integration/targets/lookup_first_found/tasks/main.yml
+++ b/test/integration/targets/lookup_first_found/tasks/main.yml
@@ -94,3 +94,56 @@
- assert:
that:
- foo is defined
+
+# TODO: no 'terms' test
+- name: test first_found lookup with no terms
+ set_fact:
+ no_terms: "{{ query('first_found', files=['missing1', 'hosts', 'missing2'], paths=['/etc'], errors='ignore') }}"
+
+- assert:
+ that: "no_terms|first == '/etc/hosts'"
+
+- name: handle templatable dictionary entries
+ block:
+
+ - name: Load variables specific for OS family
+ assert:
+ that:
+ - "item is file"
+ - "item|basename == 'itworks.yml'"
+ with_first_found:
+ - files:
+ - "{{ansible_id}}-{{ansible_lsb.major_release}}.yml" # invalid var, should be skipped
+ - "{{ansible_lsb.id}}-{{ansible_lsb.major_release}}.yml" # does not exist, but should try
+ - "{{ansible_distribution}}-{{ansible_distribution_major_version}}.yml" # does not exist, but should try
+ - itworks.yml
+ - ishouldnotbefound.yml # this exist, but should not be found
+ paths:
+ - "{{role_path}}/vars"
+
+ - name: Load variables specific for OS family, but now as list of dicts, same options as above
+ assert:
+ that:
+ - "item is file"
+ - "item|basename == 'itworks.yml'"
+ with_first_found:
+ - files:
+ - "{{ansible_id}}-{{ansible_lsb.major_release}}.yml"
+ paths:
+ - "{{role_path}}/vars"
+ - files:
+ - "{{ansible_lsb.id}}-{{ansible_lsb.major_release}}.yml"
+ paths:
+ - "{{role_path}}/vars"
+ - files:
+ - "{{ansible_distribution}}-{{ansible_distribution_major_version}}.yml"
+ paths:
+ - "{{role_path}}/vars"
+ - files:
+ - itworks.yml
+ paths:
+ - "{{role_path}}/vars"
+ - files:
+ - ishouldnotbefound.yml
+ paths:
+ - "{{role_path}}/vars"
diff --git a/test/integration/targets/lookup_sequence/tasks/main.yml b/test/integration/targets/lookup_sequence/tasks/main.yml
index bd0a4d80..e64801d3 100644
--- a/test/integration/targets/lookup_sequence/tasks/main.yml
+++ b/test/integration/targets/lookup_sequence/tasks/main.yml
@@ -195,4 +195,4 @@
- ansible_failed_task.name == "EXPECTED FAILURE - test bad format string message"
- ansible_failed_result.msg == expected
vars:
- expected: "bad formatting string: d" \ No newline at end of file
+ expected: "bad formatting string: d"
diff --git a/test/integration/targets/lookup_together/tasks/main.yml b/test/integration/targets/lookup_together/tasks/main.yml
index 71365a15..115c9e52 100644
--- a/test/integration/targets/lookup_together/tasks/main.yml
+++ b/test/integration/targets/lookup_together/tasks/main.yml
@@ -26,4 +26,4 @@
- assert:
that:
- ansible_failed_task.name == "EXPECTED FAILURE - test empty list"
- - ansible_failed_result.msg == "with_together requires at least one element in each list" \ No newline at end of file
+ - ansible_failed_result.msg == "with_together requires at least one element in each list"
diff --git a/test/integration/targets/lookup_url/aliases b/test/integration/targets/lookup_url/aliases
index ef37fce1..19b7d98f 100644
--- a/test/integration/targets/lookup_url/aliases
+++ b/test/integration/targets/lookup_url/aliases
@@ -1,4 +1,11 @@
destructive
shippable/posix/group3
needs/httptester
-skip/macos/12.0 # This test crashes Python due to https://wefearchange.org/2018/11/forkmacos.rst.html
+skip/macos # This test crashes Python due to https://wefearchange.org/2018/11/forkmacos.rst.html
+# Example failure:
+#
+# TASK [lookup_url : Test that retrieving a url works] ***************************
+# objc[15394]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
+# objc[15394]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in t
+# he fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
+# ERROR! A worker was found in a dead state
diff --git a/test/integration/targets/lookup_url/meta/main.yml b/test/integration/targets/lookup_url/meta/main.yml
index 374b5fdf..6853708f 100644
--- a/test/integration/targets/lookup_url/meta/main.yml
+++ b/test/integration/targets/lookup_url/meta/main.yml
@@ -1,2 +1,2 @@
-dependencies:
+dependencies:
- prepare_http_tests
diff --git a/test/integration/targets/lookup_url/tasks/main.yml b/test/integration/targets/lookup_url/tasks/main.yml
index 2fb227ad..83fd5db6 100644
--- a/test/integration/targets/lookup_url/tasks/main.yml
+++ b/test/integration/targets/lookup_url/tasks/main.yml
@@ -1,6 +1,6 @@
- name: Test that retrieving a url works
set_fact:
- web_data: "{{ lookup('url', 'https://{{ httpbin_host }}/get?one') }}"
+ web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/get?one') }}"
- name: Assert that the url was retrieved
assert:
@@ -9,7 +9,7 @@
- name: Test that retrieving a url with invalid cert fails
set_fact:
- web_data: "{{ lookup('url', 'https://{{ badssl_host }}/') }}"
+ web_data: "{{ lookup('url', 'https://' ~ badssl_host ~ '/') }}"
ignore_errors: True
register: url_invalid_cert
@@ -20,12 +20,12 @@
- name: Test that retrieving a url with invalid cert with validate_certs=False works
set_fact:
- web_data: "{{ lookup('url', 'https://{{ badssl_host }}/', validate_certs=False) }}"
+ web_data: "{{ lookup('url', 'https://' ~ badssl_host ~ '/', validate_certs=False) }}"
register: url_no_validate_cert
- assert:
that:
- - "'{{ badssl_host_substring }}' in web_data"
+ - badssl_host_substring in web_data
- vars:
url: https://{{ httpbin_host }}/get
@@ -52,3 +52,27 @@
- name: Test use_netrc=False
import_tasks: use_netrc.yml
+
+- vars:
+ ansible_lookup_url_agent: ansible-test-lookup-url-agent
+ block:
+ - name: Test user agent
+ set_fact:
+ web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/user-agent') }}"
+
+ - name: Assert that user agent is set
+ assert:
+ that:
+ - ansible_lookup_url_agent in web_data['user-agent']
+
+- vars:
+ ansible_lookup_url_force_basic_auth: yes
+ block:
+ - name: Test force basic auth
+ set_fact:
+ web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/headers', username='abc') }}"
+
+ - name: Assert that Authorization header is set
+ assert:
+ that:
+ - "'Authorization' in web_data.headers"
diff --git a/test/integration/targets/lookup_url/tasks/use_netrc.yml b/test/integration/targets/lookup_url/tasks/use_netrc.yml
index 68dc8934..b90d05dc 100644
--- a/test/integration/targets/lookup_url/tasks/use_netrc.yml
+++ b/test/integration/targets/lookup_url/tasks/use_netrc.yml
@@ -10,7 +10,7 @@
- name: test Url lookup with ~/.netrc forced Basic auth
set_fact:
- web_data: "{{ lookup('ansible.builtin.url', 'https://{{ httpbin_host }}/bearer', headers={'Authorization':'Bearer foobar'}) }}"
+ web_data: "{{ lookup('ansible.builtin.url', 'https://' ~ httpbin_host ~ '/bearer', headers={'Authorization':'Bearer foobar'}) }}"
ignore_errors: yes
- name: assert test Url lookup with ~/.netrc forced Basic auth
@@ -18,11 +18,11 @@
that:
- "web_data.token.find('v=' ~ 'Zm9vOmJhcg==') == -1"
fail_msg: "Was expecting 'foo:bar' in base64, but received: {{ web_data }}"
- success_msg: "Expected Basic authentication even Bearer headers were sent"
+ success_msg: "Expected Basic authentication even Bearer headers were sent"
- name: test Url lookup with use_netrc=False
set_fact:
- web_data: "{{ lookup('ansible.builtin.url', 'https://{{ httpbin_host }}/bearer', headers={'Authorization':'Bearer foobar'}, use_netrc='False') }}"
+ web_data: "{{ lookup('ansible.builtin.url', 'https://' ~ httpbin_host ~ '/bearer', headers={'Authorization':'Bearer foobar'}, use_netrc='False') }}"
- name: assert test Url lookup with netrc=False used Bearer authentication
assert:
@@ -34,4 +34,4 @@
- name: Clean up. Removing ~/.netrc
file:
path: ~/.netrc
- state: absent \ No newline at end of file
+ state: absent
diff --git a/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml b/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml
index 09322a9d..bd892de9 100644
--- a/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml
+++ b/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml
@@ -1,4 +1,4 @@
plugin_routing:
connection:
redirected_dummy:
- redirect: ns.name.dummy \ No newline at end of file
+ redirect: ns.name.dummy
diff --git a/test/integration/targets/loop-connection/main.yml b/test/integration/targets/loop-connection/main.yml
index fbffe309..ba60e649 100644
--- a/test/integration/targets/loop-connection/main.yml
+++ b/test/integration/targets/loop-connection/main.yml
@@ -30,4 +30,4 @@
- assert:
that:
- connected_test.results[0].stderr == "ran - 1"
- - connected_test.results[1].stderr == "ran - 2" \ No newline at end of file
+ - connected_test.results[1].stderr == "ran - 2"
diff --git a/test/integration/targets/missing_required_lib/library/missing_required_lib.py b/test/integration/targets/missing_required_lib/library/missing_required_lib.py
index 480ea001..8c7ba884 100644
--- a/test/integration/targets/missing_required_lib/library/missing_required_lib.py
+++ b/test/integration/targets/missing_required_lib/library/missing_required_lib.py
@@ -8,7 +8,7 @@ __metaclass__ = type
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
try:
- import ansible_missing_lib
+ import ansible_missing_lib # pylint: disable=unused-import
HAS_LIB = True
except ImportError as e:
HAS_LIB = False
diff --git a/test/integration/targets/module_defaults/action_plugins/debug.py b/test/integration/targets/module_defaults/action_plugins/debug.py
index 2584fd3d..0c43201c 100644
--- a/test/integration/targets/module_defaults/action_plugins/debug.py
+++ b/test/integration/targets/module_defaults/action_plugins/debug.py
@@ -20,7 +20,7 @@ __metaclass__ = type
from ansible.errors import AnsibleUndefinedVariable
from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.action import ActionBase
diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py
index 0d39f26d..174f3725 100644
--- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py
+++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py
@@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action.normal import ActionModule as ActionBase
-from ansible.utils.vars import merge_hash
class ActionModule(ActionBase):
diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py
index 20284fd1..7ba24348 100644
--- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py
+++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py
@@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action.normal import ActionModule as ActionBase
-from ansible.utils.vars import merge_hash
class ActionModule(ActionBase):
diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py
index b0e1904b..67050fbd 100644
--- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py
+++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py
@@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.plugins.action.normal import ActionModule as ActionBase
-from ansible.utils.vars import merge_hash
class ActionModule(ActionBase):
diff --git a/test/integration/targets/module_no_log/aliases b/test/integration/targets/module_no_log/aliases
index 9e84f636..afa1c9c3 100644
--- a/test/integration/targets/module_no_log/aliases
+++ b/test/integration/targets/module_no_log/aliases
@@ -1,5 +1,4 @@
shippable/posix/group3
context/controller
skip/freebsd # not configured to log user.info to /var/log/syslog
-skip/osx # not configured to log user.info to /var/log/syslog
skip/macos # not configured to log user.info to /var/log/syslog
diff --git a/test/integration/targets/module_no_log/tasks/main.yml b/test/integration/targets/module_no_log/tasks/main.yml
index cf9e5802..bf024105 100644
--- a/test/integration/targets/module_no_log/tasks/main.yml
+++ b/test/integration/targets/module_no_log/tasks/main.yml
@@ -59,3 +59,41 @@
# 2) the AnsibleModule.log method is not working
- good_message in grep.stdout
- bad_message not in grep.stdout
+
+- name: Ensure we do not obscure what we should not
+ block:
+ - module_that_has_secret:
+ secret: u
+ notsecret: u
+ register: ouch
+ ignore_errors: true
+
+ - name: no log wont obscure booleans when True, but still hide in msg
+ assert:
+ that:
+ - ouch['changed'] is boolean
+ - "'*' in ouch['msg']"
+
+ - module_that_has_secret:
+ secret: a
+ notsecret: b
+ register: ouch
+ ignore_errors: true
+
+ - name: no log wont obscure booleans when False, but still hide in msg
+ assert:
+ that:
+ - ouch['changed'] is boolean
+ - "'*' in ouch['msg']"
+
+ - module_that_has_secret:
+ secret: True
+ notsecret: False
+ register: ouch
+ ignore_errors: true
+
+ - name: no log does not hide bool values
+ assert:
+ that:
+ - ouch['changed'] is boolean
+ - "'*' not in ouch['msg']"
diff --git a/test/integration/targets/module_utils/library/test.py b/test/integration/targets/module_utils/library/test.py
index fb6c8a81..857d3d8e 100644
--- a/test/integration/targets/module_utils/library/test.py
+++ b/test/integration/targets/module_utils/library/test.py
@@ -11,8 +11,8 @@ import ansible.module_utils.foo0
results['foo0'] = ansible.module_utils.foo0.data
# Test depthful import with no from
-import ansible.module_utils.bar0.foo
-results['bar0'] = ansible.module_utils.bar0.foo.data
+import ansible.module_utils.bar0.foo3
+results['bar0'] = ansible.module_utils.bar0.foo3.data
# Test import of module_utils/foo1.py
from ansible.module_utils import foo1
@@ -72,12 +72,12 @@ from ansible.module_utils.spam8.ham import eggs
results['spam8'] = (bacon.data, eggs)
# Test that import of module_utils/qux1/quux.py using as works
-from ansible.module_utils.qux1 import quux as one
-results['qux1'] = one.data
+from ansible.module_utils.qux1 import quux as two
+results['qux1'] = two.data
# Test that importing qux2/quux.py and qux2/quuz.py using as works
-from ansible.module_utils.qux2 import quux as one, quuz as two
-results['qux2'] = (one.data, two.data)
+from ansible.module_utils.qux2 import quux as three, quuz as four
+results['qux2'] = (three.data, four.data)
# Test depth
from ansible.module_utils.a.b.c.d.e.f.g.h import data
diff --git a/test/integration/targets/module_utils/library/test_failure.py b/test/integration/targets/module_utils/library/test_failure.py
index efb3ddae..ab80ceae 100644
--- a/test/integration/targets/module_utils/library/test_failure.py
+++ b/test/integration/targets/module_utils/library/test_failure.py
@@ -6,9 +6,9 @@ results = {}
# Test that we are rooted correctly
# Following files:
# module_utils/yak/zebra/foo.py
-from ansible.module_utils.zebra import foo
+from ansible.module_utils.zebra import foo4
-results['zebra'] = foo.data
+results['zebra'] = foo4.data
from ansible.module_utils.basic import AnsibleModule
AnsibleModule(argument_spec=dict()).exit_json(**results)
diff --git a/test/integration/targets/module_utils/module_utils_test.yml b/test/integration/targets/module_utils/module_utils_test.yml
index 4e948bd6..352bc582 100644
--- a/test/integration/targets/module_utils/module_utils_test.yml
+++ b/test/integration/targets/module_utils/module_utils_test.yml
@@ -47,7 +47,7 @@
assert:
that:
- result is failed
- - result['msg'] == "Could not find imported module support code for ansible.modules.test_failure. Looked for (['ansible.module_utils.zebra.foo', 'ansible.module_utils.zebra'])"
+ - result['msg'] == "Could not find imported module support code for ansible.modules.test_failure. Looked for (['ansible.module_utils.zebra.foo4', 'ansible.module_utils.zebra'])"
- name: Test that alias deprecation works
test_alias_deprecation:
diff --git a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
index 6170f046..9644df93 100644
--- a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
+++ b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1
@@ -87,7 +87,7 @@ Function Assert-DictionaryEqual {
}
Function Exit-Module {
- # Make sure Exit actually calls exit and not our overriden test behaviour
+ # Make sure Exit actually calls exit and not our overridden test behaviour
[Ansible.Basic.AnsibleModule]::Exit = { param([Int32]$rc) exit $rc }
Write-Output -InputObject (ConvertTo-Json -InputObject $module.Result -Compress -Depth 99)
$module.ExitJson()
diff --git a/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1 b/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1
index d18c42d7..5cb1a72d 100644
--- a/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1
+++ b/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1
@@ -328,5 +328,73 @@ finally {
}
Assert-Equal -actual ([Namespace12.Class12]::GetString()) -expected "b"
+$unsafe_code_fail = @'
+using System;
+
+namespace Namespace13
+{
+ public class Class13
+ {
+
+ public static int GetNumber()
+ {
+ int num = 2;
+ int* numPtr = &num;
+
+ DoubleNumber(numPtr);
+
+ return num;
+ }
+
+ private unsafe static void DoubleNumber(int* num)
+ {
+ *num = *num * 3;
+ }
+ }
+}
+'@
+$failed = $false
+try {
+ Add-CSharpType -Reference $unsafe_code_fail
+}
+catch {
+ $failed = $true
+ $actual = $_.Exception.Message.Contains("error CS0227: Unsafe code may only appear if compiling with /unsafe")
+ Assert-Equal -actual $actual -expected $true
+}
+Assert-Equal -actual $failed -expected $true
+
+$unsafe_code = @'
+using System;
+
+//AllowUnsafe
+
+namespace Namespace13
+{
+ public class Class13
+ {
+ public static int GetNumber()
+ {
+ int num = 2;
+ unsafe
+ {
+ int* numPtr = &num;
+
+ DoubleNumber(numPtr);
+ }
+
+ return num;
+ }
+
+ private unsafe static void DoubleNumber(int* num)
+ {
+ *num = *num * 2;
+ }
+ }
+}
+'@
+Add-CSharpType -Reference $unsafe_code
+Assert-Equal -actual ([Namespace13.Class13]::GetNumber()) -expected 4
+
$result.res = "success"
Exit-Json -obj $result
diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh
index bb5c048f..bf764bf9 100755
--- a/test/integration/targets/no_log/runme.sh
+++ b/test/integration/targets/no_log/runme.sh
@@ -5,7 +5,7 @@ set -eux
# This test expects 7 loggable vars and 0 non-loggable ones.
# If either mismatches it fails, run the ansible-playbook command to debug.
[ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \
-'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "26/0" ]
+'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "27/0" ]
# deal with corner cases with no log and loops
# no log enabled, should produce 6 censored messages
@@ -19,3 +19,8 @@ set -eux
# test invalid data passed to a suboption
[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]
+
+# test variations on ANSIBLE_NO_LOG
+[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
+[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ]
diff --git a/test/integration/targets/old_style_cache_plugins/aliases b/test/integration/targets/old_style_cache_plugins/aliases
index 37773831..163129e2 100644
--- a/test/integration/targets/old_style_cache_plugins/aliases
+++ b/test/integration/targets/old_style_cache_plugins/aliases
@@ -2,5 +2,4 @@ destructive
needs/root
shippable/posix/group5
context/controller
-skip/osx
skip/macos
diff --git a/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py b/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py
index 44b6cf93..23c7789b 100644
--- a/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py
+++ b/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py
@@ -44,7 +44,6 @@ DOCUMENTATION = '''
import time
import json
-from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
from ansible.plugins.cache import BaseCacheModule
diff --git a/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml b/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml
index 8aad37a3..b7cd4831 100644
--- a/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml
+++ b/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml
@@ -20,8 +20,9 @@
- name: get the latest stable redis server release
get_url:
- url: http://download.redis.io/redis-stable.tar.gz
+ url: https://download.redis.io/redis-stable.tar.gz
dest: ./
+ timeout: 60
- name: unzip download
unarchive:
diff --git a/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py b/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py
index d5c9a422..f554be04 100644
--- a/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py
+++ b/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py
@@ -2,7 +2,7 @@ from ansible.plugins.vars import BaseVarsPlugin
class VarsModule(BaseVarsPlugin):
- REQUIRES_WHITELIST = False
+ REQUIRES_WHITELIST = True
def get_vars(self, loader, path, entities):
return {}
diff --git a/test/integration/targets/old_style_vars_plugins/runme.sh b/test/integration/targets/old_style_vars_plugins/runme.sh
index 4cd19168..9f416235 100755
--- a/test/integration/targets/old_style_vars_plugins/runme.sh
+++ b/test/integration/targets/old_style_vars_plugins/runme.sh
@@ -12,9 +12,39 @@ export ANSIBLE_VARS_PLUGINS=./vars_plugins
export ANSIBLE_VARS_ENABLED=require_enabled
[ "$(ansible-inventory -i localhost, --list --yaml all "$@" | grep -c 'require_enabled')" = "1" ]
-# Test the deprecated class attribute
+# Test deprecated features
export ANSIBLE_VARS_PLUGINS=./deprecation_warning
-WARNING="The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. Use 'REQUIRES_ENABLED' instead."
+WARNING_1="The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. Use 'REQUIRES_ENABLED' instead."
+WARNING_2="The vars plugin v2_vars_plugin .* is relying on the deprecated entrypoints 'get_host_vars' and 'get_group_vars'"
ANSIBLE_DEPRECATION_WARNINGS=True ANSIBLE_NOCOLOR=True ANSIBLE_FORCE_COLOR=False \
- ansible-inventory -i localhost, --list all 2> err.txt
-ansible localhost -m debug -a "msg={{ lookup('file', 'err.txt') | regex_replace('\n', '') }}" | grep "$WARNING"
+ ansible-inventory -i localhost, --list all "$@" 2> err.txt
+for WARNING in "$WARNING_1" "$WARNING_2"; do
+ ansible localhost -m debug -a "msg={{ lookup('file', 'err.txt') | regex_replace('\n', '') }}" | grep "$WARNING"
+done
+
+# Test how many times vars plugins are loaded for a simple play containing a task
+# host_group_vars is stateless, so we can load it once and reuse it, every other vars plugin should be instantiated before it runs
+cat << EOF > "test_task_vars.yml"
+---
+- hosts: localhost
+ connection: local
+ gather_facts: no
+ tasks:
+ - debug:
+EOF
+
+# hide the debug noise by dumping to a file
+trap 'rm -rf -- "out.txt"' EXIT
+
+ANSIBLE_DEBUG=True ansible-playbook test_task_vars.yml > out.txt
+[ "$(grep -c "Loading VarsModule 'host_group_vars'" out.txt)" -eq 1 ]
+[ "$(grep -c "Loading VarsModule 'require_enabled'" out.txt)" -gt 50 ]
+[ "$(grep -c "Loading VarsModule 'auto_enabled'" out.txt)" -gt 50 ]
+
+export ANSIBLE_VARS_ENABLED=ansible.builtin.host_group_vars
+ANSIBLE_DEBUG=True ansible-playbook test_task_vars.yml > out.txt
+[ "$(grep -c "Loading VarsModule 'host_group_vars'" out.txt)" -eq 1 ]
+[ "$(grep -c "Loading VarsModule 'require_enabled'" out.txt)" -lt 3 ]
+[ "$(grep -c "Loading VarsModule 'auto_enabled'" out.txt)" -gt 50 ]
+
+ansible localhost -m include_role -a 'name=a' "$@"
diff --git a/test/integration/targets/omit/75692.yml b/test/integration/targets/omit/75692.yml
index b4000c97..5ba8a2df 100644
--- a/test/integration/targets/omit/75692.yml
+++ b/test/integration/targets/omit/75692.yml
@@ -2,10 +2,10 @@
hosts: testhost
gather_facts: false
become: yes
+ # become_user needed at play level for testing this behavior
become_user: nobody
roles:
- name: setup_test_user
- become: yes
become_user: root
tasks:
- shell: whoami
diff --git a/test/integration/targets/package/tasks/main.yml b/test/integration/targets/package/tasks/main.yml
index c17525d8..37267aa6 100644
--- a/test/integration/targets/package/tasks/main.yml
+++ b/test/integration/targets/package/tasks/main.yml
@@ -239,4 +239,4 @@
that:
- "result is changed"
- when: ansible_distribution == "Fedora" \ No newline at end of file
+ when: ansible_distribution == "Fedora"
diff --git a/test/integration/targets/package_facts/aliases b/test/integration/targets/package_facts/aliases
index 5a5e4646..f5edf4b1 100644
--- a/test/integration/targets/package_facts/aliases
+++ b/test/integration/targets/package_facts/aliases
@@ -1,3 +1,2 @@
shippable/posix/group2
-skip/osx
skip/macos
diff --git a/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml b/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml
index d225c0f9..25e91f28 100644
--- a/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml
+++ b/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml
@@ -121,7 +121,10 @@
- result.cmd == "echo foo --arg=a --arg=b"
- name: test includes with params
- include: test_include.yml fact_name=include_params param="{{ test_input }}"
+ include_tasks: test_include.yml
+ vars:
+ fact_name: include_params
+ param: "{{ test_input }}"
- name: assert the include set the correct fact for the param
assert:
@@ -129,7 +132,10 @@
- include_params == test_input
- name: test includes with quoted params
- include: test_include.yml fact_name=double_quoted_param param="this is a param with double quotes"
+ include_tasks: test_include.yml
+ vars:
+ fact_name: double_quoted_param
+ param: "this is a param with double quotes"
- name: assert the include set the correct fact for the double quoted param
assert:
@@ -137,7 +143,10 @@
- double_quoted_param == "this is a param with double quotes"
- name: test includes with single quoted params
- include: test_include.yml fact_name=single_quoted_param param='this is a param with single quotes'
+ include_tasks: test_include.yml
+ vars:
+ fact_name: single_quoted_param
+ param: 'this is a param with single quotes'
- name: assert the include set the correct fact for the single quoted param
assert:
@@ -145,7 +154,7 @@
- single_quoted_param == "this is a param with single quotes"
- name: test includes with quoted params in complex args
- include: test_include.yml
+ include_tasks: test_include.yml
vars:
fact_name: complex_param
param: "this is a param in a complex arg with double quotes"
@@ -165,7 +174,7 @@
- result.msg == "this should be debugged"
- name: test conditional includes
- include: test_include_conditional.yml
+ include_tasks: test_include_conditional.yml
when: false
- name: assert the nested include from test_include_conditional was not set
diff --git a/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml b/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml
index 070888da..a1d8b7ce 100644
--- a/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml
+++ b/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml
@@ -1 +1 @@
-- include: test_include_nested.yml
+- include_tasks: test_include_nested.yml
diff --git a/test/integration/targets/parsing/runme.sh b/test/integration/targets/parsing/runme.sh
index 022ce4cf..2d550082 100755
--- a/test/integration/targets/parsing/runme.sh
+++ b/test/integration/targets/parsing/runme.sh
@@ -2,5 +2,5 @@
set -eux
-ansible-playbook bad_parsing.yml -i ../../inventory -vvv "$@" --tags prepare,common,scenario5
-ansible-playbook good_parsing.yml -i ../../inventory -v "$@"
+ansible-playbook parsing.yml -i ../../inventory "$@" -e "output_dir=${OUTPUT_DIR}"
+ansible-playbook good_parsing.yml -i ../../inventory "$@"
diff --git a/test/integration/targets/path_lookups/testplay.yml b/test/integration/targets/path_lookups/testplay.yml
index 8bf45532..bc05c7e5 100644
--- a/test/integration/targets/path_lookups/testplay.yml
+++ b/test/integration/targets/path_lookups/testplay.yml
@@ -4,9 +4,11 @@
pre_tasks:
- name: remove {{ remove }}
file: path={{ playbook_dir }}/{{ remove }} state=absent
- roles:
- - showfile
- post_tasks:
+ tasks:
+ - import_role:
+ name: showfile
+ tasks_from: notmain.yml
+
- name: from play
set_fact: play_result="{{lookup('file', 'testfile')}}"
diff --git a/test/integration/targets/pause/test-pause.py b/test/integration/targets/pause/test-pause.py
index 3703470d..ab771fa0 100755
--- a/test/integration/targets/pause/test-pause.py
+++ b/test/integration/targets/pause/test-pause.py
@@ -168,7 +168,9 @@ pause_test = pexpect.spawn(
pause_test.logfile = log_buffer
pause_test.expect(r'Pausing for \d+ seconds')
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
+pause_test.send('\n') # test newline does not stop the prompt - waiting for a timeout or ctrl+C
pause_test.send('\x03')
+pause_test.expect("Press 'C' to continue the play or 'A' to abort")
pause_test.send('C')
pause_test.expect('Task after pause')
pause_test.expect(pexpect.EOF)
@@ -187,6 +189,7 @@ pause_test.logfile = log_buffer
pause_test.expect(r'Pausing for \d+ seconds')
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
pause_test.send('\x03')
+pause_test.expect("Press 'C' to continue the play or 'A' to abort")
pause_test.send('A')
pause_test.expect('user requested abort!')
pause_test.expect(pexpect.EOF)
@@ -225,6 +228,7 @@ pause_test.expect(r'Pausing for \d+ seconds')
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
pause_test.expect(r"Waiting for two seconds:")
pause_test.send('\x03')
+pause_test.expect("Press 'C' to continue the play or 'A' to abort")
pause_test.send('C')
pause_test.expect('Task after pause')
pause_test.expect(pexpect.EOF)
@@ -244,6 +248,7 @@ pause_test.expect(r'Pausing for \d+ seconds')
pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)")
pause_test.expect(r"Waiting for two seconds:")
pause_test.send('\x03')
+pause_test.expect("Press 'C' to continue the play or 'A' to abort")
pause_test.send('A')
pause_test.expect('user requested abort!')
pause_test.expect(pexpect.EOF)
@@ -275,6 +280,24 @@ pause_test.send('\r')
pause_test.expect(pexpect.EOF)
pause_test.close()
+# Test input is not returned if a timeout is given
+
+playbook = 'pause-6.yml'
+
+pause_test = pexpect.spawn(
+ 'ansible-playbook',
+ args=[playbook] + args,
+ timeout=10,
+ env=os.environ
+)
+
+pause_test.logfile = log_buffer
+pause_test.expect(r'Wait for three seconds:')
+pause_test.send('ignored user input')
+pause_test.expect('Task after pause')
+pause_test.expect(pexpect.EOF)
+pause_test.close()
+
# Test that enter presses may not continue the play when a timeout is set.
diff --git a/test/integration/targets/pip/tasks/main.yml b/test/integration/targets/pip/tasks/main.yml
index 66992fd0..a3770702 100644
--- a/test/integration/targets/pip/tasks/main.yml
+++ b/test/integration/targets/pip/tasks/main.yml
@@ -40,6 +40,9 @@
extra_args: "-c {{ remote_constraints }}"
- include_tasks: pip.yml
+
+ - include_tasks: no_setuptools.yml
+ when: ansible_python.version_info[:2] >= [3, 8]
always:
- name: platform specific cleanup
include_tasks: "{{ cleanup_filename }}"
diff --git a/test/integration/targets/pip/tasks/pip.yml b/test/integration/targets/pip/tasks/pip.yml
index 39480614..9f1034d2 100644
--- a/test/integration/targets/pip/tasks/pip.yml
+++ b/test/integration/targets/pip/tasks/pip.yml
@@ -568,6 +568,28 @@
that:
- "version13 is success"
+- name: Test virtualenv command with venv formatting
+ when: ansible_python.version.major > 2
+ block:
+ - name: Clean up the virtualenv
+ file:
+ state: absent
+ name: "{{ remote_tmp_dir }}/pipenv"
+
+ # ref: https://github.com/ansible/ansible/issues/76372
+ - name: install using different venv formatting
+ pip:
+ name: "{{ pip_test_package }}"
+ virtualenv: "{{ remote_tmp_dir }}/pipenv"
+ virtualenv_command: "{{ ansible_python_interpreter ~ ' -mvenv' }}"
+ state: present
+ register: version14
+
+ - name: ensure install using virtualenv_command with venv formatting
+ assert:
+ that:
+ - "version14 is changed"
+
### test virtualenv_command end ###
# https://github.com/ansible/ansible/issues/68592
diff --git a/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py b/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py
index 9f1c5c0b..44412f22 100644
--- a/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py
+++ b/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py
@@ -11,7 +11,7 @@ __metaclass__ = type
# noinspection PyUnresolvedReferences
try:
- from pkg_resources import Requirement
+ from pkg_resources import Requirement # pylint: disable=unused-import
except ImportError:
Requirement = None
diff --git a/test/integration/targets/plugin_filtering/filter_lookup.yml b/test/integration/targets/plugin_filtering/filter_lookup.yml
index 694ebfcb..5f183e9f 100644
--- a/test/integration/targets/plugin_filtering/filter_lookup.yml
+++ b/test/integration/targets/plugin_filtering/filter_lookup.yml
@@ -1,6 +1,6 @@
---
filter_version: 1.0
-module_blacklist:
+module_rejectlist:
# Specify the name of a lookup plugin here. This should have no effect as
# this is only for filtering modules
- list
diff --git a/test/integration/targets/plugin_filtering/filter_modules.yml b/test/integration/targets/plugin_filtering/filter_modules.yml
index 6cffa676..bef7d6d8 100644
--- a/test/integration/targets/plugin_filtering/filter_modules.yml
+++ b/test/integration/targets/plugin_filtering/filter_modules.yml
@@ -1,6 +1,6 @@
---
filter_version: 1.0
-module_blacklist:
+module_rejectlist:
# A pure action plugin
- pause
# A hybrid action plugin with module
diff --git a/test/integration/targets/plugin_filtering/filter_ping.yml b/test/integration/targets/plugin_filtering/filter_ping.yml
index 08e56f24..8604716e 100644
--- a/test/integration/targets/plugin_filtering/filter_ping.yml
+++ b/test/integration/targets/plugin_filtering/filter_ping.yml
@@ -1,5 +1,5 @@
---
filter_version: 1.0
-module_blacklist:
+module_rejectlist:
# Ping is special
- ping
diff --git a/test/integration/targets/plugin_filtering/filter_stat.yml b/test/integration/targets/plugin_filtering/filter_stat.yml
index c1ce42ef..132bf03f 100644
--- a/test/integration/targets/plugin_filtering/filter_stat.yml
+++ b/test/integration/targets/plugin_filtering/filter_stat.yml
@@ -1,5 +1,5 @@
---
filter_version: 1.0
-module_blacklist:
+module_rejectlist:
# Stat is special
- stat
diff --git a/test/integration/targets/plugin_filtering/runme.sh b/test/integration/targets/plugin_filtering/runme.sh
index aa0e2b0c..03d78abc 100755
--- a/test/integration/targets/plugin_filtering/runme.sh
+++ b/test/integration/targets/plugin_filtering/runme.sh
@@ -22,11 +22,11 @@ if test $? != 0 ; then
fi
#
-# Check that if no modules are blacklisted then Ansible should not through traceback
+# Check that if no modules are rejected then Ansible should not through traceback
#
-ANSIBLE_CONFIG=no_blacklist_module.ini ansible-playbook tempfile.yml -i ../../inventory -vvv "$@"
+ANSIBLE_CONFIG=no_rejectlist_module.ini ansible-playbook tempfile.yml -i ../../inventory -vvv "$@"
if test $? != 0 ; then
- echo "### Failed to run tempfile with no modules blacklisted"
+ echo "### Failed to run tempfile with no modules rejected"
exit 1
fi
@@ -87,7 +87,7 @@ fi
ANSIBLE_CONFIG=filter_lookup.ini ansible-playbook lookup.yml -i ../../inventory -vvv "$@"
if test $? != 0 ; then
- echo "### Failed to use a lookup plugin when it is incorrectly specified in the *module* blacklist"
+ echo "### Failed to use a lookup plugin when it is incorrectly specified in the *module* reject list"
exit 1
fi
@@ -107,10 +107,10 @@ ANSIBLE_CONFIG=filter_stat.ini
export ANSIBLE_CONFIG
CAPTURE=$(ansible-playbook copy.yml -i ../../inventory -vvv "$@" 2>&1)
if test $? = 0 ; then
- echo "### Copy ran even though stat is in the module blacklist"
+ echo "### Copy ran even though stat is in the module reject list"
exit 1
else
- echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.'
+ echo "$CAPTURE" | grep 'The stat module was specified in the module reject list file,.*, but Ansible will not function without the stat module. Please remove stat from the reject list.'
if test $? != 0 ; then
echo "### Stat did not give us our custom error message"
exit 1
@@ -124,10 +124,10 @@ ANSIBLE_CONFIG=filter_stat.ini
export ANSIBLE_CONFIG
CAPTURE=$(ansible-playbook stat.yml -i ../../inventory -vvv "$@" 2>&1)
if test $? = 0 ; then
- echo "### Stat ran even though it is in the module blacklist"
+ echo "### Stat ran even though it is in the module reject list"
exit 1
else
- echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.'
+ echo "$CAPTURE" | grep 'The stat module was specified in the module reject list file,.*, but Ansible will not function without the stat module. Please remove stat from the reject list.'
if test $? != 0 ; then
echo "### Stat did not give us our custom error message"
exit 1
diff --git a/test/integration/targets/plugin_loader/override/filters.yml b/test/integration/targets/plugin_loader/override/filters.yml
index e51ab4e9..569a4479 100644
--- a/test/integration/targets/plugin_loader/override/filters.yml
+++ b/test/integration/targets/plugin_loader/override/filters.yml
@@ -1,7 +1,7 @@
- hosts: testhost
gather_facts: false
tasks:
- - name: ensure local 'flag' filter works, 'flatten' is overriden and 'ternary' is still from core
+ - name: ensure local 'flag' filter works, 'flatten' is overridden and 'ternary' is still from core
assert:
that:
- a|flag == 'flagged'
diff --git a/test/integration/targets/plugin_loader/runme.sh b/test/integration/targets/plugin_loader/runme.sh
index e30f6241..e68f06ad 100755
--- a/test/integration/targets/plugin_loader/runme.sh
+++ b/test/integration/targets/plugin_loader/runme.sh
@@ -34,3 +34,8 @@ done
# test config loading
ansible-playbook use_coll_name.yml -i ../../inventory -e 'ansible_connection=ansible.builtin.ssh' "$@"
+
+# test filter loading ignoring duplicate file basename
+ansible-playbook file_collision/play.yml "$@"
+
+ANSIBLE_COLLECTIONS_PATH=$PWD/collections ansible-playbook unsafe_plugin_name.yml "$@"
diff --git a/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py b/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py
index e542913d..41a76d9b 100644
--- a/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py
+++ b/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py
@@ -64,7 +64,7 @@ from collections.abc import MutableMapping
from ansible.errors import AnsibleError, AnsibleParserError
from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.plugins.inventory import BaseFileInventoryPlugin
NoneType = type(None)
diff --git a/test/integration/targets/remote_tmp/playbook.yml b/test/integration/targets/remote_tmp/playbook.yml
index 5adef626..2d0db4e8 100644
--- a/test/integration/targets/remote_tmp/playbook.yml
+++ b/test/integration/targets/remote_tmp/playbook.yml
@@ -30,30 +30,43 @@
- name: Test tempdir is removed
hosts: testhost
gather_facts: false
+ vars:
+ # These tests cannot be run with pipelining as it defeats the purpose of
+ # ensuring remote_tmp is cleaned up. Pipelining is enabled in the test
+ # inventory
+ ansible_pipelining: false
+ # Ensure that the remote_tmp_dir we create allows the unpriv connection user
+ # to create the remote_tmp
+ ansible_become: false
tasks:
- import_role:
name: ../setup_remote_tmp_dir
- - file:
- state: touch
- path: "{{ remote_tmp_dir }}/65393"
+ - vars:
+ # Isolate the remote_tmp used by these tests
+ ansible_remote_tmp: "{{ remote_tmp_dir }}/remote_tmp"
+ block:
+ - file:
+ state: touch
+ path: "{{ remote_tmp_dir }}/65393"
- - copy:
- src: "{{ remote_tmp_dir }}/65393"
- dest: "{{ remote_tmp_dir }}/65393.2"
- remote_src: true
+ - copy:
+ src: "{{ remote_tmp_dir }}/65393"
+ dest: "{{ remote_tmp_dir }}/65393.2"
+ remote_src: true
- - find:
- path: "~/.ansible/tmp"
- use_regex: yes
- patterns: 'AnsiballZ_.+\.py'
- recurse: true
- register: result
+ - find:
+ path: "{{ ansible_remote_tmp }}"
+ use_regex: yes
+ patterns: 'AnsiballZ_.+\.py'
+ recurse: true
+ register: result
- debug:
var: result
- assert:
that:
- # Should find nothing since pipelining is used
- - result.files|length == 0
+ # Should only be AnsiballZ_find.py because find is actively running
+ - result.files|length == 1
+ - result.files[0].path.endswith('/AnsiballZ_find.py')
diff --git a/test/integration/targets/replace/tasks/main.yml b/test/integration/targets/replace/tasks/main.yml
index d267b783..ca8b4ec1 100644
--- a/test/integration/targets/replace/tasks/main.yml
+++ b/test/integration/targets/replace/tasks/main.yml
@@ -263,3 +263,22 @@
- replace_cat8.stdout_lines[1] == "9.9.9.9"
- replace_cat8.stdout_lines[7] == "0.0.0.0"
- replace_cat8.stdout_lines[13] == "0.0.0.0"
+
+# For Python 3.6 or greater - https://github.com/ansible/ansible/issues/79364
+- name: Handle bad escape character in regular expression
+ replace:
+ path: /dev/null
+ after: ^
+ before: $
+ regexp: \.
+ replace: '\D'
+ ignore_errors: true
+ register: replace_test9
+ when: ansible_python.version.major == 3 and ansible_python.version.minor > 6
+
+- name: Validate the failure
+ assert:
+ that:
+ - replace_test9 is failure
+ - replace_test9.msg.startswith("Unable to process replace")
+ when: ansible_python.version.major == 3 and ansible_python.version.minor > 6
diff --git a/test/integration/targets/roles/runme.sh b/test/integration/targets/roles/runme.sh
index bb98a932..bf3aaf58 100755
--- a/test/integration/targets/roles/runme.sh
+++ b/test/integration/targets/roles/runme.sh
@@ -3,26 +3,47 @@
set -eux
# test no dupes when dependencies in b and c point to a in roles:
-[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags inroles "$@" | grep -c '"msg": "A"')" = "1" ]
-[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags acrossroles "$@" | grep -c '"msg": "A"')" = "1" ]
-[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags intasks "$@" | grep -c '"msg": "A"')" = "1" ]
+[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags inroles | grep -c '"msg": "A"')" = "1" ]
+[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags acrossroles | grep -c '"msg": "A"')" = "1" ]
+[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags intasks | grep -c '"msg": "A"')" = "1" ]
# but still dupe across plays
-[ "$(ansible-playbook no_dupes.yml -i ../../inventory "$@" | grep -c '"msg": "A"')" = "3" ]
+[ "$(ansible-playbook no_dupes.yml -i ../../inventory | grep -c '"msg": "A"')" = "3" ]
+
+# and don't dedupe before the role successfully completes
+[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_skipped | grep -c '"msg": "A"')" = "1" ]
+[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_failed | grep -c '"msg": "failed_when task succeeded"')" = "1" ]
+[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable -vvv | grep -c '"data": "reachable"')" = "1" ]
+ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable | grep -e 'ignored=2'
# include/import can execute another instance of role
-[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags importrole "$@" | grep -c '"msg": "A"')" = "2" ]
-[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags includerole "$@" | grep -c '"msg": "A"')" = "2" ]
+[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags importrole | grep -c '"msg": "A"')" = "2" ]
+[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags includerole | grep -c '"msg": "A"')" = "2" ]
+[ "$(ansible-playbook dupe_inheritance.yml -i ../../inventory | grep -c '"msg": "abc"')" = "3" ]
# ensure role data is merged correctly
ansible-playbook data_integrity.yml -i ../../inventory "$@"
# ensure role fails when trying to load 'non role' in _from
-ansible-playbook no_outside.yml -i ../../inventory "$@" > role_outside_output.log 2>&1 || true
+ansible-playbook no_outside.yml -i ../../inventory > role_outside_output.log 2>&1 || true
if grep "as it is not inside the expected role path" role_outside_output.log >/dev/null; then
echo "Test passed (playbook failed with expected output, output not shown)."
else
echo "Test failed, expected output from playbook failure is missing, output not shown)."
exit 1
fi
+
+# ensure vars scope is correct
+ansible-playbook vars_scope.yml -i ../../inventory "$@"
+
+# test nested includes get parent roles greater than a depth of 3
+[ "$(ansible-playbook 47023.yml -i ../../inventory | grep '\<\(Default\|Var\)\>' | grep -c 'is defined')" = "2" ]
+
+# ensure import_role called from include_role has the include_role in the dep chain
+ansible-playbook role_dep_chain.yml -i ../../inventory "$@"
+
+# global role privacy setting test, set to private, set to not private, default
+ANSIBLE_PRIVATE_ROLE_VARS=1 ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@"
+ANSIBLE_PRIVATE_ROLE_VARS=0 ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@"
+ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@"
diff --git a/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml b/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml
index 1a1ccbe4..10dce6d2 100644
--- a/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml
+++ b/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml
@@ -2,6 +2,15 @@ argument_specs:
main:
short_description: Main entry point for role C.
options:
+ c_dict:
+ type: "dict"
+ required: true
c_int:
type: "int"
required: true
+ c_list:
+ type: "list"
+ required: true
+ c_raw:
+ type: "raw"
+ required: true
diff --git a/test/integration/targets/roles_arg_spec/test.yml b/test/integration/targets/roles_arg_spec/test.yml
index 5eca7c73..b88d2e18 100644
--- a/test/integration/targets/roles_arg_spec/test.yml
+++ b/test/integration/targets/roles_arg_spec/test.yml
@@ -48,6 +48,7 @@
name: a
vars:
a_int: "{{ INT_VALUE }}"
+ a_str: "import_role"
- name: "Call role entry point that is defined, but has no spec data"
import_role:
@@ -144,7 +145,10 @@
hosts: localhost
gather_facts: false
vars:
+ c_dict: {}
c_int: 1
+ c_list: []
+ c_raw: ~
a_str: "some string"
a_int: 42
tasks:
@@ -156,6 +160,125 @@
include_role:
name: c
+- name: "New play to reset vars: Test nested role including/importing role fails with null required options"
+ hosts: localhost
+ gather_facts: false
+ vars:
+ a_main_spec:
+ a_str:
+ required: true
+ type: "str"
+ c_main_spec:
+ c_int:
+ required: true
+ type: "int"
+ c_list:
+ required: true
+ type: "list"
+ c_dict:
+ required: true
+ type: "dict"
+ c_raw:
+ required: true
+ type: "raw"
+ # role c calls a's main and alternate entrypoints
+ a_str: ''
+ c_dict: {}
+ c_int: 0
+ c_list: []
+ c_raw: ~
+ tasks:
+ - name: test type coercion fails on None for required str
+ block:
+ - name: "Test import_role of role C (missing a_str)"
+ import_role:
+ name: c
+ vars:
+ a_str: ~
+ - fail:
+ msg: "Should not get here"
+ rescue:
+ - debug:
+ var: ansible_failed_result
+ - name: "Validate import_role failure"
+ assert:
+ that:
+ # NOTE: a bug here that prevents us from getting ansible_failed_task
+ - ansible_failed_result.argument_errors == [error]
+ - ansible_failed_result.argument_spec_data == a_main_spec
+ vars:
+ error: >-
+ argument 'a_str' is of type <class 'NoneType'> and we were unable to convert to str:
+ 'None' is not a string and conversion is not allowed
+
+ - name: test type coercion fails on None for required int
+ block:
+ - name: "Test import_role of role C (missing c_int)"
+ import_role:
+ name: c
+ vars:
+ c_int: ~
+ - fail:
+ msg: "Should not get here"
+ rescue:
+ - debug:
+ var: ansible_failed_result
+ - name: "Validate import_role failure"
+ assert:
+ that:
+ # NOTE: a bug here that prevents us from getting ansible_failed_task
+ - ansible_failed_result.argument_errors == [error]
+ - ansible_failed_result.argument_spec_data == c_main_spec
+ vars:
+ error: >-
+ argument 'c_int' is of type <class 'NoneType'> and we were unable to convert to int:
+ <class 'NoneType'> cannot be converted to an int
+
+ - name: test type coercion fails on None for required list
+ block:
+ - name: "Test import_role of role C (missing c_list)"
+ import_role:
+ name: c
+ vars:
+ c_list: ~
+ - fail:
+ msg: "Should not get here"
+ rescue:
+ - debug:
+ var: ansible_failed_result
+ - name: "Validate import_role failure"
+ assert:
+ that:
+ # NOTE: a bug here that prevents us from getting ansible_failed_task
+ - ansible_failed_result.argument_errors == [error]
+ - ansible_failed_result.argument_spec_data == c_main_spec
+ vars:
+ error: >-
+ argument 'c_list' is of type <class 'NoneType'> and we were unable to convert to list:
+ <class 'NoneType'> cannot be converted to a list
+
+ - name: test type coercion fails on None for required dict
+ block:
+ - name: "Test import_role of role C (missing c_dict)"
+ import_role:
+ name: c
+ vars:
+ c_dict: ~
+ - fail:
+ msg: "Should not get here"
+ rescue:
+ - debug:
+ var: ansible_failed_result
+ - name: "Validate import_role failure"
+ assert:
+ that:
+ # NOTE: a bug here that prevents us from getting ansible_failed_task
+ - ansible_failed_result.argument_errors == [error]
+ - ansible_failed_result.argument_spec_data == c_main_spec
+ vars:
+ error: >-
+ argument 'c_dict' is of type <class 'NoneType'> and we were unable to convert to dict:
+ <class 'NoneType'> cannot be converted to a dict
- name: "New play to reset vars: Test nested role including/importing role fails"
hosts: localhost
@@ -170,13 +293,15 @@
required: true
type: "int"
+ c_int: 100
+ c_list: []
+ c_dict: {}
+ c_raw: ~
tasks:
- block:
- name: "Test import_role of role C (missing a_str)"
import_role:
name: c
- vars:
- c_int: 100
- fail:
msg: "Should not get here"
@@ -201,7 +326,6 @@
include_role:
name: c
vars:
- c_int: 200
a_str: "some string"
- fail:
diff --git a/test/integration/targets/rpm_key/tasks/rpm_key.yaml b/test/integration/targets/rpm_key/tasks/rpm_key.yaml
index 89ed2361..204b42ac 100644
--- a/test/integration/targets/rpm_key/tasks/rpm_key.yaml
+++ b/test/integration/targets/rpm_key/tasks/rpm_key.yaml
@@ -123,6 +123,32 @@
assert:
that: "'rsa sha1 (md5) pgp md5 OK' in sl_check.stdout or 'digests signatures OK' in sl_check.stdout"
+- name: get keyid
+ shell: "rpm -q gpg-pubkey | head -n 1 | xargs rpm -q --qf %{version}"
+ register: key_id
+
+- name: remove GPG key using keyid
+ rpm_key:
+ state: absent
+ key: "{{ key_id.stdout }}"
+ register: remove_keyid
+ failed_when: remove_keyid.changed == false
+
+- name: remove GPG key using keyid (idempotent)
+ rpm_key:
+ state: absent
+ key: "{{ key_id.stdout }}"
+ register: key_id_idempotence
+
+- name: verify idempotent (key_id)
+ assert:
+ that: "not key_id_idempotence.changed"
+
+- name: add very first key on system again
+ rpm_key:
+ state: present
+ key: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/RPM-GPG-KEY-EPEL-7
+
- name: Issue 20325 - Verify fingerprint of key, invalid fingerprint - EXPECTED FAILURE
rpm_key:
key: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/RPM-GPG-KEY.dag
diff --git a/test/integration/targets/script/tasks/main.yml b/test/integration/targets/script/tasks/main.yml
index 74189f81..668ec48e 100644
--- a/test/integration/targets/script/tasks/main.yml
+++ b/test/integration/targets/script/tasks/main.yml
@@ -37,6 +37,17 @@
## script
##
+- name: Required one of free-form and cmd
+ script:
+ ignore_errors: yes
+ register: script_required
+
+- name: assert that the script fails if neither free-form nor cmd is given
+ assert:
+ that:
+ - script_required.failed
+ - "'one of the following' in script_required.msg"
+
- name: execute the test.sh script via command
script: test.sh
register: script_result0
diff --git a/test/integration/targets/service/aliases b/test/integration/targets/service/aliases
index f2f9ac9d..f3703f85 100644
--- a/test/integration/targets/service/aliases
+++ b/test/integration/targets/service/aliases
@@ -1,4 +1,3 @@
destructive
shippable/posix/group1
-skip/osx
skip/macos
diff --git a/test/integration/targets/service/files/ansible_test_service.py b/test/integration/targets/service/files/ansible_test_service.py
index 522493fc..6292272e 100644
--- a/test/integration/targets/service/files/ansible_test_service.py
+++ b/test/integration/targets/service/files/ansible_test_service.py
@@ -9,7 +9,6 @@ __metaclass__ = type
import os
import resource
import signal
-import sys
import time
UMASK = 0
diff --git a/test/integration/targets/service_facts/aliases b/test/integration/targets/service_facts/aliases
index 17d3eb75..32e10b03 100644
--- a/test/integration/targets/service_facts/aliases
+++ b/test/integration/targets/service_facts/aliases
@@ -1,4 +1,3 @@
shippable/posix/group2
skip/freebsd
-skip/osx
skip/macos
diff --git a/test/integration/targets/setup_deb_repo/tasks/main.yml b/test/integration/targets/setup_deb_repo/tasks/main.yml
index 471fb2a2..3e640f69 100644
--- a/test/integration/targets/setup_deb_repo/tasks/main.yml
+++ b/test/integration/targets/setup_deb_repo/tasks/main.yml
@@ -59,6 +59,7 @@
loop:
- stable
- testing
+ when: install_repo|default(True)|bool is true
# Need to uncomment the deb-src for the universe component for build-dep state
- name: Ensure deb-src for the universe component
diff --git a/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml b/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml
index f16d9b53..8c0b28bf 100644
--- a/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml
+++ b/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml
@@ -1,9 +1,2 @@
-- name: Setup remote constraints
- include_tasks: setup-remote-constraints.yml
- name: Install Paramiko for Python 3 on Alpine
- pip: # no apk package manager in core, just use pip
- name: paramiko
- extra_args: "-c {{ remote_constraints }}"
- environment:
- # Not sure why this fixes the test, but it does.
- SETUPTOOLS_USE_DISTUTILS: stdlib
+ command: apk add py3-paramiko
diff --git a/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml b/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml
index e9dcc62c..edb504ff 100644
--- a/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml
+++ b/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml
@@ -1,4 +1,2 @@
- name: Uninstall Paramiko for Python 3 on Alpine
- pip:
- name: paramiko
- state: absent
+ command: apk del py3-paramiko
diff --git a/test/integration/targets/setup_rpm_repo/tasks/main.yml b/test/integration/targets/setup_rpm_repo/tasks/main.yml
index be20078f..bf5af101 100644
--- a/test/integration/targets/setup_rpm_repo/tasks/main.yml
+++ b/test/integration/targets/setup_rpm_repo/tasks/main.yml
@@ -24,9 +24,18 @@
args:
name: "{{ rpm_repo_packages }}"
- - name: Install rpmfluff via pip
- pip:
- name: rpmfluff
+ - name: Install rpmfluff via pip, ensure it is installed with default python as python3-rpm may not exist for other versions
+ block:
+ - action: "{{ ansible_facts.pkg_mgr }}"
+ args:
+ name:
+ - python3-pip
+ - python3
+ state: latest
+
+ - pip:
+ name: rpmfluff
+ executable: pip3
when: ansible_facts.os_family == 'RedHat' and ansible_distribution_major_version is version('9', '==')
- set_fact:
diff --git a/test/integration/targets/strategy_linear/runme.sh b/test/integration/targets/strategy_linear/runme.sh
index cbb6aea3..a2734f97 100755
--- a/test/integration/targets/strategy_linear/runme.sh
+++ b/test/integration/targets/strategy_linear/runme.sh
@@ -5,3 +5,5 @@ set -eux
ansible-playbook test_include_file_noop.yml -i inventory "$@"
ansible-playbook task_action_templating.yml -i inventory "$@"
+
+ansible-playbook task_templated_run_once.yml -i inventory "$@"
diff --git a/test/integration/targets/subversion/aliases b/test/integration/targets/subversion/aliases
index 3cc41e4c..03b96434 100644
--- a/test/integration/targets/subversion/aliases
+++ b/test/integration/targets/subversion/aliases
@@ -1,6 +1,4 @@
shippable/posix/group2
-skip/osx
skip/macos
-skip/rhel/9.0b # svn checkout hangs
destructive
needs/root
diff --git a/test/integration/targets/systemd/tasks/test_indirect_service.yml b/test/integration/targets/systemd/tasks/test_indirect_service.yml
index fc11343e..0df60486 100644
--- a/test/integration/targets/systemd/tasks/test_indirect_service.yml
+++ b/test/integration/targets/systemd/tasks/test_indirect_service.yml
@@ -34,4 +34,4 @@
- assert:
that:
- systemd_enable_dummy_indirect_1 is changed
- - systemd_enable_dummy_indirect_2 is not changed \ No newline at end of file
+ - systemd_enable_dummy_indirect_2 is not changed
diff --git a/test/integration/targets/systemd/vars/Debian.yml b/test/integration/targets/systemd/vars/Debian.yml
index 613410f0..2dd0affb 100644
--- a/test/integration/targets/systemd/vars/Debian.yml
+++ b/test/integration/targets/systemd/vars/Debian.yml
@@ -1,3 +1,3 @@
ssh_service: ssh
sleep_bin_path: /bin/sleep
-indirect_service: dummy \ No newline at end of file
+indirect_service: dummy
diff --git a/test/integration/targets/tags/runme.sh b/test/integration/targets/tags/runme.sh
index 9da0b301..7dcb9985 100755
--- a/test/integration/targets/tags/runme.sh
+++ b/test/integration/targets/tags/runme.sh
@@ -73,3 +73,12 @@ ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=list --tags t
ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged --tags untagged "$@"
ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged_list --tags untagged,tag3 "$@"
ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=tagged --tags tagged "$@"
+
+ansible-playbook test_template_parent_tags.yml "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt
+
+ansible-playbook test_template_parent_tags.yml --tags tag1 "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt
+
+ansible-playbook test_template_parent_tags.yml --skip-tags tag1 "$@" 2>&1 | tee out.txt
+[ "$(grep out.txt -ce 'Tagged_task')" = "0" ]; rm out.txt
diff --git a/test/integration/targets/tasks/playbook.yml b/test/integration/targets/tasks/playbook.yml
index 80d9f8b1..10bd8591 100644
--- a/test/integration/targets/tasks/playbook.yml
+++ b/test/integration/targets/tasks/playbook.yml
@@ -6,6 +6,11 @@
debug:
msg: Hello
+ # ensure we properly test for an action name, not a task name when cheking for a meta task
+ - name: "meta"
+ debug:
+ msg: Hello
+
- name: ensure malformed raw_params on arbitrary actions are not ignored
debug:
garbage {{"with a template"}}
diff --git a/test/integration/targets/tasks/runme.sh b/test/integration/targets/tasks/runme.sh
index 594447bd..57cbf28a 100755
--- a/test/integration/targets/tasks/runme.sh
+++ b/test/integration/targets/tasks/runme.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
-ansible-playbook playbook.yml "$@"
+ansible-playbook playbook.yml \ No newline at end of file
diff --git a/test/integration/targets/template/runme.sh b/test/integration/targets/template/runme.sh
index 30163af7..d3913d97 100755
--- a/test/integration/targets/template/runme.sh
+++ b/test/integration/targets/template/runme.sh
@@ -8,7 +8,10 @@ ANSIBLE_ROLES_PATH=../ ansible-playbook template.yml -i ../../inventory -v "$@"
ansible testhost -i testhost, -m debug -a 'msg={{ hostvars["localhost"] }}' -e "vars1={{ undef() }}" -e "vars2={{ vars1 }}"
# Test for https://github.com/ansible/ansible/issues/27262
-ansible-playbook ansible_managed.yml -c ansible_managed.cfg -i ../../inventory -v "$@"
+ANSIBLE_CONFIG=ansible_managed.cfg ansible-playbook ansible_managed.yml -i ../../inventory -v "$@"
+
+# Test for https://github.com/ansible/ansible/pull/79129
+ANSIBLE_CONFIG=ansible_managed.cfg ansible-playbook ansible_managed_79129.yml -i ../../inventory -v "$@"
# Test for #42585
ANSIBLE_ROLES_PATH=../ ansible-playbook custom_template.yml -i ../../inventory -v "$@"
@@ -39,7 +42,7 @@ ansible-playbook 72262.yml -v "$@"
ansible-playbook unsafe.yml -v "$@"
# ensure Jinja2 overrides from a template are used
-ansible-playbook in_template_overrides.yml -v "$@"
+ansible-playbook template_overrides.yml -v "$@"
ansible-playbook lazy_eval.yml -i ../../inventory -v "$@"
diff --git a/test/integration/targets/template/tasks/main.yml b/test/integration/targets/template/tasks/main.yml
index 3c91734b..34e88287 100644
--- a/test/integration/targets/template/tasks/main.yml
+++ b/test/integration/targets/template/tasks/main.yml
@@ -25,7 +25,7 @@
- name: show jinja2 version
debug:
- msg: "{{ lookup('pipe', '{{ ansible_python[\"executable\"] }} -c \"import jinja2; print(jinja2.__version__)\"') }}"
+ msg: "{{ lookup('pipe', ansible_python.executable ~ ' -c \"import jinja2; print(jinja2.__version__)\"') }}"
- name: get default group
shell: id -gn
@@ -760,7 +760,7 @@
that:
- test
vars:
- test: "{{ lookup('file', '{{ output_dir }}/empty_template.templated')|length == 0 }}"
+ test: "{{ lookup('file', output_dir ~ '/empty_template.templated')|length == 0 }}"
- name: test jinja2 override without colon throws proper error
block:
diff --git a/test/integration/targets/template/unsafe.yml b/test/integration/targets/template/unsafe.yml
index bef9a4b4..6f163881 100644
--- a/test/integration/targets/template/unsafe.yml
+++ b/test/integration/targets/template/unsafe.yml
@@ -3,6 +3,7 @@
vars:
nottemplated: this should not be seen
imunsafe: !unsafe '{{ nottemplated }}'
+ unsafe_set: !unsafe '{{ "test" }}'
tasks:
- set_fact:
@@ -12,11 +13,15 @@
- set_fact:
this_always_safe: '{{ imunsafe }}'
+ - set_fact:
+ this_unsafe_set: "{{ unsafe_set }}"
+
- name: ensure nothing was templated
assert:
that:
- this_always_safe == imunsafe
- imunsafe == this_was_unsafe.strip()
+ - unsafe_set == this_unsafe_set.strip()
- hosts: localhost
diff --git a/test/integration/targets/template_jinja2_non_native/macro_override.yml b/test/integration/targets/template_jinja2_non_native/macro_override.yml
index 8a1cabd2..c3f9ab69 100644
--- a/test/integration/targets/template_jinja2_non_native/macro_override.yml
+++ b/test/integration/targets/template_jinja2_non_native/macro_override.yml
@@ -12,4 +12,4 @@
- "'foobar' not in data"
- "'\"foo\" \"bar\"' in data"
vars:
- data: "{{ lookup('file', '{{ output_dir }}/macro_override.out') }}"
+ data: "{{ lookup('file', output_dir ~ '/macro_override.out') }}"
diff --git a/test/integration/targets/templating/tasks/main.yml b/test/integration/targets/templating/tasks/main.yml
index 312e171d..edbf012e 100644
--- a/test/integration/targets/templating/tasks/main.yml
+++ b/test/integration/targets/templating/tasks/main.yml
@@ -33,3 +33,14 @@
- result is failed
- >-
"TemplateSyntaxError: Could not load \"asdf \": 'invalid plugin name: ansible.builtin.asdf '" in result.msg
+
+- name: Make sure syntax errors originating from a template being compiled into Python code object result in a failure
+ debug:
+ msg: "{{ lookup('vars', 'v1', default='', default='') }}"
+ ignore_errors: true
+ register: r
+
+- assert:
+ that:
+ - r is failed
+ - "'keyword argument repeated' in r.msg"
diff --git a/test/integration/targets/test_core/tasks/main.yml b/test/integration/targets/test_core/tasks/main.yml
index 8c2decbd..ac06d67e 100644
--- a/test/integration/targets/test_core/tasks/main.yml
+++ b/test/integration/targets/test_core/tasks/main.yml
@@ -126,6 +126,16 @@
hello: world
register: executed_task
+- name: Skip me with multiple conditions
+ set_fact:
+ hello: world
+ when:
+ - True == True
+ - foo == 'bar'
+ vars:
+ foo: foo
+ register: skipped_task_multi_condition
+
- name: Try skipped test on non-dictionary
set_fact:
hello: "{{ 'nope' is skipped }}"
@@ -136,8 +146,11 @@
assert:
that:
- skipped_task is skipped
+ - skipped_task.false_condition == False
- executed_task is not skipped
- misuse_of_skipped is failure
+ - skipped_task_multi_condition is skipped
+ - skipped_task_multi_condition.false_condition == "foo == 'bar'"
- name: Not an async task
set_fact:
diff --git a/test/integration/targets/unarchive/tasks/main.yml b/test/integration/targets/unarchive/tasks/main.yml
index 148e583f..b07c2fe7 100644
--- a/test/integration/targets/unarchive/tasks/main.yml
+++ b/test/integration/targets/unarchive/tasks/main.yml
@@ -20,3 +20,4 @@
- import_tasks: test_different_language_var.yml
- import_tasks: test_invalid_options.yml
- import_tasks: test_ownership_top_folder.yml
+- import_tasks: test_relative_dest.yml
diff --git a/test/integration/targets/unarchive/tasks/test_different_language_var.yml b/test/integration/targets/unarchive/tasks/test_different_language_var.yml
index 9eec658e..32c84f4b 100644
--- a/test/integration/targets/unarchive/tasks/test_different_language_var.yml
+++ b/test/integration/targets/unarchive/tasks/test_different_language_var.yml
@@ -2,10 +2,10 @@
when: ansible_os_family == 'Debian'
block:
- name: install fr language pack
- apt:
+ apt:
name: language-pack-fr
state: present
-
+
- name: create our unarchive destination
file:
path: "{{ remote_tmp_dir }}/test-unarchive-nonascii-くらとみ-tar-gz"
diff --git a/test/integration/targets/unarchive/tasks/test_mode.yml b/test/integration/targets/unarchive/tasks/test_mode.yml
index 06fbc7b8..efd428eb 100644
--- a/test/integration/targets/unarchive/tasks/test_mode.yml
+++ b/test/integration/targets/unarchive/tasks/test_mode.yml
@@ -3,6 +3,29 @@
path: '{{remote_tmp_dir}}/test-unarchive-tar-gz'
state: directory
+- name: test invalid modes
+ unarchive:
+ src: "{{ remote_tmp_dir }}/test-unarchive.tar.gz"
+ dest: "{{ remote_tmp_dir }}/test-unarchive-tar-gz"
+ remote_src: yes
+ mode: "{{ item }}"
+ list_files: True
+ register: unarchive_mode_errors
+ ignore_errors: yes
+ loop:
+ - u=foo
+ - foo=r
+ - ufoo=r
+ - abc=r
+ - ao=r
+ - oa=r
+
+- assert:
+ that:
+ - item.failed
+ - "'bad symbolic permission for mode: ' + item.item == item.details"
+ loop: "{{ unarchive_mode_errors.results }}"
+
- name: unarchive and set mode to 0600, directories 0700
unarchive:
src: "{{ remote_tmp_dir }}/test-unarchive.tar.gz"
diff --git a/test/integration/targets/unsafe_writes/aliases b/test/integration/targets/unsafe_writes/aliases
index da1b554e..3560af2f 100644
--- a/test/integration/targets/unsafe_writes/aliases
+++ b/test/integration/targets/unsafe_writes/aliases
@@ -1,7 +1,6 @@
context/target
needs/root
skip/freebsd
-skip/osx
skip/macos
shippable/posix/group2
needs/target/setup_remote_tmp_dir
diff --git a/test/integration/targets/until/tasks/main.yml b/test/integration/targets/until/tasks/main.yml
index 2b2ac94e..42ce9c8f 100644
--- a/test/integration/targets/until/tasks/main.yml
+++ b/test/integration/targets/until/tasks/main.yml
@@ -82,3 +82,37 @@
register: counter
delay: 0.5
until: counter.rc == 0
+
+- name: test retries without explicit until, defaults to "until task succeeds"
+ block:
+ - name: EXPECTED FAILURE
+ fail:
+ retries: 3
+ delay: 0.1
+ register: r
+ ignore_errors: true
+
+ - assert:
+ that:
+ - r.attempts == 3
+
+ - vars:
+ test_file: "{{ lookup('env', 'OUTPUT_DIR') }}/until_success_test_file"
+ block:
+ - file:
+ name: "{{ test_file }}"
+ state: absent
+
+ - name: fail on the first invocation, succeed on the second
+ shell: "[ -f {{ test_file }} ] || (touch {{ test_file }} && false)"
+ retries: 5
+ delay: 0.1
+ register: r
+ always:
+ - file:
+ name: "{{ test_file }}"
+ state: absent
+
+ - assert:
+ that:
+ - r.attempts == 2
diff --git a/test/integration/targets/unvault/main.yml b/test/integration/targets/unvault/main.yml
index a0f97b4b..8f0adc75 100644
--- a/test/integration/targets/unvault/main.yml
+++ b/test/integration/targets/unvault/main.yml
@@ -1,4 +1,5 @@
- hosts: localhost
+ gather_facts: false
tasks:
- set_fact:
unvaulted: "{{ lookup('unvault', 'vault') }}"
diff --git a/test/integration/targets/unvault/runme.sh b/test/integration/targets/unvault/runme.sh
index df4585e3..054a14df 100755
--- a/test/integration/targets/unvault/runme.sh
+++ b/test/integration/targets/unvault/runme.sh
@@ -2,5 +2,5 @@
set -eux
-
+# simple run
ansible-playbook --vault-password-file password main.yml
diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml
index 9ba09ece..ddae83a0 100644
--- a/test/integration/targets/uri/tasks/main.yml
+++ b/test/integration/targets/uri/tasks/main.yml
@@ -132,7 +132,7 @@
- "result.changed == true"
- name: "get ca certificate {{ self_signed_host }}"
- get_url:
+ uri:
url: "http://{{ httpbin_host }}/ca2cert.pem"
dest: "{{ remote_tmp_dir }}/ca2cert.pem"
@@ -638,9 +638,18 @@
- assert:
that:
- result['set_cookie'] == 'Foo=bar, Baz=qux'
- # Python sorts cookies in order of most specific (ie. longest) path first
+ # Python 3.10 and earlier sorts cookies in order of most specific (ie. longest) path first
# items with the same path are reversed from response order
- result['cookies_string'] == 'Baz=qux; Foo=bar'
+ when: ansible_python_version is version('3.11', '<')
+
+- assert:
+ that:
+ - result['set_cookie'] == 'Foo=bar, Baz=qux'
+ # Python 3.11 no longer sorts cookies.
+ # See: https://github.com/python/cpython/issues/86232
+ - result['cookies_string'] == 'Foo=bar; Baz=qux'
+ when: ansible_python_version is version('3.11', '>=')
- name: Write out netrc template
template:
@@ -757,6 +766,30 @@
dest: "{{ remote_tmp_dir }}/output"
state: absent
+- name: Test download root to dir without content-disposition
+ uri:
+ url: "https://{{ httpbin_host }}/"
+ dest: "{{ remote_tmp_dir }}"
+ register: get_root_no_filename
+
+- name: Test downloading to dir without content-disposition
+ uri:
+ url: "https://{{ httpbin_host }}/response-headers"
+ dest: "{{ remote_tmp_dir }}"
+ register: get_dir_no_filename
+
+- name: Test downloading to dir with content-disposition
+ uri:
+ url: 'https://{{ httpbin_host }}/response-headers?Content-Disposition=attachment%3B%20filename%3D%22filename.json%22'
+ dest: "{{ remote_tmp_dir }}"
+ register: get_dir_filename
+
+- assert:
+ that:
+ - get_root_no_filename.path == remote_tmp_dir ~ "/index.html"
+ - get_dir_no_filename.path == remote_tmp_dir ~ "/response-headers"
+ - get_dir_filename.path == remote_tmp_dir ~ "/filename.json"
+
- name: Test follow_redirects=none
import_tasks: redirect-none.yml
diff --git a/test/integration/targets/uri/tasks/redirect-none.yml b/test/integration/targets/uri/tasks/redirect-none.yml
index 0d1b2b34..060950d2 100644
--- a/test/integration/targets/uri/tasks/redirect-none.yml
+++ b/test/integration/targets/uri/tasks/redirect-none.yml
@@ -240,7 +240,7 @@
url: https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything
follow_redirects: none
return_content: yes
- method: GET
+ method: HEAD
ignore_errors: yes
register: http_308_head
diff --git a/test/integration/targets/uri/tasks/redirect-urllib2.yml b/test/integration/targets/uri/tasks/redirect-urllib2.yml
index 6cdafdb2..73e87960 100644
--- a/test/integration/targets/uri/tasks/redirect-urllib2.yml
+++ b/test/integration/targets/uri/tasks/redirect-urllib2.yml
@@ -237,7 +237,7 @@
url: https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything
follow_redirects: urllib2
return_content: yes
- method: GET
+ method: HEAD
ignore_errors: yes
register: http_308_head
@@ -250,6 +250,23 @@
- http_308_head.redirected == false
- http_308_head.status == 308
- http_308_head.url == 'https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything'
+ # Python 3.10 and earlier do not support HTTP 308 responses.
+ # See: https://github.com/python/cpython/issues/84501
+ when: ansible_python_version is version('3.11', '<')
+
+# NOTE: The HTTP HEAD turns into an HTTP GET
+- assert:
+ that:
+ - http_308_head is successful
+ - http_308_head.json.data == ''
+ - http_308_head.json.method == 'GET'
+ - http_308_head.json.url == 'https://{{ httpbin_host }}/anything'
+ - http_308_head.redirected == true
+ - http_308_head.status == 200
+ - http_308_head.url == 'https://{{ httpbin_host }}/anything'
+ # Python 3.11 introduced support for HTTP 308 responses.
+ # See: https://github.com/python/cpython/issues/84501
+ when: ansible_python_version is version('3.11', '>=')
# FIXME: This is fixed in https://github.com/ansible/ansible/pull/36809
- name: Test HTTP 308 using GET
@@ -270,6 +287,22 @@
- http_308_get.redirected == false
- http_308_get.status == 308
- http_308_get.url == 'https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything'
+ # Python 3.10 and earlier do not support HTTP 308 responses.
+ # See: https://github.com/python/cpython/issues/84501
+ when: ansible_python_version is version('3.11', '<')
+
+- assert:
+ that:
+ - http_308_get is successful
+ - http_308_get.json.data == ''
+ - http_308_get.json.method == 'GET'
+ - http_308_get.json.url == 'https://{{ httpbin_host }}/anything'
+ - http_308_get.redirected == true
+ - http_308_get.status == 200
+ - http_308_get.url == 'https://{{ httpbin_host }}/anything'
+ # Python 3.11 introduced support for HTTP 308 responses.
+ # See: https://github.com/python/cpython/issues/84501
+ when: ansible_python_version is version('3.11', '>=')
# FIXME: This is fixed in https://github.com/ansible/ansible/pull/36809
- name: Test HTTP 308 using POST
diff --git a/test/integration/targets/uri/tasks/return-content.yml b/test/integration/targets/uri/tasks/return-content.yml
index 5a9b97e6..cb8aeea2 100644
--- a/test/integration/targets/uri/tasks/return-content.yml
+++ b/test/integration/targets/uri/tasks/return-content.yml
@@ -46,4 +46,4 @@
assert:
that:
- result is failed
- - "'content' not in result" \ No newline at end of file
+ - "'content' not in result"
diff --git a/test/integration/targets/uri/tasks/use_netrc.yml b/test/integration/targets/uri/tasks/use_netrc.yml
index da745b89..521f8ebf 100644
--- a/test/integration/targets/uri/tasks/use_netrc.yml
+++ b/test/integration/targets/uri/tasks/use_netrc.yml
@@ -48,4 +48,4 @@
- name: Clean up
file:
dest: "{{ remote_tmp_dir }}/netrc"
- state: absent \ No newline at end of file
+ state: absent
diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml
index 9d36bfca..be4c4d6f 100644
--- a/test/integration/targets/user/tasks/main.yml
+++ b/test/integration/targets/user/tasks/main.yml
@@ -31,7 +31,9 @@
- import_tasks: test_expires.yml
- import_tasks: test_expires_new_account.yml
- import_tasks: test_expires_new_account_epoch_negative.yml
+- import_tasks: test_expires_no_shadow.yml
- import_tasks: test_expires_min_max.yml
+- import_tasks: test_expires_warn.yml
- import_tasks: test_shadow_backup.yml
- import_tasks: test_ssh_key_passphrase.yml
- import_tasks: test_password_lock.yml
diff --git a/test/integration/targets/user/tasks/test_create_user.yml b/test/integration/targets/user/tasks/test_create_user.yml
index bced7905..644dbebb 100644
--- a/test/integration/targets/user/tasks/test_create_user.yml
+++ b/test/integration/targets/user/tasks/test_create_user.yml
@@ -65,3 +65,15 @@
- "user_test1.results[2]['state'] == 'present'"
- "user_test1.results[3]['state'] == 'present'"
- "user_test1.results[4]['state'] == 'present'"
+
+- name: register user informations
+ when: ansible_facts.system == 'Darwin'
+ command: dscl . -read /Users/ansibulluser
+ register: user_test2
+
+- name: validate user defaults for MacOS
+ when: ansible_facts.system == 'Darwin'
+ assert:
+ that:
+ - "'RealName: ansibulluser' in user_test2.stdout_lines "
+ - "'PrimaryGroupID: 20' in user_test2.stdout_lines "
diff --git a/test/integration/targets/user/tasks/test_create_user_home.yml b/test/integration/targets/user/tasks/test_create_user_home.yml
index 1b529f76..5561a2f5 100644
--- a/test/integration/targets/user/tasks/test_create_user_home.yml
+++ b/test/integration/targets/user/tasks/test_create_user_home.yml
@@ -134,3 +134,21 @@
name: randomuser
state: absent
remove: yes
+
+- name: Create user home directory with /dev/null as skeleton, https://github.com/ansible/ansible/issues/75063
+ # create_homedir is mostly used by linux, rest of OSs take care of it themselves via -k option (which fails this task)
+ when: ansible_system == 'Linux'
+ block:
+ - name: "Create user home directory with /dev/null as skeleton"
+ user:
+ name: withskeleton
+ state: present
+ skeleton: "/dev/null"
+ createhome: yes
+ register: create_user_with_skeleton_dev_null
+ always:
+ - name: "Remove test user"
+ user:
+ name: withskeleton
+ state: absent
+ remove: yes
diff --git a/test/integration/targets/user/tasks/test_local.yml b/test/integration/targets/user/tasks/test_local.yml
index 67c24a21..217d4769 100644
--- a/test/integration/targets/user/tasks/test_local.yml
+++ b/test/integration/targets/user/tasks/test_local.yml
@@ -86,9 +86,11 @@
- testgroup3
- testgroup4
- testgroup5
+ - testgroup6
- local_ansibulluser
tags:
- user_test_local_mode
+ register: test_groups
- name: Create local_ansibulluser with groups
user:
@@ -113,6 +115,18 @@
tags:
- user_test_local_mode
+- name: Append groups for local_ansibulluser (again)
+ user:
+ name: local_ansibulluser
+ state: present
+ local: yes
+ groups: ['testgroup3', 'testgroup4']
+ append: yes
+ register: local_user_test_4_again
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
- name: Test append without groups for local_ansibulluser
user:
name: local_ansibulluser
@@ -133,6 +147,28 @@
tags:
- user_test_local_mode
+- name: Append groups for local_ansibulluser using group id
+ user:
+ name: local_ansibulluser
+ state: present
+ append: yes
+ groups: "{{ test_groups.results[5]['gid'] }}"
+ register: local_user_test_7
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
+- name: Append groups for local_ansibulluser using gid (again)
+ user:
+ name: local_ansibulluser
+ state: present
+ append: yes
+ groups: "{{ test_groups.results[5]['gid'] }}"
+ register: local_user_test_7_again
+ ignore_errors: yes
+ tags:
+ - user_test_local_mode
+
# If we don't re-assign, then "Set user expiration" will
# fail.
- name: Re-assign named group for local_ansibulluser
@@ -164,6 +200,7 @@
- testgroup3
- testgroup4
- testgroup5
+ - testgroup6
- local_ansibulluser
tags:
- user_test_local_mode
@@ -175,7 +212,10 @@
- local_user_test_2 is not changed
- local_user_test_3 is changed
- local_user_test_4 is changed
+ - local_user_test_4_again is not changed
- local_user_test_6 is changed
+ - local_user_test_7 is changed
+ - local_user_test_7_again is not changed
- local_user_test_remove_1 is changed
- local_user_test_remove_2 is not changed
tags:
diff --git a/test/integration/targets/user/vars/main.yml b/test/integration/targets/user/vars/main.yml
index 4b328f71..2acd1e12 100644
--- a/test/integration/targets/user/vars/main.yml
+++ b/test/integration/targets/user/vars/main.yml
@@ -10,4 +10,4 @@ status_command:
default_user_group:
openSUSE Leap: users
- MacOSX: admin
+ MacOSX: staff
diff --git a/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml b/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml
index f2b2e54a..ef2a06e1 100644
--- a/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml
+++ b/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml
@@ -1,4 +1,4 @@
-# test code
+# test code
# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
# This file is part of Ansible
@@ -22,7 +22,7 @@
output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}"
- name: deploy a template that will use variables at various levels
- template: src=foo.j2 dest={{output_dir}}/foo.templated
+ template: src=foo.j2 dest={{output_dir}}/foo.templated
register: template_result
- name: copy known good into place
@@ -33,9 +33,9 @@
register: diff_result
- name: verify templated file matches known good
- assert:
- that:
- - 'diff_result.stdout == ""'
+ assert:
+ that:
+ - 'diff_result.stdout == ""'
- name: check debug variable with same name as var content
debug: var=same_value_as_var_name_var
diff --git a/test/integration/targets/var_precedence/ansible-var-precedence-check.py b/test/integration/targets/var_precedence/ansible-var-precedence-check.py
index fc31688b..b03c87b8 100755
--- a/test/integration/targets/var_precedence/ansible-var-precedence-check.py
+++ b/test/integration/targets/var_precedence/ansible-var-precedence-check.py
@@ -14,7 +14,6 @@ import stat
import subprocess
import tempfile
import yaml
-from pprint import pprint
from optparse import OptionParser
from jinja2 import Environment
@@ -364,9 +363,9 @@ class VarTestMaker(object):
block_wrapper = [debug_task, test_task]
if 'include_params' in self.features:
- self.tasks.append(dict(name='including tasks', include='included_tasks.yml', vars=dict(findme='include_params')))
+ self.tasks.append(dict(name='including tasks', include_tasks='included_tasks.yml', vars=dict(findme='include_params')))
else:
- self.tasks.append(dict(include='included_tasks.yml'))
+ self.tasks.append(dict(include_tasks='included_tasks.yml'))
fname = os.path.join(TESTDIR, 'included_tasks.yml')
with open(fname, 'w') as f:
diff --git a/test/integration/targets/var_precedence/test_var_precedence.yml b/test/integration/targets/var_precedence/test_var_precedence.yml
index 58584bfb..bba661db 100644
--- a/test/integration/targets/var_precedence/test_var_precedence.yml
+++ b/test/integration/targets/var_precedence/test_var_precedence.yml
@@ -1,14 +1,18 @@
---
- hosts: testhost
vars:
- - ansible_hostname: "BAD!"
- - vars_var: "vars_var"
- - param_var: "BAD!"
- - vars_files_var: "BAD!"
- - extra_var_override_once_removed: "{{ extra_var_override }}"
- - from_inventory_once_removed: "{{ inven_var | default('BAD!') }}"
+ ansible_hostname: "BAD!"
+ vars_var: "vars_var"
+ param_var: "BAD!"
+ vars_files_var: "BAD!"
+ extra_var_override_once_removed: "{{ extra_var_override }}"
+ from_inventory_once_removed: "{{ inven_var | default('BAD!') }}"
vars_files:
- vars/test_var_precedence.yml
+ pre_tasks:
+ - name: param vars should also override set_fact
+ set_fact:
+ param_var: "BAD!"
roles:
- { role: test_var_precedence, param_var: "param_var" }
tasks:
diff --git a/test/integration/targets/wait_for/tasks/main.yml b/test/integration/targets/wait_for/tasks/main.yml
index f81fd0f2..74b8e9aa 100644
--- a/test/integration/targets/wait_for/tasks/main.yml
+++ b/test/integration/targets/wait_for/tasks/main.yml
@@ -91,7 +91,7 @@
wait_for:
path: "{{remote_tmp_dir}}/wait_for_keyword"
search_regex: completed (?P<foo>\w+) ([0-9]+)
- timeout: 5
+ timeout: 25
register: waitfor
- name: verify test wait for keyword in file with match groups
@@ -114,6 +114,15 @@
path: "{{remote_tmp_dir}}/utf16.txt"
search_regex: completed
+- name: test non mmapable file
+ wait_for:
+ path: "/sys/class/net/lo/carrier"
+ search_regex: "1"
+ timeout: 30
+ when:
+ - ansible_facts['os_family'] not in ['FreeBSD', 'Darwin']
+ - not (ansible_facts['os_family'] in ['RedHat', 'CentOS'] and ansible_facts['distribution_major_version'] is version('7', '<='))
+
- name: test wait for port timeout
wait_for:
port: 12121
diff --git a/test/integration/targets/win_exec_wrapper/tasks/main.yml b/test/integration/targets/win_exec_wrapper/tasks/main.yml
index 8fc54f7c..f1342c48 100644
--- a/test/integration/targets/win_exec_wrapper/tasks/main.yml
+++ b/test/integration/targets/win_exec_wrapper/tasks/main.yml
@@ -272,3 +272,12 @@
assert:
that:
- ps_log_count.stdout | int == 0
+
+- name: test module that sets HadErrors with no error records
+ test_rc_1:
+ register: module_had_errors
+
+- name: assert test module that sets HadErrors with no error records
+ assert:
+ that:
+ - module_had_errors.rc == 0
diff --git a/test/integration/targets/win_fetch/tasks/main.yml b/test/integration/targets/win_fetch/tasks/main.yml
index b5818352..16a28761 100644
--- a/test/integration/targets/win_fetch/tasks/main.yml
+++ b/test/integration/targets/win_fetch/tasks/main.yml
@@ -215,3 +215,17 @@
- fetch_special_file.checksum == '34d4150adc3347f1dd8ce19fdf65b74d971ab602'
- fetch_special_file.dest == host_output_dir + "/abc$not var'quote‘"
- fetch_special_file_actual.stdout == 'abc'
+
+- name: create file with wildcard characters
+ raw: Set-Content -LiteralPath '{{ remote_tmp_dir }}\abc[].txt' -Value 'abc'
+
+- name: fetch file with wildcard characters
+ fetch:
+ src: '{{ remote_tmp_dir }}\abc[].txt'
+ dest: '{{ host_output_dir }}/'
+ register: fetch_wildcard_file_nofail
+
+- name: assert fetch file with wildcard characters
+ assert:
+ that:
+ - "fetch_wildcard_file_nofail is not failed"
diff --git a/test/integration/targets/win_script/files/test_script_with_args.ps1 b/test/integration/targets/win_script/files/test_script_with_args.ps1
index 01bb37f5..669c6410 100644
--- a/test/integration/targets/win_script/files/test_script_with_args.ps1
+++ b/test/integration/targets/win_script/files/test_script_with_args.ps1
@@ -2,5 +2,5 @@
# passed to the script.
foreach ($i in $args) {
- Write-Host $i;
+ Write-Host $i
}
diff --git a/test/integration/targets/win_script/files/test_script_with_errors.ps1 b/test/integration/targets/win_script/files/test_script_with_errors.ps1
index 56f97735..bdf7ee48 100644
--- a/test/integration/targets/win_script/files/test_script_with_errors.ps1
+++ b/test/integration/targets/win_script/files/test_script_with_errors.ps1
@@ -2,7 +2,7 @@
trap {
Write-Error -ErrorRecord $_
- exit 1;
+ exit 1
}
throw "Oh noes I has an error"
diff --git a/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1 b/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1
index f1704964..d23bbc74 100644
--- a/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1
+++ b/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1
@@ -16,16 +16,16 @@
# POWERSHELL_COMMON
-$params = Parse-Args $args $true;
+$params = Parse-Args $args $true
-$data = Get-Attr $params "data" "pong";
+$data = Get-Attr $params "data" "pong"
$result = @{
changed = $false
ping = "pong"
-};
+}
# Test that Set-Attr will replace an existing attribute.
Set-Attr $result "ping" $data
-Exit-Json $result;
+Exit-Json $result
diff --git a/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1 b/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1
index 508174af..09400d08 100644
--- a/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1
+++ b/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1
@@ -16,15 +16,15 @@
# POWERSHELL_COMMON
-$params = Parse-Args $args $true;
+$params = Parse-Args $args $true
$params.thisPropertyDoesNotExist
-$data = Get-Attr $params "data" "pong";
+$data = Get-Attr $params "data" "pong"
$result = @{
changed = $false
ping = $data
-};
+}
-Exit-Json $result;
+Exit-Json $result
diff --git a/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1 b/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1
index d4c9f07a..6932d538 100644
--- a/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1
+++ b/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1
@@ -18,13 +18,13 @@
$blah = 'I can't quote my strings correctly.'
-$params = Parse-Args $args $true;
+$params = Parse-Args $args $true
-$data = Get-Attr $params "data" "pong";
+$data = Get-Attr $params "data" "pong"
$result = @{
changed = $false
ping = $data
-};
+}
-Exit-Json $result;
+Exit-Json $result
diff --git a/test/integration/targets/windows-minimal/library/win_ping_throw.ps1 b/test/integration/targets/windows-minimal/library/win_ping_throw.ps1
index 7306f4d2..2fba2092 100644
--- a/test/integration/targets/windows-minimal/library/win_ping_throw.ps1
+++ b/test/integration/targets/windows-minimal/library/win_ping_throw.ps1
@@ -18,13 +18,13 @@
throw
-$params = Parse-Args $args $true;
+$params = Parse-Args $args $true
-$data = Get-Attr $params "data" "pong";
+$data = Get-Attr $params "data" "pong"
$result = @{
changed = $false
ping = $data
-};
+}
-Exit-Json $result;
+Exit-Json $result
diff --git a/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1 b/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1
index 09e3b7cb..62de8263 100644
--- a/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1
+++ b/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1
@@ -18,13 +18,13 @@
throw "no ping for you"
-$params = Parse-Args $args $true;
+$params = Parse-Args $args $true
-$data = Get-Attr $params "data" "pong";
+$data = Get-Attr $params "data" "pong"
$result = @{
changed = $false
ping = $data
-};
+}
-Exit-Json $result;
+Exit-Json $result
diff --git a/test/integration/targets/yum/aliases b/test/integration/targets/yum/aliases
index 1d491339..b12f3547 100644
--- a/test/integration/targets/yum/aliases
+++ b/test/integration/targets/yum/aliases
@@ -1,5 +1,4 @@
destructive
shippable/posix/group1
skip/freebsd
-skip/osx
skip/macos
diff --git a/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py b/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py
index 27f38ce5..306ccd9a 100644
--- a/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py
+++ b/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py
@@ -1,8 +1,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.errors import AnsibleError, AnsibleFilterError
-
def filter_list_of_tuples_by_first_param(lst, search, startswith=False):
out = []
diff --git a/test/lib/ansible_test/_data/completion/docker.txt b/test/lib/ansible_test/_data/completion/docker.txt
index 9e1a9d5e..a863ecbf 100644
--- a/test/lib/ansible_test/_data/completion/docker.txt
+++ b/test/lib/ansible_test/_data/completion/docker.txt
@@ -1,9 +1,9 @@
-base image=quay.io/ansible/base-test-container:3.9.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10
-default image=quay.io/ansible/default-test-container:6.13.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10 context=collection
-default image=quay.io/ansible/ansible-core-test-container:6.13.0 python=3.11,2.7,3.5,3.6,3.7,3.8,3.9,3.10 context=ansible-core
-alpine3 image=quay.io/ansible/alpine3-test-container:4.8.0 python=3.10 cgroup=none audit=none
-centos7 image=quay.io/ansible/centos7-test-container:4.8.0 python=2.7 cgroup=v1-only
-fedora36 image=quay.io/ansible/fedora36-test-container:4.8.0 python=3.10
-opensuse15 image=quay.io/ansible/opensuse15-test-container:4.8.0 python=3.6
-ubuntu2004 image=quay.io/ansible/ubuntu2004-test-container:4.8.0 python=3.8
-ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:4.8.0 python=3.10
+base image=quay.io/ansible/base-test-container:5.10.0 python=3.12,2.7,3.6,3.7,3.8,3.9,3.10,3.11
+default image=quay.io/ansible/default-test-container:8.12.0 python=3.12,2.7,3.6,3.7,3.8,3.9,3.10,3.11 context=collection
+default image=quay.io/ansible/ansible-core-test-container:8.12.0 python=3.12,2.7,3.6,3.7,3.8,3.9,3.10,3.11 context=ansible-core
+alpine3 image=quay.io/ansible/alpine3-test-container:6.3.0 python=3.11 cgroup=none audit=none
+centos7 image=quay.io/ansible/centos7-test-container:6.3.0 python=2.7 cgroup=v1-only
+fedora38 image=quay.io/ansible/fedora38-test-container:6.3.0 python=3.11
+opensuse15 image=quay.io/ansible/opensuse15-test-container:6.3.0 python=3.6
+ubuntu2004 image=quay.io/ansible/ubuntu2004-test-container:6.3.0 python=3.8
+ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:6.3.0 python=3.10
diff --git a/test/lib/ansible_test/_data/completion/remote.txt b/test/lib/ansible_test/_data/completion/remote.txt
index 9cb8dee8..06d4b5ef 100644
--- a/test/lib/ansible_test/_data/completion/remote.txt
+++ b/test/lib/ansible_test/_data/completion/remote.txt
@@ -1,16 +1,14 @@
-alpine/3.16 python=3.10 become=doas_sudo provider=aws arch=x86_64
+alpine/3.18 python=3.11 become=doas_sudo provider=aws arch=x86_64
alpine become=doas_sudo provider=aws arch=x86_64
-fedora/36 python=3.10 become=sudo provider=aws arch=x86_64
+fedora/38 python=3.11 become=sudo provider=aws arch=x86_64
fedora become=sudo provider=aws arch=x86_64
-freebsd/12.4 python=3.9 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
-freebsd/13.2 python=3.8,3.7,3.9,3.10 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
+freebsd/13.2 python=3.9,3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
-macos/12.0 python=3.10 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
+macos/13.2 python=3.11 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
rhel/7.9 python=2.7 become=sudo provider=aws arch=x86_64
-rhel/8.6 python=3.6,3.8,3.9 become=sudo provider=aws arch=x86_64
-rhel/9.0 python=3.9 become=sudo provider=aws arch=x86_64
+rhel/8.8 python=3.6,3.11 become=sudo provider=aws arch=x86_64
+rhel/9.2 python=3.9,3.11 become=sudo provider=aws arch=x86_64
rhel become=sudo provider=aws arch=x86_64
-ubuntu/20.04 python=3.8,3.9 become=sudo provider=aws arch=x86_64
ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64
ubuntu become=sudo provider=aws arch=x86_64
diff --git a/test/lib/ansible_test/_data/completion/windows.txt b/test/lib/ansible_test/_data/completion/windows.txt
index 92b0d086..860a2e32 100644
--- a/test/lib/ansible_test/_data/completion/windows.txt
+++ b/test/lib/ansible_test/_data/completion/windows.txt
@@ -1,5 +1,3 @@
-windows/2012 provider=azure arch=x86_64
-windows/2012-R2 provider=azure arch=x86_64
windows/2016 provider=aws arch=x86_64
windows/2019 provider=aws arch=x86_64
windows/2022 provider=aws arch=x86_64
diff --git a/test/lib/ansible_test/_data/requirements/ansible-test.txt b/test/lib/ansible_test/_data/requirements/ansible-test.txt
index f7cb9c27..17662f07 100644
--- a/test/lib/ansible_test/_data/requirements/ansible-test.txt
+++ b/test/lib/ansible_test/_data/requirements/ansible-test.txt
@@ -1,4 +1,5 @@
# The test-constraints sanity test verifies this file, but changes must be made manually to keep it in up-to-date.
virtualenv == 16.7.12 ; python_version < '3'
-coverage == 6.5.0 ; python_version >= '3.7' and python_version <= '3.11'
+coverage == 7.3.2 ; python_version >= '3.8' and python_version <= '3.12'
+coverage == 6.5.0 ; python_version >= '3.7' and python_version <= '3.7'
coverage == 4.5.4 ; python_version >= '2.6' and python_version <= '3.6'
diff --git a/test/lib/ansible_test/_data/requirements/ansible.txt b/test/lib/ansible_test/_data/requirements/ansible.txt
index 20562c3e..5eaf9f2c 100644
--- a/test/lib/ansible_test/_data/requirements/ansible.txt
+++ b/test/lib/ansible_test/_data/requirements/ansible.txt
@@ -12,4 +12,4 @@ packaging
# NOTE: Ref: https://github.com/sarugaku/resolvelib/issues/69
# NOTE: When updating the upper bound, also update the latest version used
# NOTE: in the ansible-galaxy-collection test suite.
-resolvelib >= 0.5.3, < 0.9.0 # dependency resolver used by ansible-galaxy
+resolvelib >= 0.5.3, < 1.1.0 # dependency resolver used by ansible-galaxy
diff --git a/test/lib/ansible_test/_data/requirements/constraints.txt b/test/lib/ansible_test/_data/requirements/constraints.txt
index 627f41df..dd837e3b 100644
--- a/test/lib/ansible_test/_data/requirements/constraints.txt
+++ b/test/lib/ansible_test/_data/requirements/constraints.txt
@@ -5,7 +5,6 @@ pywinrm >= 0.3.0 ; python_version < '3.11' # message encryption support
pywinrm >= 0.4.3 ; python_version >= '3.11' # support for Python 3.11
pytest < 5.0.0, >= 4.5.0 ; python_version == '2.7' # pytest 5.0.0 and later will no longer support python 2.7
pytest >= 4.5.0 ; python_version > '2.7' # pytest 4.5.0 added support for --strict-markers
-pytest-forked >= 1.0.2 # pytest-forked before 1.0.2 does not work with pytest 4.2.0+
ntlm-auth >= 1.3.0 # message encryption support using cryptography
requests-ntlm >= 1.1.0 # message encryption support
requests-credssp >= 0.1.0 # message encryption support
@@ -13,5 +12,4 @@ pyparsing < 3.0.0 ; python_version < '3.5' # pyparsing 3 and later require pytho
mock >= 2.0.0 # needed for features backported from Python 3.6 unittest.mock (assert_called, assert_called_once...)
pytest-mock >= 1.4.0 # needed for mock_use_standalone_module pytest option
setuptools < 45 ; python_version == '2.7' # setuptools 45 and later require python 3.5 or later
-pyspnego >= 0.1.6 ; python_version >= '3.10' # bug in older releases breaks on Python 3.10
wheel < 0.38.0 ; python_version < '3.7' # wheel 0.38.0 and later require python 3.7 or later
diff --git a/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt b/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt
index 580f0641..66801459 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.ansible-doc.txt
@@ -1,8 +1,5 @@
# edit "sanity.ansible-doc.in" and generate with: hacking/update-sanity-requirements.py --test ansible-doc
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
Jinja2==3.1.2
-MarkupSafe==2.1.1
-packaging==21.3
-pyparsing==3.0.9
-PyYAML==6.0
+MarkupSafe==2.1.3
+packaging==23.2
+PyYAML==6.0.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.changelog.in b/test/lib/ansible_test/_data/requirements/sanity.changelog.in
index 7f231827..81d65ff8 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.changelog.in
+++ b/test/lib/ansible_test/_data/requirements/sanity.changelog.in
@@ -1,3 +1,2 @@
-rstcheck < 4 # match version used in other sanity tests
+rstcheck < 6 # newer versions have too many dependencies
antsibull-changelog
-docutils < 0.18 # match version required by sphinx in the docs-build sanity test
diff --git a/test/lib/ansible_test/_data/requirements/sanity.changelog.txt b/test/lib/ansible_test/_data/requirements/sanity.changelog.txt
index 1755a489..d763bad2 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.changelog.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.changelog.txt
@@ -1,10 +1,9 @@
# edit "sanity.changelog.in" and generate with: hacking/update-sanity-requirements.py --test changelog
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-antsibull-changelog==0.16.0
-docutils==0.17.1
-packaging==21.3
-pyparsing==3.0.9
-PyYAML==6.0
-rstcheck==3.5.0
+antsibull-changelog==0.23.0
+docutils==0.18.1
+packaging==23.2
+PyYAML==6.0.1
+rstcheck==5.0.0
semantic-version==2.10.0
+types-docutils==0.18.3
+typing_extensions==4.8.0
diff --git a/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt b/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt
index 93e147a5..56366b77 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.import.plugin.txt
@@ -1,6 +1,4 @@
# edit "sanity.import.plugin.in" and generate with: hacking/update-sanity-requirements.py --test import.plugin
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
Jinja2==3.1.2
-MarkupSafe==2.1.1
-PyYAML==6.0
+MarkupSafe==2.1.3
+PyYAML==6.0.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.import.txt b/test/lib/ansible_test/_data/requirements/sanity.import.txt
index 4fda120d..4d9d4f53 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.import.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.import.txt
@@ -1,4 +1,2 @@
# edit "sanity.import.in" and generate with: hacking/update-sanity-requirements.py --test import
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-PyYAML==6.0
+PyYAML==6.0.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt b/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt
index 51cc1ca3..17d60b6f 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.integration-aliases.txt
@@ -1,4 +1,2 @@
# edit "sanity.integration-aliases.in" and generate with: hacking/update-sanity-requirements.py --test integration-aliases
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-PyYAML==6.0
+PyYAML==6.0.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.mypy.in b/test/lib/ansible_test/_data/requirements/sanity.mypy.in
index 98dead6c..f01ae948 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.mypy.in
+++ b/test/lib/ansible_test/_data/requirements/sanity.mypy.in
@@ -1,10 +1,10 @@
-mypy[python2] != 0.971 # regression in 0.971 (see https://github.com/python/mypy/pull/13223)
+mypy
+cryptography # type stubs not published separately
+jinja2 # type stubs not published separately
packaging # type stubs not published separately
types-backports
-types-jinja2
-types-paramiko < 2.8.14 # newer versions drop support for Python 2.7
-types-pyyaml < 6 # PyYAML 6+ stubs do not support Python 2.7
-types-cryptography < 3.3.16 # newer versions drop support for Python 2.7
+types-paramiko
+types-pyyaml
types-requests
types-setuptools
types-toml
diff --git a/test/lib/ansible_test/_data/requirements/sanity.mypy.txt b/test/lib/ansible_test/_data/requirements/sanity.mypy.txt
index 9dffc8fb..f6a47fb0 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.mypy.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.mypy.txt
@@ -1,20 +1,18 @@
# edit "sanity.mypy.in" and generate with: hacking/update-sanity-requirements.py --test mypy
-mypy==0.961
-mypy-extensions==0.4.3
-packaging==21.3
-pyparsing==3.0.9
+cffi==1.16.0
+cryptography==41.0.4
+Jinja2==3.1.2
+MarkupSafe==2.1.3
+mypy==1.5.1
+mypy-extensions==1.0.0
+packaging==23.2
+pycparser==2.21
tomli==2.0.1
-typed-ast==1.5.4
types-backports==0.1.3
-types-cryptography==3.3.15
-types-enum34==1.1.8
-types-ipaddress==1.0.8
-types-Jinja2==2.11.9
-types-MarkupSafe==1.1.10
-types-paramiko==2.8.13
-types-PyYAML==5.4.12
-types-requests==2.28.10
-types-setuptools==65.3.0
-types-toml==0.10.8
-types-urllib3==1.26.24
-typing_extensions==4.3.0
+types-paramiko==3.3.0.0
+types-PyYAML==6.0.12.12
+types-requests==2.31.0.7
+types-setuptools==68.2.0.0
+types-toml==0.10.8.7
+typing_extensions==4.8.0
+urllib3==2.0.6
diff --git a/test/lib/ansible_test/_data/requirements/sanity.pep8.txt b/test/lib/ansible_test/_data/requirements/sanity.pep8.txt
index 60d5784f..1a36d4da 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.pep8.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.pep8.txt
@@ -1,2 +1,2 @@
# edit "sanity.pep8.in" and generate with: hacking/update-sanity-requirements.py --test pep8
-pycodestyle==2.9.1
+pycodestyle==2.11.0
diff --git a/test/lib/ansible_test/_data/requirements/sanity.pslint.ps1 b/test/lib/ansible_test/_data/requirements/sanity.pslint.ps1
index 68545c9e..df36d61a 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.pslint.ps1
+++ b/test/lib/ansible_test/_data/requirements/sanity.pslint.ps1
@@ -28,8 +28,10 @@ Function Install-PSModule {
}
}
+# Versions changes should be made first in ansible-test which is then synced to
+# the default-test-container over time
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
-Install-PSModule -Name PSScriptAnalyzer -RequiredVersion 1.20.0
+Install-PSModule -Name PSScriptAnalyzer -RequiredVersion 1.21.0
if ($IsContainer) {
# PSScriptAnalyzer contain lots of json files for the UseCompatibleCommands check. We don't use this rule so by
diff --git a/test/lib/ansible_test/_data/requirements/sanity.pylint.in b/test/lib/ansible_test/_data/requirements/sanity.pylint.in
index fde21f12..ae189587 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.pylint.in
+++ b/test/lib/ansible_test/_data/requirements/sanity.pylint.in
@@ -1,2 +1,2 @@
-pylint == 2.15.5 # currently vetted version
+pylint
pyyaml # needed for collection_detail.py
diff --git a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
index 44d8b88c..c3144fe5 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.pylint.txt
@@ -1,15 +1,11 @@
# edit "sanity.pylint.in" and generate with: hacking/update-sanity-requirements.py --test pylint
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-astroid==2.12.12
-dill==0.3.6
-isort==5.10.1
-lazy-object-proxy==1.7.1
+astroid==3.0.0
+dill==0.3.7
+isort==5.12.0
mccabe==0.7.0
-platformdirs==2.5.2
-pylint==2.15.5
-PyYAML==6.0
+platformdirs==3.11.0
+pylint==3.0.1
+PyYAML==6.0.1
tomli==2.0.1
-tomlkit==0.11.5
-typing_extensions==4.3.0
-wrapt==1.14.1
+tomlkit==0.12.1
+typing_extensions==4.8.0
diff --git a/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt b/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt
index b2b70567..4af9b95e 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.runtime-metadata.txt
@@ -1,5 +1,3 @@
# edit "sanity.runtime-metadata.in" and generate with: hacking/update-sanity-requirements.py --test runtime-metadata
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-PyYAML==6.0
+PyYAML==6.0.1
voluptuous==0.13.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.in b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.in
index efe94004..78e116f5 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.in
+++ b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.in
@@ -1,3 +1,4 @@
jinja2 # ansible-core requirement
pyyaml # needed for collection_detail.py
voluptuous
+antsibull-docs-parser==1.0.0
diff --git a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt
index 8a877bba..4e24d64d 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.validate-modules.txt
@@ -1,7 +1,6 @@
# edit "sanity.validate-modules.in" and generate with: hacking/update-sanity-requirements.py --test validate-modules
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
+antsibull-docs-parser==1.0.0
Jinja2==3.1.2
-MarkupSafe==2.1.1
-PyYAML==6.0
+MarkupSafe==2.1.3
+PyYAML==6.0.1
voluptuous==0.13.1
diff --git a/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt b/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt
index dd401113..bafd30b6 100644
--- a/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt
+++ b/test/lib/ansible_test/_data/requirements/sanity.yamllint.txt
@@ -1,6 +1,4 @@
# edit "sanity.yamllint.in" and generate with: hacking/update-sanity-requirements.py --test yamllint
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-pathspec==0.10.1
-PyYAML==6.0
-yamllint==1.28.0
+pathspec==0.11.2
+PyYAML==6.0.1
+yamllint==1.32.0
diff --git a/test/lib/ansible_test/_data/requirements/units.txt b/test/lib/ansible_test/_data/requirements/units.txt
index d2f56d35..d723a65f 100644
--- a/test/lib/ansible_test/_data/requirements/units.txt
+++ b/test/lib/ansible_test/_data/requirements/units.txt
@@ -2,5 +2,4 @@ mock
pytest
pytest-mock
pytest-xdist
-pytest-forked
pyyaml # required by the collection loader (only needed for collections)
diff --git a/test/lib/ansible_test/_internal/ci/azp.py b/test/lib/ansible_test/_internal/ci/azp.py
index 404f8056..ebf260b9 100644
--- a/test/lib/ansible_test/_internal/ci/azp.py
+++ b/test/lib/ansible_test/_internal/ci/azp.py
@@ -70,7 +70,7 @@ class AzurePipelines(CIProvider):
os.environ['SYSTEM_JOBIDENTIFIER'],
)
except KeyError as ex:
- raise MissingEnvironmentVariable(name=ex.args[0])
+ raise MissingEnvironmentVariable(name=ex.args[0]) from None
return prefix
@@ -121,7 +121,7 @@ class AzurePipelines(CIProvider):
task_id=str(uuid.UUID(os.environ['SYSTEM_TASKINSTANCEID'])),
)
except KeyError as ex:
- raise MissingEnvironmentVariable(name=ex.args[0])
+ raise MissingEnvironmentVariable(name=ex.args[0]) from None
self.auth.sign_request(request)
@@ -154,7 +154,7 @@ class AzurePipelinesAuthHelper(CryptographyAuthHelper):
try:
agent_temp_directory = os.environ['AGENT_TEMPDIRECTORY']
except KeyError as ex:
- raise MissingEnvironmentVariable(name=ex.args[0])
+ raise MissingEnvironmentVariable(name=ex.args[0]) from None
# the temporary file cannot be deleted because we do not know when the agent has processed it
# placing the file in the agent's temp directory allows it to be picked up when the job is running in a container
@@ -181,7 +181,7 @@ class AzurePipelinesChanges:
self.source_branch_name = os.environ['BUILD_SOURCEBRANCHNAME']
self.pr_branch_name = os.environ.get('SYSTEM_PULLREQUEST_TARGETBRANCH')
except KeyError as ex:
- raise MissingEnvironmentVariable(name=ex.args[0])
+ raise MissingEnvironmentVariable(name=ex.args[0]) from None
if self.source_branch.startswith('refs/tags/'):
raise ChangeDetectionNotSupported('Change detection is not supported for tags.')
diff --git a/test/lib/ansible_test/_internal/cli/environments.py b/test/lib/ansible_test/_internal/cli/environments.py
index 94cafae3..7b1fd1c2 100644
--- a/test/lib/ansible_test/_internal/cli/environments.py
+++ b/test/lib/ansible_test/_internal/cli/environments.py
@@ -146,12 +146,6 @@ def add_global_options(
help='install command requirements',
)
- global_parser.add_argument(
- '--no-pip-check',
- action='store_true',
- help=argparse.SUPPRESS, # deprecated, kept for now (with a warning) for backwards compatibility
- )
-
add_global_remote(global_parser, controller_mode)
add_global_docker(global_parser, controller_mode)
@@ -396,7 +390,6 @@ def add_global_docker(
"""Add global options for Docker."""
if controller_mode != ControllerMode.DELEGATED:
parser.set_defaults(
- docker_no_pull=False,
docker_network=None,
docker_terminate=None,
prime_containers=False,
@@ -407,12 +400,6 @@ def add_global_docker(
return
parser.add_argument(
- '--docker-no-pull',
- action='store_true',
- help=argparse.SUPPRESS, # deprecated, kept for now (with a warning) for backwards compatibility
- )
-
- parser.add_argument(
'--docker-network',
metavar='NET',
help='run using the specified network',
diff --git a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py
index ad6cf86f..64bb13b0 100644
--- a/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py
+++ b/test/lib/ansible_test/_internal/commands/coverage/analyze/targets/__init__.py
@@ -57,9 +57,9 @@ def load_report(report: dict[str, t.Any]) -> tuple[list[str], Arcs, Lines]:
arc_data: dict[str, dict[str, int]] = report['arcs']
line_data: dict[str, dict[int, int]] = report['lines']
except KeyError as ex:
- raise ApplicationError('Document is missing key "%s".' % ex.args)
+ raise ApplicationError('Document is missing key "%s".' % ex.args) from None
except TypeError:
- raise ApplicationError('Document is type "%s" instead of "dict".' % type(report).__name__)
+ raise ApplicationError('Document is type "%s" instead of "dict".' % type(report).__name__) from None
arcs = dict((path, dict((parse_arc(arc), set(target_sets[index])) for arc, index in data.items())) for path, data in arc_data.items())
lines = dict((path, dict((int(line), set(target_sets[index])) for line, index in data.items())) for path, data in line_data.items())
@@ -72,12 +72,12 @@ def read_report(path: str) -> tuple[list[str], Arcs, Lines]:
try:
report = read_json_file(path)
except Exception as ex:
- raise ApplicationError('File "%s" is not valid JSON: %s' % (path, ex))
+ raise ApplicationError('File "%s" is not valid JSON: %s' % (path, ex)) from None
try:
return load_report(report)
except ApplicationError as ex:
- raise ApplicationError('File "%s" is not an aggregated coverage data file. %s' % (path, ex))
+ raise ApplicationError('File "%s" is not an aggregated coverage data file. %s' % (path, ex)) from None
def write_report(args: CoverageAnalyzeTargetsConfig, report: dict[str, t.Any], path: str) -> None:
diff --git a/test/lib/ansible_test/_internal/commands/coverage/combine.py b/test/lib/ansible_test/_internal/commands/coverage/combine.py
index 12cb54e2..fdeac838 100644
--- a/test/lib/ansible_test/_internal/commands/coverage/combine.py
+++ b/test/lib/ansible_test/_internal/commands/coverage/combine.py
@@ -121,7 +121,7 @@ def _command_coverage_combine_python(args: CoverageCombineConfig, host_state: Ho
coverage_files = get_python_coverage_files()
def _default_stub_value(source_paths: list[str]) -> dict[str, set[tuple[int, int]]]:
- return {path: set() for path in source_paths}
+ return {path: {(0, 0)} for path in source_paths}
counter = 0
sources = _get_coverage_targets(args, walk_compile_targets)
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py b/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py
index e8020ca9..136c5331 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/acme.py
@@ -8,7 +8,6 @@ from ....config import (
)
from ....containers import (
- CleanupMode,
run_support_container,
)
@@ -22,8 +21,6 @@ from . import (
class ACMEProvider(CloudProvider):
"""ACME plugin. Sets up cloud resources for tests."""
- DOCKER_SIMULATOR_NAME = 'acme-simulator'
-
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
@@ -51,17 +48,18 @@ class ACMEProvider(CloudProvider):
14000, # Pebble ACME CA
]
- run_support_container(
+ descriptor = run_support_container(
self.args,
self.platform,
self.image,
- self.DOCKER_SIMULATOR_NAME,
+ 'acme-simulator',
ports,
- allow_existing=True,
- cleanup=CleanupMode.YES,
)
- self._set_cloud_config('acme_host', self.DOCKER_SIMULATOR_NAME)
+ if not descriptor:
+ return
+
+ self._set_cloud_config('acme_host', descriptor.name)
def _setup_static(self) -> None:
raise NotImplementedError()
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py b/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py
index 8588df7d..8060804a 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/cs.py
@@ -21,7 +21,6 @@ from ....docker_util import (
)
from ....containers import (
- CleanupMode,
run_support_container,
wait_for_file,
)
@@ -36,12 +35,10 @@ from . import (
class CsCloudProvider(CloudProvider):
"""CloudStack cloud provider plugin. Sets up cloud resources before delegation."""
- DOCKER_SIMULATOR_NAME = 'cloudstack-sim'
-
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
- self.image = os.environ.get('ANSIBLE_CLOUDSTACK_CONTAINER', 'quay.io/ansible/cloudstack-test-container:1.4.0')
+ self.image = os.environ.get('ANSIBLE_CLOUDSTACK_CONTAINER', 'quay.io/ansible/cloudstack-test-container:1.6.1')
self.host = ''
self.port = 0
@@ -96,10 +93,8 @@ class CsCloudProvider(CloudProvider):
self.args,
self.platform,
self.image,
- self.DOCKER_SIMULATOR_NAME,
+ 'cloudstack-sim',
ports,
- allow_existing=True,
- cleanup=CleanupMode.YES,
)
if not descriptor:
@@ -107,7 +102,7 @@ class CsCloudProvider(CloudProvider):
# apply work-around for OverlayFS issue
# https://github.com/docker/for-linux/issues/72#issuecomment-319904698
- docker_exec(self.args, self.DOCKER_SIMULATOR_NAME, ['find', '/var/lib/mysql', '-type', 'f', '-exec', 'touch', '{}', ';'], capture=True)
+ docker_exec(self.args, descriptor.name, ['find', '/var/lib/mysql', '-type', 'f', '-exec', 'touch', '{}', ';'], capture=True)
if self.args.explain:
values = dict(
@@ -115,10 +110,10 @@ class CsCloudProvider(CloudProvider):
PORT=str(self.port),
)
else:
- credentials = self._get_credentials(self.DOCKER_SIMULATOR_NAME)
+ credentials = self._get_credentials(descriptor.name)
values = dict(
- HOST=self.DOCKER_SIMULATOR_NAME,
+ HOST=descriptor.name,
PORT=str(self.port),
KEY=credentials['apikey'],
SECRET=credentials['secretkey'],
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/galaxy.py b/test/lib/ansible_test/_internal/commands/integration/cloud/galaxy.py
index 1391cd84..f7053c8b 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/galaxy.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/galaxy.py
@@ -10,12 +10,21 @@ from ....config import (
from ....docker_util import (
docker_cp_to,
+ docker_exec,
)
from ....containers import (
run_support_container,
)
+from ....encoding import (
+ to_text,
+)
+
+from ....util import (
+ display,
+)
+
from . import (
CloudEnvironment,
CloudEnvironmentConfig,
@@ -23,53 +32,59 @@ from . import (
)
-# We add BasicAuthentication, to make the tasks that deal with
-# direct API access easier to deal with across galaxy_ng and pulp
-SETTINGS = b'''
-CONTENT_ORIGIN = 'http://ansible-ci-pulp:80'
-ANSIBLE_API_HOSTNAME = 'http://ansible-ci-pulp:80'
-ANSIBLE_CONTENT_HOSTNAME = 'http://ansible-ci-pulp:80/pulp/content'
-TOKEN_AUTH_DISABLED = True
-GALAXY_REQUIRE_CONTENT_APPROVAL = False
-GALAXY_AUTHENTICATION_CLASSES = [
- "rest_framework.authentication.SessionAuthentication",
- "rest_framework.authentication.TokenAuthentication",
- "rest_framework.authentication.BasicAuthentication",
-]
-'''
-
-SET_ADMIN_PASSWORD = b'''#!/usr/bin/execlineb -S0
-foreground {
- redirfd -w 1 /dev/null
- redirfd -w 2 /dev/null
- export DJANGO_SETTINGS_MODULE pulpcore.app.settings
- export PULP_CONTENT_ORIGIN localhost
- s6-setuidgid postgres
- if { /usr/local/bin/django-admin reset-admin-password --password password }
- if { /usr/local/bin/pulpcore-manager create-group system:partner-engineers --users admin }
-}
-'''
-
-# There are 2 overrides here:
-# 1. Change the gunicorn bind address from 127.0.0.1 to 0.0.0.0 now that Galaxy NG does not allow us to access the
-# Pulp API through it.
-# 2. Grant access allowing us to DELETE a namespace in Galaxy NG. This is as CI deletes and recreates repos and
-# distributions in Pulp which now breaks the namespace in Galaxy NG. Recreating it is the "simple" fix to get it
-# working again.
-# These may not be needed in the future, especially if 1 becomes configurable by an env var but for now they must be
-# done.
-OVERRIDES = b'''#!/usr/bin/execlineb -S0
-foreground {
- sed -i "0,/\\"127.0.0.1:24817\\"/s//\\"0.0.0.0:24817\\"/" /etc/services.d/pulpcore-api/run
+GALAXY_HOST_NAME = 'galaxy-pulp'
+SETTINGS = {
+ 'PULP_CONTENT_ORIGIN': f'http://{GALAXY_HOST_NAME}',
+ 'PULP_ANSIBLE_API_HOSTNAME': f'http://{GALAXY_HOST_NAME}',
+ 'PULP_GALAXY_API_PATH_PREFIX': '/api/galaxy/',
+ # These paths are unique to the container image which has an nginx location for /pulp/content to route
+ # requests to the content backend
+ 'PULP_ANSIBLE_CONTENT_HOSTNAME': f'http://{GALAXY_HOST_NAME}/pulp/content/api/galaxy/v3/artifacts/collections/',
+ 'PULP_CONTENT_PATH_PREFIX': '/pulp/content/api/galaxy/v3/artifacts/collections/',
+ 'PULP_GALAXY_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.TokenAuthentication',
+ 'rest_framework.authentication.BasicAuthentication',
+ 'django.contrib.auth.backends.ModelBackend',
+ ],
+ # This should probably be false see https://issues.redhat.com/browse/AAH-2328
+ 'PULP_GALAXY_REQUIRE_CONTENT_APPROVAL': 'true',
+ 'PULP_GALAXY_DEPLOYMENT_MODE': 'standalone',
+ 'PULP_GALAXY_AUTO_SIGN_COLLECTIONS': 'false',
+ 'PULP_GALAXY_COLLECTION_SIGNING_SERVICE': 'ansible-default',
+ 'PULP_RH_ENTITLEMENT_REQUIRED': 'insights',
+ 'PULP_TOKEN_AUTH_DISABLED': 'false',
+ 'PULP_TOKEN_SERVER': f'http://{GALAXY_HOST_NAME}/token/',
+ 'PULP_TOKEN_SIGNATURE_ALGORITHM': 'ES256',
+ 'PULP_PUBLIC_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_public_key.pem',
+ 'PULP_PRIVATE_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_private_key.pem',
+ 'PULP_ANALYTICS': 'false',
+ 'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_ACCESS': 'true',
+ 'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_DOWNLOAD': 'true',
+ 'PULP_GALAXY_ENABLE_LEGACY_ROLES': 'true',
+ 'PULP_GALAXY_FEATURE_FLAGS__execution_environments': 'false',
+ 'PULP_SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/',
+ 'PULP_GALAXY_FEATURE_FLAGS__ai_deny_index': 'true',
+ 'PULP_DEFAULT_ADMIN_PASSWORD': 'password'
}
-# This sed calls changes the first occurrence to "allow" which is conveniently the delete operation for a namespace.
-# https://github.com/ansible/galaxy_ng/blob/master/galaxy_ng/app/access_control/statements/standalone.py#L9-L11.
-backtick NG_PREFIX { python -c "import galaxy_ng; print(galaxy_ng.__path__[0], end='')" }
-importas ng_prefix NG_PREFIX
-foreground {
- sed -i "0,/\\"effect\\": \\"deny\\"/s//\\"effect\\": \\"allow\\"/" ${ng_prefix}/app/access_control/statements/standalone.py
-}'''
+
+GALAXY_IMPORTER = b'''
+[galaxy-importer]
+ansible_local_tmp=~/.ansible/tmp
+ansible_test_local_image=false
+check_required_tags=false
+check_runtime_yaml=false
+check_changelog=false
+infra_osd=false
+local_image_docker=false
+log_level_main=INFO
+require_v1_or_greater=false
+run_ansible_doc=false
+run_ansible_lint=false
+run_ansible_test=false
+run_flake8=false
+'''.strip()
class GalaxyProvider(CloudProvider):
@@ -81,13 +96,9 @@ class GalaxyProvider(CloudProvider):
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
- # Cannot use the latest container image as either galaxy_ng 4.2.0rc2 or pulp 0.5.0 has sporatic issues with
- # dropping published collections in CI. Try running the tests multiple times when updating. Will also need to
- # comment out the cache tests in 'test/integration/targets/ansible-galaxy-collection/tasks/install.yml' when
- # the newer update is available.
- self.pulp = os.environ.get(
+ self.image = os.environ.get(
'ANSIBLE_PULP_CONTAINER',
- 'quay.io/ansible/pulp-galaxy-ng:b79a7be64eff'
+ 'quay.io/pulp/galaxy:4.7.1'
)
self.uses_docker = True
@@ -96,48 +107,46 @@ class GalaxyProvider(CloudProvider):
"""Setup cloud resource before delegation and reg cleanup callback."""
super().setup()
- galaxy_port = 80
- pulp_host = 'ansible-ci-pulp'
- pulp_port = 24817
-
- ports = [
- galaxy_port,
- pulp_port,
- ]
-
- # Create the container, don't run it, we need to inject configs before it starts
- descriptor = run_support_container(
- self.args,
- self.platform,
- self.pulp,
- pulp_host,
- ports,
- start=False,
- allow_existing=True,
- )
+ with tempfile.NamedTemporaryFile(mode='w+') as env_fd:
+ settings = '\n'.join(
+ f'{key}={value}' for key, value in SETTINGS.items()
+ )
+ env_fd.write(settings)
+ env_fd.flush()
+ display.info(f'>>> galaxy_ng Configuration\n{settings}', verbosity=3)
+ descriptor = run_support_container(
+ self.args,
+ self.platform,
+ self.image,
+ GALAXY_HOST_NAME,
+ [
+ 80,
+ ],
+ aliases=[
+ GALAXY_HOST_NAME,
+ ],
+ start=True,
+ options=[
+ '--env-file', env_fd.name,
+ ],
+ )
if not descriptor:
return
- if not descriptor.running:
- pulp_id = descriptor.container_id
-
- injected_files = {
- '/etc/pulp/settings.py': SETTINGS,
- '/etc/cont-init.d/111-postgres': SET_ADMIN_PASSWORD,
- '/etc/cont-init.d/000-ansible-test-overrides': OVERRIDES,
- }
- for path, content in injected_files.items():
- with tempfile.NamedTemporaryFile() as temp_fd:
- temp_fd.write(content)
- temp_fd.flush()
- docker_cp_to(self.args, pulp_id, temp_fd.name, path)
-
- descriptor.start(self.args)
-
- self._set_cloud_config('PULP_HOST', pulp_host)
- self._set_cloud_config('PULP_PORT', str(pulp_port))
- self._set_cloud_config('GALAXY_PORT', str(galaxy_port))
+ injected_files = [
+ ('/etc/galaxy-importer/galaxy-importer.cfg', GALAXY_IMPORTER, 'galaxy-importer'),
+ ]
+ for path, content, friendly_name in injected_files:
+ with tempfile.NamedTemporaryFile() as temp_fd:
+ temp_fd.write(content)
+ temp_fd.flush()
+ display.info(f'>>> {friendly_name} Configuration\n{to_text(content)}', verbosity=3)
+ docker_exec(self.args, descriptor.container_id, ['mkdir', '-p', os.path.dirname(path)], True)
+ docker_cp_to(self.args, descriptor.container_id, temp_fd.name, path)
+ docker_exec(self.args, descriptor.container_id, ['chown', 'pulp:pulp', path], True)
+
+ self._set_cloud_config('PULP_HOST', GALAXY_HOST_NAME)
self._set_cloud_config('PULP_USER', 'admin')
self._set_cloud_config('PULP_PASSWORD', 'password')
@@ -150,21 +159,19 @@ class GalaxyEnvironment(CloudEnvironment):
pulp_user = str(self._get_cloud_config('PULP_USER'))
pulp_password = str(self._get_cloud_config('PULP_PASSWORD'))
pulp_host = self._get_cloud_config('PULP_HOST')
- galaxy_port = self._get_cloud_config('GALAXY_PORT')
- pulp_port = self._get_cloud_config('PULP_PORT')
return CloudEnvironmentConfig(
ansible_vars=dict(
pulp_user=pulp_user,
pulp_password=pulp_password,
- pulp_api='http://%s:%s' % (pulp_host, pulp_port),
- pulp_server='http://%s:%s/pulp_ansible/galaxy/' % (pulp_host, pulp_port),
- galaxy_ng_server='http://%s:%s/api/galaxy/' % (pulp_host, galaxy_port),
+ pulp_api=f'http://{pulp_host}',
+ pulp_server=f'http://{pulp_host}/pulp_ansible/galaxy/',
+ galaxy_ng_server=f'http://{pulp_host}/api/galaxy/',
),
env_vars=dict(
PULP_USER=pulp_user,
PULP_PASSWORD=pulp_password,
- PULP_SERVER='http://%s:%s/pulp_ansible/galaxy/api/' % (pulp_host, pulp_port),
- GALAXY_NG_SERVER='http://%s:%s/api/galaxy/' % (pulp_host, galaxy_port),
+ PULP_SERVER=f'http://{pulp_host}/pulp_ansible/galaxy/api/',
+ GALAXY_NG_SERVER=f'http://{pulp_host}/api/galaxy/',
),
)
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/httptester.py b/test/lib/ansible_test/_internal/commands/integration/cloud/httptester.py
index 85065d6f..b3cf2d49 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/httptester.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/httptester.py
@@ -13,7 +13,6 @@ from ....config import (
)
from ....containers import (
- CleanupMode,
run_support_container,
)
@@ -62,8 +61,6 @@ class HttptesterProvider(CloudProvider):
'http-test-container',
ports,
aliases=aliases,
- allow_existing=True,
- cleanup=CleanupMode.YES,
env={
KRB5_PASSWORD_ENV: generate_password(),
},
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/nios.py b/test/lib/ansible_test/_internal/commands/integration/cloud/nios.py
index 5bed8340..62dd1558 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/nios.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/nios.py
@@ -8,7 +8,6 @@ from ....config import (
)
from ....containers import (
- CleanupMode,
run_support_container,
)
@@ -22,8 +21,6 @@ from . import (
class NiosProvider(CloudProvider):
"""Nios plugin. Sets up NIOS mock server for tests."""
- DOCKER_SIMULATOR_NAME = 'nios-simulator'
-
# Default image to run the nios simulator.
#
# The simulator must be pinned to a specific version
@@ -31,7 +28,7 @@ class NiosProvider(CloudProvider):
#
# It's source source itself resides at:
# https://github.com/ansible/nios-test-container
- DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:1.4.0'
+ DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:2.0.0'
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
@@ -65,17 +62,18 @@ class NiosProvider(CloudProvider):
nios_port,
]
- run_support_container(
+ descriptor = run_support_container(
self.args,
self.platform,
self.image,
- self.DOCKER_SIMULATOR_NAME,
+ 'nios-simulator',
ports,
- allow_existing=True,
- cleanup=CleanupMode.YES,
)
- self._set_cloud_config('NIOS_HOST', self.DOCKER_SIMULATOR_NAME)
+ if not descriptor:
+ return
+
+ self._set_cloud_config('NIOS_HOST', descriptor.name)
def _setup_static(self) -> None:
raise NotImplementedError()
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/openshift.py b/test/lib/ansible_test/_internal/commands/integration/cloud/openshift.py
index ddd434a8..6e8a5e4f 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/openshift.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/openshift.py
@@ -16,7 +16,6 @@ from ....config import (
)
from ....containers import (
- CleanupMode,
run_support_container,
wait_for_file,
)
@@ -31,8 +30,6 @@ from . import (
class OpenShiftCloudProvider(CloudProvider):
"""OpenShift cloud provider plugin. Sets up cloud resources before delegation."""
- DOCKER_CONTAINER_NAME = 'openshift-origin'
-
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args, config_extension='.kubeconfig')
@@ -74,10 +71,8 @@ class OpenShiftCloudProvider(CloudProvider):
self.args,
self.platform,
self.image,
- self.DOCKER_CONTAINER_NAME,
+ 'openshift-origin',
ports,
- allow_existing=True,
- cleanup=CleanupMode.YES,
cmd=cmd,
)
@@ -87,7 +82,7 @@ class OpenShiftCloudProvider(CloudProvider):
if self.args.explain:
config = '# Unknown'
else:
- config = self._get_config(self.DOCKER_CONTAINER_NAME, 'https://%s:%s/' % (self.DOCKER_CONTAINER_NAME, port))
+ config = self._get_config(descriptor.name, 'https://%s:%s/' % (descriptor.name, port))
self._write_config(config)
diff --git a/test/lib/ansible_test/_internal/commands/integration/cloud/vcenter.py b/test/lib/ansible_test/_internal/commands/integration/cloud/vcenter.py
index 242b0204..b0ff7fe3 100644
--- a/test/lib/ansible_test/_internal/commands/integration/cloud/vcenter.py
+++ b/test/lib/ansible_test/_internal/commands/integration/cloud/vcenter.py
@@ -2,7 +2,6 @@
from __future__ import annotations
import configparser
-import os
from ....util import (
ApplicationError,
@@ -13,11 +12,6 @@ from ....config import (
IntegrationConfig,
)
-from ....containers import (
- CleanupMode,
- run_support_container,
-)
-
from . import (
CloudEnvironment,
CloudEnvironmentConfig,
@@ -28,66 +22,16 @@ from . import (
class VcenterProvider(CloudProvider):
"""VMware vcenter/esx plugin. Sets up cloud resources for tests."""
- DOCKER_SIMULATOR_NAME = 'vcenter-simulator'
-
def __init__(self, args: IntegrationConfig) -> None:
super().__init__(args)
- # The simulator must be pinned to a specific version to guarantee CI passes with the version used.
- if os.environ.get('ANSIBLE_VCSIM_CONTAINER'):
- self.image = os.environ.get('ANSIBLE_VCSIM_CONTAINER')
- else:
- self.image = 'quay.io/ansible/vcenter-test-container:1.7.0'
-
- # VMware tests can be run on govcsim or BYO with a static config file.
- # The simulator is the default if no config is provided.
- self.vmware_test_platform = os.environ.get('VMWARE_TEST_PLATFORM', 'govcsim')
-
- if self.vmware_test_platform == 'govcsim':
- self.uses_docker = True
- self.uses_config = False
- elif self.vmware_test_platform == 'static':
- self.uses_docker = False
- self.uses_config = True
+ self.uses_config = True
def setup(self) -> None:
"""Setup the cloud resource before delegation and register a cleanup callback."""
super().setup()
- self._set_cloud_config('vmware_test_platform', self.vmware_test_platform)
-
- if self.vmware_test_platform == 'govcsim':
- self._setup_dynamic_simulator()
- self.managed = True
- elif self.vmware_test_platform == 'static':
- self._use_static_config()
- self._setup_static()
- else:
- raise ApplicationError('Unknown vmware_test_platform: %s' % self.vmware_test_platform)
-
- def _setup_dynamic_simulator(self) -> None:
- """Create a vcenter simulator using docker."""
- ports = [
- 443,
- 8080,
- 8989,
- 5000, # control port for flask app in simulator
- ]
-
- run_support_container(
- self.args,
- self.platform,
- self.image,
- self.DOCKER_SIMULATOR_NAME,
- ports,
- allow_existing=True,
- cleanup=CleanupMode.YES,
- )
-
- self._set_cloud_config('vcenter_hostname', self.DOCKER_SIMULATOR_NAME)
-
- def _setup_static(self) -> None:
- if not os.path.exists(self.config_static_path):
+ if not self._use_static_config():
raise ApplicationError('Configuration file does not exist: %s' % self.config_static_path)
@@ -96,37 +40,21 @@ class VcenterEnvironment(CloudEnvironment):
def get_environment_config(self) -> CloudEnvironmentConfig:
"""Return environment configuration for use in the test environment after delegation."""
- try:
- # We may be in a container, so we cannot just reach VMWARE_TEST_PLATFORM,
- # We do a try/except instead
- parser = configparser.ConfigParser()
- parser.read(self.config_path) # static
-
- env_vars = {}
- ansible_vars = dict(
- resource_prefix=self.resource_prefix,
- )
- ansible_vars.update(dict(parser.items('DEFAULT', raw=True)))
- except KeyError: # govcsim
- env_vars = dict(
- VCENTER_HOSTNAME=str(self._get_cloud_config('vcenter_hostname')),
- VCENTER_USERNAME='user',
- VCENTER_PASSWORD='pass',
- )
-
- ansible_vars = dict(
- vcsim=str(self._get_cloud_config('vcenter_hostname')),
- vcenter_hostname=str(self._get_cloud_config('vcenter_hostname')),
- vcenter_username='user',
- vcenter_password='pass',
- )
+ # We may be in a container, so we cannot just reach VMWARE_TEST_PLATFORM,
+ # We do a try/except instead
+ parser = configparser.ConfigParser()
+ parser.read(self.config_path) # static
+
+ ansible_vars = dict(
+ resource_prefix=self.resource_prefix,
+ )
+ ansible_vars.update(dict(parser.items('DEFAULT', raw=True)))
for key, value in ansible_vars.items():
if key.endswith('_password'):
display.sensitive.add(value)
return CloudEnvironmentConfig(
- env_vars=env_vars,
ansible_vars=ansible_vars,
module_defaults={
'group/vmware': {
diff --git a/test/lib/ansible_test/_internal/commands/sanity/__init__.py b/test/lib/ansible_test/_internal/commands/sanity/__init__.py
index 0bc68a21..9b675e4a 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/__init__.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/__init__.py
@@ -127,9 +127,13 @@ TARGET_SANITY_ROOT = os.path.join(ANSIBLE_TEST_TARGET_ROOT, 'sanity')
# NOTE: must match ansible.constants.DOCUMENTABLE_PLUGINS, but with 'module' replaced by 'modules'!
DOCUMENTABLE_PLUGINS = (
- 'become', 'cache', 'callback', 'cliconf', 'connection', 'httpapi', 'inventory', 'lookup', 'netconf', 'modules', 'shell', 'strategy', 'vars'
+ 'become', 'cache', 'callback', 'cliconf', 'connection', 'filter', 'httpapi', 'inventory',
+ 'lookup', 'netconf', 'modules', 'shell', 'strategy', 'test', 'vars',
)
+# Plugin types that can have multiple plugins per file, and where filenames not always correspond to plugin names
+MULTI_FILE_PLUGINS = ('filter', 'test', )
+
created_venvs: list[str] = []
@@ -260,7 +264,7 @@ def command_sanity(args: SanityConfig) -> None:
virtualenv_python = create_sanity_virtualenv(args, test_profile.python, test.name)
if virtualenv_python:
- virtualenv_yaml = check_sanity_virtualenv_yaml(virtualenv_python)
+ virtualenv_yaml = args.explain or check_sanity_virtualenv_yaml(virtualenv_python)
if test.require_libyaml and not virtualenv_yaml:
result = SanitySkipped(test.name)
@@ -875,6 +879,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
self.__include_directories: bool = self.config.get('include_directories')
self.__include_symlinks: bool = self.config.get('include_symlinks')
self.__py2_compat: bool = self.config.get('py2_compat', False)
+ self.__error_code: str | None = self.config.get('error_code', None)
else:
self.output = None
self.extensions = []
@@ -890,6 +895,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
self.__include_directories = False
self.__include_symlinks = False
self.__py2_compat = False
+ self.__error_code = None
if self.no_targets:
mutually_exclusive = (
@@ -909,6 +915,11 @@ class SanityCodeSmellTest(SanitySingleVersion):
raise ApplicationError('Sanity test "%s" option "no_targets" is mutually exclusive with options: %s' % (self.name, ', '.join(problems)))
@property
+ def error_code(self) -> t.Optional[str]:
+ """Error code for ansible-test matching the format used by the underlying test program, or None if the program does not use error codes."""
+ return self.__error_code
+
+ @property
def all_targets(self) -> bool:
"""True if test targets will not be filtered using includes, excludes, requires or changes. Mutually exclusive with no_targets."""
return self.__all_targets
@@ -992,6 +1003,8 @@ class SanityCodeSmellTest(SanitySingleVersion):
pattern = '^(?P<path>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+): (?P<message>.*)$'
elif self.output == 'path-message':
pattern = '^(?P<path>[^:]*): (?P<message>.*)$'
+ elif self.output == 'path-line-column-code-message':
+ pattern = '^(?P<path>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+): (?P<code>[^:]*): (?P<message>.*)$'
else:
raise ApplicationError('Unsupported output type: %s' % self.output)
@@ -1021,6 +1034,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
path=m['path'],
line=int(m.get('line', 0)),
column=int(m.get('column', 0)),
+ code=m.get('code'),
) for m in matches]
messages = settings.process_errors(messages, paths)
@@ -1166,20 +1180,23 @@ def create_sanity_virtualenv(
run_pip(args, virtualenv_python, commands, None) # create_sanity_virtualenv()
- write_text_file(meta_install, virtualenv_install)
+ if not args.explain:
+ write_text_file(meta_install, virtualenv_install)
# false positive: pylint: disable=no-member
if any(isinstance(command, PipInstall) and command.has_package('pyyaml') for command in commands):
- virtualenv_yaml = yamlcheck(virtualenv_python)
+ virtualenv_yaml = yamlcheck(virtualenv_python, args.explain)
else:
virtualenv_yaml = None
- write_json_file(meta_yaml, virtualenv_yaml)
+ if not args.explain:
+ write_json_file(meta_yaml, virtualenv_yaml)
created_venvs.append(f'{label}-{python.version}')
- # touch the marker to keep track of when the virtualenv was last used
- pathlib.Path(virtualenv_marker).touch()
+ if not args.explain:
+ # touch the marker to keep track of when the virtualenv was last used
+ pathlib.Path(virtualenv_marker).touch()
return virtualenv_python
diff --git a/test/lib/ansible_test/_internal/commands/sanity/ansible_doc.py b/test/lib/ansible_test/_internal/commands/sanity/ansible_doc.py
index 04080f60..ff035ef9 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/ansible_doc.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/ansible_doc.py
@@ -2,11 +2,13 @@
from __future__ import annotations
import collections
+import json
import os
import re
from . import (
DOCUMENTABLE_PLUGINS,
+ MULTI_FILE_PLUGINS,
SanitySingleVersion,
SanityFailure,
SanitySuccess,
@@ -85,6 +87,44 @@ class AnsibleDocTest(SanitySingleVersion):
doc_targets[plugin_type].append(plugin_fqcn)
env = ansible_environment(args, color=False)
+
+ for doc_type in MULTI_FILE_PLUGINS:
+ if doc_targets.get(doc_type):
+ # List plugins
+ cmd = ['ansible-doc', '-l', '--json', '-t', doc_type]
+ prefix = data_context().content.prefix if data_context().content.collection else 'ansible.builtin.'
+ cmd.append(prefix[:-1])
+ try:
+ stdout, stderr = intercept_python(args, python, cmd, env, capture=True)
+ status = 0
+ except SubprocessError as ex:
+ stdout = ex.stdout
+ stderr = ex.stderr
+ status = ex.status
+
+ if status:
+ summary = '%s' % SubprocessError(cmd=cmd, status=status, stderr=stderr)
+ return SanityFailure(self.name, summary=summary)
+
+ if stdout:
+ display.info(stdout.strip(), verbosity=3)
+
+ if stderr:
+ summary = 'Output on stderr from ansible-doc is considered an error.\n\n%s' % SubprocessError(cmd, stderr=stderr)
+ return SanityFailure(self.name, summary=summary)
+
+ if args.explain:
+ continue
+
+ plugin_list_json = json.loads(stdout)
+ doc_targets[doc_type] = []
+ for plugin_name, plugin_value in sorted(plugin_list_json.items()):
+ if plugin_value != 'UNDOCUMENTED':
+ doc_targets[doc_type].append(plugin_name)
+
+ if not doc_targets[doc_type]:
+ del doc_targets[doc_type]
+
error_messages: list[SanityMessage] = []
for doc_type in sorted(doc_targets):
diff --git a/test/lib/ansible_test/_internal/commands/sanity/import.py b/test/lib/ansible_test/_internal/commands/sanity/import.py
index b8083324..36f52415 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/import.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/import.py
@@ -127,20 +127,26 @@ class ImportTest(SanityMultipleVersion):
('plugin', _get_module_test(False)),
):
if import_type == 'plugin' and python.version in REMOTE_ONLY_PYTHON_VERSIONS:
- continue
+ # Plugins are not supported on remote-only Python versions.
+ # However, the collection loader is used by the import sanity test and unit tests on remote-only Python versions.
+ # To support this, it is tested as a plugin, but using a venv which installs no requirements.
+ # Filtering of paths relevant to the Python version tested has already been performed by filter_remote_targets.
+ venv_type = 'empty'
+ else:
+ venv_type = import_type
data = '\n'.join([path for path in paths if test(path)])
if not data and not args.prime_venvs:
continue
- virtualenv_python = create_sanity_virtualenv(args, python, f'{self.name}.{import_type}', coverage=args.coverage, minimize=True)
+ virtualenv_python = create_sanity_virtualenv(args, python, f'{self.name}.{venv_type}', coverage=args.coverage, minimize=True)
if not virtualenv_python:
display.warning(f'Skipping sanity test "{self.name}" on Python {python.version} due to missing virtual environment support.')
return SanitySkipped(self.name, python.version)
- virtualenv_yaml = check_sanity_virtualenv_yaml(virtualenv_python)
+ virtualenv_yaml = args.explain or check_sanity_virtualenv_yaml(virtualenv_python)
if virtualenv_yaml is False:
display.warning(f'Sanity test "{self.name}" ({import_type}) on Python {python.version} may be slow due to missing libyaml support in PyYAML.')
diff --git a/test/lib/ansible_test/_internal/commands/sanity/mypy.py b/test/lib/ansible_test/_internal/commands/sanity/mypy.py
index 57ce1277..c93474e8 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/mypy.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/mypy.py
@@ -19,6 +19,7 @@ from . import (
from ...constants import (
CONTROLLER_PYTHON_VERSIONS,
REMOTE_ONLY_PYTHON_VERSIONS,
+ SUPPORTED_PYTHON_VERSIONS,
)
from ...test import (
@@ -36,6 +37,7 @@ from ...util import (
ANSIBLE_TEST_CONTROLLER_ROOT,
ApplicationError,
is_subdir,
+ str_to_version,
)
from ...util_common import (
@@ -71,9 +73,19 @@ class MypyTest(SanityMultipleVersion):
"""Return the given list of test targets, filtered to include only those relevant for the test."""
return [target for target in targets if os.path.splitext(target.path)[1] == '.py' and target.path not in self.vendored_paths and (
target.path.startswith('lib/ansible/') or target.path.startswith('test/lib/ansible_test/_internal/')
+ or target.path.startswith('packaging/')
or target.path.startswith('test/lib/ansible_test/_util/target/sanity/import/'))]
@property
+ def supported_python_versions(self) -> t.Optional[tuple[str, ...]]:
+ """A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
+ # mypy 0.981 dropped support for Python 2
+ # see: https://mypy-lang.blogspot.com/2022/09/mypy-0981-released.html
+ # cryptography dropped support for Python 3.5 in version 3.3
+ # see: https://cryptography.io/en/latest/changelog/#v3-3
+ return tuple(version for version in SUPPORTED_PYTHON_VERSIONS if str_to_version(version) >= (3, 6))
+
+ @property
def error_code(self) -> t.Optional[str]:
"""Error code for ansible-test matching the format used by the underlying test program, or None if the program does not use error codes."""
return 'ansible-test'
@@ -105,6 +117,7 @@ class MypyTest(SanityMultipleVersion):
MyPyContext('ansible-test', ['test/lib/ansible_test/_internal/'], controller_python_versions),
MyPyContext('ansible-core', ['lib/ansible/'], controller_python_versions),
MyPyContext('modules', ['lib/ansible/modules/', 'lib/ansible/module_utils/'], remote_only_python_versions),
+ MyPyContext('packaging', ['packaging/'], controller_python_versions),
)
unfiltered_messages: list[SanityMessage] = []
@@ -157,6 +170,9 @@ class MypyTest(SanityMultipleVersion):
# However, it will also report issues on those files, which is not the desired behavior.
messages = [message for message in messages if message.path in paths_set]
+ if args.explain:
+ return SanitySuccess(self.name, python_version=python.version)
+
results = settings.process_errors(messages, paths)
if results:
@@ -239,7 +255,7 @@ class MypyTest(SanityMultipleVersion):
pattern = r'^(?P<path>[^:]*):(?P<line>[0-9]+):((?P<column>[0-9]+):)? (?P<level>[^:]+): (?P<message>.*)$'
- parsed = parse_to_list_of_dict(pattern, stdout)
+ parsed = parse_to_list_of_dict(pattern, stdout or '')
messages = [SanityMessage(
level=r['level'],
diff --git a/test/lib/ansible_test/_internal/commands/sanity/pylint.py b/test/lib/ansible_test/_internal/commands/sanity/pylint.py
index c089f834..54b1952f 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/pylint.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/pylint.py
@@ -18,6 +18,11 @@ from . import (
SANITY_ROOT,
)
+from ...constants import (
+ CONTROLLER_PYTHON_VERSIONS,
+ REMOTE_ONLY_PYTHON_VERSIONS,
+)
+
from ...io import (
make_dirs,
)
@@ -38,6 +43,7 @@ from ...util import (
from ...util_common import (
run_command,
+ process_scoped_temporary_file,
)
from ...ansible_util import (
@@ -81,6 +87,8 @@ class PylintTest(SanitySingleVersion):
return [target for target in targets if os.path.splitext(target.path)[1] == '.py' or is_subdir(target.path, 'bin')]
def test(self, args: SanityConfig, targets: SanityTargets, python: PythonConfig) -> TestResult:
+ min_python_version_db_path = self.create_min_python_db(args, targets.targets)
+
plugin_dir = os.path.join(SANITY_ROOT, 'pylint', 'plugins')
plugin_names = sorted(p[0] for p in [
os.path.splitext(p) for p in os.listdir(plugin_dir)] if p[1] == '.py' and p[0] != '__init__')
@@ -163,7 +171,7 @@ class PylintTest(SanitySingleVersion):
continue
context_start = datetime.datetime.now(tz=datetime.timezone.utc)
- messages += self.pylint(args, context, context_paths, plugin_dir, plugin_names, python, collection_detail)
+ messages += self.pylint(args, context, context_paths, plugin_dir, plugin_names, python, collection_detail, min_python_version_db_path)
context_end = datetime.datetime.now(tz=datetime.timezone.utc)
context_times.append('%s: %d (%s)' % (context, len(context_paths), context_end - context_start))
@@ -194,6 +202,22 @@ class PylintTest(SanitySingleVersion):
return SanitySuccess(self.name)
+ def create_min_python_db(self, args: SanityConfig, targets: t.Iterable[TestTarget]) -> str:
+ """Create a database of target file paths and their minimum required Python version, returning the path to the database."""
+ target_paths = set(target.path for target in self.filter_remote_targets(list(targets)))
+ controller_min_version = CONTROLLER_PYTHON_VERSIONS[0]
+ target_min_version = REMOTE_ONLY_PYTHON_VERSIONS[0]
+ min_python_versions = {
+ os.path.abspath(target.path): target_min_version if target.path in target_paths else controller_min_version for target in targets
+ }
+
+ min_python_version_db_path = process_scoped_temporary_file(args)
+
+ with open(min_python_version_db_path, 'w') as database_file:
+ json.dump(min_python_versions, database_file)
+
+ return min_python_version_db_path
+
@staticmethod
def pylint(
args: SanityConfig,
@@ -203,6 +227,7 @@ class PylintTest(SanitySingleVersion):
plugin_names: list[str],
python: PythonConfig,
collection_detail: CollectionDetail,
+ min_python_version_db_path: str,
) -> list[dict[str, str]]:
"""Run pylint using the config specified by the context on the specified paths."""
rcfile = os.path.join(SANITY_ROOT, 'pylint', 'config', context.split('/')[0] + '.cfg')
@@ -234,6 +259,7 @@ class PylintTest(SanitySingleVersion):
'--rcfile', rcfile,
'--output-format', 'json',
'--load-plugins', ','.join(sorted(load_plugins)),
+ '--min-python-version-db', min_python_version_db_path,
] + paths # fmt: skip
if data_context().content.collection:
diff --git a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py
index 3153bc99..e29b5dec 100644
--- a/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py
+++ b/test/lib/ansible_test/_internal/commands/sanity/validate_modules.py
@@ -10,6 +10,7 @@ import typing as t
from . import (
DOCUMENTABLE_PLUGINS,
+ MULTI_FILE_PLUGINS,
SanitySingleVersion,
SanityMessage,
SanityFailure,
@@ -128,6 +129,10 @@ class ValidateModulesTest(SanitySingleVersion):
for target in targets.include:
target_per_type[self.get_plugin_type(target)].append(target)
+ # Remove plugins that cannot be associated to a single file (test and filter plugins).
+ for plugin_type in MULTI_FILE_PLUGINS:
+ target_per_type.pop(plugin_type, None)
+
cmd = [
python.path,
os.path.join(SANITY_ROOT, 'validate-modules', 'validate.py'),
diff --git a/test/lib/ansible_test/_internal/commands/units/__init__.py b/test/lib/ansible_test/_internal/commands/units/__init__.py
index 7d192e1b..71ce5c4d 100644
--- a/test/lib/ansible_test/_internal/commands/units/__init__.py
+++ b/test/lib/ansible_test/_internal/commands/units/__init__.py
@@ -253,7 +253,6 @@ def command_units(args: UnitsConfig) -> None:
cmd = [
'pytest',
- '--forked',
'-r', 'a',
'-n', str(args.num_workers) if args.num_workers else 'auto',
'--color', 'yes' if args.color else 'no',
@@ -262,6 +261,7 @@ def command_units(args: UnitsConfig) -> None:
'--junit-xml', os.path.join(ResultType.JUNIT.path, 'python%s-%s-units.xml' % (python.version, test_context)),
'--strict-markers', # added in pytest 4.5.0
'--rootdir', data_context().content.root,
+ '--confcutdir', data_context().content.root, # avoid permission errors when running from an installed version and using pytest >= 8
] # fmt:skip
if not data_context().content.collection:
@@ -275,6 +275,8 @@ def command_units(args: UnitsConfig) -> None:
if data_context().content.collection:
plugins.append('ansible_pytest_collections')
+ plugins.append('ansible_forked')
+
if plugins:
env['PYTHONPATH'] += ':%s' % os.path.join(ANSIBLE_TEST_TARGET_ROOT, 'pytest/plugins')
env['PYTEST_PLUGINS'] = ','.join(plugins)
diff --git a/test/lib/ansible_test/_internal/config.py b/test/lib/ansible_test/_internal/config.py
index 4e697933..dbc137b5 100644
--- a/test/lib/ansible_test/_internal/config.py
+++ b/test/lib/ansible_test/_internal/config.py
@@ -8,7 +8,6 @@ import sys
import typing as t
from .util import (
- display,
verify_sys_executable,
version_to_str,
type_guard,
@@ -136,12 +135,6 @@ class EnvironmentConfig(CommonConfig):
data_context().register_payload_callback(host_callback)
- if args.docker_no_pull:
- display.warning('The --docker-no-pull option is deprecated and has no effect. It will be removed in a future version of ansible-test.')
-
- if args.no_pip_check:
- display.warning('The --no-pip-check option is deprecated and has no effect. It will be removed in a future version of ansible-test.')
-
@property
def controller(self) -> ControllerHostConfig:
"""Host configuration for the controller."""
diff --git a/test/lib/ansible_test/_internal/containers.py b/test/lib/ansible_test/_internal/containers.py
index 869f1fba..92a40a48 100644
--- a/test/lib/ansible_test/_internal/containers.py
+++ b/test/lib/ansible_test/_internal/containers.py
@@ -3,7 +3,6 @@ from __future__ import annotations
import collections.abc as c
import contextlib
-import enum
import json
import random
import time
@@ -46,6 +45,7 @@ from .docker_util import (
get_docker_container_id,
get_docker_host_ip,
get_podman_host_ip,
+ get_session_container_name,
require_docker,
detect_host_properties,
)
@@ -101,14 +101,6 @@ class HostType:
managed = 'managed'
-class CleanupMode(enum.Enum):
- """How container cleanup should be handled."""
-
- YES = enum.auto()
- NO = enum.auto()
- INFO = enum.auto()
-
-
def run_support_container(
args: EnvironmentConfig,
context: str,
@@ -117,8 +109,7 @@ def run_support_container(
ports: list[int],
aliases: t.Optional[list[str]] = None,
start: bool = True,
- allow_existing: bool = False,
- cleanup: t.Optional[CleanupMode] = None,
+ cleanup: bool = True,
cmd: t.Optional[list[str]] = None,
env: t.Optional[dict[str, str]] = None,
options: t.Optional[list[str]] = None,
@@ -128,6 +119,8 @@ def run_support_container(
Start a container used to support tests, but not run them.
Containers created this way will be accessible from tests.
"""
+ name = get_session_container_name(args, name)
+
if args.prime_containers:
docker_pull(args, image)
return None
@@ -165,46 +158,13 @@ def run_support_container(
options.extend(['--ulimit', 'nofile=%s' % max_open_files])
- support_container_id = None
-
- if allow_existing:
- try:
- container = docker_inspect(args, name)
- except ContainerNotFoundError:
- container = None
-
- if container:
- support_container_id = container.id
-
- if not container.running:
- display.info('Ignoring existing "%s" container which is not running.' % name, verbosity=1)
- support_container_id = None
- elif not container.image:
- display.info('Ignoring existing "%s" container which has the wrong image.' % name, verbosity=1)
- support_container_id = None
- elif publish_ports and not all(port and len(port) == 1 for port in [container.get_tcp_port(port) for port in ports]):
- display.info('Ignoring existing "%s" container which does not have the required published ports.' % name, verbosity=1)
- support_container_id = None
-
- if not support_container_id:
- docker_rm(args, name)
-
if args.dev_systemd_debug:
options.extend(('--env', 'SYSTEMD_LOG_LEVEL=debug'))
- if support_container_id:
- display.info('Using existing "%s" container.' % name)
- running = True
- existing = True
- else:
- display.info('Starting new "%s" container.' % name)
- docker_pull(args, image)
- support_container_id = run_container(args, image, name, options, create_only=not start, cmd=cmd)
- running = start
- existing = False
-
- if cleanup is None:
- cleanup = CleanupMode.INFO if existing else CleanupMode.YES
+ display.info('Starting new "%s" container.' % name)
+ docker_pull(args, image)
+ support_container_id = run_container(args, image, name, options, create_only=not start, cmd=cmd)
+ running = start
descriptor = ContainerDescriptor(
image,
@@ -215,7 +175,6 @@ def run_support_container(
aliases,
publish_ports,
running,
- existing,
cleanup,
env,
)
@@ -694,8 +653,7 @@ class ContainerDescriptor:
aliases: list[str],
publish_ports: bool,
running: bool,
- existing: bool,
- cleanup: CleanupMode,
+ cleanup: bool,
env: t.Optional[dict[str, str]],
) -> None:
self.image = image
@@ -706,7 +664,6 @@ class ContainerDescriptor:
self.aliases = aliases
self.publish_ports = publish_ports
self.running = running
- self.existing = existing
self.cleanup = cleanup
self.env = env
self.details: t.Optional[SupportContainer] = None
@@ -805,10 +762,8 @@ def wait_for_file(
def cleanup_containers(args: EnvironmentConfig) -> None:
"""Clean up containers."""
for container in support_containers.values():
- if container.cleanup == CleanupMode.YES:
- docker_rm(args, container.container_id)
- elif container.cleanup == CleanupMode.INFO:
- display.notice(f'Remember to run `{require_docker().command} rm -f {container.name}` when finished testing.')
+ if container.cleanup:
+ docker_rm(args, container.name)
def create_hosts_entries(context: dict[str, ContainerAccess]) -> list[str]:
diff --git a/test/lib/ansible_test/_internal/core_ci.py b/test/lib/ansible_test/_internal/core_ci.py
index 6e44b3d9..77e6753f 100644
--- a/test/lib/ansible_test/_internal/core_ci.py
+++ b/test/lib/ansible_test/_internal/core_ci.py
@@ -28,7 +28,6 @@ from .io import (
from .util import (
ApplicationError,
display,
- ANSIBLE_TEST_TARGET_ROOT,
mutex,
)
@@ -292,18 +291,12 @@ class AnsibleCoreCI:
"""Start instance."""
display.info(f'Initializing new {self.label} instance using: {self._uri}', verbosity=1)
- if self.platform == 'windows':
- winrm_config = read_text_file(os.path.join(ANSIBLE_TEST_TARGET_ROOT, 'setup', 'ConfigureRemotingForAnsible.ps1'))
- else:
- winrm_config = None
-
data = dict(
config=dict(
platform=self.platform,
version=self.version,
architecture=self.arch,
public_key=self.ssh_key.pub_contents,
- winrm_config=winrm_config,
)
)
diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py
index ae640249..30176236 100644
--- a/test/lib/ansible_test/_internal/coverage_util.py
+++ b/test/lib/ansible_test/_internal/coverage_util.py
@@ -69,7 +69,8 @@ class CoverageVersion:
COVERAGE_VERSIONS = (
# IMPORTANT: Keep this in sync with the ansible-test.txt requirements file.
- CoverageVersion('6.5.0', 7, (3, 7), (3, 11)),
+ CoverageVersion('7.3.2', 7, (3, 8), (3, 12)),
+ CoverageVersion('6.5.0', 7, (3, 7), (3, 7)),
CoverageVersion('4.5.4', 0, (2, 6), (3, 6)),
)
"""
@@ -250,7 +251,9 @@ def generate_ansible_coverage_config() -> str:
coverage_config = '''
[run]
branch = True
-concurrency = multiprocessing
+concurrency =
+ multiprocessing
+ thread
parallel = True
omit =
@@ -271,7 +274,9 @@ def generate_collection_coverage_config(args: TestConfig) -> str:
coverage_config = '''
[run]
branch = True
-concurrency = multiprocessing
+concurrency =
+ multiprocessing
+ thread
parallel = True
disable_warnings =
no-data-collected
diff --git a/test/lib/ansible_test/_internal/delegation.py b/test/lib/ansible_test/_internal/delegation.py
index f9e54455..84896830 100644
--- a/test/lib/ansible_test/_internal/delegation.py
+++ b/test/lib/ansible_test/_internal/delegation.py
@@ -328,7 +328,6 @@ def filter_options(
) -> c.Iterable[str]:
"""Return an iterable that filters out unwanted CLI options and injects new ones as requested."""
replace: list[tuple[str, int, t.Optional[t.Union[bool, str, list[str]]]]] = [
- ('--docker-no-pull', 0, False),
('--truncate', 1, str(args.truncate)),
('--color', 1, 'yes' if args.color else 'no'),
('--redact', 0, False),
diff --git a/test/lib/ansible_test/_internal/diff.py b/test/lib/ansible_test/_internal/diff.py
index 2ddc2ff9..5a94aafc 100644
--- a/test/lib/ansible_test/_internal/diff.py
+++ b/test/lib/ansible_test/_internal/diff.py
@@ -143,7 +143,7 @@ class DiffParser:
traceback.format_exc(),
)
- raise ApplicationError(message.strip())
+ raise ApplicationError(message.strip()) from None
self.previous_line = self.line
diff --git a/test/lib/ansible_test/_internal/docker_util.py b/test/lib/ansible_test/_internal/docker_util.py
index 06f383b5..52b9691e 100644
--- a/test/lib/ansible_test/_internal/docker_util.py
+++ b/test/lib/ansible_test/_internal/docker_util.py
@@ -300,7 +300,7 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties:
options = ['--volume', '/sys/fs/cgroup:/probe:ro']
cmd = ['sh', '-c', ' && echo "-" && '.join(multi_line_commands)]
- stdout = run_utility_container(args, f'ansible-test-probe-{args.session_name}', cmd, options)[0]
+ stdout = run_utility_container(args, 'ansible-test-probe', cmd, options)[0]
if args.explain:
return ContainerHostProperties(
@@ -336,7 +336,7 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties:
cmd = ['sh', '-c', 'ulimit -Hn']
try:
- stdout = run_utility_container(args, f'ansible-test-ulimit-{args.session_name}', cmd, options)[0]
+ stdout = run_utility_container(args, 'ansible-test-ulimit', cmd, options)[0]
except SubprocessError as ex:
display.warning(str(ex))
else:
@@ -402,6 +402,11 @@ def detect_host_properties(args: CommonConfig) -> ContainerHostProperties:
return properties
+def get_session_container_name(args: CommonConfig, name: str) -> str:
+ """Return the given container name with the current test session name applied to it."""
+ return f'{name}-{args.session_name}'
+
+
def run_utility_container(
args: CommonConfig,
name: str,
@@ -410,6 +415,8 @@ def run_utility_container(
data: t.Optional[str] = None,
) -> tuple[t.Optional[str], t.Optional[str]]:
"""Run the specified command using the ansible-test utility container, returning stdout and stderr."""
+ name = get_session_container_name(args, name)
+
options = options + [
'--name', name,
'--rm',
diff --git a/test/lib/ansible_test/_internal/host_profiles.py b/test/lib/ansible_test/_internal/host_profiles.py
index a51eb693..09812456 100644
--- a/test/lib/ansible_test/_internal/host_profiles.py
+++ b/test/lib/ansible_test/_internal/host_profiles.py
@@ -99,7 +99,6 @@ from .ansible_util import (
)
from .containers import (
- CleanupMode,
HostType,
get_container_database,
run_support_container,
@@ -447,7 +446,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
@property
def label(self) -> str:
"""Label to apply to resources related to this profile."""
- return f'{"controller" if self.controller else "target"}-{self.args.session_name}'
+ return f'{"controller" if self.controller else "target"}'
def provision(self) -> None:
"""Provision the host before delegation."""
@@ -462,7 +461,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
ports=[22],
publish_ports=not self.controller, # connections to the controller over SSH are not required
options=init_config.options,
- cleanup=CleanupMode.NO,
+ cleanup=False,
cmd=self.build_init_command(init_config, init_probe),
)
@@ -807,6 +806,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
- Avoid hanging indefinitely or for an unreasonably long time.
NOTE: The container must have a POSIX-compliant default shell "sh" with a non-builtin "sleep" command.
+ The "sleep" command is invoked through "env" to avoid using a shell builtin "sleep" (if present).
"""
command = ''
@@ -814,7 +814,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
command += f'{init_config.command} && '
if sleep or init_config.command_privileged:
- command += 'sleep 60 ; '
+ command += 'env sleep 60 ; '
if not command:
return None
@@ -838,7 +838,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
"""Check the cgroup v1 systemd hierarchy to verify it is writeable for our container."""
probe_script = (read_text_file(os.path.join(ANSIBLE_TEST_TARGET_ROOT, 'setup', 'check_systemd_cgroup_v1.sh'))
.replace('@MARKER@', self.MARKER)
- .replace('@LABEL@', self.label))
+ .replace('@LABEL@', f'{self.label}-{self.args.session_name}'))
cmd = ['sh']
@@ -853,7 +853,7 @@ class DockerProfile(ControllerHostProfile[DockerConfig], SshTargetHostProfile[Do
def create_systemd_cgroup_v1(self) -> str:
"""Create a unique ansible-test cgroup in the v1 systemd hierarchy and return its path."""
- self.cgroup_path = f'/sys/fs/cgroup/systemd/ansible-test-{self.label}'
+ self.cgroup_path = f'/sys/fs/cgroup/systemd/ansible-test-{self.label}-{self.args.session_name}'
# Privileged mode is required to create the cgroup directories on some hosts, such as Fedora 36 and RHEL 9.0.
# The mkdir command will fail with "Permission denied" otherwise.
diff --git a/test/lib/ansible_test/_internal/http.py b/test/lib/ansible_test/_internal/http.py
index 8b4154bf..66afc60d 100644
--- a/test/lib/ansible_test/_internal/http.py
+++ b/test/lib/ansible_test/_internal/http.py
@@ -126,7 +126,7 @@ class HttpResponse:
try:
return json.loads(self.response)
except ValueError:
- raise HttpError(self.status_code, 'Cannot parse response to %s %s as JSON:\n%s' % (self.method, self.url, self.response))
+ raise HttpError(self.status_code, 'Cannot parse response to %s %s as JSON:\n%s' % (self.method, self.url, self.response)) from None
class HttpError(ApplicationError):
diff --git a/test/lib/ansible_test/_internal/junit_xml.py b/test/lib/ansible_test/_internal/junit_xml.py
index 76c8878b..8c4dba01 100644
--- a/test/lib/ansible_test/_internal/junit_xml.py
+++ b/test/lib/ansible_test/_internal/junit_xml.py
@@ -15,7 +15,7 @@ from xml.dom import minidom
from xml.etree import ElementTree as ET
-@dataclasses.dataclass # type: ignore[misc] # https://github.com/python/mypy/issues/5374
+@dataclasses.dataclass
class TestResult(metaclass=abc.ABCMeta):
"""Base class for the result of a test case."""
diff --git a/test/lib/ansible_test/_internal/pypi_proxy.py b/test/lib/ansible_test/_internal/pypi_proxy.py
index 5380dd9b..d119efa1 100644
--- a/test/lib/ansible_test/_internal/pypi_proxy.py
+++ b/test/lib/ansible_test/_internal/pypi_proxy.py
@@ -76,7 +76,7 @@ def run_pypi_proxy(args: EnvironmentConfig, targets_use_pypi: bool) -> None:
args=args,
context='__pypi_proxy__',
image=image,
- name=f'pypi-test-container-{args.session_name}',
+ name='pypi-test-container',
ports=[port],
)
diff --git a/test/lib/ansible_test/_internal/python_requirements.py b/test/lib/ansible_test/_internal/python_requirements.py
index 506b802c..81006e41 100644
--- a/test/lib/ansible_test/_internal/python_requirements.py
+++ b/test/lib/ansible_test/_internal/python_requirements.py
@@ -297,7 +297,7 @@ def run_pip(
connection.run([python.path], data=script, capture=True)
except SubprocessError as ex:
if 'pip is unavailable:' in ex.stdout + ex.stderr:
- raise PipUnavailableError(python)
+ raise PipUnavailableError(python) from None
raise
@@ -441,8 +441,8 @@ def get_venv_packages(python: PythonConfig) -> dict[str, str]:
# See: https://github.com/ansible/base-test-container/blob/main/files/installer.py
default_packages = dict(
- pip='21.3.1',
- setuptools='60.8.2',
+ pip='23.1.2',
+ setuptools='67.7.2',
wheel='0.37.1',
)
@@ -452,11 +452,6 @@ def get_venv_packages(python: PythonConfig) -> dict[str, str]:
setuptools='44.1.1', # 45.0.0 requires Python 3.5+
wheel=None,
),
- '3.5': dict(
- pip='20.3.4', # 21.0 requires Python 3.6+
- setuptools='50.3.2', # 51.0.0 requires Python 3.6+
- wheel=None,
- ),
'3.6': dict(
pip='21.3.1', # 22.0 requires Python 3.7+
setuptools='59.6.0', # 59.7.0 requires Python 3.7+
diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py
index 1859be5b..394c2632 100644
--- a/test/lib/ansible_test/_internal/util.py
+++ b/test/lib/ansible_test/_internal/util.py
@@ -31,11 +31,6 @@ from termios import TIOCGWINSZ
# CAUTION: Avoid third-party imports in this module whenever possible.
# Any third-party imports occurring here will result in an error if they are vendored by ansible-core.
-try:
- from typing_extensions import TypeGuard # TypeGuard was added in Python 3.10
-except ImportError:
- TypeGuard = None
-
from .locale_util import (
LOCALE_WARNING,
CONFIGURED_LOCALE,
@@ -436,7 +431,7 @@ def raw_command(
display.info(f'{description}: {escaped_cmd}', verbosity=cmd_verbosity, truncate=True)
display.info('Working directory: %s' % cwd, verbosity=2)
- program = find_executable(cmd[0], cwd=cwd, path=env['PATH'], required='warning')
+ program = find_executable(cmd[0], cwd=cwd, path=env['PATH'], required=False)
if program:
display.info('Program found: %s' % program, verbosity=2)
@@ -1155,7 +1150,7 @@ def verify_sys_executable(path: str) -> t.Optional[str]:
return expected_executable
-def type_guard(sequence: c.Sequence[t.Any], guard_type: t.Type[C]) -> TypeGuard[c.Sequence[C]]:
+def type_guard(sequence: c.Sequence[t.Any], guard_type: t.Type[C]) -> t.TypeGuard[c.Sequence[C]]:
"""
Raises an exception if any item in the given sequence does not match the specified guard type.
Use with assert so that type checkers are aware of the type guard.
diff --git a/test/lib/ansible_test/_internal/util_common.py b/test/lib/ansible_test/_internal/util_common.py
index 222366e4..77a6165c 100644
--- a/test/lib/ansible_test/_internal/util_common.py
+++ b/test/lib/ansible_test/_internal/util_common.py
@@ -88,7 +88,7 @@ class ExitHandler:
try:
func(*args, **kwargs)
- except BaseException as ex: # pylint: disable=broad-except
+ except BaseException as ex: # pylint: disable=broad-exception-caught
last_exception = ex
display.fatal(f'Exit handler failed: {ex}')
@@ -498,9 +498,14 @@ def run_command(
)
-def yamlcheck(python: PythonConfig) -> t.Optional[bool]:
+def yamlcheck(python: PythonConfig, explain: bool = False) -> t.Optional[bool]:
"""Return True if PyYAML has libyaml support, False if it does not and None if it was not found."""
- result = json.loads(raw_command([python.path, os.path.join(ANSIBLE_TEST_TARGET_TOOLS_ROOT, 'yamlcheck.py')], capture=True)[0])
+ stdout = raw_command([python.path, os.path.join(ANSIBLE_TEST_TARGET_TOOLS_ROOT, 'yamlcheck.py')], capture=True, explain=explain)[0]
+
+ if explain:
+ return None
+
+ result = json.loads(stdout)
if not result['yaml']:
return None
diff --git a/test/lib/ansible_test/_util/controller/sanity/code-smell/no-get-exception.json b/test/lib/ansible_test/_util/controller/sanity/code-smell/no-get-exception.json
index 88858aeb..da4a0b10 100644
--- a/test/lib/ansible_test/_util/controller/sanity/code-smell/no-get-exception.json
+++ b/test/lib/ansible_test/_util/controller/sanity/code-smell/no-get-exception.json
@@ -2,6 +2,10 @@
"extensions": [
".py"
],
+ "prefixes": [
+ "lib/ansible/",
+ "plugins/"
+ ],
"ignore_self": true,
"output": "path-line-column-message"
}
diff --git a/test/lib/ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json b/test/lib/ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json
index 88858aeb..da4a0b10 100644
--- a/test/lib/ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json
+++ b/test/lib/ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json
@@ -2,6 +2,10 @@
"extensions": [
".py"
],
+ "prefixes": [
+ "lib/ansible/",
+ "plugins/"
+ ],
"ignore_self": true,
"output": "path-line-column-message"
}
diff --git a/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py b/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
index 6cf27774..188d50fe 100644
--- a/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
+++ b/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
@@ -16,9 +16,19 @@ from voluptuous.humanize import humanize_error
from ansible.module_utils.compat.version import StrictVersion, LooseVersion
from ansible.module_utils.six import string_types
+from ansible.utils.collection_loader import AnsibleCollectionRef
from ansible.utils.version import SemanticVersion
+def fqcr(value):
+ """Validate a FQCR."""
+ if not isinstance(value, string_types):
+ raise Invalid('Must be a string that is a FQCR')
+ if not AnsibleCollectionRef.is_valid_fqcr(value):
+ raise Invalid('Must be a FQCR')
+ return value
+
+
def isodate(value, check_deprecation_date=False, is_tombstone=False):
"""Validate a datetime.date or ISO 8601 date string."""
# datetime.date objects come from YAML dates, these are ok
@@ -126,12 +136,15 @@ def validate_metadata_file(path, is_ansible, check_deprecation_dates=False):
with open(path, 'r', encoding='utf-8') as f_path:
routing = yaml.safe_load(f_path)
except yaml.error.MarkedYAMLError as ex:
- print('%s:%d:%d: YAML load failed: %s' % (path, ex.context_mark.line +
- 1, ex.context_mark.column + 1, re.sub(r'\s+', ' ', str(ex))))
+ print('%s:%d:%d: YAML load failed: %s' % (
+ path,
+ ex.context_mark.line + 1 if ex.context_mark else 0,
+ ex.context_mark.column + 1 if ex.context_mark else 0,
+ re.sub(r'\s+', ' ', str(ex)),
+ ))
return
except Exception as ex: # pylint: disable=broad-except
- print('%s:%d:%d: YAML load failed: %s' %
- (path, 0, 0, re.sub(r'\s+', ' ', str(ex))))
+ print('%s:%d:%d: YAML load failed: %s' % (path, 0, 0, re.sub(r'\s+', ' ', str(ex))))
return
if is_ansible:
@@ -184,17 +197,37 @@ def validate_metadata_file(path, is_ansible, check_deprecation_dates=False):
avoid_additional_data
)
- plugin_routing_schema = Any(
- Schema({
- ('deprecation'): Any(deprecation_schema),
- ('tombstone'): Any(tombstoning_schema),
- ('redirect'): Any(*string_types),
- }, extra=PREVENT_EXTRA),
+ plugins_routing_common_schema = Schema({
+ ('deprecation'): Any(deprecation_schema),
+ ('tombstone'): Any(tombstoning_schema),
+ ('redirect'): fqcr,
+ }, extra=PREVENT_EXTRA)
+
+ plugin_routing_schema = Any(plugins_routing_common_schema)
+
+ # Adjusted schema for modules only
+ plugin_routing_schema_modules = Any(
+ plugins_routing_common_schema.extend({
+ ('action_plugin'): fqcr}
+ )
+ )
+
+ # Adjusted schema for module_utils
+ plugin_routing_schema_mu = Any(
+ plugins_routing_common_schema.extend({
+ ('redirect'): Any(*string_types)}
+ ),
)
list_dict_plugin_routing_schema = [{str_type: plugin_routing_schema}
for str_type in string_types]
+ list_dict_plugin_routing_schema_mu = [{str_type: plugin_routing_schema_mu}
+ for str_type in string_types]
+
+ list_dict_plugin_routing_schema_modules = [{str_type: plugin_routing_schema_modules}
+ for str_type in string_types]
+
plugin_schema = Schema({
('action'): Any(None, *list_dict_plugin_routing_schema),
('become'): Any(None, *list_dict_plugin_routing_schema),
@@ -207,8 +240,8 @@ def validate_metadata_file(path, is_ansible, check_deprecation_dates=False):
('httpapi'): Any(None, *list_dict_plugin_routing_schema),
('inventory'): Any(None, *list_dict_plugin_routing_schema),
('lookup'): Any(None, *list_dict_plugin_routing_schema),
- ('module_utils'): Any(None, *list_dict_plugin_routing_schema),
- ('modules'): Any(None, *list_dict_plugin_routing_schema),
+ ('module_utils'): Any(None, *list_dict_plugin_routing_schema_mu),
+ ('modules'): Any(None, *list_dict_plugin_routing_schema_modules),
('netconf'): Any(None, *list_dict_plugin_routing_schema),
('shell'): Any(None, *list_dict_plugin_routing_schema),
('strategy'): Any(None, *list_dict_plugin_routing_schema),
diff --git a/test/lib/ansible_test/_util/controller/sanity/code-smell/use-compat-six.json b/test/lib/ansible_test/_util/controller/sanity/code-smell/use-compat-six.json
index 776590b7..ccee80a2 100644
--- a/test/lib/ansible_test/_util/controller/sanity/code-smell/use-compat-six.json
+++ b/test/lib/ansible_test/_util/controller/sanity/code-smell/use-compat-six.json
@@ -2,5 +2,9 @@
"extensions": [
".py"
],
+ "prefixes": [
+ "lib/ansible/",
+ "plugins/"
+ ],
"output": "path-line-column-message"
}
diff --git a/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-core.ini b/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-core.ini
index 4d93f359..41d824b2 100644
--- a/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-core.ini
+++ b/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-core.ini
@@ -34,6 +34,9 @@ ignore_missing_imports = True
[mypy-md5.*]
ignore_missing_imports = True
+[mypy-imp.*]
+ignore_missing_imports = True
+
[mypy-scp.*]
ignore_missing_imports = True
diff --git a/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-test.ini b/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-test.ini
index 55738f87..6be35724 100644
--- a/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-test.ini
+++ b/test/lib/ansible_test/_util/controller/sanity/mypy/ansible-test.ini
@@ -6,10 +6,10 @@
# There are ~350 errors reported in ansible-test when strict optional checking is enabled.
# Until the number of occurrences are greatly reduced, it's better to disable strict checking.
strict_optional = False
-# There are ~25 errors reported in ansible-test under the 'misc' code.
-# The majority of those errors are "Only concrete class can be given", which is due to a limitation of mypy.
-# See: https://github.com/python/mypy/issues/5374
-disable_error_code = misc
+# There are ~13 type-abstract errors reported in ansible-test.
+# This is due to assumptions mypy makes about Type and abstract types.
+# See: https://discuss.python.org/t/add-abstracttype-to-the-typing-module/21996/13
+disable_error_code = type-abstract
[mypy-argcomplete]
ignore_missing_imports = True
diff --git a/test/lib/ansible_test/_util/controller/sanity/pep8/current-ignore.txt b/test/lib/ansible_test/_util/controller/sanity/pep8/current-ignore.txt
index 659c7f59..4d1de692 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pep8/current-ignore.txt
+++ b/test/lib/ansible_test/_util/controller/sanity/pep8/current-ignore.txt
@@ -2,3 +2,8 @@ E402
W503
W504
E741
+
+# The E203 rule is not PEP 8 compliant.
+# Unfortunately this means it also conflicts with the output from `black`.
+# See: https://github.com/PyCQA/pycodestyle/issues/373
+E203
diff --git a/test/lib/ansible_test/_util/controller/sanity/pslint/settings.psd1 b/test/lib/ansible_test/_util/controller/sanity/pslint/settings.psd1
index 2ae13b4c..7beb38c1 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pslint/settings.psd1
+++ b/test/lib/ansible_test/_util/controller/sanity/pslint/settings.psd1
@@ -4,6 +4,9 @@
Enable = $true
MaximumLineLength = 160
}
+ PSAvoidSemicolonsAsLineTerminators = @{
+ Enable = $true
+ }
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg b/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg
index aa347729..f8a0a8af 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg
@@ -10,6 +10,7 @@ disable=
raise-missing-from, # Python 2.x does not support raise from
super-with-arguments, # Python 2.x does not support super without arguments
redundant-u-string-prefix, # Python 2.x support still required
+ broad-exception-raised, # many exceptions with no need for a custom type
too-few-public-methods,
too-many-arguments,
too-many-branches,
@@ -19,6 +20,7 @@ disable=
too-many-nested-blocks,
too-many-return-statements,
too-many-statements,
+ use-dict-literal, # ignoring as a common style issue
useless-return, # complains about returning None when the return type is optional
[BASIC]
@@ -55,3 +57,5 @@ preferred-modules =
# Listing them here makes it possible to enable the import-error check.
ignored-modules =
py,
+ pytest,
+ _pytest.runner,
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg b/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg
index 1c03472c..5bec36fd 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg
@@ -7,7 +7,7 @@ disable=
deprecated-module, # results vary by Python version
duplicate-code, # consistent results require running with --jobs 1 and testing all files
import-outside-toplevel, # common pattern in ansible related code
- raise-missing-from, # Python 2.x does not support raise from
+ broad-exception-raised, # many exceptions with no need for a custom type
too-few-public-methods,
too-many-public-methods,
too-many-arguments,
@@ -18,6 +18,7 @@ disable=
too-many-nested-blocks,
too-many-return-statements,
too-many-statements,
+ use-dict-literal, # ignoring as a common style issue
unspecified-encoding, # always run with UTF-8 encoding enforced
useless-return, # complains about returning None when the return type is optional
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg b/test/lib/ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg
index e3aa8eed..c30eb37a 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg
@@ -17,6 +17,7 @@ disable=
too-many-nested-blocks,
too-many-return-statements,
too-many-statements,
+ use-dict-literal, # ignoring as a common style issue
unspecified-encoding, # always run with UTF-8 encoding enforced
useless-return, # complains about returning None when the return type is optional
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/config/collection.cfg b/test/lib/ansible_test/_util/controller/sanity/pylint/config/collection.cfg
index 38b8d2d0..762d488d 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/config/collection.cfg
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/config/collection.cfg
@@ -9,7 +9,8 @@ disable=
attribute-defined-outside-init,
bad-indentation,
bad-mcs-classmethod-argument,
- broad-except,
+ broad-exception-caught,
+ broad-exception-raised,
c-extension-no-member,
cell-var-from-loop,
chained-comparison,
@@ -29,6 +30,7 @@ disable=
consider-using-max-builtin,
consider-using-min-builtin,
cyclic-import, # consistent results require running with --jobs 1 and testing all files
+ deprecated-comment, # custom plugin only used by ansible-core, not collections
deprecated-method, # results vary by Python version
deprecated-module, # results vary by Python version
duplicate-code, # consistent results require running with --jobs 1 and testing all files
@@ -95,8 +97,6 @@ disable=
too-many-public-methods,
too-many-return-statements,
too-many-statements,
- trailing-comma-tuple,
- trailing-comma-tuple,
try-except-raise,
unbalanced-tuple-unpacking,
undefined-loop-variable,
@@ -110,10 +110,9 @@ disable=
unsupported-delete-operation,
unsupported-membership-test,
unused-argument,
- unused-import,
unused-variable,
unspecified-encoding, # always run with UTF-8 encoding enforced
- use-dict-literal, # many occurrences
+ use-dict-literal, # ignoring as a common style issue
use-list-literal, # many occurrences
use-implicit-booleaness-not-comparison, # many occurrences
useless-object-inheritance,
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/config/default.cfg b/test/lib/ansible_test/_util/controller/sanity/pylint/config/default.cfg
index 6a242b8d..825e5df7 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/config/default.cfg
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/config/default.cfg
@@ -10,7 +10,8 @@ disable=
attribute-defined-outside-init,
bad-indentation,
bad-mcs-classmethod-argument,
- broad-except,
+ broad-exception-caught,
+ broad-exception-raised,
c-extension-no-member,
cell-var-from-loop,
chained-comparison,
@@ -61,8 +62,6 @@ disable=
not-a-mapping,
not-an-iterable,
not-callable,
- pointless-statement,
- pointless-string-statement,
possibly-unused-variable,
protected-access,
raise-missing-from, # Python 2.x does not support raise from
@@ -91,8 +90,6 @@ disable=
too-many-public-methods,
too-many-return-statements,
too-many-statements,
- trailing-comma-tuple,
- trailing-comma-tuple,
try-except-raise,
unbalanced-tuple-unpacking,
undefined-loop-variable,
@@ -105,10 +102,9 @@ disable=
unsupported-delete-operation,
unsupported-membership-test,
unused-argument,
- unused-import,
unused-variable,
unspecified-encoding, # always run with UTF-8 encoding enforced
- use-dict-literal, # many occurrences
+ use-dict-literal, # ignoring as a common style issue
use-list-literal, # many occurrences
use-implicit-booleaness-not-comparison, # many occurrences
useless-object-inheritance,
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py
index 79b8bf15..f6c83373 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py
@@ -5,14 +5,31 @@
from __future__ import annotations
import datetime
+import functools
+import json
import re
+import shlex
import typing as t
+from tokenize import COMMENT, TokenInfo
import astroid
-from pylint.interfaces import IAstroidChecker
-from pylint.checkers import BaseChecker
-from pylint.checkers.utils import check_messages
+# support pylint 2.x and 3.x -- remove when supporting only 3.x
+try:
+ from pylint.interfaces import IAstroidChecker, ITokenChecker
+except ImportError:
+ class IAstroidChecker:
+ """Backwards compatibility for 2.x / 3.x support."""
+
+ class ITokenChecker:
+ """Backwards compatibility for 2.x / 3.x support."""
+
+try:
+ from pylint.checkers.utils import check_messages
+except ImportError:
+ from pylint.checkers.utils import only_required_for_messages as check_messages
+
+from pylint.checkers import BaseChecker, BaseTokenChecker
from ansible.module_utils.compat.version import LooseVersion
from ansible.module_utils.six import string_types
@@ -95,7 +112,7 @@ ANSIBLE_VERSION = LooseVersion('.'.join(ansible_version_raw.split('.')[:3]))
def _get_expr_name(node):
- """Funciton to get either ``attrname`` or ``name`` from ``node.func.expr``
+ """Function to get either ``attrname`` or ``name`` from ``node.func.expr``
Created specifically for the case of ``display.deprecated`` or ``self._display.deprecated``
"""
@@ -106,6 +123,17 @@ def _get_expr_name(node):
return node.func.expr.name
+def _get_func_name(node):
+ """Function to get either ``attrname`` or ``name`` from ``node.func``
+
+ Created specifically for the case of ``from ansible.module_utils.common.warnings import deprecate``
+ """
+ try:
+ return node.func.attrname
+ except AttributeError:
+ return node.func.name
+
+
def parse_isodate(value):
"""Parse an ISO 8601 date string."""
msg = 'Expected ISO 8601 date string (YYYY-MM-DD)'
@@ -118,7 +146,7 @@ def parse_isodate(value):
try:
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
except ValueError:
- raise ValueError(msg)
+ raise ValueError(msg) from None
class AnsibleDeprecatedChecker(BaseChecker):
@@ -160,6 +188,8 @@ class AnsibleDeprecatedChecker(BaseChecker):
self.add_message('ansible-deprecated-date', node=node, args=(date,))
def _check_version(self, node, version, collection_name):
+ if collection_name is None:
+ collection_name = 'ansible.builtin'
if not isinstance(version, (str, float)):
if collection_name == 'ansible.builtin':
symbol = 'ansible-invalid-deprecated-version'
@@ -197,12 +227,17 @@ class AnsibleDeprecatedChecker(BaseChecker):
@property
def collection_name(self) -> t.Optional[str]:
"""Return the collection name, or None if ansible-core is being tested."""
- return self.config.collection_name
+ return self.linter.config.collection_name
@property
def collection_version(self) -> t.Optional[SemanticVersion]:
"""Return the collection version, or None if ansible-core is being tested."""
- return SemanticVersion(self.config.collection_version) if self.config.collection_version is not None else None
+ if self.linter.config.collection_version is None:
+ return None
+ sem_ver = SemanticVersion(self.linter.config.collection_version)
+ # Ignore pre-release for version comparison to catch issues before the final release is cut.
+ sem_ver.prerelease = ()
+ return sem_ver
@check_messages(*(MSGS.keys()))
def visit_call(self, node):
@@ -211,8 +246,9 @@ class AnsibleDeprecatedChecker(BaseChecker):
date = None
collection_name = None
try:
- if (node.func.attrname == 'deprecated' and 'display' in _get_expr_name(node) or
- node.func.attrname == 'deprecate' and _get_expr_name(node)):
+ funcname = _get_func_name(node)
+ if (funcname == 'deprecated' and 'display' in _get_expr_name(node) or
+ funcname == 'deprecate'):
if node.keywords:
for keyword in node.keywords:
if len(node.keywords) == 1 and keyword.arg is None:
@@ -258,6 +294,137 @@ class AnsibleDeprecatedChecker(BaseChecker):
pass
+class AnsibleDeprecatedCommentChecker(BaseTokenChecker):
+ """Checks for ``# deprecated:`` comments to ensure that the ``version``
+ has not passed or met the time for removal
+ """
+
+ __implements__ = (ITokenChecker,)
+
+ name = 'deprecated-comment'
+ msgs = {
+ 'E9601': ("Deprecated core version (%r) found: %s",
+ "ansible-deprecated-version-comment",
+ "Used when a '# deprecated:' comment specifies a version "
+ "less than or equal to the current version of Ansible",
+ {'minversion': (2, 6)}),
+ 'E9602': ("Deprecated comment contains invalid keys %r",
+ "ansible-deprecated-version-comment-invalid-key",
+ "Used when a '#deprecated:' comment specifies invalid data",
+ {'minversion': (2, 6)}),
+ 'E9603': ("Deprecated comment missing version",
+ "ansible-deprecated-version-comment-missing-version",
+ "Used when a '#deprecated:' comment specifies invalid data",
+ {'minversion': (2, 6)}),
+ 'E9604': ("Deprecated python version (%r) found: %s",
+ "ansible-deprecated-python-version-comment",
+ "Used when a '#deprecated:' comment specifies a python version "
+ "less than or equal to the minimum python version",
+ {'minversion': (2, 6)}),
+ 'E9605': ("Deprecated comment contains invalid version %r: %s",
+ "ansible-deprecated-version-comment-invalid-version",
+ "Used when a '#deprecated:' comment specifies an invalid version",
+ {'minversion': (2, 6)}),
+ }
+
+ options = (
+ ('min-python-version-db', {
+ 'default': None,
+ 'type': 'string',
+ 'metavar': '<path>',
+ 'help': 'The path to the DB mapping paths to minimum Python versions.',
+ }),
+ )
+
+ def process_tokens(self, tokens: list[TokenInfo]) -> None:
+ for token in tokens:
+ if token.type == COMMENT:
+ self._process_comment(token)
+
+ def _deprecated_string_to_dict(self, token: TokenInfo, string: str) -> dict[str, str]:
+ valid_keys = {'description', 'core_version', 'python_version'}
+ data = dict.fromkeys(valid_keys)
+ for opt in shlex.split(string):
+ if '=' not in opt:
+ data[opt] = None
+ continue
+ key, _sep, value = opt.partition('=')
+ data[key] = value
+ if not any((data['core_version'], data['python_version'])):
+ self.add_message(
+ 'ansible-deprecated-version-comment-missing-version',
+ line=token.start[0],
+ col_offset=token.start[1],
+ )
+ bad = set(data).difference(valid_keys)
+ if bad:
+ self.add_message(
+ 'ansible-deprecated-version-comment-invalid-key',
+ line=token.start[0],
+ col_offset=token.start[1],
+ args=(','.join(bad),)
+ )
+ return data
+
+ @functools.cached_property
+ def _min_python_version_db(self) -> dict[str, str]:
+ """A dictionary of absolute file paths and their minimum required Python version."""
+ with open(self.linter.config.min_python_version_db) as db_file:
+ return json.load(db_file)
+
+ def _process_python_version(self, token: TokenInfo, data: dict[str, str]) -> None:
+ current_file = self.linter.current_file
+ check_version = self._min_python_version_db[current_file]
+
+ try:
+ if LooseVersion(data['python_version']) < LooseVersion(check_version):
+ self.add_message(
+ 'ansible-deprecated-python-version-comment',
+ line=token.start[0],
+ col_offset=token.start[1],
+ args=(
+ data['python_version'],
+ data['description'] or 'description not provided',
+ ),
+ )
+ except (ValueError, TypeError) as exc:
+ self.add_message(
+ 'ansible-deprecated-version-comment-invalid-version',
+ line=token.start[0],
+ col_offset=token.start[1],
+ args=(data['python_version'], exc)
+ )
+
+ def _process_core_version(self, token: TokenInfo, data: dict[str, str]) -> None:
+ try:
+ if ANSIBLE_VERSION >= LooseVersion(data['core_version']):
+ self.add_message(
+ 'ansible-deprecated-version-comment',
+ line=token.start[0],
+ col_offset=token.start[1],
+ args=(
+ data['core_version'],
+ data['description'] or 'description not provided',
+ )
+ )
+ except (ValueError, TypeError) as exc:
+ self.add_message(
+ 'ansible-deprecated-version-comment-invalid-version',
+ line=token.start[0],
+ col_offset=token.start[1],
+ args=(data['core_version'], exc)
+ )
+
+ def _process_comment(self, token: TokenInfo) -> None:
+ if token.string.startswith('# deprecated:'):
+ data = self._deprecated_string_to_dict(token, token.string[13:].strip())
+ if data['core_version']:
+ self._process_core_version(token, data)
+ if data['python_version']:
+ self._process_python_version(token, data)
+
+
def register(linter):
"""required method to auto register this checker """
linter.register_checker(AnsibleDeprecatedChecker(linter))
+ linter.register_checker(AnsibleDeprecatedCommentChecker(linter))
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py
index 934a9ae7..83c27734 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/string_format.py
@@ -5,23 +5,26 @@
from __future__ import annotations
import astroid
-from pylint.interfaces import IAstroidChecker
-from pylint.checkers import BaseChecker
-from pylint.checkers import utils
-from pylint.checkers.utils import check_messages
+
+# support pylint 2.x and 3.x -- remove when supporting only 3.x
+try:
+ from pylint.interfaces import IAstroidChecker
+except ImportError:
+ class IAstroidChecker:
+ """Backwards compatibility for 2.x / 3.x support."""
+
try:
- from pylint.checkers.utils import parse_format_method_string
+ from pylint.checkers.utils import check_messages
except ImportError:
- # noinspection PyUnresolvedReferences
- from pylint.checkers.strings import parse_format_method_string
+ from pylint.checkers.utils import only_required_for_messages as check_messages
+
+from pylint.checkers import BaseChecker
+from pylint.checkers import utils
MSGS = {
- 'E9305': ("Format string contains automatic field numbering "
- "specification",
+ 'E9305': ("disabled", # kept for backwards compatibility with inline ignores, remove after 2.14 is EOL
"ansible-format-automatic-specification",
- "Used when a PEP 3101 format string contains automatic "
- "field numbering (e.g. '{}').",
- {'minversion': (2, 6)}),
+ "disabled"),
'E9390': ("bytes object has no .format attribute",
"ansible-no-format-on-bytestring",
"Used when a bytestring was used as a PEP 3101 format string "
@@ -64,20 +67,6 @@ class AnsibleStringFormatChecker(BaseChecker):
if isinstance(strnode.value, bytes):
self.add_message('ansible-no-format-on-bytestring', node=node)
return
- if not isinstance(strnode.value, str):
- return
-
- if node.starargs or node.kwargs:
- return
- try:
- num_args = parse_format_method_string(strnode.value)[1]
- except utils.IncompleteFormatString:
- return
-
- if num_args:
- self.add_message('ansible-format-automatic-specification',
- node=node)
- return
def register(linter):
diff --git a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py
index 1be42f51..f121ea58 100644
--- a/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py
+++ b/test/lib/ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py
@@ -6,8 +6,14 @@ import typing as t
import astroid
+# support pylint 2.x and 3.x -- remove when supporting only 3.x
+try:
+ from pylint.interfaces import IAstroidChecker
+except ImportError:
+ class IAstroidChecker:
+ """Backwards compatibility for 2.x / 3.x support."""
+
from pylint.checkers import BaseChecker
-from pylint.interfaces import IAstroidChecker
ANSIBLE_TEST_MODULES_PATH = os.environ['ANSIBLE_TEST_MODULES_PATH']
ANSIBLE_TEST_MODULE_UTILS_PATH = os.environ['ANSIBLE_TEST_MODULE_UTILS_PATH']
@@ -94,10 +100,7 @@ class AnsibleUnwantedChecker(BaseChecker):
)),
# see https://docs.python.org/3/library/collections.abc.html
- collections=UnwantedEntry('ansible.module_utils.common._collections_compat',
- ignore_paths=(
- '/lib/ansible/module_utils/common/_collections_compat.py',
- ),
+ collections=UnwantedEntry('ansible.module_utils.six.moves.collections_abc',
names=(
'MappingView',
'ItemsView',
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
index 25c61798..2b92a56c 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py
@@ -33,6 +33,9 @@ from collections.abc import Mapping
from contextlib import contextmanager
from fnmatch import fnmatch
+from antsibull_docs_parser import dom
+from antsibull_docs_parser.parser import parse, Context
+
import yaml
from voluptuous.humanize import humanize_error
@@ -63,6 +66,7 @@ setup_collection_loader()
from ansible import __version__ as ansible_version
from ansible.executor.module_common import REPLACER_WINDOWS, NEW_STYLE_PYTHON_MODULE_RE
+from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.common.parameters import DEFAULT_TYPE_VALIDATORS
from ansible.module_utils.compat.version import StrictVersion, LooseVersion
from ansible.module_utils.basic import to_bytes
@@ -74,9 +78,13 @@ from ansible.utils.version import SemanticVersion
from .module_args import AnsibleModuleImportError, AnsibleModuleNotInitialized, get_argument_spec
-from .schema import ansible_module_kwargs_schema, doc_schema, return_schema
+from .schema import (
+ ansible_module_kwargs_schema,
+ doc_schema,
+ return_schema,
+)
-from .utils import CaptureStd, NoArgsAnsibleModule, compare_unordered_lists, is_empty, parse_yaml, parse_isodate
+from .utils import CaptureStd, NoArgsAnsibleModule, compare_unordered_lists, parse_yaml, parse_isodate
if PY3:
@@ -297,8 +305,6 @@ class ModuleValidator(Validator):
# win_dsc is a dynamic arg spec, the docs won't ever match
PS_ARG_VALIDATE_REJECTLIST = frozenset(('win_dsc.ps1', ))
- ACCEPTLIST_FUTURE_IMPORTS = frozenset(('absolute_import', 'division', 'print_function'))
-
def __init__(self, path, git_cache: GitCache, analyze_arg_spec=False, collection=None, collection_version=None,
reporter=None, routing=None, plugin_type='module'):
super(ModuleValidator, self).__init__(reporter=reporter or Reporter())
@@ -401,13 +407,10 @@ class ModuleValidator(Validator):
if isinstance(child, ast.Expr) and isinstance(child.value, ast.Constant) and isinstance(child.value.value, str):
continue
- # allowed from __future__ imports
+ # allow __future__ imports (the specific allowed imports are checked by other sanity tests)
if isinstance(child, ast.ImportFrom) and child.module == '__future__':
- for future_import in child.names:
- if future_import.name not in self.ACCEPTLIST_FUTURE_IMPORTS:
- break
- else:
- continue
+ continue
+
return False
return True
except AttributeError:
@@ -636,29 +639,21 @@ class ModuleValidator(Validator):
)
def _ensure_imports_below_docs(self, doc_info, first_callable):
- min_doc_line = min(doc_info[key]['lineno'] for key in doc_info)
+ doc_line_numbers = [lineno for lineno in (doc_info[key]['lineno'] for key in doc_info) if lineno > 0]
+
+ min_doc_line = min(doc_line_numbers) if doc_line_numbers else None
max_doc_line = max(doc_info[key]['end_lineno'] for key in doc_info)
import_lines = []
for child in self.ast.body:
if isinstance(child, (ast.Import, ast.ImportFrom)):
+ # allow __future__ imports (the specific allowed imports are checked by other sanity tests)
if isinstance(child, ast.ImportFrom) and child.module == '__future__':
- # allowed from __future__ imports
- for future_import in child.names:
- if future_import.name not in self.ACCEPTLIST_FUTURE_IMPORTS:
- self.reporter.error(
- path=self.object_path,
- code='illegal-future-imports',
- msg=('Only the following from __future__ imports are allowed: %s'
- % ', '.join(self.ACCEPTLIST_FUTURE_IMPORTS)),
- line=child.lineno
- )
- break
- else: # for-else. If we didn't find a problem nad break out of the loop, then this is a legal import
- continue
+ continue
+
import_lines.append(child.lineno)
- if child.lineno < min_doc_line:
+ if min_doc_line and child.lineno < min_doc_line:
self.reporter.error(
path=self.object_path,
code='import-before-documentation',
@@ -675,7 +670,7 @@ class ModuleValidator(Validator):
for grandchild in bodies:
if isinstance(grandchild, (ast.Import, ast.ImportFrom)):
import_lines.append(grandchild.lineno)
- if grandchild.lineno < min_doc_line:
+ if min_doc_line and grandchild.lineno < min_doc_line:
self.reporter.error(
path=self.object_path,
code='import-before-documentation',
@@ -813,22 +808,22 @@ class ModuleValidator(Validator):
continue
if grandchild.id == 'DOCUMENTATION':
- docs['DOCUMENTATION']['value'] = child.value.s
+ docs['DOCUMENTATION']['value'] = child.value.value
docs['DOCUMENTATION']['lineno'] = child.lineno
docs['DOCUMENTATION']['end_lineno'] = (
- child.lineno + len(child.value.s.splitlines())
+ child.lineno + len(child.value.value.splitlines())
)
elif grandchild.id == 'EXAMPLES':
- docs['EXAMPLES']['value'] = child.value.s
+ docs['EXAMPLES']['value'] = child.value.value
docs['EXAMPLES']['lineno'] = child.lineno
docs['EXAMPLES']['end_lineno'] = (
- child.lineno + len(child.value.s.splitlines())
+ child.lineno + len(child.value.value.splitlines())
)
elif grandchild.id == 'RETURN':
- docs['RETURN']['value'] = child.value.s
+ docs['RETURN']['value'] = child.value.value
docs['RETURN']['lineno'] = child.lineno
docs['RETURN']['end_lineno'] = (
- child.lineno + len(child.value.s.splitlines())
+ child.lineno + len(child.value.value.splitlines())
)
return docs
@@ -1041,6 +1036,8 @@ class ModuleValidator(Validator):
'invalid-documentation',
)
+ self._validate_all_semantic_markup(doc, returns)
+
if not self.collection:
existing_doc = self._check_for_new_args(doc)
self._check_version_added(doc, existing_doc)
@@ -1166,6 +1163,113 @@ class ModuleValidator(Validator):
return doc_info, doc
+ def _check_sem_option(self, part: dom.OptionNamePart, current_plugin: dom.PluginIdentifier) -> None:
+ if part.plugin is None or part.plugin != current_plugin:
+ return
+ if part.entrypoint is not None:
+ return
+ if tuple(part.link) not in self._all_options:
+ self.reporter.error(
+ path=self.object_path,
+ code='invalid-documentation-markup',
+ msg='Directive "%s" contains a non-existing option "%s"' % (part.source, part.name)
+ )
+
+ def _check_sem_return_value(self, part: dom.ReturnValuePart, current_plugin: dom.PluginIdentifier) -> None:
+ if part.plugin is None or part.plugin != current_plugin:
+ return
+ if part.entrypoint is not None:
+ return
+ if tuple(part.link) not in self._all_return_values:
+ self.reporter.error(
+ path=self.object_path,
+ code='invalid-documentation-markup',
+ msg='Directive "%s" contains a non-existing return value "%s"' % (part.source, part.name)
+ )
+
+ def _validate_semantic_markup(self, object) -> None:
+ # Make sure we operate on strings
+ if is_iterable(object):
+ for entry in object:
+ self._validate_semantic_markup(entry)
+ return
+ if not isinstance(object, string_types):
+ return
+
+ if self.collection:
+ fqcn = f'{self.collection_name}.{self.name}'
+ else:
+ fqcn = f'ansible.builtin.{self.name}'
+ current_plugin = dom.PluginIdentifier(fqcn=fqcn, type=self.plugin_type)
+ for par in parse(object, Context(current_plugin=current_plugin), errors='message', add_source=True):
+ for part in par:
+ # Errors are already covered during schema validation, we only check for option and
+ # return value references
+ if part.type == dom.PartType.OPTION_NAME:
+ self._check_sem_option(part, current_plugin)
+ if part.type == dom.PartType.RETURN_VALUE:
+ self._check_sem_return_value(part, current_plugin)
+
+ def _validate_semantic_markup_collect(self, destination, sub_key, data, all_paths):
+ if not isinstance(data, dict):
+ return
+ for key, value in data.items():
+ if not isinstance(value, dict):
+ continue
+ keys = {key}
+ if is_iterable(value.get('aliases')):
+ keys.update(value['aliases'])
+ new_paths = [path + [key] for path in all_paths for key in keys]
+ destination.update([tuple(path) for path in new_paths])
+ self._validate_semantic_markup_collect(destination, sub_key, value.get(sub_key), new_paths)
+
+ def _validate_semantic_markup_options(self, options):
+ if not isinstance(options, dict):
+ return
+ for key, value in options.items():
+ self._validate_semantic_markup(value.get('description'))
+ self._validate_semantic_markup_options(value.get('suboptions'))
+
+ def _validate_semantic_markup_return_values(self, return_vars):
+ if not isinstance(return_vars, dict):
+ return
+ for key, value in return_vars.items():
+ self._validate_semantic_markup(value.get('description'))
+ self._validate_semantic_markup(value.get('returned'))
+ self._validate_semantic_markup_return_values(value.get('contains'))
+
+ def _validate_all_semantic_markup(self, docs, return_docs):
+ if not isinstance(docs, dict):
+ docs = {}
+ if not isinstance(return_docs, dict):
+ return_docs = {}
+
+ self._all_options = set()
+ self._all_return_values = set()
+ self._validate_semantic_markup_collect(self._all_options, 'suboptions', docs.get('options'), [[]])
+ self._validate_semantic_markup_collect(self._all_return_values, 'contains', return_docs, [[]])
+
+ for string_keys in ('short_description', 'description', 'notes', 'requirements', 'todo'):
+ self._validate_semantic_markup(docs.get(string_keys))
+
+ if is_iterable(docs.get('seealso')):
+ for entry in docs.get('seealso'):
+ if isinstance(entry, dict):
+ self._validate_semantic_markup(entry.get('description'))
+
+ if isinstance(docs.get('attributes'), dict):
+ for entry in docs.get('attributes').values():
+ if isinstance(entry, dict):
+ for key in ('description', 'details'):
+ self._validate_semantic_markup(entry.get(key))
+
+ if isinstance(docs.get('deprecated'), dict):
+ for key in ('why', 'alternative'):
+ self._validate_semantic_markup(docs.get('deprecated').get(key))
+
+ self._validate_semantic_markup_options(docs.get('options'))
+ self._validate_semantic_markup_return_values(return_docs)
+
def _check_version_added(self, doc, existing_doc):
version_added_raw = doc.get('version_added')
try:
@@ -1233,6 +1337,31 @@ class ModuleValidator(Validator):
self._validate_argument_spec(docs, spec, kwargs)
+ if isinstance(docs, Mapping) and isinstance(docs.get('attributes'), Mapping):
+ if isinstance(docs['attributes'].get('check_mode'), Mapping):
+ support_value = docs['attributes']['check_mode'].get('support')
+ if not kwargs.get('supports_check_mode', False):
+ if support_value != 'none':
+ self.reporter.error(
+ path=self.object_path,
+ code='attributes-check-mode',
+ msg="The module does not declare support for check mode, but the check_mode attribute's"
+ " support value is '%s' and not 'none'" % support_value
+ )
+ else:
+ if support_value not in ('full', 'partial', 'N/A'):
+ self.reporter.error(
+ path=self.object_path,
+ code='attributes-check-mode',
+ msg="The module does declare support for check mode, but the check_mode attribute's support value is '%s'" % support_value
+ )
+ if support_value in ('partial', 'N/A') and docs['attributes']['check_mode'].get('details') in (None, '', []):
+ self.reporter.error(
+ path=self.object_path,
+ code='attributes-check-mode-details',
+ msg="The module declares it does not fully support check mode, but has no details on what exactly that means"
+ )
+
def _validate_list_of_module_args(self, name, terms, spec, context):
if terms is None:
return
@@ -1748,7 +1877,7 @@ class ModuleValidator(Validator):
)
arg_default = None
- if 'default' in data and not is_empty(data['default']):
+ if 'default' in data and data['default'] is not None:
try:
with CaptureStd():
arg_default = _type_checker(data['default'])
@@ -1789,7 +1918,7 @@ class ModuleValidator(Validator):
try:
doc_default = None
- if 'default' in doc_options_arg and not is_empty(doc_options_arg['default']):
+ if 'default' in doc_options_arg and doc_options_arg['default'] is not None:
with CaptureStd():
doc_default = _type_checker(doc_options_arg['default'])
except (Exception, SystemExit):
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py
index 03a14019..1b712171 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py
@@ -29,7 +29,7 @@ from contextlib import contextmanager
from ansible.executor.powershell.module_manifest import PSModuleDepFinder
from ansible.module_utils.basic import FILE_COMMON_ARGUMENTS, AnsibleModule
from ansible.module_utils.six import reraise
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
from .utils import CaptureStd, find_executable, get_module_name_from_filename
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
index b2623ff7..a6068c60 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py
@@ -11,7 +11,8 @@ from ansible.module_utils.compat.version import StrictVersion
from functools import partial
from urllib.parse import urlparse
-from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, Required, Schema, Self, ValueInvalid, Exclusive
+from voluptuous import ALLOW_EXTRA, PREVENT_EXTRA, All, Any, Invalid, Length, MultipleInvalid, Required, Schema, Self, ValueInvalid, Exclusive
+from ansible.constants import DOCUMENTABLE_PLUGINS
from ansible.module_utils.six import string_types
from ansible.module_utils.common.collections import is_iterable
from ansible.module_utils.parsing.convert_bool import boolean
@@ -19,6 +20,9 @@ from ansible.parsing.quoting import unquote
from ansible.utils.version import SemanticVersion
from ansible.release import __version__
+from antsibull_docs_parser import dom
+from antsibull_docs_parser.parser import parse, Context
+
from .utils import parse_isodate
list_string_types = list(string_types)
@@ -80,26 +84,8 @@ def date(error_code=None):
return Any(isodate, error_code=error_code)
-_MODULE = re.compile(r"\bM\(([^)]+)\)")
-_LINK = re.compile(r"\bL\(([^)]+)\)")
-_URL = re.compile(r"\bU\(([^)]+)\)")
-_REF = re.compile(r"\bR\(([^)]+)\)")
-
-
-def _check_module_link(directive, content):
- if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(content):
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a FQCN' % directive), 'invalid-documentation-markup')
-
-
-def _check_link(directive, content):
- if ',' not in content:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
- idx = content.rindex(',')
- title = content[:idx]
- url = content[idx + 1:].lstrip(' ')
- _check_url(directive, url)
+# Roles can also be referenced by semantic markup
+_VALID_PLUGIN_TYPES = set(DOCUMENTABLE_PLUGINS + ('role', ))
def _check_url(directive, content):
@@ -107,15 +93,10 @@ def _check_url(directive, content):
parsed_url = urlparse(content)
if parsed_url.scheme not in ('', 'http', 'https'):
raise ValueError('Schema must be HTTP, HTTPS, or not specified')
- except ValueError as exc:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain an URL' % directive), 'invalid-documentation-markup')
-
-
-def _check_ref(directive, content):
- if ',' not in content:
- raise _add_ansible_error_code(
- Invalid('Directive "%s" must contain a comma' % directive), 'invalid-documentation-markup')
+ return []
+ except ValueError:
+ return [_add_ansible_error_code(
+ Invalid('Directive %s must contain a valid URL' % directive), 'invalid-documentation-markup')]
def doc_string(v):
@@ -123,25 +104,55 @@ def doc_string(v):
if not isinstance(v, string_types):
raise _add_ansible_error_code(
Invalid('Must be a string'), 'invalid-documentation')
- for m in _MODULE.finditer(v):
- _check_module_link(m.group(0), m.group(1))
- for m in _LINK.finditer(v):
- _check_link(m.group(0), m.group(1))
- for m in _URL.finditer(v):
- _check_url(m.group(0), m.group(1))
- for m in _REF.finditer(v):
- _check_ref(m.group(0), m.group(1))
+ errors = []
+ for par in parse(v, Context(), errors='message', strict=True, add_source=True):
+ for part in par:
+ if part.type == dom.PartType.ERROR:
+ errors.append(_add_ansible_error_code(Invalid(part.message), 'invalid-documentation-markup'))
+ if part.type == dom.PartType.URL:
+ errors.extend(_check_url('U()', part.url))
+ if part.type == dom.PartType.LINK:
+ errors.extend(_check_url('L()', part.url))
+ if part.type == dom.PartType.MODULE:
+ if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.PLUGIN:
+ if not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.OPTION_NAME:
+ if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if part.type == dom.PartType.RETURN_VALUE:
+ if part.plugin is not None and not FULLY_QUALIFIED_COLLECTION_RESOURCE_RE.match(part.plugin.fqcn):
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a FQCN; found "%s"' % (part.source, part.plugin.fqcn)),
+ 'invalid-documentation-markup'))
+ if part.plugin is not None and part.plugin.type not in _VALID_PLUGIN_TYPES:
+ errors.append(_add_ansible_error_code(Invalid(
+ 'Directive "%s" must contain a valid plugin type; found "%s"' % (part.source, part.plugin.type)),
+ 'invalid-documentation-markup'))
+ if len(errors) == 1:
+ raise errors[0]
+ if errors:
+ raise MultipleInvalid(errors)
return v
-def doc_string_or_strings(v):
- """Match a documentation string, or list of strings."""
- if isinstance(v, string_types):
- return doc_string(v)
- if isinstance(v, (list, tuple)):
- return [doc_string(vv) for vv in v]
- raise _add_ansible_error_code(
- Invalid('Must be a string or list of strings'), 'invalid-documentation')
+doc_string_or_strings = Any(doc_string, [doc_string])
def is_callable(v):
@@ -173,6 +184,11 @@ seealso_schema = Schema(
'description': doc_string,
},
{
+ Required('plugin'): Any(*string_types),
+ Required('plugin_type'): Any(*DOCUMENTABLE_PLUGINS),
+ 'description': doc_string,
+ },
+ {
Required('ref'): Any(*string_types),
Required('description'): doc_string,
},
@@ -794,7 +810,7 @@ def author(value):
def doc_schema(module_name, for_collection=False, deprecated_module=False, plugin_type='module'):
- if module_name.startswith('_'):
+ if module_name.startswith('_') and not for_collection:
module_name = module_name[1:]
deprecated_module = True
if for_collection is False and plugin_type == 'connection' and module_name == 'paramiko_ssh':
@@ -864,9 +880,6 @@ def doc_schema(module_name, for_collection=False, deprecated_module=False, plugi
'action_group': add_default_attributes({
Required('membership'): list_string_types,
}),
- 'forced_action_plugin': add_default_attributes({
- Required('action_plugin'): any_string_types,
- }),
'platform': add_default_attributes({
Required('platforms'): Any(list_string_types, *string_types)
}),
diff --git a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py
index 88d5b01a..15cb7037 100644
--- a/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py
+++ b/test/lib/ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py
@@ -28,7 +28,7 @@ from io import BytesIO, TextIOWrapper
import yaml
import yaml.reader
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.yaml import SafeLoader
from ansible.module_utils.six import string_types
diff --git a/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py b/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py
index d6de6117..ed1afcf3 100644
--- a/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py
+++ b/test/lib/ansible_test/_util/controller/sanity/yamllint/yamllinter.py
@@ -181,15 +181,15 @@ class YamlChecker:
if doc_types and target.id not in doc_types:
continue
- fmt_match = fmt_re.match(statement.value.s.lstrip())
+ fmt_match = fmt_re.match(statement.value.value.lstrip())
fmt = 'yaml'
if fmt_match:
fmt = fmt_match.group(1)
docs[target.id] = dict(
- yaml=statement.value.s,
+ yaml=statement.value.value,
lineno=statement.lineno,
- end_lineno=statement.lineno + len(statement.value.s.splitlines()),
+ end_lineno=statement.lineno + len(statement.value.value.splitlines()),
fmt=fmt.lower(),
)
diff --git a/test/lib/ansible_test/_util/controller/tools/collection_detail.py b/test/lib/ansible_test/_util/controller/tools/collection_detail.py
index 870ea59e..df52d099 100644
--- a/test/lib/ansible_test/_util/controller/tools/collection_detail.py
+++ b/test/lib/ansible_test/_util/controller/tools/collection_detail.py
@@ -50,7 +50,7 @@ def read_manifest_json(collection_path):
)
validate_version(result['version'])
except Exception as ex: # pylint: disable=broad-except
- raise Exception('{0}: {1}'.format(os.path.basename(manifest_path), ex))
+ raise Exception('{0}: {1}'.format(os.path.basename(manifest_path), ex)) from None
return result
@@ -71,7 +71,7 @@ def read_galaxy_yml(collection_path):
)
validate_version(result['version'])
except Exception as ex: # pylint: disable=broad-except
- raise Exception('{0}: {1}'.format(os.path.basename(galaxy_path), ex))
+ raise Exception('{0}: {1}'.format(os.path.basename(galaxy_path), ex)) from None
return result
diff --git a/test/lib/ansible_test/_util/target/common/constants.py b/test/lib/ansible_test/_util/target/common/constants.py
index 9bddfaf4..36a5a2c4 100644
--- a/test/lib/ansible_test/_util/target/common/constants.py
+++ b/test/lib/ansible_test/_util/target/common/constants.py
@@ -7,14 +7,14 @@ __metaclass__ = type
REMOTE_ONLY_PYTHON_VERSIONS = (
'2.7',
- '3.5',
'3.6',
'3.7',
'3.8',
+ '3.9',
)
CONTROLLER_PYTHON_VERSIONS = (
- '3.9',
'3.10',
'3.11',
+ '3.12',
)
diff --git a/test/lib/ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py b/test/lib/ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py
index fefd6b0f..2f77c03b 100644
--- a/test/lib/ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py
+++ b/test/lib/ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py
@@ -32,6 +32,50 @@ def collection_pypkgpath(self):
raise Exception('File "%s" not found in collection path "%s".' % (self.strpath, ANSIBLE_COLLECTIONS_PATH))
+def enable_assertion_rewriting_hook(): # type: () -> None
+ """
+ Enable pytest's AssertionRewritingHook on Python 3.x.
+ This is necessary because the Ansible collection loader intercepts imports before the pytest provided loader ever sees them.
+ """
+ import sys
+
+ if sys.version_info[0] == 2:
+ return # Python 2.x is not supported
+
+ hook_name = '_pytest.assertion.rewrite.AssertionRewritingHook'
+ hooks = [hook for hook in sys.meta_path if hook.__class__.__module__ + '.' + hook.__class__.__qualname__ == hook_name]
+
+ if len(hooks) != 1:
+ raise Exception('Found {} instance(s) of "{}" in sys.meta_path.'.format(len(hooks), hook_name))
+
+ assertion_rewriting_hook = hooks[0]
+
+ # This is based on `_AnsibleCollectionPkgLoaderBase.exec_module` from `ansible/utils/collection_loader/_collection_finder.py`.
+ def exec_module(self, module):
+ # short-circuit redirect; avoid reinitializing existing modules
+ if self._redirect_module: # pylint: disable=protected-access
+ return
+
+ # execute the module's code in its namespace
+ code_obj = self.get_code(self._fullname) # pylint: disable=protected-access
+
+ if code_obj is not None: # things like NS packages that can't have code on disk will return None
+ # This logic is loosely based on `AssertionRewritingHook._should_rewrite` from pytest.
+ # See: https://github.com/pytest-dev/pytest/blob/779a87aada33af444f14841a04344016a087669e/src/_pytest/assertion/rewrite.py#L209
+ should_rewrite = self._package_to_load == 'conftest' or self._package_to_load.startswith('test_') # pylint: disable=protected-access
+
+ if should_rewrite:
+ # noinspection PyUnresolvedReferences
+ assertion_rewriting_hook.exec_module(module)
+ else:
+ exec(code_obj, module.__dict__) # pylint: disable=exec-used
+
+ # noinspection PyProtectedMember
+ from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionPkgLoaderBase
+
+ _AnsibleCollectionPkgLoaderBase.exec_module = exec_module
+
+
def pytest_configure():
"""Configure this pytest plugin."""
try:
@@ -40,6 +84,8 @@ def pytest_configure():
except AttributeError:
pytest_configure.executed = True
+ enable_assertion_rewriting_hook()
+
# noinspection PyProtectedMember
from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder
diff --git a/test/lib/ansible_test/_util/target/sanity/import/importer.py b/test/lib/ansible_test/_util/target/sanity/import/importer.py
index 44a5ddc9..38a73643 100644
--- a/test/lib/ansible_test/_util/target/sanity/import/importer.py
+++ b/test/lib/ansible_test/_util/target/sanity/import/importer.py
@@ -552,13 +552,11 @@ def main():
"Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography,"
" and will be removed in the next release.")
- if sys.version_info[:2] == (3, 5):
- warnings.filterwarnings(
- "ignore",
- "Python 3.5 support will be dropped in the next release ofcryptography. Please upgrade your Python.")
- warnings.filterwarnings(
- "ignore",
- "Python 3.5 support will be dropped in the next release of cryptography. Please upgrade your Python.")
+ # ansible.utils.unsafe_proxy attempts patching sys.intern generating a warning if it was already patched
+ warnings.filterwarnings(
+ "ignore",
+ "skipped sys.intern patch; appears to have already been patched"
+ )
try:
yield
diff --git a/test/lib/ansible_test/_util/target/setup/bootstrap.sh b/test/lib/ansible_test/_util/target/setup/bootstrap.sh
index ea17dad3..65673da5 100644
--- a/test/lib/ansible_test/_util/target/setup/bootstrap.sh
+++ b/test/lib/ansible_test/_util/target/setup/bootstrap.sh
@@ -53,7 +53,7 @@ install_pip() {
pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-20.3.4.py"
;;
*)
- pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-21.3.1.py"
+ pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-23.1.2.py"
;;
esac
@@ -111,6 +111,15 @@ bootstrap_remote_alpine()
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
done
+
+ # Upgrade the `libexpat` package to ensure that an upgraded Python (`pyexpat`) continues to work.
+ while true; do
+ # shellcheck disable=SC2086
+ apk upgrade -q libexpat \
+ && break
+ echo "Failed to upgrade libexpat. Sleeping before trying again..."
+ sleep 10
+ done
}
bootstrap_remote_fedora()
@@ -163,8 +172,6 @@ bootstrap_remote_freebsd()
# Declare platform/python version combinations which do not have supporting OS packages available.
# For these combinations ansible-test will use pip to install the requirements instead.
case "${platform_version}/${python_version}" in
- "12.4/3.9")
- ;;
*)
jinja2_pkg="" # not available
cryptography_pkg="" # not available
@@ -261,7 +268,7 @@ bootstrap_remote_rhel_8()
if [ "${python_version}" = "3.6" ]; then
py_pkg_prefix="python3"
else
- py_pkg_prefix="python${python_package_version}"
+ py_pkg_prefix="python${python_version}"
fi
packages="
@@ -269,6 +276,14 @@ bootstrap_remote_rhel_8()
${py_pkg_prefix}-devel
"
+ # pip isn't included in the Python devel package under Python 3.11
+ if [ "${python_version}" != "3.6" ]; then
+ packages="
+ ${packages}
+ ${py_pkg_prefix}-pip
+ "
+ fi
+
# Jinja2 is not installed with an OS package since the provided version is too old.
# Instead, ansible-test will install it using pip.
if [ "${controller}" ]; then
@@ -278,9 +293,19 @@ bootstrap_remote_rhel_8()
"
fi
+ # Python 3.11 isn't a module like the earlier versions
+ if [ "${python_version}" = "3.6" ]; then
+ while true; do
+ # shellcheck disable=SC2086
+ yum module install -q -y "python${python_package_version}" \
+ && break
+ echo "Failed to install packages. Sleeping before trying again..."
+ sleep 10
+ done
+ fi
+
while true; do
# shellcheck disable=SC2086
- yum module install -q -y "python${python_package_version}" && \
yum install -q -y ${packages} \
&& break
echo "Failed to install packages. Sleeping before trying again..."
@@ -292,22 +317,34 @@ bootstrap_remote_rhel_8()
bootstrap_remote_rhel_9()
{
- py_pkg_prefix="python3"
+ if [ "${python_version}" = "3.9" ]; then
+ py_pkg_prefix="python3"
+ else
+ py_pkg_prefix="python${python_version}"
+ fi
packages="
gcc
${py_pkg_prefix}-devel
"
+ # pip is not included in the Python devel package under Python 3.11
+ if [ "${python_version}" != "3.9" ]; then
+ packages="
+ ${packages}
+ ${py_pkg_prefix}-pip
+ "
+ fi
+
# Jinja2 is not installed with an OS package since the provided version is too old.
# Instead, ansible-test will install it using pip.
+ # packaging and resolvelib are missing for Python 3.11 (and possible later) so we just
+ # skip them and let ansible-test install them from PyPI.
if [ "${controller}" ]; then
packages="
${packages}
${py_pkg_prefix}-cryptography
- ${py_pkg_prefix}-packaging
${py_pkg_prefix}-pyyaml
- ${py_pkg_prefix}-resolvelib
"
fi
@@ -387,14 +424,6 @@ bootstrap_remote_ubuntu()
echo "Failed to install packages. Sleeping before trying again..."
sleep 10
done
-
- if [ "${controller}" ]; then
- if [ "${platform_version}/${python_version}" = "20.04/3.9" ]; then
- # Install pyyaml using pip so libyaml support is available on Python 3.9.
- # The OS package install (which is installed by default) only has a .so file for Python 3.8.
- pip_install "--upgrade pyyaml"
- fi
- fi
}
bootstrap_docker()
diff --git a/test/lib/ansible_test/_util/target/setup/quiet_pip.py b/test/lib/ansible_test/_util/target/setup/quiet_pip.py
index 54f0f860..171ff8f3 100644
--- a/test/lib/ansible_test/_util/target/setup/quiet_pip.py
+++ b/test/lib/ansible_test/_util/target/setup/quiet_pip.py
@@ -27,10 +27,6 @@ WARNING_MESSAGE_FILTERS = (
# pip 21.0 will drop support for Python 2.7 in January 2021.
# More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
'DEPRECATION: Python 2.7 reached the end of its life ',
-
- # DEPRECATION: Python 3.5 reached the end of its life on September 13th, 2020. Please upgrade your Python as Python 3.5 is no longer maintained.
- # pip 21.0 will drop support for Python 3.5 in January 2021. pip 21.0 will remove support for this functionality.
- 'DEPRECATION: Python 3.5 reached the end of its life ',
)
diff --git a/test/lib/ansible_test/config/cloud-config-aws.ini.template b/test/lib/ansible_test/config/cloud-config-aws.ini.template
index 88b9fea6..503a14b3 100644
--- a/test/lib/ansible_test/config/cloud-config-aws.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-aws.ini.template
@@ -6,7 +6,9 @@
# 2) Using the automatically provisioned AWS credentials in ansible-test.
#
# If you do not want to use the automatically provisioned temporary AWS credentials,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
+# If you need to omit optional fields like security_token, comment out that line.
# This will cause ansible-test to use the given configuration instead of temporary credentials.
#
# NOTE: Automatic provisioning of AWS credentials requires an ansible-core-ci API key.
diff --git a/test/lib/ansible_test/config/cloud-config-azure.ini.template b/test/lib/ansible_test/config/cloud-config-azure.ini.template
index 766553d1..bf7cc022 100644
--- a/test/lib/ansible_test/config/cloud-config-azure.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-azure.ini.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned Azure credentials in ansible-test.
#
# If you do not want to use the automatically provisioned temporary Azure credentials,
-# fill in the values below and save this file without the .template extension.
+# fill in the values below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration instead of temporary credentials.
#
# NOTE: Automatic provisioning of Azure credentials requires an ansible-core-ci API key in ~/.ansible-core-ci.key
diff --git a/test/lib/ansible_test/config/cloud-config-cloudscale.ini.template b/test/lib/ansible_test/config/cloud-config-cloudscale.ini.template
index 1c99e9b8..8396e4c8 100644
--- a/test/lib/ansible_test/config/cloud-config-cloudscale.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-cloudscale.ini.template
@@ -4,6 +4,8 @@
#
# 1) Running integration tests without using ansible-test.
#
+# Fill in the value below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
[default]
cloudscale_api_token = @API_TOKEN
diff --git a/test/lib/ansible_test/config/cloud-config-cs.ini.template b/test/lib/ansible_test/config/cloud-config-cs.ini.template
index f8d8a915..0589fd5f 100644
--- a/test/lib/ansible_test/config/cloud-config-cs.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-cs.ini.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned cloudstack-sim docker container in ansible-test.
#
# If you do not want to use the automatically provided CloudStack simulator,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration and not launch the simulator.
#
# It is recommended that you DO NOT use this template unless you cannot use the simulator.
diff --git a/test/lib/ansible_test/config/cloud-config-gcp.ini.template b/test/lib/ansible_test/config/cloud-config-gcp.ini.template
index 00a20971..626063da 100644
--- a/test/lib/ansible_test/config/cloud-config-gcp.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-gcp.ini.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned cloudstack-sim docker container in ansible-test.
#
# If you do not want to use the automatically provided GCP simulator,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration and not launch the simulator.
#
# It is recommended that you DO NOT use this template unless you cannot use the simulator.
diff --git a/test/lib/ansible_test/config/cloud-config-hcloud.ini.template b/test/lib/ansible_test/config/cloud-config-hcloud.ini.template
index 8db658db..8fc7fa77 100644
--- a/test/lib/ansible_test/config/cloud-config-hcloud.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-hcloud.ini.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned Hetzner Cloud credentials in ansible-test.
#
# If you do not want to use the automatically provisioned temporary Hetzner Cloud credentials,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration instead of temporary credentials.
#
# NOTE: Automatic provisioning of Hetzner Cloud credentials requires an ansible-core-ci API key.
diff --git a/test/lib/ansible_test/config/cloud-config-opennebula.ini.template b/test/lib/ansible_test/config/cloud-config-opennebula.ini.template
index 00c56db1..f155d987 100644
--- a/test/lib/ansible_test/config/cloud-config-opennebula.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-opennebula.ini.template
@@ -6,7 +6,8 @@
# 2) Running integration tests against previously recorded XMLRPC fixtures
#
# If you want to test against a Live OpenNebula platform,
-# fill in the values below and save this file without the .template extension.
+# fill in the values below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration.
#
# If you run with @FIXTURES enabled (true) then you can decide if you want to
@@ -17,4 +18,4 @@ opennebula_url: @URL
opennebula_username: @USERNAME
opennebula_password: @PASSWORD
opennebula_test_fixture: @FIXTURES
-opennebula_test_fixture_replay: @REPLAY \ No newline at end of file
+opennebula_test_fixture_replay: @REPLAY
diff --git a/test/lib/ansible_test/config/cloud-config-openshift.kubeconfig.template b/test/lib/ansible_test/config/cloud-config-openshift.kubeconfig.template
index 0a10f23b..5c022cde 100644
--- a/test/lib/ansible_test/config/cloud-config-openshift.kubeconfig.template
+++ b/test/lib/ansible_test/config/cloud-config-openshift.kubeconfig.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned openshift-origin docker container in ansible-test.
#
# If you do not want to use the automatically provided OpenShift container,
-# place your kubeconfig file next to this file, with the same name, but without the .template extension.
+# place your kubeconfig file next into the tests/integration directory of the collection you're testing,
+# with the same name is this file, but without the .template extension.
# This will cause ansible-test to use the given configuration and not launch the automatically provided container.
#
# It is recommended that you DO NOT use this template unless you cannot use the automatically provided container.
diff --git a/test/lib/ansible_test/config/cloud-config-scaleway.ini.template b/test/lib/ansible_test/config/cloud-config-scaleway.ini.template
index f10419e0..63e4e48f 100644
--- a/test/lib/ansible_test/config/cloud-config-scaleway.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-scaleway.ini.template
@@ -5,7 +5,8 @@
# 1) Running integration tests without using ansible-test.
#
# If you want to test against the Vultr public API,
-# fill in the values below and save this file without the .template extension.
+# fill in the values below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration.
[default]
diff --git a/test/lib/ansible_test/config/cloud-config-vcenter.ini.template b/test/lib/ansible_test/config/cloud-config-vcenter.ini.template
index eff8bf74..4e980137 100644
--- a/test/lib/ansible_test/config/cloud-config-vcenter.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-vcenter.ini.template
@@ -6,7 +6,8 @@
# 2) Using the automatically provisioned VMware credentials in ansible-test.
#
# If you do not want to use the automatically provisioned temporary VMware credentials,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration instead of temporary credentials.
#
# NOTE: Automatic provisioning of VMware credentials requires an ansible-core-ci API key.
diff --git a/test/lib/ansible_test/config/cloud-config-vultr.ini.template b/test/lib/ansible_test/config/cloud-config-vultr.ini.template
index 48b82108..4530c326 100644
--- a/test/lib/ansible_test/config/cloud-config-vultr.ini.template
+++ b/test/lib/ansible_test/config/cloud-config-vultr.ini.template
@@ -5,7 +5,8 @@
# 1) Running integration tests without using ansible-test.
#
# If you want to test against the Vultr public API,
-# fill in the values below and save this file without the .template extension.
+# fill in the values below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
# This will cause ansible-test to use the given configuration.
[default]
diff --git a/test/lib/ansible_test/config/inventory.networking.template b/test/lib/ansible_test/config/inventory.networking.template
index a1545684..40a9f207 100644
--- a/test/lib/ansible_test/config/inventory.networking.template
+++ b/test/lib/ansible_test/config/inventory.networking.template
@@ -6,7 +6,8 @@
# 2) Using the `--platform` option to provision temporary network instances on EC2.
#
# If you do not want to use the automatically provisioned temporary network instances,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
#
# NOTE: Automatic provisioning of network instances on EC2 requires an ansible-core-ci API key.
diff --git a/test/lib/ansible_test/config/inventory.winrm.template b/test/lib/ansible_test/config/inventory.winrm.template
index 34bbee2d..3238b22e 100644
--- a/test/lib/ansible_test/config/inventory.winrm.template
+++ b/test/lib/ansible_test/config/inventory.winrm.template
@@ -6,7 +6,8 @@
# 1) Using the `--windows` option to provision temporary Windows instances on EC2.
#
# If you do not want to use the automatically provisioned temporary Windows instances,
-# fill in the @VAR placeholders below and save this file without the .template extension.
+# fill in the @VAR placeholders below and save this file without the .template extension,
+# into the tests/integration directory of the collection you're testing.
#
# NOTE: Automatic provisioning of Windows instances on EC2 requires an ansible-core-ci API key.
#
diff --git a/test/sanity/code-smell/ansible-requirements.py b/test/sanity/code-smell/ansible-requirements.py
index 4d1a652f..25d4ec88 100644
--- a/test/sanity/code-smell/ansible-requirements.py
+++ b/test/sanity/code-smell/ansible-requirements.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import re
-import sys
def read_file(path):
diff --git a/test/sanity/code-smell/deprecated-config.requirements.in b/test/sanity/code-smell/deprecated-config.requirements.in
index 859c4ee7..4e859bb8 100644
--- a/test/sanity/code-smell/deprecated-config.requirements.in
+++ b/test/sanity/code-smell/deprecated-config.requirements.in
@@ -1,2 +1,2 @@
-jinja2 # ansible-core requirement
+jinja2
pyyaml
diff --git a/test/sanity/code-smell/deprecated-config.requirements.txt b/test/sanity/code-smell/deprecated-config.requirements.txt
index 338e3f38..ae96cdf4 100644
--- a/test/sanity/code-smell/deprecated-config.requirements.txt
+++ b/test/sanity/code-smell/deprecated-config.requirements.txt
@@ -1,6 +1,4 @@
# edit "deprecated-config.requirements.in" and generate with: hacking/update-sanity-requirements.py --test deprecated-config
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
Jinja2==3.1.2
-MarkupSafe==2.1.1
-PyYAML==6.0
+MarkupSafe==2.1.3
+PyYAML==6.0.1
diff --git a/test/sanity/code-smell/obsolete-files.json b/test/sanity/code-smell/obsolete-files.json
index 02d39204..3f69cdd6 100644
--- a/test/sanity/code-smell/obsolete-files.json
+++ b/test/sanity/code-smell/obsolete-files.json
@@ -1,6 +1,8 @@
{
"include_symlinks": true,
"prefixes": [
+ "docs/",
+ "examples/",
"test/runner/",
"test/sanity/ansible-doc/",
"test/sanity/compile/",
diff --git a/test/sanity/code-smell/package-data.requirements.in b/test/sanity/code-smell/package-data.requirements.in
index 3162feb6..81b58bcf 100644
--- a/test/sanity/code-smell/package-data.requirements.in
+++ b/test/sanity/code-smell/package-data.requirements.in
@@ -1,8 +1,8 @@
build # required to build sdist
wheel # required to build wheel
jinja2
-pyyaml # ansible-core requirement
-resolvelib < 0.9.0
-rstcheck < 4 # match version used in other sanity tests
+pyyaml
+resolvelib < 1.1.0
+rstcheck < 6 # newer versions have too many dependencies
antsibull-changelog
-setuptools == 45.2.0 # minimum supported setuptools
+setuptools == 66.1.0 # minimum supported setuptools
diff --git a/test/sanity/code-smell/package-data.requirements.txt b/test/sanity/code-smell/package-data.requirements.txt
index b66079d0..ce0fb9cf 100644
--- a/test/sanity/code-smell/package-data.requirements.txt
+++ b/test/sanity/code-smell/package-data.requirements.txt
@@ -1,18 +1,17 @@
# edit "package-data.requirements.in" and generate with: hacking/update-sanity-requirements.py --test package-data
-# pre-build requirement: pyyaml == 6.0
-# pre-build constraint: Cython < 3.0
-antsibull-changelog==0.16.0
-build==0.10.0
-docutils==0.17.1
+antsibull-changelog==0.23.0
+build==1.0.3
+docutils==0.18.1
Jinja2==3.1.2
-MarkupSafe==2.1.1
-packaging==21.3
+MarkupSafe==2.1.3
+packaging==23.2
pyproject_hooks==1.0.0
-pyparsing==3.0.9
-PyYAML==6.0
-resolvelib==0.8.1
-rstcheck==3.5.0
+PyYAML==6.0.1
+resolvelib==1.0.1
+rstcheck==5.0.0
semantic-version==2.10.0
-setuptools==45.2.0
+setuptools==66.1.0
tomli==2.0.1
-wheel==0.41.0
+types-docutils==0.18.3
+typing_extensions==4.8.0
+wheel==0.41.2
diff --git a/test/sanity/code-smell/release-names.py b/test/sanity/code-smell/release-names.py
index 81d90d81..cac3071d 100644
--- a/test/sanity/code-smell/release-names.py
+++ b/test/sanity/code-smell/release-names.py
@@ -22,7 +22,7 @@ Test that the release name is present in the list of used up release names
from __future__ import annotations
-from yaml import safe_load
+import pathlib
from ansible.release import __codename__
@@ -30,8 +30,7 @@ from ansible.release import __codename__
def main():
"""Entrypoint to the script"""
- with open('.github/RELEASE_NAMES.yml') as f:
- releases = safe_load(f.read())
+ releases = pathlib.Path('.github/RELEASE_NAMES.txt').read_text().splitlines()
# Why this format? The file's sole purpose is to be read by a human when they need to know
# which release names have already been used. So:
@@ -41,7 +40,7 @@ def main():
if __codename__ == name:
break
else:
- print('.github/RELEASE_NAMES.yml: Current codename was not present in the file')
+ print(f'.github/RELEASE_NAMES.txt: Current codename {__codename__!r} not present in the file')
if __name__ == '__main__':
diff --git a/test/sanity/code-smell/test-constraints.py b/test/sanity/code-smell/test-constraints.py
index df30fe12..ac5bb4eb 100644
--- a/test/sanity/code-smell/test-constraints.py
+++ b/test/sanity/code-smell/test-constraints.py
@@ -65,12 +65,6 @@ def main():
# keeping constraints for tests other than sanity tests in one file helps avoid conflicts
print('%s:%d:%d: put the constraint (%s%s) in `%s`' % (path, lineno, 1, name, raw_constraints, constraints_path))
- for name, requirements in frozen_sanity.items():
- if len(set(req[3].group('constraints').strip() for req in requirements)) != 1:
- for req in requirements:
- print('%s:%d:%d: sanity constraint (%s) does not match others for package `%s`' % (
- req[0], req[1], req[3].start('constraints') + 1, req[3].group('constraints'), name))
-
def check_ansible_test(path: str, requirements: list[tuple[int, str, re.Match]]) -> None:
sys.path.insert(0, str(pathlib.Path(__file__).parent.parent.parent.joinpath('lib')))
diff --git a/test/sanity/code-smell/update-bundled.requirements.txt b/test/sanity/code-smell/update-bundled.requirements.txt
index d9785e7b..53f1e434 100644
--- a/test/sanity/code-smell/update-bundled.requirements.txt
+++ b/test/sanity/code-smell/update-bundled.requirements.txt
@@ -1,3 +1,2 @@
# edit "update-bundled.requirements.in" and generate with: hacking/update-sanity-requirements.py --test update-bundled
-packaging==21.3
-pyparsing==3.0.9
+packaging==23.2
diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt
index 869522b1..c683fbe7 100644
--- a/test/sanity/ignore.txt
+++ b/test/sanity/ignore.txt
@@ -1,16 +1,21 @@
-.azure-pipelines/scripts/publish-codecov.py replace-urlopen
lib/ansible/cli/scripts/ansible_connection_cli_stub.py shebang
lib/ansible/config/base.yml no-unwanted-files
-lib/ansible/executor/playbook_executor.py pylint:disallowed-name
lib/ansible/executor/powershell/async_watchdog.ps1 pslint:PSCustomUseLiteralPath
lib/ansible/executor/powershell/async_wrapper.ps1 pslint:PSCustomUseLiteralPath
lib/ansible/executor/powershell/exec_wrapper.ps1 pslint:PSCustomUseLiteralPath
-lib/ansible/executor/task_queue_manager.py pylint:disallowed-name
+lib/ansible/galaxy/collection/__init__.py mypy-3.10:attr-defined # inline ignore has no effect
+lib/ansible/galaxy/collection/__init__.py mypy-3.11:attr-defined # inline ignore has no effect
+lib/ansible/galaxy/collection/__init__.py mypy-3.12:attr-defined # inline ignore has no effect
+lib/ansible/galaxy/collection/gpg.py mypy-3.10:arg-type
+lib/ansible/galaxy/collection/gpg.py mypy-3.11:arg-type
+lib/ansible/galaxy/collection/gpg.py mypy-3.12:arg-type
+lib/ansible/parsing/yaml/constructor.py mypy-3.10:type-var # too many occurrences to ignore inline
+lib/ansible/parsing/yaml/constructor.py mypy-3.11:type-var # too many occurrences to ignore inline
+lib/ansible/parsing/yaml/constructor.py mypy-3.12:type-var # too many occurrences to ignore inline
lib/ansible/keyword_desc.yml no-unwanted-files
lib/ansible/modules/apt.py validate-modules:parameter-invalid
lib/ansible/modules/apt_repository.py validate-modules:parameter-invalid
lib/ansible/modules/assemble.py validate-modules:nonexistent-parameter-documented
-lib/ansible/modules/async_status.py use-argspec-type-path
lib/ansible/modules/async_status.py validate-modules!skip
lib/ansible/modules/async_wrapper.py ansible-doc!skip # not an actual module
lib/ansible/modules/async_wrapper.py pylint:ansible-bad-function # ignore, required
@@ -21,61 +26,48 @@ lib/ansible/modules/command.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/command.py validate-modules:doc-missing-type
lib/ansible/modules/command.py validate-modules:nonexistent-parameter-documented
lib/ansible/modules/command.py validate-modules:undocumented-parameter
-lib/ansible/modules/copy.py pylint:disallowed-name
lib/ansible/modules/copy.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/copy.py validate-modules:nonexistent-parameter-documented
lib/ansible/modules/copy.py validate-modules:undocumented-parameter
-lib/ansible/modules/dnf.py validate-modules:doc-required-mismatch
lib/ansible/modules/dnf.py validate-modules:parameter-invalid
+lib/ansible/modules/dnf5.py validate-modules:parameter-invalid
lib/ansible/modules/file.py validate-modules:undocumented-parameter
lib/ansible/modules/find.py use-argspec-type-path # fix needed
-lib/ansible/modules/git.py pylint:disallowed-name
lib/ansible/modules/git.py use-argspec-type-path
-lib/ansible/modules/git.py validate-modules:doc-missing-type
lib/ansible/modules/git.py validate-modules:doc-required-mismatch
-lib/ansible/modules/iptables.py pylint:disallowed-name
lib/ansible/modules/lineinfile.py validate-modules:doc-choices-do-not-match-spec
lib/ansible/modules/lineinfile.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/lineinfile.py validate-modules:nonexistent-parameter-documented
lib/ansible/modules/package_facts.py validate-modules:doc-choices-do-not-match-spec
-lib/ansible/modules/pip.py pylint:disallowed-name
lib/ansible/modules/replace.py validate-modules:nonexistent-parameter-documented
+lib/ansible/modules/replace.py pylint:used-before-assignment # false positive detection by pylint
lib/ansible/modules/service.py validate-modules:nonexistent-parameter-documented
lib/ansible/modules/service.py validate-modules:use-run-command-not-popen
-lib/ansible/modules/stat.py validate-modules:doc-default-does-not-match-spec # get_md5 is undocumented
lib/ansible/modules/stat.py validate-modules:parameter-invalid
-lib/ansible/modules/stat.py validate-modules:parameter-type-not-in-doc
-lib/ansible/modules/stat.py validate-modules:undocumented-parameter
lib/ansible/modules/systemd_service.py validate-modules:parameter-invalid
-lib/ansible/modules/systemd_service.py validate-modules:return-syntax-error
-lib/ansible/modules/sysvinit.py validate-modules:return-syntax-error
lib/ansible/modules/uri.py validate-modules:doc-required-mismatch
lib/ansible/modules/user.py validate-modules:doc-default-does-not-match-spec
lib/ansible/modules/user.py validate-modules:use-run-command-not-popen
-lib/ansible/modules/yum.py pylint:disallowed-name
lib/ansible/modules/yum.py validate-modules:parameter-invalid
-lib/ansible/modules/yum_repository.py validate-modules:doc-default-does-not-match-spec
-lib/ansible/modules/yum_repository.py validate-modules:parameter-type-not-in-doc
-lib/ansible/modules/yum_repository.py validate-modules:undocumented-parameter
+lib/ansible/module_utils/basic.py pylint:unused-import # deferring resolution to allow enabling the rule now
lib/ansible/module_utils/compat/_selectors2.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/compat/_selectors2.py metaclass-boilerplate # ignore bundled
-lib/ansible/module_utils/compat/_selectors2.py pylint:disallowed-name
lib/ansible/module_utils/compat/selinux.py import-2.7!skip # pass/fail depends on presence of libselinux.so
-lib/ansible/module_utils/compat/selinux.py import-3.5!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.6!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.7!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.8!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.9!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.10!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/compat/selinux.py import-3.11!skip # pass/fail depends on presence of libselinux.so
+lib/ansible/module_utils/compat/selinux.py import-3.12!skip # pass/fail depends on presence of libselinux.so
lib/ansible/module_utils/distro/_distro.py future-import-boilerplate # ignore bundled
lib/ansible/module_utils/distro/_distro.py metaclass-boilerplate # ignore bundled
lib/ansible/module_utils/distro/_distro.py no-assert
-lib/ansible/module_utils/distro/_distro.py pylint:using-constant-test # bundled code we don't want to modify
lib/ansible/module_utils/distro/_distro.py pep8!skip # bundled code we don't want to modify
+lib/ansible/module_utils/distro/_distro.py pylint:undefined-variable # ignore bundled
+lib/ansible/module_utils/distro/_distro.py pylint:using-constant-test # bundled code we don't want to modify
lib/ansible/module_utils/distro/__init__.py empty-init # breaks namespacing, bundled, do not override
lib/ansible/module_utils/facts/__init__.py empty-init # breaks namespacing, deprecate and eventually remove
-lib/ansible/module_utils/facts/network/linux.py pylint:disallowed-name
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.ArgvParser.psm1 pslint:PSUseApprovedVerbs
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 pslint:PSProvideCommentHelp # need to agree on best format for comment location
lib/ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 pslint:PSUseApprovedVerbs
@@ -93,33 +85,23 @@ lib/ansible/module_utils/six/__init__.py no-dict-iteritems
lib/ansible/module_utils/six/__init__.py no-dict-iterkeys
lib/ansible/module_utils/six/__init__.py no-dict-itervalues
lib/ansible/module_utils/six/__init__.py pylint:self-assigning-variable
+lib/ansible/module_utils/six/__init__.py pylint:trailing-comma-tuple
lib/ansible/module_utils/six/__init__.py replace-urlopen
-lib/ansible/module_utils/urls.py pylint:arguments-renamed
-lib/ansible/module_utils/urls.py pylint:disallowed-name
lib/ansible/module_utils/urls.py replace-urlopen
-lib/ansible/parsing/vault/__init__.py pylint:disallowed-name
lib/ansible/parsing/yaml/objects.py pylint:arguments-renamed
-lib/ansible/playbook/base.py pylint:disallowed-name
lib/ansible/playbook/collectionsearch.py required-and-default-attributes # https://github.com/ansible/ansible/issues/61460
-lib/ansible/playbook/helpers.py pylint:disallowed-name
-lib/ansible/playbook/playbook_include.py pylint:arguments-renamed
lib/ansible/playbook/role/include.py pylint:arguments-renamed
lib/ansible/plugins/action/normal.py action-plugin-docs # default action plugin for modules without a dedicated action plugin
lib/ansible/plugins/cache/base.py ansible-doc!skip # not a plugin, but a stub for backwards compatibility
lib/ansible/plugins/callback/__init__.py pylint:arguments-renamed
lib/ansible/plugins/inventory/advanced_host_list.py pylint:arguments-renamed
lib/ansible/plugins/inventory/host_list.py pylint:arguments-renamed
-lib/ansible/plugins/lookup/random_choice.py pylint:arguments-renamed
-lib/ansible/plugins/lookup/sequence.py pylint:disallowed-name
-lib/ansible/plugins/shell/cmd.py pylint:arguments-renamed
-lib/ansible/plugins/strategy/__init__.py pylint:disallowed-name
-lib/ansible/plugins/strategy/linear.py pylint:disallowed-name
lib/ansible/utils/collection_loader/_collection_finder.py pylint:deprecated-class
lib/ansible/utils/collection_loader/_collection_meta.py pylint:deprecated-class
-lib/ansible/vars/hostvars.py pylint:disallowed-name
test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function # ignore, required for testing
test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from # ignore, required for testing
test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import # ignore, required for testing
+test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py pylint:disallowed-name # ignore, required for testing
test/integration/targets/ansible-test-integration/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level
test/integration/targets/ansible-test-units/ansible_collections/ns/col/plugins/modules/hello.py pylint:relative-beyond-top-level
test/integration/targets/ansible-test-units/ansible_collections/ns/col/tests/unit/plugins/modules/test_hello.py pylint:relative-beyond-top-level
@@ -132,8 +114,10 @@ test/integration/targets/collections_relative_imports/collection_root/ansible_co
test/integration/targets/collections_relative_imports/collection_root/ansible_collections/my_ns/my_col/plugins/module_utils/my_util2.py pylint:relative-beyond-top-level
test/integration/targets/fork_safe_stdio/vendored_pty.py pep8!skip # vendored code
test/integration/targets/gathering_facts/library/bogus_facts shebang
+test/integration/targets/gathering_facts/library/dummy1 shebang
test/integration/targets/gathering_facts/library/facts_one shebang
test/integration/targets/gathering_facts/library/facts_two shebang
+test/integration/targets/gathering_facts/library/slow shebang
test/integration/targets/incidental_win_reboot/templates/post_reboot.ps1 pslint!skip
test/integration/targets/json_cleanup/library/bad_json shebang
test/integration/targets/lookup_csvfile/files/crlf.csv line-endings
@@ -143,11 +127,6 @@ test/integration/targets/module_precedence/lib_with_extension/ping.ini shebang
test/integration/targets/module_precedence/roles_with_extension/foo/library/a.ini shebang
test/integration/targets/module_precedence/roles_with_extension/foo/library/ping.ini shebang
test/integration/targets/module_utils/library/test.py future-import-boilerplate # allow testing of Python 2.x implicit relative imports
-test/integration/targets/module_utils/module_utils/bar0/foo.py pylint:disallowed-name
-test/integration/targets/module_utils/module_utils/foo.py pylint:disallowed-name
-test/integration/targets/module_utils/module_utils/sub/bar/bar.py pylint:disallowed-name
-test/integration/targets/module_utils/module_utils/sub/bar/__init__.py pylint:disallowed-name
-test/integration/targets/module_utils/module_utils/yak/zebra/foo.py pylint:disallowed-name
test/integration/targets/old_style_modules_posix/library/helloworld.sh shebang
test/integration/targets/template/files/encoding_1252_utf-8.expected no-smart-quotes
test/integration/targets/template/files/encoding_1252_windows-1252.expected no-smart-quotes
@@ -165,28 +144,9 @@ test/integration/targets/win_script/files/test_script_removes_file.ps1 pslint:PS
test/integration/targets/win_script/files/test_script_with_args.ps1 pslint:PSAvoidUsingWriteHost # Keep
test/integration/targets/win_script/files/test_script_with_splatting.ps1 pslint:PSAvoidUsingWriteHost # Keep
test/lib/ansible_test/_data/requirements/sanity.pslint.ps1 pslint:PSCustomUseLiteralPath # Uses wildcards on purpose
-test/lib/ansible_test/_util/target/setup/ConfigureRemotingForAnsible.ps1 pslint:PSCustomUseLiteralPath
-test/lib/ansible_test/_util/target/setup/requirements.py replace-urlopen
-test/support/integration/plugins/modules/timezone.py pylint:disallowed-name
-test/support/integration/plugins/module_utils/compat/ipaddress.py future-import-boilerplate
-test/support/integration/plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
-test/support/integration/plugins/module_utils/compat/ipaddress.py no-unicode-literals
-test/support/integration/plugins/module_utils/network/common/utils.py future-import-boilerplate
-test/support/integration/plugins/module_utils/network/common/utils.py metaclass-boilerplate
-test/support/integration/plugins/module_utils/network/common/utils.py pylint:use-a-generator
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/netconf/netconf.py pylint:used-before-assignment
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/filter/network.py pylint:consider-using-dict-comprehension
test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py no-unicode-literals
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/compat/ipaddress.py pep8:E203
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py pylint:unnecessary-comprehension
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py pylint:use-a-generator
-test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/netconf/default.py pylint:unnecessary-comprehension
test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py pylint:arguments-renamed
-test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py pep8:E501
test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py pylint:arguments-renamed
-test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py pep8:E231
-test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py pylint:disallowed-name
-test/support/windows-integration/plugins/action/win_copy.py pylint:used-before-assignment
test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/module_utils/WebRequest.psm1 pslint!skip
test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_uri.ps1 pslint!skip
test/support/windows-integration/plugins/modules/async_status.ps1 pslint!skip
@@ -207,19 +167,11 @@ test/support/windows-integration/plugins/modules/win_user_right.ps1 pslint!skip
test/support/windows-integration/plugins/modules/win_user.ps1 pslint!skip
test/support/windows-integration/plugins/modules/win_wait_for.ps1 pslint!skip
test/support/windows-integration/plugins/modules/win_whoami.ps1 pslint!skip
-test/units/executor/test_play_iterator.py pylint:disallowed-name
-test/units/modules/test_apt.py pylint:disallowed-name
test/units/module_utils/basic/test_deprecate_warn.py pylint:ansible-deprecated-no-version
test/units/module_utils/basic/test_deprecate_warn.py pylint:ansible-deprecated-version
-test/units/module_utils/basic/test_run_command.py pylint:disallowed-name
+test/units/module_utils/common/warnings/test_deprecate.py pylint:ansible-deprecated-no-version # testing Display.deprecated call without a version or date
+test/units/module_utils/common/warnings/test_deprecate.py pylint:ansible-deprecated-version # testing Deprecated version found in call to Display.deprecated or AnsibleModule.deprecate
test/units/module_utils/urls/fixtures/multipart.txt line-endings # Fixture for HTTP tests that use CRLF
-test/units/module_utils/urls/test_fetch_url.py replace-urlopen
-test/units/module_utils/urls/test_gzip.py replace-urlopen
-test/units/module_utils/urls/test_Request.py replace-urlopen
-test/units/parsing/vault/test_vault.py pylint:disallowed-name
-test/units/playbook/role/test_role.py pylint:disallowed-name
-test/units/plugins/test_plugins.py pylint:disallowed-name
-test/units/template/test_templar.py pylint:disallowed-name
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py pylint:relative-beyond-top-level
test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/modules/__init__.py empty-init # testing that collections don't need inits
test/units/utils/collection_loader/fixtures/collections_masked/ansible_collections/ansible/__init__.py empty-init # testing that collections don't need inits
@@ -227,3 +179,26 @@ test/units/utils/collection_loader/fixtures/collections_masked/ansible_collectio
test/units/utils/collection_loader/fixtures/collections_masked/ansible_collections/testns/__init__.py empty-init # testing that collections don't need inits
test/units/utils/collection_loader/fixtures/collections_masked/ansible_collections/testns/testcoll/__init__.py empty-init # testing that collections don't need inits
test/units/utils/collection_loader/test_collection_loader.py pylint:undefined-variable # magic runtime local var splatting
+.github/CONTRIBUTING.md pymarkdown:line-length
+hacking/backport/README.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/bug_internal_api.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/bug_wrong_repo.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/collections.md pymarkdown:line-length
+hacking/ticket_stubs/collections.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/guide_newbie_about_gh_and_contributing_to_ansible.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/no_thanks.md pymarkdown:line-length
+hacking/ticket_stubs/no_thanks.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/pr_duplicate.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/pr_merged.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/proposal.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/question_not_bug.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/resolved.md pymarkdown:no-bare-urls
+hacking/ticket_stubs/wider_discussion.md pymarkdown:no-bare-urls
+lib/ansible/galaxy/data/apb/README.md pymarkdown:line-length
+lib/ansible/galaxy/data/container/README.md pymarkdown:line-length
+lib/ansible/galaxy/data/default/role/README.md pymarkdown:line-length
+lib/ansible/galaxy/data/network/README.md pymarkdown:line-length
+README.md pymarkdown:line-length
+test/integration/targets/ansible-vault/invalid_format/README.md pymarkdown:no-bare-urls
+test/support/README.md pymarkdown:no-bare-urls
+test/units/cli/test_data/role_skeleton/README.md pymarkdown:line-length
diff --git a/test/support/README.md b/test/support/README.md
index 850bc921..d5244823 100644
--- a/test/support/README.md
+++ b/test/support/README.md
@@ -1,4 +1,4 @@
-# IMPORTANT!
+# IMPORTANT
Files under this directory are not actual plugins and modules used by Ansible
and as such should **not be modified**. They are used for testing purposes
diff --git a/test/support/integration/plugins/modules/sefcontext.py b/test/support/integration/plugins/modules/sefcontext.py
index 5574abca..946ae880 100644
--- a/test/support/integration/plugins/modules/sefcontext.py
+++ b/test/support/integration/plugins/modules/sefcontext.py
@@ -105,13 +105,11 @@ RETURN = r'''
# Default return values
'''
-import os
-import subprocess
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.respawn import has_respawned, probe_interpreters_for_module, respawn_module
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
SELINUX_IMP_ERR = None
try:
diff --git a/test/support/integration/plugins/modules/timezone.py b/test/support/integration/plugins/modules/timezone.py
index b7439a12..dd374838 100644
--- a/test/support/integration/plugins/modules/timezone.py
+++ b/test/support/integration/plugins/modules/timezone.py
@@ -121,7 +121,7 @@ class Timezone(object):
# running in the global zone where changing the timezone has no effect.
zonename_cmd = module.get_bin_path('zonename')
if zonename_cmd is not None:
- (rc, stdout, _) = module.run_command(zonename_cmd)
+ (rc, stdout, stderr) = module.run_command(zonename_cmd)
if rc == 0 and stdout.strip() == 'global':
module.fail_json(msg='Adjusting timezone is not supported in Global Zone')
@@ -731,7 +731,7 @@ class BSDTimezone(Timezone):
# Strategy 3:
# (If /etc/localtime is not symlinked)
# Check all files in /usr/share/zoneinfo and return first non-link match.
- for dname, _, fnames in sorted(os.walk(zoneinfo_dir)):
+ for dname, dirs, fnames in sorted(os.walk(zoneinfo_dir)):
for fname in sorted(fnames):
zoneinfo_file = os.path.join(dname, fname)
if not os.path.islink(zoneinfo_file) and filecmp.cmp(zoneinfo_file, localtime_file):
diff --git a/test/support/integration/plugins/modules/zypper.py b/test/support/integration/plugins/modules/zypper.py
index bfb31819..cd67b605 100644
--- a/test/support/integration/plugins/modules/zypper.py
+++ b/test/support/integration/plugins/modules/zypper.py
@@ -41,7 +41,7 @@ options:
- Package name C(name) or package specifier or a list of either.
- Can include a version like C(name=1.0), C(name>3.4) or C(name<=2.7). If a version is given, C(oldpackage) is implied and zypper is allowed to
update the package within the version range given.
- - You can also pass a url or a local path to a rpm file.
+ - You can also pass a url or a local path to an rpm file.
- When using state=latest, this can be '*', which updates all installed packages.
required: true
aliases: [ 'pkg' ]
@@ -202,8 +202,7 @@ EXAMPLES = '''
import xml
import re
from xml.dom.minidom import parseString as parseXML
-from ansible.module_utils.six import iteritems
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
# import module snippets
from ansible.module_utils.basic import AnsibleModule
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_get.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_get.py
index 40205a46..c6dbb2cf 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_get.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_get.py
@@ -24,7 +24,7 @@ import uuid
import hashlib
from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.common.text.converters import to_text, to_bytes
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.plugins.action import ActionBase
from ansible.module_utils.six.moves.urllib.parse import urlsplit
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_put.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_put.py
index 955329d4..6fa3b8d6 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_put.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/net_put.py
@@ -23,7 +23,7 @@ import uuid
import hashlib
from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.common.text.converters import to_text, to_bytes
from ansible.module_utils.connection import Connection, ConnectionError
from ansible.plugins.action import ActionBase
from ansible.module_utils.six.moves.urllib.parse import urlsplit
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/network.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/network.py
index 5d05d338..fbcc9c13 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/network.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/action/network.py
@@ -25,7 +25,7 @@ import time
import re
from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.common.text.converters import to_text, to_bytes
from ansible.module_utils.six.moves.urllib.parse import urlsplit
from ansible.plugins.action.normal import ActionModule as _ActionModule
from ansible.utils.display import Display
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/network_cli.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/network_cli.py
index fef40810..d0d977fa 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/network_cli.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/network_cli.py
@@ -302,7 +302,7 @@ from functools import wraps
from io import BytesIO
from ansible.errors import AnsibleConnectionFailure, AnsibleError
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.six import PY3
from ansible.module_utils.six.moves import cPickle
@@ -1310,7 +1310,6 @@ class Connection(NetworkConnectionBase):
remote host before triggering timeout exception
:return: None
"""
- """Fetch file over scp/sftp from remote device"""
ssh = self.ssh_type_conn._connect_uncached()
if self.ssh_type == "libssh":
self.ssh_type_conn.fetch_file(source, destination, proto=proto)
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/persistent.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/persistent.py
index b29b4872..c7379a63 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/persistent.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/connection/persistent.py
@@ -29,7 +29,7 @@ options:
"""
from ansible.executor.task_executor import start_connection
from ansible.plugins.connection import ConnectionBase
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.connection import Connection as SocketConnection
from ansible.utils.display import Display
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py
index bc458eb5..64150405 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/config.py
@@ -29,7 +29,7 @@ import re
import hashlib
from ansible.module_utils.six.moves import zip
-from ansible.module_utils._text import to_bytes, to_native
+from ansible.module_utils.common.text.converters import to_bytes, to_native
DEFAULT_COMMENT_TOKENS = ["#", "!", "/*", "*/", "echo"]
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py
index 477d3184..2afa650e 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/facts/facts.py
@@ -79,7 +79,7 @@ class FactsBase(object):
self._module.fail_json(
msg="Subset must be one of [%s], got %s"
% (
- ", ".join(sorted([item for item in valid_subsets])),
+ ", ".join(sorted(list(valid_subsets))),
subset,
)
)
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py
index 53a91e8c..1857f7df 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/netconf.py
@@ -27,7 +27,7 @@
#
import sys
-from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.common.text.converters import to_text, to_bytes
from ansible.module_utils.connection import Connection, ConnectionError
try:
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py
index 555fc713..149b4413 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py
@@ -28,7 +28,7 @@
import traceback
import json
-from ansible.module_utils._text import to_text, to_native
+from ansible.module_utils.common.text.converters import to_text, to_native
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.connection import Connection, ConnectionError
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py
index 64eca157..4095f594 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/utils.py
@@ -36,26 +36,12 @@ import json
from itertools import chain
-from ansible.module_utils._text import to_text, to_bytes
-from ansible.module_utils.common._collections_compat import Mapping
+from ansible.module_utils.common.text.converters import to_text, to_bytes
+from ansible.module_utils.six.moves.collections_abc import Mapping
from ansible.module_utils.six import iteritems, string_types
from ansible.module_utils import basic
from ansible.module_utils.parsing.convert_bool import boolean
-# Backwards compatibility for 3rd party modules
-# TODO(pabelanger): With move to ansible.netcommon, we should clean this code
-# up and have modules import directly themself.
-from ansible.module_utils.common.network import ( # noqa: F401
- to_bits,
- is_netmask,
- is_masklen,
- to_netmask,
- to_masklen,
- to_subnet,
- to_ipv6_network,
- VALID_MASKS,
-)
-
try:
from jinja2 import Environment, StrictUndefined
from jinja2.exceptions import UndefinedError
@@ -607,7 +593,7 @@ def remove_empties(cfg_dict):
elif (
isinstance(val, list)
and val
- and all([isinstance(x, dict) for x in val])
+ and all(isinstance(x, dict) for x in val)
):
child_val = [remove_empties(x) for x in val]
if child_val:
diff --git a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/modules/cli_config.py b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/modules/cli_config.py
index c1384c1d..9d07e856 100644
--- a/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/modules/cli_config.py
+++ b/test/support/network-integration/collections/ansible_collections/ansible/netcommon/plugins/modules/cli_config.py
@@ -206,7 +206,7 @@ import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
def validate_args(module, device_operations):
diff --git a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py
index feba971a..b9cb19d7 100644
--- a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py
+++ b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/cliconf/ios.py
@@ -38,7 +38,7 @@ import json
from collections.abc import Mapping
from ansible.errors import AnsibleConnectionFailure
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
NetworkConfig,
diff --git a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py
index 6818a0ce..c16d84c6 100644
--- a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py
+++ b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/module_utils/network/ios/ios.py
@@ -27,7 +27,7 @@
#
import json
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import env_fallback
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
diff --git a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py
index ef383fcc..0b3be2a9 100644
--- a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py
+++ b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_command.py
@@ -134,7 +134,7 @@ failed_conditions:
"""
import time
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
Conditional,
diff --git a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py
index beec5b8d..5048bbb5 100644
--- a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py
+++ b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/modules/ios_config.py
@@ -34,7 +34,8 @@ extends_documentation_fragment:
- cisco.ios.ios
notes:
- Tested against IOS 15.6
-- Abbreviated commands are NOT idempotent, see L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
+- Abbreviated commands are NOT idempotent,
+ see L(Network FAQ,../network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
options:
lines:
description:
@@ -326,7 +327,7 @@ time:
"""
import json
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.connection import ConnectionError
from ansible_collections.cisco.ios.plugins.module_utils.network.ios.ios import (
run_commands,
@@ -575,6 +576,7 @@ def main():
)
if running_config.sha1 != base_config.sha1:
+ before, after = "", ""
if module.params["diff_against"] == "intended":
before = running_config
after = base_config
diff --git a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/terminal/ios.py b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/terminal/ios.py
index 29f31b0e..97169529 100644
--- a/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/terminal/ios.py
+++ b/test/support/network-integration/collections/ansible_collections/cisco/ios/plugins/terminal/ios.py
@@ -24,7 +24,7 @@ import json
import re
from ansible.errors import AnsibleConnectionFailure
-from ansible.module_utils._text import to_text, to_bytes
+from ansible.module_utils.common.text.converters import to_text, to_bytes
from ansible.plugins.terminal import TerminalBase
from ansible.utils.display import Display
diff --git a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py
index 3212615f..1f351dc5 100644
--- a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py
+++ b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/cliconf/vyos.py
@@ -37,7 +37,7 @@ import json
from collections.abc import Mapping
from ansible.errors import AnsibleConnectionFailure
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
NetworkConfig,
)
diff --git a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py
index 908395a6..7e8b2048 100644
--- a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py
+++ b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/module_utils/network/vyos/vyos.py
@@ -27,7 +27,7 @@
#
import json
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.connection import Connection, ConnectionError
diff --git a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py
index 18538491..7f7c30c2 100644
--- a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py
+++ b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_command.py
@@ -133,7 +133,7 @@ warnings:
"""
import time
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
Conditional,
@@ -192,7 +192,7 @@ def main():
interval = module.params["interval"]
match = module.params["match"]
- for _ in range(retries):
+ for dummy in range(retries):
responses = run_commands(module, commands)
for item in list(conditionals):
@@ -213,7 +213,7 @@ def main():
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update(
- {"stdout": responses, "stdout_lines": list(to_lines(responses)),}
+ {"stdout": responses, "stdout_lines": list(to_lines(responses)), }
)
module.exit_json(**result)
diff --git a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py
index b899045a..e65f3ffd 100644
--- a/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py
+++ b/test/support/network-integration/collections/ansible_collections/vyos/vyos/plugins/modules/vyos_config.py
@@ -178,7 +178,7 @@ time:
"""
import re
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import ConnectionError
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
diff --git a/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/action/win_copy.py b/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/action/win_copy.py
index adb918be..79f72ef6 100644
--- a/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/action/win_copy.py
+++ b/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/action/win_copy.py
@@ -18,7 +18,7 @@ import zipfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.hashing import checksum
@@ -439,7 +439,7 @@ class ActionModule(ActionBase):
source_full = self._loader.get_real_file(source, decrypt=decrypt)
except AnsibleFileNotFound as e:
result['failed'] = True
- result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e))
+ result['msg'] = "could not find src=%s, %s" % (source, to_text(e))
return result
original_basename = os.path.basename(source)
diff --git a/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps1 b/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps1
index 071eb11c..9d29d6fc 100644
--- a/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps1
+++ b/test/support/windows-integration/collections/ansible_collections/ansible/windows/plugins/modules/win_stat.ps1
@@ -95,7 +95,7 @@ If ($null -ne $info) {
isreadonly = ($attributes -contains "ReadOnly")
isreg = $false
isshared = $false
- nlink = 1 # Number of links to the file (hard links), overriden below if islnk
+ nlink = 1 # Number of links to the file (hard links), overridden below if islnk
# lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
# lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
hlnk_targets = @()
diff --git a/test/support/windows-integration/plugins/action/win_copy.py b/test/support/windows-integration/plugins/action/win_copy.py
index adb918be..79f72ef6 100644
--- a/test/support/windows-integration/plugins/action/win_copy.py
+++ b/test/support/windows-integration/plugins/action/win_copy.py
@@ -18,7 +18,7 @@ import zipfile
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleFileNotFound
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.action import ActionBase
from ansible.utils.hashing import checksum
@@ -439,7 +439,7 @@ class ActionModule(ActionBase):
source_full = self._loader.get_real_file(source, decrypt=decrypt)
except AnsibleFileNotFound as e:
result['failed'] = True
- result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e))
+ result['msg'] = "could not find src=%s, %s" % (source, to_text(e))
return result
original_basename = os.path.basename(source)
diff --git a/test/support/windows-integration/plugins/action/win_reboot.py b/test/support/windows-integration/plugins/action/win_reboot.py
index c408f4f3..76f4a66b 100644
--- a/test/support/windows-integration/plugins/action/win_reboot.py
+++ b/test/support/windows-integration/plugins/action/win_reboot.py
@@ -4,10 +4,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from datetime import datetime
+from datetime import datetime, timezone
-from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.action import ActionBase
from ansible.plugins.action.reboot import ActionModule as RebootActionModule
from ansible.utils.display import Display
@@ -65,7 +64,7 @@ class ActionModule(RebootActionModule, ActionBase):
result = {}
reboot_result = self._low_level_execute_command(reboot_command, sudoable=self.DEFAULT_SUDOABLE)
- result['start'] = datetime.utcnow()
+ result['start'] = datetime.now(timezone.utc)
# Test for "A system shutdown has already been scheduled. (1190)" and handle it gracefully
stdout = reboot_result['stdout']
diff --git a/test/support/windows-integration/plugins/modules/win_stat.ps1 b/test/support/windows-integration/plugins/modules/win_stat.ps1
index 071eb11c..9d29d6fc 100644
--- a/test/support/windows-integration/plugins/modules/win_stat.ps1
+++ b/test/support/windows-integration/plugins/modules/win_stat.ps1
@@ -95,7 +95,7 @@ If ($null -ne $info) {
isreadonly = ($attributes -contains "ReadOnly")
isreg = $false
isshared = $false
- nlink = 1 # Number of links to the file (hard links), overriden below if islnk
+ nlink = 1 # Number of links to the file (hard links), overridden below if islnk
# lnk_target = islnk or isjunction Target of the symlink. Note that relative paths remain relative
# lnk_source = islnk os isjunction Target of the symlink normalized for the remote filesystem
hlnk_targets = @()
diff --git a/test/units/_vendor/test_vendor.py b/test/units/_vendor/test_vendor.py
index 84b850e2..265f5b27 100644
--- a/test/units/_vendor/test_vendor.py
+++ b/test/units/_vendor/test_vendor.py
@@ -1,27 +1,22 @@
# (c) 2020 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
import os
import pkgutil
import pytest
import sys
-from unittest.mock import MagicMock, NonCallableMagicMock, patch
+from unittest.mock import patch
def reset_internal_vendor_package():
import ansible
ansible_vendor_path = os.path.join(os.path.dirname(ansible.__file__), '_vendor')
- if ansible_vendor_path in sys.path:
- sys.path.remove(ansible_vendor_path)
+ list(map(sys.path.remove, [path for path in sys.path if path == ansible_vendor_path]))
for pkg in ['ansible._vendor', 'ansible']:
- if pkg in sys.modules:
- del sys.modules[pkg]
+ sys.modules.pop(pkg, None)
def test_package_path_masking():
@@ -50,16 +45,10 @@ def test_vendored(vendored_pkg_names=None):
import ansible
ansible_vendor_path = os.path.join(os.path.dirname(ansible.__file__), '_vendor')
assert sys.path[0] == ansible_vendor_path
-
- if ansible_vendor_path in previous_path:
- previous_path.remove(ansible_vendor_path)
-
assert sys.path[1:] == previous_path
def test_vendored_conflict():
with pytest.warns(UserWarning) as w:
- import pkgutil
- import sys
test_vendored(vendored_pkg_names=['sys', 'pkgutil']) # pass a real package we know is already loaded
- assert any('pkgutil, sys' in str(msg.message) for msg in w) # ensure both conflicting modules are listed and sorted
+ assert any(list('pkgutil, sys' in str(msg.message) for msg in w)) # ensure both conflicting modules are listed and sorted
diff --git a/test/units/cli/arguments/test_optparse_helpers.py b/test/units/cli/arguments/test_optparse_helpers.py
index 082c9be4..ae8e8d73 100644
--- a/test/units/cli/arguments/test_optparse_helpers.py
+++ b/test/units/cli/arguments/test_optparse_helpers.py
@@ -14,10 +14,7 @@ from ansible.cli.arguments import option_helpers as opt_help
from ansible import __path__ as ansible_path
from ansible.release import __version__ as ansible_version
-if C.DEFAULT_MODULE_PATH is None:
- cpath = u'Default w/o overrides'
-else:
- cpath = C.DEFAULT_MODULE_PATH
+cpath = C.DEFAULT_MODULE_PATH
FAKE_PROG = u'ansible-cli-test'
VERSION_OUTPUT = opt_help.version(prog=FAKE_PROG)
diff --git a/test/units/cli/galaxy/test_execute_list_collection.py b/test/units/cli/galaxy/test_execute_list_collection.py
index e8a834d9..5641cb86 100644
--- a/test/units/cli/galaxy/test_execute_list_collection.py
+++ b/test/units/cli/galaxy/test_execute_list_collection.py
@@ -5,37 +5,29 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
+import pathlib
+
import pytest
+from ansible import constants as C
from ansible import context
from ansible.cli.galaxy import GalaxyCLI
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.galaxy import collection
from ansible.galaxy.dependency_resolution.dataclasses import Requirement
-from ansible.module_utils._text import to_native
-
-
-def path_exists(path):
- if to_native(path) == '/root/.ansible/collections/ansible_collections/sandwiches/ham':
- return False
- elif to_native(path) == '/usr/share/ansible/collections/ansible_collections/sandwiches/reuben':
- return False
- elif to_native(path) == 'nope':
- return False
- else:
- return True
+from ansible.module_utils.common.text.converters import to_native
+from ansible.plugins.loader import init_plugin_loader
def isdir(path):
if to_native(path) == 'nope':
return False
- else:
- return True
+ return True
def cliargs(collections_paths=None, collection_name=None):
if collections_paths is None:
- collections_paths = ['~/root/.ansible/collections', '/usr/share/ansible/collections']
+ collections_paths = ['/root/.ansible/collections', '/usr/share/ansible/collections']
context.CLIARGS._store = {
'collections_path': collections_paths,
@@ -46,95 +38,61 @@ def cliargs(collections_paths=None, collection_name=None):
@pytest.fixture
-def mock_collection_objects(mocker):
- mocker.patch('ansible.cli.galaxy.GalaxyCLI._resolve_path', side_effect=['/root/.ansible/collections', '/usr/share/ansible/collections'])
- mocker.patch('ansible.cli.galaxy.validate_collection_path',
- side_effect=['/root/.ansible/collections/ansible_collections', '/usr/share/ansible/collections/ansible_collections'])
-
- collection_args_1 = (
- (
+def mock_from_path(mocker, monkeypatch):
+ collection_args = {
+ '/usr/share/ansible/collections/ansible_collections/sandwiches/pbj': (
'sandwiches.pbj',
- '1.5.0',
- None,
+ '1.0.0',
+ '/usr/share/ansible/collections/ansible_collections/sandwiches/pbj',
'dir',
None,
),
- (
- 'sandwiches.reuben',
- '2.5.0',
- None,
+ '/usr/share/ansible/collections/ansible_collections/sandwiches/ham': (
+ 'sandwiches.ham',
+ '1.0.0',
+ '/usr/share/ansible/collections/ansible_collections/sandwiches/ham',
'dir',
None,
),
- )
-
- collection_args_2 = (
- (
+ '/root/.ansible/collections/ansible_collections/sandwiches/pbj': (
'sandwiches.pbj',
- '1.0.0',
- None,
+ '1.5.0',
+ '/root/.ansible/collections/ansible_collections/sandwiches/pbj',
'dir',
None,
),
- (
- 'sandwiches.ham',
- '1.0.0',
- None,
+ '/root/.ansible/collections/ansible_collections/sandwiches/reuben': (
+ 'sandwiches.reuben',
+ '2.5.0',
+ '/root/.ansible/collections/ansible_collections/sandwiches/reuben',
'dir',
None,
),
- )
+ }
- collections_path_1 = [Requirement(*cargs) for cargs in collection_args_1]
- collections_path_2 = [Requirement(*cargs) for cargs in collection_args_2]
+ def dispatch_requirement(path, am):
+ return Requirement(*collection_args[to_native(path)])
- mocker.patch('ansible.cli.galaxy.find_existing_collections', side_effect=[collections_path_1, collections_path_2])
+ files_mock = mocker.MagicMock()
+ mocker.patch('ansible.galaxy.collection.files', return_value=files_mock)
+ files_mock.glob.return_value = []
+ mocker.patch.object(pathlib.Path, 'is_dir', return_value=True)
+ for path, args in collection_args.items():
+ files_mock.glob.return_value.append(pathlib.Path(args[2]))
-@pytest.fixture
-def mock_from_path(mocker):
- def _from_path(collection_name='pbj'):
- collection_args = {
- 'sandwiches.pbj': (
- (
- 'sandwiches.pbj',
- '1.5.0',
- None,
- 'dir',
- None,
- ),
- (
- 'sandwiches.pbj',
- '1.0.0',
- None,
- 'dir',
- None,
- ),
- ),
- 'sandwiches.ham': (
- (
- 'sandwiches.ham',
- '1.0.0',
- None,
- 'dir',
- None,
- ),
- ),
- }
-
- from_path_objects = [Requirement(*args) for args in collection_args[collection_name]]
- mocker.patch('ansible.cli.galaxy.Requirement.from_dir_path_as_unknown', side_effect=from_path_objects)
-
- return _from_path
-
-
-def test_execute_list_collection_all(mocker, capsys, mock_collection_objects, tmp_path_factory):
+ mocker.patch('ansible.galaxy.collection.Candidate.from_dir_path_as_unknown', side_effect=dispatch_requirement)
+
+ monkeypatch.setattr(C, 'COLLECTIONS_PATHS', ['/root/.ansible/collections', '/usr/share/ansible/collections'])
+
+
+def test_execute_list_collection_all(mocker, capsys, mock_from_path, tmp_path_factory):
"""Test listing all collections from multiple paths"""
cliargs()
+ init_plugin_loader()
mocker.patch('os.path.exists', return_value=True)
- mocker.patch('os.path.isdir', return_value=True)
gc = GalaxyCLI(['ansible-galaxy', 'collection', 'list'])
tmp_path = tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections')
concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(tmp_path, validate_certs=False)
@@ -152,21 +110,20 @@ def test_execute_list_collection_all(mocker, capsys, mock_collection_objects, tm
assert out_lines[5] == 'sandwiches.reuben 2.5.0 '
assert out_lines[6] == ''
assert out_lines[7] == '# /usr/share/ansible/collections/ansible_collections'
- assert out_lines[8] == 'Collection Version'
- assert out_lines[9] == '-------------- -------'
- assert out_lines[10] == 'sandwiches.ham 1.0.0 '
- assert out_lines[11] == 'sandwiches.pbj 1.0.0 '
+ assert out_lines[8] == 'Collection Version'
+ assert out_lines[9] == '----------------- -------'
+ assert out_lines[10] == 'sandwiches.ham 1.0.0 '
+ assert out_lines[11] == 'sandwiches.pbj 1.0.0 '
-def test_execute_list_collection_specific(mocker, capsys, mock_collection_objects, mock_from_path, tmp_path_factory):
+def test_execute_list_collection_specific(mocker, capsys, mock_from_path, tmp_path_factory):
"""Test listing a specific collection"""
collection_name = 'sandwiches.ham'
- mock_from_path(collection_name)
cliargs(collection_name=collection_name)
- mocker.patch('os.path.exists', path_exists)
- mocker.patch('os.path.isdir', return_value=True)
+ init_plugin_loader()
+
mocker.patch('ansible.galaxy.collection.validate_collection_name', collection_name)
mocker.patch('ansible.cli.galaxy._get_collection_widths', return_value=(14, 5))
@@ -186,15 +143,14 @@ def test_execute_list_collection_specific(mocker, capsys, mock_collection_object
assert out_lines[4] == 'sandwiches.ham 1.0.0 '
-def test_execute_list_collection_specific_duplicate(mocker, capsys, mock_collection_objects, mock_from_path, tmp_path_factory):
+def test_execute_list_collection_specific_duplicate(mocker, capsys, mock_from_path, tmp_path_factory):
"""Test listing a specific collection that exists at multiple paths"""
collection_name = 'sandwiches.pbj'
- mock_from_path(collection_name)
cliargs(collection_name=collection_name)
- mocker.patch('os.path.exists', path_exists)
- mocker.patch('os.path.isdir', return_value=True)
+ init_plugin_loader()
+
mocker.patch('ansible.galaxy.collection.validate_collection_name', collection_name)
gc = GalaxyCLI(['ansible-galaxy', 'collection', 'list', collection_name])
@@ -221,6 +177,8 @@ def test_execute_list_collection_specific_duplicate(mocker, capsys, mock_collect
def test_execute_list_collection_specific_invalid_fqcn(mocker, tmp_path_factory):
"""Test an invalid fully qualified collection name (FQCN)"""
+ init_plugin_loader()
+
collection_name = 'no.good.name'
cliargs(collection_name=collection_name)
@@ -238,6 +196,7 @@ def test_execute_list_collection_no_valid_paths(mocker, capsys, tmp_path_factory
"""Test listing collections when no valid paths are given"""
cliargs()
+ init_plugin_loader()
mocker.patch('os.path.exists', return_value=True)
mocker.patch('os.path.isdir', return_value=False)
@@ -257,13 +216,14 @@ def test_execute_list_collection_no_valid_paths(mocker, capsys, tmp_path_factory
assert 'exists, but it\nis not a directory.' in err
-def test_execute_list_collection_one_invalid_path(mocker, capsys, mock_collection_objects, tmp_path_factory):
+def test_execute_list_collection_one_invalid_path(mocker, capsys, mock_from_path, tmp_path_factory):
"""Test listing all collections when one invalid path is given"""
- cliargs()
+ cliargs(collections_paths=['nope'])
+ init_plugin_loader()
+
mocker.patch('os.path.exists', return_value=True)
mocker.patch('os.path.isdir', isdir)
- mocker.patch('ansible.cli.galaxy.GalaxyCLI._resolve_path', side_effect=['/root/.ansible/collections', 'nope'])
mocker.patch('ansible.utils.color.ANSIBLE_COLOR', False)
gc = GalaxyCLI(['ansible-galaxy', 'collection', 'list', '-p', 'nope'])
diff --git a/test/units/cli/test_adhoc.py b/test/units/cli/test_adhoc.py
index 18775f5d..7bcca471 100644
--- a/test/units/cli/test_adhoc.py
+++ b/test/units/cli/test_adhoc.py
@@ -93,19 +93,15 @@ def test_run_no_extra_vars():
assert exec_info.value.code == 2
-def test_ansible_version(capsys, mocker):
+def test_ansible_version(capsys):
adhoc_cli = AdHocCLI(args=['/bin/ansible', '--version'])
with pytest.raises(SystemExit):
adhoc_cli.run()
version = capsys.readouterr()
- try:
- version_lines = version.out.splitlines()
- except AttributeError:
- # Python 2.6 does return a named tuple, so get the first item
- version_lines = version[0].splitlines()
+ version_lines = version.out.splitlines()
assert len(version_lines) == 9, 'Incorrect number of lines in "ansible --version" output'
- assert re.match(r'ansible \[core [0-9.a-z]+\]$', version_lines[0]), 'Incorrect ansible version line in "ansible --version" output'
+ assert re.match(r'ansible \[core [0-9.a-z]+\]', version_lines[0]), 'Incorrect ansible version line in "ansible --version" output'
assert re.match(' config file = .*$', version_lines[1]), 'Incorrect config file line in "ansible --version" output'
assert re.match(' configured module search path = .*$', version_lines[2]), 'Incorrect module search path in "ansible --version" output'
assert re.match(' ansible python module location = .*$', version_lines[3]), 'Incorrect python module location in "ansible --version" output'
diff --git a/test/units/cli/test_data/collection_skeleton/README.md b/test/units/cli/test_data/collection_skeleton/README.md
index 4cfd8afe..2e3e4ce5 100644
--- a/test/units/cli/test_data/collection_skeleton/README.md
+++ b/test/units/cli/test_data/collection_skeleton/README.md
@@ -1 +1 @@
-A readme \ No newline at end of file
+A readme
diff --git a/test/units/cli/test_data/collection_skeleton/docs/My Collection.md b/test/units/cli/test_data/collection_skeleton/docs/My Collection.md
index 6fa917f2..0d6781bc 100644
--- a/test/units/cli/test_data/collection_skeleton/docs/My Collection.md
+++ b/test/units/cli/test_data/collection_skeleton/docs/My Collection.md
@@ -1 +1 @@
-Welcome to my test collection doc for {{ namespace }}. \ No newline at end of file
+Welcome to my test collection doc for {{ namespace }}.
diff --git a/test/units/cli/test_doc.py b/test/units/cli/test_doc.py
index b10f0888..50b714eb 100644
--- a/test/units/cli/test_doc.py
+++ b/test/units/cli/test_doc.py
@@ -5,7 +5,7 @@ __metaclass__ = type
import pytest
from ansible.cli.doc import DocCLI, RoleMixin
-from ansible.plugins.loader import module_loader
+from ansible.plugins.loader import module_loader, init_plugin_loader
TTY_IFY_DATA = {
@@ -118,6 +118,7 @@ def test_builtin_modules_list():
args = ['ansible-doc', '-l', 'ansible.builtin', '-t', 'module']
obj = DocCLI(args=args)
obj.parse()
+ init_plugin_loader()
result = obj._list_plugins('module', module_loader)
assert len(result) > 0
diff --git a/test/units/cli/test_galaxy.py b/test/units/cli/test_galaxy.py
index 8ff56408..80a2dfae 100644
--- a/test/units/cli/test_galaxy.py
+++ b/test/units/cli/test_galaxy.py
@@ -20,6 +20,8 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import contextlib
+
import ansible
from io import BytesIO
import json
@@ -37,7 +39,7 @@ from ansible.cli.galaxy import GalaxyCLI
from ansible.galaxy import collection
from ansible.galaxy.api import GalaxyAPI
from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.utils import context_objects as co
from ansible.utils.display import Display
from units.compat import unittest
@@ -60,8 +62,7 @@ class TestGalaxy(unittest.TestCase):
cls.temp_dir = tempfile.mkdtemp(prefix='ansible-test_galaxy-')
os.chdir(cls.temp_dir)
- if os.path.exists("./delete_me"):
- shutil.rmtree("./delete_me")
+ shutil.rmtree("./delete_me", ignore_errors=True)
# creating framework for a role
gc = GalaxyCLI(args=["ansible-galaxy", "init", "--offline", "delete_me"])
@@ -71,8 +72,7 @@ class TestGalaxy(unittest.TestCase):
# making a temp dir for role installation
cls.role_path = os.path.join(tempfile.mkdtemp(), "roles")
- if not os.path.isdir(cls.role_path):
- os.makedirs(cls.role_path)
+ os.makedirs(cls.role_path)
# creating a tar file name for class data
cls.role_tar = './delete_me.tar.gz'
@@ -80,37 +80,29 @@ class TestGalaxy(unittest.TestCase):
# creating a temp file with installation requirements
cls.role_req = './delete_me_requirements.yml'
- fd = open(cls.role_req, "w")
- fd.write("- 'src': '%s'\n 'name': '%s'\n 'path': '%s'" % (cls.role_tar, cls.role_name, cls.role_path))
- fd.close()
+ with open(cls.role_req, "w") as fd:
+ fd.write("- 'src': '%s'\n 'name': '%s'\n 'path': '%s'" % (cls.role_tar, cls.role_name, cls.role_path))
@classmethod
def makeTar(cls, output_file, source_dir):
''' used for making a tarfile from a role directory '''
# adding directory into a tar file
- try:
- tar = tarfile.open(output_file, "w:gz")
+ with tarfile.open(output_file, "w:gz") as tar:
tar.add(source_dir, arcname=os.path.basename(source_dir))
- except AttributeError: # tarfile obj. has no attribute __exit__ prior to python 2. 7
- pass
- finally: # ensuring closure of tarfile obj
- tar.close()
@classmethod
def tearDownClass(cls):
'''After tests are finished removes things created in setUpClass'''
# deleting the temp role directory
- if os.path.exists(cls.role_dir):
- shutil.rmtree(cls.role_dir)
- if os.path.exists(cls.role_req):
+ shutil.rmtree(cls.role_dir, ignore_errors=True)
+ with contextlib.suppress(FileNotFoundError):
os.remove(cls.role_req)
- if os.path.exists(cls.role_tar):
+ with contextlib.suppress(FileNotFoundError):
os.remove(cls.role_tar)
- if os.path.isdir(cls.role_path):
- shutil.rmtree(cls.role_path)
+ shutil.rmtree(cls.role_path, ignore_errors=True)
os.chdir('/')
- shutil.rmtree(cls.temp_dir)
+ shutil.rmtree(cls.temp_dir, ignore_errors=True)
def setUp(self):
# Reset the stored command line args
@@ -137,8 +129,7 @@ class TestGalaxy(unittest.TestCase):
role_info = {'name': 'some_role_name',
'galaxy_info': galaxy_info}
display_result = gc._display_role_info(role_info)
- if display_result.find('\n\tgalaxy_info:') == -1:
- self.fail('Expected galaxy_info to be indented once')
+ self.assertNotEqual(display_result.find('\n\tgalaxy_info:'), -1, 'Expected galaxy_info to be indented once')
def test_run(self):
''' verifies that the GalaxyCLI object's api is created and that execute() is called. '''
@@ -176,7 +167,9 @@ class TestGalaxy(unittest.TestCase):
with patch.object(ansible.utils.display.Display, "display", return_value=None) as mocked_display:
# testing that error expected is raised
self.assertRaises(AnsibleError, gc.run)
- self.assertTrue(mocked_display.called_once_with("- downloading role 'fake_role_name', owned by "))
+ assert mocked_display.call_count == 2
+ assert mocked_display.mock_calls[0].args[0] == "Starting galaxy role install process"
+ assert "fake_role_name was NOT installed successfully" in mocked_display.mock_calls[1].args[0]
def test_exit_without_ignore_with_flag(self):
''' tests that GalaxyCLI exits without the error specified if the --ignore-errors flag is used '''
@@ -184,7 +177,9 @@ class TestGalaxy(unittest.TestCase):
gc = GalaxyCLI(args=["ansible-galaxy", "install", "--server=None", "fake_role_name", "--ignore-errors"])
with patch.object(ansible.utils.display.Display, "display", return_value=None) as mocked_display:
gc.run()
- self.assertTrue(mocked_display.called_once_with("- downloading role 'fake_role_name', owned by "))
+ assert mocked_display.call_count == 2
+ assert mocked_display.mock_calls[0].args[0] == "Starting galaxy role install process"
+ assert "fake_role_name was NOT installed successfully" in mocked_display.mock_calls[1].args[0]
def test_parse_no_action(self):
''' testing the options parser when no action is given '''
@@ -277,8 +272,6 @@ class ValidRoleTests(object):
# Make temp directory for testing
cls.test_dir = tempfile.mkdtemp()
- if not os.path.isdir(cls.test_dir):
- os.makedirs(cls.test_dir)
cls.role_dir = os.path.join(cls.test_dir, role_name)
cls.role_name = role_name
@@ -297,9 +290,8 @@ class ValidRoleTests(object):
cls.role_skeleton_path = gc.galaxy.default_role_skeleton_path
@classmethod
- def tearDownClass(cls):
- if os.path.isdir(cls.test_dir):
- shutil.rmtree(cls.test_dir)
+ def tearDownRole(cls):
+ shutil.rmtree(cls.test_dir, ignore_errors=True)
def test_metadata(self):
with open(os.path.join(self.role_dir, 'meta', 'main.yml'), 'r') as mf:
@@ -349,6 +341,10 @@ class TestGalaxyInitDefault(unittest.TestCase, ValidRoleTests):
def setUpClass(cls):
cls.setUpRole(role_name='delete_me')
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRole()
+
def test_metadata_contents(self):
with open(os.path.join(self.role_dir, 'meta', 'main.yml'), 'r') as mf:
metadata = yaml.safe_load(mf)
@@ -361,6 +357,10 @@ class TestGalaxyInitAPB(unittest.TestCase, ValidRoleTests):
def setUpClass(cls):
cls.setUpRole('delete_me_apb', galaxy_args=['--type=apb'])
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRole()
+
def test_metadata_apb_tag(self):
with open(os.path.join(self.role_dir, 'meta', 'main.yml'), 'r') as mf:
metadata = yaml.safe_load(mf)
@@ -391,6 +391,10 @@ class TestGalaxyInitContainer(unittest.TestCase, ValidRoleTests):
def setUpClass(cls):
cls.setUpRole('delete_me_container', galaxy_args=['--type=container'])
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRole()
+
def test_metadata_container_tag(self):
with open(os.path.join(self.role_dir, 'meta', 'main.yml'), 'r') as mf:
metadata = yaml.safe_load(mf)
@@ -422,6 +426,10 @@ class TestGalaxyInitSkeleton(unittest.TestCase, ValidRoleTests):
role_skeleton_path = os.path.join(os.path.split(__file__)[0], 'test_data', 'role_skeleton')
cls.setUpRole('delete_me_skeleton', skeleton_path=role_skeleton_path, use_explicit_type=True)
+ @classmethod
+ def tearDownClass(cls):
+ cls.tearDownRole()
+
def test_empty_files_dir(self):
files_dir = os.path.join(self.role_dir, 'files')
self.assertTrue(os.path.isdir(files_dir))
@@ -763,6 +771,20 @@ def test_collection_install_with_names(collection_install):
assert mock_install.call_args[0][6] is False # force_deps
+def test_collection_install_with_invalid_requirements_format(collection_install):
+ output_dir = collection_install[2]
+
+ requirements_file = os.path.join(output_dir, 'requirements.yml')
+ with open(requirements_file, 'wb') as req_obj:
+ req_obj.write(b'"invalid"')
+
+ galaxy_args = ['ansible-galaxy', 'collection', 'install', '--requirements-file', requirements_file,
+ '--collections-path', output_dir]
+
+ with pytest.raises(AnsibleError, match="Expecting requirements yaml to be a list or dictionary but got str"):
+ GalaxyCLI(args=galaxy_args).run()
+
+
def test_collection_install_with_requirements_file(collection_install):
mock_install, mock_warning, output_dir = collection_install
@@ -1242,12 +1264,7 @@ def test_install_implicit_role_with_collections(requirements_file, monkeypatch):
assert len(mock_role_install.call_args[0][0]) == 1
assert str(mock_role_install.call_args[0][0][0]) == 'namespace.name'
- found = False
- for mock_call in mock_display.mock_calls:
- if 'contains collections which will be ignored' in mock_call[1][0]:
- found = True
- break
- assert not found
+ assert not any(list('contains collections which will be ignored' in mock_call[1][0] for mock_call in mock_display.mock_calls))
@pytest.mark.parametrize('requirements_file', ['''
@@ -1274,12 +1291,7 @@ def test_install_explicit_role_with_collections(requirements_file, monkeypatch):
assert len(mock_role_install.call_args[0][0]) == 1
assert str(mock_role_install.call_args[0][0][0]) == 'namespace.name'
- found = False
- for mock_call in mock_display.mock_calls:
- if 'contains collections which will be ignored' in mock_call[1][0]:
- found = True
- break
- assert found
+ assert any(list('contains collections which will be ignored' in mock_call[1][0] for mock_call in mock_display.mock_calls))
@pytest.mark.parametrize('requirements_file', ['''
@@ -1306,12 +1318,7 @@ def test_install_role_with_collections_and_path(requirements_file, monkeypatch):
assert len(mock_role_install.call_args[0][0]) == 1
assert str(mock_role_install.call_args[0][0][0]) == 'namespace.name'
- found = False
- for mock_call in mock_display.mock_calls:
- if 'contains collections which will be ignored' in mock_call[1][0]:
- found = True
- break
- assert found
+ assert any(list('contains collections which will be ignored' in mock_call[1][0] for mock_call in mock_display.mock_calls))
@pytest.mark.parametrize('requirements_file', ['''
@@ -1338,9 +1345,4 @@ def test_install_collection_with_roles(requirements_file, monkeypatch):
assert mock_role_install.call_count == 0
- found = False
- for mock_call in mock_display.mock_calls:
- if 'contains roles which will be ignored' in mock_call[1][0]:
- found = True
- break
- assert found
+ assert any(list('contains roles which will be ignored' in mock_call[1][0] for mock_call in mock_display.mock_calls))
diff --git a/test/units/cli/test_vault.py b/test/units/cli/test_vault.py
index 2304f4d5..f1399c3f 100644
--- a/test/units/cli/test_vault.py
+++ b/test/units/cli/test_vault.py
@@ -29,7 +29,7 @@ from units.mock.vault_helper import TextVaultSecret
from ansible import context, errors
from ansible.cli.vault import VaultCLI
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.utils import context_objects as co
@@ -171,7 +171,28 @@ class TestVaultCli(unittest.TestCase):
mock_setup_vault_secrets.return_value = [('default', TextVaultSecret('password'))]
cli = VaultCLI(args=['ansible-vault', 'create', '/dev/null/foo'])
cli.parse()
+ self.assertRaisesRegex(errors.AnsibleOptionsError,
+ "not a tty, editor cannot be opened",
+ cli.run)
+
+ @patch('ansible.cli.vault.VaultCLI.setup_vault_secrets')
+ @patch('ansible.cli.vault.VaultEditor')
+ def test_create_skip_tty_check(self, mock_vault_editor, mock_setup_vault_secrets):
+ mock_setup_vault_secrets.return_value = [('default', TextVaultSecret('password'))]
+ cli = VaultCLI(args=['ansible-vault', 'create', '--skip-tty-check', '/dev/null/foo'])
+ cli.parse()
+ cli.run()
+
+ @patch('ansible.cli.vault.VaultCLI.setup_vault_secrets')
+ @patch('ansible.cli.vault.VaultEditor')
+ def test_create_with_tty(self, mock_vault_editor, mock_setup_vault_secrets):
+ mock_setup_vault_secrets.return_value = [('default', TextVaultSecret('password'))]
+ self.tty_stdout_patcher = patch('ansible.cli.sys.stdout.isatty', return_value=True)
+ self.tty_stdout_patcher.start()
+ cli = VaultCLI(args=['ansible-vault', 'create', '/dev/null/foo'])
+ cli.parse()
cli.run()
+ self.tty_stdout_patcher.stop()
@patch('ansible.cli.vault.VaultCLI.setup_vault_secrets')
@patch('ansible.cli.vault.VaultEditor')
diff --git a/test/units/compat/mock.py b/test/units/compat/mock.py
index 58dc78e0..03154609 100644
--- a/test/units/compat/mock.py
+++ b/test/units/compat/mock.py
@@ -6,7 +6,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
try:
- from unittest.mock import (
+ from unittest.mock import ( # pylint: disable=unused-import
call,
patch,
mock_open,
diff --git a/test/units/config/manager/test_find_ini_config_file.py b/test/units/config/manager/test_find_ini_config_file.py
index df411388..e67eecd9 100644
--- a/test/units/config/manager/test_find_ini_config_file.py
+++ b/test/units/config/manager/test_find_ini_config_file.py
@@ -13,7 +13,7 @@ import stat
import pytest
from ansible.config.manager import find_ini_config_file
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
real_exists = os.path.exists
real_isdir = os.path.isdir
@@ -28,22 +28,17 @@ cfg_in_homedir = os.path.expanduser('~/.ansible.cfg')
@pytest.fixture
-def setup_env(request):
+def setup_env(request, monkeypatch):
cur_config = os.environ.get('ANSIBLE_CONFIG', None)
cfg_path = request.param[0]
if cfg_path is None and cur_config:
- del os.environ['ANSIBLE_CONFIG']
+ monkeypatch.delenv('ANSIBLE_CONFIG')
else:
- os.environ['ANSIBLE_CONFIG'] = request.param[0]
+ monkeypatch.setenv('ANSIBLE_CONFIG', request.param[0])
yield
- if cur_config is None and cfg_path:
- del os.environ['ANSIBLE_CONFIG']
- else:
- os.environ['ANSIBLE_CONFIG'] = cur_config
-
@pytest.fixture
def setup_existing_files(request, monkeypatch):
@@ -54,10 +49,8 @@ def setup_existing_files(request, monkeypatch):
return False
def _os_access(path, access):
- if to_text(path) in (request.param[0]):
- return True
- else:
- return False
+ assert to_text(path) in (request.param[0])
+ return True
# Enable user and system dirs so that we know cwd takes precedence
monkeypatch.setattr("os.path.exists", _os_path_exists)
@@ -162,13 +155,11 @@ class TestFindIniFile:
real_stat = os.stat
def _os_stat(path):
- if path == working_dir:
- from posix import stat_result
- stat_info = list(real_stat(path))
- stat_info[stat.ST_MODE] |= stat.S_IWOTH
- return stat_result(stat_info)
- else:
- return real_stat(path)
+ assert path == working_dir
+ from posix import stat_result
+ stat_info = list(real_stat(path))
+ stat_info[stat.ST_MODE] |= stat.S_IWOTH
+ return stat_result(stat_info)
monkeypatch.setattr('os.stat', _os_stat)
@@ -187,13 +178,11 @@ class TestFindIniFile:
real_stat = os.stat
def _os_stat(path):
- if path == working_dir:
- from posix import stat_result
- stat_info = list(real_stat(path))
- stat_info[stat.ST_MODE] |= stat.S_IWOTH
- return stat_result(stat_info)
- else:
- return real_stat(path)
+ assert path == working_dir
+ from posix import stat_result
+ stat_info = list(real_stat(path))
+ stat_info[stat.ST_MODE] |= stat.S_IWOTH
+ return stat_result(stat_info)
monkeypatch.setattr('os.stat', _os_stat)
@@ -215,14 +204,14 @@ class TestFindIniFile:
real_stat = os.stat
def _os_stat(path):
- if path == working_dir:
- from posix import stat_result
- stat_info = list(real_stat(path))
- stat_info[stat.ST_MODE] |= stat.S_IWOTH
- return stat_result(stat_info)
- else:
+ if path != working_dir:
return real_stat(path)
+ from posix import stat_result
+ stat_info = list(real_stat(path))
+ stat_info[stat.ST_MODE] |= stat.S_IWOTH
+ return stat_result(stat_info)
+
monkeypatch.setattr('os.stat', _os_stat)
warnings = set()
@@ -240,13 +229,11 @@ class TestFindIniFile:
real_stat = os.stat
def _os_stat(path):
- if path == working_dir:
- from posix import stat_result
- stat_info = list(real_stat(path))
- stat_info[stat.ST_MODE] |= stat.S_IWOTH
- return stat_result(stat_info)
- else:
- return real_stat(path)
+ assert path == working_dir
+ from posix import stat_result
+ stat_info = list(real_stat(path))
+ stat_info[stat.ST_MODE] |= stat.S_IWOTH
+ return stat_result(stat_info)
monkeypatch.setattr('os.stat', _os_stat)
diff --git a/test/units/config/test_manager.py b/test/units/config/test_manager.py
index 8ef40437..0848276c 100644
--- a/test/units/config/test_manager.py
+++ b/test/units/config/test_manager.py
@@ -10,7 +10,7 @@ import os
import os.path
import pytest
-from ansible.config.manager import ConfigManager, Setting, ensure_type, resolve_path, get_config_type
+from ansible.config.manager import ConfigManager, ensure_type, resolve_path, get_config_type
from ansible.errors import AnsibleOptionsError, AnsibleError
from ansible.module_utils.six import integer_types, string_types
from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
@@ -18,6 +18,7 @@ from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
curdir = os.path.dirname(__file__)
cfg_file = os.path.join(curdir, 'test.cfg')
cfg_file2 = os.path.join(curdir, 'test2.cfg')
+cfg_file3 = os.path.join(curdir, 'test3.cfg')
ensure_test_data = [
('a,b', 'list', list),
@@ -65,6 +66,15 @@ ensure_test_data = [
('None', 'none', type(None))
]
+ensure_unquoting_test_data = [
+ ('"value"', '"value"', 'str', 'env'),
+ ('"value"', '"value"', 'str', 'yaml'),
+ ('"value"', 'value', 'str', 'ini'),
+ ('\'value\'', 'value', 'str', 'ini'),
+ ('\'\'value\'\'', '\'value\'', 'str', 'ini'),
+ ('""value""', '"value"', 'str', 'ini')
+]
+
class TestConfigManager:
@classmethod
@@ -79,6 +89,11 @@ class TestConfigManager:
def test_ensure_type(self, value, expected_type, python_type):
assert isinstance(ensure_type(value, expected_type), python_type)
+ @pytest.mark.parametrize("value, expected_value, value_type, origin", ensure_unquoting_test_data)
+ def test_ensure_type_unquoting(self, value, expected_value, value_type, origin):
+ actual_value = ensure_type(value, value_type, origin)
+ assert actual_value == expected_value
+
def test_resolve_path(self):
assert os.path.join(curdir, 'test.yml') == resolve_path('./test.yml', cfg_file)
@@ -142,3 +157,16 @@ class TestConfigManager:
actual_value = ensure_type(vault_var, value_type)
assert actual_value == "vault text"
+
+
+@pytest.mark.parametrize(("key", "expected_value"), (
+ ("COLOR_UNREACHABLE", "bright red"),
+ ("COLOR_VERBOSE", "rgb013"),
+ ("COLOR_DEBUG", "gray10")))
+def test_256color_support(key, expected_value):
+ # GIVEN: a config file containing 256-color values with default definitions
+ manager = ConfigManager(cfg_file3)
+ # WHEN: get config values
+ actual_value = manager.get_config_value(key)
+ # THEN: no error
+ assert actual_value == expected_value
diff --git a/test/units/executor/module_common/test_modify_module.py b/test/units/executor/module_common/test_modify_module.py
index dceef763..89e4a163 100644
--- a/test/units/executor/module_common/test_modify_module.py
+++ b/test/units/executor/module_common/test_modify_module.py
@@ -8,9 +8,6 @@ __metaclass__ = type
import pytest
from ansible.executor.module_common import modify_module
-from ansible.module_utils.six import PY2
-
-from test_module_common import templar
FAKE_OLD_MODULE = b'''#!/usr/bin/python
@@ -22,10 +19,7 @@ print('{"result": "%s"}' % sys.executable)
@pytest.fixture
def fake_old_module_open(mocker):
m = mocker.mock_open(read_data=FAKE_OLD_MODULE)
- if PY2:
- mocker.patch('__builtin__.open', m)
- else:
- mocker.patch('builtins.open', m)
+ mocker.patch('builtins.open', m)
# this test no longer makes sense, since a Python module will always either have interpreter discovery run or
# an explicit interpreter passed (so we'll never default to the module shebang)
diff --git a/test/units/executor/module_common/test_module_common.py b/test/units/executor/module_common/test_module_common.py
index fa6add8c..6e2a4956 100644
--- a/test/units/executor/module_common/test_module_common.py
+++ b/test/units/executor/module_common/test_module_common.py
@@ -27,7 +27,6 @@ import ansible.errors
from ansible.executor import module_common as amc
from ansible.executor.interpreter_discovery import InterpreterDiscoveryRequiredError
-from ansible.module_utils.six import PY2
class TestStripComments:
@@ -44,15 +43,16 @@ class TestStripComments:
assert amc._strip_comments(all_comments) == u""
def test_all_whitespace(self):
- # Note: Do not remove the spaces on the blank lines below. They're
- # test data to show that the lines get removed despite having spaces
- # on them
- all_whitespace = u"""
-
-
-
-\t\t\r\n
- """ # nopep8
+ all_whitespace = (
+ '\n'
+ ' \n'
+ '\n'
+ ' \n'
+ '\t\t\r\n'
+ '\n'
+ ' '
+ )
+
assert amc._strip_comments(all_whitespace) == u""
def test_somewhat_normal(self):
@@ -80,31 +80,16 @@ class TestSlurp:
def test_slurp_file(self, mocker):
mocker.patch('os.path.exists', side_effect=lambda x: True)
m = mocker.mock_open(read_data='This is a test')
- if PY2:
- mocker.patch('__builtin__.open', m)
- else:
- mocker.patch('builtins.open', m)
+ mocker.patch('builtins.open', m)
assert amc._slurp('some_file') == 'This is a test'
def test_slurp_file_with_newlines(self, mocker):
mocker.patch('os.path.exists', side_effect=lambda x: True)
m = mocker.mock_open(read_data='#!/usr/bin/python\ndef test(args):\nprint("hi")\n')
- if PY2:
- mocker.patch('__builtin__.open', m)
- else:
- mocker.patch('builtins.open', m)
+ mocker.patch('builtins.open', m)
assert amc._slurp('some_file') == '#!/usr/bin/python\ndef test(args):\nprint("hi")\n'
-@pytest.fixture
-def templar():
- class FakeTemplar:
- def template(self, template_string, *args, **kwargs):
- return template_string
-
- return FakeTemplar()
-
-
class TestGetShebang:
"""Note: We may want to change the API of this function in the future. It isn't a great API"""
def test_no_interpreter_set(self, templar):
diff --git a/test/units/executor/module_common/test_recursive_finder.py b/test/units/executor/module_common/test_recursive_finder.py
index 8136a006..95b49d35 100644
--- a/test/units/executor/module_common/test_recursive_finder.py
+++ b/test/units/executor/module_common/test_recursive_finder.py
@@ -29,7 +29,7 @@ from io import BytesIO
import ansible.errors
from ansible.executor.module_common import recursive_finder
-
+from ansible.plugins.loader import init_plugin_loader
# These are the modules that are brought in by module_utils/basic.py This may need to be updated
# when basic.py gains new imports
@@ -42,7 +42,6 @@ MODULE_UTILS_BASIC_FILES = frozenset(('ansible/__init__.py',
'ansible/module_utils/basic.py',
'ansible/module_utils/six/__init__.py',
'ansible/module_utils/_text.py',
- 'ansible/module_utils/common/_collections_compat.py',
'ansible/module_utils/common/_json_compat.py',
'ansible/module_utils/common/collections.py',
'ansible/module_utils/common/parameters.py',
@@ -79,6 +78,8 @@ ANSIBLE_LIB = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.pa
@pytest.fixture
def finder_containers():
+ init_plugin_loader()
+
FinderContainers = namedtuple('FinderContainers', ['zf'])
zipoutput = BytesIO()
diff --git a/test/units/executor/test_interpreter_discovery.py b/test/units/executor/test_interpreter_discovery.py
index 43db5950..10fc64be 100644
--- a/test/units/executor/test_interpreter_discovery.py
+++ b/test/units/executor/test_interpreter_discovery.py
@@ -9,7 +9,7 @@ __metaclass__ = type
from unittest.mock import MagicMock
from ansible.executor.interpreter_discovery import discover_interpreter
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
mock_ubuntu_platform_res = to_text(
r'{"osrelease_content": "NAME=\"Ubuntu\"\nVERSION=\"16.04.5 LTS (Xenial Xerus)\"\nID=ubuntu\nID_LIKE=debian\n'
@@ -20,7 +20,7 @@ mock_ubuntu_platform_res = to_text(
def test_discovery_interpreter_linux_auto_legacy():
- res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3.5\n/usr/bin/python3\nENDFOUND'
+ res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3\nENDFOUND'
mock_action = MagicMock()
mock_action._low_level_execute_command.side_effect = [{'stdout': res1}, {'stdout': mock_ubuntu_platform_res}]
@@ -35,7 +35,7 @@ def test_discovery_interpreter_linux_auto_legacy():
def test_discovery_interpreter_linux_auto_legacy_silent():
- res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3.5\n/usr/bin/python3\nENDFOUND'
+ res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3\nENDFOUND'
mock_action = MagicMock()
mock_action._low_level_execute_command.side_effect = [{'stdout': res1}, {'stdout': mock_ubuntu_platform_res}]
@@ -47,7 +47,7 @@ def test_discovery_interpreter_linux_auto_legacy_silent():
def test_discovery_interpreter_linux_auto():
- res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3.5\n/usr/bin/python3\nENDFOUND'
+ res1 = u'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python3\nENDFOUND'
mock_action = MagicMock()
mock_action._low_level_execute_command.side_effect = [{'stdout': res1}, {'stdout': mock_ubuntu_platform_res}]
diff --git a/test/units/executor/test_play_iterator.py b/test/units/executor/test_play_iterator.py
index 6670888e..0fc59756 100644
--- a/test/units/executor/test_play_iterator.py
+++ b/test/units/executor/test_play_iterator.py
@@ -25,6 +25,7 @@ from unittest.mock import patch, MagicMock
from ansible.executor.play_iterator import HostState, PlayIterator, IteratingStates, FailedStates
from ansible.playbook import Playbook
from ansible.playbook.play_context import PlayContext
+from ansible.plugins.loader import init_plugin_loader
from units.mock.loader import DictDataLoader
from units.mock.path import mock_unfrackpath_noop
@@ -85,7 +86,8 @@ class TestPlayIterator(unittest.TestCase):
always:
- name: role always task
debug: msg="always task in block in role"
- - include: foo.yml
+ - name: role include_tasks
+ include_tasks: foo.yml
- name: role task after include
debug: msg="after include in role"
- block:
@@ -170,12 +172,12 @@ class TestPlayIterator(unittest.TestCase):
self.assertIsNotNone(task)
self.assertEqual(task.name, "role always task")
self.assertIsNotNone(task._role)
- # role include task
- # (host_state, task) = itr.get_next_task_for_host(hosts[0])
- # self.assertIsNotNone(task)
- # self.assertEqual(task.action, 'debug')
- # self.assertEqual(task.name, "role included task")
- # self.assertIsNotNone(task._role)
+ # role include_tasks
+ (host_state, task) = itr.get_next_task_for_host(hosts[0])
+ self.assertIsNotNone(task)
+ self.assertEqual(task.action, 'include_tasks')
+ self.assertEqual(task.name, "role include_tasks")
+ self.assertIsNotNone(task._role)
# role task after include
(host_state, task) = itr.get_next_task_for_host(hosts[0])
self.assertIsNotNone(task)
@@ -286,6 +288,7 @@ class TestPlayIterator(unittest.TestCase):
self.assertNotIn(hosts[0], failed_hosts)
def test_play_iterator_nested_blocks(self):
+ init_plugin_loader()
fake_loader = DictDataLoader({
"test_play.yml": """
- hosts: all
@@ -427,12 +430,11 @@ class TestPlayIterator(unittest.TestCase):
)
# iterate past first task
- _, task = itr.get_next_task_for_host(hosts[0])
+ dummy, task = itr.get_next_task_for_host(hosts[0])
while (task and task.action != 'debug'):
- _, task = itr.get_next_task_for_host(hosts[0])
+ dummy, task = itr.get_next_task_for_host(hosts[0])
- if task is None:
- raise Exception("iterated past end of play while looking for place to insert tasks")
+ self.assertIsNotNone(task, 'iterated past end of play while looking for place to insert tasks')
# get the current host state and copy it so we can mutate it
s = itr.get_host_state(hosts[0])
diff --git a/test/units/executor/test_task_executor.py b/test/units/executor/test_task_executor.py
index 315d26ae..66ab0036 100644
--- a/test/units/executor/test_task_executor.py
+++ b/test/units/executor/test_task_executor.py
@@ -25,7 +25,7 @@ from units.compat import unittest
from unittest.mock import patch, MagicMock
from ansible.errors import AnsibleError
from ansible.executor.task_executor import TaskExecutor, remove_omit
-from ansible.plugins.loader import action_loader, lookup_loader, module_loader
+from ansible.plugins.loader import action_loader, lookup_loader
from ansible.parsing.yaml.objects import AnsibleUnicode
from ansible.utils.unsafe_proxy import AnsibleUnsafeText, AnsibleUnsafeBytes
from ansible.module_utils.six import text_type
@@ -57,6 +57,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
+ variable_manager=MagicMock(),
)
def test_task_executor_run(self):
@@ -84,6 +85,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
+ variable_manager=MagicMock(),
)
te._get_loop_items = MagicMock(return_value=None)
@@ -102,7 +104,7 @@ class TestTaskExecutor(unittest.TestCase):
self.assertIn("failed", res)
def test_task_executor_run_clean_res(self):
- te = TaskExecutor(None, MagicMock(), None, None, None, None, None, None)
+ te = TaskExecutor(None, MagicMock(), None, None, None, None, None, None, None)
te._get_loop_items = MagicMock(return_value=[1])
te._run_loop = MagicMock(
return_value=[
@@ -150,6 +152,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
+ variable_manager=MagicMock(),
)
items = te._get_loop_items()
@@ -186,6 +189,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
+ variable_manager=MagicMock(),
)
def _execute(variables):
@@ -206,6 +210,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
+ variable_manager=MagicMock(),
)
context = MagicMock(resolved=False)
@@ -214,20 +219,20 @@ class TestTaskExecutor(unittest.TestCase):
action_loader.has_plugin.return_value = True
action_loader.get.return_value = mock.sentinel.handler
- mock_connection = MagicMock()
mock_templar = MagicMock()
action = 'namespace.prefix_suffix'
te._task.action = action
+ te._connection = MagicMock()
- handler = te._get_action_handler(mock_connection, mock_templar)
+ with patch('ansible.executor.task_executor.start_connection'):
+ handler = te._get_action_handler(mock_templar)
self.assertIs(mock.sentinel.handler, handler)
- action_loader.has_plugin.assert_called_once_with(
- action, collection_list=te._task.collections)
+ action_loader.has_plugin.assert_called_once_with(action, collection_list=te._task.collections)
- action_loader.get.assert_called_once_with(
- te._task.action, task=te._task, connection=mock_connection,
+ action_loader.get.assert_called_with(
+ te._task.action, task=te._task, connection=te._connection,
play_context=te._play_context, loader=te._loader,
templar=mock_templar, shared_loader_obj=te._shared_loader_obj,
collection_list=te._task.collections)
@@ -242,6 +247,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
+ variable_manager=MagicMock(),
)
context = MagicMock(resolved=False)
@@ -251,20 +257,21 @@ class TestTaskExecutor(unittest.TestCase):
action_loader.get.return_value = mock.sentinel.handler
action_loader.__contains__.return_value = True
- mock_connection = MagicMock()
mock_templar = MagicMock()
action = 'namespace.netconf_suffix'
module_prefix = action.split('_', 1)[0]
te._task.action = action
+ te._connection = MagicMock()
- handler = te._get_action_handler(mock_connection, mock_templar)
+ with patch('ansible.executor.task_executor.start_connection'):
+ handler = te._get_action_handler(mock_templar)
self.assertIs(mock.sentinel.handler, handler)
action_loader.has_plugin.assert_has_calls([mock.call(action, collection_list=te._task.collections), # called twice
mock.call(module_prefix, collection_list=te._task.collections)])
- action_loader.get.assert_called_once_with(
- module_prefix, task=te._task, connection=mock_connection,
+ action_loader.get.assert_called_with(
+ module_prefix, task=te._task, connection=te._connection,
play_context=te._play_context, loader=te._loader,
templar=mock_templar, shared_loader_obj=te._shared_loader_obj,
collection_list=te._task.collections)
@@ -279,6 +286,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
+ variable_manager=MagicMock(),
)
action_loader = te._shared_loader_obj.action_loader
@@ -289,20 +297,22 @@ class TestTaskExecutor(unittest.TestCase):
context = MagicMock(resolved=False)
module_loader.find_plugin_with_context.return_value = context
- mock_connection = MagicMock()
mock_templar = MagicMock()
action = 'namespace.prefix_suffix'
module_prefix = action.split('_', 1)[0]
te._task.action = action
- handler = te._get_action_handler(mock_connection, mock_templar)
+ te._connection = MagicMock()
+
+ with patch('ansible.executor.task_executor.start_connection'):
+ handler = te._get_action_handler(mock_templar)
self.assertIs(mock.sentinel.handler, handler)
action_loader.has_plugin.assert_has_calls([mock.call(action, collection_list=te._task.collections),
mock.call(module_prefix, collection_list=te._task.collections)])
- action_loader.get.assert_called_once_with(
- 'ansible.legacy.normal', task=te._task, connection=mock_connection,
+ action_loader.get.assert_called_with(
+ 'ansible.legacy.normal', task=te._task, connection=te._connection,
play_context=te._play_context, loader=te._loader,
templar=mock_templar, shared_loader_obj=te._shared_loader_obj,
collection_list=None)
@@ -318,6 +328,7 @@ class TestTaskExecutor(unittest.TestCase):
mock_task.become = False
mock_task.retries = 0
mock_task.delay = -1
+ mock_task.delegate_to = None
mock_task.register = 'foo'
mock_task.until = None
mock_task.changed_when = None
@@ -329,6 +340,7 @@ class TestTaskExecutor(unittest.TestCase):
# other reason is that if I specify 0 here, the test fails. ;)
mock_task.async_val = 1
mock_task.poll = 0
+ mock_task.evaluate_conditional_with_result.return_value = (True, None)
mock_play_context = MagicMock()
mock_play_context.post_validate.return_value = None
@@ -343,6 +355,9 @@ class TestTaskExecutor(unittest.TestCase):
mock_action = MagicMock()
mock_queue = MagicMock()
+ mock_vm = MagicMock()
+ mock_vm.get_delegated_vars_and_hostname.return_value = {}, None
+
shared_loader = MagicMock()
new_stdin = None
job_vars = dict(omit="XXXXXXXXXXXXXXXXXXX")
@@ -356,11 +371,14 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=shared_loader,
final_q=mock_queue,
+ variable_manager=mock_vm,
)
te._get_connection = MagicMock(return_value=mock_connection)
context = MagicMock()
- te._get_action_handler_with_context = MagicMock(return_value=get_with_context_result(mock_action, context))
+
+ with patch('ansible.executor.task_executor.start_connection'):
+ te._get_action_handler_with_context = MagicMock(return_value=get_with_context_result(mock_action, context))
mock_action.run.return_value = dict(ansible_facts=dict())
res = te._execute()
@@ -392,8 +410,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_play_context = MagicMock()
- mock_connection = MagicMock()
-
mock_action = MagicMock()
mock_queue = MagicMock()
@@ -412,6 +428,7 @@ class TestTaskExecutor(unittest.TestCase):
loader=fake_loader,
shared_loader_obj=shared_loader,
final_q=mock_queue,
+ variable_manager=MagicMock(),
)
te._connection = MagicMock()
diff --git a/test/units/galaxy/test_api.py b/test/units/galaxy/test_api.py
index 064aff29..b019f1aa 100644
--- a/test/units/galaxy/test_api.py
+++ b/test/units/galaxy/test_api.py
@@ -24,7 +24,7 @@ from ansible.errors import AnsibleError
from ansible.galaxy import api as galaxy_api
from ansible.galaxy.api import CollectionVersionMetadata, GalaxyAPI, GalaxyError
from ansible.galaxy.token import BasicAuthToken, GalaxyToken, KeycloakToken
-from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.six.moves.urllib import error as urllib_error
from ansible.utils import context_objects as co
from ansible.utils.display import Display
@@ -463,10 +463,9 @@ def test_publish_failure(api_version, collection_url, response, expected, collec
def test_wait_import_task(server_url, api_version, token_type, token_ins, import_uri, full_import_uri, monkeypatch):
api = get_test_galaxy_api(server_url, api_version, token_ins=token_ins)
- if token_ins:
- mock_token_get = MagicMock()
- mock_token_get.return_value = 'my token'
- monkeypatch.setattr(token_ins, 'get', mock_token_get)
+ mock_token_get = MagicMock()
+ mock_token_get.return_value = 'my token'
+ monkeypatch.setattr(token_ins, 'get', mock_token_get)
mock_open = MagicMock()
mock_open.return_value = StringIO(u'{"state":"success","finished_at":"time"}')
@@ -496,10 +495,9 @@ def test_wait_import_task(server_url, api_version, token_type, token_ins, import
def test_wait_import_task_multiple_requests(server_url, api_version, token_type, token_ins, import_uri, full_import_uri, monkeypatch):
api = get_test_galaxy_api(server_url, api_version, token_ins=token_ins)
- if token_ins:
- mock_token_get = MagicMock()
- mock_token_get.return_value = 'my token'
- monkeypatch.setattr(token_ins, 'get', mock_token_get)
+ mock_token_get = MagicMock()
+ mock_token_get.return_value = 'my token'
+ monkeypatch.setattr(token_ins, 'get', mock_token_get)
mock_open = MagicMock()
mock_open.side_effect = [
@@ -543,10 +541,9 @@ def test_wait_import_task_multiple_requests(server_url, api_version, token_type,
def test_wait_import_task_with_failure(server_url, api_version, token_type, token_ins, import_uri, full_import_uri, monkeypatch):
api = get_test_galaxy_api(server_url, api_version, token_ins=token_ins)
- if token_ins:
- mock_token_get = MagicMock()
- mock_token_get.return_value = 'my token'
- monkeypatch.setattr(token_ins, 'get', mock_token_get)
+ mock_token_get = MagicMock()
+ mock_token_get.return_value = 'my token'
+ monkeypatch.setattr(token_ins, 'get', mock_token_get)
mock_open = MagicMock()
mock_open.side_effect = [
@@ -620,10 +617,9 @@ def test_wait_import_task_with_failure(server_url, api_version, token_type, toke
def test_wait_import_task_with_failure_no_error(server_url, api_version, token_type, token_ins, import_uri, full_import_uri, monkeypatch):
api = get_test_galaxy_api(server_url, api_version, token_ins=token_ins)
- if token_ins:
- mock_token_get = MagicMock()
- mock_token_get.return_value = 'my token'
- monkeypatch.setattr(token_ins, 'get', mock_token_get)
+ mock_token_get = MagicMock()
+ mock_token_get.return_value = 'my token'
+ monkeypatch.setattr(token_ins, 'get', mock_token_get)
mock_open = MagicMock()
mock_open.side_effect = [
@@ -693,10 +689,9 @@ def test_wait_import_task_with_failure_no_error(server_url, api_version, token_t
def test_wait_import_task_timeout(server_url, api_version, token_type, token_ins, import_uri, full_import_uri, monkeypatch):
api = get_test_galaxy_api(server_url, api_version, token_ins=token_ins)
- if token_ins:
- mock_token_get = MagicMock()
- mock_token_get.return_value = 'my token'
- monkeypatch.setattr(token_ins, 'get', mock_token_get)
+ mock_token_get = MagicMock()
+ mock_token_get.return_value = 'my token'
+ monkeypatch.setattr(token_ins, 'get', mock_token_get)
def return_response(*args, **kwargs):
return StringIO(u'{"state":"waiting"}')
diff --git a/test/units/galaxy/test_collection.py b/test/units/galaxy/test_collection.py
index 106251c5..991184ae 100644
--- a/test/units/galaxy/test_collection.py
+++ b/test/units/galaxy/test_collection.py
@@ -20,10 +20,11 @@ from unittest.mock import MagicMock, mock_open, patch
import ansible.constants as C
from ansible import context
-from ansible.cli.galaxy import GalaxyCLI, SERVER_DEF
+from ansible.cli import galaxy
+from ansible.cli.galaxy import GalaxyCLI
from ansible.errors import AnsibleError
from ansible.galaxy import api, collection, token
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.six.moves import builtins
from ansible.utils import context_objects as co
from ansible.utils.display import Display
@@ -171,28 +172,6 @@ def manifest_info(manifest_template):
@pytest.fixture()
-def files_manifest_info():
- return {
- "files": [
- {
- "name": ".",
- "ftype": "dir",
- "chksum_type": None,
- "chksum_sha256": None,
- "format": 1
- },
- {
- "name": "README.md",
- "ftype": "file",
- "chksum_type": "sha256",
- "chksum_sha256": "individual_file_checksum",
- "format": 1
- }
- ],
- "format": 1}
-
-
-@pytest.fixture()
def manifest(manifest_info):
b_data = to_bytes(json.dumps(manifest_info))
@@ -245,23 +224,19 @@ def test_cli_options(required_signature_count, valid, monkeypatch):
{
'url': 'https://galaxy.ansible.com',
'validate_certs': 'False',
- 'v3': 'False',
},
# Expected server attributes
{
'validate_certs': False,
- '_available_api_versions': {},
},
),
(
{
'url': 'https://galaxy.ansible.com',
'validate_certs': 'True',
- 'v3': 'True',
},
{
'validate_certs': True,
- '_available_api_versions': {'v3': '/v3'},
},
),
],
@@ -279,7 +254,6 @@ def test_bool_type_server_config_options(config, server, monkeypatch):
"server_list=server1\n",
"[galaxy_server.server1]",
"url=%s" % config['url'],
- "v3=%s" % config['v3'],
"validate_certs=%s\n" % config['validate_certs'],
]
@@ -299,7 +273,6 @@ def test_bool_type_server_config_options(config, server, monkeypatch):
assert galaxy_cli.api_servers[0].name == 'server1'
assert galaxy_cli.api_servers[0].validate_certs == server['validate_certs']
- assert galaxy_cli.api_servers[0]._available_api_versions == server['_available_api_versions']
@pytest.mark.parametrize('global_ignore_certs', [True, False])
@@ -411,6 +384,55 @@ def test_validate_certs_server_config(ignore_certs_cfg, ignore_certs_cli, expect
assert galaxy_cli.api_servers[2].validate_certs is expected_server3_validate_certs
+@pytest.mark.parametrize(
+ ["timeout_cli", "timeout_cfg", "timeout_fallback", "expected_timeout"],
+ [
+ (None, None, None, 60),
+ (None, None, 10, 10),
+ (None, 20, 10, 20),
+ (30, 20, 10, 30),
+ ]
+)
+def test_timeout_server_config(timeout_cli, timeout_cfg, timeout_fallback, expected_timeout, monkeypatch):
+ cli_args = [
+ 'ansible-galaxy',
+ 'collection',
+ 'install',
+ 'namespace.collection:1.0.0',
+ ]
+ if timeout_cli is not None:
+ cli_args.extend(["--timeout", f"{timeout_cli}"])
+
+ cfg_lines = ["[galaxy]", "server_list=server1"]
+ if timeout_fallback is not None:
+ cfg_lines.append(f"server_timeout={timeout_fallback}")
+
+ # fix default in server config since C.GALAXY_SERVER_TIMEOUT was already evaluated
+ server_additional = galaxy.SERVER_ADDITIONAL.copy()
+ server_additional['timeout']['default'] = timeout_fallback
+ monkeypatch.setattr(galaxy, 'SERVER_ADDITIONAL', server_additional)
+
+ cfg_lines.extend(["[galaxy_server.server1]", "url=https://galaxy.ansible.com/api/"])
+ if timeout_cfg is not None:
+ cfg_lines.append(f"timeout={timeout_cfg}")
+
+ monkeypatch.setattr(C, 'GALAXY_SERVER_LIST', ['server1'])
+
+ with tempfile.NamedTemporaryFile(suffix='.cfg') as tmp_file:
+ tmp_file.write(to_bytes('\n'.join(cfg_lines), errors='surrogate_or_strict'))
+ tmp_file.flush()
+
+ monkeypatch.setattr(C.config, '_config_file', tmp_file.name)
+ C.config._parse_config_file()
+
+ galaxy_cli = GalaxyCLI(args=cli_args)
+ mock_execute_install = MagicMock()
+ monkeypatch.setattr(galaxy_cli, '_execute_install_collection', mock_execute_install)
+ galaxy_cli.run()
+
+ assert galaxy_cli.api_servers[0].timeout == expected_timeout
+
+
def test_build_collection_no_galaxy_yaml():
fake_path = u'/fake/ÅÑŚÌβŁÈ/path'
expected = to_native("The collection galaxy.yml path '%s/galaxy.yml' does not exist." % fake_path)
@@ -479,19 +501,19 @@ def test_build_with_existing_files_and_manifest(collection_input):
with tarfile.open(output_artifact, mode='r') as actual:
members = actual.getmembers()
- manifest_file = next(m for m in members if m.path == "MANIFEST.json")
+ manifest_file = [m for m in members if m.path == "MANIFEST.json"][0]
manifest_file_obj = actual.extractfile(manifest_file.name)
manifest_file_text = manifest_file_obj.read()
manifest_file_obj.close()
assert manifest_file_text != b'{"collection_info": {"version": "6.6.6"}, "version": 1}'
- json_file = next(m for m in members if m.path == "MANIFEST.json")
+ json_file = [m for m in members if m.path == "MANIFEST.json"][0]
json_file_obj = actual.extractfile(json_file.name)
json_file_text = json_file_obj.read()
json_file_obj.close()
assert json_file_text != b'{"files": [], "format": 1}'
- sub_manifest_file = next(m for m in members if m.path == "plugins/MANIFEST.json")
+ sub_manifest_file = [m for m in members if m.path == "plugins/MANIFEST.json"][0]
sub_manifest_file_obj = actual.extractfile(sub_manifest_file.name)
sub_manifest_file_text = sub_manifest_file_obj.read()
sub_manifest_file_obj.close()
@@ -618,7 +640,7 @@ def test_build_ignore_files_and_folders(collection_input, monkeypatch):
tests_file.write('random')
tests_file.flush()
- actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel)
+ actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel, None)
assert actual['format'] == 1
for manifest_entry in actual['files']:
@@ -654,7 +676,7 @@ def test_build_ignore_older_release_in_root(collection_input, monkeypatch):
file_obj.write('random')
file_obj.flush()
- actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel)
+ actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel, None)
assert actual['format'] == 1
plugin_release_found = False
@@ -682,7 +704,7 @@ def test_build_ignore_patterns(collection_input, monkeypatch):
actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection',
['*.md', 'plugins/action', 'playbooks/*.j2'],
- Sentinel)
+ Sentinel, None)
assert actual['format'] == 1
expected_missing = [
@@ -733,7 +755,7 @@ def test_build_ignore_symlink_target_outside_collection(collection_input, monkey
link_path = os.path.join(input_dir, 'plugins', 'connection')
os.symlink(outside_dir, link_path)
- actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel)
+ actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel, None)
for manifest_entry in actual['files']:
assert manifest_entry['name'] != 'plugins/connection'
@@ -757,7 +779,7 @@ def test_build_copy_symlink_target_inside_collection(collection_input):
os.symlink(roles_target, roles_link)
- actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel)
+ actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', [], Sentinel, None)
linked_entries = [e for e in actual['files'] if e['name'].startswith('playbooks/roles/linked')]
assert len(linked_entries) == 1
@@ -790,11 +812,11 @@ def test_build_with_symlink_inside_collection(collection_input):
with tarfile.open(output_artifact, mode='r') as actual:
members = actual.getmembers()
- linked_folder = next(m for m in members if m.path == 'playbooks/roles/linked')
+ linked_folder = [m for m in members if m.path == 'playbooks/roles/linked'][0]
assert linked_folder.type == tarfile.SYMTYPE
assert linked_folder.linkname == '../../roles/linked'
- linked_file = next(m for m in members if m.path == 'docs/README.md')
+ linked_file = [m for m in members if m.path == 'docs/README.md'][0]
assert linked_file.type == tarfile.SYMTYPE
assert linked_file.linkname == '../README.md'
@@ -802,7 +824,7 @@ def test_build_with_symlink_inside_collection(collection_input):
actual_file = secure_hash_s(linked_file_obj.read())
linked_file_obj.close()
- assert actual_file == '63444bfc766154e1bc7557ef6280de20d03fcd81'
+ assert actual_file == '08f24200b9fbe18903e7a50930c9d0df0b8d7da3' # shasum test/units/cli/test_data/collection_skeleton/README.md
def test_publish_no_wait(galaxy_server, collection_artifact, monkeypatch):
@@ -854,57 +876,6 @@ def test_publish_with_wait(galaxy_server, collection_artifact, monkeypatch):
% galaxy_server.api_server
-def test_find_existing_collections(tmp_path_factory, monkeypatch):
- test_dir = to_text(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections'))
- concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False)
- collection1 = os.path.join(test_dir, 'namespace1', 'collection1')
- collection2 = os.path.join(test_dir, 'namespace2', 'collection2')
- fake_collection1 = os.path.join(test_dir, 'namespace3', 'collection3')
- fake_collection2 = os.path.join(test_dir, 'namespace4')
- os.makedirs(collection1)
- os.makedirs(collection2)
- os.makedirs(os.path.split(fake_collection1)[0])
-
- open(fake_collection1, 'wb+').close()
- open(fake_collection2, 'wb+').close()
-
- collection1_manifest = json.dumps({
- 'collection_info': {
- 'namespace': 'namespace1',
- 'name': 'collection1',
- 'version': '1.2.3',
- 'authors': ['Jordan Borean'],
- 'readme': 'README.md',
- 'dependencies': {},
- },
- 'format': 1,
- })
- with open(os.path.join(collection1, 'MANIFEST.json'), 'wb') as manifest_obj:
- manifest_obj.write(to_bytes(collection1_manifest))
-
- mock_warning = MagicMock()
- monkeypatch.setattr(Display, 'warning', mock_warning)
-
- actual = list(collection.find_existing_collections(test_dir, artifacts_manager=concrete_artifact_cm))
-
- assert len(actual) == 2
- for actual_collection in actual:
- if '%s.%s' % (actual_collection.namespace, actual_collection.name) == 'namespace1.collection1':
- assert actual_collection.namespace == 'namespace1'
- assert actual_collection.name == 'collection1'
- assert actual_collection.ver == '1.2.3'
- assert to_text(actual_collection.src) == collection1
- else:
- assert actual_collection.namespace == 'namespace2'
- assert actual_collection.name == 'collection2'
- assert actual_collection.ver == '*'
- assert to_text(actual_collection.src) == collection2
-
- assert mock_warning.call_count == 1
- assert mock_warning.mock_calls[0][1][0] == "Collection at '%s' does not have a MANIFEST.json file, nor has it galaxy.yml: " \
- "cannot detect version." % to_text(collection2)
-
-
def test_download_file(tmp_path_factory, monkeypatch):
temp_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections'))
@@ -1111,7 +1082,7 @@ def test_verify_file_hash_deleted_file(manifest_info):
with patch.object(collection.os.path, 'isfile', MagicMock(return_value=False)) as mock_isfile:
collection._verify_file_hash(b'path/', 'file', digest, error_queue)
- assert mock_isfile.called_once
+ mock_isfile.assert_called_once()
assert len(error_queue) == 1
assert error_queue[0].installed is None
@@ -1134,7 +1105,7 @@ def test_verify_file_hash_matching_hash(manifest_info):
with patch.object(collection.os.path, 'isfile', MagicMock(return_value=True)) as mock_isfile:
collection._verify_file_hash(b'path/', 'file', digest, error_queue)
- assert mock_isfile.called_once
+ mock_isfile.assert_called_once()
assert error_queue == []
@@ -1156,7 +1127,7 @@ def test_verify_file_hash_mismatching_hash(manifest_info):
with patch.object(collection.os.path, 'isfile', MagicMock(return_value=True)) as mock_isfile:
collection._verify_file_hash(b'path/', 'file', different_digest, error_queue)
- assert mock_isfile.called_once
+ mock_isfile.assert_called_once()
assert len(error_queue) == 1
assert error_queue[0].installed == digest
diff --git a/test/units/galaxy/test_collection_install.py b/test/units/galaxy/test_collection_install.py
index 2118f0ec..a61ae406 100644
--- a/test/units/galaxy/test_collection_install.py
+++ b/test/units/galaxy/test_collection_install.py
@@ -18,7 +18,6 @@ import yaml
from io import BytesIO, StringIO
from unittest.mock import MagicMock, patch
-from unittest import mock
import ansible.module_utils.six.moves.urllib.error as urllib_error
@@ -27,7 +26,7 @@ from ansible.cli.galaxy import GalaxyCLI
from ansible.errors import AnsibleError
from ansible.galaxy import collection, api, dependency_resolution
from ansible.galaxy.dependency_resolution.dataclasses import Candidate, Requirement
-from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible.module_utils.common.process import get_bin_path
from ansible.utils import context_objects as co
from ansible.utils.display import Display
@@ -53,78 +52,6 @@ def call_galaxy_cli(args):
co.GlobalCLIArgs._Singleton__instance = orig
-def artifact_json(namespace, name, version, dependencies, server):
- json_str = json.dumps({
- 'artifact': {
- 'filename': '%s-%s-%s.tar.gz' % (namespace, name, version),
- 'sha256': '2d76f3b8c4bab1072848107fb3914c345f71a12a1722f25c08f5d3f51f4ab5fd',
- 'size': 1234,
- },
- 'download_url': '%s/download/%s-%s-%s.tar.gz' % (server, namespace, name, version),
- 'metadata': {
- 'namespace': namespace,
- 'name': name,
- 'dependencies': dependencies,
- },
- 'version': version
- })
- return to_text(json_str)
-
-
-def artifact_versions_json(namespace, name, versions, galaxy_api, available_api_versions=None):
- results = []
- available_api_versions = available_api_versions or {}
- api_version = 'v2'
- if 'v3' in available_api_versions:
- api_version = 'v3'
- for version in versions:
- results.append({
- 'href': '%s/api/%s/%s/%s/versions/%s/' % (galaxy_api.api_server, api_version, namespace, name, version),
- 'version': version,
- })
-
- if api_version == 'v2':
- json_str = json.dumps({
- 'count': len(versions),
- 'next': None,
- 'previous': None,
- 'results': results
- })
-
- if api_version == 'v3':
- response = {'meta': {'count': len(versions)},
- 'data': results,
- 'links': {'first': None,
- 'last': None,
- 'next': None,
- 'previous': None},
- }
- json_str = json.dumps(response)
- return to_text(json_str)
-
-
-def error_json(galaxy_api, errors_to_return=None, available_api_versions=None):
- errors_to_return = errors_to_return or []
- available_api_versions = available_api_versions or {}
-
- response = {}
-
- api_version = 'v2'
- if 'v3' in available_api_versions:
- api_version = 'v3'
-
- if api_version == 'v2':
- assert len(errors_to_return) <= 1
- if errors_to_return:
- response = errors_to_return[0]
-
- if api_version == 'v3':
- response['errors'] = errors_to_return
-
- json_str = json.dumps(response)
- return to_text(json_str)
-
-
@pytest.fixture(autouse='function')
def reset_cli_args():
co.GlobalCLIArgs._Singleton__instance = None
@@ -371,6 +298,27 @@ def test_build_requirement_from_tar(collection_artifact):
assert actual.ver == u'0.1.0'
+def test_build_requirement_from_tar_url(tmp_path_factory):
+ test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input'))
+ concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False)
+ test_url = 'https://example.com/org/repo/sample.tar.gz'
+ expected = fr"^Failed to download collection tar from '{to_text(test_url)}'"
+
+ with pytest.raises(AnsibleError, match=expected):
+ Requirement.from_requirement_dict({'name': test_url, 'type': 'url'}, concrete_artifact_cm)
+
+
+def test_build_requirement_from_tar_url_wrong_type(tmp_path_factory):
+ test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input'))
+ concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(test_dir, validate_certs=False)
+ test_url = 'https://example.com/org/repo/sample.tar.gz'
+ expected = fr"^Unable to find collection artifact file at '{to_text(test_url)}'\.$"
+
+ with pytest.raises(AnsibleError, match=expected):
+ # Specified wrong collection type for http URL
+ Requirement.from_requirement_dict({'name': test_url, 'type': 'file'}, concrete_artifact_cm)
+
+
def test_build_requirement_from_tar_fail_not_tar(tmp_path_factory):
test_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections Input'))
test_file = os.path.join(test_dir, b'fake.tar.gz')
@@ -895,7 +843,8 @@ def test_install_collections_from_tar(collection_artifact, monkeypatch):
concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False)
requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)]
- collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False)
+ collection.install_collections(
+ requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set())
assert os.path.isdir(collection_path)
@@ -919,57 +868,6 @@ def test_install_collections_from_tar(collection_artifact, monkeypatch):
assert display_msgs[2] == "Installing 'ansible_namespace.collection:0.1.0' to '%s'" % to_text(collection_path)
-def test_install_collections_existing_without_force(collection_artifact, monkeypatch):
- collection_path, collection_tar = collection_artifact
- temp_path = os.path.split(collection_tar)[0]
-
- mock_display = MagicMock()
- monkeypatch.setattr(Display, 'display', mock_display)
-
- concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False)
-
- assert os.path.isdir(collection_path)
-
- requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)]
- collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False)
-
- assert os.path.isdir(collection_path)
-
- actual_files = os.listdir(collection_path)
- actual_files.sort()
- assert actual_files == [b'README.md', b'docs', b'galaxy.yml', b'playbooks', b'plugins', b'roles', b'runme.sh']
-
- # Filter out the progress cursor display calls.
- display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1]
- assert len(display_msgs) == 1
-
- assert display_msgs[0] == 'Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.'
-
- for msg in display_msgs:
- assert 'WARNING' not in msg
-
-
-def test_install_missing_metadata_warning(collection_artifact, monkeypatch):
- collection_path, collection_tar = collection_artifact
- temp_path = os.path.split(collection_tar)[0]
-
- mock_display = MagicMock()
- monkeypatch.setattr(Display, 'display', mock_display)
-
- for file in [b'MANIFEST.json', b'galaxy.yml']:
- b_path = os.path.join(collection_path, file)
- if os.path.isfile(b_path):
- os.unlink(b_path)
-
- concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False)
- requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)]
- collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False)
-
- display_msgs = [m[1][0] for m in mock_display.mock_calls if 'newline' not in m[2] and len(m[1]) == 1]
-
- assert 'WARNING' in display_msgs[0]
-
-
# Makes sure we don't get stuck in some recursive loop
@pytest.mark.parametrize('collection_artifact', [
{'ansible_namespace.collection': '>=0.0.1'},
@@ -984,7 +882,8 @@ def test_install_collection_with_circular_dependency(collection_artifact, monkey
concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False)
requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)]
- collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False)
+ collection.install_collections(
+ requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set())
assert os.path.isdir(collection_path)
@@ -1021,7 +920,8 @@ def test_install_collection_with_no_dependency(collection_artifact, monkeypatch)
concrete_artifact_cm = collection.concrete_artifact_manager.ConcreteArtifactsManager(temp_path, validate_certs=False)
requirements = [Requirement('ansible_namespace.collection', '0.1.0', to_text(collection_tar), 'file', None)]
- collection.install_collections(requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False)
+ collection.install_collections(
+ requirements, to_text(temp_path), [], False, False, False, False, False, False, concrete_artifact_cm, True, False, set())
assert os.path.isdir(collection_path)
diff --git a/test/units/galaxy/test_role_install.py b/test/units/galaxy/test_role_install.py
index 687fcac1..819ed186 100644
--- a/test/units/galaxy/test_role_install.py
+++ b/test/units/galaxy/test_role_install.py
@@ -7,6 +7,7 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+import json
import os
import functools
import pytest
@@ -16,7 +17,7 @@ from io import StringIO
from ansible import context
from ansible.cli.galaxy import GalaxyCLI
from ansible.galaxy import api, role, Galaxy
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from ansible.utils import context_objects as co
@@ -24,7 +25,7 @@ def call_galaxy_cli(args):
orig = co.GlobalCLIArgs._Singleton__instance
co.GlobalCLIArgs._Singleton__instance = None
try:
- GalaxyCLI(args=['ansible-galaxy', 'role'] + args).run()
+ return GalaxyCLI(args=['ansible-galaxy', 'role'] + args).run()
finally:
co.GlobalCLIArgs._Singleton__instance = orig
@@ -120,6 +121,22 @@ def test_role_download_github_no_download_url_for_version(init_mock_temp_file, m
assert mock_role_download_api.mock_calls[0][1][0] == 'https://github.com/test_owner/test_role/archive/0.0.1.tar.gz'
+@pytest.mark.parametrize(
+ 'state,rc',
+ [('SUCCESS', 0), ('FAILED', 1),]
+)
+def test_role_import(state, rc, mocker, galaxy_server, monkeypatch):
+ responses = [
+ {"available_versions": {"v1": "v1/"}},
+ {"results": [{'id': 12345, 'github_user': 'user', 'github_repo': 'role', 'github_reference': None, 'summary_fields': {'role': {'name': 'role'}}}]},
+ {"results": [{'state': 'WAITING', 'id': 12345, 'summary_fields': {'task_messages': []}}]},
+ {"results": [{'state': state, 'id': 12345, 'summary_fields': {'task_messages': []}}]},
+ ]
+ mock_api = mocker.MagicMock(side_effect=[StringIO(json.dumps(rsp)) for rsp in responses])
+ monkeypatch.setattr(api, 'open_url', mock_api)
+ assert call_galaxy_cli(['import', 'user', 'role']) == rc
+
+
def test_role_download_url(init_mock_temp_file, mocker, galaxy_server, mock_role_download_api, monkeypatch):
mock_api = mocker.MagicMock()
mock_api.side_effect = [
diff --git a/test/units/galaxy/test_token.py b/test/units/galaxy/test_token.py
index 24af3863..9fc12d46 100644
--- a/test/units/galaxy/test_token.py
+++ b/test/units/galaxy/test_token.py
@@ -13,7 +13,7 @@ from unittest.mock import MagicMock
import ansible.constants as C
from ansible.cli.galaxy import GalaxyCLI, SERVER_DEF
from ansible.galaxy.token import GalaxyToken, NoTokenSentinel
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
@pytest.fixture()
diff --git a/test/units/inventory/test_host.py b/test/units/inventory/test_host.py
index c8f47714..712ed302 100644
--- a/test/units/inventory/test_host.py
+++ b/test/units/inventory/test_host.py
@@ -69,10 +69,10 @@ class TestHost(unittest.TestCase):
def test_equals_none(self):
other = None
- self.hostA == other
- other == self.hostA
- self.hostA != other
- other != self.hostA
+ assert not (self.hostA == other)
+ assert not (other == self.hostA)
+ assert self.hostA != other
+ assert other != self.hostA
self.assertNotEqual(self.hostA, other)
def test_serialize(self):
diff --git a/test/units/mock/loader.py b/test/units/mock/loader.py
index f6ceb379..9dc32cae 100644
--- a/test/units/mock/loader.py
+++ b/test/units/mock/loader.py
@@ -21,16 +21,15 @@ __metaclass__ = type
import os
-from ansible.errors import AnsibleParserError
from ansible.parsing.dataloader import DataLoader
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
class DictDataLoader(DataLoader):
def __init__(self, file_mapping=None):
file_mapping = {} if file_mapping is None else file_mapping
- assert type(file_mapping) == dict
+ assert isinstance(file_mapping, dict)
super(DictDataLoader, self).__init__()
@@ -48,11 +47,7 @@ class DictDataLoader(DataLoader):
# TODO: the real _get_file_contents returns a bytestring, so we actually convert the
# unicode/text it's created with to utf-8
def _get_file_contents(self, file_name):
- path = to_text(file_name)
- if path in self._file_mapping:
- return to_bytes(self._file_mapping[file_name]), False
- else:
- raise AnsibleParserError("file not found: %s" % file_name)
+ return to_bytes(self._file_mapping[file_name]), False
def path_exists(self, path):
path = to_text(path)
@@ -91,25 +86,6 @@ class DictDataLoader(DataLoader):
self._add_known_directory(dirname)
dirname = os.path.dirname(dirname)
- def push(self, path, content):
- rebuild_dirs = False
- if path not in self._file_mapping:
- rebuild_dirs = True
-
- self._file_mapping[path] = content
-
- if rebuild_dirs:
- self._build_known_directories()
-
- def pop(self, path):
- if path in self._file_mapping:
- del self._file_mapping[path]
- self._build_known_directories()
-
- def clear(self):
- self._file_mapping = dict()
- self._known_directories = []
-
def get_basedir(self):
return os.getcwd()
diff --git a/test/units/mock/procenv.py b/test/units/mock/procenv.py
index 271a207e..1570c87e 100644
--- a/test/units/mock/procenv.py
+++ b/test/units/mock/procenv.py
@@ -27,7 +27,7 @@ from contextlib import contextmanager
from io import BytesIO, StringIO
from units.compat import unittest
from ansible.module_utils.six import PY3
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
@contextmanager
@@ -54,30 +54,9 @@ def swap_stdin_and_argv(stdin_data='', argv_data=tuple()):
sys.argv = real_argv
-@contextmanager
-def swap_stdout():
- """
- context manager that temporarily replaces stdout for tests that need to verify output
- """
- old_stdout = sys.stdout
-
- if PY3:
- fake_stream = StringIO()
- else:
- fake_stream = BytesIO()
-
- try:
- sys.stdout = fake_stream
-
- yield fake_stream
- finally:
- sys.stdout = old_stdout
-
-
class ModuleTestCase(unittest.TestCase):
- def setUp(self, module_args=None):
- if module_args is None:
- module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False}
+ def setUp(self):
+ module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False}
args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args))
diff --git a/test/units/mock/vault_helper.py b/test/units/mock/vault_helper.py
index dcce9c78..5b2fdd2a 100644
--- a/test/units/mock/vault_helper.py
+++ b/test/units/mock/vault_helper.py
@@ -15,7 +15,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.parsing.vault import VaultSecret
diff --git a/test/units/mock/yaml_helper.py b/test/units/mock/yaml_helper.py
index 1ef17215..9f8b063b 100644
--- a/test/units/mock/yaml_helper.py
+++ b/test/units/mock/yaml_helper.py
@@ -4,8 +4,6 @@ __metaclass__ = type
import io
import yaml
-from ansible.module_utils.six import PY3
-from ansible.parsing.yaml.loader import AnsibleLoader
from ansible.parsing.yaml.dumper import AnsibleDumper
@@ -15,21 +13,14 @@ class YamlTestUtils(object):
"""Vault related tests will want to override this.
Vault cases should setup a AnsibleLoader that has the vault password."""
- return AnsibleLoader(stream)
def _dump_stream(self, obj, stream, dumper=None):
"""Dump to a py2-unicode or py3-string stream."""
- if PY3:
- return yaml.dump(obj, stream, Dumper=dumper)
- else:
- return yaml.dump(obj, stream, Dumper=dumper, encoding=None)
+ return yaml.dump(obj, stream, Dumper=dumper)
def _dump_string(self, obj, dumper=None):
"""Dump to a py2-unicode or py3-string"""
- if PY3:
- return yaml.dump(obj, Dumper=dumper)
- else:
- return yaml.dump(obj, Dumper=dumper, encoding=None)
+ return yaml.dump(obj, Dumper=dumper)
def _dump_load_cycle(self, obj):
# Each pass though a dump or load revs the 'generation'
@@ -62,63 +53,3 @@ class YamlTestUtils(object):
# should be transitive, but...
self.assertEqual(obj_2, obj_3)
self.assertEqual(string_from_object_dump, string_from_object_dump_3)
-
- def _old_dump_load_cycle(self, obj):
- '''Dump the passed in object to yaml, load it back up, dump again, compare.'''
- stream = io.StringIO()
-
- yaml_string = self._dump_string(obj, dumper=AnsibleDumper)
- self._dump_stream(obj, stream, dumper=AnsibleDumper)
-
- yaml_string_from_stream = stream.getvalue()
-
- # reset stream
- stream.seek(0)
-
- loader = self._loader(stream)
- # loader = AnsibleLoader(stream, vault_password=self.vault_password)
- obj_from_stream = loader.get_data()
-
- stream_from_string = io.StringIO(yaml_string)
- loader2 = self._loader(stream_from_string)
- # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password)
- obj_from_string = loader2.get_data()
-
- stream_obj_from_stream = io.StringIO()
- stream_obj_from_string = io.StringIO()
-
- if PY3:
- yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper)
- yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper)
- else:
- yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None)
- yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None)
-
- yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue()
- yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue()
-
- stream_obj_from_stream.seek(0)
- stream_obj_from_string.seek(0)
-
- if PY3:
- yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper)
- yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper)
- else:
- yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None)
- yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None)
-
- assert yaml_string == yaml_string_obj_from_stream
- assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
- assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream ==
- yaml_string_stream_obj_from_string)
- assert obj == obj_from_stream
- assert obj == obj_from_string
- assert obj == yaml_string_obj_from_stream
- assert obj == yaml_string_obj_from_string
- assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string
- return {'obj': obj,
- 'yaml_string': yaml_string,
- 'yaml_string_from_stream': yaml_string_from_stream,
- 'obj_from_stream': obj_from_stream,
- 'obj_from_string': obj_from_string,
- 'yaml_string_obj_from_string': yaml_string_obj_from_string}
diff --git a/test/units/module_utils/basic/test__symbolic_mode_to_octal.py b/test/units/module_utils/basic/test__symbolic_mode_to_octal.py
index 7793b348..b3a73e5a 100644
--- a/test/units/module_utils/basic/test__symbolic_mode_to_octal.py
+++ b/test/units/module_utils/basic/test__symbolic_mode_to_octal.py
@@ -63,6 +63,14 @@ DATA = ( # Going from no permissions to setting all for user, group, and/or oth
# Multiple permissions
(0o040000, u'u=rw-x+X,g=r-x+X,o=r-x+X', 0o0755),
(0o100000, u'u=rw-x+X,g=r-x+X,o=r-x+X', 0o0644),
+ (0o040000, u'ug=rx,o=', 0o0550),
+ (0o100000, u'ug=rx,o=', 0o0550),
+ (0o040000, u'u=rx,g=r', 0o0540),
+ (0o100000, u'u=rx,g=r', 0o0540),
+ (0o040777, u'ug=rx,o=', 0o0550),
+ (0o100777, u'ug=rx,o=', 0o0550),
+ (0o040777, u'u=rx,g=r', 0o0547),
+ (0o100777, u'u=rx,g=r', 0o0547),
)
UMASK_DATA = (
diff --git a/test/units/module_utils/basic/test_argument_spec.py b/test/units/module_utils/basic/test_argument_spec.py
index 211d65a2..5dbaf50c 100644
--- a/test/units/module_utils/basic/test_argument_spec.py
+++ b/test/units/module_utils/basic/test_argument_spec.py
@@ -453,7 +453,7 @@ class TestComplexOptions:
'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}]
),
# Check for elements in sub-options
- ({"foobar": [{"foo": "good", "bam": "required_one_of", "bar1": [1, "good", "yes"], "bar2": ['1', 1], "bar3":['1.3', 1.3, 1]}]},
+ ({"foobar": [{"foo": "good", "bam": "required_one_of", "bar1": [1, "good", "yes"], "bar2": ['1', 1], "bar3": ['1.3', 1.3, 1]}]},
[{'foo': 'good', 'bam1': None, 'bam2': 'test', 'bam3': None, 'bam4': None, 'bar': None, 'baz': None, 'bam': 'required_one_of',
'bar1': ["1", "good", "yes"], 'bar2': [1, 1], 'bar3': [1.3, 1.3, 1.0], 'bar4': None}]
),
diff --git a/test/units/module_utils/basic/test_command_nonexisting.py b/test/units/module_utils/basic/test_command_nonexisting.py
index 6ed7f91b..0dd3bd98 100644
--- a/test/units/module_utils/basic/test_command_nonexisting.py
+++ b/test/units/module_utils/basic/test_command_nonexisting.py
@@ -1,14 +1,11 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import sys
-import pytest
import json
import sys
import pytest
import subprocess
-import ansible.module_utils.basic
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils import basic
diff --git a/test/units/module_utils/basic/test_filesystem.py b/test/units/module_utils/basic/test_filesystem.py
index f09cecf4..50e674c4 100644
--- a/test/units/module_utils/basic/test_filesystem.py
+++ b/test/units/module_utils/basic/test_filesystem.py
@@ -143,6 +143,8 @@ class TestOtherFilesystem(ModuleTestCase):
argument_spec=dict(),
)
+ am.selinux_enabled = lambda: False
+
file_args = {
'path': '/path/to/file',
'mode': None,
diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py
index 04211e2d..259ac6c4 100644
--- a/test/units/module_utils/basic/test_run_command.py
+++ b/test/units/module_utils/basic/test_run_command.py
@@ -12,7 +12,7 @@ from io import BytesIO
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.six import PY2
from ansible.module_utils.compat import selectors
@@ -109,7 +109,7 @@ def mock_subprocess(mocker):
super(MockSelector, self).close()
self._file_objs = []
- selectors.DefaultSelector = MockSelector
+ selectors.PollSelector = MockSelector
subprocess = mocker.patch('ansible.module_utils.basic.subprocess')
subprocess._output = {mocker.sentinel.stdout: SpecialBytesIO(b'', fh=mocker.sentinel.stdout),
@@ -194,7 +194,7 @@ class TestRunCommandPrompt:
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
def test_prompt_no_match(self, mocker, rc_am):
rc_am._os._cmd_out[mocker.sentinel.stdout] = BytesIO(b'hello')
- (rc, _, _) = rc_am.run_command('foo', prompt_regex='[pP]assword:')
+ (rc, stdout, stderr) = rc_am.run_command('foo', prompt_regex='[pP]assword:')
assert rc == 0
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
@@ -204,7 +204,7 @@ class TestRunCommandPrompt:
fh=mocker.sentinel.stdout),
mocker.sentinel.stderr:
SpecialBytesIO(b'', fh=mocker.sentinel.stderr)}
- (rc, _, _) = rc_am.run_command('foo', prompt_regex=r'[pP]assword:', data=None)
+ (rc, stdout, stderr) = rc_am.run_command('foo', prompt_regex=r'[pP]assword:', data=None)
assert rc == 257
@@ -212,7 +212,7 @@ class TestRunCommandRc:
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
def test_check_rc_false(self, rc_am):
rc_am._subprocess.Popen.return_value.returncode = 1
- (rc, _, _) = rc_am.run_command('/bin/false', check_rc=False)
+ (rc, stdout, stderr) = rc_am.run_command('/bin/false', check_rc=False)
assert rc == 1
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
diff --git a/test/units/module_utils/basic/test_safe_eval.py b/test/units/module_utils/basic/test_safe_eval.py
index e8538ca9..fdaab18a 100644
--- a/test/units/module_utils/basic/test_safe_eval.py
+++ b/test/units/module_utils/basic/test_safe_eval.py
@@ -67,4 +67,4 @@ def test_invalid_strings_with_exceptions(am, code, expected, exception):
if exception is None:
assert res[1] == exception
else:
- assert type(res[1]) == exception
+ assert isinstance(res[1], exception)
diff --git a/test/units/module_utils/basic/test_sanitize_keys.py b/test/units/module_utils/basic/test_sanitize_keys.py
index 180f8662..3edb216b 100644
--- a/test/units/module_utils/basic/test_sanitize_keys.py
+++ b/test/units/module_utils/basic/test_sanitize_keys.py
@@ -6,7 +6,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import pytest
from ansible.module_utils.basic import sanitize_keys
diff --git a/test/units/module_utils/basic/test_selinux.py b/test/units/module_utils/basic/test_selinux.py
index d8557685..bdb6b9de 100644
--- a/test/units/module_utils/basic/test_selinux.py
+++ b/test/units/module_utils/basic/test_selinux.py
@@ -43,16 +43,21 @@ class TestSELinuxMU:
with patch.object(basic, 'HAVE_SELINUX', False):
assert no_args_module().selinux_enabled() is False
- # test selinux present/not-enabled
- disabled_mod = no_args_module()
- with patch('ansible.module_utils.compat.selinux.is_selinux_enabled', return_value=0):
- assert disabled_mod.selinux_enabled() is False
+ # test selinux present/not-enabled
+ disabled_mod = no_args_module()
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.is_selinux_enabled.return_value = 0
+ assert disabled_mod.selinux_enabled() is False
+
# ensure value is cached (same answer after unpatching)
assert disabled_mod.selinux_enabled() is False
+
# and present / enabled
- enabled_mod = no_args_module()
- with patch('ansible.module_utils.compat.selinux.is_selinux_enabled', return_value=1):
- assert enabled_mod.selinux_enabled() is True
+ with patch.object(basic, 'HAVE_SELINUX', True):
+ enabled_mod = no_args_module()
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.is_selinux_enabled.return_value = 1
+ assert enabled_mod.selinux_enabled() is True
# ensure value is cached (same answer after unpatching)
assert enabled_mod.selinux_enabled() is True
@@ -60,12 +65,16 @@ class TestSELinuxMU:
# selinux unavailable, should return false
with patch.object(basic, 'HAVE_SELINUX', False):
assert no_args_module().selinux_mls_enabled() is False
- # selinux disabled, should return false
- with patch('ansible.module_utils.compat.selinux.is_selinux_mls_enabled', return_value=0):
- assert no_args_module(selinux_enabled=False).selinux_mls_enabled() is False
- # selinux enabled, should pass through the value of is_selinux_mls_enabled
- with patch('ansible.module_utils.compat.selinux.is_selinux_mls_enabled', return_value=1):
- assert no_args_module(selinux_enabled=True).selinux_mls_enabled() is True
+ # selinux disabled, should return false
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.is_selinux_mls_enabled.return_value = 0
+ assert no_args_module(selinux_enabled=False).selinux_mls_enabled() is False
+
+ with patch.object(basic, 'HAVE_SELINUX', True):
+ # selinux enabled, should pass through the value of is_selinux_mls_enabled
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.is_selinux_mls_enabled.return_value = 1
+ assert no_args_module(selinux_enabled=True).selinux_mls_enabled() is True
def test_selinux_initial_context(self):
# selinux missing/disabled/enabled sans MLS is 3-element None
@@ -80,16 +89,19 @@ class TestSELinuxMU:
assert no_args_module().selinux_default_context(path='/foo/bar') == [None, None, None]
am = no_args_module(selinux_enabled=True, selinux_mls_enabled=True)
- # matchpathcon success
- with patch('ansible.module_utils.compat.selinux.matchpathcon', return_value=[0, 'unconfined_u:object_r:default_t:s0']):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ # matchpathcon success
+ selinux.matchpathcon.return_value = [0, 'unconfined_u:object_r:default_t:s0']
assert am.selinux_default_context(path='/foo/bar') == ['unconfined_u', 'object_r', 'default_t', 's0']
- # matchpathcon fail (return initial context value)
- with patch('ansible.module_utils.compat.selinux.matchpathcon', return_value=[-1, '']):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ # matchpathcon fail (return initial context value)
+ selinux.matchpathcon.return_value = [-1, '']
assert am.selinux_default_context(path='/foo/bar') == [None, None, None, None]
- # matchpathcon OSError
- with patch('ansible.module_utils.compat.selinux.matchpathcon', side_effect=OSError):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ # matchpathcon OSError
+ selinux.matchpathcon.side_effect = OSError
assert am.selinux_default_context(path='/foo/bar') == [None, None, None, None]
def test_selinux_context(self):
@@ -99,19 +111,23 @@ class TestSELinuxMU:
am = no_args_module(selinux_enabled=True, selinux_mls_enabled=True)
# lgetfilecon_raw passthru
- with patch('ansible.module_utils.compat.selinux.lgetfilecon_raw', return_value=[0, 'unconfined_u:object_r:default_t:s0']):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lgetfilecon_raw.return_value = [0, 'unconfined_u:object_r:default_t:s0']
assert am.selinux_context(path='/foo/bar') == ['unconfined_u', 'object_r', 'default_t', 's0']
# lgetfilecon_raw returned a failure
- with patch('ansible.module_utils.compat.selinux.lgetfilecon_raw', return_value=[-1, '']):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lgetfilecon_raw.return_value = [-1, '']
assert am.selinux_context(path='/foo/bar') == [None, None, None, None]
# lgetfilecon_raw OSError (should bomb the module)
- with patch('ansible.module_utils.compat.selinux.lgetfilecon_raw', side_effect=OSError(errno.ENOENT, 'NotFound')):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lgetfilecon_raw.side_effect = OSError(errno.ENOENT, 'NotFound')
with pytest.raises(SystemExit):
am.selinux_context(path='/foo/bar')
- with patch('ansible.module_utils.compat.selinux.lgetfilecon_raw', side_effect=OSError()):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lgetfilecon_raw.side_effect = OSError()
with pytest.raises(SystemExit):
am.selinux_context(path='/foo/bar')
@@ -166,25 +182,29 @@ class TestSELinuxMU:
am.selinux_context = lambda path: ['bar_u', 'bar_r', None, None]
am.is_special_selinux_path = lambda path: (False, None)
- with patch('ansible.module_utils.compat.selinux.lsetfilecon', return_value=0) as m:
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lsetfilecon.return_value = 0
assert am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False) is True
- m.assert_called_with('/path/to/file', 'foo_u:foo_r:foo_t:s0')
- m.reset_mock()
+ selinux.lsetfilecon.assert_called_with('/path/to/file', 'foo_u:foo_r:foo_t:s0')
+ selinux.lsetfilecon.reset_mock()
am.check_mode = True
assert am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False) is True
- assert not m.called
+ assert not selinux.lsetfilecon.called
am.check_mode = False
- with patch('ansible.module_utils.compat.selinux.lsetfilecon', return_value=1):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lsetfilecon.return_value = 1
with pytest.raises(SystemExit):
am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], True)
- with patch('ansible.module_utils.compat.selinux.lsetfilecon', side_effect=OSError):
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lsetfilecon.side_effect = OSError
with pytest.raises(SystemExit):
am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], True)
am.is_special_selinux_path = lambda path: (True, ['sp_u', 'sp_r', 'sp_t', 's0'])
- with patch('ansible.module_utils.compat.selinux.lsetfilecon', return_value=0) as m:
+ with patch.object(basic, 'selinux', create=True) as selinux:
+ selinux.lsetfilecon.return_value = 0
assert am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False) is True
- m.assert_called_with('/path/to/file', 'sp_u:sp_r:sp_t:s0')
+ selinux.lsetfilecon.assert_called_with('/path/to/file', 'sp_u:sp_r:sp_t:s0')
diff --git a/test/units/module_utils/basic/test_set_cwd.py b/test/units/module_utils/basic/test_set_cwd.py
index 159236b7..c094c622 100644
--- a/test/units/module_utils/basic/test_set_cwd.py
+++ b/test/units/module_utils/basic/test_set_cwd.py
@@ -8,13 +8,10 @@ __metaclass__ = type
import json
import os
-import shutil
import tempfile
-import pytest
-
-from units.compat.mock import patch, MagicMock
-from ansible.module_utils._text import to_bytes
+from units.compat.mock import patch
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils import basic
diff --git a/test/units/module_utils/basic/test_tmpdir.py b/test/units/module_utils/basic/test_tmpdir.py
index 818cb9b1..ec12508b 100644
--- a/test/units/module_utils/basic/test_tmpdir.py
+++ b/test/units/module_utils/basic/test_tmpdir.py
@@ -14,7 +14,7 @@ import tempfile
import pytest
from units.compat.mock import patch, MagicMock
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils import basic
diff --git a/test/units/module_utils/common/arg_spec/test_aliases.py b/test/units/module_utils/common/arg_spec/test_aliases.py
index 7d30fb0f..7522c769 100644
--- a/test/units/module_utils/common/arg_spec/test_aliases.py
+++ b/test/units/module_utils/common/arg_spec/test_aliases.py
@@ -9,7 +9,6 @@ import pytest
from ansible.module_utils.errors import AnsibleValidationError, AnsibleValidationErrorMultiple
from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
-from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages
# id, argument spec, parameters, expected parameters, deprecation, warning
ALIAS_TEST_CASES = [
diff --git a/test/units/module_utils/common/parameters/test_handle_aliases.py b/test/units/module_utils/common/parameters/test_handle_aliases.py
index e20a8882..6a8c2b2c 100644
--- a/test/units/module_utils/common/parameters/test_handle_aliases.py
+++ b/test/units/module_utils/common/parameters/test_handle_aliases.py
@@ -9,7 +9,7 @@ __metaclass__ = type
import pytest
from ansible.module_utils.common.parameters import _handle_aliases
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
def test_handle_aliases_no_aliases():
diff --git a/test/units/module_utils/common/parameters/test_list_deprecations.py b/test/units/module_utils/common/parameters/test_list_deprecations.py
index 6f0bb71a..d667a2f0 100644
--- a/test/units/module_utils/common/parameters/test_list_deprecations.py
+++ b/test/units/module_utils/common/parameters/test_list_deprecations.py
@@ -5,21 +5,10 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import pytest
from ansible.module_utils.common.parameters import _list_deprecations
-@pytest.fixture
-def params():
- return {
- 'name': 'bob',
- 'dest': '/etc/hosts',
- 'state': 'present',
- 'value': 5,
- }
-
-
def test_list_deprecations():
argument_spec = {
'old': {'type': 'str', 'removed_in_version': '2.5'},
diff --git a/test/units/module_utils/common/test_collections.py b/test/units/module_utils/common/test_collections.py
index 95b2a402..8424502e 100644
--- a/test/units/module_utils/common/test_collections.py
+++ b/test/units/module_utils/common/test_collections.py
@@ -8,8 +8,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils.six import Iterator
-from ansible.module_utils.common._collections_compat import Sequence
+from ansible.module_utils.six.moves.collections_abc import Sequence
from ansible.module_utils.common.collections import ImmutableDict, is_iterable, is_sequence
@@ -25,16 +24,6 @@ class SeqStub:
Sequence.register(SeqStub)
-class IteratorStub(Iterator):
- def __next__(self):
- raise StopIteration
-
-
-class IterableStub:
- def __iter__(self):
- return IteratorStub()
-
-
class FakeAnsibleVaultEncryptedUnicode(Sequence):
__ENCRYPTED__ = True
@@ -42,10 +31,10 @@ class FakeAnsibleVaultEncryptedUnicode(Sequence):
self.data = data
def __getitem__(self, index):
- return self.data[index]
+ raise NotImplementedError() # pragma: nocover
def __len__(self):
- return len(self.data)
+ raise NotImplementedError() # pragma: nocover
TEST_STRINGS = u'he', u'Україна', u'Česká republika'
@@ -93,14 +82,14 @@ def test_sequence_string_types_without_strings(string_input):
@pytest.mark.parametrize(
'seq',
- ([], (), {}, set(), frozenset(), IterableStub()),
+ ([], (), {}, set(), frozenset()),
)
def test_iterable_positive(seq):
assert is_iterable(seq)
@pytest.mark.parametrize(
- 'seq', (IteratorStub(), object(), 5, 9.)
+ 'seq', (object(), 5, 9.)
)
def test_iterable_negative(seq):
assert not is_iterable(seq)
diff --git a/test/units/module_utils/common/text/converters/test_json_encode_fallback.py b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
index 022f38f4..808bf410 100644
--- a/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
+++ b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
@@ -20,12 +20,6 @@ class timezone(tzinfo):
def utcoffset(self, dt):
return self._offset
- def dst(self, dt):
- return timedelta(0)
-
- def tzname(self, dt):
- return None
-
@pytest.mark.parametrize(
'test_input,expected',
diff --git a/test/units/module_utils/common/validation/test_check_missing_parameters.py b/test/units/module_utils/common/validation/test_check_missing_parameters.py
index 6cbcb8bf..364f9439 100644
--- a/test/units/module_utils/common/validation/test_check_missing_parameters.py
+++ b/test/units/module_utils/common/validation/test_check_missing_parameters.py
@@ -8,16 +8,10 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
-from ansible.module_utils.common.validation import check_required_one_of
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_missing_parameters
-@pytest.fixture
-def arguments_terms():
- return {"path": ""}
-
-
def test_check_missing_parameters():
assert check_missing_parameters([], {}) == []
diff --git a/test/units/module_utils/common/validation/test_check_mutually_exclusive.py b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
index 7bf90760..acc67be8 100644
--- a/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
+++ b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_mutually_exclusive
diff --git a/test/units/module_utils/common/validation/test_check_required_arguments.py b/test/units/module_utils/common/validation/test_check_required_arguments.py
index 1dd54584..eb3d52e2 100644
--- a/test/units/module_utils/common/validation/test_check_required_arguments.py
+++ b/test/units/module_utils/common/validation/test_check_required_arguments.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_required_arguments
diff --git a/test/units/module_utils/common/validation/test_check_required_by.py b/test/units/module_utils/common/validation/test_check_required_by.py
index 62cccff3..fcba0c14 100644
--- a/test/units/module_utils/common/validation/test_check_required_by.py
+++ b/test/units/module_utils/common/validation/test_check_required_by.py
@@ -8,7 +8,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_required_by
diff --git a/test/units/module_utils/common/validation/test_check_required_if.py b/test/units/module_utils/common/validation/test_check_required_if.py
index 4189164a..4590b05c 100644
--- a/test/units/module_utils/common/validation/test_check_required_if.py
+++ b/test/units/module_utils/common/validation/test_check_required_if.py
@@ -8,7 +8,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_required_if
diff --git a/test/units/module_utils/common/validation/test_check_required_one_of.py b/test/units/module_utils/common/validation/test_check_required_one_of.py
index b0818891..efdba537 100644
--- a/test/units/module_utils/common/validation/test_check_required_one_of.py
+++ b/test/units/module_utils/common/validation/test_check_required_one_of.py
@@ -8,7 +8,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_required_one_of
diff --git a/test/units/module_utils/common/validation/test_check_required_together.py b/test/units/module_utils/common/validation/test_check_required_together.py
index 8a2daab1..cf4626ab 100644
--- a/test/units/module_utils/common/validation/test_check_required_together.py
+++ b/test/units/module_utils/common/validation/test_check_required_together.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_required_together
diff --git a/test/units/module_utils/common/validation/test_check_type_bits.py b/test/units/module_utils/common/validation/test_check_type_bits.py
index 7f6b11d3..aa91da94 100644
--- a/test/units/module_utils/common/validation/test_check_type_bits.py
+++ b/test/units/module_utils/common/validation/test_check_type_bits.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_bits
diff --git a/test/units/module_utils/common/validation/test_check_type_bool.py b/test/units/module_utils/common/validation/test_check_type_bool.py
index bd867dc9..00b785f6 100644
--- a/test/units/module_utils/common/validation/test_check_type_bool.py
+++ b/test/units/module_utils/common/validation/test_check_type_bool.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_bool
diff --git a/test/units/module_utils/common/validation/test_check_type_bytes.py b/test/units/module_utils/common/validation/test_check_type_bytes.py
index 6ff62dc2..c29e42f8 100644
--- a/test/units/module_utils/common/validation/test_check_type_bytes.py
+++ b/test/units/module_utils/common/validation/test_check_type_bytes.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_bytes
diff --git a/test/units/module_utils/common/validation/test_check_type_float.py b/test/units/module_utils/common/validation/test_check_type_float.py
index 57837fae..a0218875 100644
--- a/test/units/module_utils/common/validation/test_check_type_float.py
+++ b/test/units/module_utils/common/validation/test_check_type_float.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_float
diff --git a/test/units/module_utils/common/validation/test_check_type_int.py b/test/units/module_utils/common/validation/test_check_type_int.py
index 22cedf61..6f4dc6a2 100644
--- a/test/units/module_utils/common/validation/test_check_type_int.py
+++ b/test/units/module_utils/common/validation/test_check_type_int.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_int
diff --git a/test/units/module_utils/common/validation/test_check_type_jsonarg.py b/test/units/module_utils/common/validation/test_check_type_jsonarg.py
index e78e54bb..d43bb035 100644
--- a/test/units/module_utils/common/validation/test_check_type_jsonarg.py
+++ b/test/units/module_utils/common/validation/test_check_type_jsonarg.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_jsonarg
diff --git a/test/units/module_utils/common/validation/test_check_type_str.py b/test/units/module_utils/common/validation/test_check_type_str.py
index f10dad28..71af2a0b 100644
--- a/test/units/module_utils/common/validation/test_check_type_str.py
+++ b/test/units/module_utils/common/validation/test_check_type_str.py
@@ -7,7 +7,7 @@ __metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.validation import check_type_str
diff --git a/test/units/module_utils/conftest.py b/test/units/module_utils/conftest.py
index 8bc13c4d..8e82bf2a 100644
--- a/test/units/module_utils/conftest.py
+++ b/test/units/module_utils/conftest.py
@@ -12,8 +12,8 @@ import pytest
import ansible.module_utils.basic
from ansible.module_utils.six import PY3, string_types
-from ansible.module_utils._text import to_bytes
-from ansible.module_utils.common._collections_compat import MutableMapping
+from ansible.module_utils.common.text.converters import to_bytes
+from ansible.module_utils.six.moves.collections_abc import MutableMapping
@pytest.fixture
diff --git a/test/units/module_utils/facts/base.py b/test/units/module_utils/facts/base.py
index 33d3087b..3cada8f1 100644
--- a/test/units/module_utils/facts/base.py
+++ b/test/units/module_utils/facts/base.py
@@ -48,6 +48,9 @@ class BaseFactsTest(unittest.TestCase):
@patch('platform.system', return_value='Linux')
@patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value='systemd')
def test_collect(self, mock_gfc, mock_ps):
+ self._test_collect()
+
+ def _test_collect(self):
module = self._mock_module()
fact_collector = self.collector_class()
facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
@@ -62,4 +65,3 @@ class BaseFactsTest(unittest.TestCase):
facts_dict = fact_collector.collect_with_namespace(module=module,
collected_facts=self.collected_facts)
self.assertIsInstance(facts_dict, dict)
- return facts_dict
diff --git a/test/units/module_utils/facts/hardware/linux_data.py b/test/units/module_utils/facts/hardware/linux_data.py
index 3879188d..f92f14eb 100644
--- a/test/units/module_utils/facts/hardware/linux_data.py
+++ b/test/units/module_utils/facts/hardware/linux_data.py
@@ -18,6 +18,12 @@ __metaclass__ = type
import os
+
+def read_lines(path):
+ with open(path) as file:
+ return file.readlines()
+
+
LSBLK_OUTPUT = b"""
/dev/sda
/dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0
@@ -368,7 +374,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'armv61',
'nproc_out': 1,
'sched_getaffinity': set([0]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo')),
'expected_result': {
'processor': ['0', 'ARMv6-compatible processor rev 7 (v6l)'],
'processor_cores': 1,
@@ -381,7 +387,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'armv71',
'nproc_out': 4,
'sched_getaffinity': set([0, 1, 2, 3]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'ARMv7 Processor rev 4 (v7l)',
@@ -399,7 +405,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'aarch64',
'nproc_out': 4,
'sched_getaffinity': set([0, 1, 2, 3]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/aarch64-4cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/aarch64-4cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'AArch64 Processor rev 4 (aarch64)',
@@ -417,7 +423,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'x86_64',
'nproc_out': 4,
'sched_getaffinity': set([0, 1, 2, 3]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-4cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-4cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'AuthenticAMD', 'Dual-Core AMD Opteron(tm) Processor 2216',
@@ -435,7 +441,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'x86_64',
'nproc_out': 4,
'sched_getaffinity': set([0, 1, 2, 3]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-8cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-8cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
@@ -457,7 +463,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'arm64',
'nproc_out': 4,
'sched_getaffinity': set([0, 1, 2, 3]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/arm64-4cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/arm64-4cpu-cpuinfo')),
'expected_result': {
'processor': ['0', '1', '2', '3'],
'processor_cores': 1,
@@ -470,7 +476,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'armv71',
'nproc_out': 8,
'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'ARMv7 Processor rev 3 (v7l)',
@@ -492,7 +498,7 @@ CPU_INFO_TEST_SCENARIOS = [
'architecture': 'x86_64',
'nproc_out': 2,
'sched_getaffinity': set([0, 1]),
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-2cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/x86_64-2cpu-cpuinfo')),
'expected_result': {
'processor': [
'0', 'GenuineIntel', 'Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz',
@@ -505,7 +511,7 @@ CPU_INFO_TEST_SCENARIOS = [
'processor_vcpus': 2},
},
{
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo')),
'architecture': 'ppc64',
'nproc_out': 8,
'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7]),
@@ -528,7 +534,7 @@ CPU_INFO_TEST_SCENARIOS = [
},
},
{
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo')),
'architecture': 'ppc64le',
'nproc_out': 24,
'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]),
@@ -567,7 +573,41 @@ CPU_INFO_TEST_SCENARIOS = [
},
},
{
- 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu')).readlines(),
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/s390x-z13-2cpu-cpuinfo')),
+ 'architecture': 's390x',
+ 'nproc_out': 2,
+ 'sched_getaffinity': set([0, 1]),
+ 'expected_result': {
+ 'processor': [
+ 'IBM/S390',
+ ],
+ 'processor_cores': 2,
+ 'processor_count': 1,
+ 'processor_nproc': 2,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 2
+ },
+ },
+ {
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/s390x-z14-64cpu-cpuinfo')),
+ 'architecture': 's390x',
+ 'nproc_out': 64,
+ 'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]),
+ 'expected_result': {
+ 'processor': [
+ 'IBM/S390',
+ ],
+ 'processor_cores': 32,
+ 'processor_count': 1,
+ 'processor_nproc': 64,
+ 'processor_threads_per_core': 2,
+ 'processor_vcpus': 64
+ },
+ },
+ {
+ 'cpuinfo': read_lines(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu')),
'architecture': 'sparc64',
'nproc_out': 24,
'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]),
diff --git a/test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py b/test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py
index aea8694e..41674344 100644
--- a/test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py
+++ b/test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py
@@ -45,7 +45,7 @@ def test_get_cpu_info_missing_arch(mocker):
module = mocker.Mock()
inst = linux.LinuxHardware(module)
- # ARM and Power will report incorrect processor count if architecture is not available
+ # ARM, Power, and zSystems will report incorrect processor count if architecture is not available
mocker.patch('os.path.exists', return_value=False)
mocker.patch('os.access', return_value=True)
for test in CPU_INFO_TEST_SCENARIOS:
@@ -56,7 +56,7 @@ def test_get_cpu_info_missing_arch(mocker):
test_result = inst.get_cpu_facts()
- if test['architecture'].startswith(('armv', 'aarch', 'ppc')):
+ if test['architecture'].startswith(('armv', 'aarch', 'ppc', 's390')):
assert test['expected_result'] != test_result
else:
assert test['expected_result'] == test_result
diff --git a/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py
index c0957566..6667ada7 100644
--- a/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py
+++ b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py
@@ -21,7 +21,8 @@ def test_input():
def test_parse_distribution_file_clear_linux(mock_module, test_input):
- test_input['data'] = open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files/ClearLinux')).read()
+ with open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files/ClearLinux')) as file:
+ test_input['data'] = file.read()
result = (
True,
@@ -43,7 +44,8 @@ def test_parse_distribution_file_clear_linux_no_match(mock_module, distro_file,
Test against data from Linux Mint and CoreOS to ensure we do not get a reported
match from parse_distribution_file_ClearLinux()
"""
- test_input['data'] = open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files', distro_file)).read()
+ with open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files', distro_file)) as file:
+ test_input['data'] = file.read()
result = (False, {})
diff --git a/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py
index 53fd4ea1..efb937e0 100644
--- a/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py
+++ b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py
@@ -19,9 +19,12 @@ from ansible.module_utils.facts.system.distribution import DistributionFiles
)
)
def test_parse_distribution_file_slackware(mock_module, distro_file, expected_version):
+ with open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files', distro_file)) as file:
+ data = file.read()
+
test_input = {
'name': 'Slackware',
- 'data': open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files', distro_file)).read(),
+ 'data': data,
'path': '/etc/os-release',
'collected_facts': None,
}
diff --git a/test/units/module_utils/facts/test_collectors.py b/test/units/module_utils/facts/test_collectors.py
index c4806025..984b5859 100644
--- a/test/units/module_utils/facts/test_collectors.py
+++ b/test/units/module_utils/facts/test_collectors.py
@@ -93,7 +93,7 @@ class TestApparmorFacts(BaseFactsTest):
collector_class = ApparmorFactCollector
def test_collect(self):
- facts_dict = super(TestApparmorFacts, self).test_collect()
+ facts_dict = super(TestApparmorFacts, self)._test_collect()
self.assertIn('status', facts_dict['apparmor'])
@@ -191,7 +191,7 @@ class TestEnvFacts(BaseFactsTest):
collector_class = EnvFactCollector
def test_collect(self):
- facts_dict = super(TestEnvFacts, self).test_collect()
+ facts_dict = super(TestEnvFacts, self)._test_collect()
self.assertIn('HOME', facts_dict['env'])
@@ -355,7 +355,6 @@ class TestSelinuxFacts(BaseFactsTest):
facts_dict = fact_collector.collect(module=module)
self.assertIsInstance(facts_dict, dict)
self.assertEqual(facts_dict['selinux']['status'], 'Missing selinux Python library')
- return facts_dict
class TestServiceMgrFacts(BaseFactsTest):
diff --git a/test/units/module_utils/facts/test_date_time.py b/test/units/module_utils/facts/test_date_time.py
index 6abc36a7..6cc05f97 100644
--- a/test/units/module_utils/facts/test_date_time.py
+++ b/test/units/module_utils/facts/test_date_time.py
@@ -10,28 +10,27 @@ import datetime
import string
import time
+from ansible.module_utils.compat.datetime import UTC
from ansible.module_utils.facts.system import date_time
EPOCH_TS = 1594449296.123456
DT = datetime.datetime(2020, 7, 11, 12, 34, 56, 124356)
-DT_UTC = datetime.datetime(2020, 7, 11, 2, 34, 56, 124356)
+UTC_DT = datetime.datetime(2020, 7, 11, 2, 34, 56, 124356)
@pytest.fixture
def fake_now(monkeypatch):
"""
- Patch `datetime.datetime.fromtimestamp()`, `datetime.datetime.utcfromtimestamp()`,
+ Patch `datetime.datetime.fromtimestamp()`,
and `time.time()` to return deterministic values.
"""
class FakeNow:
@classmethod
- def fromtimestamp(cls, timestamp):
- return DT
-
- @classmethod
- def utcfromtimestamp(cls, timestamp):
- return DT_UTC
+ def fromtimestamp(cls, timestamp, tz=None):
+ if tz == UTC:
+ return UTC_DT.replace(tzinfo=tz)
+ return DT.replace(tzinfo=tz)
def _time():
return EPOCH_TS
diff --git a/test/units/module_utils/facts/test_sysctl.py b/test/units/module_utils/facts/test_sysctl.py
index c369b610..0f1632bf 100644
--- a/test/units/module_utils/facts/test_sysctl.py
+++ b/test/units/module_utils/facts/test_sysctl.py
@@ -20,13 +20,9 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import os
-
-import pytest
-
# for testing
from units.compat import unittest
-from units.compat.mock import patch, MagicMock, mock_open, Mock
+from units.compat.mock import MagicMock
from ansible.module_utils.facts.sysctl import get_sysctl
diff --git a/test/units/module_utils/facts/test_timeout.py b/test/units/module_utils/facts/test_timeout.py
index 2adbc4a6..6ba7c397 100644
--- a/test/units/module_utils/facts/test_timeout.py
+++ b/test/units/module_utils/facts/test_timeout.py
@@ -139,7 +139,7 @@ def function_other_timeout():
@timeout.timeout(1)
def function_raises():
- 1 / 0
+ return 1 / 0
@timeout.timeout(1)
diff --git a/test/units/module_utils/urls/test_Request.py b/test/units/module_utils/urls/test_Request.py
index d2c4ea38..a8bc3a0b 100644
--- a/test/units/module_utils/urls/test_Request.py
+++ b/test/units/module_utils/urls/test_Request.py
@@ -33,6 +33,7 @@ def install_opener_mock(mocker):
def test_Request_fallback(urlopen_mock, install_opener_mock, mocker):
here = os.path.dirname(__file__)
pem = os.path.join(here, 'fixtures/client.pem')
+ client_key = os.path.join(here, 'fixtures/client.key')
cookies = cookiejar.CookieJar()
request = Request(
@@ -46,8 +47,8 @@ def test_Request_fallback(urlopen_mock, install_opener_mock, mocker):
http_agent='ansible-tests',
force_basic_auth=True,
follow_redirects='all',
- client_cert='/tmp/client.pem',
- client_key='/tmp/client.key',
+ client_cert=pem,
+ client_key=client_key,
cookies=cookies,
unix_socket='/foo/bar/baz.sock',
ca_path=pem,
@@ -68,8 +69,8 @@ def test_Request_fallback(urlopen_mock, install_opener_mock, mocker):
call(None, 'ansible-tests'), # http_agent
call(None, True), # force_basic_auth
call(None, 'all'), # follow_redirects
- call(None, '/tmp/client.pem'), # client_cert
- call(None, '/tmp/client.key'), # client_key
+ call(None, pem), # client_cert
+ call(None, client_key), # client_key
call(None, cookies), # cookies
call(None, '/foo/bar/baz.sock'), # unix_socket
call(None, pem), # ca_path
@@ -358,10 +359,7 @@ def test_Request_open_client_cert(urlopen_mock, install_opener_mock):
assert ssl_handler.client_cert == client_cert
assert ssl_handler.client_key == client_key
- https_connection = ssl_handler._build_https_connection('ansible.com')
-
- assert https_connection.key_file == client_key
- assert https_connection.cert_file == client_cert
+ ssl_handler._build_https_connection('ansible.com')
def test_Request_open_cookies(urlopen_mock, install_opener_mock):
diff --git a/test/units/module_utils/urls/test_fetch_file.py b/test/units/module_utils/urls/test_fetch_file.py
index ed112270..ecb6b9f1 100644
--- a/test/units/module_utils/urls/test_fetch_file.py
+++ b/test/units/module_utils/urls/test_fetch_file.py
@@ -10,7 +10,6 @@ import os
from ansible.module_utils.urls import fetch_file
import pytest
-from units.compat.mock import MagicMock
class FakeTemporaryFile:
diff --git a/test/units/module_utils/urls/test_prepare_multipart.py b/test/units/module_utils/urls/test_prepare_multipart.py
index 226d9edd..ee320477 100644
--- a/test/units/module_utils/urls/test_prepare_multipart.py
+++ b/test/units/module_utils/urls/test_prepare_multipart.py
@@ -7,8 +7,6 @@ __metaclass__ = type
import os
-from io import StringIO
-
from email.message import Message
import pytest
diff --git a/test/units/module_utils/urls/test_urls.py b/test/units/module_utils/urls/test_urls.py
index 69c1b824..f0e5e9ea 100644
--- a/test/units/module_utils/urls/test_urls.py
+++ b/test/units/module_utils/urls/test_urls.py
@@ -6,7 +6,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils import urls
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
import pytest
diff --git a/test/units/modules/conftest.py b/test/units/modules/conftest.py
index a7d1e047..c60c586d 100644
--- a/test/units/modules/conftest.py
+++ b/test/units/modules/conftest.py
@@ -8,24 +8,15 @@ import json
import pytest
-from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_bytes
-from ansible.module_utils.common._collections_compat import MutableMapping
+from ansible.module_utils.common.text.converters import to_bytes
@pytest.fixture
def patch_ansible_module(request, mocker):
- if isinstance(request.param, string_types):
- args = request.param
- elif isinstance(request.param, MutableMapping):
- if 'ANSIBLE_MODULE_ARGS' not in request.param:
- request.param = {'ANSIBLE_MODULE_ARGS': request.param}
- if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']:
- request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp'
- if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']:
- request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False
- args = json.dumps(request.param)
- else:
- raise Exception('Malformed data to the patch_ansible_module pytest fixture')
+ request.param = {'ANSIBLE_MODULE_ARGS': request.param}
+ request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp'
+ request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False
+
+ args = json.dumps(request.param)
mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args))
diff --git a/test/units/modules/test_apt.py b/test/units/modules/test_apt.py
index 20e056ff..a5aa4a90 100644
--- a/test/units/modules/test_apt.py
+++ b/test/units/modules/test_apt.py
@@ -2,20 +2,13 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import collections
-import sys
from units.compat.mock import Mock
from units.compat import unittest
-try:
- from ansible.modules.apt import (
- expand_pkgspec_from_fnmatches,
- )
-except Exception:
- # Need some more module_utils work (porting urls.py) before we can test
- # modules. So don't error out in this case.
- if sys.version_info[0] >= 3:
- pass
+from ansible.modules.apt import (
+ expand_pkgspec_from_fnmatches,
+)
class AptExpandPkgspecTestCase(unittest.TestCase):
@@ -29,25 +22,25 @@ class AptExpandPkgspecTestCase(unittest.TestCase):
]
def test_trivial(self):
- foo = ["apt"]
+ pkg = ["apt"]
self.assertEqual(
- expand_pkgspec_from_fnmatches(None, foo, self.fake_cache), foo)
+ expand_pkgspec_from_fnmatches(None, pkg, self.fake_cache), pkg)
def test_version_wildcard(self):
- foo = ["apt=1.0*"]
+ pkg = ["apt=1.0*"]
self.assertEqual(
- expand_pkgspec_from_fnmatches(None, foo, self.fake_cache), foo)
+ expand_pkgspec_from_fnmatches(None, pkg, self.fake_cache), pkg)
def test_pkgname_wildcard_version_wildcard(self):
- foo = ["apt*=1.0*"]
+ pkg = ["apt*=1.0*"]
m_mock = Mock()
self.assertEqual(
- expand_pkgspec_from_fnmatches(m_mock, foo, self.fake_cache),
+ expand_pkgspec_from_fnmatches(m_mock, pkg, self.fake_cache),
['apt', 'apt-utils'])
def test_pkgname_expands(self):
- foo = ["apt*"]
+ pkg = ["apt*"]
m_mock = Mock()
self.assertEqual(
- expand_pkgspec_from_fnmatches(m_mock, foo, self.fake_cache),
+ expand_pkgspec_from_fnmatches(m_mock, pkg, self.fake_cache),
["apt", "apt-utils"])
diff --git a/test/units/modules/test_async_wrapper.py b/test/units/modules/test_async_wrapper.py
index 37b1fda3..dbaf6834 100644
--- a/test/units/modules/test_async_wrapper.py
+++ b/test/units/modules/test_async_wrapper.py
@@ -7,26 +7,21 @@ __metaclass__ = type
import os
import json
import shutil
+import sys
import tempfile
-import pytest
-
-from units.compat.mock import patch, MagicMock
from ansible.modules import async_wrapper
-from pprint import pprint
-
class TestAsyncWrapper:
def test_run_module(self, monkeypatch):
def mock_get_interpreter(module_path):
- return ['/usr/bin/python']
+ return [sys.executable]
module_result = {'rc': 0}
module_lines = [
- '#!/usr/bin/python',
'import sys',
'sys.stderr.write("stderr stuff")',
"print('%s')" % json.dumps(module_result)
diff --git a/test/units/modules/test_copy.py b/test/units/modules/test_copy.py
index 20c309b6..beeef6d7 100644
--- a/test/units/modules/test_copy.py
+++ b/test/units/modules/test_copy.py
@@ -128,16 +128,19 @@ def test_split_pre_existing_dir_working_dir_exists(directory, expected, mocker):
#
# Info helpful for making new test cases:
#
-# base_mode = {'dir no perms': 0o040000,
-# 'file no perms': 0o100000,
-# 'dir all perms': 0o400000 | 0o777,
-# 'file all perms': 0o100000, | 0o777}
+# base_mode = {
+# 'dir no perms': 0o040000,
+# 'file no perms': 0o100000,
+# 'dir all perms': 0o040000 | 0o777,
+# 'file all perms': 0o100000 | 0o777}
#
-# perm_bits = {'x': 0b001,
+# perm_bits = {
+# 'x': 0b001,
# 'w': 0b010,
# 'r': 0b100}
#
-# role_shift = {'u': 6,
+# role_shift = {
+# 'u': 6,
# 'g': 3,
# 'o': 0}
@@ -172,6 +175,10 @@ DATA = ( # Going from no permissions to setting all for user, group, and/or oth
# chmod a-X statfile <== removes execute from statfile
(0o100777, u'a-X', 0o0666),
+ # Verify X uses computed not original mode
+ (0o100777, u'a=,u=rX', 0o0400),
+ (0o040777, u'a=,u=rX', 0o0500),
+
# Multiple permissions
(0o040000, u'u=rw-x+X,g=r-x+X,o=r-x+X', 0o0755),
(0o100000, u'u=rw-x+X,g=r-x+X,o=r-x+X', 0o0644),
@@ -185,6 +192,10 @@ UMASK_DATA = (
INVALID_DATA = (
(0o040000, u'a=foo', "bad symbolic permission for mode: a=foo"),
(0o040000, u'f=rwx', "bad symbolic permission for mode: f=rwx"),
+ (0o100777, u'of=r', "bad symbolic permission for mode: of=r"),
+
+ (0o100777, u'ao=r', "bad symbolic permission for mode: ao=r"),
+ (0o100777, u'oa=r', "bad symbolic permission for mode: oa=r"),
)
diff --git a/test/units/modules/test_hostname.py b/test/units/modules/test_hostname.py
index 9050fd04..1aa4a57a 100644
--- a/test/units/modules/test_hostname.py
+++ b/test/units/modules/test_hostname.py
@@ -6,7 +6,6 @@ import shutil
import tempfile
from units.compat.mock import patch, MagicMock, mock_open
-from ansible.module_utils import basic
from ansible.module_utils.common._utils import get_all_subclasses
from ansible.modules import hostname
from units.modules.utils import ModuleTestCase, set_module_args
@@ -44,12 +43,9 @@ class TestHostname(ModuleTestCase):
classname = "%sStrategy" % prefix
cls = getattr(hostname, classname, None)
- if cls is None:
- self.assertFalse(
- cls is None, "%s is None, should be a subclass" % classname
- )
- else:
- self.assertTrue(issubclass(cls, hostname.BaseStrategy))
+ assert cls is not None
+
+ self.assertTrue(issubclass(cls, hostname.BaseStrategy))
class TestRedhatStrategy(ModuleTestCase):
diff --git a/test/units/modules/test_iptables.py b/test/units/modules/test_iptables.py
index 265e770a..2459cf77 100644
--- a/test/units/modules/test_iptables.py
+++ b/test/units/modules/test_iptables.py
@@ -181,7 +181,7 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 2)
+ self.assertEqual(run_command.call_count, 1)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t',
@@ -208,7 +208,6 @@ class TestIptables(ModuleTestCase):
commands_results = [
(1, '', ''), # check_rule_present
- (0, '', ''), # check_chain_present
(0, '', ''),
]
@@ -218,7 +217,7 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 3)
+ self.assertEqual(run_command.call_count, 2)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t',
@@ -232,7 +231,7 @@ class TestIptables(ModuleTestCase):
'-j',
'ACCEPT'
])
- self.assertEqual(run_command.call_args_list[2][0][0], [
+ self.assertEqual(run_command.call_args_list[1][0][0], [
'/sbin/iptables',
'-t',
'filter',
@@ -272,7 +271,7 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 2)
+ self.assertEqual(run_command.call_count, 1)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t',
@@ -321,7 +320,7 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 3)
+ self.assertEqual(run_command.call_count, 2)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t',
@@ -343,7 +342,7 @@ class TestIptables(ModuleTestCase):
'--to-ports',
'8600'
])
- self.assertEqual(run_command.call_args_list[2][0][0], [
+ self.assertEqual(run_command.call_args_list[1][0][0], [
'/sbin/iptables',
'-t',
'nat',
@@ -1019,10 +1018,8 @@ class TestIptables(ModuleTestCase):
})
commands_results = [
- (1, '', ''), # check_rule_present
(1, '', ''), # check_chain_present
(0, '', ''), # create_chain
- (0, '', ''), # append_rule
]
with patch.object(basic.AnsibleModule, 'run_command') as run_command:
@@ -1031,32 +1028,20 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 4)
+ self.assertEqual(run_command.call_count, 2)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t', 'filter',
- '-C', 'FOOBAR',
- ])
-
- self.assertEqual(run_command.call_args_list[1][0][0], [
- '/sbin/iptables',
- '-t', 'filter',
'-L', 'FOOBAR',
])
- self.assertEqual(run_command.call_args_list[2][0][0], [
+ self.assertEqual(run_command.call_args_list[1][0][0], [
'/sbin/iptables',
'-t', 'filter',
'-N', 'FOOBAR',
])
- self.assertEqual(run_command.call_args_list[3][0][0], [
- '/sbin/iptables',
- '-t', 'filter',
- '-A', 'FOOBAR',
- ])
-
commands_results = [
(0, '', ''), # check_rule_present
]
@@ -1078,7 +1063,6 @@ class TestIptables(ModuleTestCase):
commands_results = [
(1, '', ''), # check_rule_present
- (1, '', ''), # check_chain_present
]
with patch.object(basic.AnsibleModule, 'run_command') as run_command:
@@ -1087,17 +1071,11 @@ class TestIptables(ModuleTestCase):
iptables.main()
self.assertTrue(result.exception.args[0]['changed'])
- self.assertEqual(run_command.call_count, 2)
+ self.assertEqual(run_command.call_count, 1)
self.assertEqual(run_command.call_args_list[0][0][0], [
'/sbin/iptables',
'-t', 'filter',
- '-C', 'FOOBAR',
- ])
-
- self.assertEqual(run_command.call_args_list[1][0][0], [
- '/sbin/iptables',
- '-t', 'filter',
'-L', 'FOOBAR',
])
diff --git a/test/units/modules/test_known_hosts.py b/test/units/modules/test_known_hosts.py
index 123dd75f..667f3e50 100644
--- a/test/units/modules/test_known_hosts.py
+++ b/test/units/modules/test_known_hosts.py
@@ -6,7 +6,7 @@ import tempfile
from ansible.module_utils import basic
from units.compat import unittest
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.basic import AnsibleModule
from ansible.modules.known_hosts import compute_diff, sanity_check
diff --git a/test/units/modules/test_unarchive.py b/test/units/modules/test_unarchive.py
index 3e7a58c9..935231ba 100644
--- a/test/units/modules/test_unarchive.py
+++ b/test/units/modules/test_unarchive.py
@@ -8,20 +8,6 @@ import pytest
from ansible.modules.unarchive import ZipArchive, TgzArchive
-class AnsibleModuleExit(Exception):
- def __init__(self, *args, **kwargs):
- self.args = args
- self.kwargs = kwargs
-
-
-class ExitJson(AnsibleModuleExit):
- pass
-
-
-class FailJson(AnsibleModuleExit):
- pass
-
-
@pytest.fixture
def fake_ansible_module():
return FakeAnsibleModule()
@@ -32,12 +18,6 @@ class FakeAnsibleModule:
self.params = {}
self.tmpdir = None
- def exit_json(self, *args, **kwargs):
- raise ExitJson(*args, **kwargs)
-
- def fail_json(self, *args, **kwargs):
- raise FailJson(*args, **kwargs)
-
class TestCaseZipArchive:
@pytest.mark.parametrize(
diff --git a/test/units/modules/utils.py b/test/units/modules/utils.py
index 6d169e36..b56229e8 100644
--- a/test/units/modules/utils.py
+++ b/test/units/modules/utils.py
@@ -6,14 +6,12 @@ import json
from units.compat import unittest
from units.compat.mock import patch
from ansible.module_utils import basic
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
def set_module_args(args):
- if '_ansible_remote_tmp' not in args:
- args['_ansible_remote_tmp'] = '/tmp'
- if '_ansible_keep_remote_files' not in args:
- args['_ansible_keep_remote_files'] = False
+ args['_ansible_remote_tmp'] = '/tmp'
+ args['_ansible_keep_remote_files'] = False
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
@@ -28,8 +26,6 @@ class AnsibleFailJson(Exception):
def exit_json(*args, **kwargs):
- if 'changed' not in kwargs:
- kwargs['changed'] = False
raise AnsibleExitJson(kwargs)
diff --git a/test/units/parsing/test_ajson.py b/test/units/parsing/test_ajson.py
index 1b9a76b4..bb7bf1a7 100644
--- a/test/units/parsing/test_ajson.py
+++ b/test/units/parsing/test_ajson.py
@@ -109,7 +109,11 @@ class TestAnsibleJSONEncoder:
def __len__(self):
return len(self.__dict__)
- return M(request.param)
+ mapping = M(request.param)
+
+ assert isinstance(len(mapping), int) # ensure coverage of __len__
+
+ return mapping
@pytest.fixture
def ansible_json_encoder(self):
diff --git a/test/units/parsing/test_dataloader.py b/test/units/parsing/test_dataloader.py
index 9ec49a8d..a7f8b1d2 100644
--- a/test/units/parsing/test_dataloader.py
+++ b/test/units/parsing/test_dataloader.py
@@ -25,8 +25,7 @@ from units.compat import unittest
from unittest.mock import patch, mock_open
from ansible.errors import AnsibleParserError, yaml_strings, AnsibleFileNotFound
from ansible.parsing.vault import AnsibleVaultError
-from ansible.module_utils._text import to_text
-from ansible.module_utils.six import PY3
+from ansible.module_utils.common.text.converters import to_text
from units.mock.vault_helper import TextVaultSecret
from ansible.parsing.dataloader import DataLoader
@@ -92,11 +91,11 @@ class TestDataLoader(unittest.TestCase):
- { role: 'testrole' }
testrole/tasks/main.yml:
- - include: "include1.yml"
+ - include_tasks: "include1.yml"
static: no
testrole/tasks/include1.yml:
- - include: include2.yml
+ - include_tasks: include2.yml
static: no
testrole/tasks/include2.yml:
@@ -229,11 +228,7 @@ class TestDataLoaderWithVault(unittest.TestCase):
3135306561356164310a343937653834643433343734653137383339323330626437313562306630
3035
"""
- if PY3:
- builtins_name = 'builtins'
- else:
- builtins_name = '__builtin__'
- with patch(builtins_name + '.open', mock_open(read_data=vaulted_data.encode('utf-8'))):
+ with patch('builtins.open', mock_open(read_data=vaulted_data.encode('utf-8'))):
output = self._loader.load_from_file('dummy_vault.txt')
self.assertEqual(output, dict(foo='bar'))
diff --git a/test/units/parsing/test_mod_args.py b/test/units/parsing/test_mod_args.py
index 5d3f5d25..aeb74ad5 100644
--- a/test/units/parsing/test_mod_args.py
+++ b/test/units/parsing/test_mod_args.py
@@ -6,10 +6,10 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import pytest
-import re
from ansible.errors import AnsibleParserError
from ansible.parsing.mod_args import ModuleArgsParser
+from ansible.plugins.loader import init_plugin_loader
from ansible.utils.sentinel import Sentinel
@@ -119,19 +119,19 @@ class TestModArgsDwim:
assert err.value.args[0] == msg
def test_multiple_actions_ping_shell(self):
+ init_plugin_loader()
args_dict = {'ping': 'data=hi', 'shell': 'echo hi'}
m = ModuleArgsParser(args_dict)
with pytest.raises(AnsibleParserError) as err:
m.parse()
- assert err.value.args[0].startswith("conflicting action statements: ")
- actions = set(re.search(r'(\w+), (\w+)', err.value.args[0]).groups())
- assert actions == set(['ping', 'shell'])
+ assert err.value.args[0] == f'conflicting action statements: {", ".join(args_dict)}'
def test_bogus_action(self):
+ init_plugin_loader()
args_dict = {'bogusaction': {}}
m = ModuleArgsParser(args_dict)
with pytest.raises(AnsibleParserError) as err:
m.parse()
- assert err.value.args[0].startswith("couldn't resolve module/action 'bogusaction'")
+ assert err.value.args[0].startswith(f"couldn't resolve module/action '{next(iter(args_dict))}'")
diff --git a/test/units/parsing/test_splitter.py b/test/units/parsing/test_splitter.py
index a37de0f9..893f0473 100644
--- a/test/units/parsing/test_splitter.py
+++ b/test/units/parsing/test_splitter.py
@@ -21,10 +21,17 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from ansible.parsing.splitter import split_args, parse_kv
+from ansible.errors import AnsibleParserError
import pytest
SPLIT_DATA = (
+ (None,
+ [],
+ {}),
+ (u'',
+ [],
+ {}),
(u'a',
[u'a'],
{u'_raw_params': u'a'}),
@@ -46,6 +53,18 @@ SPLIT_DATA = (
(u'a="echo \\"hello world\\"" b=bar',
[u'a="echo \\"hello world\\""', u'b=bar'],
{u'a': u'echo "hello world"', u'b': u'bar'}),
+ (u'a="nest\'ed"',
+ [u'a="nest\'ed"'],
+ {u'a': u'nest\'ed'}),
+ (u' ',
+ [u' '],
+ {u'_raw_params': u' '}),
+ (u'\\ ',
+ [u' '],
+ {u'_raw_params': u' '}),
+ (u'a\\=escaped',
+ [u'a\\=escaped'],
+ {u'_raw_params': u'a=escaped'}),
(u'a="multi\nline"',
[u'a="multi\nline"'],
{u'a': u'multi\nline'}),
@@ -61,12 +80,27 @@ SPLIT_DATA = (
(u'a="multiline\nmessage1\\\n" b="multiline\nmessage2\\\n"',
[u'a="multiline\nmessage1\\\n"', u'b="multiline\nmessage2\\\n"'],
{u'a': 'multiline\nmessage1\\\n', u'b': u'multiline\nmessage2\\\n'}),
+ (u'line \\\ncontinuation',
+ [u'line', u'continuation'],
+ {u'_raw_params': u'line continuation'}),
+ (u'not jinja}}',
+ [u'not', u'jinja}}'],
+ {u'_raw_params': u'not jinja}}'}),
+ (u'a={{multiline\njinja}}',
+ [u'a={{multiline\njinja}}'],
+ {u'a': u'{{multiline\njinja}}'}),
(u'a={{jinja}}',
[u'a={{jinja}}'],
{u'a': u'{{jinja}}'}),
(u'a={{ jinja }}',
[u'a={{ jinja }}'],
{u'a': u'{{ jinja }}'}),
+ (u'a={% jinja %}',
+ [u'a={% jinja %}'],
+ {u'a': u'{% jinja %}'}),
+ (u'a={# jinja #}',
+ [u'a={# jinja #}'],
+ {u'a': u'{# jinja #}'}),
(u'a="{{jinja}}"',
[u'a="{{jinja}}"'],
{u'a': u'{{jinja}}'}),
@@ -94,17 +128,50 @@ SPLIT_DATA = (
(u'One\n Two\n Three\n',
[u'One\n ', u'Two\n ', u'Three\n'],
{u'_raw_params': u'One\n Two\n Three\n'}),
+ (u'\nOne\n Two\n Three\n',
+ [u'\n', u'One\n ', u'Two\n ', u'Three\n'],
+ {u'_raw_params': u'\nOne\n Two\n Three\n'}),
)
-SPLIT_ARGS = ((test[0], test[1]) for test in SPLIT_DATA)
-PARSE_KV = ((test[0], test[2]) for test in SPLIT_DATA)
+PARSE_KV_CHECK_RAW = (
+ (u'raw=yes', {u'_raw_params': u'raw=yes'}),
+ (u'creates=something', {u'creates': u'something'}),
+)
+
+PARSER_ERROR = (
+ '"',
+ "'",
+ '{{',
+ '{%',
+ '{#',
+)
+SPLIT_ARGS = tuple((test[0], test[1]) for test in SPLIT_DATA)
+PARSE_KV = tuple((test[0], test[2]) for test in SPLIT_DATA)
-@pytest.mark.parametrize("args, expected", SPLIT_ARGS)
+
+@pytest.mark.parametrize("args, expected", SPLIT_ARGS, ids=[str(arg[0]) for arg in SPLIT_ARGS])
def test_split_args(args, expected):
assert split_args(args) == expected
-@pytest.mark.parametrize("args, expected", PARSE_KV)
+@pytest.mark.parametrize("args, expected", PARSE_KV, ids=[str(arg[0]) for arg in PARSE_KV])
def test_parse_kv(args, expected):
assert parse_kv(args) == expected
+
+
+@pytest.mark.parametrize("args, expected", PARSE_KV_CHECK_RAW, ids=[str(arg[0]) for arg in PARSE_KV_CHECK_RAW])
+def test_parse_kv_check_raw(args, expected):
+ assert parse_kv(args, check_raw=True) == expected
+
+
+@pytest.mark.parametrize("args", PARSER_ERROR)
+def test_split_args_error(args):
+ with pytest.raises(AnsibleParserError):
+ split_args(args)
+
+
+@pytest.mark.parametrize("args", PARSER_ERROR)
+def test_parse_kv_error(args):
+ with pytest.raises(AnsibleParserError):
+ parse_kv(args)
diff --git a/test/units/parsing/vault/test_vault.py b/test/units/parsing/vault/test_vault.py
index 7afd3560..f94171a2 100644
--- a/test/units/parsing/vault/test_vault.py
+++ b/test/units/parsing/vault/test_vault.py
@@ -21,7 +21,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import binascii
import io
import os
import tempfile
@@ -34,7 +33,7 @@ from unittest.mock import patch, MagicMock
from ansible import errors
from ansible.module_utils import six
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.parsing import vault
from units.mock.loader import DictDataLoader
@@ -606,9 +605,6 @@ class TestVaultLib(unittest.TestCase):
('test_id', text_secret)]
self.v = vault.VaultLib(self.vault_secrets)
- def _vault_secrets(self, vault_id, secret):
- return [(vault_id, secret)]
-
def _vault_secrets_from_password(self, vault_id, password):
return [(vault_id, TextVaultSecret(password))]
@@ -779,43 +775,6 @@ class TestVaultLib(unittest.TestCase):
b_plaintext = self.v.decrypt(b_vaulttext)
self.assertEqual(b_plaintext, b_orig_plaintext, msg="decryption failed")
- # FIXME This test isn't working quite yet.
- @pytest.mark.skip(reason='This test is not ready yet')
- def test_encrypt_decrypt_aes256_bad_hmac(self):
-
- self.v.cipher_name = 'AES256'
- # plaintext = "Setec Astronomy"
- enc_data = '''$ANSIBLE_VAULT;1.1;AES256
-33363965326261303234626463623963633531343539616138316433353830356566396130353436
-3562643163366231316662386565383735653432386435610a306664636137376132643732393835
-63383038383730306639353234326630666539346233376330303938323639306661313032396437
-6233623062366136310a633866373936313238333730653739323461656662303864663666653563
-3138'''
- b_data = to_bytes(enc_data, errors='strict', encoding='utf-8')
- b_data = self.v._split_header(b_data)
- foo = binascii.unhexlify(b_data)
- lines = foo.splitlines()
- # line 0 is salt, line 1 is hmac, line 2+ is ciphertext
- b_salt = lines[0]
- b_hmac = lines[1]
- b_ciphertext_data = b'\n'.join(lines[2:])
-
- b_ciphertext = binascii.unhexlify(b_ciphertext_data)
- # b_orig_ciphertext = b_ciphertext[:]
-
- # now muck with the text
- # b_munged_ciphertext = b_ciphertext[:10] + b'\x00' + b_ciphertext[11:]
- # b_munged_ciphertext = b_ciphertext
- # assert b_orig_ciphertext != b_munged_ciphertext
-
- b_ciphertext_data = binascii.hexlify(b_ciphertext)
- b_payload = b'\n'.join([b_salt, b_hmac, b_ciphertext_data])
- # reformat
- b_invalid_ciphertext = self.v._format_output(b_payload)
-
- # assert we throw an error
- self.v.decrypt(b_invalid_ciphertext)
-
def test_decrypt_and_get_vault_id(self):
b_expected_plaintext = to_bytes('foo bar\n')
vaulttext = '''$ANSIBLE_VAULT;1.2;AES256;ansible_devel
diff --git a/test/units/parsing/vault/test_vault_editor.py b/test/units/parsing/vault/test_vault_editor.py
index 77509f08..28561c6a 100644
--- a/test/units/parsing/vault/test_vault_editor.py
+++ b/test/units/parsing/vault/test_vault_editor.py
@@ -33,8 +33,7 @@ from ansible import errors
from ansible.parsing import vault
from ansible.parsing.vault import VaultLib, VaultEditor, match_encrypt_secret
-from ansible.module_utils.six import PY3
-from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common.text.converters import to_bytes, to_text
from units.mock.vault_helper import TextVaultSecret
@@ -88,12 +87,10 @@ class TestVaultEditor(unittest.TestCase):
suffix = '_ansible_unit_test_%s_' % (self.__class__.__name__)
return tempfile.mkdtemp(suffix=suffix)
- def _create_file(self, test_dir, name, content=None, symlink=False):
+ def _create_file(self, test_dir, name, content, symlink=False):
file_path = os.path.join(test_dir, name)
- opened_file = open(file_path, 'wb')
- if content:
+ with open(file_path, 'wb') as opened_file:
opened_file.write(content)
- opened_file.close()
return file_path
def _vault_editor(self, vault_secrets=None):
@@ -118,11 +115,8 @@ class TestVaultEditor(unittest.TestCase):
def test_stdin_binary(self):
stdin_data = '\0'
- if PY3:
- fake_stream = StringIO(stdin_data)
- fake_stream.buffer = BytesIO(to_bytes(stdin_data))
- else:
- fake_stream = BytesIO(to_bytes(stdin_data))
+ fake_stream = StringIO(stdin_data)
+ fake_stream.buffer = BytesIO(to_bytes(stdin_data))
with patch('sys.stdin', fake_stream):
ve = self._vault_editor()
@@ -167,17 +161,15 @@ class TestVaultEditor(unittest.TestCase):
self.assertNotEqual(src_file_contents, b_ciphertext,
'b_ciphertext should be encrypted and not equal to src_contents')
- def _faux_editor(self, editor_args, new_src_contents=None):
+ def _faux_editor(self, editor_args, new_src_contents):
if editor_args[0] == 'shred':
return
tmp_path = editor_args[-1]
# simulate the tmp file being editted
- tmp_file = open(tmp_path, 'wb')
- if new_src_contents:
+ with open(tmp_path, 'wb') as tmp_file:
tmp_file.write(new_src_contents)
- tmp_file.close()
def _faux_command(self, tmp_path):
pass
@@ -198,13 +190,13 @@ class TestVaultEditor(unittest.TestCase):
ve._edit_file_helper(src_file_path, self.vault_secret, existing_data=src_file_contents)
- new_target_file = open(src_file_path, 'rb')
- new_target_file_contents = new_target_file.read()
- self.assertEqual(src_file_contents, new_target_file_contents)
+ with open(src_file_path, 'rb') as new_target_file:
+ new_target_file_contents = new_target_file.read()
+ self.assertEqual(src_file_contents, new_target_file_contents)
def _assert_file_is_encrypted(self, vault_editor, src_file_path, src_contents):
- new_src_file = open(src_file_path, 'rb')
- new_src_file_contents = new_src_file.read()
+ with open(src_file_path, 'rb') as new_src_file:
+ new_src_file_contents = new_src_file.read()
# TODO: assert that it is encrypted
self.assertTrue(vault.is_encrypted(new_src_file_contents))
@@ -339,8 +331,8 @@ class TestVaultEditor(unittest.TestCase):
ve.encrypt_file(src_file_path, self.vault_secret)
ve.edit_file(src_file_path)
- new_src_file = open(src_file_path, 'rb')
- new_src_file_contents = new_src_file.read()
+ with open(src_file_path, 'rb') as new_src_file:
+ new_src_file_contents = new_src_file.read()
self.assertTrue(b'$ANSIBLE_VAULT;1.1;AES256' in new_src_file_contents)
@@ -367,8 +359,8 @@ class TestVaultEditor(unittest.TestCase):
vault_id='vault_secrets')
ve.edit_file(src_file_path)
- new_src_file = open(src_file_path, 'rb')
- new_src_file_contents = new_src_file.read()
+ with open(src_file_path, 'rb') as new_src_file:
+ new_src_file_contents = new_src_file.read()
self.assertTrue(b'$ANSIBLE_VAULT;1.2;AES256;vault_secrets' in new_src_file_contents)
@@ -399,8 +391,8 @@ class TestVaultEditor(unittest.TestCase):
ve.edit_file(src_file_link_path)
- new_src_file = open(src_file_path, 'rb')
- new_src_file_contents = new_src_file.read()
+ with open(src_file_path, 'rb') as new_src_file:
+ new_src_file_contents = new_src_file.read()
src_file_plaintext = ve.vault.decrypt(new_src_file_contents)
@@ -418,13 +410,6 @@ class TestVaultEditor(unittest.TestCase):
src_file_path = self._create_file(self._test_dir, 'src_file', content=src_contents)
- new_src_contents = to_bytes("The info is different now.")
-
- def faux_editor(editor_args):
- self._faux_editor(editor_args, new_src_contents)
-
- mock_sp_call.side_effect = faux_editor
-
ve = self._vault_editor()
self.assertRaisesRegex(errors.AnsibleError,
'input is not vault encrypted data',
@@ -478,20 +463,14 @@ class TestVaultEditor(unittest.TestCase):
ve = self._vault_editor(self._secrets("ansible"))
# make sure the password functions for the cipher
- error_hit = False
- try:
- ve.decrypt_file(v11_file.name)
- except errors.AnsibleError:
- error_hit = True
+ ve.decrypt_file(v11_file.name)
# verify decrypted content
- f = open(v11_file.name, "rb")
- fdata = to_text(f.read())
- f.close()
+ with open(v11_file.name, "rb") as f:
+ fdata = to_text(f.read())
os.unlink(v11_file.name)
- assert error_hit is False, "error decrypting 1.1 file"
assert fdata.strip() == "foo", "incorrect decryption of 1.1 file: %s" % fdata.strip()
def test_real_path_dash(self):
@@ -501,21 +480,9 @@ class TestVaultEditor(unittest.TestCase):
res = ve._real_path(filename)
self.assertEqual(res, '-')
- def test_real_path_dev_null(self):
+ def test_real_path_not_dash(self):
filename = '/dev/null'
ve = self._vault_editor()
res = ve._real_path(filename)
- self.assertEqual(res, '/dev/null')
-
- def test_real_path_symlink(self):
- self._test_dir = os.path.realpath(self._create_test_dir())
- file_path = self._create_file(self._test_dir, 'test_file', content=b'this is a test file')
- file_link_path = os.path.join(self._test_dir, 'a_link_to_test_file')
-
- os.symlink(file_path, file_link_path)
-
- ve = self._vault_editor()
-
- res = ve._real_path(file_link_path)
- self.assertEqual(res, file_path)
+ self.assertNotEqual(res, '-')
diff --git a/test/units/parsing/yaml/test_dumper.py b/test/units/parsing/yaml/test_dumper.py
index cbf5b456..8af1eeed 100644
--- a/test/units/parsing/yaml/test_dumper.py
+++ b/test/units/parsing/yaml/test_dumper.py
@@ -19,7 +19,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import io
-import yaml
from jinja2.exceptions import UndefinedError
@@ -27,7 +26,6 @@ from units.compat import unittest
from ansible.parsing import vault
from ansible.parsing.yaml import dumper, objects
from ansible.parsing.yaml.loader import AnsibleLoader
-from ansible.module_utils.six import PY2
from ansible.template import AnsibleUndefined
from units.mock.yaml_helper import YamlTestUtils
@@ -76,20 +74,6 @@ class TestAnsibleDumper(unittest.TestCase, YamlTestUtils):
data_from_yaml = loader.get_single_data()
result = b_text
- if PY2:
- # https://pyyaml.org/wiki/PyYAMLDocumentation#string-conversion-python-2-only
- # pyyaml on Python 2 can return either unicode or bytes when given byte strings.
- # We normalize that to always return unicode on Python2 as that's right most of the
- # time. However, this means byte strings can round trip through yaml on Python3 but
- # not on Python2. To make this code work the same on Python2 and Python3 (we want
- # the Python3 behaviour) we need to change the methods in Ansible to:
- # (1) Let byte strings pass through yaml without being converted on Python2
- # (2) Convert byte strings to text strings before being given to pyyaml (Without this,
- # strings would end up as byte strings most of the time which would mostly be wrong)
- # In practice, we mostly read bytes in from files and then pass that to pyyaml, for which
- # the present behavior is correct.
- # This is a workaround for the current behavior.
- result = u'tr\xe9ma'
self.assertEqual(result, data_from_yaml)
@@ -105,10 +89,7 @@ class TestAnsibleDumper(unittest.TestCase, YamlTestUtils):
self.assertEqual(u_text, data_from_yaml)
def test_vars_with_sources(self):
- try:
- self._dump_string(VarsWithSources(), dumper=self.dumper)
- except yaml.representer.RepresenterError:
- self.fail("Dump VarsWithSources raised RepresenterError unexpectedly!")
+ self._dump_string(VarsWithSources(), dumper=self.dumper)
def test_undefined(self):
undefined_object = AnsibleUndefined()
diff --git a/test/units/parsing/yaml/test_objects.py b/test/units/parsing/yaml/test_objects.py
index f64b708f..f899915d 100644
--- a/test/units/parsing/yaml/test_objects.py
+++ b/test/units/parsing/yaml/test_objects.py
@@ -24,7 +24,7 @@ from units.compat import unittest
from ansible.errors import AnsibleError
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.parsing import vault
from ansible.parsing.yaml.loader import AnsibleLoader
@@ -105,11 +105,6 @@ class TestAnsibleVaultEncryptedUnicode(unittest.TestCase, YamlTestUtils):
id_secret = vault.match_encrypt_secret(self.good_vault_secrets)
return objects.AnsibleVaultEncryptedUnicode.from_plaintext(seq, vault=self.vault, secret=id_secret[1])
- def _from_ciphertext(self, ciphertext):
- avu = objects.AnsibleVaultEncryptedUnicode(ciphertext)
- avu.vault = self.vault
- return avu
-
def test_empty_init(self):
self.assertRaises(TypeError, objects.AnsibleVaultEncryptedUnicode)
diff --git a/test/units/playbook/role/test_include_role.py b/test/units/playbook/role/test_include_role.py
index 5e7625ba..aa97da15 100644
--- a/test/units/playbook/role/test_include_role.py
+++ b/test/units/playbook/role/test_include_role.py
@@ -108,8 +108,6 @@ class TestIncludeRole(unittest.TestCase):
# skip meta: role_complete
continue
role = task._role
- if not role:
- continue
yield (role.get_name(),
self.var_manager.get_vars(play=play, task=task))
@@ -201,7 +199,7 @@ class TestIncludeRole(unittest.TestCase):
self.assertEqual(task_vars.get('l3_variable'), 'l3-main')
self.assertEqual(task_vars.get('test_variable'), 'l3-main')
else:
- self.fail()
+ self.fail() # pragma: nocover
self.assertFalse(expected_roles)
@patch('ansible.playbook.role.definition.unfrackpath',
@@ -247,5 +245,5 @@ class TestIncludeRole(unittest.TestCase):
self.assertEqual(task_vars.get('l3_variable'), 'l3-alt')
self.assertEqual(task_vars.get('test_variable'), 'l3-alt')
else:
- self.fail()
+ self.fail() # pragma: nocover
self.assertFalse(expected_roles)
diff --git a/test/units/playbook/role/test_role.py b/test/units/playbook/role/test_role.py
index 5d47631f..9d6b0edc 100644
--- a/test/units/playbook/role/test_role.py
+++ b/test/units/playbook/role/test_role.py
@@ -21,10 +21,12 @@ __metaclass__ = type
from collections.abc import Container
+import pytest
+
from units.compat import unittest
from unittest.mock import patch, MagicMock
-from ansible.errors import AnsibleError, AnsibleParserError
+from ansible.errors import AnsibleParserError
from ansible.playbook.block import Block
from units.mock.loader import DictDataLoader
@@ -42,12 +44,9 @@ class TestHashParams(unittest.TestCase):
self._assert_set(res)
self._assert_hashable(res)
- def _assert_hashable(self, res):
- a_dict = {}
- try:
- a_dict[res] = res
- except TypeError as e:
- self.fail('%s is not hashable: %s' % (res, e))
+ @staticmethod
+ def _assert_hashable(res):
+ hash(res)
def _assert_set(self, res):
self.assertIsInstance(res, frozenset)
@@ -87,36 +86,28 @@ class TestHashParams(unittest.TestCase):
def test_generator(self):
def my_generator():
- for i in ['a', 1, None, {}]:
- yield i
+ yield
params = my_generator()
res = hash_params(params)
self._assert_hashable(res)
+ assert list(params)
def test_container_but_not_iterable(self):
# This is a Container that is not iterable, which is unlikely but...
class MyContainer(Container):
- def __init__(self, some_thing):
- self.data = []
- self.data.append(some_thing)
+ def __init__(self, _some_thing):
+ pass
def __contains__(self, item):
- return item in self.data
-
- def __hash__(self):
- return hash(self.data)
-
- def __len__(self):
- return len(self.data)
+ """Implementation omitted, since it will never be called."""
- def __call__(self):
- return False
+ params = MyContainer('foo bar')
- foo = MyContainer('foo bar')
- params = foo
+ with pytest.raises(TypeError) as ex:
+ hash_params(params)
- self.assertRaises(TypeError, hash_params, params)
+ assert ex.value.args == ("'MyContainer' object is not iterable",)
def test_param_dict_dupe_values(self):
params1 = {'foo': False}
@@ -151,18 +142,18 @@ class TestHashParams(unittest.TestCase):
self.assertNotEqual(hash(res1), hash(res2))
self.assertNotEqual(res1, res2)
- foo = {}
- foo[res1] = 'params1'
- foo[res2] = 'params2'
+ params_dict = {}
+ params_dict[res1] = 'params1'
+ params_dict[res2] = 'params2'
- self.assertEqual(len(foo), 2)
+ self.assertEqual(len(params_dict), 2)
- del foo[res2]
- self.assertEqual(len(foo), 1)
+ del params_dict[res2]
+ self.assertEqual(len(params_dict), 1)
- for key in foo:
- self.assertTrue(key in foo)
- self.assertIn(key, foo)
+ for key in params_dict:
+ self.assertTrue(key in params_dict)
+ self.assertIn(key, params_dict)
class TestRole(unittest.TestCase):
@@ -177,7 +168,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_tasks', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -199,7 +190,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_tasks', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play, from_files=dict(tasks='custom_main'))
@@ -217,7 +208,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_handlers', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -238,7 +229,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -259,7 +250,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -280,7 +271,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -303,7 +294,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -323,7 +314,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_vars', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -370,7 +361,7 @@ class TestRole(unittest.TestCase):
mock_play = MagicMock()
mock_play.collections = None
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load('foo_metadata', play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
@@ -415,7 +406,7 @@ class TestRole(unittest.TestCase):
})
mock_play = MagicMock()
- mock_play.ROLE_CACHE = {}
+ mock_play.role_cache = {}
i = RoleInclude.load(dict(role='foo_complex'), play=mock_play, loader=fake_loader)
r = Role.load(i, play=mock_play)
diff --git a/test/units/playbook/test_base.py b/test/units/playbook/test_base.py
index d5810e73..bedd96a8 100644
--- a/test/units/playbook/test_base.py
+++ b/test/units/playbook/test_base.py
@@ -21,13 +21,12 @@ __metaclass__ = type
from units.compat import unittest
-from ansible.errors import AnsibleParserError
+from ansible.errors import AnsibleParserError, AnsibleAssertionError
from ansible.module_utils.six import string_types
from ansible.playbook.attribute import FieldAttribute, NonInheritableFieldAttribute
from ansible.template import Templar
from ansible.playbook import base
-from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText
-from ansible.utils.sentinel import Sentinel
+from ansible.utils.unsafe_proxy import AnsibleUnsafeText
from units.mock.loader import DictDataLoader
@@ -331,12 +330,6 @@ class ExampleSubClass(base.Base):
def __init__(self):
super(ExampleSubClass, self).__init__()
- def get_dep_chain(self):
- if self._parent:
- return self._parent.get_dep_chain()
- else:
- return None
-
class BaseSubClass(base.Base):
name = FieldAttribute(isa='string', default='', always_post_validate=True)
@@ -588,10 +581,11 @@ class TestBaseSubClass(TestBase):
bsc.post_validate, templar)
def test_attr_unknown(self):
- a_list = ['some string']
- ds = {'test_attr_unknown_isa': a_list}
- bsc = self._base_validate(ds)
- self.assertEqual(bsc.test_attr_unknown_isa, a_list)
+ self.assertRaises(
+ AnsibleAssertionError,
+ self._base_validate,
+ {'test_attr_unknown_isa': True}
+ )
def test_attr_method(self):
ds = {'test_attr_method': 'value from the ds'}
diff --git a/test/units/playbook/test_collectionsearch.py b/test/units/playbook/test_collectionsearch.py
index be40d85e..d16541b7 100644
--- a/test/units/playbook/test_collectionsearch.py
+++ b/test/units/playbook/test_collectionsearch.py
@@ -22,7 +22,6 @@ from ansible.errors import AnsibleParserError
from ansible.playbook.play import Play
from ansible.playbook.task import Task
from ansible.playbook.block import Block
-from ansible.playbook.collectionsearch import CollectionSearch
import pytest
diff --git a/test/units/playbook/test_helpers.py b/test/units/playbook/test_helpers.py
index a89730ca..23385c00 100644
--- a/test/units/playbook/test_helpers.py
+++ b/test/units/playbook/test_helpers.py
@@ -52,10 +52,6 @@ class MixinForMocks(object):
self.mock_inventory = MagicMock(name='MockInventory')
self.mock_inventory._hosts_cache = dict()
- def _get_host(host_name):
- return None
-
- self.mock_inventory.get_host.side_effect = _get_host
# TODO: can we use a real VariableManager?
self.mock_variable_manager = MagicMock(name='MockVariableManager')
self.mock_variable_manager.get_vars.return_value = dict()
@@ -69,11 +65,11 @@ class MixinForMocks(object):
self._test_data_path = os.path.dirname(__file__)
self.fake_include_loader = DictDataLoader({"/dev/null/includes/test_include.yml": """
- - include: other_test_include.yml
+ - include_tasks: other_test_include.yml
- shell: echo 'hello world'
""",
"/dev/null/includes/static_test_include.yml": """
- - include: other_test_include.yml
+ - include_tasks: other_test_include.yml
- shell: echo 'hello static world'
""",
"/dev/null/includes/other_test_include.yml": """
@@ -86,10 +82,6 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks):
def setUp(self):
self._setup()
- def _assert_is_task_list(self, results):
- for result in results:
- self.assertIsInstance(result, Task)
-
def _assert_is_task_list_or_blocks(self, results):
self.assertIsInstance(results, list)
for result in results:
@@ -168,57 +160,57 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks):
ds, play=self.mock_play, use_handlers=True,
variable_manager=self.mock_variable_manager, loader=self.fake_loader)
- def test_one_bogus_include(self):
- ds = [{'include': 'somefile.yml'}]
+ def test_one_bogus_include_tasks(self):
+ ds = [{'include_tasks': 'somefile.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_loader)
self.assertIsInstance(res, list)
- self.assertEqual(len(res), 0)
+ self.assertEqual(len(res), 1)
+ self.assertIsInstance(res[0], TaskInclude)
- def test_one_bogus_include_use_handlers(self):
- ds = [{'include': 'somefile.yml'}]
+ def test_one_bogus_include_tasks_use_handlers(self):
+ ds = [{'include_tasks': 'somefile.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play, use_handlers=True,
variable_manager=self.mock_variable_manager, loader=self.fake_loader)
self.assertIsInstance(res, list)
- self.assertEqual(len(res), 0)
+ self.assertEqual(len(res), 1)
+ self.assertIsInstance(res[0], TaskInclude)
- def test_one_bogus_include_static(self):
+ def test_one_bogus_import_tasks(self):
ds = [{'import_tasks': 'somefile.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_loader)
self.assertIsInstance(res, list)
self.assertEqual(len(res), 0)
- def test_one_include(self):
- ds = [{'include': '/dev/null/includes/other_test_include.yml'}]
+ def test_one_include_tasks(self):
+ ds = [{'include_tasks': '/dev/null/includes/other_test_include.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
self.assertEqual(len(res), 1)
self._assert_is_task_list_or_blocks(res)
- def test_one_parent_include(self):
- ds = [{'include': '/dev/null/includes/test_include.yml'}]
+ def test_one_parent_include_tasks(self):
+ ds = [{'include_tasks': '/dev/null/includes/test_include.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
self._assert_is_task_list_or_blocks(res)
- self.assertIsInstance(res[0], Block)
- self.assertIsInstance(res[0]._parent, TaskInclude)
+ self.assertIsInstance(res[0], TaskInclude)
+ self.assertIsNone(res[0]._parent)
- # TODO/FIXME: do this non deprecated way
- def test_one_include_tags(self):
- ds = [{'include': '/dev/null/includes/other_test_include.yml',
+ def test_one_include_tasks_tags(self):
+ ds = [{'include_tasks': '/dev/null/includes/other_test_include.yml',
'tags': ['test_one_include_tags_tag1', 'and_another_tagB']
}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
self._assert_is_task_list_or_blocks(res)
- self.assertIsInstance(res[0], Block)
+ self.assertIsInstance(res[0], TaskInclude)
self.assertIn('test_one_include_tags_tag1', res[0].tags)
self.assertIn('and_another_tagB', res[0].tags)
- # TODO/FIXME: do this non deprecated way
- def test_one_parent_include_tags(self):
- ds = [{'include': '/dev/null/includes/test_include.yml',
+ def test_one_parent_include_tasks_tags(self):
+ ds = [{'include_tasks': '/dev/null/includes/test_include.yml',
# 'vars': {'tags': ['test_one_parent_include_tags_tag1', 'and_another_tag2']}
'tags': ['test_one_parent_include_tags_tag1', 'and_another_tag2']
}
@@ -226,20 +218,20 @@ class TestLoadListOfTasks(unittest.TestCase, MixinForMocks):
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
self._assert_is_task_list_or_blocks(res)
- self.assertIsInstance(res[0], Block)
+ self.assertIsInstance(res[0], TaskInclude)
self.assertIn('test_one_parent_include_tags_tag1', res[0].tags)
self.assertIn('and_another_tag2', res[0].tags)
- def test_one_include_use_handlers(self):
- ds = [{'include': '/dev/null/includes/other_test_include.yml'}]
+ def test_one_include_tasks_use_handlers(self):
+ ds = [{'include_tasks': '/dev/null/includes/other_test_include.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
use_handlers=True,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
self._assert_is_task_list_or_blocks(res)
self.assertIsInstance(res[0], Handler)
- def test_one_parent_include_use_handlers(self):
- ds = [{'include': '/dev/null/includes/test_include.yml'}]
+ def test_one_parent_include_tasks_use_handlers(self):
+ ds = [{'include_tasks': '/dev/null/includes/test_include.yml'}]
res = helpers.load_list_of_tasks(ds, play=self.mock_play,
use_handlers=True,
variable_manager=self.mock_variable_manager, loader=self.fake_include_loader)
diff --git a/test/units/playbook/test_included_file.py b/test/units/playbook/test_included_file.py
index 7341dffa..c7a66b06 100644
--- a/test/units/playbook/test_included_file.py
+++ b/test/units/playbook/test_included_file.py
@@ -105,7 +105,7 @@ def test_included_file_instantiation():
assert inc_file._task is None
-def test_process_include_results(mock_iterator, mock_variable_manager):
+def test_process_include_tasks_results(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
@@ -113,7 +113,7 @@ def test_process_include_results(mock_iterator, mock_variable_manager):
parent_task = Task.load(parent_task_ds)
parent_task._play = None
- task_ds = {'include': 'include_test.yml'}
+ task_ds = {'include_tasks': 'include_test.yml'}
loaded_task = TaskInclude.load(task_ds, task_include=parent_task)
return_data = {'include': 'include_test.yml'}
@@ -133,7 +133,7 @@ def test_process_include_results(mock_iterator, mock_variable_manager):
assert res[0]._vars == {}
-def test_process_include_diff_files(mock_iterator, mock_variable_manager):
+def test_process_include_tasks_diff_files(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
@@ -141,11 +141,11 @@ def test_process_include_diff_files(mock_iterator, mock_variable_manager):
parent_task = Task.load(parent_task_ds)
parent_task._play = None
- task_ds = {'include': 'include_test.yml'}
+ task_ds = {'include_tasks': 'include_test.yml'}
loaded_task = TaskInclude.load(task_ds, task_include=parent_task)
loaded_task._play = None
- child_task_ds = {'include': 'other_include_test.yml'}
+ child_task_ds = {'include_tasks': 'other_include_test.yml'}
loaded_child_task = TaskInclude.load(child_task_ds, task_include=loaded_task)
loaded_child_task._play = None
@@ -175,7 +175,7 @@ def test_process_include_diff_files(mock_iterator, mock_variable_manager):
assert res[1]._vars == {}
-def test_process_include_simulate_free(mock_iterator, mock_variable_manager):
+def test_process_include_tasks_simulate_free(mock_iterator, mock_variable_manager):
hostname = "testhost1"
hostname2 = "testhost2"
@@ -186,7 +186,7 @@ def test_process_include_simulate_free(mock_iterator, mock_variable_manager):
parent_task1._play = None
parent_task2._play = None
- task_ds = {'include': 'include_test.yml'}
+ task_ds = {'include_tasks': 'include_test.yml'}
loaded_task1 = TaskInclude.load(task_ds, task_include=parent_task1)
loaded_task2 = TaskInclude.load(task_ds, task_include=parent_task2)
diff --git a/test/units/playbook/test_play_context.py b/test/units/playbook/test_play_context.py
index 7c24de51..7461b45f 100644
--- a/test/units/playbook/test_play_context.py
+++ b/test/units/playbook/test_play_context.py
@@ -12,10 +12,8 @@ import pytest
from ansible import constants as C
from ansible import context
from ansible.cli.arguments import option_helpers as opt_help
-from ansible.errors import AnsibleError
from ansible.playbook.play_context import PlayContext
from ansible.playbook.play import Play
-from ansible.plugins.loader import become_loader
from ansible.utils import context_objects as co
diff --git a/test/units/playbook/test_taggable.py b/test/units/playbook/test_taggable.py
index 3881e17d..c6ce35d3 100644
--- a/test/units/playbook/test_taggable.py
+++ b/test/units/playbook/test_taggable.py
@@ -29,6 +29,7 @@ class TaggableTestObj(Taggable):
def __init__(self):
self._loader = DictDataLoader({})
self.tags = []
+ self._parent = None
class TestTaggable(unittest.TestCase):
diff --git a/test/units/playbook/test_task.py b/test/units/playbook/test_task.py
index 070d7aa7..e28d2ecd 100644
--- a/test/units/playbook/test_task.py
+++ b/test/units/playbook/test_task.py
@@ -22,6 +22,7 @@ __metaclass__ = type
from units.compat import unittest
from unittest.mock import patch
from ansible.playbook.task import Task
+from ansible.plugins.loader import init_plugin_loader
from ansible.parsing.yaml import objects
from ansible import errors
@@ -74,6 +75,7 @@ class TestTask(unittest.TestCase):
@patch.object(errors.AnsibleError, '_get_error_lines_from_file')
def test_load_task_kv_form_error_36848(self, mock_get_err_lines):
+ init_plugin_loader()
ds = objects.AnsibleMapping(kv_bad_args_ds)
ds.ansible_pos = ('test_task_faux_playbook.yml', 1, 1)
mock_get_err_lines.return_value = (kv_bad_args_str, '')
diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py
index f2bbe194..33d09c42 100644
--- a/test/units/plugins/action/test_action.py
+++ b/test/units/plugins/action/test_action.py
@@ -22,6 +22,7 @@ __metaclass__ = type
import os
import re
+from importlib import import_module
from ansible import constants as C
from units.compat import unittest
@@ -30,9 +31,10 @@ from unittest.mock import patch, MagicMock, mock_open
from ansible.errors import AnsibleError, AnsibleAuthenticationFailure
from ansible.module_utils.six import text_type
from ansible.module_utils.six.moves import shlex_quote, builtins
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.playbook.play_context import PlayContext
from ansible.plugins.action import ActionBase
+from ansible.plugins.loader import init_plugin_loader
from ansible.template import Templar
from ansible.vars.clean import clean_facts
@@ -109,6 +111,11 @@ class TestActionBase(unittest.TestCase):
self.assertEqual(results, {})
def test_action_base__configure_module(self):
+ init_plugin_loader()
+ # Pre-populate the ansible.builtin collection
+ # so reading the ansible_builtin_runtime.yml happens
+ # before the mock_open below
+ import_module('ansible_collections.ansible.builtin')
fake_loader = DictDataLoader({
})
@@ -262,11 +269,8 @@ class TestActionBase(unittest.TestCase):
def get_shell_opt(opt):
- ret = None
- if opt == 'admin_users':
- ret = ['root', 'toor', 'Administrator']
- elif opt == 'remote_tmp':
- ret = '~/.ansible/tmp'
+ assert opt == 'admin_users'
+ ret = ['root', 'toor', 'Administrator']
return ret
@@ -662,17 +666,10 @@ class TestActionBase(unittest.TestCase):
mock_task.no_log = False
# create a mock connection, so we don't actually try and connect to things
- def build_module_command(env_string, shebang, cmd, arg_path=None):
- to_run = [env_string, cmd]
- if arg_path:
- to_run.append(arg_path)
- return " ".join(to_run)
-
def get_option(option):
return {'admin_users': ['root', 'toor']}.get(option)
mock_connection = MagicMock()
- mock_connection.build_module_command.side_effect = build_module_command
mock_connection.socket_path = None
mock_connection._shell.get_remote_filename.return_value = 'copy.py'
mock_connection._shell.join_path.side_effect = os.path.join
@@ -799,41 +796,7 @@ class TestActionBase(unittest.TestCase):
class TestActionBaseCleanReturnedData(unittest.TestCase):
def test(self):
-
- fake_loader = DictDataLoader({
- })
- mock_module_loader = MagicMock()
- mock_shared_loader_obj = MagicMock()
- mock_shared_loader_obj.module_loader = mock_module_loader
- connection_loader_paths = ['/tmp/asdfadf', '/usr/lib64/whatever',
- 'dfadfasf',
- 'foo.py',
- '.*',
- # FIXME: a path with parans breaks the regex
- # '(.*)',
- '/path/to/ansible/lib/ansible/plugins/connection/custom_connection.py',
- '/path/to/ansible/lib/ansible/plugins/connection/ssh.py']
-
- def fake_all(path_only=None):
- for path in connection_loader_paths:
- yield path
-
- mock_connection_loader = MagicMock()
- mock_connection_loader.all = fake_all
-
- mock_shared_loader_obj.connection_loader = mock_connection_loader
- mock_connection = MagicMock()
- # mock_connection._shell.env_prefix.side_effect = env_prefix
-
- # action_base = DerivedActionBase(mock_task, mock_connection, play_context, None, None, None)
- action_base = DerivedActionBase(task=None,
- connection=mock_connection,
- play_context=None,
- loader=fake_loader,
- templar=None,
- shared_loader_obj=mock_shared_loader_obj)
data = {'ansible_playbook_python': '/usr/bin/python',
- # 'ansible_rsync_path': '/usr/bin/rsync',
'ansible_python_interpreter': '/usr/bin/python',
'ansible_ssh_some_var': 'whatever',
'ansible_ssh_host_key_somehost': 'some key here',
diff --git a/test/units/plugins/action/test_raw.py b/test/units/plugins/action/test_raw.py
index 33480516..c50004a7 100644
--- a/test/units/plugins/action/test_raw.py
+++ b/test/units/plugins/action/test_raw.py
@@ -20,7 +20,6 @@ __metaclass__ = type
import os
-from ansible.errors import AnsibleActionFail
from units.compat import unittest
from unittest.mock import MagicMock, Mock
from ansible.plugins.action.raw import ActionModule
@@ -68,10 +67,7 @@ class TestCopyResultExclude(unittest.TestCase):
task.args = {'_raw_params': 'Args1'}
self.play_context.check_mode = True
- try:
- self.mock_am = ActionModule(task, self.connection, self.play_context, loader=None, templar=None, shared_loader_obj=None)
- except AnsibleActionFail:
- pass
+ self.mock_am = ActionModule(task, self.connection, self.play_context, loader=None, templar=None, shared_loader_obj=None)
def test_raw_test_environment_is_None(self):
diff --git a/test/units/plugins/cache/test_cache.py b/test/units/plugins/cache/test_cache.py
index 25b84c06..b4ffe4e3 100644
--- a/test/units/plugins/cache/test_cache.py
+++ b/test/units/plugins/cache/test_cache.py
@@ -29,7 +29,7 @@ from units.compat import unittest
from ansible.errors import AnsibleError
from ansible.plugins.cache import CachePluginAdjudicator
from ansible.plugins.cache.memory import CacheModule as MemoryCache
-from ansible.plugins.loader import cache_loader
+from ansible.plugins.loader import cache_loader, init_plugin_loader
from ansible.vars.fact_cache import FactCache
import pytest
@@ -66,7 +66,7 @@ class TestCachePluginAdjudicator(unittest.TestCase):
def test___getitem__(self):
with pytest.raises(KeyError):
- self.cache['foo']
+ self.cache['foo'] # pylint: disable=pointless-statement
def test_pop_with_default(self):
assert self.cache.pop('foo', 'bar') == 'bar'
@@ -183,6 +183,7 @@ class TestFactCache(unittest.TestCase):
assert len(self.cache.keys()) == 0
def test_plugin_load_failure(self):
+ init_plugin_loader()
# See https://github.com/ansible/ansible/issues/18751
# Note no fact_connection config set, so this will fail
with mock.patch('ansible.constants.CACHE_PLUGIN', 'json'):
diff --git a/test/units/plugins/connection/test_connection.py b/test/units/plugins/connection/test_connection.py
index 38d66910..56095c60 100644
--- a/test/units/plugins/connection/test_connection.py
+++ b/test/units/plugins/connection/test_connection.py
@@ -27,6 +27,28 @@ from ansible.plugins.connection import ConnectionBase
from ansible.plugins.loader import become_loader
+class NoOpConnection(ConnectionBase):
+
+ @property
+ def transport(self):
+ """This method is never called by unit tests."""
+
+ def _connect(self):
+ """This method is never called by unit tests."""
+
+ def exec_command(self):
+ """This method is never called by unit tests."""
+
+ def put_file(self):
+ """This method is never called by unit tests."""
+
+ def fetch_file(self):
+ """This method is never called by unit tests."""
+
+ def close(self):
+ """This method is never called by unit tests."""
+
+
class TestConnectionBaseClass(unittest.TestCase):
def setUp(self):
@@ -45,36 +67,8 @@ class TestConnectionBaseClass(unittest.TestCase):
with self.assertRaises(TypeError):
ConnectionModule1() # pylint: disable=abstract-class-instantiated
- class ConnectionModule2(ConnectionBase):
- def get(self, key):
- super(ConnectionModule2, self).get(key)
-
- with self.assertRaises(TypeError):
- ConnectionModule2() # pylint: disable=abstract-class-instantiated
-
def test_subclass_success(self):
- class ConnectionModule3(ConnectionBase):
-
- @property
- def transport(self):
- pass
-
- def _connect(self):
- pass
-
- def exec_command(self):
- pass
-
- def put_file(self):
- pass
-
- def fetch_file(self):
- pass
-
- def close(self):
- pass
-
- self.assertIsInstance(ConnectionModule3(self.play_context, self.in_stream), ConnectionModule3)
+ self.assertIsInstance(NoOpConnection(self.play_context, self.in_stream), NoOpConnection)
def test_check_password_prompt(self):
local = (
@@ -129,28 +123,7 @@ debug3: receive packet: type 98
debug1: Sending command: /bin/sh -c 'sudo -H -S -p "[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: " -u root /bin/sh -c '"'"'echo
'''
- class ConnectionFoo(ConnectionBase):
-
- @property
- def transport(self):
- pass
-
- def _connect(self):
- pass
-
- def exec_command(self):
- pass
-
- def put_file(self):
- pass
-
- def fetch_file(self):
- pass
-
- def close(self):
- pass
-
- c = ConnectionFoo(self.play_context, self.in_stream)
+ c = NoOpConnection(self.play_context, self.in_stream)
c.set_become_plugin(become_loader.get('sudo'))
c.become.prompt = '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: '
diff --git a/test/units/plugins/connection/test_local.py b/test/units/plugins/connection/test_local.py
index e5525855..483a881b 100644
--- a/test/units/plugins/connection/test_local.py
+++ b/test/units/plugins/connection/test_local.py
@@ -21,7 +21,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from io import StringIO
-import pytest
from units.compat import unittest
from ansible.plugins.connection import local
diff --git a/test/units/plugins/connection/test_ssh.py b/test/units/plugins/connection/test_ssh.py
index 662dff91..48ad3b73 100644
--- a/test/units/plugins/connection/test_ssh.py
+++ b/test/units/plugins/connection/test_ssh.py
@@ -24,14 +24,13 @@ from io import StringIO
import pytest
-from ansible import constants as C
from ansible.errors import AnsibleAuthenticationFailure
from units.compat import unittest
from unittest.mock import patch, MagicMock, PropertyMock
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
from ansible.module_utils.compat.selectors import SelectorKey, EVENT_READ
from ansible.module_utils.six.moves import shlex_quote
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.playbook.play_context import PlayContext
from ansible.plugins.connection import ssh
from ansible.plugins.loader import connection_loader, become_loader
@@ -142,9 +141,8 @@ class TestConnectionBaseClass(unittest.TestCase):
conn.become.check_missing_password = MagicMock(side_effect=_check_missing_password)
def get_option(option):
- if option == 'become_pass':
- return 'password'
- return None
+ assert option == 'become_pass'
+ return 'password'
conn.become.get_option = get_option
output, unprocessed = conn._examine_output(u'source', u'state', b'line 1\nline 2\nfoo\nline 3\nthis should be the remainder', False)
@@ -351,7 +349,7 @@ class MockSelector(object):
self.register = MagicMock(side_effect=self._register)
self.unregister = MagicMock(side_effect=self._unregister)
self.close = MagicMock()
- self.get_map = MagicMock(side_effect=self._get_map)
+ self.get_map = MagicMock()
self.select = MagicMock()
def _register(self, *args, **kwargs):
@@ -360,9 +358,6 @@ class MockSelector(object):
def _unregister(self, *args, **kwargs):
self.files_watched -= 1
- def _get_map(self, *args, **kwargs):
- return self.files_watched
-
@pytest.fixture
def mock_run_env(request, mocker):
@@ -457,7 +452,8 @@ class TestSSHConnectionRun(object):
def _password_with_prompt_examine_output(self, sourice, state, b_chunk, sudoable):
if state == 'awaiting_prompt':
self.conn._flags['become_prompt'] = True
- elif state == 'awaiting_escalation':
+ else:
+ assert state == 'awaiting_escalation'
self.conn._flags['become_success'] = True
return (b'', b'')
@@ -546,7 +542,6 @@ class TestSSHConnectionRetries(object):
def test_incorrect_password(self, monkeypatch):
self.conn.set_option('host_key_checking', False)
self.conn.set_option('reconnection_retries', 5)
- monkeypatch.setattr('time.sleep', lambda x: None)
self.mock_popen_res.stdout.read.side_effect = [b'']
self.mock_popen_res.stderr.read.side_effect = [b'Permission denied, please try again.\r\n']
@@ -669,7 +664,6 @@ class TestSSHConnectionRetries(object):
self.conn.set_option('reconnection_retries', 3)
monkeypatch.setattr('time.sleep', lambda x: None)
- monkeypatch.setattr('ansible.plugins.connection.ssh.os.path.exists', lambda x: True)
self.mock_popen_res.stdout.read.side_effect = [b"", b"my_stdout\n", b"second_line"]
self.mock_popen_res.stderr.read.side_effect = [b"", b"my_stderr"]
diff --git a/test/units/plugins/connection/test_winrm.py b/test/units/plugins/connection/test_winrm.py
index cb52814b..c3060da5 100644
--- a/test/units/plugins/connection/test_winrm.py
+++ b/test/units/plugins/connection/test_winrm.py
@@ -13,8 +13,8 @@ import pytest
from io import StringIO
from unittest.mock import MagicMock
-from ansible.errors import AnsibleConnectionFailure
-from ansible.module_utils._text import to_bytes
+from ansible.errors import AnsibleConnectionFailure, AnsibleError
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.playbook.play_context import PlayContext
from ansible.plugins.loader import connection_loader
from ansible.plugins.connection import winrm
@@ -441,3 +441,103 @@ class TestWinRMKerbAuth(object):
assert str(err.value) == \
"Kerberos auth failure for principal username with pexpect: " \
"Error with kinit\n<redacted>"
+
+ def test_exec_command_with_timeout(self, monkeypatch):
+ requests_exc = pytest.importorskip("requests.exceptions")
+
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+
+ mock_proto = MagicMock()
+ mock_proto.run_command.side_effect = requests_exc.Timeout("msg")
+
+ conn._connected = True
+ conn._winrm_host = 'hostname'
+
+ monkeypatch.setattr(conn, "_winrm_connect", lambda: mock_proto)
+
+ with pytest.raises(AnsibleConnectionFailure) as e:
+ conn.exec_command('cmd', in_data=None, sudoable=True)
+
+ assert str(e.value) == "winrm connection error: msg"
+
+ def test_exec_command_get_output_timeout(self, monkeypatch):
+ requests_exc = pytest.importorskip("requests.exceptions")
+
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+
+ mock_proto = MagicMock()
+ mock_proto.run_command.return_value = "command_id"
+ mock_proto.send_message.side_effect = requests_exc.Timeout("msg")
+
+ conn._connected = True
+ conn._winrm_host = 'hostname'
+
+ monkeypatch.setattr(conn, "_winrm_connect", lambda: mock_proto)
+
+ with pytest.raises(AnsibleConnectionFailure) as e:
+ conn.exec_command('cmd', in_data=None, sudoable=True)
+
+ assert str(e.value) == "winrm connection error: msg"
+
+ def test_connect_failure_auth_401(self, monkeypatch):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+ conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
+
+ mock_proto = MagicMock()
+ mock_proto.open_shell.side_effect = ValueError("Custom exc Code 401")
+
+ mock_proto_init = MagicMock()
+ mock_proto_init.return_value = mock_proto
+ monkeypatch.setattr(winrm, "Protocol", mock_proto_init)
+
+ with pytest.raises(AnsibleConnectionFailure, match="the specified credentials were rejected by the server"):
+ conn.exec_command('cmd', in_data=None, sudoable=True)
+
+ def test_connect_failure_other_exception(self, monkeypatch):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+ conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
+
+ mock_proto = MagicMock()
+ mock_proto.open_shell.side_effect = ValueError("Custom exc")
+
+ mock_proto_init = MagicMock()
+ mock_proto_init.return_value = mock_proto
+ monkeypatch.setattr(winrm, "Protocol", mock_proto_init)
+
+ with pytest.raises(AnsibleConnectionFailure, match="basic: Custom exc"):
+ conn.exec_command('cmd', in_data=None, sudoable=True)
+
+ def test_connect_failure_operation_timed_out(self, monkeypatch):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+ conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
+
+ mock_proto = MagicMock()
+ mock_proto.open_shell.side_effect = ValueError("Custom exc Operation timed out")
+
+ mock_proto_init = MagicMock()
+ mock_proto_init.return_value = mock_proto
+ monkeypatch.setattr(winrm, "Protocol", mock_proto_init)
+
+ with pytest.raises(AnsibleError, match="the connection attempt timed out"):
+ conn.exec_command('cmd', in_data=None, sudoable=True)
+
+ def test_connect_no_transport(self):
+ pc = PlayContext()
+ new_stdin = StringIO()
+ conn = connection_loader.get('winrm', pc, new_stdin)
+ conn.set_options(var_options={"_extras": {}})
+ conn._build_winrm_kwargs()
+ conn._winrm_transport = []
+
+ with pytest.raises(AnsibleError, match="No transport found for WinRM connection"):
+ conn._winrm_connect()
diff --git a/test/units/plugins/filter/test_core.py b/test/units/plugins/filter/test_core.py
index df4e4725..ab09ec43 100644
--- a/test/units/plugins/filter/test_core.py
+++ b/test/units/plugins/filter/test_core.py
@@ -3,13 +3,11 @@
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
-from jinja2.runtime import Undefined
-from jinja2.exceptions import UndefinedError
__metaclass__ = type
import pytest
-from ansible.module_utils._text import to_native
+from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.filter.core import to_uuid
from ansible.errors import AnsibleFilterError
diff --git a/test/units/plugins/filter/test_mathstuff.py b/test/units/plugins/filter/test_mathstuff.py
index f7938714..4ac5487f 100644
--- a/test/units/plugins/filter/test_mathstuff.py
+++ b/test/units/plugins/filter/test_mathstuff.py
@@ -1,9 +1,8 @@
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-# Make coding more python3-ish
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
+from __future__ import annotations
+
import pytest
from jinja2 import Environment
@@ -12,54 +11,68 @@ import ansible.plugins.filter.mathstuff as ms
from ansible.errors import AnsibleFilterError, AnsibleFilterTypeError
-UNIQUE_DATA = (([1, 3, 4, 2], [1, 3, 4, 2]),
- ([1, 3, 2, 4, 2, 3], [1, 3, 2, 4]),
- (['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd']),
- (['a', 'a', 'd', 'b', 'a', 'd', 'c', 'b'], ['a', 'd', 'b', 'c']),
- )
+UNIQUE_DATA = [
+ ([], []),
+ ([1, 3, 4, 2], [1, 3, 4, 2]),
+ ([1, 3, 2, 4, 2, 3], [1, 3, 2, 4]),
+ ([1, 2, 3, 4], [1, 2, 3, 4]),
+ ([1, 1, 4, 2, 1, 4, 3, 2], [1, 4, 2, 3]),
+]
+
+TWO_SETS_DATA = [
+ ([], [], ([], [], [])),
+ ([1, 2], [1, 2], ([1, 2], [], [])),
+ ([1, 2], [3, 4], ([], [1, 2], [1, 2, 3, 4])),
+ ([1, 2, 3], [5, 3, 4], ([3], [1, 2], [1, 2, 5, 4])),
+ ([1, 2, 3], [4, 3, 5], ([3], [1, 2], [1, 2, 4, 5])),
+]
+
+
+def dict_values(values: list[int]) -> list[dict[str, int]]:
+ """Return a list of non-hashable values derived from the given list."""
+ return [dict(x=value) for value in values]
+
+
+for _data, _expected in list(UNIQUE_DATA):
+ UNIQUE_DATA.append((dict_values(_data), dict_values(_expected)))
+
+for _dataset1, _dataset2, _expected in list(TWO_SETS_DATA):
+ TWO_SETS_DATA.append((dict_values(_dataset1), dict_values(_dataset2), tuple(dict_values(answer) for answer in _expected)))
-TWO_SETS_DATA = (([1, 2], [3, 4], ([], sorted([1, 2]), sorted([1, 2, 3, 4]), sorted([1, 2, 3, 4]))),
- ([1, 2, 3], [5, 3, 4], ([3], sorted([1, 2]), sorted([1, 2, 5, 4]), sorted([1, 2, 3, 4, 5]))),
- (['a', 'b', 'c'], ['d', 'c', 'e'], (['c'], sorted(['a', 'b']), sorted(['a', 'b', 'd', 'e']), sorted(['a', 'b', 'c', 'e', 'd']))),
- )
env = Environment()
-@pytest.mark.parametrize('data, expected', UNIQUE_DATA)
-class TestUnique:
- def test_unhashable(self, data, expected):
- assert ms.unique(env, list(data)) == expected
+def assert_lists_contain_same_elements(a, b) -> None:
+ """Assert that the two values given are lists that contain the same elements, even when the elements cannot be sorted or hashed."""
+ assert isinstance(a, list)
+ assert isinstance(b, list)
- def test_hashable(self, data, expected):
- assert ms.unique(env, tuple(data)) == expected
+ missing_from_a = [item for item in b if item not in a]
+ missing_from_b = [item for item in a if item not in b]
+ assert not missing_from_a, f'elements from `b` {missing_from_a} missing from `a` {a}'
+ assert not missing_from_b, f'elements from `a` {missing_from_b} missing from `b` {b}'
-@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA)
-class TestIntersect:
- def test_unhashable(self, dataset1, dataset2, expected):
- assert sorted(ms.intersect(env, list(dataset1), list(dataset2))) == expected[0]
- def test_hashable(self, dataset1, dataset2, expected):
- assert sorted(ms.intersect(env, tuple(dataset1), tuple(dataset2))) == expected[0]
+@pytest.mark.parametrize('data, expected', UNIQUE_DATA, ids=str)
+def test_unique(data, expected):
+ assert_lists_contain_same_elements(ms.unique(env, data), expected)
-@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA)
-class TestDifference:
- def test_unhashable(self, dataset1, dataset2, expected):
- assert sorted(ms.difference(env, list(dataset1), list(dataset2))) == expected[1]
+@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA, ids=str)
+def test_intersect(dataset1, dataset2, expected):
+ assert_lists_contain_same_elements(ms.intersect(env, dataset1, dataset2), expected[0])
- def test_hashable(self, dataset1, dataset2, expected):
- assert sorted(ms.difference(env, tuple(dataset1), tuple(dataset2))) == expected[1]
+@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA, ids=str)
+def test_difference(dataset1, dataset2, expected):
+ assert_lists_contain_same_elements(ms.difference(env, dataset1, dataset2), expected[1])
-@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA)
-class TestSymmetricDifference:
- def test_unhashable(self, dataset1, dataset2, expected):
- assert sorted(ms.symmetric_difference(env, list(dataset1), list(dataset2))) == expected[2]
- def test_hashable(self, dataset1, dataset2, expected):
- assert sorted(ms.symmetric_difference(env, tuple(dataset1), tuple(dataset2))) == expected[2]
+@pytest.mark.parametrize('dataset1, dataset2, expected', TWO_SETS_DATA, ids=str)
+def test_symmetric_difference(dataset1, dataset2, expected):
+ assert_lists_contain_same_elements(ms.symmetric_difference(env, dataset1, dataset2), expected[2])
class TestLogarithm:
diff --git a/test/units/plugins/inventory/test_constructed.py b/test/units/plugins/inventory/test_constructed.py
index 581e0253..8ae78f1d 100644
--- a/test/units/plugins/inventory/test_constructed.py
+++ b/test/units/plugins/inventory/test_constructed.py
@@ -194,11 +194,11 @@ def test_parent_group_templating_error(inventory_module):
'parent_group': '{{ location.barn-yard }}'
}
]
- with pytest.raises(AnsibleParserError) as err_message:
+ with pytest.raises(AnsibleParserError) as ex:
inventory_module._add_host_to_keyed_groups(
keyed_groups, host.vars, host.name, strict=True
)
- assert 'Could not generate parent group' in err_message
+ assert 'Could not generate parent group' in str(ex.value)
# invalid parent group did not raise an exception with strict=False
inventory_module._add_host_to_keyed_groups(
keyed_groups, host.vars, host.name, strict=False
@@ -213,17 +213,17 @@ def test_keyed_group_exclusive_argument(inventory_module):
host = inventory_module.inventory.get_host('cow')
keyed_groups = [
{
- 'key': 'tag',
+ 'key': 'nickname',
'separator': '_',
'default_value': 'default_value_name',
'trailing_separator': True
}
]
- with pytest.raises(AnsibleParserError) as err_message:
+ with pytest.raises(AnsibleParserError) as ex:
inventory_module._add_host_to_keyed_groups(
keyed_groups, host.vars, host.name, strict=True
)
- assert 'parameters are mutually exclusive' in err_message
+ assert 'parameters are mutually exclusive' in str(ex.value)
def test_keyed_group_empty_value(inventory_module):
diff --git a/test/units/plugins/inventory/test_inventory.py b/test/units/plugins/inventory/test_inventory.py
index df246073..fb5342af 100644
--- a/test/units/plugins/inventory/test_inventory.py
+++ b/test/units/plugins/inventory/test_inventory.py
@@ -27,7 +27,7 @@ from unittest import mock
from ansible import constants as C
from units.compat import unittest
from ansible.module_utils.six import string_types
-from ansible.module_utils._text import to_text
+from ansible.module_utils.common.text.converters import to_text
from units.mock.path import mock_unfrackpath_noop
from ansible.inventory.manager import InventoryManager, split_host_pattern
diff --git a/test/units/plugins/inventory/test_script.py b/test/units/plugins/inventory/test_script.py
index 9f75199f..89eb4f5b 100644
--- a/test/units/plugins/inventory/test_script.py
+++ b/test/units/plugins/inventory/test_script.py
@@ -28,7 +28,7 @@ from ansible import constants as C
from ansible.errors import AnsibleError
from ansible.plugins.loader import PluginLoader
from units.compat import unittest
-from ansible.module_utils._text import to_bytes, to_native
+from ansible.module_utils.common.text.converters import to_bytes, to_native
class TestInventoryModule(unittest.TestCase):
@@ -103,3 +103,11 @@ class TestInventoryModule(unittest.TestCase):
self.inventory_module.parse(self.inventory, self.loader, '/foo/bar/foobar.py')
assert e.value.message == to_native("failed to parse executable inventory script results from "
"/foo/bar/foobar.py: needs to be a json dict\ndummyédata\n")
+
+ def test_get_host_variables_subprocess_script_raises_error(self):
+ self.popen_result.returncode = 1
+ self.popen_result.stderr = to_bytes("dummyéerror")
+
+ with pytest.raises(AnsibleError) as e:
+ self.inventory_module.get_host_variables('/foo/bar/foobar.py', 'dummy host')
+ assert e.value.message == "Inventory script (/foo/bar/foobar.py) had an execution error: dummyéerror"
diff --git a/test/units/plugins/lookup/test_password.py b/test/units/plugins/lookup/test_password.py
index 318bc10b..685f2ce7 100644
--- a/test/units/plugins/lookup/test_password.py
+++ b/test/units/plugins/lookup/test_password.py
@@ -23,7 +23,7 @@ __metaclass__ = type
try:
import passlib
from passlib.handlers import pbkdf2
-except ImportError:
+except ImportError: # pragma: nocover
passlib = None
pbkdf2 = None
@@ -36,7 +36,7 @@ from unittest.mock import mock_open, patch
from ansible.errors import AnsibleError
from ansible.module_utils.six import text_type
from ansible.module_utils.six.moves import builtins
-from ansible.module_utils._text import to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible.plugins.loader import PluginLoader, lookup_loader
from ansible.plugins.lookup import password
@@ -416,8 +416,6 @@ class BaseTestLookupModule(unittest.TestCase):
password.os.open = lambda path, flag: None
self.os_close = password.os.close
password.os.close = lambda fd: None
- self.os_remove = password.os.remove
- password.os.remove = lambda path: None
self.makedirs_safe = password.makedirs_safe
password.makedirs_safe = lambda path, mode: None
@@ -425,7 +423,6 @@ class BaseTestLookupModule(unittest.TestCase):
password.os.path.exists = self.os_path_exists
password.os.open = self.os_open
password.os.close = self.os_close
- password.os.remove = self.os_remove
password.makedirs_safe = self.makedirs_safe
@@ -467,23 +464,17 @@ class TestLookupModuleWithoutPasslib(BaseTestLookupModule):
def test_lock_been_held(self, mock_sleep):
# pretend the lock file is here
password.os.path.exists = lambda x: True
- try:
+ with pytest.raises(AnsibleError):
with patch.object(builtins, 'open', mock_open(read_data=b'hunter42 salt=87654321\n')) as m:
# should timeout here
- results = self.password_lookup.run([u'/path/to/somewhere chars=anything'], None)
- self.fail("Lookup didn't timeout when lock already been held")
- except AnsibleError:
- pass
+ self.password_lookup.run([u'/path/to/somewhere chars=anything'], None)
def test_lock_not_been_held(self):
# pretend now there is password file but no lock
password.os.path.exists = lambda x: x == to_bytes('/path/to/somewhere')
- try:
- with patch.object(builtins, 'open', mock_open(read_data=b'hunter42 salt=87654321\n')) as m:
- # should not timeout here
- results = self.password_lookup.run([u'/path/to/somewhere chars=anything'], None)
- except AnsibleError:
- self.fail('Lookup timeouts when lock is free')
+ with patch.object(builtins, 'open', mock_open(read_data=b'hunter42 salt=87654321\n')) as m:
+ # should not timeout here
+ results = self.password_lookup.run([u'/path/to/somewhere chars=anything'], None)
for result in results:
self.assertEqual(result, u'hunter42')
@@ -531,10 +522,8 @@ class TestLookupModuleWithPasslib(BaseTestLookupModule):
self.assertEqual(int(str_parts[2]), crypt_parts['rounds'])
self.assertIsInstance(result, text_type)
- @patch.object(PluginLoader, '_get_paths')
@patch('ansible.plugins.lookup.password._write_password_file')
- def test_password_already_created_encrypt(self, mock_get_paths, mock_write_file):
- mock_get_paths.return_value = ['/path/one', '/path/two', '/path/three']
+ def test_password_already_created_encrypt(self, mock_write_file):
password.os.path.exists = lambda x: x == to_bytes('/path/to/somewhere')
with patch.object(builtins, 'open', mock_open(read_data=b'hunter42 salt=87654321\n')) as m:
@@ -542,6 +531,9 @@ class TestLookupModuleWithPasslib(BaseTestLookupModule):
for result in results:
self.assertEqual(result, u'$pbkdf2-sha256$20000$ODc2NTQzMjE$Uikde0cv0BKaRaAXMrUQB.zvG4GmnjClwjghwIRf2gU')
+ # Assert the password file is not rewritten
+ mock_write_file.assert_not_called()
+
@pytest.mark.skipif(passlib is None, reason='passlib must be installed to run these tests')
class TestLookupModuleWithPasslibWrappedAlgo(BaseTestLookupModule):
diff --git a/test/units/plugins/test_plugins.py b/test/units/plugins/test_plugins.py
index be123b15..ba2ad2b6 100644
--- a/test/units/plugins/test_plugins.py
+++ b/test/units/plugins/test_plugins.py
@@ -46,14 +46,14 @@ class TestErrors(unittest.TestCase):
# python library, and then uses the __file__ attribute of
# the result for that to get the library path, so we mock
# that here and patch the builtin to use our mocked result
- foo = MagicMock()
- bar = MagicMock()
+ foo_pkg = MagicMock()
+ bar_pkg = MagicMock()
bam = MagicMock()
bam.__file__ = '/path/to/my/foo/bar/bam/__init__.py'
- bar.bam = bam
- foo.return_value.bar = bar
+ bar_pkg.bam = bam
+ foo_pkg.return_value.bar = bar_pkg
pl = PluginLoader('test', 'foo.bar.bam', 'test', 'test_plugin')
- with patch('builtins.__import__', foo):
+ with patch('builtins.__import__', foo_pkg):
self.assertEqual(pl._get_package_paths(), ['/path/to/my/foo/bar/bam'])
def test_plugins__get_paths(self):
diff --git a/test/units/requirements.txt b/test/units/requirements.txt
index 1822adaa..c77c55cd 100644
--- a/test/units/requirements.txt
+++ b/test/units/requirements.txt
@@ -1,4 +1,4 @@
-bcrypt ; python_version >= '3.9' # controller only
-passlib ; python_version >= '3.9' # controller only
-pexpect ; python_version >= '3.9' # controller only
-pywinrm ; python_version >= '3.9' # controller only
+bcrypt ; python_version >= '3.10' # controller only
+passlib ; python_version >= '3.10' # controller only
+pexpect ; python_version >= '3.10' # controller only
+pywinrm ; python_version >= '3.10' # controller only
diff --git a/test/units/template/test_templar.py b/test/units/template/test_templar.py
index 6747f768..02840e16 100644
--- a/test/units/template/test_templar.py
+++ b/test/units/template/test_templar.py
@@ -22,11 +22,10 @@ __metaclass__ = type
from jinja2.runtime import Context
from units.compat import unittest
-from unittest.mock import patch
from ansible import constants as C
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
-from ansible.module_utils.six import string_types
+from ansible.plugins.loader import init_plugin_loader
from ansible.template import Templar, AnsibleContext, AnsibleEnvironment, AnsibleUndefined
from ansible.utils.unsafe_proxy import AnsibleUnsafe, wrap_var
from units.mock.loader import DictDataLoader
@@ -34,6 +33,7 @@ from units.mock.loader import DictDataLoader
class BaseTemplar(object):
def setUp(self):
+ init_plugin_loader()
self.test_vars = dict(
foo="bar",
bam="{{foo}}",
@@ -62,14 +62,6 @@ class BaseTemplar(object):
return self._ansible_context._is_unsafe(obj)
-# class used for testing arbitrary objects passed to template
-class SomeClass(object):
- foo = 'bar'
-
- def __init__(self):
- self.blip = 'blip'
-
-
class SomeUnsafeClass(AnsibleUnsafe):
def __init__(self):
super(SomeUnsafeClass, self).__init__()
@@ -266,8 +258,6 @@ class TestTemplarMisc(BaseTemplar, unittest.TestCase):
templar.available_variables = "foo=bam"
except AssertionError:
pass
- except Exception as e:
- self.fail(e)
def test_templar_escape_backslashes(self):
# Rule of thumb: If escape backslashes is True you should end up with
diff --git a/test/units/template/test_vars.py b/test/units/template/test_vars.py
index 514104f2..f43cfac4 100644
--- a/test/units/template/test_vars.py
+++ b/test/units/template/test_vars.py
@@ -19,23 +19,16 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from units.compat import unittest
-from unittest.mock import MagicMock
-
+from ansible.template import Templar
from ansible.template.vars import AnsibleJ2Vars
-class TestVars(unittest.TestCase):
- def setUp(self):
- self.mock_templar = MagicMock(name='mock_templar')
+def test_globals_empty():
+ assert isinstance(dict(AnsibleJ2Vars(Templar(None), {})), dict)
- def test_globals_empty(self):
- ajvars = AnsibleJ2Vars(self.mock_templar, {})
- res = dict(ajvars)
- self.assertIsInstance(res, dict)
- def test_globals(self):
- res = dict(AnsibleJ2Vars(self.mock_templar, {'foo': 'bar', 'blip': [1, 2, 3]}))
- self.assertIsInstance(res, dict)
- self.assertIn('foo', res)
- self.assertEqual(res['foo'], 'bar')
+def test_globals():
+ res = dict(AnsibleJ2Vars(Templar(None), {'foo': 'bar', 'blip': [1, 2, 3]}))
+ assert isinstance(res, dict)
+ assert 'foo' in res
+ assert res['foo'] == 'bar'
diff --git a/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py b/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py
index 9d30580f..a85f422a 100644
--- a/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py
+++ b/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/action/my_action.py
@@ -1,7 +1,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from ..module_utils.my_util import question
+from ..module_utils.my_util import question # pylint: disable=unused-import
def action_code():
diff --git a/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_other_util.py b/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_other_util.py
index 35e1381b..463b1334 100644
--- a/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_other_util.py
+++ b/test/units/utils/collection_loader/fixtures/collections/ansible_collections/testns/testcoll/plugins/module_utils/my_other_util.py
@@ -1,4 +1,4 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from .my_util import question
+from .my_util import question # pylint: disable=unused-import
diff --git a/test/units/utils/collection_loader/test_collection_loader.py b/test/units/utils/collection_loader/test_collection_loader.py
index f7050dcd..feaaf97a 100644
--- a/test/units/utils/collection_loader/test_collection_loader.py
+++ b/test/units/utils/collection_loader/test_collection_loader.py
@@ -13,7 +13,7 @@ from ansible.modules import ping as ping_module
from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
from ansible.utils.collection_loader._collection_finder import (
_AnsibleCollectionFinder, _AnsibleCollectionLoader, _AnsibleCollectionNSPkgLoader, _AnsibleCollectionPkgLoader,
- _AnsibleCollectionPkgLoaderBase, _AnsibleCollectionRootPkgLoader, _AnsiblePathHookFinder,
+ _AnsibleCollectionPkgLoaderBase, _AnsibleCollectionRootPkgLoader, _AnsibleNSTraversable, _AnsiblePathHookFinder,
_get_collection_name_from_path, _get_collection_role_path, _get_collection_metadata, _iter_modules_impl
)
from ansible.utils.collection_loader._collection_config import _EventSource
@@ -29,8 +29,16 @@ def teardown(*args, **kwargs):
# BEGIN STANDALONE TESTS - these exercise behaviors of the individual components without the import machinery
-@pytest.mark.skipif(not PY3, reason='Testing Python 2 codepath (find_module) on Python 3')
-def test_find_module_py3():
+@pytest.mark.filterwarnings(
+ 'ignore:'
+ r'find_module\(\) is deprecated and slated for removal in Python 3\.12; use find_spec\(\) instead'
+ ':DeprecationWarning',
+ 'ignore:'
+ r'FileFinder\.find_loader\(\) is deprecated and slated for removal in Python 3\.12; use find_spec\(\) instead'
+ ':DeprecationWarning',
+)
+@pytest.mark.skipif(not PY3 or sys.version_info >= (3, 12), reason='Testing Python 2 codepath (find_module) on Python 3, <= 3.11')
+def test_find_module_py3_lt_312():
dir_to_a_file = os.path.dirname(ping_module.__file__)
path_hook_finder = _AnsiblePathHookFinder(_AnsibleCollectionFinder(), dir_to_a_file)
@@ -40,6 +48,16 @@ def test_find_module_py3():
assert path_hook_finder.find_module('missing') is None
+@pytest.mark.skipif(sys.version_info < (3, 12), reason='Testing Python 2 codepath (find_module) on Python >= 3.12')
+def test_find_module_py3_gt_311():
+ dir_to_a_file = os.path.dirname(ping_module.__file__)
+ path_hook_finder = _AnsiblePathHookFinder(_AnsibleCollectionFinder(), dir_to_a_file)
+
+ # setuptools may fall back to find_module on Python 3 if find_spec returns None
+ # see https://github.com/pypa/setuptools/pull/2918
+ assert path_hook_finder.find_spec('missing') is None
+
+
def test_finder_setup():
# ensure scalar path is listified
f = _AnsibleCollectionFinder(paths='/bogus/bogus')
@@ -828,6 +846,53 @@ def test_collectionref_components_invalid(name, subdirs, resource, ref_type, exp
assert re.search(expected_error_expression, str(curerr.value))
+@pytest.mark.skipif(not PY3, reason='importlib.resources only supported for py3')
+def test_importlib_resources():
+ if sys.version_info < (3, 10):
+ from importlib_resources import files
+ else:
+ from importlib.resources import files
+ from pathlib import Path
+
+ f = get_default_finder()
+ reset_collections_loader_state(f)
+
+ ansible_collections_ns = files('ansible_collections')
+ ansible_ns = files('ansible_collections.ansible')
+ testns = files('ansible_collections.testns')
+ testcoll = files('ansible_collections.testns.testcoll')
+ testcoll2 = files('ansible_collections.testns.testcoll2')
+ module_utils = files('ansible_collections.testns.testcoll.plugins.module_utils')
+
+ assert isinstance(ansible_collections_ns, _AnsibleNSTraversable)
+ assert isinstance(ansible_ns, _AnsibleNSTraversable)
+ assert isinstance(testcoll, Path)
+ assert isinstance(module_utils, Path)
+
+ assert ansible_collections_ns.is_dir()
+ assert ansible_ns.is_dir()
+ assert testcoll.is_dir()
+ assert module_utils.is_dir()
+
+ first_path = Path(default_test_collection_paths[0])
+ second_path = Path(default_test_collection_paths[1])
+ testns_paths = []
+ ansible_ns_paths = []
+ for path in default_test_collection_paths[:2]:
+ ansible_ns_paths.append(Path(path) / 'ansible_collections' / 'ansible')
+ testns_paths.append(Path(path) / 'ansible_collections' / 'testns')
+
+ assert testns._paths == testns_paths
+ # NOTE: The next two asserts check for subsets to accommodate running the unit tests when externally installed collections are available.
+ assert set(ansible_ns_paths).issubset(ansible_ns._paths)
+ assert set(Path(p) / 'ansible_collections' for p in default_test_collection_paths[:2]).issubset(ansible_collections_ns._paths)
+ assert testcoll2 == second_path / 'ansible_collections' / 'testns' / 'testcoll2'
+
+ assert {p.name for p in module_utils.glob('*.py')} == {'__init__.py', 'my_other_util.py', 'my_util.py'}
+ nestcoll_mu_init = first_path / 'ansible_collections' / 'testns' / 'testcoll' / 'plugins' / 'module_utils' / '__init__.py'
+ assert next(module_utils.glob('__init__.py')) == nestcoll_mu_init
+
+
# BEGIN TEST SUPPORT
default_test_collection_paths = [
diff --git a/test/units/utils/display/test_broken_cowsay.py b/test/units/utils/display/test_broken_cowsay.py
index d888010a..96157e1a 100644
--- a/test/units/utils/display/test_broken_cowsay.py
+++ b/test/units/utils/display/test_broken_cowsay.py
@@ -12,16 +12,13 @@ from unittest.mock import MagicMock
def test_display_with_fake_cowsay_binary(capsys, mocker):
- mocker.patch("ansible.constants.ANSIBLE_COW_PATH", "./cowsay.sh")
+ display = Display()
- def mock_communicate(input=None, timeout=None):
- return b"", b""
+ mocker.patch("ansible.constants.ANSIBLE_COW_PATH", "./cowsay.sh")
mock_popen = MagicMock()
- mock_popen.return_value.communicate = mock_communicate
mock_popen.return_value.returncode = 1
mocker.patch("subprocess.Popen", mock_popen)
- display = Display()
assert not hasattr(display, "cows_available")
assert display.b_cowsay is None
diff --git a/test/units/utils/test_cleanup_tmp_file.py b/test/units/utils/test_cleanup_tmp_file.py
index 2a44a55b..35374f4d 100644
--- a/test/units/utils/test_cleanup_tmp_file.py
+++ b/test/units/utils/test_cleanup_tmp_file.py
@@ -6,16 +6,11 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
-import pytest
import tempfile
from ansible.utils.path import cleanup_tmp_file
-def raise_error():
- raise OSError
-
-
def test_cleanup_tmp_file_file():
tmp_fd, tmp = tempfile.mkstemp()
cleanup_tmp_file(tmp)
@@ -34,15 +29,21 @@ def test_cleanup_tmp_file_nonexistant():
assert None is cleanup_tmp_file('nope')
-def test_cleanup_tmp_file_failure(mocker):
+def test_cleanup_tmp_file_failure(mocker, capsys):
tmp = tempfile.mkdtemp()
- with pytest.raises(Exception):
- mocker.patch('shutil.rmtree', side_effect=raise_error())
- cleanup_tmp_file(tmp)
+ rmtree = mocker.patch('shutil.rmtree', side_effect=OSError('test induced failure'))
+ cleanup_tmp_file(tmp)
+ out, err = capsys.readouterr()
+ assert out == ''
+ assert err == ''
+ rmtree.assert_called_once()
def test_cleanup_tmp_file_failure_warning(mocker, capsys):
tmp = tempfile.mkdtemp()
- with pytest.raises(Exception):
- mocker.patch('shutil.rmtree', side_effect=raise_error())
- cleanup_tmp_file(tmp, warn=True)
+ rmtree = mocker.patch('shutil.rmtree', side_effect=OSError('test induced failure'))
+ cleanup_tmp_file(tmp, warn=True)
+ out, err = capsys.readouterr()
+ assert out == 'Unable to remove temporary file test induced failure\n'
+ assert err == ''
+ rmtree.assert_called_once()
diff --git a/test/units/utils/test_display.py b/test/units/utils/test_display.py
index 6b1914bb..80b7a099 100644
--- a/test/units/utils/test_display.py
+++ b/test/units/utils/test_display.py
@@ -18,16 +18,14 @@ from ansible.utils.multiprocessing import context as multiprocessing_context
@pytest.fixture
def problematic_wcswidth_chars():
- problematic = []
- try:
- locale.setlocale(locale.LC_ALL, 'C.UTF-8')
- except Exception:
- return problematic
+ locale.setlocale(locale.LC_ALL, 'C.UTF-8')
candidates = set(chr(c) for c in range(sys.maxunicode) if unicodedata.category(chr(c)) == 'Cf')
- for c in candidates:
- if _LIBC.wcswidth(c, _MAX_INT) == -1:
- problematic.append(c)
+ problematic = [candidate for candidate in candidates if _LIBC.wcswidth(candidate, _MAX_INT) == -1]
+
+ if not problematic:
+ # Newer distributions (Ubuntu 22.04, Fedora 38) include a libc which does not report problematic characters.
+ pytest.skip("no problematic wcswidth chars found") # pragma: nocover
return problematic
@@ -54,9 +52,6 @@ def test_get_text_width():
def test_get_text_width_no_locale(problematic_wcswidth_chars):
- if not problematic_wcswidth_chars:
- pytest.skip("No problmatic wcswidth chars")
- locale.setlocale(locale.LC_ALL, 'C.UTF-8')
pytest.raises(EnvironmentError, get_text_width, problematic_wcswidth_chars[0])
@@ -108,9 +103,21 @@ def test_Display_display_fork():
display = Display()
display.set_queue(queue)
display.display('foo')
- queue.send_display.assert_called_once_with(
- 'foo', color=None, stderr=False, screen_only=False, log_only=False, newline=True
- )
+ queue.send_display.assert_called_once_with('display', 'foo')
+
+ p = multiprocessing_context.Process(target=test)
+ p.start()
+ p.join()
+ assert p.exitcode == 0
+
+
+def test_Display_display_warn_fork():
+ def test():
+ queue = MagicMock()
+ display = Display()
+ display.set_queue(queue)
+ display.warning('foo')
+ queue.send_display.assert_called_once_with('warning', 'foo')
p = multiprocessing_context.Process(target=test)
p.start()
diff --git a/test/units/utils/test_encrypt.py b/test/units/utils/test_encrypt.py
index 72fe3b07..be325790 100644
--- a/test/units/utils/test_encrypt.py
+++ b/test/units/utils/test_encrypt.py
@@ -27,17 +27,26 @@ class passlib_off(object):
def assert_hash(expected, secret, algorithm, **settings):
+ assert encrypt.do_encrypt(secret, algorithm, **settings) == expected
if encrypt.PASSLIB_AVAILABLE:
- assert encrypt.passlib_or_crypt(secret, algorithm, **settings) == expected
assert encrypt.PasslibHash(algorithm).hash(secret, **settings) == expected
else:
- assert encrypt.passlib_or_crypt(secret, algorithm, **settings) == expected
with pytest.raises(AnsibleError) as excinfo:
encrypt.PasslibHash(algorithm).hash(secret, **settings)
assert excinfo.value.args[0] == "passlib must be installed and usable to hash with '%s'" % algorithm
@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib')
+def test_passlib_or_crypt():
+ with passlib_off():
+ expected = "$5$rounds=5000$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7"
+ assert encrypt.passlib_or_crypt("123", "sha256_crypt", salt="12345678", rounds=5000) == expected
+
+ expected = "$5$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7"
+ assert encrypt.passlib_or_crypt("123", "sha256_crypt", salt="12345678", rounds=5000) == expected
+
+
+@pytest.mark.skipif(sys.platform.startswith('darwin'), reason='macOS requires passlib')
def test_encrypt_with_rounds_no_passlib():
with passlib_off():
assert_hash("$5$rounds=5000$12345678$uAZsE3BenI2G.nA8DpTl.9Dc8JiqacI53pEqRr5ppT7",
diff --git a/test/units/utils/test_unsafe_proxy.py b/test/units/utils/test_unsafe_proxy.py
index ea653cfe..55f1b6dd 100644
--- a/test/units/utils/test_unsafe_proxy.py
+++ b/test/units/utils/test_unsafe_proxy.py
@@ -5,7 +5,9 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
-from ansible.module_utils.six import PY3
+import pathlib
+import sys
+
from ansible.utils.unsafe_proxy import AnsibleUnsafe, AnsibleUnsafeBytes, AnsibleUnsafeText, wrap_var
from ansible.module_utils.common.text.converters import to_text, to_bytes
@@ -19,10 +21,7 @@ def test_wrap_var_bytes():
def test_wrap_var_string():
- if PY3:
- assert isinstance(wrap_var('foo'), AnsibleUnsafeText)
- else:
- assert isinstance(wrap_var('foo'), AnsibleUnsafeBytes)
+ assert isinstance(wrap_var('foo'), AnsibleUnsafeText)
def test_wrap_var_dict():
@@ -95,12 +94,12 @@ def test_wrap_var_no_ref():
'text': 'text',
}
wrapped_thing = wrap_var(thing)
- thing is not wrapped_thing
- thing['foo'] is not wrapped_thing['foo']
- thing['bar'][0] is not wrapped_thing['bar'][0]
- thing['baz'][0] is not wrapped_thing['baz'][0]
- thing['none'] is not wrapped_thing['none']
- thing['text'] is not wrapped_thing['text']
+ assert thing is not wrapped_thing
+ assert thing['foo'] is not wrapped_thing['foo']
+ assert thing['bar'][0] is not wrapped_thing['bar'][0]
+ assert thing['baz'][0] is not wrapped_thing['baz'][0]
+ assert thing['none'] is wrapped_thing['none']
+ assert thing['text'] is not wrapped_thing['text']
def test_AnsibleUnsafeText():
@@ -119,3 +118,10 @@ def test_to_text_unsafe():
def test_to_bytes_unsafe():
assert isinstance(to_bytes(AnsibleUnsafeText(u'foo')), AnsibleUnsafeBytes)
assert to_bytes(AnsibleUnsafeText(u'foo')) == AnsibleUnsafeBytes(b'foo')
+
+
+def test_unsafe_with_sys_intern():
+ # Specifically this is actually about sys.intern, test of pathlib
+ # because that is a specific affected use
+ assert sys.intern(AnsibleUnsafeText('foo')) == 'foo'
+ assert pathlib.Path(AnsibleUnsafeText('/tmp')) == pathlib.Path('/tmp')
diff --git a/test/units/vars/test_module_response_deepcopy.py b/test/units/vars/test_module_response_deepcopy.py
index 78f9de0e..3313dea1 100644
--- a/test/units/vars/test_module_response_deepcopy.py
+++ b/test/units/vars/test_module_response_deepcopy.py
@@ -7,8 +7,6 @@ __metaclass__ = type
from ansible.vars.clean import module_response_deepcopy
-import pytest
-
def test_module_response_deepcopy_basic():
x = 42
@@ -37,15 +35,6 @@ def test_module_response_deepcopy_empty_tuple():
assert x is y
-@pytest.mark.skip(reason='No current support for this situation')
-def test_module_response_deepcopy_tuple():
- x = ([1, 2], 3)
- y = module_response_deepcopy(x)
- assert y == x
- assert x is not y
- assert x[0] is not y[0]
-
-
def test_module_response_deepcopy_tuple_of_immutables():
x = ((1, 2), 3)
y = module_response_deepcopy(x)
diff --git a/test/units/vars/test_variable_manager.py b/test/units/vars/test_variable_manager.py
index 67ec120b..ee6de817 100644
--- a/test/units/vars/test_variable_manager.py
+++ b/test/units/vars/test_variable_manager.py
@@ -141,10 +141,8 @@ class TestVariableManager(unittest.TestCase):
return
# pylint: disable=unreachable
- '''
- Tests complex variations and combinations of get_vars() with different
- objects to modify the context under which variables are merged.
- '''
+ # Tests complex variations and combinations of get_vars() with different
+ # objects to modify the context under which variables are merged.
# FIXME: BCS makethiswork
# return True