summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changelogs/fragments/ansible-test-refactor.yml5
-rw-r--r--test/lib/ansible_test/_internal/classification.py1
-rw-r--r--test/lib/ansible_test/_internal/cli.py4
-rw-r--r--test/lib/ansible_test/_internal/cloud/__init__.py18
-rw-r--r--test/lib/ansible_test/_internal/cloud/azure.py7
-rw-r--r--test/lib/ansible_test/_internal/cloud/cloudscale.py6
-rw-r--r--test/lib/ansible_test/_internal/cloud/foreman.py2
-rw-r--r--test/lib/ansible_test/_internal/cloud/nios.py2
-rw-r--r--test/lib/ansible_test/_internal/cloud/openshift.py7
-rw-r--r--test/lib/ansible_test/_internal/core_ci.py31
-rw-r--r--test/lib/ansible_test/_internal/coverage/combine.py14
-rw-r--r--test/lib/ansible_test/_internal/coverage/html.py5
-rw-r--r--test/lib/ansible_test/_internal/coverage/report.py9
-rw-r--r--test/lib/ansible_test/_internal/coverage/xml.py8
-rw-r--r--test/lib/ansible_test/_internal/coverage_util.py8
-rw-r--r--test/lib/ansible_test/_internal/csharp_import_analysis.py8
-rw-r--r--test/lib/ansible_test/_internal/delegation.py4
-rw-r--r--test/lib/ansible_test/_internal/docker_util.py12
-rw-r--r--test/lib/ansible_test/_internal/encoding.py41
-rw-r--r--test/lib/ansible_test/_internal/env.py11
-rw-r--r--test/lib/ansible_test/_internal/executor.py29
-rw-r--r--test/lib/ansible_test/_internal/import_analysis.py33
-rw-r--r--test/lib/ansible_test/_internal/integration/__init__.py16
-rw-r--r--test/lib/ansible_test/_internal/io.py74
-rw-r--r--test/lib/ansible_test/_internal/metadata.py9
-rw-r--r--test/lib/ansible_test/_internal/powershell_import_analysis.py37
-rw-r--r--test/lib/ansible_test/_internal/provider/layout/__init__.py8
-rw-r--r--test/lib/ansible_test/_internal/provider/layout/collection.py2
-rw-r--r--test/lib/ansible_test/_internal/provider/source/git.py2
-rw-r--r--test/lib/ansible_test/_internal/provider/source/unversioned.py2
-rw-r--r--test/lib/ansible_test/_internal/sanity/__init__.py8
-rw-r--r--test/lib/ansible_test/_internal/sanity/ansible_doc.py1
-rw-r--r--test/lib/ansible_test/_internal/sanity/integration_aliases.py7
-rw-r--r--test/lib/ansible_test/_internal/target.py24
-rw-r--r--test/lib/ansible_test/_internal/test.py1
-rw-r--r--test/lib/ansible_test/_internal/types.py9
-rw-r--r--test/lib/ansible_test/_internal/util.py85
-rw-r--r--test/lib/ansible_test/_internal/util_common.py32
38 files changed, 358 insertions, 224 deletions
diff --git a/changelogs/fragments/ansible-test-refactor.yml b/changelogs/fragments/ansible-test-refactor.yml
new file mode 100644
index 0000000000..b7e69e6f6e
--- /dev/null
+++ b/changelogs/fragments/ansible-test-refactor.yml
@@ -0,0 +1,5 @@
+minor_changes:
+ - "ansible-test - Support writing compact JSON files instead of formatting and indenting the output."
+ - "ansible-test - Add a verbosity option for displaying warnings."
+ - "ansible-test - Refactor code to consolidate filesystem access and improve handling of encoding."
+ - "ansible-test - General code cleanup."
diff --git a/test/lib/ansible_test/_internal/classification.py b/test/lib/ansible_test/_internal/classification.py
index f7126ee475..d1a068a60e 100644
--- a/test/lib/ansible_test/_internal/classification.py
+++ b/test/lib/ansible_test/_internal/classification.py
@@ -733,6 +733,7 @@ class PathMapper:
if path.startswith('test/lib/ansible_test/config/'):
if name.startswith('cloud-config-'):
+ # noinspection PyTypeChecker
cloud_target = 'cloud/%s/' % name.split('-')[2].split('.')[0]
if cloud_target in self.integration_targets_by_alias:
diff --git a/test/lib/ansible_test/_internal/cli.py b/test/lib/ansible_test/_internal/cli.py
index 8bf5de2741..acf8d72272 100644
--- a/test/lib/ansible_test/_internal/cli.py
+++ b/test/lib/ansible_test/_internal/cli.py
@@ -822,6 +822,7 @@ def complete_target(prefix, parsed_args, **_):
return find_target_completion(parsed_args.targets, prefix)
+# noinspection PyUnusedLocal
def complete_remote(prefix, parsed_args, **_):
"""
:type prefix: unicode
@@ -835,6 +836,7 @@ def complete_remote(prefix, parsed_args, **_):
return [i for i in images if i.startswith(prefix)]
+# noinspection PyUnusedLocal
def complete_remote_shell(prefix, parsed_args, **_):
"""
:type prefix: unicode
@@ -852,6 +854,7 @@ def complete_remote_shell(prefix, parsed_args, **_):
return [i for i in images if i.startswith(prefix)]
+# noinspection PyUnusedLocal
def complete_docker(prefix, parsed_args, **_):
"""
:type prefix: unicode
@@ -911,6 +914,7 @@ def complete_network_testcase(prefix, parsed_args, **_):
return testcases
+# noinspection PyUnusedLocal
def complete_sanity_test(prefix, parsed_args, **_):
"""
:type prefix: unicode
diff --git a/test/lib/ansible_test/_internal/cloud/__init__.py b/test/lib/ansible_test/_internal/cloud/__init__.py
index bdc2bd8108..e1f1908436 100644
--- a/test/lib/ansible_test/_internal/cloud/__init__.py
+++ b/test/lib/ansible_test/_internal/cloud/__init__.py
@@ -14,6 +14,14 @@ import tempfile
from .. import types as t
+from ..encoding import (
+ to_bytes,
+)
+
+from ..io import (
+ read_text_file,
+)
+
from ..util import (
ApplicationError,
display,
@@ -21,7 +29,6 @@ from ..util import (
import_plugins,
load_plugins,
ABC,
- to_bytes,
ANSIBLE_TEST_CONFIG_ROOT,
)
@@ -365,11 +372,10 @@ class CloudProvider(CloudBase):
"""
:rtype: str
"""
- with open(self.config_template_path, 'r') as template_fd:
- lines = template_fd.read().splitlines()
- lines = [l for l in lines if not l.startswith('#')]
- config = '\n'.join(lines).strip() + '\n'
- return config
+ lines = read_text_file(self.config_template_path).splitlines()
+ lines = [line for line in lines if not line.startswith('#')]
+ config = '\n'.join(lines).strip() + '\n'
+ return config
@staticmethod
def _populate_config_template(template, values):
diff --git a/test/lib/ansible_test/_internal/cloud/azure.py b/test/lib/ansible_test/_internal/cloud/azure.py
index 9128237a9b..277fce76c1 100644
--- a/test/lib/ansible_test/_internal/cloud/azure.py
+++ b/test/lib/ansible_test/_internal/cloud/azure.py
@@ -4,6 +4,10 @@ __metaclass__ = type
import os
+from ..io import (
+ read_text_file,
+)
+
from ..util import (
ApplicationError,
display,
@@ -86,8 +90,7 @@ class AzureCloudProvider(CloudProvider):
response = {}
if os.path.isfile(self.SHERLOCK_CONFIG_PATH):
- with open(self.SHERLOCK_CONFIG_PATH, 'r') as sherlock_fd:
- sherlock_uri = sherlock_fd.readline().strip() + '&rgcount=2'
+ sherlock_uri = read_text_file(self.SHERLOCK_CONFIG_PATH).splitlines()[0].strip() + '&rgcount=2'
parts = urlparse(sherlock_uri)
query_string = parse_qs(parts.query)
diff --git a/test/lib/ansible_test/_internal/cloud/cloudscale.py b/test/lib/ansible_test/_internal/cloud/cloudscale.py
index 466ba420fd..8e5885b2d5 100644
--- a/test/lib/ansible_test/_internal/cloud/cloudscale.py
+++ b/test/lib/ansible_test/_internal/cloud/cloudscale.py
@@ -7,7 +7,7 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-from os.path import isfile
+import os
from . import (
CloudProvider,
@@ -34,7 +34,7 @@ class CloudscaleCloudProvider(CloudProvider):
:type targets: tuple[TestTarget]
:type exclude: list[str]
"""
- if isfile(self.config_static_path):
+ if os.path.isfile(self.config_static_path):
return
super(CloudscaleCloudProvider, self).filter(targets, exclude)
@@ -43,7 +43,7 @@ class CloudscaleCloudProvider(CloudProvider):
"""Setup the cloud resource before delegation and register a cleanup callback."""
super(CloudscaleCloudProvider, self).setup()
- if isfile(self.config_static_path):
+ if os.path.isfile(self.config_static_path):
display.info('Using existing %s cloud config: %s'
% (self.platform, self.config_static_path),
verbosity=1)
diff --git a/test/lib/ansible_test/_internal/cloud/foreman.py b/test/lib/ansible_test/_internal/cloud/foreman.py
index b421c4fd07..72e92d2a06 100644
--- a/test/lib/ansible_test/_internal/cloud/foreman.py
+++ b/test/lib/ansible_test/_internal/cloud/foreman.py
@@ -49,7 +49,7 @@ class ForemanProvider(CloudProvider):
"""
super(ForemanProvider, self).__init__(args)
- self.__container_from_env = os.getenv('ANSIBLE_FRMNSIM_CONTAINER')
+ self.__container_from_env = os.environ.get('ANSIBLE_FRMNSIM_CONTAINER')
"""Overrides target container, might be used for development.
Use ANSIBLE_FRMNSIM_CONTAINER=whatever_you_want if you want
diff --git a/test/lib/ansible_test/_internal/cloud/nios.py b/test/lib/ansible_test/_internal/cloud/nios.py
index 91456ae501..c9dae517fd 100644
--- a/test/lib/ansible_test/_internal/cloud/nios.py
+++ b/test/lib/ansible_test/_internal/cloud/nios.py
@@ -49,7 +49,7 @@ class NiosProvider(CloudProvider):
"""
super(NiosProvider, self).__init__(args)
- self.__container_from_env = os.getenv('ANSIBLE_NIOSSIM_CONTAINER')
+ self.__container_from_env = os.environ.get('ANSIBLE_NIOSSIM_CONTAINER')
"""Overrides target container, might be used for development.
Use ANSIBLE_NIOSSIM_CONTAINER=whatever_you_want if you want
diff --git a/test/lib/ansible_test/_internal/cloud/openshift.py b/test/lib/ansible_test/_internal/cloud/openshift.py
index 15d90cd1ad..a616487a89 100644
--- a/test/lib/ansible_test/_internal/cloud/openshift.py
+++ b/test/lib/ansible_test/_internal/cloud/openshift.py
@@ -13,6 +13,10 @@ from . import (
CloudEnvironmentConfig,
)
+from ..io import (
+ read_text_file,
+)
+
from ..util import (
find_executable,
ApplicationError,
@@ -106,8 +110,7 @@ class OpenShiftCloudProvider(CloudProvider):
def _setup_static(self):
"""Configure OpenShift tests for use with static configuration."""
- with open(self.config_static_path, 'r') as config_fd:
- config = config_fd.read()
+ config = read_text_file(self.config_static_path)
match = re.search(r'^ *server: (?P<server>.*)$', config, flags=re.MULTILINE)
diff --git a/test/lib/ansible_test/_internal/core_ci.py b/test/lib/ansible_test/_internal/core_ci.py
index 163931ce69..cac1f0bbfd 100644
--- a/test/lib/ansible_test/_internal/core_ci.py
+++ b/test/lib/ansible_test/_internal/core_ci.py
@@ -18,18 +18,22 @@ from .http import (
HttpError,
)
+from .io import (
+ make_dirs,
+ read_text_file,
+ write_json_file,
+ write_text_file,
+)
+
from .util import (
ApplicationError,
- make_dirs,
display,
is_shippable,
- to_text,
ANSIBLE_TEST_DATA_ROOT,
)
from .util_common import (
run_command,
- write_json_file,
ResultType,
)
@@ -233,8 +237,7 @@ class AnsibleCoreCI:
def start_remote(self):
"""Start instance for remote development/testing."""
- with open(self.ci_key, 'r') as key_fd:
- auth_key = key_fd.read().strip()
+ auth_key = read_text_file(self.ci_key).strip()
return self._start(dict(
remote=dict(
@@ -367,8 +370,7 @@ class AnsibleCoreCI:
display.info('Initializing new %s/%s instance %s.' % (self.platform, self.version, self.instance_id), verbosity=1)
if self.platform == 'windows':
- with open(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'setup', 'ConfigureRemotingForAnsible.ps1'), 'rb') as winrm_config_fd:
- winrm_config = to_text(winrm_config_fd.read())
+ winrm_config = read_text_file(os.path.join(ANSIBLE_TEST_DATA_ROOT, 'setup', 'ConfigureRemotingForAnsible.ps1'))
else:
winrm_config = None
@@ -470,8 +472,7 @@ class AnsibleCoreCI:
def _load(self):
"""Load instance information."""
try:
- with open(self.path, 'r') as instance_fd:
- data = instance_fd.read()
+ data = read_text_file(self.path)
except IOError as ex:
if ex.errno != errno.ENOENT:
raise
@@ -597,8 +598,7 @@ class SshKey:
if args.explain:
self.pub_contents = None
else:
- with open(self.pub, 'r') as pub_fd:
- self.pub_contents = pub_fd.read().strip()
+ self.pub_contents = read_text_file(self.pub).strip()
def get_in_tree_key_pair_paths(self): # type: () -> t.Optional[t.Tuple[str, str]]
"""Return the ansible-test SSH key pair paths from the content tree."""
@@ -643,11 +643,10 @@ class SshKey:
run_command(args, ['ssh-keygen', '-m', 'PEM', '-q', '-t', 'rsa', '-N', '', '-f', key])
# newer ssh-keygen PEM output (such as on RHEL 8.1) is not recognized by paramiko
- with open(key, 'r+') as key_fd:
- key_contents = key_fd.read()
- key_contents = re.sub(r'(BEGIN|END) PRIVATE KEY', r'\1 RSA PRIVATE KEY', key_contents)
- key_fd.seek(0)
- key_fd.write(key_contents)
+ key_contents = read_text_file(key)
+ key_contents = re.sub(r'(BEGIN|END) PRIVATE KEY', r'\1 RSA PRIVATE KEY', key_contents)
+
+ write_text_file(key, key_contents)
return key, pub
diff --git a/test/lib/ansible_test/_internal/coverage/combine.py b/test/lib/ansible_test/_internal/coverage/combine.py
index 9d6d82d2a7..a07a4dd6de 100644
--- a/test/lib/ansible_test/_internal/coverage/combine.py
+++ b/test/lib/ansible_test/_internal/coverage/combine.py
@@ -2,7 +2,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
import os
import re
@@ -12,9 +11,13 @@ from ..target import (
walk_powershell_targets,
)
+from ..io import (
+ read_json_file,
+ read_text_file,
+)
+
from ..util import (
display,
- to_text,
)
from ..util_common import (
@@ -191,8 +194,7 @@ def _command_coverage_combine_powershell(args):
continue
try:
- with open(coverage_file, 'rb') as original_fd:
- coverage_run = json.loads(to_text(original_fd.read(), errors='replace'))
+ coverage_run = read_json_file(coverage_file)
except Exception as ex: # pylint: disable=locally-disabled, broad-except
display.error(u'%s' % ex)
continue
@@ -275,8 +277,7 @@ def _get_coverage_targets(args, walk_func):
for target in walk_func(include_symlinks=False):
target_path = os.path.abspath(target.path)
- with open(target_path, 'r') as target_fd:
- target_lines = len(target_fd.read().splitlines())
+ target_lines = len(read_text_file(target_path).splitlines())
sources.append((target_path, target_lines))
@@ -327,6 +328,7 @@ def get_coverage_group(args, coverage_file):
"""
parts = os.path.basename(coverage_file).split('=', 4)
+ # noinspection PyTypeChecker
if len(parts) != 5 or not parts[4].startswith('coverage.'):
return None
diff --git a/test/lib/ansible_test/_internal/coverage/html.py b/test/lib/ansible_test/_internal/coverage/html.py
index 4bd87e9d7a..63956a1966 100644
--- a/test/lib/ansible_test/_internal/coverage/html.py
+++ b/test/lib/ansible_test/_internal/coverage/html.py
@@ -4,9 +4,12 @@ __metaclass__ = type
import os
+from ..io import (
+ make_dirs,
+)
+
from ..util import (
display,
- make_dirs,
)
from ..util_common import (
diff --git a/test/lib/ansible_test/_internal/coverage/report.py b/test/lib/ansible_test/_internal/coverage/report.py
index e9fce6055c..24efa63717 100644
--- a/test/lib/ansible_test/_internal/coverage/report.py
+++ b/test/lib/ansible_test/_internal/coverage/report.py
@@ -2,12 +2,14 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
import os
+from ..io import (
+ read_json_file,
+)
+
from ..util import (
display,
- to_text,
)
from ..data import (
@@ -57,8 +59,7 @@ def _generate_powershell_output_report(args, coverage_file):
:type coverage_file: str
:rtype: str
"""
- with open(coverage_file, 'rb') as coverage_fd:
- coverage_info = json.loads(to_text(coverage_fd.read()))
+ coverage_info = read_json_file(coverage_file)
root_path = data_context().content.root + '/'
diff --git a/test/lib/ansible_test/_internal/coverage/xml.py b/test/lib/ansible_test/_internal/coverage/xml.py
index 5a5875e99b..94b5abc595 100644
--- a/test/lib/ansible_test/_internal/coverage/xml.py
+++ b/test/lib/ansible_test/_internal/coverage/xml.py
@@ -2,7 +2,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
import os
import time
@@ -17,9 +16,9 @@ from xml.dom import (
minidom,
)
-from ..util import (
- to_text,
+from ..io import (
make_dirs,
+ read_json_file,
)
from ..util_common import (
@@ -72,8 +71,7 @@ def _generate_powershell_xml(coverage_file):
:type coverage_file: str
:rtype: Element
"""
- with open(coverage_file, 'rb') as coverage_fd:
- coverage_info = json.loads(to_text(coverage_fd.read()))
+ coverage_info = read_json_file(coverage_file)
content_root = data_context().content.root
is_ansible = data_context().content.is_ansible
diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py
index 6d70ba6ad0..e5434231f1 100644
--- a/test/lib/ansible_test/_internal/coverage_util.py
+++ b/test/lib/ansible_test/_internal/coverage_util.py
@@ -12,15 +12,15 @@ from .config import (
TestConfig,
)
+from .io import (
+ write_text_file,
+)
+
from .util import (
COVERAGE_CONFIG_NAME,
remove_tree,
)
-from .util_common import (
- write_text_file,
-)
-
from .data import (
data_context,
)
diff --git a/test/lib/ansible_test/_internal/csharp_import_analysis.py b/test/lib/ansible_test/_internal/csharp_import_analysis.py
index a753bee599..cc99d761fa 100644
--- a/test/lib/ansible_test/_internal/csharp_import_analysis.py
+++ b/test/lib/ansible_test/_internal/csharp_import_analysis.py
@@ -5,6 +5,10 @@ __metaclass__ = type
import os
import re
+from .io import (
+ open_text_file,
+)
+
from .util import (
display,
)
@@ -53,7 +57,7 @@ def get_csharp_module_utils_name(path): # type: (str) -> str
else:
prefix = ''
- name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.sep, '.')
+ name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.path.sep, '.')
return name
@@ -80,7 +84,7 @@ def extract_csharp_module_utils_imports(path, module_utils, is_pure_csharp):
else:
pattern = re.compile(r'(?i)^#\s*ansiblerequires\s+-csharputil\s+((?:Ansible|ansible.collections)\..+)')
- with open(path, 'r') as module_file:
+ with open_text_file(path) as module_file:
for line_number, line in enumerate(module_file, 1):
match = re.search(pattern, line)
diff --git a/test/lib/ansible_test/_internal/delegation.py b/test/lib/ansible_test/_internal/delegation.py
index 009b121315..a139efd66d 100644
--- a/test/lib/ansible_test/_internal/delegation.py
+++ b/test/lib/ansible_test/_internal/delegation.py
@@ -44,14 +44,12 @@ from .manage_ci import (
from .util import (
ApplicationError,
common_environment,
- pass_vars,
display,
ANSIBLE_BIN_PATH,
ANSIBLE_TEST_DATA_ROOT,
ANSIBLE_LIB_ROOT,
ANSIBLE_TEST_ROOT,
tempdir,
- make_dirs,
)
from .util_common import (
@@ -203,7 +201,7 @@ def delegate_venv(args, # type: EnvironmentConfig
os.symlink(ANSIBLE_TEST_ROOT, os.path.join(library_path, 'ansible_test'))
env.update(
- PATH=inject_path + os.pathsep + env['PATH'],
+ PATH=inject_path + os.path.pathsep + env['PATH'],
PYTHONPATH=library_path,
)
diff --git a/test/lib/ansible_test/_internal/docker_util.py b/test/lib/ansible_test/_internal/docker_util.py
index ebee766f3a..15fe484b09 100644
--- a/test/lib/ansible_test/_internal/docker_util.py
+++ b/test/lib/ansible_test/_internal/docker_util.py
@@ -6,6 +6,11 @@ import json
import os
import time
+from .io import (
+ open_binary_file,
+ read_text_file,
+)
+
from .util import (
ApplicationError,
common_environment,
@@ -41,8 +46,7 @@ def get_docker_container_id():
if not os.path.exists(path):
return None
- with open(path) as cgroup_fd:
- contents = cgroup_fd.read()
+ contents = read_text_file(path)
paths = [line.split(':')[2] for line in contents.splitlines()]
container_ids = set(path.split('/')[2] for path in paths if path.startswith('/docker/'))
@@ -110,7 +114,7 @@ def docker_put(args, container_id, src, dst):
:type dst: str
"""
# avoid 'docker cp' due to a bug which causes 'docker rm' to fail
- with open(src, 'rb') as src_fd:
+ with open_binary_file(src) as src_fd:
docker_exec(args, container_id, ['dd', 'of=%s' % dst, 'bs=%s' % BUFFER_SIZE],
options=['-i'], stdin=src_fd, capture=True)
@@ -123,7 +127,7 @@ def docker_get(args, container_id, src, dst):
:type dst: str
"""
# avoid 'docker cp' due to a bug which causes 'docker rm' to fail
- with open(dst, 'wb') as dst_fd:
+ with open_binary_file(dst, 'wb') as dst_fd:
docker_exec(args, container_id, ['dd', 'if=%s' % src, 'bs=%s' % BUFFER_SIZE],
options=['-i'], stdout=dst_fd, capture=True)
diff --git a/test/lib/ansible_test/_internal/encoding.py b/test/lib/ansible_test/_internal/encoding.py
new file mode 100644
index 0000000000..8e014794c7
--- /dev/null
+++ b/test/lib/ansible_test/_internal/encoding.py
@@ -0,0 +1,41 @@
+"""Functions for encoding and decoding strings."""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from . import types as t
+
+ENCODING = 'utf-8'
+
+Text = type(u'')
+
+
+def to_optional_bytes(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[bytes]
+ """Return the given value as bytes encoded using UTF-8 if not already bytes, or None if the value is None."""
+ return None if value is None else to_bytes(value, errors)
+
+
+def to_optional_text(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[t.Text]
+ """Return the given value as text decoded using UTF-8 if not already text, or None if the value is None."""
+ return None if value is None else to_text(value, errors)
+
+
+def to_bytes(value, errors='strict'): # type: (t.AnyStr, str) -> bytes
+ """Return the given value as bytes encoded using UTF-8 if not already bytes."""
+ if isinstance(value, bytes):
+ return value
+
+ if isinstance(value, Text):
+ return value.encode(ENCODING, errors)
+
+ raise Exception('value is not bytes or text: %s' % type(value))
+
+
+def to_text(value, errors='strict'): # type: (t.AnyStr, str) -> t.Text
+ """Return the given value as text decoded using UTF-8 if not already text."""
+ if isinstance(value, bytes):
+ return value.decode(ENCODING, errors)
+
+ if isinstance(value, Text):
+ return value
+
+ raise Exception('value is not bytes or text: %s' % type(value))
diff --git a/test/lib/ansible_test/_internal/env.py b/test/lib/ansible_test/_internal/env.py
index cd7b176f94..31d61a8faf 100644
--- a/test/lib/ansible_test/_internal/env.py
+++ b/test/lib/ansible_test/_internal/env.py
@@ -3,7 +3,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import datetime
-import json
import functools
import os
import platform
@@ -17,6 +16,11 @@ from .config import (
TestConfig,
)
+from .io import (
+ write_json_file,
+ read_json_file,
+)
+
from .util import (
display,
find_executable,
@@ -28,7 +32,6 @@ from .util import (
from .util_common import (
write_json_test_results,
- write_json_file,
ResultType,
)
@@ -164,9 +167,7 @@ def get_timeout():
if not os.path.exists(TIMEOUT_PATH):
return None
- with open(TIMEOUT_PATH, 'r') as timeout_fd:
- data = json.load(timeout_fd)
-
+ data = read_json_file(TIMEOUT_PATH)
data['deadline'] = datetime.datetime.strptime(data['deadline'], '%Y-%m-%dT%H:%M:%SZ')
return data
diff --git a/test/lib/ansible_test/_internal/executor.py b/test/lib/ansible_test/_internal/executor.py
index ef54d6f6d1..b1e18f328f 100644
--- a/test/lib/ansible_test/_internal/executor.py
+++ b/test/lib/ansible_test/_internal/executor.py
@@ -40,13 +40,20 @@ from .cloud import (
CloudEnvironmentConfig,
)
+from .io import (
+ make_dirs,
+ open_text_file,
+ read_binary_file,
+ read_text_file,
+ write_text_file,
+)
+
from .util import (
ApplicationWarning,
ApplicationError,
SubprocessError,
display,
remove_tree,
- make_dirs,
is_shippable,
is_binary_file,
find_executable,
@@ -71,7 +78,6 @@ from .util_common import (
intercept_command,
named_temporary_file,
run_command,
- write_text_file,
write_json_test_results,
ResultType,
handle_layout_messages,
@@ -1200,12 +1206,12 @@ def inject_httptester(args):
"""
comment = ' # ansible-test httptester\n'
append_lines = ['127.0.0.1 %s%s' % (host, comment) for host in HTTPTESTER_HOSTS]
+ hosts_path = '/etc/hosts'
- with open('/etc/hosts', 'r+') as hosts_fd:
- original_lines = hosts_fd.readlines()
+ original_lines = read_text_file(hosts_path).splitlines(True)
- if not any(line.endswith(comment) for line in original_lines):
- hosts_fd.writelines(append_lines)
+ if not any(line.endswith(comment) for line in original_lines):
+ write_text_file(hosts_path, ''.join(original_lines + append_lines))
# determine which forwarding mechanism to use
pfctl = find_executable('pfctl', required=False)
@@ -1510,8 +1516,7 @@ def detect_changes(args):
elif args.changed_from or args.changed_path:
paths = args.changed_path or []
if args.changed_from:
- with open(args.changed_from, 'r') as changes_fd:
- paths += changes_fd.read().splitlines()
+ paths += read_text_file(args.changed_from).splitlines()
elif args.changed:
paths = detect_changes_local(args)
else:
@@ -1599,8 +1604,7 @@ def detect_changes_local(args):
args.metadata.changes[path] = ((0, 0),)
continue
- with open(path, 'r') as source_fd:
- line_count = len(source_fd.read().splitlines())
+ line_count = len(read_text_file(path).splitlines())
args.metadata.changes[path] = ((1, line_count),)
@@ -2056,7 +2060,7 @@ class EnvironmentDescription:
:type path: str
:rtype: str
"""
- with open(path) as script_fd:
+ with open_text_file(path) as script_fd:
return script_fd.readline().strip()
@staticmethod
@@ -2070,8 +2074,7 @@ class EnvironmentDescription:
file_hash = hashlib.md5()
- with open(path, 'rb') as file_fd:
- file_hash.update(file_fd.read())
+ file_hash.update(read_binary_file(path))
return file_hash.hexdigest()
diff --git a/test/lib/ansible_test/_internal/import_analysis.py b/test/lib/ansible_test/_internal/import_analysis.py
index fd52173861..f1cb66353e 100644
--- a/test/lib/ansible_test/_internal/import_analysis.py
+++ b/test/lib/ansible_test/_internal/import_analysis.py
@@ -7,6 +7,10 @@ import os
from . import types as t
+from .io import (
+ read_text_file,
+)
+
from .util import (
display,
ApplicationError,
@@ -130,7 +134,7 @@ def get_python_module_utils_name(path): # type: (str) -> str
if path.endswith('/__init__.py'):
path = os.path.dirname(path)
- name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.sep, '.')
+ name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.path.sep, '.')
return name
@@ -161,20 +165,19 @@ def extract_python_module_utils_imports(path, module_utils):
:type module_utils: set[str]
:rtype: set[str]
"""
- with open(path, 'r') as module_fd:
- code = module_fd.read()
-
- try:
- tree = ast.parse(code)
- except SyntaxError as ex:
- # Treat this error as a warning so tests can be executed as best as possible.
- # The compile test will detect and report this syntax error.
- display.warning('%s:%s Syntax error extracting module_utils imports: %s' % (path, ex.lineno, ex.msg))
- return set()
-
- finder = ModuleUtilFinder(path, module_utils)
- finder.visit(tree)
- return finder.imports
+ code = read_text_file(path)
+
+ try:
+ tree = ast.parse(code)
+ except SyntaxError as ex:
+ # Treat this error as a warning so tests can be executed as best as possible.
+ # The compile test will detect and report this syntax error.
+ display.warning('%s:%s Syntax error extracting module_utils imports: %s' % (path, ex.lineno, ex.msg))
+ return set()
+
+ finder = ModuleUtilFinder(path, module_utils)
+ finder.visit(tree)
+ return finder.imports
class ModuleUtilFinder(ast.NodeVisitor):
diff --git a/test/lib/ansible_test/_internal/integration/__init__.py b/test/lib/ansible_test/_internal/integration/__init__.py
index c09a656e6e..a83ef7ace0 100644
--- a/test/lib/ansible_test/_internal/integration/__init__.py
+++ b/test/lib/ansible_test/_internal/integration/__init__.py
@@ -10,6 +10,10 @@ import tempfile
from .. import types as t
+from ..encoding import (
+ to_bytes,
+)
+
from ..target import (
analyze_integration_target_dependencies,
walk_integration_targets,
@@ -22,20 +26,23 @@ from ..config import (
WindowsIntegrationConfig,
)
+from ..io import (
+ make_dirs,
+ write_text_file,
+ read_text_file,
+)
+
from ..util import (
ApplicationError,
display,
- make_dirs,
COVERAGE_CONFIG_NAME,
MODE_DIRECTORY,
MODE_DIRECTORY_WRITE,
MODE_FILE,
- to_bytes,
)
from ..util_common import (
named_temporary_file,
- write_text_file,
ResultType,
)
@@ -136,8 +143,7 @@ def check_inventory(args, inventory_path): # type: (IntegrationConfig, str) ->
"""Check the given inventory for issues."""
if args.docker or args.remote:
if os.path.exists(inventory_path):
- with open(inventory_path) as inventory_file:
- inventory = inventory_file.read()
+ inventory = read_text_file(inventory_path)
if 'ansible_ssh_private_key_file' in inventory:
display.warning('Use of "ansible_ssh_private_key_file" in inventory with the --docker or --remote option is unsupported and will likely fail.')
diff --git a/test/lib/ansible_test/_internal/io.py b/test/lib/ansible_test/_internal/io.py
new file mode 100644
index 0000000000..8daa4dda01
--- /dev/null
+++ b/test/lib/ansible_test/_internal/io.py
@@ -0,0 +1,74 @@
+"""Functions for disk IO."""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import errno
+import io
+import json
+import os
+
+from . import types as t
+
+from .encoding import (
+ ENCODING,
+ to_bytes,
+ to_text,
+)
+
+
+def read_json_file(path): # type: (t.AnyStr) -> t.Any
+ """Parse and return the json content from the specified path."""
+ return json.loads(read_text_file(path))
+
+
+def read_text_file(path): # type: (t.AnyStr) -> t.Text
+ """Return the contents of the specified path as text."""
+ return to_text(read_binary_file(path))
+
+
+def read_binary_file(path): # type: (t.AnyStr) -> bytes
+ """Return the contents of the specified path as bytes."""
+ with open_binary_file(path) as file:
+ return file.read()
+
+
+def make_dirs(path): # type: (str) -> None
+ """Create a directory at path, including any necessary parent directories."""
+ try:
+ os.makedirs(to_bytes(path))
+ except OSError as ex:
+ if ex.errno != errno.EEXIST:
+ raise
+
+
+def write_json_file(path, content, create_directories=False, formatted=True): # type: (str, t.Union[t.List[t.Any], t.Dict[str, t.Any]], bool, bool) -> None
+ """Write the given json content to the specified path, optionally creating missing directories."""
+ text_content = json.dumps(content, sort_keys=formatted, indent=4 if formatted else None, separators=(', ', ': ') if formatted else (',', ':')) + '\n'
+ write_text_file(path, text_content, create_directories=create_directories)
+
+
+def write_text_file(path, content, create_directories=False): # type: (str, str, bool) -> None
+ """Write the given text content to the specified path, optionally creating missing directories."""
+ if create_directories:
+ make_dirs(os.path.dirname(path))
+
+ with open_binary_file(path, 'wb') as file:
+ file.write(to_bytes(content))
+
+
+def open_text_file(path, mode='r'): # type: (str, str) -> t.TextIO
+ """Open the given path for text access."""
+ if 'b' in mode:
+ raise Exception('mode cannot include "b" for text files: %s' % mode)
+
+ # noinspection PyTypeChecker
+ return io.open(to_bytes(path), mode, encoding=ENCODING)
+
+
+def open_binary_file(path, mode='rb'): # type: (str, str) -> t.BinaryIO
+ """Open the given path for binary access."""
+ if 'b' not in mode:
+ raise Exception('mode must include "b" for binary files: %s' % mode)
+
+ # noinspection PyTypeChecker
+ return io.open(to_bytes(path), mode)
diff --git a/test/lib/ansible_test/_internal/metadata.py b/test/lib/ansible_test/_internal/metadata.py
index 2d1ca526e6..389836e634 100644
--- a/test/lib/ansible_test/_internal/metadata.py
+++ b/test/lib/ansible_test/_internal/metadata.py
@@ -2,8 +2,6 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
-import json
-
from . import types as t
from .util import (
@@ -11,8 +9,9 @@ from .util import (
is_shippable,
)
-from .util_common import (
+from .io import (
write_json_file,
+ read_json_file,
)
from .diff import (
@@ -84,9 +83,7 @@ class Metadata:
:type path: str
:rtype: Metadata
"""
- with open(path, 'r') as data_fd:
- data = json.load(data_fd)
-
+ data = read_json_file(path)
return Metadata.from_dict(data)
@staticmethod
diff --git a/test/lib/ansible_test/_internal/powershell_import_analysis.py b/test/lib/ansible_test/_internal/powershell_import_analysis.py
index 9c4019f533..4c9d7b8e1a 100644
--- a/test/lib/ansible_test/_internal/powershell_import_analysis.py
+++ b/test/lib/ansible_test/_internal/powershell_import_analysis.py
@@ -5,6 +5,10 @@ __metaclass__ = type
import os
import re
+from .io import (
+ read_text_file,
+)
+
from .util import (
display,
)
@@ -49,7 +53,7 @@ def get_powershell_module_utils_name(path): # type: (str) -> str
else:
prefix = ''
- name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.sep, '.')
+ name = prefix + os.path.splitext(os.path.relpath(path, base_path))[0].replace(os.path.sep, '.')
return name
@@ -71,27 +75,26 @@ def extract_powershell_module_utils_imports(path, module_utils):
"""
imports = set()
- with open(path, 'r') as module_fd:
- code = module_fd.read()
+ code = read_text_file(path)
- if '# POWERSHELL_COMMON' in code:
- imports.add('Ansible.ModuleUtils.Legacy')
+ if '# POWERSHELL_COMMON' in code:
+ imports.add('Ansible.ModuleUtils.Legacy')
- lines = code.splitlines()
- line_number = 0
+ lines = code.splitlines()
+ line_number = 0
- for line in lines:
- line_number += 1
- match = re.search(r'(?i)^#\s*(?:requires\s+-module(?:s?)|ansiblerequires\s+-powershell)\s*((?:Ansible|ansible_collections)\..+)', line)
+ for line in lines:
+ line_number += 1
+ match = re.search(r'(?i)^#\s*(?:requires\s+-module(?:s?)|ansiblerequires\s+-powershell)\s*((?:Ansible|ansible_collections)\..+)', line)
- if not match:
- continue
+ if not match:
+ continue
- import_name = match.group(1)
+ import_name = match.group(1)
- if import_name in module_utils:
- imports.add(import_name)
- else:
- display.warning('%s:%d Invalid module_utils import: %s' % (path, line_number, import_name))
+ if import_name in module_utils:
+ imports.add(import_name)
+ else:
+ display.warning('%s:%d Invalid module_utils import: %s' % (path, line_number, import_name))
return imports
diff --git a/test/lib/ansible_test/_internal/provider/layout/__init__.py b/test/lib/ansible_test/_internal/provider/layout/__init__.py
index 502b385982..03d596fcef 100644
--- a/test/lib/ansible_test/_internal/provider/layout/__init__.py
+++ b/test/lib/ansible_test/_internal/provider/layout/__init__.py
@@ -44,7 +44,7 @@ class Layout:
else:
tree = self.__files_tree
- parts = directory.rstrip(os.sep).split(os.sep)
+ parts = directory.rstrip(os.path.sep).split(os.path.sep)
item = get_tree_item(tree, parts)
if not item:
@@ -63,13 +63,13 @@ class Layout:
def get_dirs(self, directory): # type: (str) -> t.List[str]
"""Return a list directory paths found directly under the given directory."""
- parts = directory.rstrip(os.sep).split(os.sep)
+ parts = directory.rstrip(os.path.sep).split(os.path.sep)
item = get_tree_item(self.__files_tree, parts)
return [os.path.join(directory, key) for key in item[0].keys()] if item else []
def get_files(self, directory): # type: (str) -> t.List[str]
"""Return a list of file paths found directly under the given directory."""
- parts = directory.rstrip(os.sep).split(os.sep)
+ parts = directory.rstrip(os.path.sep).split(os.path.sep)
item = get_tree_item(self.__files_tree, parts)
return item[1] if item else []
@@ -205,7 +205,7 @@ def paths_to_tree(paths): # type: (t.List[str]) -> t.Tuple(t.Dict[str, t.Any],
tree = {}, []
for path in paths:
- parts = path.split(os.sep)
+ parts = path.split(os.path.sep)
root = tree
for part in parts[:-1]:
diff --git a/test/lib/ansible_test/_internal/provider/layout/collection.py b/test/lib/ansible_test/_internal/provider/layout/collection.py
index 7740b99644..ffad29f259 100644
--- a/test/lib/ansible_test/_internal/provider/layout/collection.py
+++ b/test/lib/ansible_test/_internal/provider/layout/collection.py
@@ -33,7 +33,7 @@ class CollectionLayout(LayoutProvider):
collection_root = os.path.dirname(os.path.dirname(root))
collection_dir = os.path.relpath(root, collection_root)
- collection_namespace, collection_name = collection_dir.split(os.sep)
+ collection_namespace, collection_name = collection_dir.split(os.path.sep)
collection_root = os.path.dirname(collection_root)
diff --git a/test/lib/ansible_test/_internal/provider/source/git.py b/test/lib/ansible_test/_internal/provider/source/git.py
index 7ad2b97fa4..081fbc44e2 100644
--- a/test/lib/ansible_test/_internal/provider/source/git.py
+++ b/test/lib/ansible_test/_internal/provider/source/git.py
@@ -10,7 +10,7 @@ from ...git import (
Git,
)
-from ...util import (
+from ...encoding import (
to_bytes,
)
diff --git a/test/lib/ansible_test/_internal/provider/source/unversioned.py b/test/lib/ansible_test/_internal/provider/source/unversioned.py
index 8e7d582fad..6019728083 100644
--- a/test/lib/ansible_test/_internal/provider/source/unversioned.py
+++ b/test/lib/ansible_test/_internal/provider/source/unversioned.py
@@ -10,7 +10,7 @@ from ...constants import (
TIMEOUT_PATH,
)
-from ...util import (
+from ...encoding import (
to_bytes,
)
diff --git a/test/lib/ansible_test/_internal/sanity/__init__.py b/test/lib/ansible_test/_internal/sanity/__init__.py
index 4e39a89b50..fce497881e 100644
--- a/test/lib/ansible_test/_internal/sanity/__init__.py
+++ b/test/lib/ansible_test/_internal/sanity/__init__.py
@@ -4,13 +4,16 @@ __metaclass__ = type
import abc
import glob
-import json
import os
import re
import collections
from .. import types as t
+from ..io import (
+ read_json_file,
+)
+
from ..util import (
ApplicationError,
SubprocessError,
@@ -669,8 +672,7 @@ class SanityCodeSmellTest(SanityTest):
self.config = None
if self.config_path:
- with open(self.config_path, 'r') as config_fd:
- self.config = json.load(config_fd)
+ self.config = read_json_file(self.config_path)
if self.config:
self.enabled = not self.config.get('disabled')
diff --git a/test/lib/ansible_test/_internal/sanity/ansible_doc.py b/test/lib/ansible_test/_internal/sanity/ansible_doc.py
index 13427e8a9d..7c19bcc006 100644
--- a/test/lib/ansible_test/_internal/sanity/ansible_doc.py
+++ b/test/lib/ansible_test/_internal/sanity/ansible_doc.py
@@ -12,7 +12,6 @@ from ..sanity import (
SanitySingleVersion,
SanityFailure,
SanitySuccess,
- SanityMessage,
)
from ..target import (
diff --git a/test/lib/ansible_test/_internal/sanity/integration_aliases.py b/test/lib/ansible_test/_internal/sanity/integration_aliases.py
index 4677c66329..9f30f612a6 100644
--- a/test/lib/ansible_test/_internal/sanity/integration_aliases.py
+++ b/test/lib/ansible_test/_internal/sanity/integration_aliases.py
@@ -32,6 +32,10 @@ from ..cloud import (
get_cloud_platforms,
)
+from ..io import (
+ read_text_file,
+)
+
from ..util import (
display,
)
@@ -108,8 +112,7 @@ class IntegrationAliasesTest(SanityVersionNeutral):
:rtype: list[str]
"""
if not self._shippable_yml_lines:
- with open(self.SHIPPABLE_YML, 'r') as shippable_yml_fd:
- self._shippable_yml_lines = shippable_yml_fd.read().splitlines()
+ self._shippable_yml_lines = read_text_file(self.SHIPPABLE_YML).splitlines()
return self._shippable_yml_lines
diff --git a/test/lib/ansible_test/_internal/target.py b/test/lib/ansible_test/_internal/target.py
index cdbf86406b..fa7526cb63 100644
--- a/test/lib/ansible_test/_internal/target.py
+++ b/test/lib/ansible_test/_internal/target.py
@@ -10,13 +10,19 @@ import abc
from . import types as t
+from .encoding import (
+ to_bytes,
+)
+
+from .io import (
+ read_text_file,
+)
+
from .util import (
ApplicationError,
display,
read_lines_without_comments,
is_subdir,
- to_text,
- to_bytes,
)
from .data import (
@@ -291,8 +297,7 @@ def load_integration_prefixes():
for file_path in file_paths:
prefix = os.path.splitext(file_path)[1][1:]
- with open(file_path, 'r') as prefix_fd:
- prefixes.update(dict((k, prefix) for k in prefix_fd.read().splitlines()))
+ prefixes.update(dict((k, prefix) for k in read_text_file(file_path).splitlines()))
return prefixes
@@ -398,12 +403,11 @@ def analyze_integration_target_dependencies(integration_targets):
for meta_path in meta_paths:
if os.path.exists(meta_path):
- with open(meta_path, 'rb') as meta_fd:
- # try and decode the file as a utf-8 string, skip if it contains invalid chars (binary file)
- try:
- meta_lines = to_text(meta_fd.read()).splitlines()
- except UnicodeDecodeError:
- continue
+ # try and decode the file as a utf-8 string, skip if it contains invalid chars (binary file)
+ try:
+ meta_lines = read_text_file(meta_path).splitlines()
+ except UnicodeDecodeError:
+ continue
for meta_line in meta_lines:
if re.search(r'^ *#.*$', meta_line):
diff --git a/test/lib/ansible_test/_internal/test.py b/test/lib/ansible_test/_internal/test.py
index e1ef4f3bf8..5fa5eb5504 100644
--- a/test/lib/ansible_test/_internal/test.py
+++ b/test/lib/ansible_test/_internal/test.py
@@ -3,7 +3,6 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import datetime
-import os
import re
from . import types as t
diff --git a/test/lib/ansible_test/_internal/types.py b/test/lib/ansible_test/_internal/types.py
index dfb2bbaf74..46ef70668e 100644
--- a/test/lib/ansible_test/_internal/types.py
+++ b/test/lib/ansible_test/_internal/types.py
@@ -2,20 +2,29 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
+TYPE_CHECKING = False
+
try:
from typing import (
Any,
AnyStr,
+ BinaryIO,
Callable,
Dict,
FrozenSet,
+ Generator,
+ IO,
Iterable,
+ Iterator,
List,
Optional,
+ Pattern,
Set,
Text,
+ TextIO,
Tuple,
Type,
+ TYPE_CHECKING,
TypeVar,
Union,
)
diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py
index 72058cb997..2b0218fb38 100644
--- a/test/lib/ansible_test/_internal/util.py
+++ b/test/lib/ansible_test/_internal/util.py
@@ -45,6 +45,17 @@ except ImportError:
from . import types as t
+from .encoding import (
+ to_bytes,
+ to_optional_bytes,
+ to_optional_text,
+)
+
+from .io import (
+ open_binary_file,
+ read_text_file,
+)
+
try:
C = t.TypeVar('C')
except AttributeError:
@@ -95,10 +106,6 @@ MODE_FILE_WRITE = MODE_FILE | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH
MODE_DIRECTORY = MODE_READ | stat.S_IWUSR | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
MODE_DIRECTORY_WRITE = MODE_DIRECTORY | stat.S_IWGRP | stat.S_IWOTH
-ENCODING = 'utf-8'
-
-Text = type(u'')
-
REMOTE_ONLY_PYTHON_VERSIONS = (
'2.6',
)
@@ -113,38 +120,6 @@ SUPPORTED_PYTHON_VERSIONS = (
)
-def to_optional_bytes(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[bytes]
- """Return the given value as bytes encoded using UTF-8 if not already bytes, or None if the value is None."""
- return None if value is None else to_bytes(value, errors)
-
-
-def to_optional_text(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[t.Text]
- """Return the given value as text decoded using UTF-8 if not already text, or None if the value is None."""
- return None if value is None else to_text(value, errors)
-
-
-def to_bytes(value, errors='strict'): # type: (t.AnyStr, str) -> bytes
- """Return the given value as bytes encoded using UTF-8 if not already bytes."""
- if isinstance(value, bytes):
- return value
-
- if isinstance(value, Text):
- return value.encode(ENCODING, errors)
-
- raise Exception('value is not bytes or text: %s' % type(value))
-
-
-def to_text(value, errors='strict'): # type: (t.AnyStr, str) -> t.Text
- """Return the given value as text decoded using UTF-8 if not already text."""
- if isinstance(value, bytes):
- return value.decode(ENCODING, errors)
-
- if isinstance(value, Text):
- return value
-
- raise Exception('value is not bytes or text: %s' % type(value))
-
-
def get_docker_completion():
"""
:rtype: dict[str, dict[str, str]]
@@ -213,8 +188,7 @@ def read_lines_without_comments(path, remove_blank_lines=False, optional=False):
if optional and not os.path.exists(path):
return []
- with open(path, 'r') as path_fd:
- lines = path_fd.read().splitlines()
+ lines = read_text_file(path).splitlines()
lines = [re.sub(r' *#.*$', '', line) for line in lines]
@@ -521,17 +495,6 @@ def remove_tree(path):
raise
-def make_dirs(path):
- """
- :type path: str
- """
- try:
- os.makedirs(to_bytes(path))
- except OSError as ex:
- if ex.errno != errno.EEXIST:
- raise
-
-
def is_binary_file(path):
"""
:type path: str
@@ -587,7 +550,8 @@ def is_binary_file(path):
if ext in assume_binary:
return True
- with open(path, 'rb') as path_fd:
+ with open_binary_file(path) as path_fd:
+ # noinspection PyTypeChecker
return b'\0' in path_fd.read(1024)
@@ -658,11 +622,15 @@ class Display:
for warning in self.warnings:
self.__warning(warning)
- def warning(self, message, unique=False):
+ def warning(self, message, unique=False, verbosity=0):
"""
:type message: str
:type unique: bool
+ :type verbosity: int
"""
+ if verbosity > self.verbosity:
+ return
+
if unique:
if message in self.warnings_unique:
return
@@ -839,11 +807,11 @@ def get_subclasses(class_type): # type: (t.Type[C]) -> t.Set[t.Type[C]]
def is_subdir(candidate_path, path): # type: (str, str) -> bool
"""Returns true if candidate_path is path or a subdirectory of path."""
- if not path.endswith(os.sep):
- path += os.sep
+ if not path.endswith(os.path.sep):
+ path += os.path.sep
- if not candidate_path.endswith(os.sep):
- candidate_path += os.sep
+ if not candidate_path.endswith(os.path.sep):
+ candidate_path += os.path.sep
return candidate_path.startswith(path)
@@ -874,10 +842,10 @@ def import_plugins(directory, root=None): # type: (str, t.Optional[str]) -> Non
path = os.path.join(root, directory)
package = __name__.rsplit('.', 1)[0]
- prefix = '%s.%s.' % (package, directory.replace(os.sep, '.'))
+ prefix = '%s.%s.' % (package, directory.replace(os.path.sep, '.'))
for (_module_loader, name, _ispkg) in pkgutil.iter_modules([path], prefix=prefix):
- module_path = os.path.join(root, name[len(package) + 1:].replace('.', os.sep) + '.py')
+ module_path = os.path.join(root, name[len(package) + 1:].replace('.', os.path.sep) + '.py')
load_module(module_path, name)
@@ -912,7 +880,8 @@ def load_module(path, name): # type: (str, str) -> None
# noinspection PyDeprecation
import imp
- with open(path, 'r') as module_file:
+ # load_source (and thus load_module) require a file opened with `open` in text mode
+ with open(to_bytes(path)) as module_file:
# noinspection PyDeprecation
imp.load_module(name, module_file, path, ('.py', 'r', imp.PY_SOURCE))
diff --git a/test/lib/ansible_test/_internal/util_common.py b/test/lib/ansible_test/_internal/util_common.py
index 5be20ee749..c7ff422752 100644
--- a/test/lib/ansible_test/_internal/util_common.py
+++ b/test/lib/ansible_test/_internal/util_common.py
@@ -4,7 +4,6 @@ __metaclass__ = type
import atexit
import contextlib
-import json
import os
import shutil
import sys
@@ -13,23 +12,29 @@ import textwrap
from . import types as t
+from .encoding import (
+ to_bytes,
+)
+
from .util import (
common_environment,
COVERAGE_CONFIG_NAME,
display,
find_python,
- is_shippable,
remove_tree,
MODE_DIRECTORY,
MODE_FILE_EXECUTE,
PYTHON_PATHS,
raw_command,
- to_bytes,
ANSIBLE_TEST_DATA_ROOT,
- make_dirs,
ApplicationError,
)
+from .io import (
+ write_text_file,
+ write_json_file,
+)
+
from .data import (
data_context,
)
@@ -138,10 +143,10 @@ def named_temporary_file(args, prefix, suffix, directory, content):
yield tempfile_fd.name
-def write_json_test_results(category, name, content): # type: (ResultType, str, t.Union[t.List[t.Any], t.Dict[str, t.Any]]) -> None
+def write_json_test_results(category, name, content, formatted=True): # type: (ResultType, str, t.Union[t.List[t.Any], t.Dict[str, t.Any]], bool) -> None
"""Write the given json content to the specified test results path, creating directories as needed."""
path = os.path.join(category.path, name)
- write_json_file(path, content, create_directories=True)
+ write_json_file(path, content, create_directories=True, formatted=formatted)
def write_text_test_results(category, name, content): # type: (ResultType, str, str) -> None
@@ -150,21 +155,6 @@ def write_text_test_results(category, name, content): # type: (ResultType, str,
write_text_file(path, content, create_directories=True)
-def write_json_file(path, content, create_directories=False): # type: (str, t.Union[t.List[t.Any], t.Dict[str, t.Any]], bool) -> None
- """Write the given json content to the specified path, optionally creating missing directories."""
- text_content = json.dumps(content, sort_keys=True, indent=4) + '\n'
- write_text_file(path, text_content, create_directories=create_directories)
-
-
-def write_text_file(path, content, create_directories=False): # type: (str, str, bool) -> None
- """Write the given text content to the specified path, optionally creating missing directories."""
- if create_directories:
- make_dirs(os.path.dirname(path))
-
- with open(to_bytes(path), 'wb') as file:
- file.write(to_bytes(content))
-
-
def get_python_path(args, interpreter):
"""
:type args: TestConfig