From 62d60ad1bf13bf897719d8221f0f6ba046b6db6d Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Wed, 2 Feb 2022 18:05:26 -0800 Subject: [stable-2.9] ansible-test - Use relative paths in junit output. (#76911) * [stable-2.9] ansible-test - Use relative paths in junit output. (#76871) * ansible-test - Use relative paths in junit output. * ansible-test - Handle out-of-tree JUnit paths. * Also fix a traceback in the junit callback during automatic fact gathering. (cherry picked from commit fbb5d56bd274c44b193cb95f0230b9352f62aab2). * Fix task path unicode error in junit callback. (cherry picked from commit 41db6d8d35900d425df3228406db3fec61ab2269) --- .../fragments/ansible-test-junit-relative-paths.yml | 6 ++++++ .../fragments/junit-callback-task-path-unicode.yml | 2 ++ lib/ansible/plugins/callback/junit.py | 18 ++++++++++++++++-- test/lib/ansible_test/_internal/executor.py | 10 +++++++--- .../lib/ansible_test/_internal/integration/__init__.py | 7 ++++--- 5 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/ansible-test-junit-relative-paths.yml create mode 100644 changelogs/fragments/junit-callback-task-path-unicode.yml diff --git a/changelogs/fragments/ansible-test-junit-relative-paths.yml b/changelogs/fragments/ansible-test-junit-relative-paths.yml new file mode 100644 index 0000000000..b1ace19687 --- /dev/null +++ b/changelogs/fragments/ansible-test-junit-relative-paths.yml @@ -0,0 +1,6 @@ +bugfixes: + - ansible-test - Use relative paths in JUnit files generated during integration test runs. + - ansible-test - Replace the directory portion of out-of-tree paths in JUnit files from integration tests with the ``out-of-tree:`` prefix. + - junit callback - Fix traceback during automatic fact gathering when using relative paths. +minor_changes: + - junit callback - Add support for replacing the directory portion of out-of-tree relative task paths with a placeholder. diff --git a/changelogs/fragments/junit-callback-task-path-unicode.yml b/changelogs/fragments/junit-callback-task-path-unicode.yml new file mode 100644 index 0000000000..2b1ffd41ea --- /dev/null +++ b/changelogs/fragments/junit-callback-task-path-unicode.yml @@ -0,0 +1,2 @@ +bugfixes: + - junit callback - Fix unicode error when handling non-ASCII task paths. diff --git a/lib/ansible/plugins/callback/junit.py b/lib/ansible/plugins/callback/junit.py index 2fe402ea2f..838f491def 100644 --- a/lib/ansible/plugins/callback/junit.py +++ b/lib/ansible/plugins/callback/junit.py @@ -40,6 +40,13 @@ DOCUMENTATION = ''' version_added: "2.8" env: - name: JUNIT_TASK_RELATIVE_PATH + replace_out_of_tree_path: + name: Replace out of tree path + default: none + description: Replace the directory portion of an out-of-tree relative task path with the given placeholder + version_added: "2.12.3" + env: + - name: JUNIT_REPLACE_OUT_OF_TREE_PATH fail_on_change: name: JUnit fail on change default: False @@ -155,6 +162,7 @@ class CallbackModule(CallbackBase): self._include_setup_tasks_in_report = os.getenv('JUNIT_INCLUDE_SETUP_TASKS_IN_REPORT', 'True').lower() self._hide_task_arguments = os.getenv('JUNIT_HIDE_TASK_ARGUMENTS', 'False').lower() self._test_case_prefix = os.getenv('JUNIT_TEST_CASE_PREFIX', '') + self._replace_out_of_tree_path = os.getenv('JUNIT_REPLACE_OUT_OF_TREE_PATH', None) self._playbook_path = None self._playbook_name = None self._play_name = None @@ -174,6 +182,9 @@ class CallbackModule(CallbackBase): self._display.warning('The `ordereddict` python module is not installed. ' 'Disabling the `junit` callback plugin.') + if self._replace_out_of_tree_path is not None: + self._replace_out_of_tree_path = to_text(self._replace_out_of_tree_path) + if not os.path.exists(self._output_dir): os.makedirs(self._output_dir) @@ -232,11 +243,14 @@ class CallbackModule(CallbackBase): name = '[%s] %s: %s' % (host_data.name, task_data.play, task_data.name) duration = host_data.finish - task_data.start - if self._task_relative_path: - junit_classname = os.path.relpath(task_data.path, self._task_relative_path) + if self._task_relative_path and task_data.path: + junit_classname = to_text(os.path.relpath(to_bytes(task_data.path), to_bytes(self._task_relative_path))) else: junit_classname = task_data.path + if self._replace_out_of_tree_path is not None and junit_classname.startswith('../'): + junit_classname = self._replace_out_of_tree_path + to_text(os.path.basename(to_bytes(junit_classname))) + if self._task_class == 'true': junit_classname = re.sub(r'\.yml:[0-9]+$', '', junit_classname) diff --git a/test/lib/ansible_test/_internal/executor.py b/test/lib/ansible_test/_internal/executor.py index 26afac58ac..078ef10897 100644 --- a/test/lib/ansible_test/_internal/executor.py +++ b/test/lib/ansible_test/_internal/executor.py @@ -135,6 +135,7 @@ from .integration import ( get_inventory_relative_path, check_inventory, delegate_inventory, + IntegrationEnvironment, ) from .data import ( @@ -1435,7 +1436,7 @@ def run_setup_targets(args, test_dir, target_names, targets_dict, targets_execut targets_executed.add(target_name) -def integration_environment(args, target, test_dir, inventory_path, ansible_config, env_config): +def integration_environment(args, target, test_dir, inventory_path, ansible_config, env_config, test_env): """ :type args: IntegrationConfig :type target: IntegrationTarget @@ -1443,6 +1444,7 @@ def integration_environment(args, target, test_dir, inventory_path, ansible_conf :type inventory_path: str :type ansible_config: str | None :type env_config: CloudEnvironmentConfig | None + :type test_env: IntegrationEnvironment :rtype: dict[str, str] """ env = ansible_environment(args, ansible_config=ansible_config) @@ -1456,6 +1458,8 @@ def integration_environment(args, target, test_dir, inventory_path, ansible_conf integration = dict( JUNIT_OUTPUT_DIR=ResultType.JUNIT.path, + JUNIT_TASK_RELATIVE_PATH=test_env.test_dir, + JUNIT_REPLACE_OUT_OF_TREE_PATH='out-of-tree:', ANSIBLE_CALLBACK_WHITELIST=','.join(sorted(set(callback_plugins))), ANSIBLE_TEST_CI=args.metadata.ci_provider or get_ci_provider().code, ANSIBLE_TEST_COVERAGE='check' if args.coverage_check else ('yes' if args.coverage else ''), @@ -1502,7 +1506,7 @@ def command_integration_script(args, target, test_dir, inventory_path, temp_path if args.verbosity: cmd.append('-' + ('v' * args.verbosity)) - env = integration_environment(args, target, test_dir, test_env.inventory_path, test_env.ansible_config, env_config) + env = integration_environment(args, target, test_dir, test_env.inventory_path, test_env.ansible_config, env_config, test_env) cwd = os.path.join(test_env.targets_dir, target.relative_path) env.update(dict( @@ -1610,7 +1614,7 @@ def command_integration_role(args, target, start_at_task, test_dir, inventory_pa if args.verbosity: cmd.append('-' + ('v' * args.verbosity)) - env = integration_environment(args, target, test_dir, test_env.inventory_path, test_env.ansible_config, env_config) + env = integration_environment(args, target, test_dir, test_env.inventory_path, test_env.ansible_config, env_config, test_env) cwd = test_env.integration_dir env.update(dict( diff --git a/test/lib/ansible_test/_internal/integration/__init__.py b/test/lib/ansible_test/_internal/integration/__init__.py index 84e5e097c0..a4d146307f 100644 --- a/test/lib/ansible_test/_internal/integration/__init__.py +++ b/test/lib/ansible_test/_internal/integration/__init__.py @@ -206,7 +206,7 @@ def integration_test_environment(args, target, inventory_path_src): ansible_config = ansible_config_src vars_file = os.path.join(data_context().content.root, data_context().content.integration_vars_path) - yield IntegrationEnvironment(integration_dir, targets_dir, inventory_path, ansible_config, vars_file) + yield IntegrationEnvironment(data_context().content.root, integration_dir, targets_dir, inventory_path, ansible_config, vars_file) return # When testing a collection, the temporary directory must reside within the collection. @@ -284,7 +284,7 @@ def integration_test_environment(args, target, inventory_path_src): make_dirs(os.path.dirname(file_dst)) shutil.copy2(file_src, file_dst) - yield IntegrationEnvironment(integration_dir, targets_dir, inventory_path, ansible_config, vars_file) + yield IntegrationEnvironment(temp_dir, integration_dir, targets_dir, inventory_path, ansible_config, vars_file) finally: if not args.explain: shutil.rmtree(temp_dir) @@ -322,7 +322,8 @@ def integration_test_config_file(args, env_config, integration_dir): class IntegrationEnvironment: """Details about the integration environment.""" - def __init__(self, integration_dir, targets_dir, inventory_path, ansible_config, vars_file): + def __init__(self, test_dir, integration_dir, targets_dir, inventory_path, ansible_config, vars_file): + self.test_dir = test_dir self.integration_dir = integration_dir self.targets_dir = targets_dir self.inventory_path = inventory_path -- cgit v1.2.1