summaryrefslogtreecommitdiffstats
path: root/test/units/module_utils
diff options
context:
space:
mode:
Diffstat (limited to 'test/units/module_utils')
-rw-r--r--test/units/module_utils/__init__.py0
-rw-r--r--test/units/module_utils/basic/__init__.py0
-rw-r--r--test/units/module_utils/basic/test__log_invocation.py55
-rw-r--r--test/units/module_utils/basic/test__symbolic_mode_to_octal.py103
-rw-r--r--test/units/module_utils/basic/test_argument_spec.py724
-rw-r--r--test/units/module_utils/basic/test_atomic_move.py223
-rw-r--r--test/units/module_utils/basic/test_command_nonexisting.py31
-rw-r--r--test/units/module_utils/basic/test_deprecate_warn.py77
-rw-r--r--test/units/module_utils/basic/test_dict_converters.py31
-rw-r--r--test/units/module_utils/basic/test_exit_json.py173
-rw-r--r--test/units/module_utils/basic/test_filesystem.py160
-rw-r--r--test/units/module_utils/basic/test_get_file_attributes.py75
-rw-r--r--test/units/module_utils/basic/test_get_module_path.py22
-rw-r--r--test/units/module_utils/basic/test_heuristic_log_sanitize.py92
-rw-r--r--test/units/module_utils/basic/test_imports.py128
-rw-r--r--test/units/module_utils/basic/test_log.py152
-rw-r--r--test/units/module_utils/basic/test_no_log.py160
-rw-r--r--test/units/module_utils/basic/test_platform_distribution.py188
-rw-r--r--test/units/module_utils/basic/test_run_command.py278
-rw-r--r--test/units/module_utils/basic/test_safe_eval.py70
-rw-r--r--test/units/module_utils/basic/test_sanitize_keys.py98
-rw-r--r--test/units/module_utils/basic/test_selinux.py190
-rw-r--r--test/units/module_utils/basic/test_set_cwd.py195
-rw-r--r--test/units/module_utils/basic/test_set_mode_if_different.py190
-rw-r--r--test/units/module_utils/basic/test_tmpdir.py119
-rw-r--r--test/units/module_utils/common/__init__.py0
-rw-r--r--test/units/module_utils/common/arg_spec/__init__.py0
-rw-r--r--test/units/module_utils/common/arg_spec/test_aliases.py132
-rw-r--r--test/units/module_utils/common/arg_spec/test_module_validate.py58
-rw-r--r--test/units/module_utils/common/arg_spec/test_sub_spec.py106
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_invalid.py134
-rw-r--r--test/units/module_utils/common/arg_spec/test_validate_valid.py335
-rw-r--r--test/units/module_utils/common/parameters/test_check_arguments.py38
-rw-r--r--test/units/module_utils/common/parameters/test_handle_aliases.py74
-rw-r--r--test/units/module_utils/common/parameters/test_list_deprecations.py44
-rw-r--r--test/units/module_utils/common/parameters/test_list_no_log_values.py228
-rw-r--r--test/units/module_utils/common/process/test_get_bin_path.py39
-rw-r--r--test/units/module_utils/common/test_collections.py175
-rw-r--r--test/units/module_utils/common/test_dict_transformations.py153
-rw-r--r--test/units/module_utils/common/test_locale.py42
-rw-r--r--test/units/module_utils/common/test_network.py79
-rw-r--r--test/units/module_utils/common/test_sys_info.py168
-rw-r--r--test/units/module_utils/common/test_utils.py46
-rw-r--r--test/units/module_utils/common/text/converters/test_container_to_bytes.py95
-rw-r--r--test/units/module_utils/common/text/converters/test_container_to_text.py78
-rw-r--r--test/units/module_utils/common/text/converters/test_json_encode_fallback.py68
-rw-r--r--test/units/module_utils/common/text/converters/test_jsonify.py27
-rw-r--r--test/units/module_utils/common/text/converters/test_to_str.py50
-rw-r--r--test/units/module_utils/common/text/formatters/test_bytes_to_human.py116
-rw-r--r--test/units/module_utils/common/text/formatters/test_human_to_bytes.py185
-rw-r--r--test/units/module_utils/common/text/formatters/test_lenient_lowercase.py68
-rw-r--r--test/units/module_utils/common/validation/test_check_missing_parameters.py35
-rw-r--r--test/units/module_utils/common/validation/test_check_mutually_exclusive.py57
-rw-r--r--test/units/module_utils/common/validation/test_check_required_arguments.py88
-rw-r--r--test/units/module_utils/common/validation/test_check_required_by.py98
-rw-r--r--test/units/module_utils/common/validation/test_check_required_if.py79
-rw-r--r--test/units/module_utils/common/validation/test_check_required_one_of.py47
-rw-r--r--test/units/module_utils/common/validation/test_check_required_together.py57
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bits.py43
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bool.py49
-rw-r--r--test/units/module_utils/common/validation/test_check_type_bytes.py50
-rw-r--r--test/units/module_utils/common/validation/test_check_type_dict.py34
-rw-r--r--test/units/module_utils/common/validation/test_check_type_float.py38
-rw-r--r--test/units/module_utils/common/validation/test_check_type_int.py34
-rw-r--r--test/units/module_utils/common/validation/test_check_type_jsonarg.py36
-rw-r--r--test/units/module_utils/common/validation/test_check_type_list.py32
-rw-r--r--test/units/module_utils/common/validation/test_check_type_path.py28
-rw-r--r--test/units/module_utils/common/validation/test_check_type_raw.py23
-rw-r--r--test/units/module_utils/common/validation/test_check_type_str.py33
-rw-r--r--test/units/module_utils/common/validation/test_count_terms.py40
-rw-r--r--test/units/module_utils/common/warnings/test_deprecate.py101
-rw-r--r--test/units/module_utils/common/warnings/test_warn.py61
-rw-r--r--test/units/module_utils/conftest.py72
-rw-r--r--test/units/module_utils/facts/__init__.py0
-rw-r--r--test/units/module_utils/facts/base.py65
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/aarch64-4cpu-cpuinfo40
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/arm64-4cpu-cpuinfo32
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo12
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo75
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo39
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo44
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo125
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu61
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/x86_64-2cpu-cpuinfo56
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/x86_64-4cpu-cpuinfo104
-rw-r--r--test/units/module_utils/facts/fixtures/cpuinfo/x86_64-8cpu-cpuinfo216
-rw-r--r--test/units/module_utils/facts/fixtures/distribution_files/ClearLinux10
-rw-r--r--test/units/module_utils/facts/fixtures/distribution_files/CoreOS10
-rw-r--r--test/units/module_utils/facts/fixtures/distribution_files/LinuxMint12
-rw-r--r--test/units/module_utils/facts/fixtures/distribution_files/Slackware1
-rw-r--r--test/units/module_utils/facts/fixtures/distribution_files/SlackwareCurrent1
-rw-r--r--test/units/module_utils/facts/fixtures/findmount_output.txt40
-rw-r--r--test/units/module_utils/facts/hardware/__init__.py0
-rw-r--r--test/units/module_utils/facts/hardware/aix_data.py75
-rw-r--r--test/units/module_utils/facts/hardware/linux_data.py633
-rw-r--r--test/units/module_utils/facts/hardware/test_aix_processor.py24
-rw-r--r--test/units/module_utils/facts/hardware/test_linux.py198
-rw-r--r--test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py62
-rw-r--r--test/units/module_utils/facts/hardware/test_sunos_get_uptime_facts.py20
-rw-r--r--test/units/module_utils/facts/network/__init__.py0
-rw-r--r--test/units/module_utils/facts/network/test_fc_wwn.py137
-rw-r--r--test/units/module_utils/facts/network/test_generic_bsd.py217
-rw-r--r--test/units/module_utils/facts/network/test_iscsi_get_initiator.py54
-rw-r--r--test/units/module_utils/facts/other/__init__.py0
-rw-r--r--test/units/module_utils/facts/other/test_facter.py228
-rw-r--r--test/units/module_utils/facts/other/test_ohai.py6768
-rw-r--r--test/units/module_utils/facts/system/__init__.py0
-rw-r--r--test/units/module_utils/facts/system/distribution/__init__.py0
-rw-r--r--test/units/module_utils/facts/system/distribution/conftest.py21
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/almalinux_8_3_beta.json53
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2.json39
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2016.03.json40
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2018.03.json40
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2_karoo.json34
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_release_2.json34
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/arch_linux_na.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/arch_linux_no_arch-release_na.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/archlinux_rolling.json31
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/centos_6.7.json31
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/centos_8_1.json54
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/centos_stream_8.json46
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/clearlinux_26580.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/clearlinux_28120.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/core_os_1911.5.0.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/core_os_976.0.0.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_2.5.4.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_3.7.3.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/debian_10.json42
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/debian_7.9.json39
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/debian_stretch_sid.json36
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/deepin_20.4.json29
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/devuan.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.2.2.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.6.2.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/eurolinux_8.5.json46
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/fedora_22.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/fedora_25.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/fedora_31.json55
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/flatcar_3139.2.0.json43
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/kali_2019.1.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/kde_neon_16.04.json42
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/kylin_linux_advanced_server_v10.json38
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/linux_mint_18.2.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/linux_mint_19.1.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/netbsd_8.2.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/nexenta_3.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/nexenta_4.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/omnios.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/openeuler_20.03.json28
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/openindiana.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/opensuse_13.2.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.0.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.1.json36
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_42.1.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/opensuse_tumbleweed_20160917.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/osmc.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/pardus_19.1.json41
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/parrot_4.8.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/pop_os_20.04.json29
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/redhat_6.7.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/redhat_7.2.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/redhat_7.7.json43
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/rockylinux_8_3.json46
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/sles_11.3.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/sles_11.4.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp0.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp1.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/smartos_global_zone.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/smartos_zone.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/smgl_na.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/solaris_10.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/solaris_11.3.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/solaris_11.4.json35
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/solaris_11.json26
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/steamos_2.0.json40
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/tencentos_3_1.json50
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/truenas_12.0rc1.json39
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/ubuntu_10.04_guess.json23
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/ubuntu_12.04.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/ubuntu_14.04.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/ubuntu_16.04.json24
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/ubuntu_18.04.json39
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/uos_20.json29
-rw-r--r--test/units/module_utils/facts/system/distribution/fixtures/virtuozzo_7.3.json25
-rw-r--r--test/units/module_utils/facts/system/distribution/test_distribution_sles4sap.py33
-rw-r--r--test/units/module_utils/facts/system/distribution/test_distribution_version.py158
-rw-r--r--test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py51
-rw-r--r--test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py37
-rw-r--r--test/units/module_utils/facts/system/test_cmdline.py67
-rw-r--r--test/units/module_utils/facts/system/test_lsb.py108
-rw-r--r--test/units/module_utils/facts/system/test_user.py40
-rw-r--r--test/units/module_utils/facts/test_ansible_collector.py524
-rw-r--r--test/units/module_utils/facts/test_collector.py563
-rw-r--r--test/units/module_utils/facts/test_collectors.py510
-rw-r--r--test/units/module_utils/facts/test_date_time.py106
-rw-r--r--test/units/module_utils/facts/test_facts.py646
-rw-r--r--test/units/module_utils/facts/test_sysctl.py251
-rw-r--r--test/units/module_utils/facts/test_timeout.py171
-rw-r--r--test/units/module_utils/facts/test_utils.py39
-rw-r--r--test/units/module_utils/facts/virtual/__init__.py0
-rw-r--r--test/units/module_utils/facts/virtual/test_linux.py52
-rw-r--r--test/units/module_utils/json_utils/__init__.py0
-rw-r--r--test/units/module_utils/json_utils/test_filter_non_json_lines.py88
-rw-r--r--test/units/module_utils/parsing/test_convert_bool.py60
-rw-r--r--test/units/module_utils/test_api.py121
-rw-r--r--test/units/module_utils/test_connection.py22
-rw-r--r--test/units/module_utils/test_distro.py39
-rw-r--r--test/units/module_utils/urls/__init__.py0
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem12
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem12
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem21
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem21
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem22
-rw-r--r--test/units/module_utils/urls/fixtures/client.key28
-rw-r--r--test/units/module_utils/urls/fixtures/client.pem81
-rw-r--r--test/units/module_utils/urls/fixtures/client.txt3
-rw-r--r--test/units/module_utils/urls/fixtures/multipart.txt166
-rw-r--r--test/units/module_utils/urls/fixtures/netrc3
-rw-r--r--test/units/module_utils/urls/test_RedirectHandlerFactory.py140
-rw-r--r--test/units/module_utils/urls/test_Request.py467
-rw-r--r--test/units/module_utils/urls/test_RequestWithMethod.py22
-rw-r--r--test/units/module_utils/urls/test_channel_binding.py74
-rw-r--r--test/units/module_utils/urls/test_fetch_file.py45
-rw-r--r--test/units/module_utils/urls/test_fetch_url.py230
-rw-r--r--test/units/module_utils/urls/test_generic_urlparse.py57
-rw-r--r--test/units/module_utils/urls/test_gzip.py152
-rw-r--r--test/units/module_utils/urls/test_prepare_multipart.py103
-rw-r--r--test/units/module_utils/urls/test_split.py77
-rw-r--r--test/units/module_utils/urls/test_urls.py109
234 files changed, 24516 insertions, 0 deletions
diff --git a/test/units/module_utils/__init__.py b/test/units/module_utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/__init__.py
diff --git a/test/units/module_utils/basic/__init__.py b/test/units/module_utils/basic/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/basic/__init__.py
diff --git a/test/units/module_utils/basic/test__log_invocation.py b/test/units/module_utils/basic/test__log_invocation.py
new file mode 100644
index 0000000..3beda8b
--- /dev/null
+++ b/test/units/module_utils/basic/test__log_invocation.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# (c) 2016, James Cammarata <jimi@sngx.net>
+# (c) 2017, 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 pytest
+
+
+ARGS = dict(foo=False, bar=[1, 2, 3], bam="bam", baz=u'baz')
+ARGUMENT_SPEC = dict(
+ foo=dict(default=True, type='bool'),
+ bar=dict(default=[], type='list'),
+ bam=dict(default="bam"),
+ baz=dict(default=u"baz"),
+ password=dict(default=True),
+ no_log=dict(default="you shouldn't see me", no_log=True),
+)
+
+
+@pytest.mark.parametrize('am, stdin', [(ARGUMENT_SPEC, ARGS)], indirect=['am', 'stdin'])
+def test_module_utils_basic__log_invocation(am, mocker):
+
+ am.log = mocker.MagicMock()
+ am._log_invocation()
+
+ # Message is generated from a dict so it will be in an unknown order.
+ # have to check this manually rather than with assert_called_with()
+ args = am.log.call_args[0]
+ assert len(args) == 1
+ message = args[0]
+
+ assert len(message) == \
+ len('Invoked with bam=bam bar=[1, 2, 3] foo=False baz=baz no_log=NOT_LOGGING_PARAMETER password=NOT_LOGGING_PASSWORD')
+
+ assert message.startswith('Invoked with ')
+ assert ' bam=bam' in message
+ assert ' bar=[1, 2, 3]' in message
+ assert ' foo=False' in message
+ assert ' baz=baz' in message
+ assert ' no_log=NOT_LOGGING_PARAMETER' in message
+ assert ' password=NOT_LOGGING_PASSWORD' in message
+
+ kwargs = am.log.call_args[1]
+ assert kwargs == \
+ dict(log_args={
+ 'foo': 'False',
+ 'bar': '[1, 2, 3]',
+ 'bam': 'bam',
+ 'baz': 'baz',
+ 'password': 'NOT_LOGGING_PASSWORD',
+ 'no_log': 'NOT_LOGGING_PARAMETER',
+ })
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
new file mode 100644
index 0000000..7793b34
--- /dev/null
+++ b/test/units/module_utils/basic/test__symbolic_mode_to_octal.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# Copyright:
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016-2017 Ansible Project
+# License: 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
+
+import pytest
+
+from ansible.module_utils.basic import AnsibleModule
+
+
+#
+# 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}
+#
+# perm_bits = {'x': 0b001,
+# 'w': 0b010,
+# 'r': 0b100}
+#
+# role_shift = {'u': 6,
+# 'g': 3,
+# 'o': 0}
+
+DATA = ( # Going from no permissions to setting all for user, group, and/or other
+ (0o040000, u'a+rwx', 0o0777),
+ (0o040000, u'u+rwx,g+rwx,o+rwx', 0o0777),
+ (0o040000, u'o+rwx', 0o0007),
+ (0o040000, u'g+rwx', 0o0070),
+ (0o040000, u'u+rwx', 0o0700),
+
+ # Going from all permissions to none for user, group, and/or other
+ (0o040777, u'a-rwx', 0o0000),
+ (0o040777, u'u-rwx,g-rwx,o-rwx', 0o0000),
+ (0o040777, u'o-rwx', 0o0770),
+ (0o040777, u'g-rwx', 0o0707),
+ (0o040777, u'u-rwx', 0o0077),
+
+ # now using absolute assignment from None to a set of perms
+ (0o040000, u'a=rwx', 0o0777),
+ (0o040000, u'u=rwx,g=rwx,o=rwx', 0o0777),
+ (0o040000, u'o=rwx', 0o0007),
+ (0o040000, u'g=rwx', 0o0070),
+ (0o040000, u'u=rwx', 0o0700),
+
+ # X effect on files and dirs
+ (0o040000, u'a+X', 0o0111),
+ (0o100000, u'a+X', 0),
+ (0o040000, u'a=X', 0o0111),
+ (0o100000, u'a=X', 0),
+ (0o040777, u'a-X', 0o0666),
+ # Same as chmod but is it a bug?
+ # chmod a-X statfile <== removes execute from statfile
+ (0o100777, u'a-X', 0o0666),
+
+ # 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),
+)
+
+UMASK_DATA = (
+ (0o100000, '+rwx', 0o770),
+ (0o100777, '-rwx', 0o007),
+)
+
+INVALID_DATA = (
+ (0o040000, u'a=foo', "bad symbolic permission for mode: a=foo"),
+ (0o040000, u'f=rwx', "bad symbolic permission for mode: f=rwx"),
+)
+
+
+@pytest.mark.parametrize('stat_info, mode_string, expected', DATA)
+def test_good_symbolic_modes(mocker, stat_info, mode_string, expected):
+ mock_stat = mocker.MagicMock()
+ mock_stat.st_mode = stat_info
+ assert AnsibleModule._symbolic_mode_to_octal(mock_stat, mode_string) == expected
+
+
+@pytest.mark.parametrize('stat_info, mode_string, expected', UMASK_DATA)
+def test_umask_with_symbolic_modes(mocker, stat_info, mode_string, expected):
+ mock_umask = mocker.patch('os.umask')
+ mock_umask.return_value = 0o7
+
+ mock_stat = mocker.MagicMock()
+ mock_stat.st_mode = stat_info
+
+ assert AnsibleModule._symbolic_mode_to_octal(mock_stat, mode_string) == expected
+
+
+@pytest.mark.parametrize('stat_info, mode_string, expected', INVALID_DATA)
+def test_invalid_symbolic_modes(mocker, stat_info, mode_string, expected):
+ mock_stat = mocker.MagicMock()
+ mock_stat.st_mode = stat_info
+ with pytest.raises(ValueError) as exc:
+ assert AnsibleModule._symbolic_mode_to_octal(mock_stat, mode_string) == 'blah'
+ assert exc.match(expected)
diff --git a/test/units/module_utils/basic/test_argument_spec.py b/test/units/module_utils/basic/test_argument_spec.py
new file mode 100644
index 0000000..211d65a
--- /dev/null
+++ b/test/units/module_utils/basic/test_argument_spec.py
@@ -0,0 +1,724 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# Copyright: 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 json
+import os
+
+import pytest
+
+from units.compat.mock import MagicMock
+from ansible.module_utils import basic
+from ansible.module_utils.api import basic_auth_argument_spec, rate_limit_argument_spec, retry_argument_spec
+from ansible.module_utils.common import warnings
+from ansible.module_utils.common.warnings import get_deprecation_messages, get_warning_messages
+from ansible.module_utils.six import integer_types, string_types
+from ansible.module_utils.six.moves import builtins
+
+
+MOCK_VALIDATOR_FAIL = MagicMock(side_effect=TypeError("bad conversion"))
+# Data is argspec, argument, expected
+VALID_SPECS = (
+ # Simple type=int
+ ({'arg': {'type': 'int'}}, {'arg': 42}, 42),
+ # Simple type=int with a large value (will be of type long under Python 2)
+ ({'arg': {'type': 'int'}}, {'arg': 18765432109876543210}, 18765432109876543210),
+ # Simple type=list, elements=int
+ ({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': [42, 32]}, [42, 32]),
+ # Type=int with conversion from string
+ ({'arg': {'type': 'int'}}, {'arg': '42'}, 42),
+ # Type=list elements=int with conversion from string
+ ({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': ['42', '32']}, [42, 32]),
+ # Simple type=float
+ ({'arg': {'type': 'float'}}, {'arg': 42.0}, 42.0),
+ # Simple type=list, elements=float
+ ({'arg': {'type': 'list', 'elements': 'float'}}, {'arg': [42.1, 32.2]}, [42.1, 32.2]),
+ # Type=float conversion from int
+ ({'arg': {'type': 'float'}}, {'arg': 42}, 42.0),
+ # type=list, elements=float conversion from int
+ ({'arg': {'type': 'list', 'elements': 'float'}}, {'arg': [42, 32]}, [42.0, 32.0]),
+ # Type=float conversion from string
+ ({'arg': {'type': 'float'}}, {'arg': '42.0'}, 42.0),
+ # type=list, elements=float conversion from string
+ ({'arg': {'type': 'list', 'elements': 'float'}}, {'arg': ['42.1', '32.2']}, [42.1, 32.2]),
+ # Type=float conversion from string without decimal point
+ ({'arg': {'type': 'float'}}, {'arg': '42'}, 42.0),
+ # Type=list elements=float conversion from string without decimal point
+ ({'arg': {'type': 'list', 'elements': 'float'}}, {'arg': ['42', '32.2']}, [42.0, 32.2]),
+ # Simple type=bool
+ ({'arg': {'type': 'bool'}}, {'arg': True}, True),
+ # Simple type=list elements=bool
+ ({'arg': {'type': 'list', 'elements': 'bool'}}, {'arg': [True, 'true', 1, 'yes', False, 'false', 'no', 0]},
+ [True, True, True, True, False, False, False, False]),
+ # Type=int with conversion from string
+ ({'arg': {'type': 'bool'}}, {'arg': 'yes'}, True),
+ # Type=str converts to string
+ ({'arg': {'type': 'str'}}, {'arg': 42}, '42'),
+ # Type=list elements=str simple converts to string
+ ({'arg': {'type': 'list', 'elements': 'str'}}, {'arg': ['42', '32']}, ['42', '32']),
+ # Type is implicit, converts to string
+ ({'arg': {'type': 'str'}}, {'arg': 42}, '42'),
+ # Type=list elements=str implicit converts to string
+ ({'arg': {'type': 'list', 'elements': 'str'}}, {'arg': [42, 32]}, ['42', '32']),
+ # parameter is required
+ ({'arg': {'required': True}}, {'arg': 42}, '42'),
+)
+
+INVALID_SPECS = (
+ # Type is int; unable to convert this string
+ ({'arg': {'type': 'int'}}, {'arg': "wolf"}, "is of type {0} and we were unable to convert to int: {0} cannot be converted to an int".format(type('bad'))),
+ # Type is list elements is int; unable to convert this string
+ ({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': [1, "bad"]}, "is of type {0} and we were unable to convert to int: {0} cannot be converted to "
+ "an int".format(type('int'))),
+ # Type is int; unable to convert float
+ ({'arg': {'type': 'int'}}, {'arg': 42.1}, "'float'> cannot be converted to an int"),
+ # Type is list, elements is int; unable to convert float
+ ({'arg': {'type': 'list', 'elements': 'int'}}, {'arg': [42.1, 32, 2]}, "'float'> cannot be converted to an int"),
+ # type is a callable that fails to convert
+ ({'arg': {'type': MOCK_VALIDATOR_FAIL}}, {'arg': "bad"}, "bad conversion"),
+ # type is a list, elements is callable that fails to convert
+ ({'arg': {'type': 'list', 'elements': MOCK_VALIDATOR_FAIL}}, {'arg': [1, "bad"]}, "bad conversion"),
+ # unknown parameter
+ ({'arg': {'type': 'int'}}, {'other': 'bad', '_ansible_module_name': 'ansible_unittest'},
+ 'Unsupported parameters for (ansible_unittest) module: other. Supported parameters include: arg.'),
+ ({'arg': {'type': 'int', 'aliases': ['argument']}}, {'other': 'bad', '_ansible_module_name': 'ansible_unittest'},
+ 'Unsupported parameters for (ansible_unittest) module: other. Supported parameters include: arg (argument).'),
+ # parameter is required
+ ({'arg': {'required': True}}, {}, 'missing required arguments: arg'),
+)
+
+BASIC_AUTH_VALID_ARGS = [
+ {'api_username': 'user1', 'api_password': 'password1', 'api_url': 'http://example.com', 'validate_certs': False},
+ {'api_username': 'user1', 'api_password': 'password1', 'api_url': 'http://example.com', 'validate_certs': True},
+]
+
+RATE_LIMIT_VALID_ARGS = [
+ {'rate': 1, 'rate_limit': 1},
+ {'rate': '1', 'rate_limit': 1},
+ {'rate': 1, 'rate_limit': '1'},
+ {'rate': '1', 'rate_limit': '1'},
+]
+
+RETRY_VALID_ARGS = [
+ {'retries': 1, 'retry_pause': 1.5},
+ {'retries': '1', 'retry_pause': '1.5'},
+ {'retries': 1, 'retry_pause': '1.5'},
+ {'retries': '1', 'retry_pause': 1.5},
+]
+
+
+@pytest.fixture
+def complex_argspec():
+ arg_spec = dict(
+ foo=dict(required=True, aliases=['dup']),
+ bar=dict(),
+ bam=dict(),
+ bing=dict(),
+ bang=dict(),
+ bong=dict(),
+ baz=dict(fallback=(basic.env_fallback, ['BAZ'])),
+ bar1=dict(type='bool'),
+ bar3=dict(type='list', elements='path'),
+ bar_str=dict(type='list', elements=str),
+ zardoz=dict(choices=['one', 'two']),
+ zardoz2=dict(type='list', choices=['one', 'two', 'three']),
+ zardoz3=dict(type='str', aliases=['zodraz'], deprecated_aliases=[dict(name='zodraz', version='9.99')]),
+ )
+ mut_ex = (('bar', 'bam'), ('bing', 'bang', 'bong'))
+ req_to = (('bam', 'baz'),)
+
+ kwargs = dict(
+ argument_spec=arg_spec,
+ mutually_exclusive=mut_ex,
+ required_together=req_to,
+ no_log=True,
+ add_file_common_args=True,
+ supports_check_mode=True,
+ )
+ return kwargs
+
+
+@pytest.fixture
+def options_argspec_list():
+ options_spec = dict(
+ foo=dict(required=True, aliases=['dup']),
+ bar=dict(),
+ bar1=dict(type='list', elements='str'),
+ bar2=dict(type='list', elements='int'),
+ bar3=dict(type='list', elements='float'),
+ bar4=dict(type='list', elements='path'),
+ bam=dict(),
+ baz=dict(fallback=(basic.env_fallback, ['BAZ'])),
+ bam1=dict(),
+ bam2=dict(default='test'),
+ bam3=dict(type='bool'),
+ bam4=dict(type='str'),
+ )
+
+ arg_spec = dict(
+ foobar=dict(
+ type='list',
+ elements='dict',
+ options=options_spec,
+ mutually_exclusive=[
+ ['bam', 'bam1'],
+ ],
+ required_if=[
+ ['foo', 'hello', ['bam']],
+ ['foo', 'bam2', ['bam2']]
+ ],
+ required_one_of=[
+ ['bar', 'bam']
+ ],
+ required_together=[
+ ['bam1', 'baz']
+ ],
+ required_by={
+ 'bam4': ('bam1', 'bam3'),
+ },
+ )
+ )
+
+ kwargs = dict(
+ argument_spec=arg_spec,
+ no_log=True,
+ add_file_common_args=True,
+ supports_check_mode=True
+ )
+ return kwargs
+
+
+@pytest.fixture
+def options_argspec_dict(options_argspec_list):
+ # should test ok, for options in dict format.
+ kwargs = options_argspec_list
+ kwargs['argument_spec']['foobar']['type'] = 'dict'
+ kwargs['argument_spec']['foobar']['elements'] = None
+
+ return kwargs
+
+
+#
+# Tests for one aspect of arg_spec
+#
+
+@pytest.mark.parametrize('argspec, expected, stdin', [(s[0], s[2], s[1]) for s in VALID_SPECS],
+ indirect=['stdin'])
+def test_validator_basic_types(argspec, expected, stdin):
+
+ am = basic.AnsibleModule(argspec)
+
+ if 'type' in argspec['arg']:
+ if argspec['arg']['type'] == 'int':
+ type_ = integer_types
+ else:
+ type_ = getattr(builtins, argspec['arg']['type'])
+ else:
+ type_ = str
+
+ assert isinstance(am.params['arg'], type_)
+ assert am.params['arg'] == expected
+
+
+@pytest.mark.parametrize('stdin', [{'arg': 42}, {'arg': 18765432109876543210}], indirect=['stdin'])
+def test_validator_function(mocker, stdin):
+ # Type is a callable
+ MOCK_VALIDATOR_SUCCESS = mocker.MagicMock(return_value=27)
+ argspec = {'arg': {'type': MOCK_VALIDATOR_SUCCESS}}
+ am = basic.AnsibleModule(argspec)
+
+ assert isinstance(am.params['arg'], integer_types)
+ assert am.params['arg'] == 27
+
+
+@pytest.mark.parametrize('stdin', BASIC_AUTH_VALID_ARGS, indirect=['stdin'])
+def test_validate_basic_auth_arg(mocker, stdin):
+ kwargs = dict(
+ argument_spec=basic_auth_argument_spec()
+ )
+ am = basic.AnsibleModule(**kwargs)
+ assert isinstance(am.params['api_username'], string_types)
+ assert isinstance(am.params['api_password'], string_types)
+ assert isinstance(am.params['api_url'], string_types)
+ assert isinstance(am.params['validate_certs'], bool)
+
+
+@pytest.mark.parametrize('stdin', RATE_LIMIT_VALID_ARGS, indirect=['stdin'])
+def test_validate_rate_limit_argument_spec(mocker, stdin):
+ kwargs = dict(
+ argument_spec=rate_limit_argument_spec()
+ )
+ am = basic.AnsibleModule(**kwargs)
+ assert isinstance(am.params['rate'], integer_types)
+ assert isinstance(am.params['rate_limit'], integer_types)
+
+
+@pytest.mark.parametrize('stdin', RETRY_VALID_ARGS, indirect=['stdin'])
+def test_validate_retry_argument_spec(mocker, stdin):
+ kwargs = dict(
+ argument_spec=retry_argument_spec()
+ )
+ am = basic.AnsibleModule(**kwargs)
+ assert isinstance(am.params['retries'], integer_types)
+ assert isinstance(am.params['retry_pause'], float)
+
+
+@pytest.mark.parametrize('stdin', [{'arg': '123'}, {'arg': 123}], indirect=['stdin'])
+def test_validator_string_type(mocker, stdin):
+ # Custom callable that is 'str'
+ argspec = {'arg': {'type': str}}
+ am = basic.AnsibleModule(argspec)
+
+ assert isinstance(am.params['arg'], string_types)
+ assert am.params['arg'] == '123'
+
+
+@pytest.mark.parametrize('argspec, expected, stdin', [(s[0], s[2], s[1]) for s in INVALID_SPECS],
+ indirect=['stdin'])
+def test_validator_fail(stdin, capfd, argspec, expected):
+ with pytest.raises(SystemExit):
+ basic.AnsibleModule(argument_spec=argspec)
+
+ out, err = capfd.readouterr()
+ assert not err
+ assert expected in json.loads(out)['msg']
+ assert json.loads(out)['failed']
+
+
+class TestComplexArgSpecs:
+ """Test with a more complex arg_spec"""
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello'}, {'dup': 'hello'}], indirect=['stdin'])
+ def test_complex_required(self, stdin, complex_argspec):
+ """Test that the complex argspec works if we give it its required param as either the canonical or aliased name"""
+ am = basic.AnsibleModule(**complex_argspec)
+ assert isinstance(am.params['foo'], str)
+ assert am.params['foo'] == 'hello'
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello1', 'dup': 'hello2'}], indirect=['stdin'])
+ def test_complex_duplicate_warning(self, stdin, complex_argspec):
+ """Test that the complex argspec issues a warning if we specify an option both with its canonical name and its alias"""
+ am = basic.AnsibleModule(**complex_argspec)
+ assert isinstance(am.params['foo'], str)
+ assert 'Both option foo and its alias dup are set.' in get_warning_messages()
+ assert am.params['foo'] == 'hello2'
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bam': 'test'}], indirect=['stdin'])
+ def test_complex_type_fallback(self, mocker, stdin, complex_argspec):
+ """Test that the complex argspec works if we get a required parameter via fallback"""
+ environ = os.environ.copy()
+ environ['BAZ'] = 'test data'
+ mocker.patch('ansible.module_utils.basic.os.environ', environ)
+
+ am = basic.AnsibleModule(**complex_argspec)
+
+ assert isinstance(am.params['baz'], str)
+ assert am.params['baz'] == 'test data'
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bar': 'bad', 'bam': 'bad2', 'bing': 'a', 'bang': 'b', 'bong': 'c'}], indirect=['stdin'])
+ def test_fail_mutually_exclusive(self, capfd, stdin, complex_argspec):
+ """Fail because of mutually exclusive parameters"""
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**complex_argspec)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert results['msg'] == "parameters are mutually exclusive: bar|bam, bing|bang|bong"
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bam': 'bad2'}], indirect=['stdin'])
+ def test_fail_required_together(self, capfd, stdin, complex_argspec):
+ """Fail because only one of a required_together pair of parameters was specified"""
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**complex_argspec)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert results['msg'] == "parameters are required together: bam, baz"
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bar': 'hi'}], indirect=['stdin'])
+ def test_fail_required_together_and_default(self, capfd, stdin, complex_argspec):
+ """Fail because one of a required_together pair of parameters has a default and the other was not specified"""
+ complex_argspec['argument_spec']['baz'] = {'default': 42}
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**complex_argspec)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert results['msg'] == "parameters are required together: bam, baz"
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello'}], indirect=['stdin'])
+ def test_fail_required_together_and_fallback(self, capfd, mocker, stdin, complex_argspec):
+ """Fail because one of a required_together pair of parameters has a fallback and the other was not specified"""
+ environ = os.environ.copy()
+ environ['BAZ'] = 'test data'
+ mocker.patch('ansible.module_utils.basic.os.environ', environ)
+
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**complex_argspec)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert results['msg'] == "parameters are required together: bam, baz"
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'zardoz2': ['one', 'four', 'five']}], indirect=['stdin'])
+ def test_fail_list_with_choices(self, capfd, mocker, stdin, complex_argspec):
+ """Fail because one of the items is not in the choice"""
+ with pytest.raises(SystemExit):
+ basic.AnsibleModule(**complex_argspec)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert results['msg'] == "value of zardoz2 must be one or more of: one, two, three. Got no match for: four, five"
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'zardoz2': ['one', 'three']}], indirect=['stdin'])
+ def test_list_with_choices(self, capfd, mocker, stdin, complex_argspec):
+ """Test choices with list"""
+ am = basic.AnsibleModule(**complex_argspec)
+ assert isinstance(am.params['zardoz2'], list)
+ assert am.params['zardoz2'] == ['one', 'three']
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bar3': ['~/test', 'test/']}], indirect=['stdin'])
+ def test_list_with_elements_path(self, capfd, mocker, stdin, complex_argspec):
+ """Test choices with list"""
+ am = basic.AnsibleModule(**complex_argspec)
+ assert isinstance(am.params['bar3'], list)
+ assert am.params['bar3'][0].startswith('/')
+ assert am.params['bar3'][1] == 'test/'
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'zodraz': 'one'}], indirect=['stdin'])
+ def test_deprecated_alias(self, capfd, mocker, stdin, complex_argspec, monkeypatch):
+ """Test a deprecated alias"""
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ am = basic.AnsibleModule(**complex_argspec)
+
+ assert "Alias 'zodraz' is deprecated." in get_deprecation_messages()[0]['msg']
+ assert get_deprecation_messages()[0]['version'] == '9.99'
+
+ @pytest.mark.parametrize('stdin', [{'foo': 'hello', 'bar_str': [867, '5309']}], indirect=['stdin'])
+ def test_list_with_elements_callable_str(self, capfd, mocker, stdin, complex_argspec):
+ """Test choices with list"""
+ am = basic.AnsibleModule(**complex_argspec)
+ assert isinstance(am.params['bar_str'], list)
+ assert isinstance(am.params['bar_str'][0], string_types)
+ assert isinstance(am.params['bar_str'][1], string_types)
+ assert am.params['bar_str'][0] == '867'
+ assert am.params['bar_str'][1] == '5309'
+
+
+class TestComplexOptions:
+ """Test arg spec options"""
+
+ # (Parameters, expected value of module.params['foobar'])
+ OPTIONS_PARAMS_LIST = (
+ ({'foobar': [{"foo": "hello", "bam": "good"}, {"foo": "test", "bar": "good"}]},
+ [{'foo': 'hello', 'bam': 'good', 'bam2': 'test', 'bar': None, 'baz': None, 'bam1': None, 'bam3': None, 'bam4': None,
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None},
+ {'foo': 'test', 'bam': None, 'bam2': 'test', 'bar': 'good', 'baz': None, 'bam1': None, 'bam3': None, 'bam4': None,
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}]
+ ),
+ # Alias for required param
+ ({'foobar': [{"dup": "test", "bar": "good"}]},
+ [{'foo': 'test', 'dup': 'test', 'bam': None, 'bam2': 'test', 'bar': 'good', 'baz': None, 'bam1': None, 'bam3': None, 'bam4': None,
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}]
+ ),
+ # Required_if utilizing default value of the requirement
+ ({'foobar': [{"foo": "bam2", "bar": "required_one_of"}]},
+ [{'bam': None, 'bam1': None, 'bam2': 'test', 'bam3': None, 'bam4': None, 'bar': 'required_one_of', 'baz': None, 'foo': 'bam2',
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}]
+ ),
+ # Check that a bool option is converted
+ ({"foobar": [{"foo": "required", "bam": "good", "bam3": "yes"}]},
+ [{'bam': 'good', 'bam1': None, 'bam2': 'test', 'bam3': True, 'bam4': None, 'bar': None, 'baz': None, 'foo': 'required',
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}]
+ ),
+ # Check required_by options
+ ({"foobar": [{"foo": "required", "bar": "good", "baz": "good", "bam4": "required_by", "bam1": "ok", "bam3": "yes"}]},
+ [{'bar': 'good', 'baz': 'good', 'bam1': 'ok', 'bam2': 'test', 'bam3': True, 'bam4': 'required_by', 'bam': None, 'foo': 'required',
+ '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]}]},
+ [{'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}]
+ ),
+ )
+
+ # (Parameters, expected value of module.params['foobar'])
+ OPTIONS_PARAMS_DICT = (
+ ({'foobar': {"foo": "hello", "bam": "good"}},
+ {'foo': 'hello', 'bam': 'good', 'bam2': 'test', 'bar': None, 'baz': None, 'bam1': None, 'bam3': None, 'bam4': None,
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}
+ ),
+ # Alias for required param
+ ({'foobar': {"dup": "test", "bar": "good"}},
+ {'foo': 'test', 'dup': 'test', 'bam': None, 'bam2': 'test', 'bar': 'good', 'baz': None, 'bam1': None, 'bam3': None, 'bam4': None,
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}
+ ),
+ # Required_if utilizing default value of the requirement
+ ({'foobar': {"foo": "bam2", "bar": "required_one_of"}},
+ {'bam': None, 'bam1': None, 'bam2': 'test', 'bam3': None, 'bam4': None, 'bar': 'required_one_of', 'baz': None, 'foo': 'bam2',
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}
+ ),
+ # Check that a bool option is converted
+ ({"foobar": {"foo": "required", "bam": "good", "bam3": "yes"}},
+ {'bam': 'good', 'bam1': None, 'bam2': 'test', 'bam3': True, 'bam4': None, 'bar': None, 'baz': None, 'foo': 'required',
+ 'bar1': None, 'bar2': None, 'bar3': None, 'bar4': None}
+ ),
+ # Check required_by options
+ ({"foobar": {"foo": "required", "bar": "good", "baz": "good", "bam4": "required_by", "bam1": "ok", "bam3": "yes"}},
+ {'bar': 'good', 'baz': 'good', 'bam1': 'ok', 'bam2': 'test', 'bam3': True, 'bam4': 'required_by', 'bam': None,
+ 'foo': 'required', 'bar1': None, 'bar3': None, 'bar2': 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]}},
+ {'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}
+ ),
+ )
+
+ # (Parameters, failure message)
+ FAILING_PARAMS_LIST = (
+ # Missing required option
+ ({'foobar': [{}]}, 'missing required arguments: foo found in foobar'),
+ # Invalid option
+ ({'foobar': [{"foo": "hello", "bam": "good", "invalid": "bad"}]}, 'module: foobar.invalid. Supported parameters include'),
+ # Mutually exclusive options found
+ ({'foobar': [{"foo": "test", "bam": "bad", "bam1": "bad", "baz": "req_to"}]},
+ 'parameters are mutually exclusive: bam|bam1 found in foobar'),
+ # required_if fails
+ ({'foobar': [{"foo": "hello", "bar": "bad"}]},
+ 'foo is hello but all of the following are missing: bam found in foobar'),
+ # Missing required_one_of option
+ ({'foobar': [{"foo": "test"}]},
+ 'one of the following is required: bar, bam found in foobar'),
+ # Missing required_together option
+ ({'foobar': [{"foo": "test", "bar": "required_one_of", "bam1": "bad"}]},
+ 'parameters are required together: bam1, baz found in foobar'),
+ # Missing required_by options
+ ({'foobar': [{"foo": "test", "bar": "required_one_of", "bam4": "required_by"}]},
+ "missing parameter(s) required by 'bam4': bam1, bam3"),
+ )
+
+ # (Parameters, failure message)
+ FAILING_PARAMS_DICT = (
+ # Missing required option
+ ({'foobar': {}}, 'missing required arguments: foo found in foobar'),
+ # Invalid option
+ ({'foobar': {"foo": "hello", "bam": "good", "invalid": "bad"}},
+ 'module: foobar.invalid. Supported parameters include'),
+ # Mutually exclusive options found
+ ({'foobar': {"foo": "test", "bam": "bad", "bam1": "bad", "baz": "req_to"}},
+ 'parameters are mutually exclusive: bam|bam1 found in foobar'),
+ # required_if fails
+ ({'foobar': {"foo": "hello", "bar": "bad"}},
+ 'foo is hello but all of the following are missing: bam found in foobar'),
+ # Missing required_one_of option
+ ({'foobar': {"foo": "test"}},
+ 'one of the following is required: bar, bam found in foobar'),
+ # Missing required_together option
+ ({'foobar': {"foo": "test", "bar": "required_one_of", "bam1": "bad"}},
+ 'parameters are required together: bam1, baz found in foobar'),
+ # Missing required_by options
+ ({'foobar': {"foo": "test", "bar": "required_one_of", "bam4": "required_by"}},
+ "missing parameter(s) required by 'bam4': bam1, bam3"),
+ )
+
+ @pytest.mark.parametrize('stdin, expected', OPTIONS_PARAMS_DICT, indirect=['stdin'])
+ def test_options_type_dict(self, stdin, options_argspec_dict, expected):
+ """Test that a basic creation with required and required_if works"""
+ # should test ok, tests basic foo requirement and required_if
+ am = basic.AnsibleModule(**options_argspec_dict)
+
+ assert isinstance(am.params['foobar'], dict)
+ assert am.params['foobar'] == expected
+
+ @pytest.mark.parametrize('stdin, expected', OPTIONS_PARAMS_LIST, indirect=['stdin'])
+ def test_options_type_list(self, stdin, options_argspec_list, expected):
+ """Test that a basic creation with required and required_if works"""
+ # should test ok, tests basic foo requirement and required_if
+ am = basic.AnsibleModule(**options_argspec_list)
+
+ assert isinstance(am.params['foobar'], list)
+ assert am.params['foobar'] == expected
+
+ @pytest.mark.parametrize('stdin, expected', FAILING_PARAMS_DICT, indirect=['stdin'])
+ def test_fail_validate_options_dict(self, capfd, stdin, options_argspec_dict, expected):
+ """Fail because one of a required_together pair of parameters has a default and the other was not specified"""
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**options_argspec_dict)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert expected in results['msg']
+
+ @pytest.mark.parametrize('stdin, expected', FAILING_PARAMS_LIST, indirect=['stdin'])
+ def test_fail_validate_options_list(self, capfd, stdin, options_argspec_list, expected):
+ """Fail because one of a required_together pair of parameters has a default and the other was not specified"""
+ with pytest.raises(SystemExit):
+ am = basic.AnsibleModule(**options_argspec_list)
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert results['failed']
+ assert expected in results['msg']
+
+ @pytest.mark.parametrize('stdin', [{'foobar': {'foo': 'required', 'bam1': 'test', 'bar': 'case'}}], indirect=['stdin'])
+ def test_fallback_in_option(self, mocker, stdin, options_argspec_dict):
+ """Test that the complex argspec works if we get a required parameter via fallback"""
+ environ = os.environ.copy()
+ environ['BAZ'] = 'test data'
+ mocker.patch('ansible.module_utils.basic.os.environ', environ)
+
+ am = basic.AnsibleModule(**options_argspec_dict)
+
+ assert isinstance(am.params['foobar']['baz'], str)
+ assert am.params['foobar']['baz'] == 'test data'
+
+ @pytest.mark.parametrize('stdin',
+ [{'foobar': {'foo': 'required', 'bam1': 'test', 'baz': 'data', 'bar': 'case', 'bar4': '~/test'}}],
+ indirect=['stdin'])
+ def test_elements_path_in_option(self, mocker, stdin, options_argspec_dict):
+ """Test that the complex argspec works with elements path type"""
+
+ am = basic.AnsibleModule(**options_argspec_dict)
+
+ assert isinstance(am.params['foobar']['bar4'][0], str)
+ assert am.params['foobar']['bar4'][0].startswith('/')
+
+ @pytest.mark.parametrize('stdin,spec,expected', [
+ ({},
+ {'one': {'type': 'dict', 'apply_defaults': True, 'options': {'two': {'default': True, 'type': 'bool'}}}},
+ {'two': True}),
+ ({},
+ {'one': {'type': 'dict', 'options': {'two': {'default': True, 'type': 'bool'}}}},
+ None),
+ ], indirect=['stdin'])
+ def test_subspec_not_required_defaults(self, stdin, spec, expected):
+ # Check that top level not required, processed subspec defaults
+ am = basic.AnsibleModule(spec)
+ assert am.params['one'] == expected
+
+
+class TestLoadFileCommonArguments:
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_smoketest_load_file_common_args(self, am):
+ """With no file arguments, an empty dict is returned"""
+ am.selinux_mls_enabled = MagicMock()
+ am.selinux_mls_enabled.return_value = True
+ am.selinux_default_context = MagicMock()
+ am.selinux_default_context.return_value = 'unconfined_u:object_r:default_t:s0'.split(':', 3)
+
+ assert am.load_file_common_arguments(params={}) == {}
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_load_file_common_args(self, am, mocker):
+ am.selinux_mls_enabled = MagicMock()
+ am.selinux_mls_enabled.return_value = True
+ am.selinux_default_context = MagicMock()
+ am.selinux_default_context.return_value = 'unconfined_u:object_r:default_t:s0'.split(':', 3)
+
+ base_params = dict(
+ path='/path/to/file',
+ mode=0o600,
+ owner='root',
+ group='root',
+ seuser='_default',
+ serole='_default',
+ setype='_default',
+ selevel='_default',
+ )
+
+ extended_params = base_params.copy()
+ extended_params.update(dict(
+ follow=True,
+ foo='bar',
+ ))
+
+ final_params = base_params.copy()
+ final_params.update(dict(
+ path='/path/to/real_file',
+ secontext=['unconfined_u', 'object_r', 'default_t', 's0'],
+ attributes=None,
+ ))
+
+ # with the proper params specified, the returned dictionary should represent
+ # only those params which have something to do with the file arguments, excluding
+ # other params and updated as required with proper values which may have been
+ # massaged by the method
+ mocker.patch('os.path.islink', return_value=True)
+ mocker.patch('os.path.realpath', return_value='/path/to/real_file')
+
+ res = am.load_file_common_arguments(params=extended_params)
+
+ assert res == final_params
+
+
+@pytest.mark.parametrize("stdin", [{"arg_pass": "testing"}], indirect=["stdin"])
+def test_no_log_true(stdin, capfd):
+ """Explicitly mask an argument (no_log=True)."""
+ arg_spec = {
+ "arg_pass": {"no_log": True}
+ }
+ am = basic.AnsibleModule(arg_spec)
+ # no_log=True is picked up by both am._log_invocation and list_no_log_values
+ # (called by am._handle_no_log_values). As a result, we can check for the
+ # value in am.no_log_values.
+ assert "testing" in am.no_log_values
+
+
+@pytest.mark.parametrize("stdin", [{"arg_pass": "testing"}], indirect=["stdin"])
+def test_no_log_false(stdin, capfd):
+ """Explicitly log and display an argument (no_log=False)."""
+ arg_spec = {
+ "arg_pass": {"no_log": False}
+ }
+ am = basic.AnsibleModule(arg_spec)
+ assert "testing" not in am.no_log_values and not get_warning_messages()
+
+
+@pytest.mark.parametrize("stdin", [{"arg_pass": "testing"}], indirect=["stdin"])
+def test_no_log_none(stdin, capfd):
+ """Allow Ansible to make the decision by matching the argument name
+ against PASSWORD_MATCH."""
+ arg_spec = {
+ "arg_pass": {}
+ }
+ am = basic.AnsibleModule(arg_spec)
+ # Omitting no_log is only picked up by _log_invocation, so the value never
+ # makes it into am.no_log_values. Instead we can check for the warning
+ # emitted by am._log_invocation.
+ assert len(get_warning_messages()) > 0
+
+
+@pytest.mark.parametrize("stdin", [{"pass": "testing"}], indirect=["stdin"])
+def test_no_log_alias(stdin, capfd):
+ """Given module parameters that use an alias for a parameter that matches
+ PASSWORD_MATCH and has no_log=True set, a warning should not be issued.
+ """
+ arg_spec = {
+ "other_pass": {"no_log": True, "aliases": ["pass"]},
+ }
+ am = basic.AnsibleModule(arg_spec)
+
+ assert len(get_warning_messages()) == 0
diff --git a/test/units/module_utils/basic/test_atomic_move.py b/test/units/module_utils/basic/test_atomic_move.py
new file mode 100644
index 0000000..bbdb051
--- /dev/null
+++ b/test/units/module_utils/basic/test_atomic_move.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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 errno
+import json
+from itertools import product
+
+import pytest
+
+from ansible.module_utils import basic
+
+
+@pytest.fixture
+def atomic_am(am, mocker):
+ am.selinux_enabled = mocker.MagicMock()
+ am.selinux_context = mocker.MagicMock()
+ am.selinux_default_context = mocker.MagicMock()
+ am.set_context_if_different = mocker.MagicMock()
+ am._unsafe_writes = mocker.MagicMock()
+
+ yield am
+
+
+@pytest.fixture
+def atomic_mocks(mocker, monkeypatch):
+ environ = dict()
+ mocks = {
+ 'chmod': mocker.patch('os.chmod'),
+ 'chown': mocker.patch('os.chown'),
+ 'close': mocker.patch('os.close'),
+ 'environ': mocker.patch('os.environ', environ),
+ 'getlogin': mocker.patch('os.getlogin'),
+ 'getuid': mocker.patch('os.getuid'),
+ 'path_exists': mocker.patch('os.path.exists'),
+ 'rename': mocker.patch('os.rename'),
+ 'stat': mocker.patch('os.stat'),
+ 'umask': mocker.patch('os.umask'),
+ 'getpwuid': mocker.patch('pwd.getpwuid'),
+ 'copy2': mocker.patch('shutil.copy2'),
+ 'copyfileobj': mocker.patch('shutil.copyfileobj'),
+ 'move': mocker.patch('shutil.move'),
+ 'mkstemp': mocker.patch('tempfile.mkstemp'),
+ }
+
+ mocks['getlogin'].return_value = 'root'
+ mocks['getuid'].return_value = 0
+ mocks['getpwuid'].return_value = ('root', '', 0, 0, '', '', '')
+ mocks['umask'].side_effect = [18, 0]
+ mocks['rename'].return_value = None
+
+ # normalize OS specific features
+ monkeypatch.delattr(os, 'chflags', raising=False)
+
+ yield mocks
+
+
+@pytest.fixture
+def fake_stat(mocker):
+ stat1 = mocker.MagicMock()
+ stat1.st_mode = 0o0644
+ stat1.st_uid = 0
+ stat1.st_gid = 0
+ stat1.st_flags = 0
+ yield stat1
+
+
+@pytest.mark.parametrize('stdin, selinux', product([{}], (True, False)), indirect=['stdin'])
+def test_new_file(atomic_am, atomic_mocks, mocker, selinux):
+ # test destination does not exist, login name = 'root', no environment, os.rename() succeeds
+ mock_context = atomic_am.selinux_default_context.return_value
+ atomic_mocks['path_exists'].return_value = False
+ atomic_am.selinux_enabled.return_value = selinux
+
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
+
+ if selinux:
+ assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
+ assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
+ else:
+ assert not atomic_am.selinux_default_context.called
+ assert not atomic_am.set_context_if_different.called
+
+
+@pytest.mark.parametrize('stdin, selinux', product([{}], (True, False)), indirect=['stdin'])
+def test_existing_file(atomic_am, atomic_mocks, fake_stat, mocker, selinux):
+ # Test destination already present
+ mock_context = atomic_am.selinux_context.return_value
+ atomic_mocks['stat'].return_value = fake_stat
+ atomic_mocks['path_exists'].return_value = True
+ atomic_am.selinux_enabled.return_value = selinux
+
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
+
+ if selinux:
+ assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
+ assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
+ else:
+ assert not atomic_am.selinux_default_context.called
+ assert not atomic_am.set_context_if_different.called
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_no_tty_fallback(atomic_am, atomic_mocks, fake_stat, mocker):
+ """Raise OSError when using getlogin() to simulate no tty cornercase"""
+ mock_context = atomic_am.selinux_context.return_value
+ atomic_mocks['stat'].return_value = fake_stat
+ atomic_mocks['path_exists'].return_value = True
+ atomic_am.selinux_enabled.return_value = True
+ atomic_mocks['getlogin'].side_effect = OSError()
+ atomic_mocks['environ']['LOGNAME'] = 'root'
+
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
+
+ assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
+ assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_existing_file_stat_failure(atomic_am, atomic_mocks, mocker):
+ """Failure to stat an existing file in order to copy permissions propogates the error (unless EPERM)"""
+ atomic_mocks['stat'].side_effect = OSError()
+ atomic_mocks['path_exists'].return_value = True
+
+ with pytest.raises(OSError):
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_existing_file_stat_perms_failure(atomic_am, atomic_mocks, mocker):
+ """Failure to stat an existing file to copy the permissions due to permissions passes fine"""
+ # and now have os.stat return EPERM, which should not fail
+ mock_context = atomic_am.selinux_context.return_value
+ atomic_mocks['stat'].side_effect = OSError(errno.EPERM, 'testing os stat with EPERM')
+ atomic_mocks['path_exists'].return_value = True
+ atomic_am.selinux_enabled.return_value = True
+
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
+ # FIXME: Should atomic_move() set a default permission value when it cannot retrieve the
+ # existing file's permissions? (Right now it's up to the calling code.
+ # assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
+ assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
+ assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_rename_failure(atomic_am, atomic_mocks, mocker, capfd):
+ """Test os.rename fails with EIO, causing it to bail out"""
+ atomic_mocks['path_exists'].side_effect = [False, False]
+ atomic_mocks['rename'].side_effect = OSError(errno.EIO, 'failing with EIO')
+
+ with pytest.raises(SystemExit):
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert 'Could not replace file' in results['msg']
+ assert 'failing with EIO' in results['msg']
+ assert results['failed']
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_rename_perms_fail_temp_creation_fails(atomic_am, atomic_mocks, mocker, capfd):
+ """Test os.rename fails with EPERM working but failure in mkstemp"""
+ atomic_mocks['path_exists'].return_value = False
+ atomic_mocks['close'].return_value = None
+ atomic_mocks['rename'].side_effect = [OSError(errno.EPERM, 'failing with EPERM'), None]
+ atomic_mocks['mkstemp'].return_value = None
+ atomic_mocks['mkstemp'].side_effect = OSError()
+ atomic_am.selinux_enabled.return_value = False
+
+ with pytest.raises(SystemExit):
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+
+ out, err = capfd.readouterr()
+ results = json.loads(out)
+
+ assert 'is not writable by the current user' in results['msg']
+ assert results['failed']
+
+
+@pytest.mark.parametrize('stdin, selinux', product([{}], (True, False)), indirect=['stdin'])
+def test_rename_perms_fail_temp_succeeds(atomic_am, atomic_mocks, fake_stat, mocker, selinux):
+ """Test os.rename raising an error but fallback to using mkstemp works"""
+ mock_context = atomic_am.selinux_default_context.return_value
+ atomic_mocks['path_exists'].return_value = False
+ atomic_mocks['rename'].side_effect = [OSError(errno.EPERM, 'failing with EPERM'), None]
+ atomic_mocks['stat'].return_value = fake_stat
+ atomic_mocks['stat'].side_effect = None
+ atomic_mocks['mkstemp'].return_value = (None, '/path/to/tempfile')
+ atomic_mocks['mkstemp'].side_effect = None
+ atomic_am.selinux_enabled.return_value = selinux
+
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
+ assert atomic_mocks['rename'].call_args_list == [mocker.call(b'/path/to/src', b'/path/to/dest'),
+ mocker.call(b'/path/to/tempfile', b'/path/to/dest')]
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
+
+ if selinux:
+ assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
+ assert atomic_am.set_context_if_different.call_args_list == [mocker.call(b'/path/to/tempfile', mock_context, False),
+ mocker.call('/path/to/dest', mock_context, False)]
+ else:
+ assert not atomic_am.selinux_default_context.called
+ assert not atomic_am.set_context_if_different.called
diff --git a/test/units/module_utils/basic/test_command_nonexisting.py b/test/units/module_utils/basic/test_command_nonexisting.py
new file mode 100644
index 0000000..6ed7f91
--- /dev/null
+++ b/test/units/module_utils/basic/test_command_nonexisting.py
@@ -0,0 +1,31 @@
+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 import basic
+
+
+def test_run_non_existent_command(monkeypatch):
+ """ Test that `command` returns std{out,err} even if the executable is not found """
+ def fail_json(msg, **kwargs):
+ assert kwargs["stderr"] == b''
+ assert kwargs["stdout"] == b''
+ sys.exit(1)
+
+ def popen(*args, **kwargs):
+ raise OSError()
+
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ monkeypatch.setattr(subprocess, 'Popen', popen)
+
+ am = basic.AnsibleModule(argument_spec={})
+ monkeypatch.setattr(am, 'fail_json', fail_json)
+ with pytest.raises(SystemExit):
+ am.run_command("lecho", "whatever")
diff --git a/test/units/module_utils/basic/test_deprecate_warn.py b/test/units/module_utils/basic/test_deprecate_warn.py
new file mode 100644
index 0000000..581ba6d
--- /dev/null
+++ b/test/units/module_utils/basic/test_deprecate_warn.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2017 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 json
+
+import pytest
+
+from ansible.module_utils.common import warnings
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_warn(am, capfd):
+
+ am.warn('warning1')
+
+ with pytest.raises(SystemExit):
+ am.exit_json(warnings=['warning2'])
+ out, err = capfd.readouterr()
+ assert json.loads(out)['warnings'] == ['warning1', 'warning2']
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_deprecate(am, capfd, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ am.deprecate('deprecation1')
+ am.deprecate('deprecation2', '2.3') # pylint: disable=ansible-deprecated-no-collection-name
+ am.deprecate('deprecation3', version='2.4') # pylint: disable=ansible-deprecated-no-collection-name
+ am.deprecate('deprecation4', date='2020-03-10') # pylint: disable=ansible-deprecated-no-collection-name
+ am.deprecate('deprecation5', collection_name='ansible.builtin')
+ am.deprecate('deprecation6', '2.3', collection_name='ansible.builtin')
+ am.deprecate('deprecation7', version='2.4', collection_name='ansible.builtin')
+ am.deprecate('deprecation8', date='2020-03-10', collection_name='ansible.builtin')
+
+ with pytest.raises(SystemExit):
+ am.exit_json(deprecations=['deprecation9', ('deprecation10', '2.4')])
+
+ out, err = capfd.readouterr()
+ output = json.loads(out)
+ assert ('warnings' not in output or output['warnings'] == [])
+ assert output['deprecations'] == [
+ {u'msg': u'deprecation1', u'version': None, u'collection_name': None},
+ {u'msg': u'deprecation2', u'version': '2.3', u'collection_name': None},
+ {u'msg': u'deprecation3', u'version': '2.4', u'collection_name': None},
+ {u'msg': u'deprecation4', u'date': '2020-03-10', u'collection_name': None},
+ {u'msg': u'deprecation5', u'version': None, u'collection_name': 'ansible.builtin'},
+ {u'msg': u'deprecation6', u'version': '2.3', u'collection_name': 'ansible.builtin'},
+ {u'msg': u'deprecation7', u'version': '2.4', u'collection_name': 'ansible.builtin'},
+ {u'msg': u'deprecation8', u'date': '2020-03-10', u'collection_name': 'ansible.builtin'},
+ {u'msg': u'deprecation9', u'version': None, u'collection_name': None},
+ {u'msg': u'deprecation10', u'version': '2.4', u'collection_name': None},
+ ]
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_deprecate_without_list(am, capfd):
+ with pytest.raises(SystemExit):
+ am.exit_json(deprecations='Simple deprecation warning')
+
+ out, err = capfd.readouterr()
+ output = json.loads(out)
+ assert ('warnings' not in output or output['warnings'] == [])
+ assert output['deprecations'] == [
+ {u'msg': u'Simple deprecation warning', u'version': None, u'collection_name': None},
+ ]
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_deprecate_without_list_version_date_not_set(am, capfd):
+ with pytest.raises(AssertionError) as ctx:
+ am.deprecate('Simple deprecation warning', date='', version='')
+ assert ctx.value.args[0] == "implementation error -- version and date must not both be set"
diff --git a/test/units/module_utils/basic/test_dict_converters.py b/test/units/module_utils/basic/test_dict_converters.py
new file mode 100644
index 0000000..f63ed9c
--- /dev/null
+++ b/test/units/module_utils/basic/test_dict_converters.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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
+
+from units.mock.procenv import ModuleTestCase
+
+from ansible.module_utils.six.moves import builtins
+
+realimport = builtins.__import__
+
+
+class TestTextifyContainers(ModuleTestCase):
+ def test_module_utils_basic_json_dict_converters(self):
+ from ansible.module_utils.basic import json_dict_unicode_to_bytes, json_dict_bytes_to_unicode
+
+ test_data = dict(
+ item1=u"Fóo",
+ item2=[u"Bár", u"Bam"],
+ item3=dict(sub1=u"Súb"),
+ item4=(u"föo", u"bär", u"©"),
+ item5=42,
+ )
+ res = json_dict_unicode_to_bytes(test_data)
+ res2 = json_dict_bytes_to_unicode(res)
+
+ self.assertEqual(test_data, res2)
diff --git a/test/units/module_utils/basic/test_exit_json.py b/test/units/module_utils/basic/test_exit_json.py
new file mode 100644
index 0000000..4afcb27
--- /dev/null
+++ b/test/units/module_utils/basic/test_exit_json.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015-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
+
+import json
+import sys
+import datetime
+
+import pytest
+
+from ansible.module_utils.common import warnings
+
+EMPTY_INVOCATION = {u'module_args': {}}
+DATETIME = datetime.datetime.strptime('2020-07-13 12:50:00', '%Y-%m-%d %H:%M:%S')
+
+
+class TestAnsibleModuleExitJson:
+ """
+ Test that various means of calling exitJson and FailJson return the messages they've been given
+ """
+ DATA = (
+ ({}, {'invocation': EMPTY_INVOCATION}),
+ ({'msg': 'message'}, {'msg': 'message', 'invocation': EMPTY_INVOCATION}),
+ ({'msg': 'success', 'changed': True},
+ {'msg': 'success', 'changed': True, 'invocation': EMPTY_INVOCATION}),
+ ({'msg': 'nochange', 'changed': False},
+ {'msg': 'nochange', 'changed': False, 'invocation': EMPTY_INVOCATION}),
+ ({'msg': 'message', 'date': DATETIME.date()},
+ {'msg': 'message', 'date': DATETIME.date().isoformat(), 'invocation': EMPTY_INVOCATION}),
+ ({'msg': 'message', 'datetime': DATETIME},
+ {'msg': 'message', 'datetime': DATETIME.isoformat(), 'invocation': EMPTY_INVOCATION}),
+ )
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ # pylint: disable=undefined-variable
+ @pytest.mark.parametrize('args, expected, stdin', ((a, e, {}) for a, e in DATA), indirect=['stdin'])
+ def test_exit_json_exits(self, am, capfd, args, expected, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ with pytest.raises(SystemExit) as ctx:
+ am.exit_json(**args)
+ assert ctx.value.code == 0
+
+ out, err = capfd.readouterr()
+ return_val = json.loads(out)
+ assert return_val == expected
+
+ # Fail_json is only legal if it's called with a message
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('args, expected, stdin',
+ ((a, e, {}) for a, e in DATA if 'msg' in a), # pylint: disable=undefined-variable
+ indirect=['stdin'])
+ def test_fail_json_exits(self, am, capfd, args, expected, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ with pytest.raises(SystemExit) as ctx:
+ am.fail_json(**args)
+ assert ctx.value.code == 1
+
+ out, err = capfd.readouterr()
+ return_val = json.loads(out)
+ # Fail_json should add failed=True
+ expected['failed'] = True
+ assert return_val == expected
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_fail_json_msg_positional(self, am, capfd, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ with pytest.raises(SystemExit) as ctx:
+ am.fail_json('This is the msg')
+ assert ctx.value.code == 1
+
+ out, err = capfd.readouterr()
+ return_val = json.loads(out)
+ # Fail_json should add failed=True
+ assert return_val == {'msg': 'This is the msg', 'failed': True,
+ 'invocation': EMPTY_INVOCATION}
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_fail_json_msg_as_kwarg_after(self, am, capfd, monkeypatch):
+ """Test that msg as a kwarg after other kwargs works"""
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ with pytest.raises(SystemExit) as ctx:
+ am.fail_json(arbitrary=42, msg='This is the msg')
+ assert ctx.value.code == 1
+
+ out, err = capfd.readouterr()
+ return_val = json.loads(out)
+ # Fail_json should add failed=True
+ assert return_val == {'msg': 'This is the msg', 'failed': True,
+ 'arbitrary': 42,
+ 'invocation': EMPTY_INVOCATION}
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_fail_json_no_msg(self, am):
+ with pytest.raises(TypeError) as ctx:
+ am.fail_json()
+
+ if sys.version_info < (3,):
+ error_msg = "fail_json() takes exactly 2 arguments (1 given)"
+ elif sys.version_info >= (3, 10):
+ error_msg = "AnsibleModule.fail_json() missing 1 required positional argument: 'msg'"
+ else:
+ error_msg = "fail_json() missing 1 required positional argument: 'msg'"
+
+ assert ctx.value.args[0] == error_msg
+
+
+class TestAnsibleModuleExitValuesRemoved:
+ """
+ Test that ExitJson and FailJson remove password-like values
+ """
+ OMIT = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
+
+ DATA = (
+ (
+ dict(username='person', password='$ecret k3y'),
+ dict(one=1, pwd='$ecret k3y', url='https://username:password12345@foo.com/login/',
+ not_secret='following the leader', msg='here'),
+ dict(one=1, pwd=OMIT, url='https://username:password12345@foo.com/login/',
+ not_secret='following the leader', msg='here',
+ invocation=dict(module_args=dict(password=OMIT, token=None, username='person'))),
+ ),
+ (
+ dict(username='person', password='password12345'),
+ dict(one=1, pwd='$ecret k3y', url='https://username:password12345@foo.com/login/',
+ not_secret='following the leader', msg='here'),
+ dict(one=1, pwd='$ecret k3y', url='https://username:********@foo.com/login/',
+ not_secret='following the leader', msg='here',
+ invocation=dict(module_args=dict(password=OMIT, token=None, username='person'))),
+ ),
+ (
+ dict(username='person', password='$ecret k3y'),
+ dict(one=1, pwd='$ecret k3y', url='https://username:$ecret k3y@foo.com/login/',
+ not_secret='following the leader', msg='here'),
+ dict(one=1, pwd=OMIT, url='https://username:********@foo.com/login/',
+ not_secret='following the leader', msg='here',
+ invocation=dict(module_args=dict(password=OMIT, token=None, username='person'))),
+ ),
+ )
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('am, stdin, return_val, expected',
+ (({'username': {}, 'password': {'no_log': True}, 'token': {'no_log': True}}, s, r, e)
+ for s, r, e in DATA), # pylint: disable=undefined-variable
+ indirect=['am', 'stdin'])
+ def test_exit_json_removes_values(self, am, capfd, return_val, expected, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+ with pytest.raises(SystemExit):
+ am.exit_json(**return_val)
+ out, err = capfd.readouterr()
+
+ assert json.loads(out) == expected
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('am, stdin, return_val, expected',
+ (({'username': {}, 'password': {'no_log': True}, 'token': {'no_log': True}}, s, r, e)
+ for s, r, e in DATA), # pylint: disable=undefined-variable
+ indirect=['am', 'stdin'])
+ def test_fail_json_removes_values(self, am, capfd, return_val, expected, monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+ expected['failed'] = True
+ with pytest.raises(SystemExit):
+ am.fail_json(**return_val) == expected
+ out, err = capfd.readouterr()
+
+ assert json.loads(out) == expected
diff --git a/test/units/module_utils/basic/test_filesystem.py b/test/units/module_utils/basic/test_filesystem.py
new file mode 100644
index 0000000..f09cecf
--- /dev/null
+++ b/test/units/module_utils/basic/test_filesystem.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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
+
+from units.mock.procenv import ModuleTestCase
+
+from units.compat.mock import patch, MagicMock
+from ansible.module_utils.six.moves import builtins
+
+realimport = builtins.__import__
+
+
+class TestOtherFilesystem(ModuleTestCase):
+ def test_module_utils_basic_ansible_module_user_and_group(self):
+ from ansible.module_utils import basic
+ basic._ANSIBLE_ARGS = None
+
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ mock_stat = MagicMock()
+ mock_stat.st_uid = 0
+ mock_stat.st_gid = 0
+
+ with patch('os.lstat', return_value=mock_stat):
+ self.assertEqual(am.user_and_group('/path/to/file'), (0, 0))
+
+ def test_module_utils_basic_ansible_module_find_mount_point(self):
+ from ansible.module_utils import basic
+ basic._ANSIBLE_ARGS = None
+
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ def _mock_ismount(path):
+ if path == b'/':
+ return True
+ return False
+
+ with patch('os.path.ismount', side_effect=_mock_ismount):
+ self.assertEqual(am.find_mount_point('/root/fs/../mounted/path/to/whatever'), '/')
+
+ def _mock_ismount(path):
+ if path == b'/subdir/mount':
+ return True
+ if path == b'/':
+ return True
+ return False
+
+ with patch('os.path.ismount', side_effect=_mock_ismount):
+ self.assertEqual(am.find_mount_point('/subdir/mount/path/to/whatever'), '/subdir/mount')
+
+ def test_module_utils_basic_ansible_module_set_owner_if_different(self):
+ from ansible.module_utils import basic
+ basic._ANSIBLE_ARGS = None
+
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ self.assertEqual(am.set_owner_if_different('/path/to/file', None, True), True)
+ self.assertEqual(am.set_owner_if_different('/path/to/file', None, False), False)
+
+ am.user_and_group = MagicMock(return_value=(500, 500))
+
+ with patch('os.lchown', return_value=None) as m:
+ self.assertEqual(am.set_owner_if_different('/path/to/file', 0, False), True)
+ m.assert_called_with(b'/path/to/file', 0, -1)
+
+ def _mock_getpwnam(*args, **kwargs):
+ mock_pw = MagicMock()
+ mock_pw.pw_uid = 0
+ return mock_pw
+
+ m.reset_mock()
+ with patch('pwd.getpwnam', side_effect=_mock_getpwnam):
+ self.assertEqual(am.set_owner_if_different('/path/to/file', 'root', False), True)
+ m.assert_called_with(b'/path/to/file', 0, -1)
+
+ with patch('pwd.getpwnam', side_effect=KeyError):
+ self.assertRaises(SystemExit, am.set_owner_if_different, '/path/to/file', 'root', False)
+
+ m.reset_mock()
+ am.check_mode = True
+ self.assertEqual(am.set_owner_if_different('/path/to/file', 0, False), True)
+ self.assertEqual(m.called, False)
+ am.check_mode = False
+
+ with patch('os.lchown', side_effect=OSError) as m:
+ self.assertRaises(SystemExit, am.set_owner_if_different, '/path/to/file', 'root', False)
+
+ def test_module_utils_basic_ansible_module_set_group_if_different(self):
+ from ansible.module_utils import basic
+ basic._ANSIBLE_ARGS = None
+
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ self.assertEqual(am.set_group_if_different('/path/to/file', None, True), True)
+ self.assertEqual(am.set_group_if_different('/path/to/file', None, False), False)
+
+ am.user_and_group = MagicMock(return_value=(500, 500))
+
+ with patch('os.lchown', return_value=None) as m:
+ self.assertEqual(am.set_group_if_different('/path/to/file', 0, False), True)
+ m.assert_called_with(b'/path/to/file', -1, 0)
+
+ def _mock_getgrnam(*args, **kwargs):
+ mock_gr = MagicMock()
+ mock_gr.gr_gid = 0
+ return mock_gr
+
+ m.reset_mock()
+ with patch('grp.getgrnam', side_effect=_mock_getgrnam):
+ self.assertEqual(am.set_group_if_different('/path/to/file', 'root', False), True)
+ m.assert_called_with(b'/path/to/file', -1, 0)
+
+ with patch('grp.getgrnam', side_effect=KeyError):
+ self.assertRaises(SystemExit, am.set_group_if_different, '/path/to/file', 'root', False)
+
+ m.reset_mock()
+ am.check_mode = True
+ self.assertEqual(am.set_group_if_different('/path/to/file', 0, False), True)
+ self.assertEqual(m.called, False)
+ am.check_mode = False
+
+ with patch('os.lchown', side_effect=OSError) as m:
+ self.assertRaises(SystemExit, am.set_group_if_different, '/path/to/file', 'root', False)
+
+ def test_module_utils_basic_ansible_module_set_directory_attributes_if_different(self):
+ from ansible.module_utils import basic
+ basic._ANSIBLE_ARGS = None
+
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ file_args = {
+ 'path': '/path/to/file',
+ 'mode': None,
+ 'owner': None,
+ 'group': None,
+ 'seuser': None,
+ 'serole': None,
+ 'setype': None,
+ 'selevel': None,
+ 'secontext': [None, None, None],
+ 'attributes': None,
+ }
+
+ self.assertEqual(am.set_directory_attributes_if_different(file_args, True), True)
+ self.assertEqual(am.set_directory_attributes_if_different(file_args, False), False)
diff --git a/test/units/module_utils/basic/test_get_file_attributes.py b/test/units/module_utils/basic/test_get_file_attributes.py
new file mode 100644
index 0000000..836529c
--- /dev/null
+++ b/test/units/module_utils/basic/test_get_file_attributes.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# Copyright:
+# (c) 2017, Pierre-Louis Bonicoli <pierre-louis@libregerbil.fr>
+# License: 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 itertools import product
+
+from ansible.module_utils.basic import AnsibleModule
+
+import pytest
+
+
+DATA = (
+ (
+ '3353595900 --------------e---- /usr/lib32',
+ {'attr_flags': 'e', 'version': '3353595900', 'attributes': ['extents']}
+ ),
+ # with e2fsprogs < 1.43, output isn't aligned
+ (
+ '78053594 -----------I--e---- /usr/lib',
+ {'attr_flags': 'Ie', 'version': '78053594', 'attributes': ['indexed', 'extents']}
+ ),
+ (
+ '15711607 -------A------e---- /tmp/test',
+ {'attr_flags': 'Ae', 'version': '15711607', 'attributes': ['noatime', 'extents']}
+ ),
+ # with e2fsprogs >= 1.43, output is aligned
+ (
+ '78053594 -----------I--e---- /usr/lib',
+ {'attr_flags': 'Ie', 'version': '78053594', 'attributes': ['indexed', 'extents']}
+ ),
+ (
+ '15711607 -------A------e---- /tmp/test',
+ {'attr_flags': 'Ae', 'version': '15711607', 'attributes': ['noatime', 'extents']}
+ ),
+)
+
+NO_VERSION_DATA = (
+ (
+ '--------------e---- /usr/lib32',
+ {'attr_flags': 'e', 'attributes': ['extents']}
+ ),
+ (
+ '-----------I--e---- /usr/lib',
+ {'attr_flags': 'Ie', 'attributes': ['indexed', 'extents']}
+ ),
+ (
+ '-------A------e---- /tmp/test',
+ {'attr_flags': 'Ae', 'attributes': ['noatime', 'extents']}
+ ),
+)
+
+
+@pytest.mark.parametrize('stdin, data', product(({},), DATA), indirect=['stdin'])
+def test_get_file_attributes(am, stdin, mocker, data):
+ # Test #18731
+ mocker.patch.object(AnsibleModule, 'get_bin_path', return_value=(0, '/usr/bin/lsattr', ''))
+ mocker.patch.object(AnsibleModule, 'run_command', return_value=(0, data[0], ''))
+ result = am.get_file_attributes('/path/to/file')
+ for key, value in data[1].items():
+ assert key in result and result[key] == value
+
+
+@pytest.mark.parametrize('stdin, data', product(({},), NO_VERSION_DATA), indirect=['stdin'])
+def test_get_file_attributes_no_version(am, stdin, mocker, data):
+ # Test #18731
+ mocker.patch.object(AnsibleModule, 'get_bin_path', return_value=(0, '/usr/bin/lsattr', ''))
+ mocker.patch.object(AnsibleModule, 'run_command', return_value=(0, data[0], ''))
+ result = am.get_file_attributes('/path/to/file', include_version=False)
+ for key, value in data[1].items():
+ assert key in result and result[key] == value
diff --git a/test/units/module_utils/basic/test_get_module_path.py b/test/units/module_utils/basic/test_get_module_path.py
new file mode 100644
index 0000000..6ff4a3b
--- /dev/null
+++ b/test/units/module_utils/basic/test_get_module_path.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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
+
+from units.mock.procenv import ModuleTestCase
+
+from units.compat.mock import patch
+from ansible.module_utils.six.moves import builtins
+
+realimport = builtins.__import__
+
+
+class TestGetModulePath(ModuleTestCase):
+ def test_module_utils_basic_get_module_path(self):
+ from ansible.module_utils.basic import get_module_path
+ with patch('os.path.realpath', return_value='/path/to/foo/'):
+ self.assertEqual(get_module_path(), '/path/to/foo')
diff --git a/test/units/module_utils/basic/test_heuristic_log_sanitize.py b/test/units/module_utils/basic/test_heuristic_log_sanitize.py
new file mode 100644
index 0000000..664b8a5
--- /dev/null
+++ b/test/units/module_utils/basic/test_heuristic_log_sanitize.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat import unittest
+from ansible.module_utils.basic import heuristic_log_sanitize
+
+
+class TestHeuristicLogSanitize(unittest.TestCase):
+ def setUp(self):
+ self.URL_SECRET = 'http://username:pas:word@foo.com/data'
+ self.SSH_SECRET = 'username:pas:word@foo.com/data'
+ self.clean_data = repr(self._gen_data(3, True, True, 'no_secret_here'))
+ self.url_data = repr(self._gen_data(3, True, True, self.URL_SECRET))
+ self.ssh_data = repr(self._gen_data(3, True, True, self.SSH_SECRET))
+
+ def _gen_data(self, records, per_rec, top_level, secret_text):
+ hostvars = {'hostvars': {}}
+ for i in range(1, records, 1):
+ host_facts = {
+ 'host%s' % i: {
+ 'pstack': {
+ 'running': '875.1',
+ 'symlinked': '880.0',
+ 'tars': [],
+ 'versions': ['885.0']
+ },
+ }
+ }
+ if per_rec:
+ host_facts['host%s' % i]['secret'] = secret_text
+ hostvars['hostvars'].update(host_facts)
+ if top_level:
+ hostvars['secret'] = secret_text
+ return hostvars
+
+ def test_did_not_hide_too_much(self):
+ self.assertEqual(heuristic_log_sanitize(self.clean_data), self.clean_data)
+
+ def test_hides_url_secrets(self):
+ url_output = heuristic_log_sanitize(self.url_data)
+ # Basic functionality: Successfully hid the password
+ self.assertNotIn('pas:word', url_output)
+
+ # Slightly more advanced, we hid all of the password despite the ":"
+ self.assertNotIn('pas', url_output)
+
+ # In this implementation we replace the password with 8 "*" which is
+ # also the length of our password. The url fields should be able to
+ # accurately detect where the password ends so the length should be
+ # the same:
+ self.assertEqual(len(url_output), len(self.url_data))
+
+ def test_hides_ssh_secrets(self):
+ ssh_output = heuristic_log_sanitize(self.ssh_data)
+ self.assertNotIn('pas:word', ssh_output)
+
+ # Slightly more advanced, we hid all of the password despite the ":"
+ self.assertNotIn('pas', ssh_output)
+
+ # ssh checking is harder as the heuristic is overzealous in many
+ # cases. Since the input will have at least one ":" present before
+ # the password we can tell some things about the beginning and end of
+ # the data, though:
+ self.assertTrue(ssh_output.startswith("{'"))
+ self.assertTrue(ssh_output.endswith("}"))
+ self.assertIn(":********@foo.com/data'", ssh_output)
+
+ def test_hides_parameter_secrets(self):
+ output = heuristic_log_sanitize('token="secret", user="person", token_entry="test=secret"', frozenset(['secret']))
+ self.assertNotIn('secret', output)
+
+ def test_no_password(self):
+ self.assertEqual(heuristic_log_sanitize('foo@bar'), 'foo@bar')
diff --git a/test/units/module_utils/basic/test_imports.py b/test/units/module_utils/basic/test_imports.py
new file mode 100644
index 0000000..d1a5f37
--- /dev/null
+++ b/test/units/module_utils/basic/test_imports.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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 sys
+
+from units.mock.procenv import ModuleTestCase
+
+from units.compat import unittest
+from units.compat.mock import patch
+from ansible.module_utils.six.moves import builtins
+
+realimport = builtins.__import__
+
+
+class TestImports(ModuleTestCase):
+
+ def clear_modules(self, mods):
+ for mod in mods:
+ if mod in sys.modules:
+ del sys.modules[mod]
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_syslog(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ if name == 'syslog':
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ self.clear_modules(['syslog', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.HAS_SYSLOG)
+
+ self.clear_modules(['syslog', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.HAS_SYSLOG)
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_selinux(self, mock_import):
+ def _mock_import(name, globals=None, locals=None, fromlist=tuple(), level=0, **kwargs):
+ if name == 'ansible.module_utils.compat' and fromlist == ('selinux',):
+ raise ImportError
+ return realimport(name, globals=globals, locals=locals, fromlist=fromlist, level=level, **kwargs)
+
+ try:
+ self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.HAVE_SELINUX)
+ except ImportError:
+ # no selinux on test system, so skip
+ pass
+
+ self.clear_modules(['ansible.module_utils.compat.selinux', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.HAVE_SELINUX)
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_json(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ if name == 'ansible.module_utils.common._json_compat':
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ self.clear_modules(['json', 'ansible.module_utils.basic'])
+ builtins.__import__('ansible.module_utils.basic')
+ self.clear_modules(['json', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ with self.assertRaises(SystemExit):
+ builtins.__import__('ansible.module_utils.basic')
+
+ # FIXME: doesn't work yet
+ # @patch.object(builtins, 'bytes')
+ # def test_module_utils_basic_bytes(self, mock_bytes):
+ # mock_bytes.side_effect = NameError()
+ # from ansible.module_utils import basic
+
+ @patch.object(builtins, '__import__')
+ @unittest.skipIf(sys.version_info[0] >= 3, "literal_eval is available in every version of Python3")
+ def test_module_utils_basic_import_literal_eval(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ try:
+ fromlist = kwargs.get('fromlist', args[2])
+ except IndexError:
+ fromlist = []
+ if name == 'ast' and 'literal_eval' in fromlist:
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ mock_import.side_effect = _mock_import
+ self.clear_modules(['ast', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertEqual(mod.module_utils.basic.literal_eval("'1'"), "1")
+ self.assertEqual(mod.module_utils.basic.literal_eval("1"), 1)
+ self.assertEqual(mod.module_utils.basic.literal_eval("-1"), -1)
+ self.assertEqual(mod.module_utils.basic.literal_eval("(1,2,3)"), (1, 2, 3))
+ self.assertEqual(mod.module_utils.basic.literal_eval("[1]"), [1])
+ self.assertEqual(mod.module_utils.basic.literal_eval("True"), True)
+ self.assertEqual(mod.module_utils.basic.literal_eval("False"), False)
+ self.assertEqual(mod.module_utils.basic.literal_eval("None"), None)
+ # self.assertEqual(mod.module_utils.basic.literal_eval('{"a": 1}'), dict(a=1))
+ self.assertRaises(ValueError, mod.module_utils.basic.literal_eval, "asdfasdfasdf")
+
+ @patch.object(builtins, '__import__')
+ def test_module_utils_basic_import_systemd_journal(self, mock_import):
+ def _mock_import(name, *args, **kwargs):
+ try:
+ fromlist = kwargs.get('fromlist', args[2])
+ except IndexError:
+ fromlist = []
+ if name == 'systemd' and 'journal' in fromlist:
+ raise ImportError
+ return realimport(name, *args, **kwargs)
+
+ self.clear_modules(['systemd', 'ansible.module_utils.basic'])
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertTrue(mod.module_utils.basic.has_journal)
+
+ self.clear_modules(['systemd', 'ansible.module_utils.basic'])
+ mock_import.side_effect = _mock_import
+ mod = builtins.__import__('ansible.module_utils.basic')
+ self.assertFalse(mod.module_utils.basic.has_journal)
diff --git a/test/units/module_utils/basic/test_log.py b/test/units/module_utils/basic/test_log.py
new file mode 100644
index 0000000..f3f764f
--- /dev/null
+++ b/test/units/module_utils/basic/test_log.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2017, 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 syslog
+from itertools import product
+
+import pytest
+
+import ansible.module_utils.basic
+from ansible.module_utils.six import PY3
+
+
+class TestAnsibleModuleLogSmokeTest:
+ DATA = [u'Text string', u'Toshio くらとみ non-ascii test']
+ DATA = DATA + [d.encode('utf-8') for d in DATA]
+ DATA += [b'non-utf8 :\xff: test']
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('msg, stdin', ((m, {}) for m in DATA), indirect=['stdin']) # pylint: disable=undefined-variable
+ def test_smoketest_syslog(self, am, mocker, msg):
+ # These talk to the live daemons on the system. Need to do this to
+ # show that what we send doesn't cause an issue once it gets to the
+ # daemon. These are just smoketests to test that we don't fail.
+ mocker.patch('ansible.module_utils.basic.has_journal', False)
+
+ am.log(u'Text string')
+ am.log(u'Toshio くらとみ non-ascii test')
+
+ am.log(b'Byte string')
+ am.log(u'Toshio くらとみ non-ascii test'.encode('utf-8'))
+ am.log(b'non-utf8 :\xff: test')
+
+ @pytest.mark.skipif(not ansible.module_utils.basic.has_journal, reason='python systemd bindings not installed')
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('msg, stdin', ((m, {}) for m in DATA), indirect=['stdin']) # pylint: disable=undefined-variable
+ def test_smoketest_journal(self, am, mocker, msg):
+ # These talk to the live daemons on the system. Need to do this to
+ # show that what we send doesn't cause an issue once it gets to the
+ # daemon. These are just smoketests to test that we don't fail.
+ mocker.patch('ansible.module_utils.basic.has_journal', True)
+
+ am.log(u'Text string')
+ am.log(u'Toshio くらとみ non-ascii test')
+
+ am.log(b'Byte string')
+ am.log(u'Toshio くらとみ non-ascii test'.encode('utf-8'))
+ am.log(b'non-utf8 :\xff: test')
+
+
+class TestAnsibleModuleLogSyslog:
+ """Test the AnsibleModule Log Method"""
+
+ PY2_OUTPUT_DATA = [
+ (u'Text string', b'Text string'),
+ (u'Toshio くらとみ non-ascii test', u'Toshio くらとみ non-ascii test'.encode('utf-8')),
+ (b'Byte string', b'Byte string'),
+ (u'Toshio くらとみ non-ascii test'.encode('utf-8'), u'Toshio くらとみ non-ascii test'.encode('utf-8')),
+ (b'non-utf8 :\xff: test', b'non-utf8 :\xff: test'.decode('utf-8', 'replace').encode('utf-8')),
+ ]
+
+ PY3_OUTPUT_DATA = [
+ (u'Text string', u'Text string'),
+ (u'Toshio くらとみ non-ascii test', u'Toshio くらとみ non-ascii test'),
+ (b'Byte string', u'Byte string'),
+ (u'Toshio くらとみ non-ascii test'.encode('utf-8'), u'Toshio くらとみ non-ascii test'),
+ (b'non-utf8 :\xff: test', b'non-utf8 :\xff: test'.decode('utf-8', 'replace')),
+ ]
+
+ OUTPUT_DATA = PY3_OUTPUT_DATA if PY3 else PY2_OUTPUT_DATA
+
+ @pytest.mark.parametrize('no_log, stdin', (product((True, False), [{}])), indirect=['stdin'])
+ def test_no_log(self, am, mocker, no_log):
+ """Test that when no_log is set, logging does not occur"""
+ mock_syslog = mocker.patch('syslog.syslog', autospec=True)
+ mocker.patch('ansible.module_utils.basic.has_journal', False)
+ am.no_log = no_log
+ am.log('unittest no_log')
+ if no_log:
+ assert not mock_syslog.called
+ else:
+ mock_syslog.assert_called_once_with(syslog.LOG_INFO, 'unittest no_log')
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('msg, param, stdin',
+ ((m, p, {}) for m, p in OUTPUT_DATA), # pylint: disable=undefined-variable
+ indirect=['stdin'])
+ def test_output_matches(self, am, mocker, msg, param):
+ """Check that log messages are sent correctly"""
+ mocker.patch('ansible.module_utils.basic.has_journal', False)
+ mock_syslog = mocker.patch('syslog.syslog', autospec=True)
+
+ am.log(msg)
+ mock_syslog.assert_called_once_with(syslog.LOG_INFO, param)
+
+
+@pytest.mark.skipif(not ansible.module_utils.basic.has_journal, reason='python systemd bindings not installed')
+class TestAnsibleModuleLogJournal:
+ """Test the AnsibleModule Log Method"""
+
+ OUTPUT_DATA = [
+ (u'Text string', u'Text string'),
+ (u'Toshio くらとみ non-ascii test', u'Toshio くらとみ non-ascii test'),
+ (b'Byte string', u'Byte string'),
+ (u'Toshio くらとみ non-ascii test'.encode('utf-8'), u'Toshio くらとみ non-ascii test'),
+ (b'non-utf8 :\xff: test', b'non-utf8 :\xff: test'.decode('utf-8', 'replace')),
+ ]
+
+ @pytest.mark.parametrize('no_log, stdin', (product((True, False), [{}])), indirect=['stdin'])
+ def test_no_log(self, am, mocker, no_log):
+ journal_send = mocker.patch('systemd.journal.send')
+ am.no_log = no_log
+ am.log('unittest no_log')
+ if no_log:
+ assert not journal_send.called
+ else:
+ assert journal_send.called == 1
+ # Message
+ # call_args is a 2-tuple of (arg_list, kwarg_dict)
+ assert journal_send.call_args[1]['MESSAGE'].endswith('unittest no_log'), 'Message was not sent to log'
+ # log adds this journal field
+ assert 'MODULE' in journal_send.call_args[1]
+ assert 'basic.py' in journal_send.call_args[1]['MODULE']
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ @pytest.mark.parametrize('msg, param, stdin',
+ ((m, p, {}) for m, p in OUTPUT_DATA), # pylint: disable=undefined-variable
+ indirect=['stdin'])
+ def test_output_matches(self, am, mocker, msg, param):
+ journal_send = mocker.patch('systemd.journal.send')
+ am.log(msg)
+ assert journal_send.call_count == 1, 'journal.send not called exactly once'
+ assert journal_send.call_args[1]['MESSAGE'].endswith(param)
+
+ @pytest.mark.parametrize('stdin', ({},), indirect=['stdin'])
+ def test_log_args(self, am, mocker):
+ journal_send = mocker.patch('systemd.journal.send')
+ am.log('unittest log_args', log_args=dict(TEST='log unittest'))
+ assert journal_send.called == 1
+ assert journal_send.call_args[1]['MESSAGE'].endswith('unittest log_args'), 'Message was not sent to log'
+
+ # log adds this journal field
+ assert 'MODULE' in journal_send.call_args[1]
+ assert 'basic.py' in journal_send.call_args[1]['MODULE']
+
+ # We added this journal field
+ assert 'TEST' in journal_send.call_args[1]
+ assert 'log unittest' in journal_send.call_args[1]['TEST']
diff --git a/test/units/module_utils/basic/test_no_log.py b/test/units/module_utils/basic/test_no_log.py
new file mode 100644
index 0000000..c479702
--- /dev/null
+++ b/test/units/module_utils/basic/test_no_log.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017, 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
+
+from units.compat import unittest
+
+from ansible.module_utils.basic import remove_values
+from ansible.module_utils.common.parameters import _return_datastructure_name
+
+
+class TestReturnValues(unittest.TestCase):
+ dataset = (
+ ('string', frozenset(['string'])),
+ ('', frozenset()),
+ (1, frozenset(['1'])),
+ (1.0, frozenset(['1.0'])),
+ (False, frozenset()),
+ (['1', '2', '3'], frozenset(['1', '2', '3'])),
+ (('1', '2', '3'), frozenset(['1', '2', '3'])),
+ ({'one': 1, 'two': 'dos'}, frozenset(['1', 'dos'])),
+ (
+ {
+ 'one': 1,
+ 'two': 'dos',
+ 'three': [
+ 'amigos', 'musketeers', None, {
+ 'ping': 'pong',
+ 'base': (
+ 'balls', 'raquets'
+ )
+ }
+ ]
+ },
+ frozenset(['1', 'dos', 'amigos', 'musketeers', 'pong', 'balls', 'raquets'])
+ ),
+ (u'Toshio くらとみ', frozenset(['Toshio くらとみ'])),
+ ('Toshio くらとみ', frozenset(['Toshio くらとみ'])),
+ )
+
+ def test_return_datastructure_name(self):
+ for data, expected in self.dataset:
+ self.assertEqual(frozenset(_return_datastructure_name(data)), expected)
+
+ def test_unknown_type(self):
+ self.assertRaises(TypeError, frozenset, _return_datastructure_name(object()))
+
+
+class TestRemoveValues(unittest.TestCase):
+ OMIT = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
+ dataset_no_remove = (
+ ('string', frozenset(['nope'])),
+ (1234, frozenset(['4321'])),
+ (False, frozenset(['4321'])),
+ (1.0, frozenset(['4321'])),
+ (['string', 'strang', 'strung'], frozenset(['nope'])),
+ ({'one': 1, 'two': 'dos', 'secret': 'key'}, frozenset(['nope'])),
+ (
+ {
+ 'one': 1,
+ 'two': 'dos',
+ 'three': [
+ 'amigos', 'musketeers', None, {
+ 'ping': 'pong', 'base': ['balls', 'raquets']
+ }
+ ]
+ },
+ frozenset(['nope'])
+ ),
+ (u'Toshio くら'.encode('utf-8'), frozenset([u'とみ'.encode('utf-8')])),
+ (u'Toshio くら', frozenset([u'とみ'])),
+ )
+ dataset_remove = (
+ ('string', frozenset(['string']), OMIT),
+ (1234, frozenset(['1234']), OMIT),
+ (1234, frozenset(['23']), OMIT),
+ (1.0, frozenset(['1.0']), OMIT),
+ (['string', 'strang', 'strung'], frozenset(['strang']), ['string', OMIT, 'strung']),
+ (['string', 'strang', 'strung'], frozenset(['strang', 'string', 'strung']), [OMIT, OMIT, OMIT]),
+ (('string', 'strang', 'strung'), frozenset(['string', 'strung']), [OMIT, 'strang', OMIT]),
+ ((1234567890, 345678, 987654321), frozenset(['1234567890']), [OMIT, 345678, 987654321]),
+ ((1234567890, 345678, 987654321), frozenset(['345678']), [OMIT, OMIT, 987654321]),
+ ({'one': 1, 'two': 'dos', 'secret': 'key'}, frozenset(['key']), {'one': 1, 'two': 'dos', 'secret': OMIT}),
+ ({'one': 1, 'two': 'dos', 'secret': 'key'}, frozenset(['key', 'dos', '1']), {'one': OMIT, 'two': OMIT, 'secret': OMIT}),
+ ({'one': 1, 'two': 'dos', 'secret': 'key'}, frozenset(['key', 'dos', '1']), {'one': OMIT, 'two': OMIT, 'secret': OMIT}),
+ (
+ {
+ 'one': 1,
+ 'two': 'dos',
+ 'three': [
+ 'amigos', 'musketeers', None, {
+ 'ping': 'pong', 'base': [
+ 'balls', 'raquets'
+ ]
+ }
+ ]
+ },
+ frozenset(['balls', 'base', 'pong', 'amigos']),
+ {
+ 'one': 1,
+ 'two': 'dos',
+ 'three': [
+ OMIT, 'musketeers', None, {
+ 'ping': OMIT,
+ 'base': [
+ OMIT, 'raquets'
+ ]
+ }
+ ]
+ }
+ ),
+ (
+ 'This sentence has an enigma wrapped in a mystery inside of a secret. - mr mystery',
+ frozenset(['enigma', 'mystery', 'secret']),
+ 'This sentence has an ******** wrapped in a ******** inside of a ********. - mr ********'
+ ),
+ (u'Toshio くらとみ'.encode('utf-8'), frozenset([u'くらとみ'.encode('utf-8')]), u'Toshio ********'.encode('utf-8')),
+ (u'Toshio くらとみ', frozenset([u'くらとみ']), u'Toshio ********'),
+ )
+
+ def test_no_removal(self):
+ for value, no_log_strings in self.dataset_no_remove:
+ self.assertEqual(remove_values(value, no_log_strings), value)
+
+ def test_strings_to_remove(self):
+ for value, no_log_strings, expected in self.dataset_remove:
+ self.assertEqual(remove_values(value, no_log_strings), expected)
+
+ def test_unknown_type(self):
+ self.assertRaises(TypeError, remove_values, object(), frozenset())
+
+ def test_hit_recursion_limit(self):
+ """ Check that we do not hit a recursion limit"""
+ data_list = []
+ inner_list = data_list
+ for i in range(0, 10000):
+ new_list = []
+ inner_list.append(new_list)
+ inner_list = new_list
+ inner_list.append('secret')
+
+ # Check that this does not hit a recursion limit
+ actual_data_list = remove_values(data_list, frozenset(('secret',)))
+
+ levels = 0
+ inner_list = actual_data_list
+ while inner_list:
+ if isinstance(inner_list, list):
+ self.assertEqual(len(inner_list), 1)
+ else:
+ levels -= 1
+ break
+ inner_list = inner_list[0]
+ levels += 1
+
+ self.assertEqual(inner_list, self.OMIT)
+ self.assertEqual(levels, 10000)
diff --git a/test/units/module_utils/basic/test_platform_distribution.py b/test/units/module_utils/basic/test_platform_distribution.py
new file mode 100644
index 0000000..3c1afb7
--- /dev/null
+++ b/test/units/module_utils/basic/test_platform_distribution.py
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017-2018 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 pytest
+
+from units.compat.mock import patch
+
+from ansible.module_utils.six.moves import builtins
+
+# Functions being tested
+from ansible.module_utils.basic import get_platform
+from ansible.module_utils.basic import get_all_subclasses
+from ansible.module_utils.basic import get_distribution
+from ansible.module_utils.basic import get_distribution_version
+from ansible.module_utils.basic import load_platform_subclass
+
+
+realimport = builtins.__import__
+
+
+@pytest.fixture
+def platform_linux(mocker):
+ mocker.patch('platform.system', return_value='Linux')
+
+
+#
+# get_platform tests
+#
+
+def test_get_platform():
+ with patch('platform.system', return_value='foo'):
+ assert get_platform() == 'foo'
+
+
+#
+# get_distribution tests
+#
+
+@pytest.mark.usefixtures("platform_linux")
+class TestGetDistribution:
+ """Tests for get_distribution that have to find something"""
+ def test_distro_known(self):
+ with patch('ansible.module_utils.distro.id', return_value="alpine"):
+ assert get_distribution() == "Alpine"
+
+ with patch('ansible.module_utils.distro.id', return_value="arch"):
+ assert get_distribution() == "Arch"
+
+ with patch('ansible.module_utils.distro.id', return_value="centos"):
+ assert get_distribution() == "Centos"
+
+ with patch('ansible.module_utils.distro.id', return_value="clear-linux-os"):
+ assert get_distribution() == "Clear-linux-os"
+
+ with patch('ansible.module_utils.distro.id', return_value="coreos"):
+ assert get_distribution() == "Coreos"
+
+ with patch('ansible.module_utils.distro.id', return_value="debian"):
+ assert get_distribution() == "Debian"
+
+ with patch('ansible.module_utils.distro.id', return_value="flatcar"):
+ assert get_distribution() == "Flatcar"
+
+ with patch('ansible.module_utils.distro.id', return_value="linuxmint"):
+ assert get_distribution() == "Linuxmint"
+
+ with patch('ansible.module_utils.distro.id', return_value="opensuse"):
+ assert get_distribution() == "Opensuse"
+
+ with patch('ansible.module_utils.distro.id', return_value="oracle"):
+ assert get_distribution() == "Oracle"
+
+ with patch('ansible.module_utils.distro.id', return_value="raspian"):
+ assert get_distribution() == "Raspian"
+
+ with patch('ansible.module_utils.distro.id', return_value="rhel"):
+ assert get_distribution() == "Redhat"
+
+ with patch('ansible.module_utils.distro.id', return_value="ubuntu"):
+ assert get_distribution() == "Ubuntu"
+
+ with patch('ansible.module_utils.distro.id', return_value="virtuozzo"):
+ assert get_distribution() == "Virtuozzo"
+
+ with patch('ansible.module_utils.distro.id', return_value="foo"):
+ assert get_distribution() == "Foo"
+
+ def test_distro_unknown(self):
+ with patch('ansible.module_utils.distro.id', return_value=""):
+ assert get_distribution() == "OtherLinux"
+
+ def test_distro_amazon_linux_short(self):
+ with patch('ansible.module_utils.distro.id', return_value="amzn"):
+ assert get_distribution() == "Amazon"
+
+ def test_distro_amazon_linux_long(self):
+ with patch('ansible.module_utils.distro.id', return_value="amazon"):
+ assert get_distribution() == "Amazon"
+
+
+#
+# get_distribution_version tests
+#
+
+
+@pytest.mark.usefixtures("platform_linux")
+def test_distro_found():
+ with patch('ansible.module_utils.distro.version', return_value="1"):
+ assert get_distribution_version() == "1"
+
+
+#
+# Tests for LoadPlatformSubclass
+#
+
+class TestLoadPlatformSubclass:
+ class LinuxTest:
+ pass
+
+ class Foo(LinuxTest):
+ platform = "Linux"
+ distribution = None
+
+ class Bar(LinuxTest):
+ platform = "Linux"
+ distribution = "Bar"
+
+ def test_not_linux(self):
+ # if neither match, the fallback should be the top-level class
+ with patch('platform.system', return_value="Foo"):
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert isinstance(load_platform_subclass(self.LinuxTest), self.LinuxTest)
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_none(self):
+ # match just the platform class, not a specific distribution
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert isinstance(load_platform_subclass(self.LinuxTest), self.Foo)
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_found(self):
+ # match both the distribution and platform class
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value="Bar"):
+ assert isinstance(load_platform_subclass(self.LinuxTest), self.Bar)
+
+
+#
+# Tests for get_all_subclasses
+#
+
+class TestGetAllSubclasses:
+ class Base:
+ pass
+
+ class BranchI(Base):
+ pass
+
+ class BranchII(Base):
+ pass
+
+ class BranchIA(BranchI):
+ pass
+
+ class BranchIB(BranchI):
+ pass
+
+ class BranchIIA(BranchII):
+ pass
+
+ class BranchIIB(BranchII):
+ pass
+
+ def test_bottom_level(self):
+ assert get_all_subclasses(self.BranchIIB) == []
+
+ def test_one_inheritance(self):
+ assert set(get_all_subclasses(self.BranchII)) == set([self.BranchIIA, self.BranchIIB])
+
+ def test_toplevel(self):
+ assert set(get_all_subclasses(self.Base)) == set([self.BranchI, self.BranchII,
+ self.BranchIA, self.BranchIB,
+ self.BranchIIA, self.BranchIIB])
diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py
new file mode 100644
index 0000000..04211e2
--- /dev/null
+++ b/test/units/module_utils/basic/test_run_command.py
@@ -0,0 +1,278 @@
+# -*- coding: utf-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
+
+import errno
+from itertools import product
+from io import BytesIO
+
+import pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.six import PY2
+from ansible.module_utils.compat import selectors
+
+
+class OpenBytesIO(BytesIO):
+ """BytesIO with dummy close() method
+
+ So that you can inspect the content after close() was called.
+ """
+
+ def close(self):
+ pass
+
+
+@pytest.fixture
+def mock_os(mocker):
+ def mock_os_chdir(path):
+ if path == '/inaccessible':
+ raise OSError(errno.EPERM, "Permission denied: '/inaccessible'")
+
+ def mock_os_abspath(path):
+ if path.startswith('/'):
+ return path
+ else:
+ return os.getcwd.return_value + '/' + path
+
+ os = mocker.patch('ansible.module_utils.basic.os')
+
+ os.path.expandvars.side_effect = lambda x: x
+ os.path.expanduser.side_effect = lambda x: x
+ os.environ = {'PATH': '/bin'}
+ os.getcwd.return_value = '/home/foo'
+ os.path.isdir.return_value = True
+ os.chdir.side_effect = mock_os_chdir
+ os.path.abspath.side_effect = mock_os_abspath
+
+ yield os
+
+
+class DummyFileObj():
+ def __init__(self, fileobj):
+ self.fileobj = fileobj
+
+
+class SpecialBytesIO(BytesIO):
+ def __init__(self, *args, **kwargs):
+ fh = kwargs.pop('fh', None)
+ super(SpecialBytesIO, self).__init__(*args, **kwargs)
+ self.fh = fh
+
+ def fileno(self):
+ return self.fh
+
+ # We need to do this because some of our tests create a new value for stdout and stderr
+ # The new value is able to affect the string that is returned by the subprocess stdout and
+ # stderr but by the time the test gets it, it is too late to change the SpecialBytesIO that
+ # subprocess.Popen returns for stdout and stderr. If we could figure out how to change those as
+ # well, then we wouldn't need this.
+ def __eq__(self, other):
+ if id(self) == id(other) or self.fh == other.fileno():
+ return True
+ return False
+
+
+class DummyKey:
+ def __init__(self, fileobj):
+ self.fileobj = fileobj
+
+
+@pytest.fixture
+def mock_subprocess(mocker):
+
+ class MockSelector(selectors.BaseSelector):
+ def __init__(self):
+ super(MockSelector, self).__init__()
+ self._file_objs = []
+
+ def register(self, fileobj, events, data=None):
+ self._file_objs.append(fileobj)
+
+ def unregister(self, fileobj):
+ self._file_objs.remove(fileobj)
+
+ def select(self, timeout=None):
+ ready = []
+ for file_obj in self._file_objs:
+ ready.append((DummyKey(subprocess._output[file_obj.fileno()]), selectors.EVENT_READ))
+ return ready
+
+ def get_map(self):
+ return self._file_objs
+
+ def close(self):
+ super(MockSelector, self).close()
+ self._file_objs = []
+
+ selectors.DefaultSelector = MockSelector
+
+ subprocess = mocker.patch('ansible.module_utils.basic.subprocess')
+ subprocess._output = {mocker.sentinel.stdout: SpecialBytesIO(b'', fh=mocker.sentinel.stdout),
+ mocker.sentinel.stderr: SpecialBytesIO(b'', fh=mocker.sentinel.stderr)}
+
+ cmd = mocker.MagicMock()
+ cmd.returncode = 0
+ cmd.stdin = OpenBytesIO()
+ cmd.stdout = subprocess._output[mocker.sentinel.stdout]
+ cmd.stderr = subprocess._output[mocker.sentinel.stderr]
+ subprocess.Popen.return_value = cmd
+
+ yield subprocess
+
+
+@pytest.fixture()
+def rc_am(mocker, am, mock_os, mock_subprocess):
+ am.fail_json = mocker.MagicMock(side_effect=SystemExit)
+ am._os = mock_os
+ am._subprocess = mock_subprocess
+ yield am
+
+
+class TestRunCommandArgs:
+ # Format is command as passed to run_command, command to Popen as list, command to Popen as string
+ ARGS_DATA = (
+ (['/bin/ls', 'a', 'b', 'c'], [b'/bin/ls', b'a', b'b', b'c'], b'/bin/ls a b c'),
+ ('/bin/ls a " b" "c "', [b'/bin/ls', b'a', b' b', b'c '], b'/bin/ls a " b" "c "'),
+ )
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ # pylint: disable=undefined-variable
+ @pytest.mark.parametrize('cmd, expected, shell, stdin',
+ ((arg, cmd_str if sh else cmd_lst, sh, {})
+ for (arg, cmd_lst, cmd_str), sh in product(ARGS_DATA, (True, False))),
+ indirect=['stdin'])
+ def test_args(self, cmd, expected, shell, rc_am):
+ rc_am.run_command(cmd, use_unsafe_shell=shell)
+ assert rc_am._subprocess.Popen.called
+ args, kwargs = rc_am._subprocess.Popen.call_args
+ assert args == (expected, )
+ assert kwargs['shell'] == shell
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_tuple_as_args(self, rc_am):
+ with pytest.raises(SystemExit):
+ rc_am.run_command(('ls', '/'))
+ assert rc_am.fail_json.called
+
+
+class TestRunCommandCwd:
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_cwd(self, mocker, rc_am):
+ rc_am.run_command('/bin/ls', cwd='/new')
+ assert rc_am._subprocess.Popen.mock_calls[0][2]['cwd'] == b'/new'
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_cwd_relative_path(self, mocker, rc_am):
+ rc_am.run_command('/bin/ls', cwd='sub-dir')
+ assert rc_am._subprocess.Popen.mock_calls[0][2]['cwd'] == b'/home/foo/sub-dir'
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_cwd_not_a_dir(self, mocker, rc_am):
+ rc_am.run_command('/bin/ls', cwd='/not-a-dir')
+ assert rc_am._subprocess.Popen.mock_calls[0][2]['cwd'] == b'/not-a-dir'
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_cwd_not_a_dir_noignore(self, rc_am):
+ rc_am._os.path.isdir.side_effect = lambda d: d != b'/not-a-dir'
+ with pytest.raises(SystemExit):
+ rc_am.run_command('/bin/ls', cwd='/not-a-dir', ignore_invalid_cwd=False)
+ assert rc_am.fail_json.called
+
+
+class TestRunCommandPrompt:
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_prompt_bad_regex(self, rc_am):
+ with pytest.raises(SystemExit):
+ rc_am.run_command('foo', prompt_regex='[pP)assword:')
+ assert rc_am.fail_json.called
+
+ @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:')
+ assert rc == 0
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_prompt_match_wo_data(self, mocker, rc_am):
+ rc_am._subprocess._output = {mocker.sentinel.stdout:
+ SpecialBytesIO(b'Authentication required!\nEnter password: ',
+ 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)
+ assert rc == 257
+
+
+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)
+ assert rc == 1
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_check_rc_true(self, rc_am):
+ rc_am._subprocess.Popen.return_value.returncode = 1
+ with pytest.raises(SystemExit):
+ rc_am.run_command('/bin/false', check_rc=True)
+ assert rc_am.fail_json.called
+ args, kwargs = rc_am.fail_json.call_args
+ assert kwargs['rc'] == 1
+
+
+class TestRunCommandOutput:
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_text_stdin(self, rc_am):
+ (rc, stdout, stderr) = rc_am.run_command('/bin/foo', data='hello world')
+ assert rc_am._subprocess.Popen.return_value.stdin.getvalue() == b'hello world\n'
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_ascii_stdout(self, mocker, rc_am):
+ rc_am._subprocess._output = {mocker.sentinel.stdout:
+ SpecialBytesIO(b'hello', fh=mocker.sentinel.stdout),
+ mocker.sentinel.stderr:
+ SpecialBytesIO(b'', fh=mocker.sentinel.stderr)}
+ (rc, stdout, stderr) = rc_am.run_command('/bin/cat hello.txt')
+ assert rc == 0
+ # module_utils function. On py3 it returns text and py2 it returns
+ # bytes because it's returning native strings
+ assert stdout == 'hello'
+
+ @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+ def test_utf8_output(self, mocker, rc_am):
+ rc_am._subprocess._output = {mocker.sentinel.stdout:
+ SpecialBytesIO(u'Žarn§'.encode('utf-8'),
+ fh=mocker.sentinel.stdout),
+ mocker.sentinel.stderr:
+ SpecialBytesIO(u'لرئيسية'.encode('utf-8'),
+ fh=mocker.sentinel.stderr)}
+ (rc, stdout, stderr) = rc_am.run_command('/bin/something_ugly')
+ assert rc == 0
+ # module_utils function. On py3 it returns text and py2 it returns
+ # bytes because it's returning native strings
+ assert stdout == to_native(u'Žarn§')
+ assert stderr == to_native(u'لرئيسية')
+
+
+@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
+def test_run_command_fds(mocker, rc_am):
+ subprocess_mock = mocker.patch('ansible.module_utils.basic.subprocess')
+ subprocess_mock.Popen.side_effect = AssertionError
+
+ try:
+ rc_am.run_command('synchronize', pass_fds=(101, 42))
+ except SystemExit:
+ pass
+
+ if PY2:
+ assert subprocess_mock.Popen.call_args[1]['close_fds'] is False
+ assert 'pass_fds' not in subprocess_mock.Popen.call_args[1]
+
+ else:
+ assert subprocess_mock.Popen.call_args[1]['pass_fds'] == (101, 42)
+ assert subprocess_mock.Popen.call_args[1]['close_fds'] is True
diff --git a/test/units/module_utils/basic/test_safe_eval.py b/test/units/module_utils/basic/test_safe_eval.py
new file mode 100644
index 0000000..e8538ca
--- /dev/null
+++ b/test/units/module_utils/basic/test_safe_eval.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# (c) 2015-2017, Toshio Kuratomi <tkuratomi@ansible.com>
+# 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 itertools import chain
+import pytest
+
+
+# Strings that should be converted into a typed value
+VALID_STRINGS = (
+ ("'a'", 'a'),
+ ("'1'", '1'),
+ ("1", 1),
+ ("True", True),
+ ("False", False),
+ ("{}", {}),
+)
+
+# Passing things that aren't strings should just return the object
+NONSTRINGS = (
+ ({'a': 1}, {'a': 1}),
+)
+
+# These strings are not basic types. For security, these should not be
+# executed. We return the same string and get an exception for some
+INVALID_STRINGS = (
+ ("a=1", "a=1", SyntaxError),
+ ("a.foo()", "a.foo()", None),
+ ("import foo", "import foo", None),
+ ("__import__('foo')", "__import__('foo')", ValueError),
+)
+
+
+@pytest.mark.parametrize('code, expected, stdin',
+ ((c, e, {}) for c, e in chain(VALID_STRINGS, NONSTRINGS)),
+ indirect=['stdin'])
+def test_simple_types(am, code, expected):
+ # test some basic usage for various types
+ assert am.safe_eval(code) == expected
+
+
+@pytest.mark.parametrize('code, expected, stdin',
+ ((c, e, {}) for c, e in chain(VALID_STRINGS, NONSTRINGS)),
+ indirect=['stdin'])
+def test_simple_types_with_exceptions(am, code, expected):
+ # Test simple types with exceptions requested
+ assert am.safe_eval(code, include_exceptions=True), (expected, None)
+
+
+@pytest.mark.parametrize('code, expected, stdin',
+ ((c, e, {}) for c, e, dummy in INVALID_STRINGS),
+ indirect=['stdin'])
+def test_invalid_strings(am, code, expected):
+ assert am.safe_eval(code) == expected
+
+
+@pytest.mark.parametrize('code, expected, exception, stdin',
+ ((c, e, ex, {}) for c, e, ex in INVALID_STRINGS),
+ indirect=['stdin'])
+def test_invalid_strings_with_exceptions(am, code, expected, exception):
+ res = am.safe_eval(code, include_exceptions=True)
+ assert res[0] == expected
+ if exception is None:
+ assert res[1] == exception
+ else:
+ assert type(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
new file mode 100644
index 0000000..180f866
--- /dev/null
+++ b/test/units/module_utils/basic/test_sanitize_keys.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# (c) 2020, Red Hat
+# 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
+
+import pytest
+from ansible.module_utils.basic import sanitize_keys
+
+
+def test_sanitize_keys_non_dict_types():
+ """ Test that non-dict-like objects return the same data. """
+
+ type_exception = 'Unsupported type for key sanitization.'
+ no_log_strings = set()
+
+ assert 'string value' == sanitize_keys('string value', no_log_strings)
+
+ assert sanitize_keys(None, no_log_strings) is None
+
+ assert set(['x', 'y']) == sanitize_keys(set(['x', 'y']), no_log_strings)
+
+ assert not sanitize_keys(False, no_log_strings)
+
+
+def _run_comparison(obj):
+ no_log_strings = set(['secret', 'password'])
+
+ ret = sanitize_keys(obj, no_log_strings)
+
+ expected = [
+ None,
+ True,
+ 100,
+ "some string",
+ set([1, 2]),
+ [1, 2],
+
+ {'key1': ['value1a', 'value1b'],
+ 'some-********': 'value-for-some-password',
+ 'key2': {'key3': set(['value3a', 'value3b']),
+ 'i-have-a-********': {'********-********': 'value-for-secret-password', 'key4': 'value4'}
+ }
+ },
+
+ {'foo': [{'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER': 1}]}
+ ]
+
+ assert ret == expected
+
+
+def test_sanitize_keys_dict():
+ """ Test that santize_keys works with a dict. """
+
+ d = [
+ None,
+ True,
+ 100,
+ "some string",
+ set([1, 2]),
+ [1, 2],
+
+ {'key1': ['value1a', 'value1b'],
+ 'some-password': 'value-for-some-password',
+ 'key2': {'key3': set(['value3a', 'value3b']),
+ 'i-have-a-secret': {'secret-password': 'value-for-secret-password', 'key4': 'value4'}
+ }
+ },
+
+ {'foo': [{'secret': 1}]}
+ ]
+
+ _run_comparison(d)
+
+
+def test_sanitize_keys_with_ignores():
+ """ Test that we can actually ignore keys. """
+
+ no_log_strings = set(['secret', 'rc'])
+ ignore_keys = set(['changed', 'rc', 'status'])
+
+ value = {'changed': True,
+ 'rc': 0,
+ 'test-rc': 1,
+ 'another-secret': 2,
+ 'status': 'okie dokie'}
+
+ # We expect to change 'test-rc' but NOT 'rc'.
+ expected = {'changed': True,
+ 'rc': 0,
+ 'test-********': 1,
+ 'another-********': 2,
+ 'status': 'okie dokie'}
+
+ ret = sanitize_keys(value, no_log_strings, ignore_keys)
+ assert ret == expected
diff --git a/test/units/module_utils/basic/test_selinux.py b/test/units/module_utils/basic/test_selinux.py
new file mode 100644
index 0000000..d855768
--- /dev/null
+++ b/test/units/module_utils/basic/test_selinux.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017 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 errno
+import json
+import pytest
+
+from units.compat.mock import mock_open, patch
+
+from ansible.module_utils import basic
+from ansible.module_utils.common.text.converters import to_bytes
+from ansible.module_utils.six.moves import builtins
+
+
+@pytest.fixture
+def no_args_module_exec():
+ with patch.object(basic, '_ANSIBLE_ARGS', b'{"ANSIBLE_MODULE_ARGS": {}}'):
+ yield # we're patching the global module object, so nothing to yield
+
+
+def no_args_module(selinux_enabled=None, selinux_mls_enabled=None):
+ am = basic.AnsibleModule(argument_spec={})
+ # just dirty-patch the wrappers on the object instance since it's short-lived
+ if isinstance(selinux_enabled, bool):
+ patch.object(am, 'selinux_enabled', return_value=selinux_enabled).start()
+ if isinstance(selinux_mls_enabled, bool):
+ patch.object(am, 'selinux_mls_enabled', return_value=selinux_mls_enabled).start()
+ return am
+
+
+# test AnsibleModule selinux wrapper methods
+@pytest.mark.usefixtures('no_args_module_exec')
+class TestSELinuxMU:
+ def test_selinux_enabled(self):
+ # test selinux unavailable
+ # selinux unavailable, should return false
+ 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
+ # 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
+ # ensure value is cached (same answer after unpatching)
+ assert enabled_mod.selinux_enabled() is True
+
+ def test_selinux_mls_enabled(self):
+ # 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
+
+ def test_selinux_initial_context(self):
+ # selinux missing/disabled/enabled sans MLS is 3-element None
+ assert no_args_module(selinux_enabled=False, selinux_mls_enabled=False).selinux_initial_context() == [None, None, None]
+ assert no_args_module(selinux_enabled=True, selinux_mls_enabled=False).selinux_initial_context() == [None, None, None]
+ # selinux enabled with MLS is 4-element None
+ assert no_args_module(selinux_enabled=True, selinux_mls_enabled=True).selinux_initial_context() == [None, None, None, None]
+
+ def test_selinux_default_context(self):
+ # selinux unavailable
+ with patch.object(basic, 'HAVE_SELINUX', False):
+ 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']):
+ 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, '']):
+ 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):
+ assert am.selinux_default_context(path='/foo/bar') == [None, None, None, None]
+
+ def test_selinux_context(self):
+ # selinux unavailable
+ with patch.object(basic, 'HAVE_SELINUX', False):
+ assert no_args_module().selinux_context(path='/foo/bar') == [None, None, None]
+
+ 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']):
+ 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, '']):
+ 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 pytest.raises(SystemExit):
+ am.selinux_context(path='/foo/bar')
+
+ with patch('ansible.module_utils.compat.selinux.lgetfilecon_raw', side_effect=OSError()):
+ with pytest.raises(SystemExit):
+ am.selinux_context(path='/foo/bar')
+
+ def test_is_special_selinux_path(self):
+ args = to_bytes(json.dumps(dict(ANSIBLE_MODULE_ARGS={'_ansible_selinux_special_fs': "nfs,nfsd,foos",
+ '_ansible_remote_tmp': "/tmp",
+ '_ansible_keep_remote_files': False})))
+
+ with patch.object(basic, '_ANSIBLE_ARGS', args):
+ am = basic.AnsibleModule(
+ argument_spec=dict(),
+ )
+
+ def _mock_find_mount_point(path):
+ if path.startswith('/some/path'):
+ return '/some/path'
+ elif path.startswith('/weird/random/fstype'):
+ return '/weird/random/fstype'
+ return '/'
+
+ am.find_mount_point = _mock_find_mount_point
+ am.selinux_context = lambda path: ['foo_u', 'foo_r', 'foo_t', 's0']
+
+ m = mock_open()
+ m.side_effect = OSError
+
+ with patch.object(builtins, 'open', m, create=True):
+ assert am.is_special_selinux_path('/some/path/that/should/be/nfs') == (False, None)
+
+ mount_data = [
+ '/dev/disk1 / ext4 rw,seclabel,relatime,data=ordered 0 0\n',
+ '10.1.1.1:/path/to/nfs /some/path nfs ro 0 0\n',
+ 'whatever /weird/random/fstype foos rw 0 0\n',
+ ]
+
+ # mock_open has a broken readlines() implementation apparently...
+ # this should work by default but doesn't, so we fix it
+ m = mock_open(read_data=''.join(mount_data))
+ m.return_value.readlines.return_value = mount_data
+
+ with patch.object(builtins, 'open', m, create=True):
+ assert am.is_special_selinux_path('/some/random/path') == (False, None)
+ assert am.is_special_selinux_path('/some/path/that/should/be/nfs') == (True, ['foo_u', 'foo_r', 'foo_t', 's0'])
+ assert am.is_special_selinux_path('/weird/random/fstype/path') == (True, ['foo_u', 'foo_r', 'foo_t', 's0'])
+
+ def test_set_context_if_different(self):
+ am = no_args_module(selinux_enabled=False)
+ assert am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], True) is True
+ assert am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False) is False
+
+ am = no_args_module(selinux_enabled=True, selinux_mls_enabled=True)
+ 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:
+ 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()
+ 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
+ am.check_mode = False
+
+ with patch('ansible.module_utils.compat.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 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:
+ 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')
diff --git a/test/units/module_utils/basic/test_set_cwd.py b/test/units/module_utils/basic/test_set_cwd.py
new file mode 100644
index 0000000..159236b
--- /dev/null
+++ b/test/units/module_utils/basic/test_set_cwd.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018 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
+
+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 ansible.module_utils import basic
+
+
+class TestAnsibleModuleSetCwd:
+
+ def test_set_cwd(self, monkeypatch):
+
+ '''make sure /tmp is used'''
+
+ def mock_getcwd():
+ return '/tmp'
+
+ def mock_access(path, perm):
+ return True
+
+ def mock_chdir(path):
+ pass
+
+ monkeypatch.setattr(os, 'getcwd', mock_getcwd)
+ monkeypatch.setattr(os, 'access', mock_access)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+
+ result = am._set_cwd()
+ assert result == '/tmp'
+
+ def test_set_cwd_unreadable_use_self_tmpdir(self, monkeypatch):
+
+ '''pwd is not readable, use instance's tmpdir property'''
+
+ def mock_getcwd():
+ return '/tmp'
+
+ def mock_access(path, perm):
+ if path == '/tmp' and perm == 4:
+ return False
+ return True
+
+ def mock_expandvars(var):
+ if var == '$HOME':
+ return '/home/foobar'
+ return var
+
+ def mock_gettempdir():
+ return '/tmp/testdir'
+
+ def mock_chdir(path):
+ if path == '/tmp':
+ raise Exception()
+ return
+
+ monkeypatch.setattr(os, 'getcwd', mock_getcwd)
+ monkeypatch.setattr(os, 'chdir', mock_chdir)
+ monkeypatch.setattr(os, 'access', mock_access)
+ monkeypatch.setattr(os.path, 'expandvars', mock_expandvars)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+
+ am._tmpdir = '/tmp2'
+ result = am._set_cwd()
+ assert result == am._tmpdir
+
+ def test_set_cwd_unreadable_use_home(self, monkeypatch):
+
+ '''cwd and instance tmpdir are unreadable, use home'''
+
+ def mock_getcwd():
+ return '/tmp'
+
+ def mock_access(path, perm):
+ if path in ['/tmp', '/tmp2'] and perm == 4:
+ return False
+ return True
+
+ def mock_expandvars(var):
+ if var == '$HOME':
+ return '/home/foobar'
+ return var
+
+ def mock_gettempdir():
+ return '/tmp/testdir'
+
+ def mock_chdir(path):
+ if path == '/tmp':
+ raise Exception()
+ return
+
+ monkeypatch.setattr(os, 'getcwd', mock_getcwd)
+ monkeypatch.setattr(os, 'chdir', mock_chdir)
+ monkeypatch.setattr(os, 'access', mock_access)
+ monkeypatch.setattr(os.path, 'expandvars', mock_expandvars)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+
+ am._tmpdir = '/tmp2'
+ result = am._set_cwd()
+ assert result == '/home/foobar'
+
+ def test_set_cwd_unreadable_use_gettempdir(self, monkeypatch):
+
+ '''fallback to tempfile.gettempdir'''
+
+ thisdir = None
+
+ def mock_getcwd():
+ return '/tmp'
+
+ def mock_access(path, perm):
+ if path in ['/tmp', '/tmp2', '/home/foobar'] and perm == 4:
+ return False
+ return True
+
+ def mock_expandvars(var):
+ if var == '$HOME':
+ return '/home/foobar'
+ return var
+
+ def mock_gettempdir():
+ return '/tmp3'
+
+ def mock_chdir(path):
+ if path == '/tmp':
+ raise Exception()
+ thisdir = path
+
+ monkeypatch.setattr(os, 'getcwd', mock_getcwd)
+ monkeypatch.setattr(os, 'chdir', mock_chdir)
+ monkeypatch.setattr(os, 'access', mock_access)
+ monkeypatch.setattr(os.path, 'expandvars', mock_expandvars)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+
+ am._tmpdir = '/tmp2'
+ monkeypatch.setattr(tempfile, 'gettempdir', mock_gettempdir)
+ result = am._set_cwd()
+ assert result == '/tmp3'
+
+ def test_set_cwd_unreadable_use_None(self, monkeypatch):
+
+ '''all paths are unreable, should return None and not an exception'''
+
+ def mock_getcwd():
+ return '/tmp'
+
+ def mock_access(path, perm):
+ if path in ['/tmp', '/tmp2', '/tmp3', '/home/foobar'] and perm == 4:
+ return False
+ return True
+
+ def mock_expandvars(var):
+ if var == '$HOME':
+ return '/home/foobar'
+ return var
+
+ def mock_gettempdir():
+ return '/tmp3'
+
+ def mock_chdir(path):
+ if path == '/tmp':
+ raise Exception()
+
+ monkeypatch.setattr(os, 'getcwd', mock_getcwd)
+ monkeypatch.setattr(os, 'chdir', mock_chdir)
+ monkeypatch.setattr(os, 'access', mock_access)
+ monkeypatch.setattr(os.path, 'expandvars', mock_expandvars)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})))
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+
+ am._tmpdir = '/tmp2'
+ monkeypatch.setattr(tempfile, 'gettempdir', mock_gettempdir)
+ result = am._set_cwd()
+ assert result is None
diff --git a/test/units/module_utils/basic/test_set_mode_if_different.py b/test/units/module_utils/basic/test_set_mode_if_different.py
new file mode 100644
index 0000000..5fec331
--- /dev/null
+++ b/test/units/module_utils/basic/test_set_mode_if_different.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com>
+# 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
+
+import errno
+import os
+
+from itertools import product
+
+try:
+ import builtins
+except ImportError:
+ import __builtin__ as builtins
+
+import pytest
+
+
+SYNONYMS_0660 = (
+ 0o660,
+ '0o660',
+ '660',
+ 'u+rw-x,g+rw-x,o-rwx',
+ 'u=rw,g=rw,o-rwx',
+)
+
+
+@pytest.fixture
+def mock_stats(mocker):
+ mock_stat1 = mocker.MagicMock()
+ mock_stat1.st_mode = 0o444
+ mock_stat2 = mocker.MagicMock()
+ mock_stat2.st_mode = 0o660
+ yield {"before": mock_stat1, "after": mock_stat2}
+
+
+@pytest.fixture
+def am_check_mode(am):
+ am.check_mode = True
+ yield am
+ am.check_mode = False
+
+
+@pytest.fixture
+def mock_lchmod(mocker):
+ m_lchmod = mocker.patch('ansible.module_utils.basic.os.lchmod', return_value=None, create=True)
+ yield m_lchmod
+
+
+@pytest.mark.parametrize('previous_changes, check_mode, exists, stdin',
+ product((True, False), (True, False), (True, False), ({},)),
+ indirect=['stdin'])
+def test_no_mode_given_returns_previous_changes(am, mock_stats, mock_lchmod, mocker, previous_changes, check_mode, exists):
+ am.check_mode = check_mode
+ mocker.patch('os.lstat', side_effect=[mock_stats['before']])
+ m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
+ m_path_exists = mocker.patch('os.path.exists', return_value=exists)
+
+ assert am.set_mode_if_different('/path/to/file', None, previous_changes) == previous_changes
+ assert not m_lchmod.called
+ assert not m_path_exists.called
+
+
+@pytest.mark.parametrize('mode, check_mode, stdin',
+ product(SYNONYMS_0660, (True, False), ({},)),
+ indirect=['stdin'])
+def test_mode_changed_to_0660(am, mock_stats, mocker, mode, check_mode):
+ # Note: This is for checking that all the different ways of specifying
+ # 0660 mode work. It cannot be used to check that setting a mode that is
+ # not equivalent to 0660 works.
+ am.check_mode = check_mode
+ mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after'], mock_stats['after']])
+ m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
+ mocker.patch('os.path.exists', return_value=True)
+
+ assert am.set_mode_if_different('/path/to/file', mode, False)
+ if check_mode:
+ assert not m_lchmod.called
+ else:
+ m_lchmod.assert_called_with(b'/path/to/file', 0o660)
+
+
+@pytest.mark.parametrize('mode, check_mode, stdin',
+ product(SYNONYMS_0660, (True, False), ({},)),
+ indirect=['stdin'])
+def test_mode_unchanged_when_already_0660(am, mock_stats, mocker, mode, check_mode):
+ # Note: This is for checking that all the different ways of specifying
+ # 0660 mode work. It cannot be used to check that setting a mode that is
+ # not equivalent to 0660 works.
+ am.check_mode = check_mode
+ mocker.patch('os.lstat', side_effect=[mock_stats['after'], mock_stats['after'], mock_stats['after']])
+ m_lchmod = mocker.patch('os.lchmod', return_value=None, create=True)
+ mocker.patch('os.path.exists', return_value=True)
+
+ assert not am.set_mode_if_different('/path/to/file', mode, False)
+ assert not m_lchmod.called
+
+
+@pytest.mark.parametrize('mode, stdin', product(SYNONYMS_0660, ({},)), indirect=['stdin'])
+def test_mode_changed_to_0660_check_mode_no_file(am, mocker, mode):
+ am.check_mode = True
+ mocker.patch('os.path.exists', return_value=False)
+ assert am.set_mode_if_different('/path/to/file', mode, False)
+
+
+@pytest.mark.parametrize('check_mode, stdin',
+ product((True, False), ({},)),
+ indirect=['stdin'])
+def test_missing_lchmod_is_not_link(am, mock_stats, mocker, monkeypatch, check_mode):
+ """Some platforms have lchmod (*BSD) others do not (Linux)"""
+
+ am.check_mode = check_mode
+ original_hasattr = hasattr
+
+ monkeypatch.delattr(os, 'lchmod', raising=False)
+
+ mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after']])
+ mocker.patch('os.path.islink', return_value=False)
+ mocker.patch('os.path.exists', return_value=True)
+ m_chmod = mocker.patch('os.chmod', return_value=None)
+
+ assert am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False)
+ if check_mode:
+ assert not m_chmod.called
+ else:
+ m_chmod.assert_called_with(b'/path/to/file/no_lchmod', 0o660)
+
+
+@pytest.mark.parametrize('check_mode, stdin',
+ product((True, False), ({},)),
+ indirect=['stdin'])
+def test_missing_lchmod_is_link(am, mock_stats, mocker, monkeypatch, check_mode):
+ """Some platforms have lchmod (*BSD) others do not (Linux)"""
+
+ am.check_mode = check_mode
+ original_hasattr = hasattr
+
+ monkeypatch.delattr(os, 'lchmod', raising=False)
+
+ mocker.patch('os.lstat', side_effect=[mock_stats['before'], mock_stats['after']])
+ mocker.patch('os.path.islink', return_value=True)
+ mocker.patch('os.path.exists', return_value=True)
+ m_chmod = mocker.patch('os.chmod', return_value=None)
+ mocker.patch('os.stat', return_value=mock_stats['after'])
+
+ assert am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False)
+ if check_mode:
+ assert not m_chmod.called
+ else:
+ m_chmod.assert_called_with(b'/path/to/file/no_lchmod', 0o660)
+
+ mocker.resetall()
+ mocker.stopall()
+
+
+@pytest.mark.parametrize('stdin,',
+ ({},),
+ indirect=['stdin'])
+def test_missing_lchmod_is_link_in_sticky_dir(am, mock_stats, mocker):
+ """Some platforms have lchmod (*BSD) others do not (Linux)"""
+
+ am.check_mode = False
+ original_hasattr = hasattr
+
+ def _hasattr(obj, name):
+ if obj == os and name == 'lchmod':
+ return False
+ return original_hasattr(obj, name)
+
+ mock_lstat = mocker.MagicMock()
+ mock_lstat.st_mode = 0o777
+
+ mocker.patch('os.lstat', side_effect=[mock_lstat, mock_lstat])
+ mocker.patch.object(builtins, 'hasattr', side_effect=_hasattr)
+ mocker.patch('os.path.islink', return_value=True)
+ mocker.patch('os.path.exists', return_value=True)
+ m_stat = mocker.patch('os.stat', side_effect=OSError(errno.EACCES, 'Permission denied'))
+ m_chmod = mocker.patch('os.chmod', return_value=None)
+
+ # not changed: can't set mode on symbolic links
+ assert not am.set_mode_if_different('/path/to/file/no_lchmod', 0o660, False)
+ m_stat.assert_called_with(b'/path/to/file/no_lchmod')
+ m_chmod.assert_not_called()
+
+ mocker.resetall()
+ mocker.stopall()
diff --git a/test/units/module_utils/basic/test_tmpdir.py b/test/units/module_utils/basic/test_tmpdir.py
new file mode 100644
index 0000000..818cb9b
--- /dev/null
+++ b/test/units/module_utils/basic/test_tmpdir.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018 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
+
+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 ansible.module_utils import basic
+
+
+class TestAnsibleModuleTmpDir:
+
+ DATA = (
+ (
+ {
+ "_ansible_tmpdir": "/path/to/dir",
+ "_ansible_remote_tmp": "/path/tmpdir",
+ "_ansible_keep_remote_files": False,
+ },
+ True,
+ "/path/to/dir"
+ ),
+ (
+ {
+ "_ansible_tmpdir": None,
+ "_ansible_remote_tmp": "/path/tmpdir",
+ "_ansible_keep_remote_files": False
+ },
+ False,
+ "/path/tmpdir/ansible-moduletmp-42-"
+ ),
+ (
+ {
+ "_ansible_tmpdir": None,
+ "_ansible_remote_tmp": "/path/tmpdir",
+ "_ansible_keep_remote_files": False
+ },
+ True,
+ "/path/tmpdir/ansible-moduletmp-42-"
+ ),
+ (
+ {
+ "_ansible_tmpdir": None,
+ "_ansible_remote_tmp": "$HOME/.test",
+ "_ansible_keep_remote_files": False
+ },
+ False,
+ os.path.join(os.environ['HOME'], ".test/ansible-moduletmp-42-")
+ ),
+ )
+
+ # pylint bug: https://github.com/PyCQA/pylint/issues/511
+ # pylint: disable=undefined-variable
+ @pytest.mark.parametrize('args, expected, stat_exists', ((s, e, t) for s, t, e in DATA))
+ def test_tmpdir_property(self, monkeypatch, args, expected, stat_exists):
+ makedirs = {'called': False}
+
+ def mock_mkdtemp(prefix, dir):
+ return os.path.join(dir, prefix)
+
+ def mock_makedirs(path, mode):
+ makedirs['called'] = True
+ makedirs['path'] = path
+ makedirs['mode'] = mode
+ return
+
+ monkeypatch.setattr(tempfile, 'mkdtemp', mock_mkdtemp)
+ monkeypatch.setattr(os.path, 'exists', lambda x: stat_exists)
+ monkeypatch.setattr(os, 'makedirs', mock_makedirs)
+ monkeypatch.setattr(shutil, 'rmtree', lambda x: None)
+ monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': args})))
+
+ with patch('time.time', return_value=42):
+ am = basic.AnsibleModule(argument_spec={})
+ actual_tmpdir = am.tmpdir
+
+ assert actual_tmpdir == expected
+
+ # verify subsequent calls always produces the same tmpdir
+ assert am.tmpdir == actual_tmpdir
+
+ if not stat_exists:
+ assert makedirs['called']
+ expected = os.path.expanduser(os.path.expandvars(am._remote_tmp))
+ assert makedirs['path'] == expected
+ assert makedirs['mode'] == 0o700
+
+ @pytest.mark.parametrize('stdin', ({"_ansible_tmpdir": None,
+ "_ansible_remote_tmp": "$HOME/.test",
+ "_ansible_keep_remote_files": True},),
+ indirect=['stdin'])
+ def test_tmpdir_makedirs_failure(self, am, monkeypatch):
+
+ mock_mkdtemp = MagicMock(return_value="/tmp/path")
+ mock_makedirs = MagicMock(side_effect=OSError("Some OS Error here"))
+
+ monkeypatch.setattr(tempfile, 'mkdtemp', mock_mkdtemp)
+ monkeypatch.setattr(os.path, 'exists', lambda x: False)
+ monkeypatch.setattr(os, 'makedirs', mock_makedirs)
+
+ actual = am.tmpdir
+ assert actual == "/tmp/path"
+ assert mock_makedirs.call_args[0] == (os.path.expanduser(os.path.expandvars("$HOME/.test")),)
+ assert mock_makedirs.call_args[1] == {"mode": 0o700}
+
+ # because makedirs failed the dir should be None so it uses the System tmp
+ assert mock_mkdtemp.call_args[1]['dir'] is None
+ assert mock_mkdtemp.call_args[1]['prefix'].startswith("ansible-moduletmp-")
diff --git a/test/units/module_utils/common/__init__.py b/test/units/module_utils/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/common/__init__.py
diff --git a/test/units/module_utils/common/arg_spec/__init__.py b/test/units/module_utils/common/arg_spec/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/__init__.py
diff --git a/test/units/module_utils/common/arg_spec/test_aliases.py b/test/units/module_utils/common/arg_spec/test_aliases.py
new file mode 100644
index 0000000..7d30fb0
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_aliases.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 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 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 = [
+ (
+ "alias",
+ {'path': {'aliases': ['dir', 'directory']}},
+ {'dir': '/tmp'},
+ {
+ 'dir': '/tmp',
+ 'path': '/tmp',
+ },
+ "",
+ "",
+ ),
+ (
+ "alias-duplicate-warning",
+ {'path': {'aliases': ['dir', 'directory']}},
+ {
+ 'dir': '/tmp',
+ 'directory': '/tmp',
+ },
+ {
+ 'dir': '/tmp',
+ 'directory': '/tmp',
+ 'path': '/tmp',
+ },
+ "",
+ {'alias': 'directory', 'option': 'path'},
+ ),
+ (
+ "deprecated-alias",
+ {
+ 'path': {
+ 'aliases': ['not_yo_path'],
+ 'deprecated_aliases': [
+ {
+ 'name': 'not_yo_path',
+ 'version': '1.7',
+ }
+ ]
+ }
+ },
+ {'not_yo_path': '/tmp'},
+ {
+ 'path': '/tmp',
+ 'not_yo_path': '/tmp',
+ },
+ {
+ 'version': '1.7',
+ 'date': None,
+ 'collection_name': None,
+ 'msg': "Alias 'not_yo_path' is deprecated. See the module docs for more information",
+ },
+ "",
+ )
+]
+
+
+# id, argument spec, parameters, expected parameters, error
+ALIAS_TEST_CASES_INVALID = [
+ (
+ "alias-invalid",
+ {'path': {'aliases': 'bad'}},
+ {},
+ {'path': None},
+ "internal error: aliases must be a list or tuple",
+ ),
+ (
+ # This isn't related to aliases, but it exists in the alias handling code
+ "default-and-required",
+ {'name': {'default': 'ray', 'required': True}},
+ {},
+ {'name': 'ray'},
+ "internal error: required and default are mutually exclusive for name",
+ ),
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'deprecation', 'warning'),
+ ((i[1:]) for i in ALIAS_TEST_CASES),
+ ids=[i[0] for i in ALIAS_TEST_CASES]
+)
+def test_aliases(arg_spec, parameters, expected, deprecation, warning):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
+ assert result._aliases == {
+ alias: param
+ for param, value in arg_spec.items()
+ for alias in value.get("aliases", [])
+ }
+
+ if deprecation:
+ assert deprecation == result._deprecations[0]
+ else:
+ assert result._deprecations == []
+
+ if warning:
+ assert warning == result._warnings[0]
+ else:
+ assert result._warnings == []
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'error'),
+ ((i[1:]) for i in ALIAS_TEST_CASES_INVALID),
+ ids=[i[0] for i in ALIAS_TEST_CASES_INVALID]
+)
+def test_aliases_invalid(arg_spec, parameters, expected, error):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert error in result.error_messages
+ assert isinstance(result.errors.errors[0], AnsibleValidationError)
+ assert isinstance(result.errors, AnsibleValidationErrorMultiple)
diff --git a/test/units/module_utils/common/arg_spec/test_module_validate.py b/test/units/module_utils/common/arg_spec/test_module_validate.py
new file mode 100644
index 0000000..2c2211c
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_module_validate.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 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
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.arg_spec import ModuleArgumentSpecValidator, ValidationResult
+
+
+def test_module_validate():
+ arg_spec = {'name': {}}
+ parameters = {'name': 'larry'}
+ expected = {'name': 'larry'}
+
+ v = ModuleArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.error_messages == []
+ assert result._deprecations == []
+ assert result._warnings == []
+ assert result.validated_parameters == expected
+
+
+def test_module_alias_deprecations_warnings(monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+ arg_spec = {
+ 'path': {
+ 'aliases': ['source', 'src', 'flamethrower'],
+ 'deprecated_aliases': [{'name': 'flamethrower', 'date': '2020-03-04'}],
+ },
+ }
+ parameters = {'flamethrower': '/tmp', 'source': '/tmp'}
+ expected = {
+ 'path': '/tmp',
+ 'flamethrower': '/tmp',
+ 'source': '/tmp',
+ }
+
+ v = ModuleArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert result.validated_parameters == expected
+ assert result._deprecations == [
+ {
+ 'collection_name': None,
+ 'date': '2020-03-04',
+ 'msg': "Alias 'flamethrower' is deprecated. See the module docs for more information",
+ 'version': None,
+ }
+ ]
+ assert "Alias 'flamethrower' is deprecated" in warnings._global_deprecations[0]['msg']
+ assert result._warnings == [{'alias': 'flamethrower', 'option': 'path'}]
+ assert "Both option path and its alias flamethrower are set" in warnings._global_warnings[0]
diff --git a/test/units/module_utils/common/arg_spec/test_sub_spec.py b/test/units/module_utils/common/arg_spec/test_sub_spec.py
new file mode 100644
index 0000000..a6e7575
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_sub_spec.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 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
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+
+
+def test_sub_spec():
+ arg_spec = {
+ 'state': {},
+ 'user': {
+ 'type': 'dict',
+ 'options': {
+ 'first': {'no_log': True},
+ 'last': {},
+ 'age': {'type': 'int'},
+ }
+ }
+ }
+
+ parameters = {
+ 'state': 'present',
+ 'user': {
+ 'first': 'Rey',
+ 'last': 'Skywalker',
+ 'age': '19',
+ }
+ }
+
+ expected = {
+ 'state': 'present',
+ 'user': {
+ 'first': 'Rey',
+ 'last': 'Skywalker',
+ 'age': 19,
+ }
+ }
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
+
+
+def test_nested_sub_spec():
+ arg_spec = {
+ 'type': {},
+ 'car': {
+ 'type': 'dict',
+ 'options': {
+ 'make': {},
+ 'model': {},
+ 'customizations': {
+ 'type': 'dict',
+ 'options': {
+ 'engine': {},
+ 'transmission': {},
+ 'color': {},
+ 'max_rpm': {'type': 'int'},
+ }
+ }
+ }
+ }
+ }
+
+ parameters = {
+ 'type': 'endurance',
+ 'car': {
+ 'make': 'Ford',
+ 'model': 'GT-40',
+ 'customizations': {
+ 'engine': '7.0 L',
+ 'transmission': '5-speed',
+ 'color': 'Ford blue',
+ 'max_rpm': '6000',
+ }
+
+ }
+ }
+
+ expected = {
+ 'type': 'endurance',
+ 'car': {
+ 'make': 'Ford',
+ 'model': 'GT-40',
+ 'customizations': {
+ 'engine': '7.0 L',
+ 'transmission': '5-speed',
+ 'color': 'Ford blue',
+ 'max_rpm': 6000,
+ }
+
+ }
+ }
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.error_messages == []
diff --git a/test/units/module_utils/common/arg_spec/test_validate_invalid.py b/test/units/module_utils/common/arg_spec/test_validate_invalid.py
new file mode 100644
index 0000000..7302e8a
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_validate_invalid.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 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 pytest
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+from ansible.module_utils.errors import AnsibleValidationErrorMultiple
+from ansible.module_utils.six import PY2
+
+
+# Each item is id, argument_spec, parameters, expected, unsupported parameters, error test string
+INVALID_SPECS = [
+ (
+ 'invalid-list',
+ {'packages': {'type': 'list'}},
+ {'packages': {'key': 'value'}},
+ {'packages': {'key': 'value'}},
+ set(),
+ "unable to convert to list: <class 'dict'> cannot be converted to a list",
+ ),
+ (
+ 'invalid-dict',
+ {'users': {'type': 'dict'}},
+ {'users': ['one', 'two']},
+ {'users': ['one', 'two']},
+ set(),
+ "unable to convert to dict: <class 'list'> cannot be converted to a dict",
+ ),
+ (
+ 'invalid-bool',
+ {'bool': {'type': 'bool'}},
+ {'bool': {'k': 'v'}},
+ {'bool': {'k': 'v'}},
+ set(),
+ "unable to convert to bool: <class 'dict'> cannot be converted to a bool",
+ ),
+ (
+ 'invalid-float',
+ {'float': {'type': 'float'}},
+ {'float': 'hello'},
+ {'float': 'hello'},
+ set(),
+ "unable to convert to float: <class 'str'> cannot be converted to a float",
+ ),
+ (
+ 'invalid-bytes',
+ {'bytes': {'type': 'bytes'}},
+ {'bytes': 'one'},
+ {'bytes': 'one'},
+ set(),
+ "unable to convert to bytes: <class 'str'> cannot be converted to a Byte value",
+ ),
+ (
+ 'invalid-bits',
+ {'bits': {'type': 'bits'}},
+ {'bits': 'one'},
+ {'bits': 'one'},
+ set(),
+ "unable to convert to bits: <class 'str'> cannot be converted to a Bit value",
+ ),
+ (
+ 'invalid-jsonargs',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': set()},
+ {'some_json': set()},
+ set(),
+ "unable to convert to jsonarg: <class 'set'> cannot be converted to a json string",
+ ),
+ (
+ 'invalid-parameter',
+ {'name': {}},
+ {
+ 'badparam': '',
+ 'another': '',
+ },
+ {
+ 'name': None,
+ 'badparam': '',
+ 'another': '',
+ },
+ set(('another', 'badparam')),
+ "another, badparam. Supported parameters include: name.",
+ ),
+ (
+ 'invalid-elements',
+ {'numbers': {'type': 'list', 'elements': 'int'}},
+ {'numbers': [55, 33, 34, {'key': 'value'}]},
+ {'numbers': [55, 33, 34]},
+ set(),
+ "Elements value for option 'numbers' is of type <class 'dict'> and we were unable to convert to int: <class 'dict'> cannot be converted to an int"
+ ),
+ (
+ 'required',
+ {'req': {'required': True}},
+ {},
+ {'req': None},
+ set(),
+ "missing required arguments: req"
+ ),
+ (
+ 'blank_values',
+ {'ch_param': {'elements': 'str', 'type': 'list', 'choices': ['a', 'b']}},
+ {'ch_param': ['']},
+ {'ch_param': ['']},
+ set(),
+ "value of ch_param must be one or more of"
+ )
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'unsupported', 'error'),
+ (i[1:] for i in INVALID_SPECS),
+ ids=[i[0] for i in INVALID_SPECS]
+)
+def test_invalid_spec(arg_spec, parameters, expected, unsupported, error):
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ with pytest.raises(AnsibleValidationErrorMultiple) as exc_info:
+ raise result.errors
+
+ if PY2:
+ error = error.replace('class', 'type')
+
+ assert isinstance(result, ValidationResult)
+ assert error in exc_info.value.msg
+ assert error in result.error_messages[0]
+ assert result.unsupported_parameters == unsupported
+ assert result.validated_parameters == expected
diff --git a/test/units/module_utils/common/arg_spec/test_validate_valid.py b/test/units/module_utils/common/arg_spec/test_validate_valid.py
new file mode 100644
index 0000000..7e41127
--- /dev/null
+++ b/test/units/module_utils/common/arg_spec/test_validate_valid.py
@@ -0,0 +1,335 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 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 pytest
+
+from ansible.module_utils.common.arg_spec import ArgumentSpecValidator, ValidationResult
+
+# Each item is id, argument_spec, parameters, expected, valid parameter names
+VALID_SPECS = [
+ (
+ 'str-no-type-specified',
+ {'name': {}},
+ {'name': 'rey'},
+ {'name': 'rey'},
+ set(('name',)),
+ ),
+ (
+ 'str',
+ {'name': {'type': 'str'}},
+ {'name': 'rey'},
+ {'name': 'rey'},
+ set(('name',)),
+ ),
+ (
+ 'str-convert',
+ {'name': {'type': 'str'}},
+ {'name': 5},
+ {'name': '5'},
+ set(('name',)),
+ ),
+ (
+ 'list',
+ {'packages': {'type': 'list'}},
+ {'packages': ['vim', 'python']},
+ {'packages': ['vim', 'python']},
+ set(('packages',)),
+ ),
+ (
+ 'list-comma-string',
+ {'packages': {'type': 'list'}},
+ {'packages': 'vim,python'},
+ {'packages': ['vim', 'python']},
+ set(('packages',)),
+ ),
+ (
+ 'list-comma-string-space',
+ {'packages': {'type': 'list'}},
+ {'packages': 'vim, python'},
+ {'packages': ['vim', ' python']},
+ set(('packages',)),
+ ),
+ (
+ 'dict',
+ {'user': {'type': 'dict'}},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'dict-k=v',
+ {'user': {'type': 'dict'}},
+ {'user': 'first=rey,last=skywalker'},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'dict-k=v-spaces',
+ {'user': {'type': 'dict'}},
+ {'user': 'first=rey, last=skywalker'},
+ {
+ 'user':
+ {
+ 'first': 'rey',
+ 'last': 'skywalker',
+ }
+ },
+ set(('user',)),
+ ),
+ (
+ 'bool',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-ints',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 1,
+ 'disabled': 0,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-true-false',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'true',
+ 'disabled': 'false',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-yes-no',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'yes',
+ 'disabled': 'no',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-y-n',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'y',
+ 'disabled': 'n',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-on-off',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 'on',
+ 'disabled': 'off',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-1-0',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': '1',
+ 'disabled': '0',
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'bool-float',
+ {
+ 'enabled': {'type': 'bool'},
+ 'disabled': {'type': 'bool'},
+ },
+ {
+ 'enabled': 1.0,
+ 'disabled': 0.0,
+ },
+ {
+ 'enabled': True,
+ 'disabled': False,
+ },
+ set(('enabled', 'disabled')),
+ ),
+ (
+ 'float',
+ {'digit': {'type': 'float'}},
+ {'digit': 3.14159},
+ {'digit': 3.14159},
+ set(('digit',)),
+ ),
+ (
+ 'float-str',
+ {'digit': {'type': 'float'}},
+ {'digit': '3.14159'},
+ {'digit': 3.14159},
+ set(('digit',)),
+ ),
+ (
+ 'path',
+ {'path': {'type': 'path'}},
+ {'path': '~/bin'},
+ {'path': '/home/ansible/bin'},
+ set(('path',)),
+ ),
+ (
+ 'raw',
+ {'raw': {'type': 'raw'}},
+ {'raw': 0x644},
+ {'raw': 0x644},
+ set(('raw',)),
+ ),
+ (
+ 'bytes',
+ {'bytes': {'type': 'bytes'}},
+ {'bytes': '2K'},
+ {'bytes': 2048},
+ set(('bytes',)),
+ ),
+ (
+ 'bits',
+ {'bits': {'type': 'bits'}},
+ {'bits': '1Mb'},
+ {'bits': 1048576},
+ set(('bits',)),
+ ),
+ (
+ 'jsonarg',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ set(('some_json',)),
+ ),
+ (
+ 'jsonarg-list',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': ['one', 'two']},
+ {'some_json': '["one", "two"]'},
+ set(('some_json',)),
+ ),
+ (
+ 'jsonarg-dict',
+ {'some_json': {'type': 'jsonarg'}},
+ {'some_json': {"users": {"bob": {"role": "accountant"}}}},
+ {'some_json': '{"users": {"bob": {"role": "accountant"}}}'},
+ set(('some_json',)),
+ ),
+ (
+ 'defaults',
+ {'param': {'default': 'DEFAULT'}},
+ {},
+ {'param': 'DEFAULT'},
+ set(('param',)),
+ ),
+ (
+ 'elements',
+ {'numbers': {'type': 'list', 'elements': 'int'}},
+ {'numbers': [55, 33, 34, '22']},
+ {'numbers': [55, 33, 34, 22]},
+ set(('numbers',)),
+ ),
+ (
+ 'aliases',
+ {'src': {'aliases': ['path', 'source']}},
+ {'src': '/tmp'},
+ {'src': '/tmp'},
+ set(('src (path, source)',)),
+ )
+]
+
+
+@pytest.mark.parametrize(
+ ('arg_spec', 'parameters', 'expected', 'valid_params'),
+ (i[1:] for i in VALID_SPECS),
+ ids=[i[0] for i in VALID_SPECS]
+)
+def test_valid_spec(arg_spec, parameters, expected, valid_params, mocker):
+ mocker.patch('ansible.module_utils.common.validation.os.path.expanduser', return_value='/home/ansible/bin')
+ mocker.patch('ansible.module_utils.common.validation.os.path.expandvars', return_value='/home/ansible/bin')
+
+ v = ArgumentSpecValidator(arg_spec)
+ result = v.validate(parameters)
+
+ assert isinstance(result, ValidationResult)
+ assert result.validated_parameters == expected
+ assert result.unsupported_parameters == set()
+ assert result.error_messages == []
+ assert v._valid_parameter_names == valid_params
+
+ # Again to check caching
+ assert v._valid_parameter_names == valid_params
diff --git a/test/units/module_utils/common/parameters/test_check_arguments.py b/test/units/module_utils/common/parameters/test_check_arguments.py
new file mode 100644
index 0000000..5311217
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_check_arguments.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+
+from ansible.module_utils.common.parameters import _get_unsupported_parameters
+
+
+@pytest.fixture
+def argument_spec():
+ return {
+ 'state': {'aliases': ['status']},
+ 'enabled': {},
+ }
+
+
+@pytest.mark.parametrize(
+ ('module_parameters', 'legal_inputs', 'expected'),
+ (
+ ({'fish': 'food'}, ['state', 'enabled'], set(['fish'])),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, None, set(['path'])),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, ['state', 'path'], set()),
+ ({'state': 'enabled', 'path': '/var/lib/path'}, ['state'], set(['path'])),
+ ({}, None, set()),
+ ({'state': 'enabled'}, None, set()),
+ ({'status': 'enabled', 'enabled': True, 'path': '/var/lib/path'}, None, set(['path'])),
+ ({'status': 'enabled', 'enabled': True}, None, set()),
+ )
+)
+def test_check_arguments(argument_spec, module_parameters, legal_inputs, expected, mocker):
+ result = _get_unsupported_parameters(argument_spec, module_parameters, legal_inputs)
+
+ assert result == expected
diff --git a/test/units/module_utils/common/parameters/test_handle_aliases.py b/test/units/module_utils/common/parameters/test_handle_aliases.py
new file mode 100644
index 0000000..e20a888
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_handle_aliases.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.common.parameters import _handle_aliases
+from ansible.module_utils._text import to_native
+
+
+def test_handle_aliases_no_aliases():
+ argument_spec = {
+ 'name': {'type': 'str'},
+ }
+
+ params = {
+ 'name': 'foo',
+ 'path': 'bar'
+ }
+
+ expected = {}
+ result = _handle_aliases(argument_spec, params)
+
+ assert expected == result
+
+
+def test_handle_aliases_basic():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': ['surname', 'nick']},
+ }
+
+ params = {
+ 'name': 'foo',
+ 'path': 'bar',
+ 'surname': 'foo',
+ 'nick': 'foo',
+ }
+
+ expected = {'surname': 'name', 'nick': 'name'}
+ result = _handle_aliases(argument_spec, params)
+
+ assert expected == result
+
+
+def test_handle_aliases_value_error():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': ['surname', 'nick'], 'default': 'bob', 'required': True},
+ }
+
+ params = {
+ 'name': 'foo',
+ }
+
+ with pytest.raises(ValueError) as ve:
+ _handle_aliases(argument_spec, params)
+ assert 'internal error: aliases must be a list or tuple' == to_native(ve.error)
+
+
+def test_handle_aliases_type_error():
+ argument_spec = {
+ 'name': {'type': 'str', 'aliases': 'surname'},
+ }
+
+ params = {
+ 'name': 'foo',
+ }
+
+ with pytest.raises(TypeError) as te:
+ _handle_aliases(argument_spec, params)
+ assert 'internal error: required and default are mutually exclusive' in to_native(te.error)
diff --git a/test/units/module_utils/common/parameters/test_list_deprecations.py b/test/units/module_utils/common/parameters/test_list_deprecations.py
new file mode 100644
index 0000000..6f0bb71
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_list_deprecations.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 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'},
+ 'foo': {'type': 'dict', 'options': {'old': {'type': 'str', 'removed_in_version': 1.0}}},
+ 'bar': {'type': 'list', 'elements': 'dict', 'options': {'old': {'type': 'str', 'removed_in_version': '2.10'}}},
+ }
+
+ params = {
+ 'name': 'rod',
+ 'old': 'option',
+ 'foo': {'old': 'value'},
+ 'bar': [{'old': 'value'}, {}],
+ }
+ result = _list_deprecations(argument_spec, params)
+ assert len(result) == 3
+ result.sort(key=lambda entry: entry['msg'])
+ assert result[0]['msg'] == """Param 'bar["old"]' is deprecated. See the module docs for more information"""
+ assert result[0]['version'] == '2.10'
+ assert result[1]['msg'] == """Param 'foo["old"]' is deprecated. See the module docs for more information"""
+ assert result[1]['version'] == 1.0
+ assert result[2]['msg'] == "Param 'old' is deprecated. See the module docs for more information"
+ assert result[2]['version'] == '2.5'
diff --git a/test/units/module_utils/common/parameters/test_list_no_log_values.py b/test/units/module_utils/common/parameters/test_list_no_log_values.py
new file mode 100644
index 0000000..ac0e735
--- /dev/null
+++ b/test/units/module_utils/common/parameters/test_list_no_log_values.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.common.parameters import _list_no_log_values
+
+
+@pytest.fixture
+def argument_spec():
+ # Allow extra specs to be passed to the fixture, which will be added to the output
+ def _argument_spec(extra_opts=None):
+ spec = {
+ 'secret': {'type': 'str', 'no_log': True},
+ 'other_secret': {'type': 'str', 'no_log': True},
+ 'state': {'type': 'str'},
+ 'value': {'type': 'int'},
+ }
+
+ if extra_opts:
+ spec.update(extra_opts)
+
+ return spec
+
+ return _argument_spec
+
+
+@pytest.fixture
+def module_parameters():
+ # Allow extra parameters to be passed to the fixture, which will be added to the output
+ def _module_parameters(extra_params=None):
+ params = {
+ 'secret': 'under',
+ 'other_secret': 'makeshift',
+ 'state': 'present',
+ 'value': 5,
+ }
+
+ if extra_params:
+ params.update(extra_params)
+
+ return params
+
+ return _module_parameters
+
+
+def test_list_no_log_values_no_secrets(module_parameters):
+ argument_spec = {
+ 'other_secret': {'type': 'str', 'no_log': False},
+ 'state': {'type': 'str'},
+ 'value': {'type': 'int'},
+ }
+ expected = set()
+ assert expected == _list_no_log_values(argument_spec, module_parameters)
+
+
+def test_list_no_log_values(argument_spec, module_parameters):
+ expected = set(('under', 'makeshift'))
+ assert expected == _list_no_log_values(argument_spec(), module_parameters())
+
+
+@pytest.mark.parametrize('extra_params', [
+ {'subopt1': 1},
+ {'subopt1': 3.14159},
+ {'subopt1': ['one', 'two']},
+ {'subopt1': ('one', 'two')},
+])
+def test_list_no_log_values_invalid_suboptions(argument_spec, module_parameters, extra_params):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'dict',
+ 'options': {
+ 'sub_1_1': {},
+ }
+ }
+ }
+
+ with pytest.raises(TypeError, match=r"(Value '.*?' in the sub parameter field '.*?' must by a dict, not '.*?')"
+ r"|(dictionary requested, could not parse JSON or key=value)"):
+ _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_suboptions(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {'type': 'list'},
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': {
+ 'sub_1_1': 'bagel',
+ 'sub_1_2': ['pebble'],
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'bagel'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_sub_suboptions(argument_spec, module_parameters):
+ extra_opts = {
+ 'sub_level_1': {
+ 'type': 'dict',
+ 'options': {
+ 'l1_1': {'no_log': True},
+ 'l1_2': {},
+ 'l1_3': {
+ 'type': 'dict',
+ 'options': {
+ 'l2_1': {'no_log': True},
+ 'l2_2': {},
+ }
+ }
+ }
+ }
+ }
+
+ extra_params = {
+ 'sub_level_1': {
+ 'l1_1': 'saucy',
+ 'l1_2': 'napped',
+ 'l1_3': {
+ 'l2_1': 'corporate',
+ 'l2_2': 'tinsmith',
+ }
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'saucy', 'corporate'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_suboptions_list(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {},
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': [
+ {
+ 'sub_1_1': ['playroom', 'luxury'],
+ 'sub_1_2': 'deuce',
+ },
+ {
+ 'sub_1_2': ['squishier', 'finished'],
+ }
+ ]
+ }
+
+ expected = set(('under', 'makeshift', 'playroom', 'luxury'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+def test_list_no_log_values_sub_suboptions_list(argument_spec, module_parameters):
+ extra_opts = {
+ 'subopt1': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_1_1': {'no_log': True},
+ 'sub_1_2': {},
+ 'subopt2': {
+ 'type': 'list',
+ 'elements': 'dict',
+ 'options': {
+ 'sub_2_1': {'no_log': True, 'type': 'list'},
+ 'sub_2_2': {},
+ }
+ }
+ }
+ }
+ }
+
+ extra_params = {
+ 'subopt1': {
+ 'sub_1_1': ['playroom', 'luxury'],
+ 'sub_1_2': 'deuce',
+ 'subopt2': [
+ {
+ 'sub_2_1': ['basis', 'gave'],
+ 'sub_2_2': 'liquid',
+ },
+ {
+ 'sub_2_1': ['composure', 'thumping']
+ },
+ ]
+ }
+ }
+
+ expected = set(('under', 'makeshift', 'playroom', 'luxury', 'basis', 'gave', 'composure', 'thumping'))
+ assert expected == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
+
+
+@pytest.mark.parametrize('extra_params, expected', (
+ ({'subopt_dict': 'dict_subopt1=rekindle-scandal,dict_subopt2=subgroupavenge'}, ('rekindle-scandal',)),
+ ({'subopt_dict': 'dict_subopt1=aversion-mutable dict_subopt2=subgroupavenge'}, ('aversion-mutable',)),
+ ({'subopt_dict': ['dict_subopt1=blip-marine,dict_subopt2=subgroupavenge', 'dict_subopt1=tipping,dict_subopt2=hardening']}, ('blip-marine', 'tipping')),
+))
+def test_string_suboptions_as_string(argument_spec, module_parameters, extra_params, expected):
+ extra_opts = {
+ 'subopt_dict': {
+ 'type': 'dict',
+ 'options': {
+ 'dict_subopt1': {'no_log': True},
+ 'dict_subopt2': {},
+ },
+ },
+ }
+
+ result = set(('under', 'makeshift'))
+ result.update(expected)
+ assert result == _list_no_log_values(argument_spec(extra_opts), module_parameters(extra_params))
diff --git a/test/units/module_utils/common/process/test_get_bin_path.py b/test/units/module_utils/common/process/test_get_bin_path.py
new file mode 100644
index 0000000..7c0bd0a
--- /dev/null
+++ b/test/units/module_utils/common/process/test_get_bin_path.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+
+from ansible.module_utils.common.process import get_bin_path
+
+
+def test_get_bin_path(mocker):
+ path = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
+ mocker.patch.dict('os.environ', {'PATH': path})
+ mocker.patch('os.pathsep', ':')
+
+ mocker.patch('os.path.isdir', return_value=False)
+ mocker.patch('ansible.module_utils.common.process.is_executable', return_value=True)
+
+ # pytest-mock 2.0.0 will throw when os.path.exists is messed with
+ # and then another method is patched afterwards. Likely
+ # something in the pytest-mock chain uses os.path.exists internally, and
+ # since pytest-mock prohibits context-specific patching, there's not a
+ # good solution. For now, just patch os.path.exists last.
+ mocker.patch('os.path.exists', side_effect=[False, True])
+
+ assert '/usr/local/bin/notacommand' == get_bin_path('notacommand')
+
+
+def test_get_path_path_raise_valueerror(mocker):
+ mocker.patch.dict('os.environ', {'PATH': ''})
+
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('os.path.isdir', return_value=False)
+ mocker.patch('ansible.module_utils.common.process.is_executable', return_value=True)
+
+ with pytest.raises(ValueError, match='Failed to find required executable "notacommand"'):
+ get_bin_path('notacommand')
diff --git a/test/units/module_utils/common/test_collections.py b/test/units/module_utils/common/test_collections.py
new file mode 100644
index 0000000..95b2a40
--- /dev/null
+++ b/test/units/module_utils/common/test_collections.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018–2019, Sviatoslav Sydorenko <webknjaz@redhat.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+"""Test low-level utility functions from ``module_utils.common.collections``."""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.six import Iterator
+from ansible.module_utils.common._collections_compat import Sequence
+from ansible.module_utils.common.collections import ImmutableDict, is_iterable, is_sequence
+
+
+class SeqStub:
+ """Stub emulating a sequence type.
+
+ >>> from collections.abc import Sequence
+ >>> assert issubclass(SeqStub, Sequence)
+ >>> assert isinstance(SeqStub(), Sequence)
+ """
+
+
+Sequence.register(SeqStub)
+
+
+class IteratorStub(Iterator):
+ def __next__(self):
+ raise StopIteration
+
+
+class IterableStub:
+ def __iter__(self):
+ return IteratorStub()
+
+
+class FakeAnsibleVaultEncryptedUnicode(Sequence):
+ __ENCRYPTED__ = True
+
+ def __init__(self, data):
+ self.data = data
+
+ def __getitem__(self, index):
+ return self.data[index]
+
+ def __len__(self):
+ return len(self.data)
+
+
+TEST_STRINGS = u'he', u'Україна', u'Česká republika'
+TEST_STRINGS = TEST_STRINGS + tuple(s.encode('utf-8') for s in TEST_STRINGS) + (FakeAnsibleVaultEncryptedUnicode(u'foo'),)
+
+TEST_ITEMS_NON_SEQUENCES = (
+ {}, object(), frozenset(),
+ 4, 0.,
+) + TEST_STRINGS
+
+TEST_ITEMS_SEQUENCES = (
+ [], (),
+ SeqStub(),
+)
+TEST_ITEMS_SEQUENCES = TEST_ITEMS_SEQUENCES + (
+ # Iterable effectively containing nested random data:
+ TEST_ITEMS_NON_SEQUENCES,
+)
+
+
+@pytest.mark.parametrize('sequence_input', TEST_ITEMS_SEQUENCES)
+def test_sequence_positive(sequence_input):
+ """Test that non-string item sequences are identified correctly."""
+ assert is_sequence(sequence_input)
+ assert is_sequence(sequence_input, include_strings=False)
+
+
+@pytest.mark.parametrize('non_sequence_input', TEST_ITEMS_NON_SEQUENCES)
+def test_sequence_negative(non_sequence_input):
+ """Test that non-sequences are identified correctly."""
+ assert not is_sequence(non_sequence_input)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_sequence_string_types_with_strings(string_input):
+ """Test that ``is_sequence`` can separate string and non-string."""
+ assert is_sequence(string_input, include_strings=True)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_sequence_string_types_without_strings(string_input):
+ """Test that ``is_sequence`` can separate string and non-string."""
+ assert not is_sequence(string_input, include_strings=False)
+
+
+@pytest.mark.parametrize(
+ 'seq',
+ ([], (), {}, set(), frozenset(), IterableStub()),
+)
+def test_iterable_positive(seq):
+ assert is_iterable(seq)
+
+
+@pytest.mark.parametrize(
+ 'seq', (IteratorStub(), object(), 5, 9.)
+)
+def test_iterable_negative(seq):
+ assert not is_iterable(seq)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_iterable_including_strings(string_input):
+ assert is_iterable(string_input, include_strings=True)
+
+
+@pytest.mark.parametrize('string_input', TEST_STRINGS)
+def test_iterable_excluding_strings(string_input):
+ assert not is_iterable(string_input, include_strings=False)
+
+
+class TestImmutableDict:
+ def test_scalar(self):
+ imdict = ImmutableDict({1: 2})
+ assert imdict[1] == 2
+
+ def test_string(self):
+ imdict = ImmutableDict({u'café': u'くらとみ'})
+ assert imdict[u'café'] == u'くらとみ'
+
+ def test_container(self):
+ imdict = ImmutableDict({(1, 2): ['1', '2']})
+ assert imdict[(1, 2)] == ['1', '2']
+
+ def test_from_tuples(self):
+ imdict = ImmutableDict((('a', 1), ('b', 2)))
+ assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2)))
+
+ def test_from_kwargs(self):
+ imdict = ImmutableDict(a=1, b=2)
+ assert frozenset(imdict.items()) == frozenset((('a', 1), ('b', 2)))
+
+ def test_immutable(self):
+ imdict = ImmutableDict({1: 2})
+
+ expected_reason = r"^'ImmutableDict' object does not support item assignment$"
+
+ with pytest.raises(TypeError, match=expected_reason):
+ imdict[1] = 3
+
+ with pytest.raises(TypeError, match=expected_reason):
+ imdict[5] = 3
+
+ def test_hashable(self):
+ # ImmutableDict is hashable when all of its values are hashable
+ imdict = ImmutableDict({u'café': u'くらとみ'})
+ assert hash(imdict)
+
+ def test_nonhashable(self):
+ # ImmutableDict is unhashable when one of its values is unhashable
+ imdict = ImmutableDict({u'café': u'くらとみ', 1: [1, 2]})
+
+ expected_reason = r"^unhashable type: 'list'$"
+
+ with pytest.raises(TypeError, match=expected_reason):
+ hash(imdict)
+
+ def test_len(self):
+ imdict = ImmutableDict({1: 2, 'a': 'b'})
+ assert len(imdict) == 2
+
+ def test_repr(self):
+ initial_data = {1: 2, 'a': 'b'}
+ initial_data_repr = repr(initial_data)
+ imdict = ImmutableDict(initial_data)
+ actual_repr = repr(imdict)
+ expected_repr = "ImmutableDict({0})".format(initial_data_repr)
+ assert actual_repr == expected_repr
diff --git a/test/units/module_utils/common/test_dict_transformations.py b/test/units/module_utils/common/test_dict_transformations.py
new file mode 100644
index 0000000..ba55299
--- /dev/null
+++ b/test/units/module_utils/common/test_dict_transformations.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2017, Will Thames <will.thames@xvt.com.au>
+# 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 pytest
+
+from ansible.module_utils.common.dict_transformations import (
+ _camel_to_snake,
+ _snake_to_camel,
+ camel_dict_to_snake_dict,
+ dict_merge,
+ recursive_diff,
+)
+
+
+EXPECTED_SNAKIFICATION = {
+ 'alllower': 'alllower',
+ 'TwoWords': 'two_words',
+ 'AllUpperAtEND': 'all_upper_at_end',
+ 'AllUpperButPLURALs': 'all_upper_but_plurals',
+ 'TargetGroupARNs': 'target_group_arns',
+ 'HTTPEndpoints': 'http_endpoints',
+ 'PLURALs': 'plurals'
+}
+
+EXPECTED_REVERSIBLE = {
+ 'TwoWords': 'two_words',
+ 'AllUpperAtEND': 'all_upper_at_e_n_d',
+ 'AllUpperButPLURALs': 'all_upper_but_p_l_u_r_a_ls',
+ 'TargetGroupARNs': 'target_group_a_r_ns',
+ 'HTTPEndpoints': 'h_t_t_p_endpoints',
+ 'PLURALs': 'p_l_u_r_a_ls'
+}
+
+
+class TestCaseCamelToSnake:
+
+ def test_camel_to_snake(self):
+ for (k, v) in EXPECTED_SNAKIFICATION.items():
+ assert _camel_to_snake(k) == v
+
+ def test_reversible_camel_to_snake(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _camel_to_snake(k, reversible=True) == v
+
+
+class TestCaseSnakeToCamel:
+
+ def test_snake_to_camel_reversed(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _snake_to_camel(v, capitalize_first=True) == k
+
+
+class TestCaseCamelToSnakeAndBack:
+ def test_camel_to_snake_and_back(self):
+ for (k, v) in EXPECTED_REVERSIBLE.items():
+ assert _snake_to_camel(_camel_to_snake(k, reversible=True), capitalize_first=True) == k
+
+
+class TestCaseCamelDictToSnakeDict:
+ def test_ignore_list(self):
+ camel_dict = dict(Hello=dict(One='one', Two='two'), World=dict(Three='three', Four='four'))
+ snake_dict = camel_dict_to_snake_dict(camel_dict, ignore_list='World')
+ assert snake_dict['hello'] == dict(one='one', two='two')
+ assert snake_dict['world'] == dict(Three='three', Four='four')
+
+
+class TestCaseDictMerge:
+ def test_dict_merge(self):
+ base = dict(obj2=dict(), b1=True, b2=False, b3=False,
+ one=1, two=2, three=3, obj1=dict(key1=1, key2=2),
+ l1=[1, 3], l2=[1, 2, 3], l4=[4],
+ nested=dict(n1=dict(n2=2)))
+
+ other = dict(b1=True, b2=False, b3=True, b4=True,
+ one=1, three=4, four=4, obj1=dict(key1=2),
+ l1=[2, 1], l2=[3, 2, 1], l3=[1],
+ nested=dict(n1=dict(n2=2, n3=3)))
+
+ result = dict_merge(base, other)
+
+ # string assertions
+ assert 'one' in result
+ assert 'two' in result
+ assert result['three'] == 4
+ assert result['four'] == 4
+
+ # dict assertions
+ assert 'obj1' in result
+ assert 'key1' in result['obj1']
+ assert 'key2' in result['obj1']
+
+ # list assertions
+ # this line differs from the network_utils/common test of the function of the
+ # same name as this method does not merge lists
+ assert result['l1'], [2, 1]
+ assert 'l2' in result
+ assert result['l3'], [1]
+ assert 'l4' in result
+
+ # nested assertions
+ assert 'obj1' in result
+ assert result['obj1']['key1'], 2
+ assert 'key2' in result['obj1']
+
+ # bool assertions
+ assert 'b1' in result
+ assert 'b2' in result
+ assert result['b3']
+ assert result['b4']
+
+
+class TestCaseAzureIncidental:
+
+ def test_dict_merge_invalid_dict(self):
+ ''' if b is not a dict, return b '''
+ res = dict_merge({}, None)
+ assert res is None
+
+ def test_merge_sub_dicts(self):
+ '''merge sub dicts '''
+ a = {'a': {'a1': 1}}
+ b = {'a': {'b1': 2}}
+ c = {'a': {'a1': 1, 'b1': 2}}
+ res = dict_merge(a, b)
+ assert res == c
+
+
+class TestCaseRecursiveDiff:
+ def test_recursive_diff(self):
+ a = {'foo': {'bar': [{'baz': {'qux': 'ham_sandwich'}}]}}
+ c = {'foo': {'bar': [{'baz': {'qux': 'ham_sandwich'}}]}}
+ b = {'foo': {'bar': [{'baz': {'qux': 'turkey_sandwich'}}]}}
+
+ assert recursive_diff(a, b) is not None
+ assert len(recursive_diff(a, b)) == 2
+ assert recursive_diff(a, c) is None
+
+ @pytest.mark.parametrize(
+ 'p1, p2', (
+ ([1, 2], [2, 3]),
+ ({1: 2}, [2, 3]),
+ ([1, 2], {2: 3}),
+ ({2: 3}, 'notadict'),
+ ('notadict', {2: 3}),
+ )
+ )
+ def test_recursive_diff_negative(self, p1, p2):
+ with pytest.raises(TypeError, match="Unable to diff"):
+ recursive_diff(p1, p2)
diff --git a/test/units/module_utils/common/test_locale.py b/test/units/module_utils/common/test_locale.py
new file mode 100644
index 0000000..9d95986
--- /dev/null
+++ b/test/units/module_utils/common/test_locale.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# (c) 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
+
+from units.compat.mock import MagicMock
+
+from ansible.module_utils.common.locale import get_best_parsable_locale
+
+
+class TestLocale:
+ """Tests for get_best_paresable_locale"""
+
+ mock_module = MagicMock()
+ mock_module.get_bin_path = MagicMock(return_value='/usr/bin/locale')
+
+ def test_finding_best(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "C.utf8\nen_US.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C.utf8'
+
+ def test_finding_last(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.utf8\nen_UK.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C'
+
+ def test_finding_middle(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.utf8\nen_US.utf8\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'en_US.utf8'
+
+ def test_finding_prefered(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "es_ES.utf8\nMINE\nC\nPOSIX\n", ''))
+ locale = get_best_parsable_locale(self.mock_module, preferences=['MINE', 'C.utf8'])
+ assert locale == 'MINE'
+
+ def test_finding_C_on_no_match(self):
+ self.mock_module.run_command = MagicMock(return_value=(0, "fr_FR.UTF8\nMINE\n", ''))
+ locale = get_best_parsable_locale(self.mock_module)
+ assert locale == 'C'
diff --git a/test/units/module_utils/common/test_network.py b/test/units/module_utils/common/test_network.py
new file mode 100644
index 0000000..27d9503
--- /dev/null
+++ b/test/units/module_utils/common/test_network.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# (c) 2017 Red Hat, Inc.
+# 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
+
+import pytest
+
+from ansible.module_utils.common.network import (
+ to_bits,
+ to_masklen,
+ to_netmask,
+ to_subnet,
+ to_ipv6_network,
+ is_masklen,
+ is_netmask
+)
+
+
+def test_to_masklen():
+ assert 24 == to_masklen('255.255.255.0')
+
+
+def test_to_masklen_invalid():
+ with pytest.raises(ValueError):
+ to_masklen('255')
+
+
+def test_to_netmask():
+ assert '255.0.0.0' == to_netmask(8)
+ assert '255.0.0.0' == to_netmask('8')
+
+
+def test_to_netmask_invalid():
+ with pytest.raises(ValueError):
+ to_netmask(128)
+
+
+def test_to_subnet():
+ result = to_subnet('192.168.1.1', 24)
+ assert '192.168.1.0/24' == result
+
+ result = to_subnet('192.168.1.1', 24, dotted_notation=True)
+ assert '192.168.1.0 255.255.255.0' == result
+
+
+def test_to_subnet_invalid():
+ with pytest.raises(ValueError):
+ to_subnet('foo', 'bar')
+
+
+def test_is_masklen():
+ assert is_masklen(32)
+ assert not is_masklen(33)
+ assert not is_masklen('foo')
+
+
+def test_is_netmask():
+ assert is_netmask('255.255.255.255')
+ assert not is_netmask(24)
+ assert not is_netmask('foo')
+
+
+def test_to_ipv6_network():
+ assert '2001:db8::' == to_ipv6_network('2001:db8::')
+ assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0000:0000:8a2e:0370:7334')
+ assert '2001:0db8:85a3::' == to_ipv6_network('2001:0db8:85a3:0:0:8a2e:0370:7334')
+
+
+def test_to_bits():
+ assert to_bits('0') == '00000000'
+ assert to_bits('1') == '00000001'
+ assert to_bits('2') == '00000010'
+ assert to_bits('1337') == '10100111001'
+ assert to_bits('127.0.0.1') == '01111111000000000000000000000001'
+ assert to_bits('255.255.255.255') == '11111111111111111111111111111111'
+ assert to_bits('255.255.255.0') == '11111111111111111111111100000000'
diff --git a/test/units/module_utils/common/test_sys_info.py b/test/units/module_utils/common/test_sys_info.py
new file mode 100644
index 0000000..18aafe5
--- /dev/null
+++ b/test/units/module_utils/common/test_sys_info.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# (c) 2017-2018 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 pytest
+
+from units.compat.mock import patch
+
+from ansible.module_utils.six.moves import builtins
+
+# Functions being tested
+from ansible.module_utils.common.sys_info import get_distribution
+from ansible.module_utils.common.sys_info import get_distribution_version
+from ansible.module_utils.common.sys_info import get_platform_subclass
+
+
+realimport = builtins.__import__
+
+
+@pytest.fixture
+def platform_linux(mocker):
+ mocker.patch('platform.system', return_value='Linux')
+
+
+#
+# get_distribution tests
+#
+
+@pytest.mark.parametrize(
+ ('system', 'dist'),
+ (
+ ('Darwin', 'Darwin'),
+ ('SunOS', 'Solaris'),
+ ('FreeBSD', 'Freebsd'),
+ ),
+)
+def test_get_distribution_not_linux(system, dist, mocker):
+ """For platforms other than Linux, return the distribution"""
+ mocker.patch('platform.system', return_value=system)
+ mocker.patch('ansible.module_utils.common.sys_info.distro.id', return_value=dist)
+ assert get_distribution() == dist
+
+
+@pytest.mark.usefixtures("platform_linux")
+class TestGetDistribution:
+ """Tests for get_distribution that have to find something"""
+ def test_distro_known(self):
+ with patch('ansible.module_utils.distro.id', return_value="alpine"):
+ assert get_distribution() == "Alpine"
+
+ with patch('ansible.module_utils.distro.id', return_value="arch"):
+ assert get_distribution() == "Arch"
+
+ with patch('ansible.module_utils.distro.id', return_value="centos"):
+ assert get_distribution() == "Centos"
+
+ with patch('ansible.module_utils.distro.id', return_value="clear-linux-os"):
+ assert get_distribution() == "Clear-linux-os"
+
+ with patch('ansible.module_utils.distro.id', return_value="coreos"):
+ assert get_distribution() == "Coreos"
+
+ with patch('ansible.module_utils.distro.id', return_value="debian"):
+ assert get_distribution() == "Debian"
+
+ with patch('ansible.module_utils.distro.id', return_value="flatcar"):
+ assert get_distribution() == "Flatcar"
+
+ with patch('ansible.module_utils.distro.id', return_value="linuxmint"):
+ assert get_distribution() == "Linuxmint"
+
+ with patch('ansible.module_utils.distro.id', return_value="opensuse"):
+ assert get_distribution() == "Opensuse"
+
+ with patch('ansible.module_utils.distro.id', return_value="oracle"):
+ assert get_distribution() == "Oracle"
+
+ with patch('ansible.module_utils.distro.id', return_value="raspian"):
+ assert get_distribution() == "Raspian"
+
+ with patch('ansible.module_utils.distro.id', return_value="rhel"):
+ assert get_distribution() == "Redhat"
+
+ with patch('ansible.module_utils.distro.id', return_value="ubuntu"):
+ assert get_distribution() == "Ubuntu"
+
+ with patch('ansible.module_utils.distro.id', return_value="virtuozzo"):
+ assert get_distribution() == "Virtuozzo"
+
+ with patch('ansible.module_utils.distro.id', return_value="foo"):
+ assert get_distribution() == "Foo"
+
+ def test_distro_unknown(self):
+ with patch('ansible.module_utils.distro.id', return_value=""):
+ assert get_distribution() == "OtherLinux"
+
+ def test_distro_amazon_linux_short(self):
+ with patch('ansible.module_utils.distro.id', return_value="amzn"):
+ assert get_distribution() == "Amazon"
+
+ def test_distro_amazon_linux_long(self):
+ with patch('ansible.module_utils.distro.id', return_value="amazon"):
+ assert get_distribution() == "Amazon"
+
+
+#
+# get_distribution_version tests
+#
+
+@pytest.mark.parametrize(
+ ('system', 'version'),
+ (
+ ('Darwin', '19.6.0'),
+ ('SunOS', '11.4'),
+ ('FreeBSD', '12.1'),
+ ),
+)
+def test_get_distribution_version_not_linux(mocker, system, version):
+ """If it's not Linux, then it has no distribution"""
+ mocker.patch('platform.system', return_value=system)
+ mocker.patch('ansible.module_utils.common.sys_info.distro.version', return_value=version)
+ assert get_distribution_version() == version
+
+
+@pytest.mark.usefixtures("platform_linux")
+def test_distro_found():
+ with patch('ansible.module_utils.distro.version', return_value="1"):
+ assert get_distribution_version() == "1"
+
+
+#
+# Tests for get_platform_subclass
+#
+
+class TestGetPlatformSubclass:
+ class LinuxTest:
+ pass
+
+ class Foo(LinuxTest):
+ platform = "Linux"
+ distribution = None
+
+ class Bar(LinuxTest):
+ platform = "Linux"
+ distribution = "Bar"
+
+ def test_not_linux(self):
+ # if neither match, the fallback should be the top-level class
+ with patch('platform.system', return_value="Foo"):
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert get_platform_subclass(self.LinuxTest) is self.LinuxTest
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_none(self):
+ # match just the platform class, not a specific distribution
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value=None):
+ assert get_platform_subclass(self.LinuxTest) is self.Foo
+
+ @pytest.mark.usefixtures("platform_linux")
+ def test_get_distribution_found(self):
+ # match both the distribution and platform class
+ with patch('ansible.module_utils.common.sys_info.get_distribution', return_value="Bar"):
+ assert get_platform_subclass(self.LinuxTest) is self.Bar
diff --git a/test/units/module_utils/common/test_utils.py b/test/units/module_utils/common/test_utils.py
new file mode 100644
index 0000000..ef95239
--- /dev/null
+++ b/test/units/module_utils/common/test_utils.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 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
+
+from ansible.module_utils.common.sys_info import get_all_subclasses
+
+
+#
+# Tests for get_all_subclasses
+#
+
+class TestGetAllSubclasses:
+ class Base:
+ pass
+
+ class BranchI(Base):
+ pass
+
+ class BranchII(Base):
+ pass
+
+ class BranchIA(BranchI):
+ pass
+
+ class BranchIB(BranchI):
+ pass
+
+ class BranchIIA(BranchII):
+ pass
+
+ class BranchIIB(BranchII):
+ pass
+
+ def test_bottom_level(self):
+ assert get_all_subclasses(self.BranchIIB) == set()
+
+ def test_one_inheritance(self):
+ assert set(get_all_subclasses(self.BranchII)) == set([self.BranchIIA, self.BranchIIB])
+
+ def test_toplevel(self):
+ assert set(get_all_subclasses(self.Base)) == set([self.BranchI, self.BranchII,
+ self.BranchIA, self.BranchIB,
+ self.BranchIIA, self.BranchIIB])
diff --git a/test/units/module_utils/common/text/converters/test_container_to_bytes.py b/test/units/module_utils/common/text/converters/test_container_to_bytes.py
new file mode 100644
index 0000000..091545e
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_container_to_bytes.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import container_to_bytes
+
+
+DEFAULT_ENCODING = 'utf-8'
+DEFAULT_ERR_HANDLER = 'surrogate_or_strict'
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (b'str', b'str'),
+ (u'str', b'str'),
+ ([u'str'], [b'str']),
+ ((u'str'), (b'str')),
+ ({u'str': u'str'}, {b'str': b'str'}),
+ ]
+)
+@pytest.mark.parametrize('encoding', ['utf-8', 'latin1', 'shift_jis', 'big5', 'koi8_r'])
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace'])
+def test_container_to_bytes(test_input, expected, encoding, errors):
+ """Test for passing objects to container_to_bytes()."""
+ assert container_to_bytes(test_input, encoding=encoding, errors=errors) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (True, True),
+ (None, None),
+ (u'str', u'str'.encode(DEFAULT_ENCODING)),
+ (u'くらとみ', u'くらとみ'.encode(DEFAULT_ENCODING)),
+ (u'café', u'café'.encode(DEFAULT_ENCODING)),
+ (b'str', u'str'.encode(DEFAULT_ENCODING)),
+ (u'str', u'str'.encode(DEFAULT_ENCODING)),
+ ([u'str'], [u'str'.encode(DEFAULT_ENCODING)]),
+ ((u'str'), (u'str'.encode(DEFAULT_ENCODING))),
+ ({u'str': u'str'}, {u'str'.encode(DEFAULT_ENCODING): u'str'.encode(DEFAULT_ENCODING)}),
+ ]
+)
+def test_container_to_bytes_default_encoding_err(test_input, expected):
+ """
+ Test for passing objects to container_to_bytes(). Default encoding and errors
+ """
+ assert container_to_bytes(test_input, encoding=DEFAULT_ENCODING,
+ errors=DEFAULT_ERR_HANDLER) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding',
+ [
+ (u'くらとみ', 'latin1'),
+ (u'café', 'shift_jis'),
+ ]
+)
+@pytest.mark.parametrize('errors', ['surrogate_or_strict', 'strict'])
+def test_container_to_bytes_incomp_chars_and_encod(test_input, encoding, errors):
+ """
+ Test for passing incompatible characters and encodings container_to_bytes().
+ """
+ with pytest.raises(UnicodeEncodeError, match="codec can't encode"):
+ container_to_bytes(test_input, encoding=encoding, errors=errors)
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding,expected',
+ [
+ (u'くらとみ', 'latin1', b'????'),
+ (u'café', 'shift_jis', b'caf?'),
+ ]
+)
+def test_container_to_bytes_surrogate_then_replace(test_input, encoding, expected):
+ """
+ Test for container_to_bytes() with surrogate_then_replace err handler.
+ """
+ assert container_to_bytes(test_input, encoding=encoding,
+ errors='surrogate_then_replace') == expected
diff --git a/test/units/module_utils/common/text/converters/test_container_to_text.py b/test/units/module_utils/common/text/converters/test_container_to_text.py
new file mode 100644
index 0000000..39038f5
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_container_to_text.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import container_to_text
+
+
+DEFAULT_ENCODING = 'utf-8'
+DEFAULT_ERR_HANDLER = 'surrogate_or_strict'
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (b'str', u'str'),
+ (u'str', u'str'),
+ ([b'str'], [u'str']),
+ ((b'str'), (u'str')),
+ ({b'str': b'str'}, {u'str': u'str'}),
+ ]
+)
+@pytest.mark.parametrize('encoding', ['utf-8', 'latin1', 'shift-jis', 'big5', 'koi8_r', ])
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace', ])
+def test_container_to_text_different_types(test_input, expected, encoding, errors):
+ """Test for passing objects to container_to_text()."""
+ assert container_to_text(test_input, encoding=encoding, errors=errors) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ ({1: 1}, {1: 1}),
+ ([1, 2], [1, 2]),
+ ((1, 2), (1, 2)),
+ (1, 1),
+ (1.1, 1.1),
+ (True, True),
+ (None, None),
+ (u'str', u'str'),
+ (u'くらとみ'.encode(DEFAULT_ENCODING), u'くらとみ'),
+ (u'café'.encode(DEFAULT_ENCODING), u'café'),
+ (u'str'.encode(DEFAULT_ENCODING), u'str'),
+ ([u'str'.encode(DEFAULT_ENCODING)], [u'str']),
+ ((u'str'.encode(DEFAULT_ENCODING)), (u'str')),
+ ({b'str': b'str'}, {u'str': u'str'}),
+ ]
+)
+def test_container_to_text_default_encoding_and_err(test_input, expected):
+ """
+ Test for passing objects to container_to_text(). Default encoding and errors
+ """
+ assert container_to_text(test_input, encoding=DEFAULT_ENCODING,
+ errors=DEFAULT_ERR_HANDLER) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input,encoding,expected',
+ [
+ (u'й'.encode('utf-8'), 'latin1', u'й'),
+ (u'café'.encode('utf-8'), 'shift_jis', u'cafテゥ'),
+ ]
+)
+@pytest.mark.parametrize('errors', ['strict', 'surrogate_or_strict', 'surrogate_then_replace', ])
+def test_container_to_text_incomp_encod_chars(test_input, encoding, errors, expected):
+ """
+ Test for passing incompatible characters and encodings container_to_text().
+ """
+ assert container_to_text(test_input, encoding=encoding, errors=errors) == expected
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
new file mode 100644
index 0000000..022f38f
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_json_encode_fallback.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from datetime import datetime, timedelta, tzinfo
+
+from ansible.module_utils.common.text.converters import _json_encode_fallback
+
+
+class timezone(tzinfo):
+ """Simple timezone implementation for use until we drop Python 2.7 support."""
+ def __init__(self, offset):
+ self._offset = offset
+
+ 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',
+ [
+ (set([1]), [1]),
+ (datetime(2019, 5, 14, 13, 39, 38, 569047), '2019-05-14T13:39:38.569047'),
+ (datetime(2019, 5, 14, 13, 47, 16, 923866), '2019-05-14T13:47:16.923866'),
+ (datetime(2019, 6, 15, 14, 45, tzinfo=timezone(timedelta(0))), '2019-06-15T14:45:00+00:00'),
+ (datetime(2019, 6, 15, 14, 45, tzinfo=timezone(timedelta(hours=1, minutes=40))), '2019-06-15T14:45:00+01:40'),
+ ]
+)
+def test_json_encode_fallback(test_input, expected):
+ """
+ Test for passing expected objects to _json_encode_fallback().
+ """
+ assert _json_encode_fallback(test_input) == expected
+
+
+@pytest.mark.parametrize(
+ 'test_input',
+ [
+ 1,
+ 1.1,
+ u'string',
+ b'string',
+ [1, 2],
+ True,
+ None,
+ {1: 1},
+ (1, 2),
+ ]
+)
+def test_json_encode_fallback_default_behavior(test_input):
+ """
+ Test for _json_encode_fallback() default behavior.
+
+ It must fail with TypeError.
+ """
+ with pytest.raises(TypeError, match='Cannot json serialize'):
+ _json_encode_fallback(test_input)
diff --git a/test/units/module_utils/common/text/converters/test_jsonify.py b/test/units/module_utils/common/text/converters/test_jsonify.py
new file mode 100644
index 0000000..a341531
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_jsonify.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import pytest
+
+from ansible.module_utils.common.text.converters import jsonify
+
+
+@pytest.mark.parametrize(
+ 'test_input,expected',
+ [
+ (1, '1'),
+ (u'string', u'"string"'),
+ (u'くらとみ', u'"\\u304f\\u3089\\u3068\\u307f"'),
+ (u'café', u'"caf\\u00e9"'),
+ (b'string', u'"string"'),
+ (False, u'false'),
+ (u'string'.encode('utf-8'), u'"string"'),
+ ]
+)
+def test_jsonify(test_input, expected):
+ """Test for jsonify()."""
+ assert jsonify(test_input) == expected
diff --git a/test/units/module_utils/common/text/converters/test_to_str.py b/test/units/module_utils/common/text/converters/test_to_str.py
new file mode 100644
index 0000000..712ed85
--- /dev/null
+++ b/test/units/module_utils/common/text/converters/test_to_str.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# (c) 2016 Toshio Kuratomi <tkuratomi@ansible.com>
+# Copyright (c) 2017 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 itertools
+
+import pytest
+
+from ansible.module_utils.six import PY3
+
+from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native
+
+
+# Format: byte representation, text representation, encoding of byte representation
+VALID_STRINGS = (
+ (b'abcde', u'abcde', 'ascii'),
+ (b'caf\xc3\xa9', u'caf\xe9', 'utf-8'),
+ (b'caf\xe9', u'caf\xe9', 'latin-1'),
+ # u'くらとみ'
+ (b'\xe3\x81\x8f\xe3\x82\x89\xe3\x81\xa8\xe3\x81\xbf', u'\u304f\u3089\u3068\u307f', 'utf-8'),
+ (b'\x82\xad\x82\xe7\x82\xc6\x82\xdd', u'\u304f\u3089\u3068\u307f', 'shift-jis'),
+)
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[1]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[1]) for d in VALID_STRINGS)))
+def test_to_text(in_string, encoding, expected):
+ """test happy path of decoding to text"""
+ assert to_text(in_string, encoding) == expected
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[0]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[0]) for d in VALID_STRINGS)))
+def test_to_bytes(in_string, encoding, expected):
+ """test happy path of encoding to bytes"""
+ assert to_bytes(in_string, encoding) == expected
+
+
+@pytest.mark.parametrize('in_string, encoding, expected',
+ itertools.chain(((d[0], d[2], d[1] if PY3 else d[0]) for d in VALID_STRINGS),
+ ((d[1], d[2], d[1] if PY3 else d[0]) for d in VALID_STRINGS)))
+def test_to_native(in_string, encoding, expected):
+ """test happy path of encoding to native strings"""
+ assert to_native(in_string, encoding) == expected
diff --git a/test/units/module_utils/common/text/formatters/test_bytes_to_human.py b/test/units/module_utils/common/text/formatters/test_bytes_to_human.py
new file mode 100644
index 0000000..41475f5
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_bytes_to_human.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# 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 pytest
+
+from ansible.module_utils.common.text.formatters import bytes_to_human
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, u'0.00 Bytes'),
+ (0.5, u'0.50 Bytes'),
+ (0.54, u'0.54 Bytes'),
+ (1024, u'1.00 KB'),
+ (1025, u'1.00 KB'),
+ (1536, u'1.50 KB'),
+ (1790, u'1.75 KB'),
+ (1048576, u'1.00 MB'),
+ (1073741824, u'1.00 GB'),
+ (1099511627776, u'1.00 TB'),
+ (1125899906842624, u'1.00 PB'),
+ (1152921504606846976, u'1.00 EB'),
+ (1180591620717411303424, u'1.00 ZB'),
+ (1208925819614629174706176, u'1.00 YB'),
+ ]
+)
+def test_bytes_to_human(input_data, expected):
+ """Test of bytes_to_human function, only proper numbers are passed."""
+ assert bytes_to_human(input_data) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, u'0.00 bits'),
+ (0.5, u'0.50 bits'),
+ (0.54, u'0.54 bits'),
+ (1024, u'1.00 Kb'),
+ (1025, u'1.00 Kb'),
+ (1536, u'1.50 Kb'),
+ (1790, u'1.75 Kb'),
+ (1048576, u'1.00 Mb'),
+ (1073741824, u'1.00 Gb'),
+ (1099511627776, u'1.00 Tb'),
+ (1125899906842624, u'1.00 Pb'),
+ (1152921504606846976, u'1.00 Eb'),
+ (1180591620717411303424, u'1.00 Zb'),
+ (1208925819614629174706176, u'1.00 Yb'),
+ ]
+)
+def test_bytes_to_human_isbits(input_data, expected):
+ """Test of bytes_to_human function with isbits=True proper results."""
+ assert bytes_to_human(input_data, isbits=True) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit,expected',
+ [
+ (0, u'B', u'0.00 Bytes'),
+ (0.5, u'B', u'0.50 Bytes'),
+ (0.54, u'B', u'0.54 Bytes'),
+ (1024, u'K', u'1.00 KB'),
+ (1536, u'K', u'1.50 KB'),
+ (1790, u'K', u'1.75 KB'),
+ (1048576, u'M', u'1.00 MB'),
+ (1099511627776, u'T', u'1.00 TB'),
+ (1152921504606846976, u'E', u'1.00 EB'),
+ (1180591620717411303424, u'Z', u'1.00 ZB'),
+ (1208925819614629174706176, u'Y', u'1.00 YB'),
+ (1025, u'KB', u'1025.00 Bytes'),
+ (1073741824, u'Gb', u'1073741824.00 Bytes'),
+ (1125899906842624, u'Pb', u'1125899906842624.00 Bytes'),
+ ]
+)
+def test_bytes_to_human_unit(input_data, unit, expected):
+ """Test unit argument of bytes_to_human function proper results."""
+ assert bytes_to_human(input_data, unit=unit) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit,expected',
+ [
+ (0, u'B', u'0.00 bits'),
+ (0.5, u'B', u'0.50 bits'),
+ (0.54, u'B', u'0.54 bits'),
+ (1024, u'K', u'1.00 Kb'),
+ (1536, u'K', u'1.50 Kb'),
+ (1790, u'K', u'1.75 Kb'),
+ (1048576, u'M', u'1.00 Mb'),
+ (1099511627776, u'T', u'1.00 Tb'),
+ (1152921504606846976, u'E', u'1.00 Eb'),
+ (1180591620717411303424, u'Z', u'1.00 Zb'),
+ (1208925819614629174706176, u'Y', u'1.00 Yb'),
+ (1025, u'KB', u'1025.00 bits'),
+ (1073741824, u'Gb', u'1073741824.00 bits'),
+ (1125899906842624, u'Pb', u'1125899906842624.00 bits'),
+ ]
+)
+def test_bytes_to_human_unit_isbits(input_data, unit, expected):
+ """Test unit argument of bytes_to_human function with isbits=True proper results."""
+ assert bytes_to_human(input_data, isbits=True, unit=unit) == expected
+
+
+@pytest.mark.parametrize('input_data', [0j, u'1B', [1], {1: 1}, None, b'1B'])
+def test_bytes_to_human_illegal_size(input_data):
+ """Test of bytes_to_human function, illegal objects are passed as a size."""
+ e_regexp = (r'(no ordering relation is defined for complex numbers)|'
+ r'(unsupported operand type\(s\) for /)|(unorderable types)|'
+ r'(not supported between instances of)')
+ with pytest.raises(TypeError, match=e_regexp):
+ bytes_to_human(input_data)
diff --git a/test/units/module_utils/common/text/formatters/test_human_to_bytes.py b/test/units/module_utils/common/text/formatters/test_human_to_bytes.py
new file mode 100644
index 0000000..d02699a
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_human_to_bytes.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# Copyright 2019, Sviatoslav Sydorenko <webknjaz@redhat.com>
+# 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 pytest
+
+from ansible.module_utils.common.text.formatters import human_to_bytes
+
+
+NUM_IN_METRIC = {
+ 'K': 2 ** 10,
+ 'M': 2 ** 20,
+ 'G': 2 ** 30,
+ 'T': 2 ** 40,
+ 'P': 2 ** 50,
+ 'E': 2 ** 60,
+ 'Z': 2 ** 70,
+ 'Y': 2 ** 80,
+}
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, 0),
+ (u'0B', 0),
+ (1024, NUM_IN_METRIC['K']),
+ (u'1024B', NUM_IN_METRIC['K']),
+ (u'1K', NUM_IN_METRIC['K']),
+ (u'1KB', NUM_IN_METRIC['K']),
+ (u'1M', NUM_IN_METRIC['M']),
+ (u'1MB', NUM_IN_METRIC['M']),
+ (u'1G', NUM_IN_METRIC['G']),
+ (u'1GB', NUM_IN_METRIC['G']),
+ (u'1T', NUM_IN_METRIC['T']),
+ (u'1TB', NUM_IN_METRIC['T']),
+ (u'1P', NUM_IN_METRIC['P']),
+ (u'1PB', NUM_IN_METRIC['P']),
+ (u'1E', NUM_IN_METRIC['E']),
+ (u'1EB', NUM_IN_METRIC['E']),
+ (u'1Z', NUM_IN_METRIC['Z']),
+ (u'1ZB', NUM_IN_METRIC['Z']),
+ (u'1Y', NUM_IN_METRIC['Y']),
+ (u'1YB', NUM_IN_METRIC['Y']),
+ ]
+)
+def test_human_to_bytes_number(input_data, expected):
+ """Test of human_to_bytes function, only number arg is passed."""
+ assert human_to_bytes(input_data) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit',
+ [
+ (u'1024', 'B'),
+ (1, u'K'),
+ (1, u'KB'),
+ (u'1', u'M'),
+ (u'1', u'MB'),
+ (1, u'G'),
+ (1, u'GB'),
+ (1, u'T'),
+ (1, u'TB'),
+ (u'1', u'P'),
+ (u'1', u'PB'),
+ (u'1', u'E'),
+ (u'1', u'EB'),
+ (u'1', u'Z'),
+ (u'1', u'ZB'),
+ (u'1', u'Y'),
+ (u'1', u'YB'),
+ ]
+)
+def test_human_to_bytes_number_unit(input_data, unit):
+ """Test of human_to_bytes function, number and default_unit args are passed."""
+ assert human_to_bytes(input_data, default_unit=unit) == NUM_IN_METRIC.get(unit[0], 1024)
+
+
+@pytest.mark.parametrize('test_input', [u'1024s', u'1024w', ])
+def test_human_to_bytes_wrong_unit(test_input):
+ """Test of human_to_bytes function, wrong units."""
+ with pytest.raises(ValueError, match="The suffix must be one of"):
+ human_to_bytes(test_input)
+
+
+@pytest.mark.parametrize('test_input', [u'b1bbb', u'm2mmm', u'', u' ', -1])
+def test_human_to_bytes_wrong_number(test_input):
+ """Test of human_to_bytes function, number param is invalid string / number."""
+ with pytest.raises(ValueError, match="can't interpret"):
+ human_to_bytes(test_input)
+
+
+@pytest.mark.parametrize(
+ 'input_data,expected',
+ [
+ (0, 0),
+ (u'0B', 0),
+ (u'1024b', 1024),
+ (u'1024B', 1024),
+ (u'1K', NUM_IN_METRIC['K']),
+ (u'1Kb', NUM_IN_METRIC['K']),
+ (u'1M', NUM_IN_METRIC['M']),
+ (u'1Mb', NUM_IN_METRIC['M']),
+ (u'1G', NUM_IN_METRIC['G']),
+ (u'1Gb', NUM_IN_METRIC['G']),
+ (u'1T', NUM_IN_METRIC['T']),
+ (u'1Tb', NUM_IN_METRIC['T']),
+ (u'1P', NUM_IN_METRIC['P']),
+ (u'1Pb', NUM_IN_METRIC['P']),
+ (u'1E', NUM_IN_METRIC['E']),
+ (u'1Eb', NUM_IN_METRIC['E']),
+ (u'1Z', NUM_IN_METRIC['Z']),
+ (u'1Zb', NUM_IN_METRIC['Z']),
+ (u'1Y', NUM_IN_METRIC['Y']),
+ (u'1Yb', NUM_IN_METRIC['Y']),
+ ]
+)
+def test_human_to_bytes_isbits(input_data, expected):
+ """Test of human_to_bytes function, isbits = True."""
+ assert human_to_bytes(input_data, isbits=True) == expected
+
+
+@pytest.mark.parametrize(
+ 'input_data,unit',
+ [
+ (1024, 'b'),
+ (1024, 'B'),
+ (1, u'K'),
+ (1, u'Kb'),
+ (u'1', u'M'),
+ (u'1', u'Mb'),
+ (1, u'G'),
+ (1, u'Gb'),
+ (1, u'T'),
+ (1, u'Tb'),
+ (u'1', u'P'),
+ (u'1', u'Pb'),
+ (u'1', u'E'),
+ (u'1', u'Eb'),
+ (u'1', u'Z'),
+ (u'1', u'Zb'),
+ (u'1', u'Y'),
+ (u'1', u'Yb'),
+ ]
+)
+def test_human_to_bytes_isbits_default_unit(input_data, unit):
+ """Test of human_to_bytes function, isbits = True and default_unit args are passed."""
+ assert human_to_bytes(input_data, default_unit=unit, isbits=True) == NUM_IN_METRIC.get(unit[0], 1024)
+
+
+@pytest.mark.parametrize(
+ 'test_input,isbits',
+ [
+ ('1024Kb', False),
+ ('10Mb', False),
+ ('1Gb', False),
+ ('10MB', True),
+ ('2KB', True),
+ ('4GB', True),
+ ]
+)
+def test_human_to_bytes_isbits_wrong_unit(test_input, isbits):
+ """Test of human_to_bytes function, unit identifier is in an invalid format for isbits value."""
+ with pytest.raises(ValueError, match="Value is not a valid string"):
+ human_to_bytes(test_input, isbits=isbits)
+
+
+@pytest.mark.parametrize(
+ 'test_input,unit,isbits',
+ [
+ (1024, 'Kb', False),
+ ('10', 'Mb', False),
+ ('10', 'MB', True),
+ (2, 'KB', True),
+ ('4', 'GB', True),
+ ]
+)
+def test_human_to_bytes_isbits_wrong_default_unit(test_input, unit, isbits):
+ """Test of human_to_bytes function, default_unit is in an invalid format for isbits value."""
+ with pytest.raises(ValueError, match="Value is not a valid string"):
+ human_to_bytes(test_input, default_unit=unit, isbits=isbits)
diff --git a/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py b/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py
new file mode 100644
index 0000000..1ecc013
--- /dev/null
+++ b/test/units/module_utils/common/text/formatters/test_lenient_lowercase.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019, Andrew Klychkov @Andersson007 <aaklychkov@mail.ru>
+# 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
+
+from datetime import datetime
+
+import pytest
+
+from ansible.module_utils.common.text.formatters import lenient_lowercase
+
+
+INPUT_LIST = [
+ u'HELLO',
+ u'Ёлка',
+ u'cafÉ',
+ u'くらとみ',
+ b'HELLO',
+ 1,
+ {1: 'Dict'},
+ True,
+ [1],
+ 3.14159,
+]
+
+EXPECTED_LIST = [
+ u'hello',
+ u'ёлка',
+ u'café',
+ u'くらとみ',
+ b'hello',
+ 1,
+ {1: 'Dict'},
+ True,
+ [1],
+ 3.14159,
+]
+
+result_list = lenient_lowercase(INPUT_LIST)
+
+
+@pytest.mark.parametrize(
+ 'input_value,expected_outcome',
+ [
+ (result_list[0], EXPECTED_LIST[0]),
+ (result_list[1], EXPECTED_LIST[1]),
+ (result_list[2], EXPECTED_LIST[2]),
+ (result_list[3], EXPECTED_LIST[3]),
+ (result_list[4], EXPECTED_LIST[4]),
+ (result_list[5], EXPECTED_LIST[5]),
+ (result_list[6], EXPECTED_LIST[6]),
+ (result_list[7], EXPECTED_LIST[7]),
+ (result_list[8], EXPECTED_LIST[8]),
+ (result_list[9], EXPECTED_LIST[9]),
+ ]
+)
+def test_lenient_lowercase(input_value, expected_outcome):
+ """Test that lenient_lowercase() proper results."""
+ assert input_value == expected_outcome
+
+
+@pytest.mark.parametrize('input_data', [1, False, 1.001, 1j, datetime.now(), ])
+def test_lenient_lowercase_illegal_data_type(input_data):
+ """Test passing objects of illegal types to lenient_lowercase()."""
+ with pytest.raises(TypeError, match='object is not iterable'):
+ lenient_lowercase(input_data)
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
new file mode 100644
index 0000000..6cbcb8b
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_missing_parameters.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, 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 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.validation import check_missing_parameters
+
+
+@pytest.fixture
+def arguments_terms():
+ return {"path": ""}
+
+
+def test_check_missing_parameters():
+ assert check_missing_parameters([], {}) == []
+
+
+def test_check_missing_parameters_list():
+ expected = "missing required arguments: path"
+
+ with pytest.raises(TypeError) as e:
+ check_missing_parameters({}, ["path"])
+
+ assert to_native(e.value) == expected
+
+
+def test_check_missing_parameters_positive():
+ assert check_missing_parameters({"path": "/foo"}, ["path"]) == []
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
new file mode 100644
index 0000000..7bf9076
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_mutually_exclusive.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_mutually_exclusive
+
+
+@pytest.fixture
+def mutually_exclusive_terms():
+ return [
+ ('string1', 'string2',),
+ ('box', 'fox', 'socks'),
+ ]
+
+
+def test_check_mutually_exclusive(mutually_exclusive_terms):
+ params = {
+ 'string1': 'cat',
+ 'fox': 'hat',
+ }
+ assert check_mutually_exclusive(mutually_exclusive_terms, params) == []
+
+
+def test_check_mutually_exclusive_found(mutually_exclusive_terms):
+ params = {
+ 'string1': 'cat',
+ 'string2': 'hat',
+ 'fox': 'red',
+ 'socks': 'blue',
+ }
+ expected = "parameters are mutually exclusive: string1|string2, box|fox|socks"
+
+ with pytest.raises(TypeError) as e:
+ check_mutually_exclusive(mutually_exclusive_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_mutually_exclusive_none():
+ terms = None
+ params = {
+ 'string1': 'cat',
+ 'fox': 'hat',
+ }
+ assert check_mutually_exclusive(terms, params) == []
+
+
+def test_check_mutually_exclusive_no_params(mutually_exclusive_terms):
+ with pytest.raises(TypeError) as te:
+ check_mutually_exclusive(mutually_exclusive_terms, None)
+ assert "'NoneType' object is not iterable" in to_native(te.value)
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
new file mode 100644
index 0000000..1dd5458
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_arguments.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_arguments
+
+
+@pytest.fixture
+def arguments_terms():
+ return {
+ 'foo': {
+ 'required': True,
+ },
+ 'bar': {
+ 'required': False,
+ },
+ 'tomato': {
+ 'irrelevant': 72,
+ },
+ }
+
+
+@pytest.fixture
+def arguments_terms_multiple():
+ return {
+ 'foo': {
+ 'required': True,
+ },
+ 'bar': {
+ 'required': True,
+ },
+ 'tomato': {
+ 'irrelevant': 72,
+ },
+ }
+
+
+def test_check_required_arguments(arguments_terms):
+ params = {
+ 'foo': 'hello',
+ 'bar': 'haha',
+ }
+ assert check_required_arguments(arguments_terms, params) == []
+
+
+def test_check_required_arguments_missing(arguments_terms):
+ params = {
+ 'apples': 'woohoo',
+ }
+ expected = "missing required arguments: foo"
+
+ with pytest.raises(TypeError) as e:
+ check_required_arguments(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_arguments_missing_multiple(arguments_terms_multiple):
+ params = {
+ 'apples': 'woohoo',
+ }
+ expected = "missing required arguments: bar, foo"
+
+ with pytest.raises(TypeError) as e:
+ check_required_arguments(arguments_terms_multiple, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_arguments_missing_none():
+ terms = None
+ params = {
+ 'foo': 'bar',
+ 'baz': 'buzz',
+ }
+ assert check_required_arguments(terms, params) == []
+
+
+def test_check_required_arguments_no_params(arguments_terms):
+ with pytest.raises(TypeError) as te:
+ check_required_arguments(arguments_terms, None)
+ assert "'NoneType' is not iterable" in to_native(te.value)
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
new file mode 100644
index 0000000..62cccff
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_by.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_by
+
+
+@pytest.fixture
+def path_arguments_terms():
+ return {
+ "path": ["mode", "owner"],
+ }
+
+
+def test_check_required_by():
+ arguments_terms = {}
+ params = {}
+ assert check_required_by(arguments_terms, params) == {}
+
+
+def test_check_required_by_missing():
+ arguments_terms = {
+ "force": "force_reason",
+ }
+ params = {"force": True}
+ expected = "missing parameter(s) required by 'force': force_reason"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_multiple(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ }
+ expected = "missing parameter(s) required by 'path': mode, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_single(path_arguments_terms):
+ params = {"path": "/foo/bar", "mode": "0700"}
+ expected = "missing parameter(s) required by 'path': owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_missing_none(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ "mode": "0700",
+ "owner": "root",
+ }
+ assert check_required_by(path_arguments_terms, params)
+
+
+def test_check_required_by_options_context(path_arguments_terms):
+ params = {"path": "/foo/bar", "mode": "0700"}
+
+ options_context = ["foo_context"]
+
+ expected = "missing parameter(s) required by 'path': owner found in foo_context"
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_by_missing_multiple_options_context(path_arguments_terms):
+ params = {
+ "path": "/foo/bar",
+ }
+ options_context = ["foo_context"]
+
+ expected = (
+ "missing parameter(s) required by 'path': mode, owner found in foo_context"
+ )
+
+ with pytest.raises(TypeError) as e:
+ check_required_by(path_arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
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
new file mode 100644
index 0000000..4189164
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_if.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_if
+
+
+def test_check_required_if():
+ arguments_terms = {}
+ params = {}
+ assert check_required_if(arguments_terms, params) == []
+
+
+def test_check_required_if_missing():
+ arguments_terms = [["state", "present", ("path",)]]
+ params = {"state": "present"}
+ expected = "state is present but all of the following are missing: path"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_required():
+ arguments_terms = [["state", "present", ("path", "owner"), True]]
+ params = {"state": "present"}
+ expected = "state is present but any of the following are missing: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_multiple():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ }
+ expected = "state is present but all of the following are missing: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_missing_multiple_with_context():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ }
+ options_context = ["foo_context"]
+ expected = "state is present but all of the following are missing: path, owner found in foo_context"
+
+ with pytest.raises(TypeError) as e:
+ check_required_if(arguments_terms, params, options_context)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_if_multiple():
+ arguments_terms = [["state", "present", ("path", "owner")]]
+ params = {
+ "state": "present",
+ "path": "/foo",
+ "owner": "root",
+ }
+ options_context = ["foo_context"]
+ assert check_required_if(arguments_terms, params) == []
+ assert check_required_if(arguments_terms, params, options_context) == []
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
new file mode 100644
index 0000000..b081889
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_one_of.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_one_of
+
+
+@pytest.fixture
+def arguments_terms():
+ return [["path", "owner"]]
+
+
+def test_check_required_one_of():
+ assert check_required_one_of([], {}) == []
+
+
+def test_check_required_one_of_missing(arguments_terms):
+ params = {"state": "present"}
+ expected = "one of the following is required: path, owner"
+
+ with pytest.raises(TypeError) as e:
+ check_required_one_of(arguments_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_one_of_provided(arguments_terms):
+ params = {"state": "present", "path": "/foo"}
+ assert check_required_one_of(arguments_terms, params) == []
+
+
+def test_check_required_one_of_context(arguments_terms):
+ params = {"state": "present"}
+ expected = "one of the following is required: path, owner found in foo_context"
+ option_context = ["foo_context"]
+
+ with pytest.raises(TypeError) as e:
+ check_required_one_of(arguments_terms, params, option_context)
+
+ assert to_native(e.value) == expected
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
new file mode 100644
index 0000000..8a2daab
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_required_together.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_required_together
+
+
+@pytest.fixture
+def together_terms():
+ return [
+ ['bananas', 'potatoes'],
+ ['cats', 'wolves']
+ ]
+
+
+def test_check_required_together(together_terms):
+ params = {
+ 'bananas': 'hello',
+ 'potatoes': 'this is here too',
+ 'dogs': 'haha',
+ }
+ assert check_required_together(together_terms, params) == []
+
+
+def test_check_required_together_missing(together_terms):
+ params = {
+ 'bananas': 'woohoo',
+ 'wolves': 'uh oh',
+ }
+ expected = "parameters are required together: bananas, potatoes"
+
+ with pytest.raises(TypeError) as e:
+ check_required_together(together_terms, params)
+
+ assert to_native(e.value) == expected
+
+
+def test_check_required_together_missing_none():
+ terms = None
+ params = {
+ 'foo': 'bar',
+ 'baz': 'buzz',
+ }
+ assert check_required_together(terms, params) == []
+
+
+def test_check_required_together_no_params(together_terms):
+ with pytest.raises(TypeError) as te:
+ check_required_together(together_terms, None)
+
+ assert "'NoneType' object is not iterable" in to_native(te.value)
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
new file mode 100644
index 0000000..7f6b11d
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bits.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bits
+
+
+def test_check_type_bits():
+ test_cases = (
+ ('1', 1),
+ (99, 99),
+ (1.5, 2),
+ ('1.5', 2),
+ ('2b', 2),
+ ('2k', 2048),
+ ('2K', 2048),
+ ('1m', 1048576),
+ ('1M', 1048576),
+ ('1g', 1073741824),
+ ('1G', 1073741824),
+ (1073741824, 1073741824),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bits(case[0])
+
+
+def test_check_type_bits_fail():
+ test_cases = (
+ 'foo',
+ '2KB',
+ '1MB',
+ '1GB',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bits(case)
+ assert 'cannot be converted to a Bit value' in to_native(e.value)
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
new file mode 100644
index 0000000..bd867dc
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bool.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bool
+
+
+def test_check_type_bool():
+ test_cases = (
+ (True, True),
+ (False, False),
+ ('1', True),
+ ('on', True),
+ (1, True),
+ ('0', False),
+ (0, False),
+ ('n', False),
+ ('f', False),
+ ('false', False),
+ ('true', True),
+ ('y', True),
+ ('t', True),
+ ('yes', True),
+ ('no', False),
+ ('off', False),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bool(case[0])
+
+
+def test_check_type_bool_fail():
+ default_test_msg = 'cannot be converted to a bool'
+ test_cases = (
+ ({'k1': 'v1'}, 'is not a valid bool'),
+ (3.14159, default_test_msg),
+ (-1, default_test_msg),
+ (-90810398401982340981023948192349081, default_test_msg),
+ (90810398401982340981023948192349081, default_test_msg),
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bool(case)
+ assert 'cannot be converted to a bool' in to_native(e.value)
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
new file mode 100644
index 0000000..6ff62dc
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_bytes.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_bytes
+
+
+def test_check_type_bytes():
+ test_cases = (
+ ('1', 1),
+ (99, 99),
+ (1.5, 2),
+ ('1.5', 2),
+ ('2b', 2),
+ ('2B', 2),
+ ('2k', 2048),
+ ('2K', 2048),
+ ('2KB', 2048),
+ ('1m', 1048576),
+ ('1M', 1048576),
+ ('1MB', 1048576),
+ ('1g', 1073741824),
+ ('1G', 1073741824),
+ ('1GB', 1073741824),
+ (1073741824, 1073741824),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_bytes(case[0])
+
+
+def test_check_type_bytes_fail():
+ test_cases = (
+ 'foo',
+ '2kb',
+ '2Kb',
+ '1mb',
+ '1Mb',
+ '1gb',
+ '1Gb',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_bytes(case)
+ assert 'cannot be converted to a Byte value' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_dict.py b/test/units/module_utils/common/validation/test_check_type_dict.py
new file mode 100644
index 0000000..75638c5
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_dict.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.common.validation import check_type_dict
+
+
+def test_check_type_dict():
+ test_cases = (
+ ({'k1': 'v1'}, {'k1': 'v1'}),
+ ('k1=v1,k2=v2', {'k1': 'v1', 'k2': 'v2'}),
+ ('k1=v1, k2=v2', {'k1': 'v1', 'k2': 'v2'}),
+ ('k1=v1, k2=v2, k3=v3', {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}),
+ ('{"key": "value", "list": ["one", "two"]}', {'key': 'value', 'list': ['one', 'two']})
+ )
+ for case in test_cases:
+ assert case[1] == check_type_dict(case[0])
+
+
+def test_check_type_dict_fail():
+ test_cases = (
+ 1,
+ 3.14159,
+ [1, 2],
+ 'a',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError):
+ check_type_dict(case)
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
new file mode 100644
index 0000000..57837fa
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_float.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_float
+
+
+def test_check_type_float():
+ test_cases = (
+ ('1.5', 1.5),
+ ('''1.5''', 1.5),
+ (u'1.5', 1.5),
+ (1002, 1002.0),
+ (1.0, 1.0),
+ (3.141592653589793, 3.141592653589793),
+ ('3.141592653589793', 3.141592653589793),
+ (b'3.141592653589793', 3.141592653589793),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_float(case[0])
+
+
+def test_check_type_float_fail():
+ test_cases = (
+ {'k1': 'v1'},
+ ['a', 'b'],
+ 'b',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_float(case)
+ assert 'cannot be converted to a float' in to_native(e.value)
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
new file mode 100644
index 0000000..22cedf6
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_int.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_int
+
+
+def test_check_type_int():
+ test_cases = (
+ ('1', 1),
+ (u'1', 1),
+ (1002, 1002),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_int(case[0])
+
+
+def test_check_type_int_fail():
+ test_cases = (
+ {'k1': 'v1'},
+ (b'1', 1),
+ (3.14159, 3),
+ 'b',
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_int(case)
+ assert 'cannot be converted to an int' in to_native(e.value)
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
new file mode 100644
index 0000000..e78e54b
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_jsonarg.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_jsonarg
+
+
+def test_check_type_jsonarg():
+ test_cases = (
+ ('a', 'a'),
+ ('a ', 'a'),
+ (b'99', b'99'),
+ (b'99 ', b'99'),
+ ({'k1': 'v1'}, '{"k1": "v1"}'),
+ ([1, 'a'], '[1, "a"]'),
+ ((1, 2, 'three'), '[1, 2, "three"]'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_jsonarg(case[0])
+
+
+def test_check_type_jsonarg_fail():
+ test_cases = (
+ 1.5,
+ 910313498012384012341982374109384098,
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError) as e:
+ check_type_jsonarg(case)
+ assert 'cannot be converted to a json string' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_check_type_list.py b/test/units/module_utils/common/validation/test_check_type_list.py
new file mode 100644
index 0000000..3f7a9ee
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_list.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.common.validation import check_type_list
+
+
+def test_check_type_list():
+ test_cases = (
+ ([1, 2], [1, 2]),
+ (1, ['1']),
+ (['a', 'b'], ['a', 'b']),
+ ('a', ['a']),
+ (3.14159, ['3.14159']),
+ ('a,b,1,2', ['a', 'b', '1', '2'])
+ )
+ for case in test_cases:
+ assert case[1] == check_type_list(case[0])
+
+
+def test_check_type_list_failure():
+ test_cases = (
+ {'k1': 'v1'},
+ )
+ for case in test_cases:
+ with pytest.raises(TypeError):
+ check_type_list(case)
diff --git a/test/units/module_utils/common/validation/test_check_type_path.py b/test/units/module_utils/common/validation/test_check_type_path.py
new file mode 100644
index 0000000..d6ff433
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_path.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 re
+
+import os
+from ansible.module_utils.common.validation import check_type_path
+
+
+def mock_expand(value):
+ return re.sub(r'~|\$HOME', '/home/testuser', value)
+
+
+def test_check_type_path(monkeypatch):
+ monkeypatch.setattr(os.path, 'expandvars', mock_expand)
+ monkeypatch.setattr(os.path, 'expanduser', mock_expand)
+ test_cases = (
+ ('~/foo', '/home/testuser/foo'),
+ ('$HOME/foo', '/home/testuser/foo'),
+ ('/home/jane', '/home/jane'),
+ (u'/home/jané', u'/home/jané'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_path(case[0])
diff --git a/test/units/module_utils/common/validation/test_check_type_raw.py b/test/units/module_utils/common/validation/test_check_type_raw.py
new file mode 100644
index 0000000..988e554
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_raw.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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
+
+
+from ansible.module_utils.common.validation import check_type_raw
+
+
+def test_check_type_raw():
+ test_cases = (
+ (1, 1),
+ ('1', '1'),
+ ('a', 'a'),
+ ({'k1': 'v1'}, {'k1': 'v1'}),
+ ([1, 2], [1, 2]),
+ (b'42', b'42'),
+ (u'42', u'42'),
+ )
+ for case in test_cases:
+ assert case[1] == check_type_raw(case[0])
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
new file mode 100644
index 0000000..f10dad2
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_check_type_str.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.common.validation import check_type_str
+
+
+TEST_CASES = (
+ ('string', 'string'),
+ (100, '100'),
+ (1.5, '1.5'),
+ ({'k1': 'v1'}, "{'k1': 'v1'}"),
+ ([1, 2, 'three'], "[1, 2, 'three']"),
+ ((1, 2,), '(1, 2)'),
+)
+
+
+@pytest.mark.parametrize('value, expected', TEST_CASES)
+def test_check_type_str(value, expected):
+ assert expected == check_type_str(value)
+
+
+@pytest.mark.parametrize('value, expected', TEST_CASES[1:])
+def test_check_type_str_no_conversion(value, expected):
+ with pytest.raises(TypeError) as e:
+ check_type_str(value, allow_conversion=False)
+ assert 'is not a string and conversion is not allowed' in to_native(e.value)
diff --git a/test/units/module_utils/common/validation/test_count_terms.py b/test/units/module_utils/common/validation/test_count_terms.py
new file mode 100644
index 0000000..f41dc40
--- /dev/null
+++ b/test/units/module_utils/common/validation/test_count_terms.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.common.validation import count_terms
+
+
+@pytest.fixture
+def params():
+ return {
+ 'name': 'bob',
+ 'dest': '/etc/hosts',
+ 'state': 'present',
+ 'value': 5,
+ }
+
+
+def test_count_terms(params):
+ check = set(('name', 'dest'))
+ assert count_terms(check, params) == 2
+
+
+def test_count_terms_str_input(params):
+ check = 'name'
+ assert count_terms(check, params) == 1
+
+
+def test_count_terms_tuple_input(params):
+ check = ('name', 'dest')
+ assert count_terms(check, params) == 2
+
+
+def test_count_terms_list_input(params):
+ check = ['name', 'dest']
+ assert count_terms(check, params) == 2
diff --git a/test/units/module_utils/common/warnings/test_deprecate.py b/test/units/module_utils/common/warnings/test_deprecate.py
new file mode 100644
index 0000000..08c1b35
--- /dev/null
+++ b/test/units/module_utils/common/warnings/test_deprecate.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+# (c) 2019 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 pytest
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.warnings import deprecate, get_deprecation_messages
+from ansible.module_utils.six import PY3
+
+
+@pytest.fixture
+def deprecation_messages():
+ return [
+ {'msg': 'First deprecation', 'version': None, 'collection_name': None},
+ {'msg': 'Second deprecation', 'version': None, 'collection_name': 'ansible.builtin'},
+ {'msg': 'Third deprecation', 'version': '2.14', 'collection_name': None},
+ {'msg': 'Fourth deprecation', 'version': '2.9', 'collection_name': None},
+ {'msg': 'Fifth deprecation', 'version': '2.9', 'collection_name': 'ansible.builtin'},
+ {'msg': 'Sixth deprecation', 'date': '2199-12-31', 'collection_name': None},
+ {'msg': 'Seventh deprecation', 'date': '2199-12-31', 'collection_name': 'ansible.builtin'},
+ ]
+
+
+@pytest.fixture
+def reset(monkeypatch):
+ monkeypatch.setattr(warnings, '_global_deprecations', [])
+
+
+def test_deprecate_message_only(reset):
+ deprecate('Deprecation message')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': None, 'collection_name': None}]
+
+
+def test_deprecate_with_collection(reset):
+ deprecate(msg='Deprecation message', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': None, 'collection_name': 'ansible.builtin'}]
+
+
+def test_deprecate_with_version(reset):
+ deprecate(msg='Deprecation message', version='2.14')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': None}]
+
+
+def test_deprecate_with_version_and_collection(reset):
+ deprecate(msg='Deprecation message', version='2.14', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'version': '2.14', 'collection_name': 'ansible.builtin'}]
+
+
+def test_deprecate_with_date(reset):
+ deprecate(msg='Deprecation message', date='2199-12-31')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': None}]
+
+
+def test_deprecate_with_date_and_collection(reset):
+ deprecate(msg='Deprecation message', date='2199-12-31', collection_name='ansible.builtin')
+ assert warnings._global_deprecations == [
+ {'msg': 'Deprecation message', 'date': '2199-12-31', 'collection_name': 'ansible.builtin'}]
+
+
+def test_multiple_deprecations(deprecation_messages, reset):
+ for d in deprecation_messages:
+ deprecate(**d)
+
+ assert deprecation_messages == warnings._global_deprecations
+
+
+def test_get_deprecation_messages(deprecation_messages, reset):
+ for d in deprecation_messages:
+ deprecate(**d)
+
+ accessor_deprecations = get_deprecation_messages()
+ assert isinstance(accessor_deprecations, tuple)
+ assert len(accessor_deprecations) == 7
+
+
+@pytest.mark.parametrize(
+ 'test_case',
+ (
+ 1,
+ True,
+ [1],
+ {'k1': 'v1'},
+ (1, 2),
+ 6.62607004,
+ b'bytestr' if PY3 else None,
+ None,
+ )
+)
+def test_deprecate_failure(test_case):
+ with pytest.raises(TypeError, match='deprecate requires a string not a %s' % type(test_case)):
+ deprecate(test_case)
diff --git a/test/units/module_utils/common/warnings/test_warn.py b/test/units/module_utils/common/warnings/test_warn.py
new file mode 100644
index 0000000..41e1a7b
--- /dev/null
+++ b/test/units/module_utils/common/warnings/test_warn.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# (c) 2019 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 pytest
+
+from ansible.module_utils.common import warnings
+
+from ansible.module_utils.common.warnings import warn, get_warning_messages
+from ansible.module_utils.six import PY3
+
+
+@pytest.fixture
+def warning_messages():
+ return [
+ 'First warning',
+ 'Second warning',
+ 'Third warning',
+ ]
+
+
+def test_warn():
+ warn('Warning message')
+ assert warnings._global_warnings == ['Warning message']
+
+
+def test_multiple_warningss(warning_messages):
+ for w in warning_messages:
+ warn(w)
+
+ assert warning_messages == warnings._global_warnings
+
+
+def test_get_warning_messages(warning_messages):
+ for w in warning_messages:
+ warn(w)
+
+ accessor_warnings = get_warning_messages()
+ assert isinstance(accessor_warnings, tuple)
+ assert len(accessor_warnings) == 3
+
+
+@pytest.mark.parametrize(
+ 'test_case',
+ (
+ 1,
+ True,
+ [1],
+ {'k1': 'v1'},
+ (1, 2),
+ 6.62607004,
+ b'bytestr' if PY3 else None,
+ None,
+ )
+)
+def test_warn_failure(test_case):
+ with pytest.raises(TypeError, match='warn requires a string not a %s' % type(test_case)):
+ warn(test_case)
diff --git a/test/units/module_utils/conftest.py b/test/units/module_utils/conftest.py
new file mode 100644
index 0000000..8bc13c4
--- /dev/null
+++ b/test/units/module_utils/conftest.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2017 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 json
+import sys
+from io import BytesIO
+
+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
+
+
+@pytest.fixture
+def stdin(mocker, request):
+ old_args = ansible.module_utils.basic._ANSIBLE_ARGS
+ ansible.module_utils.basic._ANSIBLE_ARGS = None
+ old_argv = sys.argv
+ sys.argv = ['ansible_unittest']
+
+ 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 stdin pytest fixture')
+
+ fake_stdin = BytesIO(to_bytes(args, errors='surrogate_or_strict'))
+ if PY3:
+ mocker.patch('ansible.module_utils.basic.sys.stdin', mocker.MagicMock())
+ mocker.patch('ansible.module_utils.basic.sys.stdin.buffer', fake_stdin)
+ else:
+ mocker.patch('ansible.module_utils.basic.sys.stdin', fake_stdin)
+
+ yield fake_stdin
+
+ ansible.module_utils.basic._ANSIBLE_ARGS = old_args
+ sys.argv = old_argv
+
+
+@pytest.fixture
+def am(stdin, request):
+ old_args = ansible.module_utils.basic._ANSIBLE_ARGS
+ ansible.module_utils.basic._ANSIBLE_ARGS = None
+ old_argv = sys.argv
+ sys.argv = ['ansible_unittest']
+
+ argspec = {}
+ if hasattr(request, 'param'):
+ if isinstance(request.param, dict):
+ argspec = request.param
+
+ am = ansible.module_utils.basic.AnsibleModule(
+ argument_spec=argspec,
+ )
+ am._name = 'ansible_unittest'
+
+ yield am
+
+ ansible.module_utils.basic._ANSIBLE_ARGS = old_args
+ sys.argv = old_argv
diff --git a/test/units/module_utils/facts/__init__.py b/test/units/module_utils/facts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/__init__.py
diff --git a/test/units/module_utils/facts/base.py b/test/units/module_utils/facts/base.py
new file mode 100644
index 0000000..33d3087
--- /dev/null
+++ b/test/units/module_utils/facts/base.py
@@ -0,0 +1,65 @@
+# base unit test classes for ansible/module_utils/facts/ tests
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat import unittest
+from units.compat.mock import Mock, patch
+
+
+class BaseFactsTest(unittest.TestCase):
+ # just a base class, not an actual test
+ __test__ = False
+
+ gather_subset = ['all']
+ valid_subsets = None
+ fact_namespace = None
+ collector_class = None
+
+ # a dict ansible_facts. Some fact collectors depend on facts gathered by
+ # other collectors (like 'ansible_architecture' or 'ansible_system') which
+ # can be passed via the collected_facts arg to collect()
+ collected_facts = None
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 5,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value=None)
+ return mock_module
+
+ @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):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ return facts_dict
+
+ @patch('platform.system', return_value='Linux')
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value='systemd')
+ def test_collect_with_namespace(self, mock_gfc, mock_ps):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ 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/fixtures/cpuinfo/aarch64-4cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/aarch64-4cpu-cpuinfo
new file mode 100644
index 0000000..c3caa01
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/aarch64-4cpu-cpuinfo
@@ -0,0 +1,40 @@
+processor : 0
+Processor : AArch64 Processor rev 4 (aarch64)
+Hardware : sun50iw1p1
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 1
+Processor : AArch64 Processor rev 4 (aarch64)
+Hardware : sun50iw1p1
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 2
+Processor : AArch64 Processor rev 4 (aarch64)
+Hardware : sun50iw1p1
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 3
+Processor : AArch64 Processor rev 4 (aarch64)
+Hardware : sun50iw1p1
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/arm64-4cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/arm64-4cpu-cpuinfo
new file mode 100644
index 0000000..38fd06e
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/arm64-4cpu-cpuinfo
@@ -0,0 +1,32 @@
+processor : 0
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 1
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 2
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 3
+BogoMIPS : 48.00
+Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid
+CPU implementer : 0x41
+CPU architecture: 8
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo
new file mode 100644
index 0000000..84ee16c
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/armv6-rev7-1cpu-cpuinfo
@@ -0,0 +1,12 @@
+processor : 0
+model name : ARMv6-compatible processor rev 7 (v6l)
+BogoMIPS : 697.95
+Features : half thumb fastmult vfp edsp java tls
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xb76
+CPU revision : 7
+Hardware : BCM2835
+Revision : 0010
+Serial : 000000004a0abca9
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo
new file mode 100644
index 0000000..d4b4d3b
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev3-8cpu-cpuinfo
@@ -0,0 +1,75 @@
+processor : 0
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 12.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xc07
+CPU revision : 3
+processor : 1
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 12.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xc07
+CPU revision : 3
+processor : 2
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 12.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xc07
+CPU revision : 3
+processor : 3
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 12.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xc07
+CPU revision : 3
+processor : 4
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 120.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x2
+CPU part : 0xc0f
+CPU revision : 3
+processor : 5
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 120.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x2
+CPU part : 0xc0f
+CPU revision : 3
+processor : 6
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 120.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x2
+CPU part : 0xc0f
+CPU revision : 3
+processor : 7
+model name : ARMv7 Processor rev 3 (v7l)
+BogoMIPS : 120.00
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x2
+CPU part : 0xc0f
+CPU revision : 3
+Hardware : ODROID-XU4
+Revision : 0100
+Serial : 0000000000000000
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo
new file mode 100644
index 0000000..f36075c
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/armv7-rev4-4cpu-cpuinfo
@@ -0,0 +1,39 @@
+processor : 0
+model name : ARMv7 Processor rev 4 (v7l)
+BogoMIPS : 38.40
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 1
+model name : ARMv7 Processor rev 4 (v7l)
+BogoMIPS : 38.40
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 2
+model name : ARMv7 Processor rev 4 (v7l)
+BogoMIPS : 38.40
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+processor : 3
+model name : ARMv7 Processor rev 4 (v7l)
+BogoMIPS : 38.40
+Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
+CPU implementer : 0x41
+CPU architecture: 7
+CPU variant : 0x0
+CPU part : 0xd03
+CPU revision : 4
+Hardware : BCM2835
+Revision : a02082
+Serial : 000000007881bb80
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo
new file mode 100644
index 0000000..1309c58
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo
@@ -0,0 +1,44 @@
+processor : 0
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 1
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 2
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 3
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 4
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 5
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 6
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+processor : 7
+cpu : POWER7 (architected), altivec supported
+clock : 3550.000000MHz
+revision : 2.1 (pvr 003f 0201)
+
+timebase : 512000000
+platform : pSeries
+model : IBM,8231-E2B
+machine : CHRP IBM,8231-E2B \ No newline at end of file
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo
new file mode 100644
index 0000000..4cbd5ac
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo
@@ -0,0 +1,125 @@
+processor : 0
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 1
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 2
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 3
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 4
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 5
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 6
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 7
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 8
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 9
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 10
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 11
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 12
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 13
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 14
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 15
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 16
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 17
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 18
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 19
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 20
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 21
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 22
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+processor : 23
+cpu : POWER8 (architected), altivec supported
+clock : 3425.000000MHz
+revision : 2.1 (pvr 004b 0201)
+
+timebase : 512000000
+platform : pSeries
+model : IBM,8247-21L
+machine : CHRP IBM,8247-21L
+
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu b/test/units/module_utils/facts/fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu
new file mode 100644
index 0000000..8c29faa
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu
@@ -0,0 +1,61 @@
+cpu : UltraSparc T5 (Niagara5)
+fpu : UltraSparc T5 integrated FPU
+pmu : niagara5
+prom : OBP 4.38.12 2018/03/28 14:54
+type : sun4v
+ncpus probed : 24
+ncpus active : 24
+D$ parity tl1 : 0
+I$ parity tl1 : 0
+cpucaps : flush,stbar,swap,muldiv,v9,blkinit,n2,mul32,div32,v8plus,popc,vis,vis2,ASIBlkInit,fmaf,vis3,hpc,ima,pause,cbcond,aes,des,kasumi,camellia,md5,sha1,sha256,sha512,mpmul,montmul,montsqr,crc32c
+Cpu0ClkTck : 00000000d6924470
+Cpu1ClkTck : 00000000d6924470
+Cpu2ClkTck : 00000000d6924470
+Cpu3ClkTck : 00000000d6924470
+Cpu4ClkTck : 00000000d6924470
+Cpu5ClkTck : 00000000d6924470
+Cpu6ClkTck : 00000000d6924470
+Cpu7ClkTck : 00000000d6924470
+Cpu8ClkTck : 00000000d6924470
+Cpu9ClkTck : 00000000d6924470
+Cpu10ClkTck : 00000000d6924470
+Cpu11ClkTck : 00000000d6924470
+Cpu12ClkTck : 00000000d6924470
+Cpu13ClkTck : 00000000d6924470
+Cpu14ClkTck : 00000000d6924470
+Cpu15ClkTck : 00000000d6924470
+Cpu16ClkTck : 00000000d6924470
+Cpu17ClkTck : 00000000d6924470
+Cpu18ClkTck : 00000000d6924470
+Cpu19ClkTck : 00000000d6924470
+Cpu20ClkTck : 00000000d6924470
+Cpu21ClkTck : 00000000d6924470
+Cpu22ClkTck : 00000000d6924470
+Cpu23ClkTck : 00000000d6924470
+MMU Type : Hypervisor (sun4v)
+MMU PGSZs : 8K,64K,4MB,256MB
+State:
+CPU0: online
+CPU1: online
+CPU2: online
+CPU3: online
+CPU4: online
+CPU5: online
+CPU6: online
+CPU7: online
+CPU8: online
+CPU9: online
+CPU10: online
+CPU11: online
+CPU12: online
+CPU13: online
+CPU14: online
+CPU15: online
+CPU16: online
+CPU17: online
+CPU18: online
+CPU19: online
+CPU20: online
+CPU21: online
+CPU22: online
+CPU23: online
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-2cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-2cpu-cpuinfo
new file mode 100644
index 0000000..1d233f8
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-2cpu-cpuinfo
@@ -0,0 +1,56 @@
+processor : 0
+vendor_id : GenuineIntel
+cpu family : 6
+model : 62
+model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz
+stepping : 4
+microcode : 0x1
+cpu MHz : 2799.998
+cache size : 16384 KB
+physical id : 0
+siblings : 1
+core id : 0
+cpu cores : 1
+apicid : 0
+initial apicid : 0
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp l'
+m constant_tsc arch_perfmon rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadlin'
+e_timer aes xsave avx f16c rdrand hypervisor lahf_lm pti fsgsbase tsc_adjust smep erms xsaveopt arat
+bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf
+bogomips : 5602.32
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management:
+processor : 1
+vendor_id : GenuineIntel
+cpu family : 6
+model : 62
+model name : Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz
+stepping : 4
+microcode : 0x1
+cpu MHz : 2799.998
+cache size : 16384 KB
+physical id : 1
+siblings : 1
+core id : 0
+cpu cores : 1
+apicid : 1
+initial apicid : 1
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp l'
+m constant_tsc arch_perfmon rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadlin'
+e_timer aes xsave avx f16c rdrand hypervisor lahf_lm pti fsgsbase tsc_adjust smep erms xsaveopt arat
+bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf
+bogomips : 5602.32
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management:
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-4cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-4cpu-cpuinfo
new file mode 100644
index 0000000..fcc396d
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-4cpu-cpuinfo
@@ -0,0 +1,104 @@
+processor : 0
+vendor_id : AuthenticAMD
+cpu family : 15
+model : 65
+model name : Dual-Core AMD Opteron(tm) Processor 2216
+stepping : 2
+cpu MHz : 1000.000
+cache size : 1024 KB
+physical id : 0
+siblings : 2
+core id : 0
+cpu cores : 2
+apicid : 0
+initial apicid : 0
+fpu : yes
+fpu_exception : yes
+cpuid level : 1
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt '
+rdtscp lm 3dnowext 3dnow art rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy retpoline_amd vmmcall
+bogomips : 1994.60
+TLB size : 1024 4K pages
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management: ts fid vid ttp tm stc
+processor : 1
+vendor_id : AuthenticAMD
+cpu family : 15
+model : 65
+model name : Dual-Core AMD Opteron(tm) Processor 2216
+stepping : 2
+cpu MHz : 1000.000
+cache size : 1024 KB
+physical id : 0
+siblings : 2
+core id : 1
+cpu cores : 2
+apicid : 1
+initial apicid : 1
+fpu : yes
+fpu_exception : yes
+cpuid level : 1
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt '
+rdtscp lm 3dnowext 3dnow art rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy retpoline_amd vmmcall
+bogomips : 1994.60
+TLB size : 1024 4K pages
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management: ts fid vid ttp tm stc
+processor : 2
+vendor_id : AuthenticAMD
+cpu family : 15
+model : 65
+model name : Dual-Core AMD Opteron(tm) Processor 2216
+stepping : 2
+cpu MHz : 1000.000
+cache size : 1024 KB
+physical id : 1
+siblings : 2
+core id : 0
+cpu cores : 2
+apicid : 2
+initial apicid : 2
+fpu : yes
+fpu_exception : yes
+cpuid level : 1
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt '
+rdtscp lm 3dnowext 3dnow art rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy retpoline_amd vmmcall
+bogomips : 1994.60
+TLB size : 1024 4K pages
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management: ts fid vid ttp tm stc
+processor : 3
+vendor_id : AuthenticAMD
+cpu family : 15
+model : 65
+model name : Dual-Core AMD Opteron(tm) Processor 2216
+stepping : 2
+cpu MHz : 1000.000
+cache size : 1024 KB
+physical id : 1
+siblings : 2
+core id : 1
+cpu cores : 2
+apicid : 3
+initial apicid : 3
+fpu : yes
+fpu_exception : yes
+cpuid level : 1
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt '
+rdtscp lm 3dnowext 3dnow art rep_good nopl extd_apicid pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy retpoline_amd vmmcall
+bogomips : 1994.60
+TLB size : 1024 4K pages
+clflush size : 64
+cache_alignment : 64
+address sizes : 40 bits physical, 48 bits virtual
+power management: ts fid vid ttp tm stc
diff --git a/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-8cpu-cpuinfo b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-8cpu-cpuinfo
new file mode 100644
index 0000000..63abea2
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/cpuinfo/x86_64-8cpu-cpuinfo
@@ -0,0 +1,216 @@
+processor : 0
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 2703.625
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 0
+cpu cores : 4
+apicid : 0
+initial apicid : 0
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5388.06
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 1
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 3398.565
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 0
+cpu cores : 4
+apicid : 1
+initial apicid : 1
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5393.53
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 2
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 3390.325
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 1
+cpu cores : 4
+apicid : 2
+initial apicid : 2
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5391.63
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 3
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 3262.774
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 1
+cpu cores : 4
+apicid : 3
+initial apicid : 3
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5392.08
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 4
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 2905.169
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 2
+cpu cores : 4
+apicid : 4
+initial apicid : 4
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5391.97
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 5
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 1834.826
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 2
+cpu cores : 4
+apicid : 5
+initial apicid : 5
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5392.11
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 6
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 2781.573
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 3
+cpu cores : 4
+apicid : 6
+initial apicid : 6
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5391.98
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
+processor : 7
+vendor_id : GenuineIntel
+cpu family : 6
+model : 60
+model name : Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz
+stepping : 3
+microcode : 0x20
+cpu MHz : 3593.353
+cache size : 6144 KB
+physical id : 0
+siblings : 8
+core id : 3
+cpu cores : 4
+apicid : 7
+initial apicid : 7
+fpu : yes
+fpu_exception : yes
+cpuid level : 13
+wp : yes
+flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts
+bugs :
+bogomips : 5392.07
+clflush size : 64
+cache_alignment : 64
+address sizes : 39 bits physical, 48 bits virtual
+power management:
+
diff --git a/test/units/module_utils/facts/fixtures/distribution_files/ClearLinux b/test/units/module_utils/facts/fixtures/distribution_files/ClearLinux
new file mode 100644
index 0000000..a5442de
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/distribution_files/ClearLinux
@@ -0,0 +1,10 @@
+NAME="Clear Linux OS"
+VERSION=1
+ID=clear-linux-os
+ID_LIKE=clear-linux-os
+VERSION_ID=28120
+PRETTY_NAME="Clear Linux OS"
+ANSI_COLOR="1;35"
+HOME_URL="https://clearlinux.org"
+SUPPORT_URL="https://clearlinux.org"
+BUG_REPORT_URL="mailto:dev@lists.clearlinux.org"',
diff --git a/test/units/module_utils/facts/fixtures/distribution_files/CoreOS b/test/units/module_utils/facts/fixtures/distribution_files/CoreOS
new file mode 100644
index 0000000..806ce30
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/distribution_files/CoreOS
@@ -0,0 +1,10 @@
+NAME="Container Linux by CoreOS"
+ID=coreos
+VERSION=1911.5.0
+VERSION_ID=1911.5.0
+BUILD_ID=2018-12-15-2317
+PRETTY_NAME="Container Linux by CoreOS 1911.5.0 (Rhyolite)"
+ANSI_COLOR="38;5;75"
+HOME_URL="https://coreos.com/"
+BUG_REPORT_URL="https://issues.coreos.com"
+COREOS_BOARD="amd64-usr"
diff --git a/test/units/module_utils/facts/fixtures/distribution_files/LinuxMint b/test/units/module_utils/facts/fixtures/distribution_files/LinuxMint
new file mode 100644
index 0000000..850f6b7
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/distribution_files/LinuxMint
@@ -0,0 +1,12 @@
+NAME="Linux Mint"
+VERSION="19.1 (Tessa)"
+ID=linuxmint
+ID_LIKE=ubuntu
+PRETTY_NAME="Linux Mint 19.1"
+VERSION_ID="19.1"
+HOME_URL="https://www.linuxmint.com/"
+SUPPORT_URL="https://forums.ubuntu.com/"
+BUG_REPORT_URL="http://linuxmint-troubleshooting-guide.readthedocs.io/en/latest/"
+PRIVACY_POLICY_URL="https://www.linuxmint.com/"
+VERSION_CODENAME=tessa
+UBUNTU_CODENAME=bionic
diff --git a/test/units/module_utils/facts/fixtures/distribution_files/Slackware b/test/units/module_utils/facts/fixtures/distribution_files/Slackware
new file mode 100644
index 0000000..1147d29
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/distribution_files/Slackware
@@ -0,0 +1 @@
+Slackware 14.1
diff --git a/test/units/module_utils/facts/fixtures/distribution_files/SlackwareCurrent b/test/units/module_utils/facts/fixtures/distribution_files/SlackwareCurrent
new file mode 100644
index 0000000..62c046c
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/distribution_files/SlackwareCurrent
@@ -0,0 +1 @@
+Slackware 14.2+
diff --git a/test/units/module_utils/facts/fixtures/findmount_output.txt b/test/units/module_utils/facts/fixtures/findmount_output.txt
new file mode 100644
index 0000000..299a262
--- /dev/null
+++ b/test/units/module_utils/facts/fixtures/findmount_output.txt
@@ -0,0 +1,40 @@
+/sys sysfs sysfs rw,nosuid,nodev,noexec,relatime,seclabel
+/proc proc proc rw,nosuid,nodev,noexec,relatime
+/dev devtmpfs devtmpfs rw,nosuid,seclabel,size=8044400k,nr_inodes=2011100,mode=755
+/sys/kernel/security securityfs securityfs rw,nosuid,nodev,noexec,relatime
+/dev/shm tmpfs tmpfs rw,nosuid,nodev,seclabel
+/dev/pts devpts devpts rw,nosuid,noexec,relatime,seclabel,gid=5,mode=620,ptmxmode=000
+/run tmpfs tmpfs rw,nosuid,nodev,seclabel,mode=755
+/sys/fs/cgroup tmpfs tmpfs ro,nosuid,nodev,noexec,seclabel,mode=755
+/sys/fs/cgroup/systemd cgroup cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,na
+/sys/fs/pstore pstore pstore rw,nosuid,nodev,noexec,relatime,seclabel
+/sys/fs/cgroup/devices cgroup cgroup rw,nosuid,nodev,noexec,relatime,devices
+/sys/fs/cgroup/freezer cgroup cgroup rw,nosuid,nodev,noexec,relatime,freezer
+/sys/fs/cgroup/memory cgroup cgroup rw,nosuid,nodev,noexec,relatime,memory
+/sys/fs/cgroup/pids cgroup cgroup rw,nosuid,nodev,noexec,relatime,pids
+/sys/fs/cgroup/blkio cgroup cgroup rw,nosuid,nodev,noexec,relatime,blkio
+/sys/fs/cgroup/cpuset cgroup cgroup rw,nosuid,nodev,noexec,relatime,cpuset
+/sys/fs/cgroup/cpu,cpuacct cgroup cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct
+/sys/fs/cgroup/hugetlb cgroup cgroup rw,nosuid,nodev,noexec,relatime,hugetlb
+/sys/fs/cgroup/perf_event cgroup cgroup rw,nosuid,nodev,noexec,relatime,perf_event
+/sys/fs/cgroup/net_cls,net_prio cgroup cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio
+/sys/kernel/config configfs configfs rw,relatime
+/ /dev/mapper/fedora_dhcp129--186-root ext4 rw,relatime,seclabel,data=ordered
+/sys/fs/selinux selinuxfs selinuxfs rw,relatime
+/proc/sys/fs/binfmt_misc systemd-1 autofs rw,relatime,fd=24,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+/sys/kernel/debug debugfs debugfs rw,relatime,seclabel
+/dev/hugepages hugetlbfs hugetlbfs rw,relatime,seclabel
+/tmp tmpfs tmpfs rw,seclabel
+/dev/mqueue mqueue mqueue rw,relatime,seclabel
+/var/lib/machines /dev/loop0 btrfs rw,relatime,seclabel,space_cache,subvolid=5,subvol=/
+/boot /dev/sda1 ext4 rw,relatime,seclabel,data=ordered
+/home /dev/mapper/fedora_dhcp129--186-home ext4 rw,relatime,seclabel,data=ordered
+/run/user/1000 tmpfs tmpfs rw,nosuid,nodev,relatime,seclabel,size=1611044k,mode=700,uid=1000,gid=1000
+/run/user/1000/gvfs gvfsd-fuse fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000
+/sys/fs/fuse/connections fusectl fusectl rw,relatime
+/not/a/real/bind_mount /dev/sdz4[/some/other/path] ext4 rw,relatime,seclabel,data=ordered
+/home/adrian/sshfs-grimlock grimlock.g.a: fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000
+/home/adrian/sshfs-grimlock-single-quote grimlock.g.a:test_path/path_with'single_quotes
+ fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000
+/home/adrian/sshfs-grimlock-single-quote-2 grimlock.g.a:path_with'single_quotes fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000
+/home/adrian/fotos grimlock.g.a:/mnt/data/foto's fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000
diff --git a/test/units/module_utils/facts/hardware/__init__.py b/test/units/module_utils/facts/hardware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/__init__.py
diff --git a/test/units/module_utils/facts/hardware/aix_data.py b/test/units/module_utils/facts/hardware/aix_data.py
new file mode 100644
index 0000000..d1a6c9a
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/aix_data.py
@@ -0,0 +1,75 @@
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+AIX_PROCESSOR_TEST_SCENARIOS = [
+ {
+ 'comment': 'AIX 7.2 (gcc119 on GCC farm)',
+ 'lsdev_output': [
+ 'proc0 Available 00-00 Processor',
+ 'proc8 Available 00-08 Processor',
+ 'proc16 Available 00-16 Processor',
+ 'proc24 Available 00-24 Processor',
+ 'proc32 Available 00-32 Processor',
+ 'proc40 Available 00-40 Processor',
+ 'proc48 Available 00-48 Processor',
+ 'proc56 Available 00-56 Processor',
+ 'proc64 Available 00-64 Processor',
+ 'proc72 Available 00-72 Processor',
+ ],
+ 'lsattr_type_output': ['type PowerPC_POWER8 Processor type False'],
+ 'lsattr_smt_threads_output': [
+ 'smt_threads 8 Processor SMT threads False'
+ ],
+ 'expected_result': {
+ 'processor': ['PowerPC_POWER8'],
+ 'processor_count': 1,
+ 'processor_cores': 10,
+ 'processor_threads_per_core': 8,
+ 'processor_vcpus': 80
+ },
+ },
+ {
+ 'comment': 'AIX 7.1 (gcc111 on GCC farm)',
+ 'lsdev_output': [
+ 'proc0 Available 00-00 Processor',
+ 'proc4 Available 00-04 Processor',
+ 'proc8 Available 00-08 Processor',
+ 'proc12 Available 00-12 Processor',
+ 'proc16 Available 00-16 Processor',
+ 'proc20 Available 00-20 Processor',
+ 'proc24 Available 00-24 Processor',
+ 'proc28 Available 00-28 Processor',
+ 'proc32 Available 00-32 Processor',
+ 'proc36 Available 00-36 Processor',
+ 'proc40 Available 00-40 Processor',
+ 'proc44 Available 00-44 Processor',
+ ],
+ 'lsattr_type_output': ['type PowerPC_POWER7 Processor type False'],
+ 'lsattr_smt_threads_output': [
+ 'smt_threads 4 Processor SMT threads False'
+ ],
+ 'expected_result': {
+ 'processor': ['PowerPC_POWER7'],
+ 'processor_count': 1,
+ 'processor_cores': 12,
+ 'processor_threads_per_core': 4,
+ 'processor_vcpus': 48
+ },
+ },
+]
diff --git a/test/units/module_utils/facts/hardware/linux_data.py b/test/units/module_utils/facts/hardware/linux_data.py
new file mode 100644
index 0000000..3879188
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/linux_data.py
@@ -0,0 +1,633 @@
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+
+LSBLK_OUTPUT = b"""
+/dev/sda
+/dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0
+/dev/sda2 66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK
+/dev/mapper/fedora_dhcp129--186-swap eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d
+/dev/mapper/fedora_dhcp129--186-root d34cf5e3-3449-4a6c-8179-a1feb2bca6ce
+/dev/mapper/fedora_dhcp129--186-home 2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d
+/dev/sr0
+/dev/loop0 0f031512-ab15-497d-9abd-3a512b4a9390
+/dev/loop1 7c1b0f30-cf34-459f-9a70-2612f82b870a
+/dev/loop9 0f031512-ab15-497d-9abd-3a512b4a9390
+/dev/loop9 7c1b4444-cf34-459f-9a70-2612f82b870a
+/dev/mapper/docker-253:1-1050967-pool
+/dev/loop2
+/dev/mapper/docker-253:1-1050967-pool
+"""
+
+LSBLK_OUTPUT_2 = b"""
+/dev/sda
+/dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0
+/dev/sda2 66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK
+/dev/mapper/fedora_dhcp129--186-swap eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d
+/dev/mapper/fedora_dhcp129--186-root d34cf5e3-3449-4a6c-8179-a1feb2bca6ce
+/dev/mapper/fedora_dhcp129--186-home 2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d
+/dev/mapper/an-example-mapper with a space in the name 84639acb-013f-4d2f-9392-526a572b4373
+/dev/sr0
+/dev/loop0 0f031512-ab15-497d-9abd-3a512b4a9390
+"""
+
+LSBLK_UUIDS = {'/dev/sda1': '66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK'}
+
+UDEVADM_UUID = 'N/A'
+
+UDEVADM_OUTPUT = """
+UDEV_LOG=3
+DEVPATH=/devices/pci0000:00/0000:00:07.0/virtio2/block/vda/vda1
+MAJOR=252
+MINOR=1
+DEVNAME=/dev/vda1
+DEVTYPE=partition
+SUBSYSTEM=block
+MPATH_SBIN_PATH=/sbin
+ID_PATH=pci-0000:00:07.0-virtio-pci-virtio2
+ID_PART_TABLE_TYPE=dos
+ID_FS_UUID=57b1a3e7-9019-4747-9809-7ec52bba9179
+ID_FS_UUID_ENC=57b1a3e7-9019-4747-9809-7ec52bba9179
+ID_FS_VERSION=1.0
+ID_FS_TYPE=ext4
+ID_FS_USAGE=filesystem
+LVM_SBIN_PATH=/sbin
+DEVLINKS=/dev/block/252:1 /dev/disk/by-path/pci-0000:00:07.0-virtio-pci-virtio2-part1 /dev/disk/by-uuid/57b1a3e7-9019-4747-9809-7ec52bba9179
+"""
+
+MTAB = """
+sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
+proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=8044400k,nr_inodes=2011100,mode=755 0 0
+securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
+tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0
+devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
+tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0
+tmpfs /sys/fs/cgroup tmpfs ro,seclabel,nosuid,nodev,noexec,mode=755 0 0
+cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
+pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0
+cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
+cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
+cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
+cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
+cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
+cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
+cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
+cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
+cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
+cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
+configfs /sys/kernel/config configfs rw,relatime 0 0
+/dev/mapper/fedora_dhcp129--186-root / ext4 rw,seclabel,relatime,data=ordered 0 0
+selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
+systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=24,pgrp=1,timeout=0,minproto=5,maxproto=5,direct 0 0
+debugfs /sys/kernel/debug debugfs rw,seclabel,relatime 0 0
+hugetlbfs /dev/hugepages hugetlbfs rw,seclabel,relatime 0 0
+tmpfs /tmp tmpfs rw,seclabel 0 0
+mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0
+/dev/loop0 /var/lib/machines btrfs rw,seclabel,relatime,space_cache,subvolid=5,subvol=/ 0 0
+/dev/sda1 /boot ext4 rw,seclabel,relatime,data=ordered 0 0
+/dev/mapper/fedora_dhcp129--186-home /home ext4 rw,seclabel,relatime,data=ordered 0 0
+tmpfs /run/user/1000 tmpfs rw,seclabel,nosuid,nodev,relatime,size=1611044k,mode=700,uid=1000,gid=1000 0 0
+gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0
+grimlock.g.a: /home/adrian/sshfs-grimlock fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:test_path/path_with'single_quotes /home/adrian/sshfs-grimlock-single-quote fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:path_with'single_quotes /home/adrian/sshfs-grimlock-single-quote-2 fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:/mnt/data/foto's /home/adrian/fotos fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+"""
+
+MTAB_ENTRIES = [
+ [
+ 'sysfs',
+ '/sys',
+ 'sysfs',
+ 'rw,seclabel,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ ['proc', '/proc', 'proc', 'rw,nosuid,nodev,noexec,relatime', '0', '0'],
+ [
+ 'devtmpfs',
+ '/dev',
+ 'devtmpfs',
+ 'rw,seclabel,nosuid,size=8044400k,nr_inodes=2011100,mode=755',
+ '0',
+ '0'
+ ],
+ [
+ 'securityfs',
+ '/sys/kernel/security',
+ 'securityfs',
+ 'rw,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/dev/shm', 'tmpfs', 'rw,seclabel,nosuid,nodev', '0', '0'],
+ [
+ 'devpts',
+ '/dev/pts',
+ 'devpts',
+ 'rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/run', 'tmpfs', 'rw,seclabel,nosuid,nodev,mode=755', '0', '0'],
+ [
+ 'tmpfs',
+ '/sys/fs/cgroup',
+ 'tmpfs',
+ 'ro,seclabel,nosuid,nodev,noexec,mode=755',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/systemd',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd',
+ '0',
+ '0'
+ ],
+ [
+ 'pstore',
+ '/sys/fs/pstore',
+ 'pstore',
+ 'rw,seclabel,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/devices',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,devices',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/freezer',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,freezer',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/memory',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,memory',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/pids',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,pids',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/blkio',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,blkio',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/cpuset',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,cpuset',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/cpu,cpuacct',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,cpu,cpuacct',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/hugetlb',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,hugetlb',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/perf_event',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,perf_event',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/net_cls,net_prio',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,net_cls,net_prio',
+ '0',
+ '0'
+ ],
+ ['configfs', '/sys/kernel/config', 'configfs', 'rw,relatime', '0', '0'],
+ [
+ '/dev/mapper/fedora_dhcp129--186-root',
+ '/',
+ 'ext4',
+ 'rw,seclabel,relatime,data=ordered',
+ '0',
+ '0'
+ ],
+ ['selinuxfs', '/sys/fs/selinux', 'selinuxfs', 'rw,relatime', '0', '0'],
+ [
+ 'systemd-1',
+ '/proc/sys/fs/binfmt_misc',
+ 'autofs',
+ 'rw,relatime,fd=24,pgrp=1,timeout=0,minproto=5,maxproto=5,direct',
+ '0',
+ '0'
+ ],
+ ['debugfs', '/sys/kernel/debug', 'debugfs', 'rw,seclabel,relatime', '0', '0'],
+ [
+ 'hugetlbfs',
+ '/dev/hugepages',
+ 'hugetlbfs',
+ 'rw,seclabel,relatime',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/tmp', 'tmpfs', 'rw,seclabel', '0', '0'],
+ ['mqueue', '/dev/mqueue', 'mqueue', 'rw,seclabel,relatime', '0', '0'],
+ [
+ '/dev/loop0',
+ '/var/lib/machines',
+ 'btrfs',
+ 'rw,seclabel,relatime,space_cache,subvolid=5,subvol=/',
+ '0',
+ '0'
+ ],
+ ['/dev/sda1', '/boot', 'ext4', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ # A 'none' fstype
+ ['/dev/sdz3', '/not/a/real/device', 'none', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ # lets assume this is a bindmount
+ ['/dev/sdz4', '/not/a/real/bind_mount', 'ext4', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ [
+ '/dev/mapper/fedora_dhcp129--186-home',
+ '/home',
+ 'ext4',
+ 'rw,seclabel,relatime,data=ordered',
+ '0',
+ '0'
+ ],
+ [
+ 'tmpfs',
+ '/run/user/1000',
+ 'tmpfs',
+ 'rw,seclabel,nosuid,nodev,relatime,size=1611044k,mode=700,uid=1000,gid=1000',
+ '0',
+ '0'
+ ],
+ [
+ 'gvfsd-fuse',
+ '/run/user/1000/gvfs',
+ 'fuse.gvfsd-fuse',
+ 'rw,nosuid,nodev,relatime,user_id=1000,group_id=1000',
+ '0',
+ '0'
+ ],
+ ['fusectl', '/sys/fs/fuse/connections', 'fusectl', 'rw,relatime', '0', '0']]
+
+STATVFS_INFO = {'/': {'block_available': 10192323,
+ 'block_size': 4096,
+ 'block_total': 12868728,
+ 'block_used': 2676405,
+ 'inode_available': 3061699,
+ 'inode_total': 3276800,
+ 'inode_used': 215101,
+ 'size_available': 41747755008,
+ 'size_total': 52710309888},
+ '/not/a/real/bind_mount': {},
+ '/home': {'block_available': 1001578731,
+ 'block_size': 4096,
+ 'block_total': 105871006,
+ 'block_used': 5713133,
+ 'inode_available': 26860880,
+ 'inode_total': 26902528,
+ 'inode_used': 41648,
+ 'size_available': 410246647808,
+ 'size_total': 433647640576},
+ '/var/lib/machines': {'block_available': 10192316,
+ 'block_size': 4096,
+ 'block_total': 12868728,
+ 'block_used': 2676412,
+ 'inode_available': 3061699,
+ 'inode_total': 3276800,
+ 'inode_used': 215101,
+ 'size_available': 41747726336,
+ 'size_total': 52710309888},
+ '/boot': {'block_available': 187585,
+ 'block_size': 4096,
+ 'block_total': 249830,
+ 'block_used': 62245,
+ 'inode_available': 65096,
+ 'inode_total': 65536,
+ 'inode_used': 440,
+ 'size_available': 768348160,
+ 'size_total': 1023303680}
+ }
+
+# ['/dev/sdz4', '/not/a/real/bind_mount', 'ext4', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+
+BIND_MOUNTS = ['/not/a/real/bind_mount']
+
+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(),
+ 'expected_result': {
+ 'processor': ['0', 'ARMv6-compatible processor rev 7 (v6l)'],
+ 'processor_cores': 1,
+ 'processor_count': 1,
+ 'processor_nproc': 1,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 1},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'ARMv7 Processor rev 4 (v7l)',
+ '1', 'ARMv7 Processor rev 4 (v7l)',
+ '2', 'ARMv7 Processor rev 4 (v7l)',
+ '3', 'ARMv7 Processor rev 4 (v7l)',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 4,
+ 'processor_nproc': 4,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 4},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'AArch64 Processor rev 4 (aarch64)',
+ '1', 'AArch64 Processor rev 4 (aarch64)',
+ '2', 'AArch64 Processor rev 4 (aarch64)',
+ '3', 'AArch64 Processor rev 4 (aarch64)',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 4,
+ 'processor_nproc': 4,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 4},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'AuthenticAMD', 'Dual-Core AMD Opteron(tm) Processor 2216',
+ '1', 'AuthenticAMD', 'Dual-Core AMD Opteron(tm) Processor 2216',
+ '2', 'AuthenticAMD', 'Dual-Core AMD Opteron(tm) Processor 2216',
+ '3', 'AuthenticAMD', 'Dual-Core AMD Opteron(tm) Processor 2216',
+ ],
+ 'processor_cores': 2,
+ 'processor_count': 2,
+ 'processor_nproc': 4,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 4},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '1', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '2', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '3', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '4', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '5', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '6', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ '7', 'GenuineIntel', 'Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz',
+ ],
+ 'processor_cores': 4,
+ 'processor_count': 1,
+ 'processor_nproc': 4,
+ 'processor_threads_per_core': 2,
+ 'processor_vcpus': 8},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': ['0', '1', '2', '3'],
+ 'processor_cores': 1,
+ 'processor_count': 4,
+ 'processor_nproc': 4,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 4},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'ARMv7 Processor rev 3 (v7l)',
+ '1', 'ARMv7 Processor rev 3 (v7l)',
+ '2', 'ARMv7 Processor rev 3 (v7l)',
+ '3', 'ARMv7 Processor rev 3 (v7l)',
+ '4', 'ARMv7 Processor rev 3 (v7l)',
+ '5', 'ARMv7 Processor rev 3 (v7l)',
+ '6', 'ARMv7 Processor rev 3 (v7l)',
+ '7', 'ARMv7 Processor rev 3 (v7l)',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 8,
+ 'processor_nproc': 8,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 8},
+ },
+ {
+ '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(),
+ 'expected_result': {
+ 'processor': [
+ '0', 'GenuineIntel', 'Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz',
+ '1', 'GenuineIntel', 'Intel(R) Xeon(R) CPU E5-2680 v2 @ 2.80GHz',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 2,
+ 'processor_nproc': 2,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 2},
+ },
+ {
+ 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64-power7-rhel7-8cpu-cpuinfo')).readlines(),
+ 'architecture': 'ppc64',
+ 'nproc_out': 8,
+ 'sched_getaffinity': set([0, 1, 2, 3, 4, 5, 6, 7]),
+ 'expected_result': {
+ 'processor': [
+ '0', 'POWER7 (architected), altivec supported',
+ '1', 'POWER7 (architected), altivec supported',
+ '2', 'POWER7 (architected), altivec supported',
+ '3', 'POWER7 (architected), altivec supported',
+ '4', 'POWER7 (architected), altivec supported',
+ '5', 'POWER7 (architected), altivec supported',
+ '6', 'POWER7 (architected), altivec supported',
+ '7', 'POWER7 (architected), altivec supported'
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 8,
+ 'processor_nproc': 8,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 8
+ },
+ },
+ {
+ 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/ppc64le-power8-24cpu-cpuinfo')).readlines(),
+ '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]),
+ 'expected_result': {
+ 'processor': [
+ '0', 'POWER8 (architected), altivec supported',
+ '1', 'POWER8 (architected), altivec supported',
+ '2', 'POWER8 (architected), altivec supported',
+ '3', 'POWER8 (architected), altivec supported',
+ '4', 'POWER8 (architected), altivec supported',
+ '5', 'POWER8 (architected), altivec supported',
+ '6', 'POWER8 (architected), altivec supported',
+ '7', 'POWER8 (architected), altivec supported',
+ '8', 'POWER8 (architected), altivec supported',
+ '9', 'POWER8 (architected), altivec supported',
+ '10', 'POWER8 (architected), altivec supported',
+ '11', 'POWER8 (architected), altivec supported',
+ '12', 'POWER8 (architected), altivec supported',
+ '13', 'POWER8 (architected), altivec supported',
+ '14', 'POWER8 (architected), altivec supported',
+ '15', 'POWER8 (architected), altivec supported',
+ '16', 'POWER8 (architected), altivec supported',
+ '17', 'POWER8 (architected), altivec supported',
+ '18', 'POWER8 (architected), altivec supported',
+ '19', 'POWER8 (architected), altivec supported',
+ '20', 'POWER8 (architected), altivec supported',
+ '21', 'POWER8 (architected), altivec supported',
+ '22', 'POWER8 (architected), altivec supported',
+ '23', 'POWER8 (architected), altivec supported',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 24,
+ 'processor_nproc': 24,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 24
+ },
+ },
+ {
+ 'cpuinfo': open(os.path.join(os.path.dirname(__file__), '../fixtures/cpuinfo/sparc-t5-debian-ldom-24vcpu')).readlines(),
+ '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]),
+ 'expected_result': {
+ 'processor': [
+ 'UltraSparc T5 (Niagara5)',
+ ],
+ 'processor_cores': 1,
+ 'processor_count': 24,
+ 'processor_nproc': 24,
+ 'processor_threads_per_core': 1,
+ 'processor_vcpus': 24
+ },
+ },
+]
+
+SG_INQ_OUTPUTS = ["""
+Identify controller for /dev/nvme0n1:
+ Model number: Amazon Elastic Block Store
+ Serial number: vol0123456789
+ Firmware revision: 1.0
+ Version: 0.0
+ No optional admin command support
+ No optional NVM command support
+ PCI vendor ID VID/SSVID: 0x1d0f/0x1d0f
+ IEEE OUI Identifier: 0xa002dc
+ Controller ID: 0x0
+ Number of namespaces: 1
+ Maximum data transfer size: 64 pages
+ Namespace 1 (deduced from device name):
+ Namespace size/capacity: 62914560/62914560 blocks
+ Namespace utilization: 0 blocks
+ Number of LBA formats: 1
+ Index LBA size: 0
+ LBA format 0 support: <-- active
+ Logical block size: 512 bytes
+ Approximate namespace size: 32 GB
+ Metadata size: 0 bytes
+ Relative performance: Best [0x0]
+""", """
+Identify controller for /dev/nvme0n1:
+ Model number: Amazon Elastic Block Store
+ Unit serial number: vol0123456789
+ Firmware revision: 1.0
+ Version: 0.0
+ No optional admin command support
+ No optional NVM command support
+ PCI vendor ID VID/SSVID: 0x1d0f/0x1d0f
+ IEEE OUI Identifier: 0xa002dc
+ Controller ID: 0x0
+ Number of namespaces: 1
+ Maximum data transfer size: 64 pages
+ Namespace 1 (deduced from device name):
+ Namespace size/capacity: 62914560/62914560 blocks
+ Namespace utilization: 0 blocks
+ Number of LBA formats: 1
+ Index LBA size: 0
+ LBA format 0 support: <-- active
+ Logical block size: 512 bytes
+ Approximate namespace size: 32 GB
+ Metadata size: 0 bytes
+ Relative performance: Best [0x0]
+"""]
diff --git a/test/units/module_utils/facts/hardware/test_aix_processor.py b/test/units/module_utils/facts/hardware/test_aix_processor.py
new file mode 100644
index 0000000..94d9b9e
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/test_aix_processor.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2022 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
+
+from ansible.module_utils.facts.hardware import aix
+import pytest
+
+from . aix_data import AIX_PROCESSOR_TEST_SCENARIOS
+
+
+@pytest.mark.parametrize('scenario', AIX_PROCESSOR_TEST_SCENARIOS)
+def test_get_cpu_info(mocker, scenario):
+ commands_results = [
+ (0, "\n".join(scenario['lsdev_output']), ''),
+ (0, "\n".join(scenario['lsattr_type_output']), ''),
+ (0, "\n".join(scenario['lsattr_smt_threads_output']), ''),
+ ]
+ module = mocker.Mock()
+ module.run_command = mocker.Mock(side_effect=commands_results)
+ inst = aix.AIXHardware(module=module)
+ assert scenario['expected_result'] == inst.get_cpu_facts()
diff --git a/test/units/module_utils/facts/hardware/test_linux.py b/test/units/module_utils/facts/hardware/test_linux.py
new file mode 100644
index 0000000..e3e07e7
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/test_linux.py
@@ -0,0 +1,198 @@
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+
+from units.compat import unittest
+from units.compat.mock import Mock, patch
+
+from ansible.module_utils.facts import timeout
+
+from ansible.module_utils.facts.hardware import linux
+
+from . linux_data import LSBLK_OUTPUT, LSBLK_OUTPUT_2, LSBLK_UUIDS, MTAB, MTAB_ENTRIES, BIND_MOUNTS, STATVFS_INFO, UDEVADM_UUID, UDEVADM_OUTPUT, SG_INQ_OUTPUTS
+
+with open(os.path.join(os.path.dirname(__file__), '../fixtures/findmount_output.txt')) as f:
+ FINDMNT_OUTPUT = f.read()
+
+GET_MOUNT_SIZE = {}
+
+
+def mock_get_mount_size(mountpoint):
+ return STATVFS_INFO.get(mountpoint, {})
+
+
+class TestFactsLinuxHardwareGetMountFacts(unittest.TestCase):
+
+ # FIXME: mock.patch instead
+ def setUp(self):
+ timeout.GATHER_TIMEOUT = 10
+
+ def tearDown(self):
+ timeout.GATHER_TIMEOUT = None
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._mtab_entries', return_value=MTAB_ENTRIES)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._find_bind_mounts', return_value=BIND_MOUNTS)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._lsblk_uuid', return_value=LSBLK_UUIDS)
+ @patch('ansible.module_utils.facts.hardware.linux.get_mount_size', side_effect=mock_get_mount_size)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._udevadm_uuid', return_value=UDEVADM_UUID)
+ def test_get_mount_facts(self,
+ mock_get_mount_size,
+ mock_lsblk_uuid,
+ mock_find_bind_mounts,
+ mock_mtab_entries,
+ mock_udevadm_uuid):
+ module = Mock()
+ # Returns a LinuxHardware-ish
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+
+ # Nothing returned, just self.facts modified as a side effect
+ mount_facts = lh.get_mount_facts()
+ self.assertIsInstance(mount_facts, dict)
+ self.assertIn('mounts', mount_facts)
+ self.assertIsInstance(mount_facts['mounts'], list)
+ self.assertIsInstance(mount_facts['mounts'][0], dict)
+
+ home_expected = {'block_available': 1001578731,
+ 'block_size': 4096,
+ 'block_total': 105871006,
+ 'block_used': 5713133,
+ 'device': '/dev/mapper/fedora_dhcp129--186-home',
+ 'fstype': 'ext4',
+ 'inode_available': 26860880,
+ 'inode_total': 26902528,
+ 'inode_used': 41648,
+ 'mount': '/home',
+ 'options': 'rw,seclabel,relatime,data=ordered',
+ 'size_available': 410246647808,
+ 'size_total': 433647640576,
+ 'uuid': 'N/A'}
+ home_info = [x for x in mount_facts['mounts'] if x['mount'] == '/home'][0]
+
+ self.maxDiff = 4096
+ self.assertDictEqual(home_info, home_expected)
+
+ @patch('ansible.module_utils.facts.hardware.linux.get_file_content', return_value=MTAB)
+ def test_get_mtab_entries(self, mock_get_file_content):
+
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ mtab_entries = lh._mtab_entries()
+ self.assertIsInstance(mtab_entries, list)
+ self.assertIsInstance(mtab_entries[0], list)
+ self.assertEqual(len(mtab_entries), 38)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(0, FINDMNT_OUTPUT, ''))
+ def test_find_bind_mounts(self, mock_run_findmnt):
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ # If bind_mounts becomes another seq type, feel free to change
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 1)
+ self.assertIn('/not/a/real/bind_mount', bind_mounts)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(37, '', ''))
+ def test_find_bind_mounts_non_zero(self, mock_run_findmnt):
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 0)
+
+ def test_find_bind_mounts_no_findmnts(self):
+ module = Mock()
+ module.get_bin_path = Mock(return_value=None)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 0)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT, ''))
+ def test_lsblk_uuid(self, mock_run_lsblk):
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertIn(b'/dev/loop9', lsblk_uuids)
+ self.assertIn(b'/dev/sda1', lsblk_uuids)
+ self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(37, LSBLK_OUTPUT, ''))
+ def test_lsblk_uuid_non_zero(self, mock_run_lsblk):
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertEqual(len(lsblk_uuids), 0)
+
+ def test_lsblk_uuid_no_lsblk(self):
+ module = Mock()
+ module.get_bin_path = Mock(return_value=None)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertEqual(len(lsblk_uuids), 0)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT_2, ''))
+ def test_lsblk_uuid_dev_with_space_in_name(self, mock_run_lsblk):
+ module = Mock()
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertIn(b'/dev/loop0', lsblk_uuids)
+ self.assertIn(b'/dev/sda1', lsblk_uuids)
+ self.assertEqual(lsblk_uuids[b'/dev/mapper/an-example-mapper with a space in the name'], b'84639acb-013f-4d2f-9392-526a572b4373')
+ self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
+
+ def test_udevadm_uuid(self):
+ module = Mock()
+ module.run_command = Mock(return_value=(0, UDEVADM_OUTPUT, '')) # (rc, out, err)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ udevadm_uuid = lh._udevadm_uuid('mock_device')
+
+ self.assertEqual(udevadm_uuid, '57b1a3e7-9019-4747-9809-7ec52bba9179')
+
+ def test_get_sg_inq_serial(self):
+ # Valid outputs
+ for sq_inq_output in SG_INQ_OUTPUTS:
+ module = Mock()
+ module.run_command = Mock(return_value=(0, sq_inq_output, '')) # (rc, out, err)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ sg_inq_serial = lh._get_sg_inq_serial('/usr/bin/sg_inq', 'nvme0n1')
+ self.assertEqual(sg_inq_serial, 'vol0123456789')
+
+ # Invalid output
+ module = Mock()
+ module.run_command = Mock(return_value=(0, '', '')) # (rc, out, err)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ sg_inq_serial = lh._get_sg_inq_serial('/usr/bin/sg_inq', 'nvme0n1')
+ self.assertEqual(sg_inq_serial, None)
+
+ # Non zero rc
+ module = Mock()
+ module.run_command = Mock(return_value=(42, '', 'Error 42')) # (rc, out, err)
+ lh = linux.LinuxHardware(module=module, load_on_init=False)
+ sg_inq_serial = lh._get_sg_inq_serial('/usr/bin/sg_inq', 'nvme0n1')
+ self.assertEqual(sg_inq_serial, None)
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
new file mode 100644
index 0000000..aea8694
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/test_linux_get_cpu_info.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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
+
+from ansible.module_utils.facts.hardware import linux
+
+from . linux_data import CPU_INFO_TEST_SCENARIOS
+
+
+def test_get_cpu_info(mocker):
+ module = mocker.Mock()
+ inst = linux.LinuxHardware(module)
+
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('os.access', return_value=True)
+ for test in CPU_INFO_TEST_SCENARIOS:
+ mocker.patch('ansible.module_utils.facts.hardware.linux.get_file_lines', side_effect=[[], test['cpuinfo']])
+ mocker.patch('os.sched_getaffinity', create=True, return_value=test['sched_getaffinity'])
+ module.run_command.return_value = (0, test['nproc_out'], '')
+ collected_facts = {'ansible_architecture': test['architecture']}
+
+ assert test['expected_result'] == inst.get_cpu_facts(collected_facts=collected_facts)
+
+
+def test_get_cpu_info_nproc(mocker):
+ module = mocker.Mock()
+ inst = linux.LinuxHardware(module)
+
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('os.access', return_value=True)
+ for test in CPU_INFO_TEST_SCENARIOS:
+ mocker.patch('ansible.module_utils.facts.hardware.linux.get_file_lines', side_effect=[[], test['cpuinfo']])
+ mocker.patch('os.sched_getaffinity', create=True, side_effect=AttributeError)
+ mocker.patch('ansible.module_utils.facts.hardware.linux.get_bin_path', return_value='/usr/bin/nproc')
+ module.run_command.return_value = (0, test['nproc_out'], '')
+ collected_facts = {'ansible_architecture': test['architecture']}
+
+ assert test['expected_result'] == inst.get_cpu_facts(collected_facts=collected_facts)
+
+
+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
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('os.access', return_value=True)
+ for test in CPU_INFO_TEST_SCENARIOS:
+ mocker.patch('ansible.module_utils.facts.hardware.linux.get_file_lines', side_effect=[[], test['cpuinfo']])
+ mocker.patch('os.sched_getaffinity', create=True, return_value=test['sched_getaffinity'])
+
+ module.run_command.return_value = (0, test['nproc_out'], '')
+
+ test_result = inst.get_cpu_facts()
+
+ if test['architecture'].startswith(('armv', 'aarch', 'ppc')):
+ assert test['expected_result'] != test_result
+ else:
+ assert test['expected_result'] == test_result
diff --git a/test/units/module_utils/facts/hardware/test_sunos_get_uptime_facts.py b/test/units/module_utils/facts/hardware/test_sunos_get_uptime_facts.py
new file mode 100644
index 0000000..e14a2da
--- /dev/null
+++ b/test/units/module_utils/facts/hardware/test_sunos_get_uptime_facts.py
@@ -0,0 +1,20 @@
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import time
+from ansible.module_utils.facts.hardware import sunos
+
+
+def test_sunos_get_uptime_facts(mocker):
+ kstat_output = '\nunix:0:system_misc:boot_time\t1548249689\n'
+
+ module_mock = mocker.patch('ansible.module_utils.basic.AnsibleModule')
+ module = module_mock()
+ module.run_command.return_value = (0, kstat_output, '')
+
+ inst = sunos.SunOSHardware(module)
+
+ mocker.patch('time.time', return_value=1567052602.5089788)
+ expected = int(time.time()) - 1548249689
+ result = inst.get_uptime_facts()
+ assert expected == result['uptime_seconds']
diff --git a/test/units/module_utils/facts/network/__init__.py b/test/units/module_utils/facts/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/network/__init__.py
diff --git a/test/units/module_utils/facts/network/test_fc_wwn.py b/test/units/module_utils/facts/network/test_fc_wwn.py
new file mode 100644
index 0000000..32a3a43
--- /dev/null
+++ b/test/units/module_utils/facts/network/test_fc_wwn.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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
+
+from ansible.module_utils.facts.network import fc_wwn
+from units.compat.mock import Mock
+
+
+# AIX lsdev
+LSDEV_OUTPUT = """
+fcs0 Defined 00-00 8Gb PCI Express Dual Port FC Adapter (df1000f114108a03)
+fcs1 Available 04-00 8Gb PCI Express Dual Port FC Adapter (df1000f114108a03)
+"""
+
+# a bit cutted output of lscfg (from Z0 to ZC)
+LSCFG_OUTPUT = """
+ fcs1 U78CB.001.WZS00ZS-P1-C9-T1 8Gb PCI Express Dual Port FC Adapter (df1000f114108a03)
+
+ Part Number.................00E0806
+ Serial Number...............1C4090830F
+ Manufacturer................001C
+ EC Level.................... D77161
+ Customer Card ID Number.....577D
+ FRU Number..................00E0806
+ Device Specific.(ZM)........3
+ Network Address.............10000090FA551508
+ ROS Level and ID............027820B7
+ Device Specific.(Z0)........31004549
+ Device Specific.(ZC)........00000000
+ Hardware Location Code......U78CB.001.WZS00ZS-P1-C9-T1
+"""
+
+# Solaris
+FCINFO_OUTPUT = """
+HBA Port WWN: 10000090fa1658de
+ Port Mode: Initiator
+ Port ID: 30100
+ OS Device Name: /dev/cfg/c13
+ Manufacturer: Emulex
+ Model: LPe12002-S
+ Firmware Version: LPe12002-S 2.01a12
+ FCode/BIOS Version: Boot:5.03a0 Fcode:3.01a1
+ Serial Number: 4925381+13090001ER
+ Driver Name: emlxs
+ Driver Version: 3.3.00.1 (2018.01.05.16.30)
+ Type: N-port
+ State: online
+ Supported Speeds: 2Gb 4Gb 8Gb
+ Current Speed: 8Gb
+ Node WWN: 20000090fa1658de
+ NPIV Not Supported
+"""
+
+IOSCAN_OUT = """
+Class I H/W Path Driver S/W State H/W Type Description
+==================================================================
+fc 0 2/0/10/1/0 fcd CLAIMED INTERFACE HP AB379-60101 4Gb Dual Port PCI/PCI-X Fibre Channel Adapter (FC Port 1)
+ /dev/fcd0
+"""
+
+FCMSUTIL_OUT = """
+ Vendor ID is = 0x1077
+ Device ID is = 0x2422
+ PCI Sub-system Vendor ID is = 0x103C
+ PCI Sub-system ID is = 0x12D7
+ PCI Mode = PCI-X 133 MHz
+ ISP Code version = 5.4.0
+ ISP Chip version = 3
+ Topology = PTTOPT_FABRIC
+ Link Speed = 4Gb
+ Local N_Port_id is = 0x010300
+ Previous N_Port_id is = None
+ N_Port Node World Wide Name = 0x50060b00006975ed
+ N_Port Port World Wide Name = 0x50060b00006975ec
+ Switch Port World Wide Name = 0x200300051e046c0f
+ Switch Node World Wide Name = 0x100000051e046c0f
+ N_Port Symbolic Port Name = server1_fcd0
+ N_Port Symbolic Node Name = server1_HP-UX_B.11.31
+ Driver state = ONLINE
+ Hardware Path is = 2/0/10/1/0
+ Maximum Frame Size = 2048
+ Driver-Firmware Dump Available = NO
+ Driver-Firmware Dump Timestamp = N/A
+ TYPE = PFC
+ NPIV Supported = YES
+ Driver Version = @(#) fcd B.11.31.1103 Dec 6 2010
+"""
+
+
+def mock_get_bin_path(cmd, required=False, opt_dirs=None):
+ result = None
+ if cmd == 'lsdev':
+ result = '/usr/sbin/lsdev'
+ elif cmd == 'lscfg':
+ result = '/usr/sbin/lscfg'
+ elif cmd == 'fcinfo':
+ result = '/usr/sbin/fcinfo'
+ elif cmd == 'ioscan':
+ result = '/usr/bin/ioscan'
+ elif cmd == 'fcmsutil':
+ result = '/opt/fcms/bin/fcmsutil'
+ return result
+
+
+def mock_run_command(cmd):
+ rc = 0
+ if 'lsdev' in cmd:
+ result = LSDEV_OUTPUT
+ elif 'lscfg' in cmd:
+ result = LSCFG_OUTPUT
+ elif 'fcinfo' in cmd:
+ result = FCINFO_OUTPUT
+ elif 'ioscan' in cmd:
+ result = IOSCAN_OUT
+ elif 'fcmsutil' in cmd:
+ result = FCMSUTIL_OUT
+ else:
+ rc = 1
+ result = 'Error'
+ return (rc, result, '')
+
+
+def test_get_fc_wwn_info(mocker):
+ module = Mock()
+ inst = fc_wwn.FcWwnInitiatorFactCollector()
+
+ mocker.patch.object(module, 'get_bin_path', side_effect=mock_get_bin_path)
+ mocker.patch.object(module, 'run_command', side_effect=mock_run_command)
+
+ d = {'aix6': ['10000090FA551508'], 'sunos5': ['10000090fa1658de'], 'hp-ux11': ['0x50060b00006975ec']}
+ for key, value in d.items():
+ mocker.patch('sys.platform', key)
+ wwn_expected = {"fibre_channel_wwn": value}
+ assert wwn_expected == inst.collect(module=module)
diff --git a/test/units/module_utils/facts/network/test_generic_bsd.py b/test/units/module_utils/facts/network/test_generic_bsd.py
new file mode 100644
index 0000000..f061f04
--- /dev/null
+++ b/test/units/module_utils/facts/network/test_generic_bsd.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat.mock import Mock
+from units.compat import unittest
+
+from ansible.module_utils.facts.network import generic_bsd
+
+
+def get_bin_path(command):
+ if command == 'ifconfig':
+ return 'fake/ifconfig'
+ elif command == 'route':
+ return 'fake/route'
+ return None
+
+
+netbsd_ifconfig_a_out_7_1 = r'''
+lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 33624
+ inet 127.0.0.1 netmask 0xff000000
+ inet6 ::1 prefixlen 128
+ inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
+re0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
+ capabilities=3f80<TSO4,IP4CSUM_Rx,IP4CSUM_Tx,TCP4CSUM_Rx,TCP4CSUM_Tx>
+ capabilities=3f80<UDP4CSUM_Rx,UDP4CSUM_Tx>
+ enabled=0
+ ec_capabilities=3<VLAN_MTU,VLAN_HWTAGGING>
+ ec_enabled=0
+ address: 52:54:00:63:55:af
+ media: Ethernet autoselect (100baseTX full-duplex)
+ status: active
+ inet 192.168.122.205 netmask 0xffffff00 broadcast 192.168.122.255
+ inet6 fe80::5054:ff:fe63:55af%re0 prefixlen 64 scopeid 0x2
+'''
+
+netbsd_ifconfig_a_out_post_7_1 = r'''
+lo0: flags=0x8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 33624
+ inet 127.0.0.1/8 flags 0x0
+ inet6 ::1/128 flags 0x20<NODAD>
+ inet6 fe80::1%lo0/64 flags 0x0 scopeid 0x1
+re0: flags=0x8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
+ capabilities=3f80<TSO4,IP4CSUM_Rx,IP4CSUM_Tx,TCP4CSUM_Rx,TCP4CSUM_Tx>
+ capabilities=3f80<UDP4CSUM_Rx,UDP4CSUM_Tx>
+ enabled=0
+ ec_capabilities=3<VLAN_MTU,VLAN_HWTAGGING>
+ ec_enabled=0
+ address: 52:54:00:63:55:af
+ media: Ethernet autoselect (100baseTX full-duplex)
+ status: active
+ inet 192.168.122.205/24 broadcast 192.168.122.255 flags 0x0
+ inet6 fe80::5054:ff:fe63:55af%re0/64 flags 0x0 scopeid 0x2
+'''
+
+NETBSD_EXPECTED = {'all_ipv4_addresses': ['192.168.122.205'],
+ 'all_ipv6_addresses': ['fe80::5054:ff:fe63:55af%re0'],
+ 'default_ipv4': {},
+ 'default_ipv6': {},
+ 'interfaces': ['lo0', 're0'],
+ 'lo0': {'device': 'lo0',
+ 'flags': ['UP', 'LOOPBACK', 'RUNNING', 'MULTICAST'],
+ 'ipv4': [{'address': '127.0.0.1',
+ 'broadcast': '127.255.255.255',
+ 'netmask': '255.0.0.0',
+ 'network': '127.0.0.0'}],
+ 'ipv6': [{'address': '::1', 'prefix': '128'},
+ {'address': 'fe80::1%lo0', 'prefix': '64', 'scope': '0x1'}],
+ 'macaddress': 'unknown',
+ 'mtu': '33624',
+ 'type': 'loopback'},
+ 're0': {'device': 're0',
+ 'flags': ['UP', 'BROADCAST', 'RUNNING', 'SIMPLEX', 'MULTICAST'],
+ 'ipv4': [{'address': '192.168.122.205',
+ 'broadcast': '192.168.122.255',
+ 'netmask': '255.255.255.0',
+ 'network': '192.168.122.0'}],
+ 'ipv6': [{'address': 'fe80::5054:ff:fe63:55af%re0',
+ 'prefix': '64',
+ 'scope': '0x2'}],
+ 'macaddress': 'unknown',
+ 'media': 'Ethernet',
+ 'media_options': [],
+ 'media_select': 'autoselect',
+ 'media_type': '100baseTX',
+ 'mtu': '1500',
+ 'status': 'active',
+ 'type': 'ether'}}
+
+
+def run_command_old_ifconfig(command):
+ if command == 'fake/route':
+ return 0, 'Foo', ''
+ if command == ['fake/ifconfig', '-a']:
+ return 0, netbsd_ifconfig_a_out_7_1, ''
+ return 1, '', ''
+
+
+def run_command_post_7_1_ifconfig(command):
+ if command == 'fake/route':
+ return 0, 'Foo', ''
+ if command == ['fake/ifconfig', '-a']:
+ return 0, netbsd_ifconfig_a_out_post_7_1, ''
+ return 1, '', ''
+
+
+class TestGenericBsdNetworkNetBSD(unittest.TestCase):
+ gather_subset = ['all']
+
+ def setUp(self):
+ self.maxDiff = None
+ self.longMessage = True
+
+ # TODO: extract module run_command/get_bin_path usage to methods I can mock without mocking all of run_command
+ def test(self):
+ module = self._mock_module()
+ module.get_bin_path.side_effect = get_bin_path
+ module.run_command.side_effect = run_command_old_ifconfig
+
+ bsd_net = generic_bsd.GenericBsdIfconfigNetwork(module)
+
+ res = bsd_net.populate()
+ self.assertDictEqual(res, NETBSD_EXPECTED)
+
+ def test_ifconfig_post_7_1(self):
+ module = self._mock_module()
+ module.get_bin_path.side_effect = get_bin_path
+ module.run_command.side_effect = run_command_post_7_1_ifconfig
+
+ bsd_net = generic_bsd.GenericBsdIfconfigNetwork(module)
+
+ res = bsd_net.populate()
+ self.assertDictEqual(res, NETBSD_EXPECTED)
+
+ def test_netbsd_ifconfig_old_and_new(self):
+ module_new = self._mock_module()
+ module_new.get_bin_path.side_effect = get_bin_path
+ module_new.run_command.side_effect = run_command_post_7_1_ifconfig
+
+ bsd_net_new = generic_bsd.GenericBsdIfconfigNetwork(module_new)
+ res_new = bsd_net_new.populate()
+
+ module_old = self._mock_module()
+ module_old.get_bin_path.side_effect = get_bin_path
+ module_old.run_command.side_effect = run_command_old_ifconfig
+
+ bsd_net_old = generic_bsd.GenericBsdIfconfigNetwork(module_old)
+ res_old = bsd_net_old.populate()
+
+ self.assertDictEqual(res_old, res_new)
+ self.assertDictEqual(res_old, NETBSD_EXPECTED)
+ self.assertDictEqual(res_new, NETBSD_EXPECTED)
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 5,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value=None)
+ return mock_module
+
+ def test_ensure_correct_netmask_parsing(self):
+ n = generic_bsd.GenericBsdIfconfigNetwork(None)
+ lines = [
+ 'inet 192.168.7.113 netmask 0xffffff00 broadcast 192.168.7.255',
+ 'inet 10.109.188.206 --> 10.109.188.206 netmask 0xffffe000',
+ ]
+ expected = [
+ (
+ {
+ 'ipv4': [
+ {
+ 'address': '192.168.7.113',
+ 'netmask': '255.255.255.0',
+ 'network': '192.168.7.0',
+ 'broadcast': '192.168.7.255'
+ }
+ ]
+ },
+ {'all_ipv4_addresses': ['192.168.7.113']},
+ ),
+ (
+ {
+ 'ipv4': [
+ {
+ 'address': '10.109.188.206',
+ 'netmask': '255.255.224.0',
+ 'network': '10.109.160.0',
+ 'broadcast': '10.109.191.255'
+ }
+ ]
+ },
+ {'all_ipv4_addresses': ['10.109.188.206']},
+ ),
+ ]
+ for i, line in enumerate(lines):
+ words = line.split()
+ current_if = {'ipv4': []}
+ ips = {'all_ipv4_addresses': []}
+ n.parse_inet_line(words, current_if, ips)
+ self.assertDictEqual(current_if, expected[i][0])
+ self.assertDictEqual(ips, expected[i][1])
diff --git a/test/units/module_utils/facts/network/test_iscsi_get_initiator.py b/test/units/module_utils/facts/network/test_iscsi_get_initiator.py
new file mode 100644
index 0000000..2048ba2
--- /dev/null
+++ b/test/units/module_utils/facts/network/test_iscsi_get_initiator.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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
+
+from ansible.module_utils.facts.network import iscsi
+from units.compat.mock import Mock
+
+
+# AIX # lsattr -E -l iscsi0
+LSATTR_OUTPUT = """
+disc_filename /etc/iscsi/targets Configuration file False
+disc_policy file Discovery Policy True
+initiator_name iqn.localhost.hostid.7f000002 iSCSI Initiator Name True
+isns_srvnames auto iSNS Servers IP Addresses True
+isns_srvports iSNS Servers Port Numbers True
+max_targets 16 Maximum Targets Allowed True
+num_cmd_elems 200 Maximum number of commands to queue to driver True
+"""
+
+# HP-UX # iscsiutil -l
+ISCSIUTIL_OUTPUT = """
+Initiator Name : iqn.2001-04.com.hp.stor:svcio
+Initiator Alias :
+Authentication Method : None
+CHAP Method : CHAP_UNI
+Initiator CHAP Name :
+CHAP Secret :
+NAS Hostname :
+NAS Secret :
+Radius Server Hostname :
+Header Digest : None,CRC32C (default)
+Data Digest : None,CRC32C (default)
+SLP Scope list for iSLPD :
+"""
+
+
+def test_get_iscsi_info(mocker):
+ module = Mock()
+ inst = iscsi.IscsiInitiatorNetworkCollector()
+
+ mocker.patch('sys.platform', 'aix6')
+ mocker.patch('ansible.module_utils.facts.network.iscsi.get_bin_path', return_value='/usr/sbin/lsattr')
+ mocker.patch.object(module, 'run_command', return_value=(0, LSATTR_OUTPUT, ''))
+ aix_iscsi_expected = {"iscsi_iqn": "iqn.localhost.hostid.7f000002"}
+ assert aix_iscsi_expected == inst.collect(module=module)
+
+ mocker.patch('sys.platform', 'hp-ux')
+ mocker.patch('ansible.module_utils.facts.network.iscsi.get_bin_path', return_value='/opt/iscsi/bin/iscsiutil')
+ mocker.patch.object(module, 'run_command', return_value=(0, ISCSIUTIL_OUTPUT, ''))
+ hpux_iscsi_expected = {"iscsi_iqn": " iqn.2001-04.com.hp.stor:svcio"}
+ assert hpux_iscsi_expected == inst.collect(module=module)
diff --git a/test/units/module_utils/facts/other/__init__.py b/test/units/module_utils/facts/other/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/other/__init__.py
diff --git a/test/units/module_utils/facts/other/test_facter.py b/test/units/module_utils/facts/other/test_facter.py
new file mode 100644
index 0000000..7466338
--- /dev/null
+++ b/test/units/module_utils/facts/other/test_facter.py
@@ -0,0 +1,228 @@
+# unit tests for ansible other facter fact collector
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat.mock import Mock, patch
+
+from .. base import BaseFactsTest
+
+from ansible.module_utils.facts.other.facter import FacterFactCollector
+
+facter_json_output = '''
+{
+ "operatingsystemmajrelease": "25",
+ "hardwareisa": "x86_64",
+ "kernel": "Linux",
+ "path": "/home/testuser/src/ansible/bin:/home/testuser/perl5/bin:/home/testuser/perl5/bin:/home/testuser/bin:/home/testuser/.local/bin:/home/testuser/pythons/bin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/testuser/.cabal/bin:/home/testuser/gopath/bin:/home/testuser/.rvm/bin",
+ "memorysize": "15.36 GB",
+ "memoryfree": "4.88 GB",
+ "swapsize": "7.70 GB",
+ "swapfree": "6.75 GB",
+ "swapsize_mb": "7880.00",
+ "swapfree_mb": "6911.41",
+ "memorysize_mb": "15732.95",
+ "memoryfree_mb": "4997.68",
+ "lsbmajdistrelease": "25",
+ "macaddress": "02:42:ea:15:d8:84",
+ "id": "testuser",
+ "domain": "example.com",
+ "augeasversion": "1.7.0",
+ "os": {
+ "name": "Fedora",
+ "family": "RedHat",
+ "release": {
+ "major": "25",
+ "full": "25"
+ },
+ "lsb": {
+ "distcodename": "TwentyFive",
+ "distid": "Fedora",
+ "distdescription": "Fedora release 25 (Twenty Five)",
+ "release": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch",
+ "distrelease": "25",
+ "majdistrelease": "25"
+ }
+ },
+ "processors": {
+ "models": [
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz"
+ ],
+ "count": 8,
+ "physicalcount": 1
+ },
+ "architecture": "x86_64",
+ "hardwaremodel": "x86_64",
+ "operatingsystem": "Fedora",
+ "processor0": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor1": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor2": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor3": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor4": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor5": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor6": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processor7": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "processorcount": 8,
+ "uptime_seconds": 1558090,
+ "fqdn": "myhostname.example.com",
+ "rubyversion": "2.3.3",
+ "gid": "testuser",
+ "physicalprocessorcount": 1,
+ "netmask": "255.255.0.0",
+ "uniqueid": "a8c01301",
+ "uptime_days": 18,
+ "interfaces": "docker0,em1,lo,vethf20ff12,virbr0,virbr1,virbr0_nic,virbr1_nic,wlp4s0",
+ "ipaddress_docker0": "172.17.0.1",
+ "macaddress_docker0": "02:42:ea:15:d8:84",
+ "netmask_docker0": "255.255.0.0",
+ "mtu_docker0": 1500,
+ "macaddress_em1": "3c:97:0e:e9:28:8e",
+ "mtu_em1": 1500,
+ "ipaddress_lo": "127.0.0.1",
+ "netmask_lo": "255.0.0.0",
+ "mtu_lo": 65536,
+ "macaddress_vethf20ff12": "ae:6e:2b:1e:a1:31",
+ "mtu_vethf20ff12": 1500,
+ "ipaddress_virbr0": "192.168.137.1",
+ "macaddress_virbr0": "52:54:00:ce:82:5e",
+ "netmask_virbr0": "255.255.255.0",
+ "mtu_virbr0": 1500,
+ "ipaddress_virbr1": "192.168.121.1",
+ "macaddress_virbr1": "52:54:00:b4:68:a9",
+ "netmask_virbr1": "255.255.255.0",
+ "mtu_virbr1": 1500,
+ "macaddress_virbr0_nic": "52:54:00:ce:82:5e",
+ "mtu_virbr0_nic": 1500,
+ "macaddress_virbr1_nic": "52:54:00:b4:68:a9",
+ "mtu_virbr1_nic": 1500,
+ "ipaddress_wlp4s0": "192.168.1.19",
+ "macaddress_wlp4s0": "5c:51:4f:e6:a8:e3",
+ "netmask_wlp4s0": "255.255.255.0",
+ "mtu_wlp4s0": 1500,
+ "virtual": "physical",
+ "is_virtual": false,
+ "partitions": {
+ "sda2": {
+ "size": "499091456"
+ },
+ "sda1": {
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0",
+ "size": "1024000",
+ "mount": "/boot"
+ }
+ },
+ "lsbdistcodename": "TwentyFive",
+ "lsbrelease": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch", # noqa
+ "filesystems": "btrfs,ext2,ext3,ext4,xfs",
+ "system_uptime": {
+ "seconds": 1558090,
+ "hours": 432,
+ "days": 18,
+ "uptime": "18 days"
+ },
+ "ipaddress": "172.17.0.1",
+ "timezone": "EDT",
+ "ps": "ps -ef",
+ "rubyplatform": "x86_64-linux",
+ "rubysitedir": "/usr/local/share/ruby/site_ruby",
+ "uptime": "18 days",
+ "lsbdistrelease": "25",
+ "operatingsystemrelease": "25",
+ "facterversion": "2.4.3",
+ "kernelrelease": "4.9.14-200.fc25.x86_64",
+ "lsbdistdescription": "Fedora release 25 (Twenty Five)",
+ "network_docker0": "172.17.0.0",
+ "network_lo": "127.0.0.0",
+ "network_virbr0": "192.168.137.0",
+ "network_virbr1": "192.168.121.0",
+ "network_wlp4s0": "192.168.1.0",
+ "lsbdistid": "Fedora",
+ "selinux": true,
+ "selinux_enforced": false,
+ "selinux_policyversion": "30",
+ "selinux_current_mode": "permissive",
+ "selinux_config_mode": "permissive",
+ "selinux_config_policy": "targeted",
+ "hostname": "myhostname",
+ "osfamily": "RedHat",
+ "kernelmajversion": "4.9",
+ "blockdevice_sr0_size": 1073741312,
+ "blockdevice_sr0_vendor": "MATSHITA",
+ "blockdevice_sr0_model": "DVD-RAM UJ8E2",
+ "blockdevice_sda_size": 256060514304,
+ "blockdevice_sda_vendor": "ATA",
+ "blockdevice_sda_model": "SAMSUNG MZ7TD256",
+ "blockdevices": "sda,sr0",
+ "uptime_hours": 432,
+ "kernelversion": "4.9.14"
+}
+'''
+
+
+class TestFacterCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'facter']
+ valid_subsets = ['facter']
+ fact_namespace = 'ansible_facter'
+ collector_class = FacterFactCollector
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 10,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value='/not/actually/facter')
+ mock_module.run_command = Mock(return_value=(0, facter_json_output, ''))
+ return mock_module
+
+ @patch('ansible.module_utils.facts.other.facter.FacterFactCollector.get_facter_output')
+ def test_bogus_json(self, mock_get_facter_output):
+ module = self._mock_module()
+
+ # bogus json
+ mock_get_facter_output.return_value = '{'
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict, {})
+
+ @patch('ansible.module_utils.facts.other.facter.FacterFactCollector.run_facter')
+ def test_facter_non_zero_return_code(self, mock_run_facter):
+ module = self._mock_module()
+
+ # bogus json
+ mock_run_facter.return_value = (1, '{}', '')
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+
+ # This assumes no 'facter' entry at all is correct
+ self.assertNotIn('facter', facts_dict)
+ self.assertEqual(facts_dict, {})
diff --git a/test/units/module_utils/facts/other/test_ohai.py b/test/units/module_utils/facts/other/test_ohai.py
new file mode 100644
index 0000000..42a72d9
--- /dev/null
+++ b/test/units/module_utils/facts/other/test_ohai.py
@@ -0,0 +1,6768 @@
+# unit tests for ansible ohai fact collector
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat.mock import Mock, patch
+
+from .. base import BaseFactsTest
+
+from ansible.module_utils.facts.other.ohai import OhaiFactCollector
+
+ohai_json_output = r'''
+{
+ "kernel": {
+ "name": "Linux",
+ "release": "4.9.14-200.fc25.x86_64",
+ "version": "#1 SMP Mon Mar 13 19:26:40 UTC 2017",
+ "machine": "x86_64",
+ "processor": "x86_64",
+ "os": "GNU/Linux",
+ "modules": {
+ "binfmt_misc": {
+ "size": "20480",
+ "refcount": "1"
+ },
+ "veth": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "xfs": {
+ "size": "1200128",
+ "refcount": "1"
+ },
+ "xt_addrtype": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "br_netfilter": {
+ "size": "24576",
+ "refcount": "0"
+ },
+ "dm_thin_pool": {
+ "size": "65536",
+ "refcount": "2"
+ },
+ "dm_persistent_data": {
+ "size": "69632",
+ "refcount": "1"
+ },
+ "dm_bio_prison": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "libcrc32c": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "rfcomm": {
+ "size": "77824",
+ "refcount": "14",
+ "version": "1.11"
+ },
+ "fuse": {
+ "size": "102400",
+ "refcount": "3"
+ },
+ "ccm": {
+ "size": "20480",
+ "refcount": "2"
+ },
+ "xt_CHECKSUM": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "iptable_mangle": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "ipt_MASQUERADE": {
+ "size": "16384",
+ "refcount": "7"
+ },
+ "nf_nat_masquerade_ipv4": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "iptable_nat": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "nf_nat_ipv4": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "nf_nat": {
+ "size": "28672",
+ "refcount": "2"
+ },
+ "nf_conntrack_ipv4": {
+ "size": "16384",
+ "refcount": "4"
+ },
+ "nf_defrag_ipv4": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "xt_conntrack": {
+ "size": "16384",
+ "refcount": "3"
+ },
+ "nf_conntrack": {
+ "size": "106496",
+ "refcount": "5"
+ },
+ "ip6t_REJECT": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "nf_reject_ipv6": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "tun": {
+ "size": "28672",
+ "refcount": "4"
+ },
+ "bridge": {
+ "size": "135168",
+ "refcount": "1",
+ "version": "2.3"
+ },
+ "stp": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "llc": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "ebtable_filter": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "ebtables": {
+ "size": "36864",
+ "refcount": "1"
+ },
+ "ip6table_filter": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "ip6_tables": {
+ "size": "28672",
+ "refcount": "1"
+ },
+ "cmac": {
+ "size": "16384",
+ "refcount": "3"
+ },
+ "uhid": {
+ "size": "20480",
+ "refcount": "2"
+ },
+ "bnep": {
+ "size": "20480",
+ "refcount": "2",
+ "version": "1.3"
+ },
+ "btrfs": {
+ "size": "1056768",
+ "refcount": "1"
+ },
+ "xor": {
+ "size": "24576",
+ "refcount": "1"
+ },
+ "raid6_pq": {
+ "size": "106496",
+ "refcount": "1"
+ },
+ "loop": {
+ "size": "28672",
+ "refcount": "6"
+ },
+ "arc4": {
+ "size": "16384",
+ "refcount": "2"
+ },
+ "snd_hda_codec_hdmi": {
+ "size": "45056",
+ "refcount": "1"
+ },
+ "intel_rapl": {
+ "size": "20480",
+ "refcount": "0"
+ },
+ "x86_pkg_temp_thermal": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "intel_powerclamp": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "coretemp": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "kvm_intel": {
+ "size": "192512",
+ "refcount": "0"
+ },
+ "kvm": {
+ "size": "585728",
+ "refcount": "1"
+ },
+ "irqbypass": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "crct10dif_pclmul": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "crc32_pclmul": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "iTCO_wdt": {
+ "size": "16384",
+ "refcount": "0",
+ "version": "1.11"
+ },
+ "ghash_clmulni_intel": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "mei_wdt": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "iTCO_vendor_support": {
+ "size": "16384",
+ "refcount": "1",
+ "version": "1.04"
+ },
+ "iwlmvm": {
+ "size": "364544",
+ "refcount": "0"
+ },
+ "intel_cstate": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "uvcvideo": {
+ "size": "90112",
+ "refcount": "0",
+ "version": "1.1.1"
+ },
+ "videobuf2_vmalloc": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "intel_uncore": {
+ "size": "118784",
+ "refcount": "0"
+ },
+ "videobuf2_memops": {
+ "size": "16384",
+ "refcount": "1"
+ },
+ "videobuf2_v4l2": {
+ "size": "24576",
+ "refcount": "1"
+ },
+ "videobuf2_core": {
+ "size": "40960",
+ "refcount": "2"
+ },
+ "intel_rapl_perf": {
+ "size": "16384",
+ "refcount": "0"
+ },
+ "mac80211": {
+ "size": "749568",
+ "refcount": "1"
+ },
+ "videodev": {
+ "size": "172032",
+ "refcount": "3"
+ },
+ "snd_usb_audio": {
+ "size": "180224",
+ "refcount": "3"
+ },
+ "e1000e": {
+ "size": "249856",
+ "refcount": "0",
+ "version": "3.2.6-k"
+ }
+ }
+ },
+ "os": "linux",
+ "os_version": "4.9.14-200.fc25.x86_64",
+ "lsb": {
+ "id": "Fedora",
+ "description": "Fedora release 25 (Twenty Five)",
+ "release": "25",
+ "codename": "TwentyFive"
+ },
+ "platform": "fedora",
+ "platform_version": "25",
+ "platform_family": "fedora",
+ "packages": {
+ "ansible": {
+ "epoch": "0",
+ "version": "2.2.1.0",
+ "release": "1.fc25",
+ "installdate": "1486050042",
+ "arch": "noarch"
+ },
+ "python3": {
+ "epoch": "0",
+ "version": "3.5.3",
+ "release": "3.fc25",
+ "installdate": "1490025957",
+ "arch": "x86_64"
+ },
+ "kernel": {
+ "epoch": "0",
+ "version": "4.9.6",
+ "release": "200.fc25",
+ "installdate": "1486047522",
+ "arch": "x86_64"
+ },
+ "glibc": {
+ "epoch": "0",
+ "version": "2.24",
+ "release": "4.fc25",
+ "installdate": "1483402427",
+ "arch": "x86_64"
+ }
+ },
+ "chef_packages": {
+ ohai": {
+ "version": "13.0.0",
+ "ohai_root": "/home/some_user/.gem/ruby/gems/ohai-13.0.0/lib/ohai"
+ }
+ },
+ "dmi": {
+ "dmidecode_version": "3.0"
+ },
+ "uptime_seconds": 2509008,
+ "uptime": "29 days 00 hours 56 minutes 48 seconds",
+ "idletime_seconds": 19455087,
+ "idletime": "225 days 04 hours 11 minutes 27 seconds",
+ "memory": {
+ "swap": {
+ "cached": "262436kB",
+ "total": "8069116kB",
+ "free": "5154396kB"
+ },
+ "hugepages": {
+ "total": "0",
+ "free": "0",
+ "reserved": "0",
+ "surplus": "0"
+ },
+ "total": "16110540kB",
+ "free": "3825844kB",
+ "buffers": "377240kB",
+ "cached": "3710084kB",
+ "active": "8104320kB",
+ "inactive": "3192920kB",
+ "dirty": "812kB",
+ "writeback": "0kB",
+ "anon_pages": "7124992kB",
+ "mapped": "580700kB",
+ "slab": "622848kB",
+ "slab_reclaimable": "307300kB",
+ "slab_unreclaim": "315548kB",
+ "page_tables": "157572kB",
+ "nfs_unstable": "0kB",
+ "bounce": "0kB",
+ "commit_limit": "16124384kB",
+ "committed_as": "31345068kB",
+ "vmalloc_total": "34359738367kB",
+ "vmalloc_used": "0kB",
+ "vmalloc_chunk": "0kB",
+ "hugepage_size": "2048kB"
+ },
+ "filesystem": {
+ "by_device": {
+ "devtmpfs": {
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ],
+ "mounts": [
+ "/dev"
+ ]
+ },
+ "tmpfs": {
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ],
+ "mounts": [
+ "/dev/shm",
+ "/run",
+ "/sys/fs/cgroup",
+ "/tmp",
+ "/run/user/0",
+ "/run/user/1000"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root": {
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "12312331-3449-4a6c-8179-a1feb2bca6ce",
+ "mounts": [
+ "/",
+ "/var/lib/docker/devicemapper"
+ ]
+ },
+ "/dev/sda1": {
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "12312311-ef40-4691-a3b6-438c3f9bc1c0",
+ "mounts": [
+ "/boot"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-home": {
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d",
+ "mounts": [
+ "/home"
+ ]
+ },
+ "/dev/loop0": {
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390",
+ "mounts": [
+ "/var/lib/machines"
+ ]
+ },
+ "sysfs": {
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys"
+ ]
+ },
+ "proc": {
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "mounts": [
+ "/proc"
+ ]
+ },
+ "securityfs": {
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/security"
+ ]
+ },
+ "devpts": {
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ],
+ "mounts": [
+ "/dev/pts"
+ ]
+ },
+ "cgroup": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ],
+ "mounts": [
+ "/sys/fs/cgroup/systemd",
+ "/sys/fs/cgroup/devices",
+ "/sys/fs/cgroup/cpuset",
+ "/sys/fs/cgroup/perf_event",
+ "/sys/fs/cgroup/hugetlb",
+ "/sys/fs/cgroup/cpu,cpuacct",
+ "/sys/fs/cgroup/blkio",
+ "/sys/fs/cgroup/freezer",
+ "/sys/fs/cgroup/memory",
+ "/sys/fs/cgroup/pids",
+ "/sys/fs/cgroup/net_cls,net_prio"
+ ]
+ },
+ "pstore": {
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys/fs/pstore"
+ ]
+ },
+ "configfs": {
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/config"
+ ]
+ },
+ "selinuxfs": {
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/fs/selinux"
+ ]
+ },
+ "debugfs": {
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys/kernel/debug"
+ ]
+ },
+ "hugetlbfs": {
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/dev/hugepages"
+ ]
+ },
+ "mqueue": {
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/dev/mqueue"
+ ]
+ },
+ "systemd-1": {
+ "fs_type": "autofs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "fd=40",
+ "pgrp=1",
+ "timeout=0",
+ "minproto=5",
+ "maxproto=5",
+ "direct",
+ "pipe_ino=17610"
+ ],
+ "mounts": [
+ "/proc/sys/fs/binfmt_misc"
+ ]
+ },
+ "/var/lib/machines.raw": {
+ "fs_type": "btrfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ],
+ "mounts": [
+ "/var/lib/machines"
+ ]
+ },
+ "fusectl": {
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/fs/fuse/connections"
+ ]
+ },
+ "gvfsd-fuse": {
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ],
+ "mounts": [
+ "/run/user/1000/gvfs"
+ ]
+ },
+ "binfmt_misc": {
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/proc/sys/fs/binfmt_misc"
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": {
+ "fs_type": "xfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "nouuid",
+ "attr2",
+ "inode64",
+ "logbsize=64k",
+ "sunit=128",
+ "swidth=128",
+ "noquota"
+ ],
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123",
+ "mounts": [
+ "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8"
+ ]
+ },
+ "shm": {
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "size=65536k"
+ ],
+ "mounts": [
+ "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm"
+ ]
+ },
+ "nsfs": {
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ],
+ "mounts": [
+ "/run/docker/netns/1ce89fd79f3d"
+ ]
+ },
+ "tracefs": {
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/debug/tracing"
+ ]
+ },
+ "/dev/loop1": {
+ "fs_type": "xfs",
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123",
+ "mounts": [
+
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-pool": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sr0": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/loop2": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sda": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sda2": {
+ "fs_type": "LVM2_member",
+ "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK",
+ "mounts": [
+
+ ]
+ },
+ "/dev/mapper/fedora_host--186-swap": {
+ "fs_type": "swap",
+ "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d",
+ "mounts": [
+
+ ]
+ }
+ },
+ "by_mountpoint": {
+ "/dev": {
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ],
+ "devices": [
+ "devtmpfs"
+ ]
+ },
+ "/dev/shm": {
+ "kb_size": "8055268",
+ "kb_used": "96036",
+ "kb_available": "7959232",
+ "percent_used": "2%",
+ "total_inodes": "2013817",
+ "inodes_used": "217",
+ "inodes_available": "2013600",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/run": {
+ "kb_size": "8055268",
+ "kb_used": "2280",
+ "kb_available": "8052988",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "1070",
+ "inodes_available": "2012747",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel",
+ "mode=755"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/sys/fs/cgroup": {
+ "kb_size": "8055268",
+ "kb_used": "0",
+ "kb_available": "8055268",
+ "percent_used": "0%",
+ "total_inodes": "2013817",
+ "inodes_used": "16",
+ "inodes_available": "2013801",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "ro",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "seclabel",
+ "mode=755"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/": {
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce",
+ "devices": [
+ "/dev/mapper/fedora_host--186-root"
+ ]
+ },
+ "/tmp": {
+ "kb_size": "8055268",
+ "kb_used": "848396",
+ "kb_available": "7206872",
+ "percent_used": "11%",
+ "total_inodes": "2013817",
+ "inodes_used": "1353",
+ "inodes_available": "2012464",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/boot": {
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0",
+ "devices": [
+ "/dev/sda1"
+ ]
+ },
+ "/home": {
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d",
+ "devices": [
+ "/dev/mapper/fedora_host--186-home"
+ ]
+ },
+ "/var/lib/machines": {
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390",
+ "devices": [
+ "/dev/loop0",
+ "/var/lib/machines.raw"
+ ],
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ]
+ },
+ "/run/user/0": {
+ "kb_size": "1611052",
+ "kb_used": "0",
+ "kb_available": "1611052",
+ "percent_used": "0%",
+ "total_inodes": "2013817",
+ "inodes_used": "7",
+ "inodes_available": "2013810",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/run/user/1000": {
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/sys": {
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "sysfs"
+ ]
+ },
+ "/proc": {
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "devices": [
+ "proc"
+ ]
+ },
+ "/sys/kernel/security": {
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "devices": [
+ "securityfs"
+ ]
+ },
+ "/dev/pts": {
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ],
+ "devices": [
+ "devpts"
+ ]
+ },
+ "/sys/fs/cgroup/systemd": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "xattr",
+ "release_agent=/usr/lib/systemd/systemd-cgroups-agent",
+ "name=systemd"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/pstore": {
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "pstore"
+ ]
+ },
+ "/sys/fs/cgroup/devices": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "devices"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/cpuset": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpuset"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/perf_event": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "perf_event"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/hugetlb": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "hugetlb"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/cpu,cpuacct": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpu",
+ "cpuacct"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/blkio": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "blkio"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/freezer": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "freezer"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/memory": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "memory"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/pids": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "pids"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/net_cls,net_prio": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/kernel/config": {
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "configfs"
+ ]
+ },
+ "/sys/fs/selinux": {
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "selinuxfs"
+ ]
+ },
+ "/sys/kernel/debug": {
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "debugfs"
+ ]
+ },
+ "/dev/hugepages": {
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "hugetlbfs"
+ ]
+ },
+ "/dev/mqueue": {
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "mqueue"
+ ]
+ },
+ "/proc/sys/fs/binfmt_misc": {
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "systemd-1",
+ "binfmt_misc"
+ ]
+ },
+ "/sys/fs/fuse/connections": {
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "fusectl"
+ ]
+ },
+ "/run/user/1000/gvfs": {
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ],
+ "devices": [
+ "gvfsd-fuse"
+ ]
+ },
+ "/var/lib/docker/devicemapper": {
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce",
+ "devices": [
+ "/dev/mapper/fedora_host--186-root"
+ ]
+ },
+ "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": {
+ "fs_type": "xfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "nouuid",
+ "attr2",
+ "inode64",
+ "logbsize=64k",
+ "sunit=128",
+ "swidth=128",
+ "noquota"
+ ],
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123",
+ "devices": [
+ "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8"
+ ]
+ },
+ "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": {
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "size=65536k"
+ ],
+ "devices": [
+ "shm"
+ ]
+ },
+ "/run/docker/netns/1ce89fd79f3d": {
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ],
+ "devices": [
+ "nsfs"
+ ]
+ },
+ "/sys/kernel/debug/tracing": {
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "tracefs"
+ ]
+ }
+ },
+ "by_pair": {
+ "devtmpfs,/dev": {
+ "device": "devtmpfs",
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "mount": "/dev",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ]
+ },
+ "tmpfs,/dev/shm": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "96036",
+ "kb_available": "7959232",
+ "percent_used": "2%",
+ "mount": "/dev/shm",
+ "total_inodes": "2013817",
+ "inodes_used": "217",
+ "inodes_available": "2013600",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ]
+ },
+ "tmpfs,/run": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "2280",
+ "kb_available": "8052988",
+ "percent_used": "1%",
+ "mount": "/run",
+ "total_inodes": "2013817",
+ "inodes_used": "1070",
+ "inodes_available": "2012747",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel",
+ "mode=755"
+ ]
+ },
+ "tmpfs,/sys/fs/cgroup": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "0",
+ "kb_available": "8055268",
+ "percent_used": "0%",
+ "mount": "/sys/fs/cgroup",
+ "total_inodes": "2013817",
+ "inodes_used": "16",
+ "inodes_available": "2013801",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "ro",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "seclabel",
+ "mode=755"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root,/": {
+ "device": "/dev/mapper/fedora_host--186-root",
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "mount": "/",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce"
+ },
+ "tmpfs,/tmp": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "848396",
+ "kb_available": "7206872",
+ "percent_used": "11%",
+ "mount": "/tmp",
+ "total_inodes": "2013817",
+ "inodes_used": "1353",
+ "inodes_available": "2012464",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ]
+ },
+ "/dev/sda1,/boot": {
+ "device": "/dev/sda1",
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "mount": "/boot",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0"
+ },
+ "/dev/mapper/fedora_host--186-home,/home": {
+ "device": "/dev/mapper/fedora_host--186-home",
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "mount": "/home",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d"
+ },
+ "/dev/loop0,/var/lib/machines": {
+ "device": "/dev/loop0",
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "mount": "/var/lib/machines",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390"
+ },
+ "tmpfs,/run/user/0": {
+ "device": "tmpfs",
+ "kb_size": "1611052",
+ "kb_used": "0",
+ "kb_available": "1611052",
+ "percent_used": "0%",
+ "mount": "/run/user/0",
+ "total_inodes": "2013817",
+ "inodes_used": "7",
+ "inodes_available": "2013810",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700"
+ ]
+ },
+ "tmpfs,/run/user/1000": {
+ "device": "tmpfs",
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "mount": "/run/user/1000",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ]
+ },
+ "sysfs,/sys": {
+ "device": "sysfs",
+ "mount": "/sys",
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "proc,/proc": {
+ "device": "proc",
+ "mount": "/proc",
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ]
+ },
+ "securityfs,/sys/kernel/security": {
+ "device": "securityfs",
+ "mount": "/sys/kernel/security",
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ]
+ },
+ "devpts,/dev/pts": {
+ "device": "devpts",
+ "mount": "/dev/pts",
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/systemd": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/systemd",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "xattr",
+ "release_agent=/usr/lib/systemd/systemd-cgroups-agent",
+ "name=systemd"
+ ]
+ },
+ "pstore,/sys/fs/pstore": {
+ "device": "pstore",
+ "mount": "/sys/fs/pstore",
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/devices": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/devices",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "devices"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/cpuset": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/cpuset",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpuset"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/perf_event": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/perf_event",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "perf_event"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/hugetlb": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/hugetlb",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "hugetlb"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/cpu,cpuacct",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpu",
+ "cpuacct"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/blkio": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/blkio",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "blkio"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/freezer": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/freezer",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "freezer"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/memory": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/memory",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "memory"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/pids": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/pids",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "pids"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/net_cls,net_prio",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ]
+ },
+ "configfs,/sys/kernel/config": {
+ "device": "configfs",
+ "mount": "/sys/kernel/config",
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "selinuxfs,/sys/fs/selinux": {
+ "device": "selinuxfs",
+ "mount": "/sys/fs/selinux",
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "debugfs,/sys/kernel/debug": {
+ "device": "debugfs",
+ "mount": "/sys/kernel/debug",
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "hugetlbfs,/dev/hugepages": {
+ "device": "hugetlbfs",
+ "mount": "/dev/hugepages",
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "mqueue,/dev/mqueue": {
+ "device": "mqueue",
+ "mount": "/dev/mqueue",
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "systemd-1,/proc/sys/fs/binfmt_misc": {
+ "device": "systemd-1",
+ "mount": "/proc/sys/fs/binfmt_misc",
+ "fs_type": "autofs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "fd=40",
+ "pgrp=1",
+ "timeout=0",
+ "minproto=5",
+ "maxproto=5",
+ "direct",
+ "pipe_ino=17610"
+ ]
+ },
+ "/var/lib/machines.raw,/var/lib/machines": {
+ "device": "/var/lib/machines.raw",
+ "mount": "/var/lib/machines",
+ "fs_type": "btrfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ]
+ },
+ "fusectl,/sys/fs/fuse/connections": {
+ "device": "fusectl",
+ "mount": "/sys/fs/fuse/connections",
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "gvfsd-fuse,/run/user/1000/gvfs": {
+ "device": "gvfsd-fuse",
+ "mount": "/run/user/1000/gvfs",
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root,/var/lib/docker/devicemapper": {
+ "device": "/dev/mapper/fedora_host--186-root",
+ "mount": "/var/lib/docker/devicemapper",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce"
+ },
+ "binfmt_misc,/proc/sys/fs/binfmt_misc": {
+ "device": "binfmt_misc",
+ "mount": "/proc/sys/fs/binfmt_misc",
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8,/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": {
+ "device": "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8",
+ "mount": "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8",
+ "fs_type": "xfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "nouuid",
+ "attr2",
+ "inode64",
+ "logbsize=64k",
+ "sunit=128",
+ "swidth=128",
+ "noquota"
+ ],
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123"
+ },
+ "shm,/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": {
+ "device": "shm",
+ "mount": "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "size=65536k"
+ ]
+ },
+ "nsfs,/run/docker/netns/1ce89fd79f3d": {
+ "device": "nsfs",
+ "mount": "/run/docker/netns/1ce89fd79f3d",
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ]
+ },
+ "tracefs,/sys/kernel/debug/tracing": {
+ "device": "tracefs",
+ "mount": "/sys/kernel/debug/tracing",
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "/dev/loop1,": {
+ "device": "/dev/loop1",
+ "fs_type": "xfs",
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123"
+ },
+ "/dev/mapper/docker-253:1-1180487-pool,": {
+ "device": "/dev/mapper/docker-253:1-1180487-pool"
+ },
+ "/dev/sr0,": {
+ "device": "/dev/sr0"
+ },
+ "/dev/loop2,": {
+ "device": "/dev/loop2"
+ },
+ "/dev/sda,": {
+ "device": "/dev/sda"
+ },
+ "/dev/sda2,": {
+ "device": "/dev/sda2",
+ "fs_type": "LVM2_member",
+ "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK"
+ },
+ "/dev/mapper/fedora_host--186-swap,": {
+ "device": "/dev/mapper/fedora_host--186-swap",
+ "fs_type": "swap",
+ "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d"
+ }
+ }
+ },
+ "filesystem2": {
+ "by_device": {
+ "devtmpfs": {
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ],
+ "mounts": [
+ "/dev"
+ ]
+ },
+ "tmpfs": {
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ],
+ "mounts": [
+ "/dev/shm",
+ "/run",
+ "/sys/fs/cgroup",
+ "/tmp",
+ "/run/user/0",
+ "/run/user/1000"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root": {
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce",
+ "mounts": [
+ "/",
+ "/var/lib/docker/devicemapper"
+ ]
+ },
+ "/dev/sda1": {
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0",
+ "mounts": [
+ "/boot"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-home": {
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d",
+ "mounts": [
+ "/home"
+ ]
+ },
+ "/dev/loop0": {
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390",
+ "mounts": [
+ "/var/lib/machines"
+ ]
+ },
+ "sysfs": {
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys"
+ ]
+ },
+ "proc": {
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "mounts": [
+ "/proc"
+ ]
+ },
+ "securityfs": {
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/security"
+ ]
+ },
+ "devpts": {
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ],
+ "mounts": [
+ "/dev/pts"
+ ]
+ },
+ "cgroup": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ],
+ "mounts": [
+ "/sys/fs/cgroup/systemd",
+ "/sys/fs/cgroup/devices",
+ "/sys/fs/cgroup/cpuset",
+ "/sys/fs/cgroup/perf_event",
+ "/sys/fs/cgroup/hugetlb",
+ "/sys/fs/cgroup/cpu,cpuacct",
+ "/sys/fs/cgroup/blkio",
+ "/sys/fs/cgroup/freezer",
+ "/sys/fs/cgroup/memory",
+ "/sys/fs/cgroup/pids",
+ "/sys/fs/cgroup/net_cls,net_prio"
+ ]
+ },
+ "pstore": {
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys/fs/pstore"
+ ]
+ },
+ "configfs": {
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/config"
+ ]
+ },
+ "selinuxfs": {
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/fs/selinux"
+ ]
+ },
+ "debugfs": {
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/sys/kernel/debug"
+ ]
+ },
+ "hugetlbfs": {
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/dev/hugepages"
+ ]
+ },
+ "mqueue": {
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "mounts": [
+ "/dev/mqueue"
+ ]
+ },
+ "systemd-1": {
+ "fs_type": "autofs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "fd=40",
+ "pgrp=1",
+ "timeout=0",
+ "minproto=5",
+ "maxproto=5",
+ "direct",
+ "pipe_ino=17610"
+ ],
+ "mounts": [
+ "/proc/sys/fs/binfmt_misc"
+ ]
+ },
+ "/var/lib/machines.raw": {
+ "fs_type": "btrfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ],
+ "mounts": [
+ "/var/lib/machines"
+ ]
+ },
+ "fusectl": {
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/fs/fuse/connections"
+ ]
+ },
+ "gvfsd-fuse": {
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ],
+ "mounts": [
+ "/run/user/1000/gvfs"
+ ]
+ },
+ "binfmt_misc": {
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/proc/sys/fs/binfmt_misc"
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": {
+ "fs_type": "xfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "nouuid",
+ "attr2",
+ "inode64",
+ "logbsize=64k",
+ "sunit=128",
+ "swidth=128",
+ "noquota"
+ ],
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123",
+ "mounts": [
+ "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8"
+ ]
+ },
+ "shm": {
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "size=65536k"
+ ],
+ "mounts": [
+ "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm"
+ ]
+ },
+ "nsfs": {
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ],
+ "mounts": [
+ "/run/docker/netns/1ce89fd79f3d"
+ ]
+ },
+ "tracefs": {
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "mounts": [
+ "/sys/kernel/debug/tracing"
+ ]
+ },
+ "/dev/loop1": {
+ "fs_type": "xfs",
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123",
+ "mounts": [
+
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-pool": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sr0": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/loop2": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sda": {
+ "mounts": [
+
+ ]
+ },
+ "/dev/sda2": {
+ "fs_type": "LVM2_member",
+ "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK",
+ "mounts": [
+
+ ]
+ },
+ "/dev/mapper/fedora_host--186-swap": {
+ "fs_type": "swap",
+ "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d",
+ "mounts": [
+
+ ]
+ }
+ },
+ "by_mountpoint": {
+ "/dev": {
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ],
+ "devices": [
+ "devtmpfs"
+ ]
+ },
+ "/dev/shm": {
+ "kb_size": "8055268",
+ "kb_used": "96036",
+ "kb_available": "7959232",
+ "percent_used": "2%",
+ "total_inodes": "2013817",
+ "inodes_used": "217",
+ "inodes_available": "2013600",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/run": {
+ "kb_size": "8055268",
+ "kb_used": "2280",
+ "kb_available": "8052988",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "1070",
+ "inodes_available": "2012747",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel",
+ "mode=755"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/sys/fs/cgroup": {
+ "kb_size": "8055268",
+ "kb_used": "0",
+ "kb_available": "8055268",
+ "percent_used": "0%",
+ "total_inodes": "2013817",
+ "inodes_used": "16",
+ "inodes_available": "2013801",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "ro",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "seclabel",
+ "mode=755"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/": {
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce",
+ "devices": [
+ "/dev/mapper/fedora_host--186-root"
+ ]
+ },
+ "/tmp": {
+ "kb_size": "8055268",
+ "kb_used": "848396",
+ "kb_available": "7206872",
+ "percent_used": "11%",
+ "total_inodes": "2013817",
+ "inodes_used": "1353",
+ "inodes_available": "2012464",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/boot": {
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0",
+ "devices": [
+ "/dev/sda1"
+ ]
+ },
+ "/home": {
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d",
+ "devices": [
+ "/dev/mapper/fedora_host--186-home"
+ ]
+ },
+ "/var/lib/machines": {
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390",
+ "devices": [
+ "/dev/loop0",
+ "/var/lib/machines.raw"
+ ],
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ]
+ },
+ "/run/user/0": {
+ "kb_size": "1611052",
+ "kb_used": "0",
+ "kb_available": "1611052",
+ "percent_used": "0%",
+ "total_inodes": "2013817",
+ "inodes_used": "7",
+ "inodes_available": "2013810",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/run/user/1000": {
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ],
+ "devices": [
+ "tmpfs"
+ ]
+ },
+ "/sys": {
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "sysfs"
+ ]
+ },
+ "/proc": {
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "devices": [
+ "proc"
+ ]
+ },
+ "/sys/kernel/security": {
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ],
+ "devices": [
+ "securityfs"
+ ]
+ },
+ "/dev/pts": {
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ],
+ "devices": [
+ "devpts"
+ ]
+ },
+ "/sys/fs/cgroup/systemd": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "xattr",
+ "release_agent=/usr/lib/systemd/systemd-cgroups-agent",
+ "name=systemd"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/pstore": {
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "pstore"
+ ]
+ },
+ "/sys/fs/cgroup/devices": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "devices"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/cpuset": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpuset"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/perf_event": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "perf_event"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/hugetlb": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "hugetlb"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/cpu,cpuacct": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpu",
+ "cpuacct"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/blkio": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "blkio"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/freezer": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "freezer"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/memory": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "memory"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/pids": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "pids"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/fs/cgroup/net_cls,net_prio": {
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ],
+ "devices": [
+ "cgroup"
+ ]
+ },
+ "/sys/kernel/config": {
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "configfs"
+ ]
+ },
+ "/sys/fs/selinux": {
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "selinuxfs"
+ ]
+ },
+ "/sys/kernel/debug": {
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "debugfs"
+ ]
+ },
+ "/dev/hugepages": {
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "hugetlbfs"
+ ]
+ },
+ "/dev/mqueue": {
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ],
+ "devices": [
+ "mqueue"
+ ]
+ },
+ "/proc/sys/fs/binfmt_misc": {
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "systemd-1",
+ "binfmt_misc"
+ ]
+ },
+ "/sys/fs/fuse/connections": {
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "fusectl"
+ ]
+ },
+ "/run/user/1000/gvfs": {
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ],
+ "devices": [
+ "gvfsd-fuse"
+ ]
+ },
+ "/var/lib/docker/devicemapper": {
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce",
+ "devices": [
+ "/dev/mapper/fedora_host--186-root"
+ ]
+ },
+ {
+ "/run/docker/netns/1ce89fd79f3d": {
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ],
+ "devices": [
+ "nsfs"
+ ]
+ },
+ "/sys/kernel/debug/tracing": {
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ],
+ "devices": [
+ "tracefs"
+ ]
+ }
+ },
+ "by_pair": {
+ "devtmpfs,/dev": {
+ "device": "devtmpfs",
+ "kb_size": "8044124",
+ "kb_used": "0",
+ "kb_available": "8044124",
+ "percent_used": "0%",
+ "mount": "/dev",
+ "total_inodes": "2011031",
+ "inodes_used": "629",
+ "inodes_available": "2010402",
+ "inodes_percent_used": "1%",
+ "fs_type": "devtmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "seclabel",
+ "size=8044124k",
+ "nr_inodes=2011031",
+ "mode=755"
+ ]
+ },
+ "tmpfs,/dev/shm": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "96036",
+ "kb_available": "7959232",
+ "percent_used": "2%",
+ "mount": "/dev/shm",
+ "total_inodes": "2013817",
+ "inodes_used": "217",
+ "inodes_available": "2013600",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ]
+ },
+ "tmpfs,/run": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "2280",
+ "kb_available": "8052988",
+ "percent_used": "1%",
+ "mount": "/run",
+ "total_inodes": "2013817",
+ "inodes_used": "1070",
+ "inodes_available": "2012747",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel",
+ "mode=755"
+ ]
+ },
+ "tmpfs,/sys/fs/cgroup": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "0",
+ "kb_available": "8055268",
+ "percent_used": "0%",
+ "mount": "/sys/fs/cgroup",
+ "total_inodes": "2013817",
+ "inodes_used": "16",
+ "inodes_available": "2013801",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "ro",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "seclabel",
+ "mode=755"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root,/": {
+ "device": "/dev/mapper/fedora_host--186-root",
+ "kb_size": "51475068",
+ "kb_used": "42551284",
+ "kb_available": "6285960",
+ "percent_used": "88%",
+ "mount": "/",
+ "total_inodes": "3276800",
+ "inodes_used": "532908",
+ "inodes_available": "2743892",
+ "inodes_percent_used": "17%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce"
+ },
+ "tmpfs,/tmp": {
+ "device": "tmpfs",
+ "kb_size": "8055268",
+ "kb_used": "848396",
+ "kb_available": "7206872",
+ "percent_used": "11%",
+ "mount": "/tmp",
+ "total_inodes": "2013817",
+ "inodes_used": "1353",
+ "inodes_available": "2012464",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "seclabel"
+ ]
+ },
+ "/dev/sda1,/boot": {
+ "device": "/dev/sda1",
+ "kb_size": "487652",
+ "kb_used": "126628",
+ "kb_available": "331328",
+ "percent_used": "28%",
+ "mount": "/boot",
+ "total_inodes": "128016",
+ "inodes_used": "405",
+ "inodes_available": "127611",
+ "inodes_percent_used": "1%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "32caaec3-ef40-4691-a3b6-438c3f9bc1c0"
+ },
+ "/dev/mapper/fedora_host--186-home,/home": {
+ "device": "/dev/mapper/fedora_host--186-home",
+ "kb_size": "185948124",
+ "kb_used": "105904724",
+ "kb_available": "70574680",
+ "percent_used": "61%",
+ "mount": "/home",
+ "total_inodes": "11821056",
+ "inodes_used": "1266687",
+ "inodes_available": "10554369",
+ "inodes_percent_used": "11%",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d"
+ },
+ "/dev/loop0,/var/lib/machines": {
+ "device": "/dev/loop0",
+ "kb_size": "512000",
+ "kb_used": "16672",
+ "kb_available": "429056",
+ "percent_used": "4%",
+ "mount": "/var/lib/machines",
+ "fs_type": "btrfs",
+ "uuid": "0f031512-ab15-497d-9abd-3a512b4a9390"
+ },
+ "tmpfs,/run/user/0": {
+ "device": "tmpfs",
+ "kb_size": "1611052",
+ "kb_used": "0",
+ "kb_available": "1611052",
+ "percent_used": "0%",
+ "mount": "/run/user/0",
+ "total_inodes": "2013817",
+ "inodes_used": "7",
+ "inodes_available": "2013810",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700"
+ ]
+ },
+ "tmpfs,/run/user/1000": {
+ "device": "tmpfs",
+ "kb_size": "1611052",
+ "kb_used": "72",
+ "kb_available": "1610980",
+ "percent_used": "1%",
+ "mount": "/run/user/1000",
+ "total_inodes": "2013817",
+ "inodes_used": "36",
+ "inodes_available": "2013781",
+ "inodes_percent_used": "1%",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "seclabel",
+ "size=1611052k",
+ "mode=700",
+ "uid=1000",
+ "gid=1000"
+ ]
+ },
+ "sysfs,/sys": {
+ "device": "sysfs",
+ "mount": "/sys",
+ "fs_type": "sysfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "proc,/proc": {
+ "device": "proc",
+ "mount": "/proc",
+ "fs_type": "proc",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ]
+ },
+ "securityfs,/sys/kernel/security": {
+ "device": "securityfs",
+ "mount": "/sys/kernel/security",
+ "fs_type": "securityfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime"
+ ]
+ },
+ "devpts,/dev/pts": {
+ "device": "devpts",
+ "mount": "/dev/pts",
+ "fs_type": "devpts",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "noexec",
+ "relatime",
+ "seclabel",
+ "gid=5",
+ "mode=620",
+ "ptmxmode=000"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/systemd": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/systemd",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "xattr",
+ "release_agent=/usr/lib/systemd/systemd-cgroups-agent",
+ "name=systemd"
+ ]
+ },
+ "pstore,/sys/fs/pstore": {
+ "device": "pstore",
+ "mount": "/sys/fs/pstore",
+ "fs_type": "pstore",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/devices": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/devices",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "devices"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/cpuset": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/cpuset",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpuset"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/perf_event": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/perf_event",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "perf_event"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/hugetlb": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/hugetlb",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "hugetlb"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/cpu,cpuacct": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/cpu,cpuacct",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "cpu",
+ "cpuacct"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/blkio": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/blkio",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "blkio"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/freezer": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/freezer",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "freezer"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/memory": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/memory",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "memory"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/pids": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/pids",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "pids"
+ ]
+ },
+ "cgroup,/sys/fs/cgroup/net_cls,net_prio": {
+ "device": "cgroup",
+ "mount": "/sys/fs/cgroup/net_cls,net_prio",
+ "fs_type": "cgroup",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "net_cls",
+ "net_prio"
+ ]
+ },
+ "configfs,/sys/kernel/config": {
+ "device": "configfs",
+ "mount": "/sys/kernel/config",
+ "fs_type": "configfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "selinuxfs,/sys/fs/selinux": {
+ "device": "selinuxfs",
+ "mount": "/sys/fs/selinux",
+ "fs_type": "selinuxfs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "debugfs,/sys/kernel/debug": {
+ "device": "debugfs",
+ "mount": "/sys/kernel/debug",
+ "fs_type": "debugfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "hugetlbfs,/dev/hugepages": {
+ "device": "hugetlbfs",
+ "mount": "/dev/hugepages",
+ "fs_type": "hugetlbfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "mqueue,/dev/mqueue": {
+ "device": "mqueue",
+ "mount": "/dev/mqueue",
+ "fs_type": "mqueue",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel"
+ ]
+ },
+ "systemd-1,/proc/sys/fs/binfmt_misc": {
+ "device": "systemd-1",
+ "mount": "/proc/sys/fs/binfmt_misc",
+ "fs_type": "autofs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "fd=40",
+ "pgrp=1",
+ "timeout=0",
+ "minproto=5",
+ "maxproto=5",
+ "direct",
+ "pipe_ino=17610"
+ ]
+ },
+ "/var/lib/machines.raw,/var/lib/machines": {
+ "device": "/var/lib/machines.raw",
+ "mount": "/var/lib/machines",
+ "fs_type": "btrfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "space_cache",
+ "subvolid=5",
+ "subvol=/"
+ ]
+ },
+ "fusectl,/sys/fs/fuse/connections": {
+ "device": "fusectl",
+ "mount": "/sys/fs/fuse/connections",
+ "fs_type": "fusectl",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "gvfsd-fuse,/run/user/1000/gvfs": {
+ "device": "gvfsd-fuse",
+ "mount": "/run/user/1000/gvfs",
+ "fs_type": "fuse.gvfsd-fuse",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "relatime",
+ "user_id=1000",
+ "group_id=1000"
+ ]
+ },
+ "/dev/mapper/fedora_host--186-root,/var/lib/docker/devicemapper": {
+ "device": "/dev/mapper/fedora_host--186-root",
+ "mount": "/var/lib/docker/devicemapper",
+ "fs_type": "ext4",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "seclabel",
+ "data=ordered"
+ ],
+ "uuid": "d34cf5e3-3449-4a6c-8179-a1feb2bca6ce"
+ },
+ "binfmt_misc,/proc/sys/fs/binfmt_misc": {
+ "device": "binfmt_misc",
+ "mount": "/proc/sys/fs/binfmt_misc",
+ "fs_type": "binfmt_misc",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8,/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8": {
+ "device": "/dev/mapper/docker-253:1-1180487-0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8",
+ "mount": "/var/lib/docker/devicemapper/mnt/0868fce108cd2524a4823aad8d665cca018ead39550ca088c440ab05deec13f8",
+ "fs_type": "xfs",
+ "mount_options": [
+ "rw",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "nouuid",
+ "attr2",
+ "inode64",
+ "logbsize=64k",
+ "sunit=128",
+ "swidth=128",
+ "noquota"
+ ],
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123"
+ },
+ "shm,/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm": {
+ "device": "shm",
+ "mount": "/var/lib/docker/containers/426e513ed508a451e3f70440eed040761f81529e4bc4240e7522d331f3f3bc12/shm",
+ "fs_type": "tmpfs",
+ "mount_options": [
+ "rw",
+ "nosuid",
+ "nodev",
+ "noexec",
+ "relatime",
+ "context=\"system_u:object_r:container_file_t:s0:c523",
+ "c681\"",
+ "size=65536k"
+ ]
+ },
+ "nsfs,/run/docker/netns/1ce89fd79f3d": {
+ "device": "nsfs",
+ "mount": "/run/docker/netns/1ce89fd79f3d",
+ "fs_type": "nsfs",
+ "mount_options": [
+ "rw"
+ ]
+ },
+ "tracefs,/sys/kernel/debug/tracing": {
+ "device": "tracefs",
+ "mount": "/sys/kernel/debug/tracing",
+ "fs_type": "tracefs",
+ "mount_options": [
+ "rw",
+ "relatime"
+ ]
+ },
+ "/dev/loop1,": {
+ "device": "/dev/loop1",
+ "fs_type": "xfs",
+ "uuid": "00e2aa25-20d8-4ad7-b3a5-c501f2f4c123"
+ },
+ "/dev/mapper/docker-253:1-1180487-pool,": {
+ "device": "/dev/mapper/docker-253:1-1180487-pool"
+ },
+ "/dev/sr0,": {
+ "device": "/dev/sr0"
+ },
+ "/dev/loop2,": {
+ "device": "/dev/loop2"
+ },
+ "/dev/sda,": {
+ "device": "/dev/sda"
+ },
+ "/dev/sda2,": {
+ "device": "/dev/sda2",
+ "fs_type": "LVM2_member",
+ "uuid": "66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK"
+ },
+ "/dev/mapper/fedora_host--186-swap,": {
+ "device": "/dev/mapper/fedora_host--186-swap",
+ "fs_type": "swap",
+ "uuid": "eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d"
+ }
+ }
+ },
+ "virtualization": {
+ "systems": {
+ "kvm": "host"
+ },
+ "system": "kvm",
+ "role": "host",
+ "libvirt_version": "2.2.0",
+ "uri": "qemu:///system",
+ "capabilities": {
+
+ },
+ "nodeinfo": {
+ "cores": 4,
+ "cpus": 8,
+ "memory": 16110540,
+ "mhz": 2832,
+ "model": "x86_64",
+ "nodes": 1,
+ "sockets": 1,
+ "threads": 2
+ },
+ "domains": {
+
+ },
+ "networks": {
+ "vagrant-libvirt": {
+ "bridge_name": "virbr1",
+ "uuid": "877ddb27-b39c-427e-a7bf-1aa829389eeb"
+ },
+ "default": {
+ "bridge_name": "virbr0",
+ "uuid": "750d2567-23a8-470d-8a2b-71cd651e30d1"
+ }
+ },
+ "storage": {
+ "virt-images": {
+ "autostart": true,
+ "uuid": "d8a189fa-f98c-462f-9ea4-204eb77a96a1",
+ "allocation": 106412863488,
+ "available": 83998015488,
+ "capacity": 190410878976,
+ "state": 2,
+ "volumes": {
+ "rhel-atomic-host-standard-2014-7-1.qcow2": {
+ "key": "/home/some_user/virt-images/rhel-atomic-host-standard-2014-7-1.qcow2",
+ "name": "rhel-atomic-host-standard-2014-7-1.qcow2",
+ "path": "/home/some_user/virt-images/rhel-atomic-host-standard-2014-7-1.qcow2",
+ "allocation": 1087115264,
+ "capacity": 8589934592,
+ "type": 0
+ },
+ "atomic-beta-instance-7.qcow2": {
+ "key": "/home/some_user/virt-images/atomic-beta-instance-7.qcow2",
+ "name": "atomic-beta-instance-7.qcow2",
+ "path": "/home/some_user/virt-images/atomic-beta-instance-7.qcow2",
+ "allocation": 200704,
+ "capacity": 8589934592,
+ "type": 0
+ },
+ "os1-atomic-meta-data": {
+ "key": "/home/some_user/virt-images/os1-atomic-meta-data",
+ "name": "os1-atomic-meta-data",
+ "path": "/home/some_user/virt-images/os1-atomic-meta-data",
+ "allocation": 4096,
+ "capacity": 49,
+ "type": 0
+ },
+ "atomic-user-data": {
+ "key": "/home/some_user/virt-images/atomic-user-data",
+ "name": "atomic-user-data",
+ "path": "/home/some_user/virt-images/atomic-user-data",
+ "allocation": 4096,
+ "capacity": 512,
+ "type": 0
+ },
+ "qemu-snap.txt": {
+ "key": "/home/some_user/virt-images/qemu-snap.txt",
+ "name": "qemu-snap.txt",
+ "path": "/home/some_user/virt-images/qemu-snap.txt",
+ "allocation": 4096,
+ "capacity": 111,
+ "type": 0
+ },
+ "atomic-beta-instance-5.qcow2": {
+ "key": "/home/some_user/virt-images/atomic-beta-instance-5.qcow2",
+ "name": "atomic-beta-instance-5.qcow2",
+ "path": "/home/some_user/virt-images/atomic-beta-instance-5.qcow2",
+ "allocation": 339091456,
+ "capacity": 8589934592,
+ "type": 0
+ },
+ "meta-data": {
+ "key": "/home/some_user/virt-images/meta-data",
+ "name": "meta-data",
+ "path": "/home/some_user/virt-images/meta-data",
+ "allocation": 4096,
+ "capacity": 49,
+ "type": 0
+ },
+ "atomic-beta-instance-8.qcow2": {
+ "key": "/home/some_user/virt-images/atomic-beta-instance-8.qcow2",
+ "name": "atomic-beta-instance-8.qcow2",
+ "path": "/home/some_user/virt-images/atomic-beta-instance-8.qcow2",
+ "allocation": 322576384,
+ "capacity": 8589934592,
+ "type": 0
+ },
+ "user-data": {
+ "key": "/home/some_user/virt-images/user-data",
+ "name": "user-data",
+ "path": "/home/some_user/virt-images/user-data",
+ "allocation": 4096,
+ "capacity": 512,
+ "type": 0
+ },
+ "rhel-6-2015-10-16.qcow2": {
+ "key": "/home/some_user/virt-images/rhel-6-2015-10-16.qcow2",
+ "name": "rhel-6-2015-10-16.qcow2",
+ "path": "/home/some_user/virt-images/rhel-6-2015-10-16.qcow2",
+ "allocation": 7209422848,
+ "capacity": 17179869184,
+ "type": 0
+ },
+ "atomic_demo_notes.txt": {
+ "key": "/home/some_user/virt-images/atomic_demo_notes.txt",
+ "name": "atomic_demo_notes.txt",
+ "path": "/home/some_user/virt-images/atomic_demo_notes.txt",
+ "allocation": 4096,
+ "capacity": 354,
+ "type": 0
+ },
+ "packer-windows-2012-R2-standard": {
+ "key": "/home/some_user/virt-images/packer-windows-2012-R2-standard",
+ "name": "packer-windows-2012-R2-standard",
+ "path": "/home/some_user/virt-images/packer-windows-2012-R2-standard",
+ "allocation": 16761495552,
+ "capacity": 64424509440,
+ "type": 0
+ },
+ "atomic3-cidata.iso": {
+ "key": "/home/some_user/virt-images/atomic3-cidata.iso",
+ "name": "atomic3-cidata.iso",
+ "path": "/home/some_user/virt-images/atomic3-cidata.iso",
+ "allocation": 376832,
+ "capacity": 374784,
+ "type": 0
+ },
+ ".atomic_demo_notes.txt.swp": {
+ "key": "/home/some_user/virt-images/.atomic_demo_notes.txt.swp",
+ "name": ".atomic_demo_notes.txt.swp",
+ "path": "/home/some_user/virt-images/.atomic_demo_notes.txt.swp",
+ "allocation": 12288,
+ "capacity": 12288,
+ "type": 0
+ },
+ "rhel7-2015-10-13.qcow2": {
+ "key": "/home/some_user/virt-images/rhel7-2015-10-13.qcow2",
+ "name": "rhel7-2015-10-13.qcow2",
+ "path": "/home/some_user/virt-images/rhel7-2015-10-13.qcow2",
+ "allocation": 4679413760,
+ "capacity": 12884901888,
+ "type": 0
+ }
+ }
+ },
+ "default": {
+ "autostart": true,
+ "uuid": "c8d9d160-efc0-4207-81c2-e79d6628f7e1",
+ "allocation": 43745488896,
+ "available": 8964980736,
+ "capacity": 52710469632,
+ "state": 2,
+ "volumes": {
+ "s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img": {
+ "key": "/var/lib/libvirt/images/s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img",
+ "name": "s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img",
+ "path": "/var/lib/libvirt/images/s3than-VAGRANTSLASH-trusty64_vagrant_box_image_0.0.1.img",
+ "allocation": 1258622976,
+ "capacity": 42949672960,
+ "type": 0
+ },
+ "centos-7.0_vagrant_box_image.img": {
+ "key": "/var/lib/libvirt/images/centos-7.0_vagrant_box_image.img",
+ "name": "centos-7.0_vagrant_box_image.img",
+ "path": "/var/lib/libvirt/images/centos-7.0_vagrant_box_image.img",
+ "allocation": 1649414144,
+ "capacity": 42949672960,
+ "type": 0
+ },
+ "baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img": {
+ "key": "/var/lib/libvirt/images/baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img",
+ "name": "baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img",
+ "path": "/var/lib/libvirt/images/baremettle-VAGRANTSLASH-centos-5.10_vagrant_box_image_1.0.0.img",
+ "allocation": 810422272,
+ "capacity": 42949672960,
+ "type": 0
+ },
+ "centos-6_vagrant_box_image.img": {
+ "key": "/var/lib/libvirt/images/centos-6_vagrant_box_image.img",
+ "name": "centos-6_vagrant_box_image.img",
+ "path": "/var/lib/libvirt/images/centos-6_vagrant_box_image.img",
+ "allocation": 1423642624,
+ "capacity": 42949672960,
+ "type": 0
+ },
+ "centos5-ansible_default.img": {
+ "key": "/var/lib/libvirt/images/centos5-ansible_default.img",
+ "name": "centos5-ansible_default.img",
+ "path": "/var/lib/libvirt/images/centos5-ansible_default.img",
+ "allocation": 8986624,
+ "capacity": 42949672960,
+ "type": 0
+ },
+ "ubuntu_default.img": {
+ "key": "/var/lib/libvirt/images/ubuntu_default.img",
+ "name": "ubuntu_default.img",
+ "path": "/var/lib/libvirt/images/ubuntu_default.img",
+ "allocation": 3446833152,
+ "capacity": 42949672960,
+ "type": 0
+ }
+ }
+ },
+ "boot-scratch": {
+ "autostart": true,
+ "uuid": "e5ef4360-b889-4843-84fb-366e8fb30f20",
+ "allocation": 43745488896,
+ "available": 8964980736,
+ "capacity": 52710469632,
+ "state": 2,
+ "volumes": {
+
+ }
+ }
+ }
+ },
+ "network": {
+ "interfaces": {
+ "lo": {
+ "mtu": "65536",
+ "flags": [
+ "LOOPBACK",
+ "UP",
+ "LOWER_UP"
+ ],
+ "encapsulation": "Loopback",
+ "addresses": {
+ "127.0.0.1": {
+ "family": "inet",
+ "prefixlen": "8",
+ "netmask": "255.0.0.0",
+ "scope": "Node",
+ "ip_scope": "LOOPBACK"
+ },
+ "::1": {
+ "family": "inet6",
+ "prefixlen": "128",
+ "scope": "Node",
+ "tags": [
+
+ ],
+ "ip_scope": "LINK LOCAL LOOPBACK"
+ }
+ },
+ "state": "unknown"
+ },
+ "em1": {
+ "type": "em",
+ "number": "1",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "3C:97:0E:E9:28:8E": {
+ "family": "lladdr"
+ }
+ },
+ "state": "down",
+ "link_speed": 0,
+ "duplex": "Unknown! (255)",
+ "port": "Twisted Pair",
+ "transceiver": "internal",
+ "auto_negotiation": "on",
+ "mdi_x": "Unknown (auto)",
+ "ring_params": {
+ "max_rx": 4096,
+ "max_rx_mini": 0,
+ "max_rx_jumbo": 0,
+ "max_tx": 4096,
+ "current_rx": 256,
+ "current_rx_mini": 0,
+ "current_rx_jumbo": 0,
+ "current_tx": 256
+ }
+ },
+ "wlp4s0": {
+ "type": "wlp4s",
+ "number": "0",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP",
+ "LOWER_UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "5C:51:4F:E6:A8:E3": {
+ "family": "lladdr"
+ },
+ "192.168.1.19": {
+ "family": "inet",
+ "prefixlen": "24",
+ "netmask": "255.255.255.0",
+ "broadcast": "192.168.1.255",
+ "scope": "Global",
+ "ip_scope": "RFC1918 PRIVATE"
+ },
+ "fe80::5e51:4fff:fee6:a8e3": {
+ "family": "inet6",
+ "prefixlen": "64",
+ "scope": "Link",
+ "tags": [
+
+ ],
+ "ip_scope": "LINK LOCAL UNICAST"
+ }
+ },
+ "state": "up",
+ "arp": {
+ "192.168.1.33": "00:11:d9:39:3e:e0",
+ "192.168.1.20": "ac:3a:7a:a7:49:e8",
+ "192.168.1.17": "00:09:b0:d0:64:19",
+ "192.168.1.22": "ac:bc:32:82:30:bb",
+ "192.168.1.15": "00:11:32:2e:10:d5",
+ "192.168.1.1": "84:1b:5e:03:50:b2",
+ "192.168.1.34": "00:11:d9:5f:e8:e6",
+ "192.168.1.16": "dc:a5:f4:ac:22:3a",
+ "192.168.1.21": "74:c2:46:73:28:d8",
+ "192.168.1.27": "00:17:88:09:3c:bb",
+ "192.168.1.24": "08:62:66:90:a2:b8"
+ },
+ "routes": [
+ {
+ "destination": "default",
+ "family": "inet",
+ "via": "192.168.1.1",
+ "metric": "600",
+ "proto": "static"
+ },
+ {
+ "destination": "66.187.232.64",
+ "family": "inet",
+ "via": "192.168.1.1",
+ "metric": "600",
+ "proto": "static"
+ },
+ {
+ "destination": "192.168.1.0/24",
+ "family": "inet",
+ "scope": "link",
+ "metric": "600",
+ "proto": "kernel",
+ "src": "192.168.1.19"
+ },
+ {
+ "destination": "192.168.1.1",
+ "family": "inet",
+ "scope": "link",
+ "metric": "600",
+ "proto": "static"
+ },
+ {
+ "destination": "fe80::/64",
+ "family": "inet6",
+ "metric": "256",
+ "proto": "kernel"
+ }
+ ],
+ "ring_params": {
+ "max_rx": 0,
+ "max_rx_mini": 0,
+ "max_rx_jumbo": 0,
+ "max_tx": 0,
+ "current_rx": 0,
+ "current_rx_mini": 0,
+ "current_rx_jumbo": 0,
+ "current_tx": 0
+ }
+ },
+ "virbr1": {
+ "type": "virbr",
+ "number": "1",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "52:54:00:B4:68:A9": {
+ "family": "lladdr"
+ },
+ "192.168.121.1": {
+ "family": "inet",
+ "prefixlen": "24",
+ "netmask": "255.255.255.0",
+ "broadcast": "192.168.121.255",
+ "scope": "Global",
+ "ip_scope": "RFC1918 PRIVATE"
+ }
+ },
+ "state": "1",
+ "routes": [
+ {
+ "destination": "192.168.121.0/24",
+ "family": "inet",
+ "scope": "link",
+ "proto": "kernel",
+ "src": "192.168.121.1"
+ }
+ ],
+ "ring_params": {
+
+ }
+ },
+ "virbr1-nic": {
+ "type": "virbr",
+ "number": "1-nic",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "52:54:00:B4:68:A9": {
+ "family": "lladdr"
+ }
+ },
+ "state": "disabled",
+ "link_speed": 10,
+ "duplex": "Full",
+ "port": "Twisted Pair",
+ "transceiver": "internal",
+ "auto_negotiation": "off",
+ "mdi_x": "Unknown",
+ "ring_params": {
+
+ }
+ },
+ "virbr0": {
+ "type": "virbr",
+ "number": "0",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "52:54:00:CE:82:5E": {
+ "family": "lladdr"
+ },
+ "192.168.137.1": {
+ "family": "inet",
+ "prefixlen": "24",
+ "netmask": "255.255.255.0",
+ "broadcast": "192.168.137.255",
+ "scope": "Global",
+ "ip_scope": "RFC1918 PRIVATE"
+ }
+ },
+ "state": "1",
+ "routes": [
+ {
+ "destination": "192.168.137.0/24",
+ "family": "inet",
+ "scope": "link",
+ "proto": "kernel",
+ "src": "192.168.137.1"
+ }
+ ],
+ "ring_params": {
+
+ }
+ },
+ "virbr0-nic": {
+ "type": "virbr",
+ "number": "0-nic",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "52:54:00:CE:82:5E": {
+ "family": "lladdr"
+ }
+ },
+ "state": "disabled",
+ "link_speed": 10,
+ "duplex": "Full",
+ "port": "Twisted Pair",
+ "transceiver": "internal",
+ "auto_negotiation": "off",
+ "mdi_x": "Unknown",
+ "ring_params": {
+
+ }
+ },
+ "docker0": {
+ "type": "docker",
+ "number": "0",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP",
+ "LOWER_UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "02:42:EA:15:D8:84": {
+ "family": "lladdr"
+ },
+ "172.17.0.1": {
+ "family": "inet",
+ "prefixlen": "16",
+ "netmask": "255.255.0.0",
+ "scope": "Global",
+ "ip_scope": "RFC1918 PRIVATE"
+ },
+ "fe80::42:eaff:fe15:d884": {
+ "family": "inet6",
+ "prefixlen": "64",
+ "scope": "Link",
+ "tags": [
+
+ ],
+ "ip_scope": "LINK LOCAL UNICAST"
+ }
+ },
+ "state": "0",
+ "arp": {
+ "172.17.0.2": "02:42:ac:11:00:02",
+ "172.17.0.4": "02:42:ac:11:00:04",
+ "172.17.0.3": "02:42:ac:11:00:03"
+ },
+ "routes": [
+ {
+ "destination": "172.17.0.0/16",
+ "family": "inet",
+ "scope": "link",
+ "proto": "kernel",
+ "src": "172.17.0.1"
+ },
+ {
+ "destination": "fe80::/64",
+ "family": "inet6",
+ "metric": "256",
+ "proto": "kernel"
+ }
+ ],
+ "ring_params": {
+
+ }
+ },
+ "vethf20ff12": {
+ "type": "vethf20ff1",
+ "number": "2",
+ "mtu": "1500",
+ "flags": [
+ "BROADCAST",
+ "MULTICAST",
+ "UP",
+ "LOWER_UP"
+ ],
+ "encapsulation": "Ethernet",
+ "addresses": {
+ "AE:6E:2B:1E:A1:31": {
+ "family": "lladdr"
+ },
+ "fe80::ac6e:2bff:fe1e:a131": {
+ "family": "inet6",
+ "prefixlen": "64",
+ "scope": "Link",
+ "tags": [
+
+ ],
+ "ip_scope": "LINK LOCAL UNICAST"
+ }
+ },
+ "state": "forwarding",
+ "routes": [
+ {
+ "destination": "fe80::/64",
+ "family": "inet6",
+ "metric": "256",
+ "proto": "kernel"
+ }
+ ],
+ "link_speed": 10000,
+ "duplex": "Full",
+ "port": "Twisted Pair",
+ "transceiver": "internal",
+ "auto_negotiation": "off",
+ "mdi_x": "Unknown",
+ "ring_params": {
+
+ }
+ },
+ "tun0": {
+ "type": "tun",
+ "number": "0",
+ "mtu": "1360",
+ "flags": [
+ "MULTICAST",
+ "NOARP",
+ "UP",
+ "LOWER_UP"
+ ],
+ "addresses": {
+ "10.10.120.68": {
+ "family": "inet",
+ "prefixlen": "21",
+ "netmask": "255.255.248.0",
+ "broadcast": "10.10.127.255",
+ "scope": "Global",
+ "ip_scope": "RFC1918 PRIVATE"
+ },
+ "fe80::365e:885c:31ca:7670": {
+ "family": "inet6",
+ "prefixlen": "64",
+ "scope": "Link",
+ "tags": [
+ "flags",
+ "800"
+ ],
+ "ip_scope": "LINK LOCAL UNICAST"
+ }
+ },
+ "state": "unknown",
+ "routes": [
+ {
+ "destination": "10.0.0.0/8",
+ "family": "inet",
+ "via": "10.10.120.1",
+ "metric": "50",
+ "proto": "static"
+ },
+ {
+ "destination": "10.10.120.0/21",
+ "family": "inet",
+ "scope": "link",
+ "metric": "50",
+ "proto": "kernel",
+ "src": "10.10.120.68"
+ },
+ {
+ "destination": "fe80::/64",
+ "family": "inet6",
+ "metric": "256",
+ "proto": "kernel"
+ }
+ ]
+ }
+ },
+ "default_interface": "wlp4s0",
+ "default_gateway": "192.168.1.1"
+ },
+ "counters": {
+ "network": {
+ "interfaces": {
+ "lo": {
+ "tx": {
+ "queuelen": "1",
+ "bytes": "202568405",
+ "packets": "1845473",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "202568405",
+ "packets": "1845473",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "em1": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "673898037",
+ "packets": "1631282",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "1536186718",
+ "packets": "1994394",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "wlp4s0": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "3927670539",
+ "packets": "15146886",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "12367173401",
+ "packets": "23981258",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "virbr1": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "virbr1-nic": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "virbr0": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "virbr0-nic": {
+ "tx": {
+ "queuelen": "1000",
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "0",
+ "packets": "0",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ },
+ "docker0": {
+ "rx": {
+ "bytes": "2471313",
+ "packets": "36915",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ },
+ "tx": {
+ "bytes": "413371670",
+ "packets": "127713",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ }
+ },
+ "vethf20ff12": {
+ "rx": {
+ "bytes": "34391",
+ "packets": "450",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ },
+ "tx": {
+ "bytes": "17919115",
+ "packets": "108069",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ }
+ },
+ "tun0": {
+ "tx": {
+ "queuelen": "100",
+ "bytes": "22343462",
+ "packets": "253442",
+ "errors": "0",
+ "drop": "0",
+ "carrier": "0",
+ "collisions": "0"
+ },
+ "rx": {
+ "bytes": "115160002",
+ "packets": "197529",
+ "errors": "0",
+ "drop": "0",
+ "overrun": "0"
+ }
+ }
+ }
+ }
+ },
+ "ipaddress": "192.168.1.19",
+ "macaddress": "5C:51:4F:E6:A8:E3",
+ "ip6address": "fe80::42:eaff:fe15:d884",
+ "cpu": {
+ "0": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "3238.714",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "0",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "1": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "3137.200",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "0",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "2": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "3077.050",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "1",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "3": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "2759.655",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "1",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "4": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "3419.000",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "2",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "5": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "2752.569",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "2",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "6": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "2953.619",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "3",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "7": {
+ "vendor_id": "GenuineIntel",
+ "family": "6",
+ "model": "60",
+ "model_name": "Intel(R) Core(TM) i7-4800MQ CPU @ 2.70GHz",
+ "stepping": "3",
+ "mhz": "2927.087",
+ "cache_size": "6144 KB",
+ "physical_id": "0",
+ "core_id": "3",
+ "cores": "4",
+ "flags": [
+ "fpu",
+ "vme",
+ "de",
+ "pse",
+ "tsc",
+ "msr",
+ "pae",
+ "mce",
+ "cx8",
+ "apic",
+ "sep",
+ "mtrr",
+ "pge",
+ "mca",
+ "cmov",
+ "pat",
+ "pse36",
+ "clflush",
+ "dts",
+ "acpi",
+ "mmx",
+ "fxsr",
+ "sse",
+ "sse2",
+ "ss",
+ "ht",
+ "tm",
+ "pbe",
+ "syscall",
+ "nx",
+ "pdpe1gb",
+ "rdtscp",
+ "lm",
+ "constant_tsc",
+ "arch_perfmon",
+ "pebs",
+ "bts",
+ "rep_good",
+ "nopl",
+ "xtopology",
+ "nonstop_tsc",
+ "aperfmperf",
+ "eagerfpu",
+ "pni",
+ "pclmulqdq",
+ "dtes64",
+ "monitor",
+ "ds_cpl",
+ "vmx",
+ "smx",
+ "est",
+ "tm2",
+ "ssse3",
+ "sdbg",
+ "fma",
+ "cx16",
+ "xtpr",
+ "pdcm",
+ "pcid",
+ "sse4_1",
+ "sse4_2",
+ "x2apic",
+ "movbe",
+ "popcnt",
+ "tsc_deadline_timer",
+ "aes",
+ "xsave",
+ "avx",
+ "f16c",
+ "rdrand",
+ "lahf_lm",
+ "abm",
+ "epb",
+ "tpr_shadow",
+ "vnmi",
+ "flexpriority",
+ "ept",
+ "vpid",
+ "fsgsbase",
+ "tsc_adjust",
+ "bmi1",
+ "avx2",
+ "smep",
+ "bmi2",
+ "erms",
+ "invpcid",
+ "xsaveopt",
+ "dtherm",
+ "ida",
+ "arat",
+ "pln",
+ "pts"
+ ]
+ },
+ "total": 8,
+ "real": 1,
+ "cores": 4
+ },
+ "etc": {
+ "passwd": {
+ "root": {
+ "dir": "/root",
+ "gid": 0,
+ "uid": 0,
+ "shell": "/bin/bash",
+ "gecos": "root"
+ },
+ "bin": {
+ "dir": "/bin",
+ "gid": 1,
+ "uid": 1,
+ "shell": "/sbin/nologin",
+ "gecos": "bin"
+ },
+ "daemon": {
+ "dir": "/sbin",
+ "gid": 2,
+ "uid": 2,
+ "shell": "/sbin/nologin",
+ "gecos": "daemon"
+ },
+ "adm": {
+ "dir": "/var/adm",
+ "gid": 4,
+ "uid": 3,
+ "shell": "/sbin/nologin",
+ "gecos": "adm"
+ },
+ "lp": {
+ "dir": "/var/spool/lpd",
+ "gid": 7,
+ "uid": 4,
+ "shell": "/sbin/nologin",
+ "gecos": "lp"
+ },
+ "sync": {
+ "dir": "/sbin",
+ "gid": 0,
+ "uid": 5,
+ "shell": "/bin/sync",
+ "gecos": "sync"
+ },
+ "shutdown": {
+ "dir": "/sbin",
+ "gid": 0,
+ "uid": 6,
+ "shell": "/sbin/shutdown",
+ "gecos": "shutdown"
+ },
+ "halt": {
+ "dir": "/sbin",
+ "gid": 0,
+ "uid": 7,
+ "shell": "/sbin/halt",
+ "gecos": "halt"
+ },
+ "mail": {
+ "dir": "/var/spool/mail",
+ "gid": 12,
+ "uid": 8,
+ "shell": "/sbin/nologin",
+ "gecos": "mail"
+ },
+ "operator": {
+ "dir": "/root",
+ "gid": 0,
+ "uid": 11,
+ "shell": "/sbin/nologin",
+ "gecos": "operator"
+ },
+ "games": {
+ "dir": "/usr/games",
+ "gid": 100,
+ "uid": 12,
+ "shell": "/sbin/nologin",
+ "gecos": "games"
+ },
+ "ftp": {
+ "dir": "/var/ftp",
+ "gid": 50,
+ "uid": 14,
+ "shell": "/sbin/nologin",
+ "gecos": "FTP User"
+ },
+ "nobody": {
+ "dir": "/",
+ "gid": 99,
+ "uid": 99,
+ "shell": "/sbin/nologin",
+ "gecos": "Nobody"
+ },
+ "avahi-autoipd": {
+ "dir": "/var/lib/avahi-autoipd",
+ "gid": 170,
+ "uid": 170,
+ "shell": "/sbin/nologin",
+ "gecos": "Avahi IPv4LL Stack"
+ },
+ "dbus": {
+ "dir": "/",
+ "gid": 81,
+ "uid": 81,
+ "shell": "/sbin/nologin",
+ "gecos": "System message bus"
+ },
+ "polkitd": {
+ "dir": "/",
+ "gid": 999,
+ "uid": 999,
+ "shell": "/sbin/nologin",
+ "gecos": "User for polkitd"
+ },
+ "abrt": {
+ "dir": "/etc/abrt",
+ "gid": 173,
+ "uid": 173,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "usbmuxd": {
+ "dir": "/",
+ "gid": 113,
+ "uid": 113,
+ "shell": "/sbin/nologin",
+ "gecos": "usbmuxd user"
+ },
+ "colord": {
+ "dir": "/var/lib/colord",
+ "gid": 998,
+ "uid": 998,
+ "shell": "/sbin/nologin",
+ "gecos": "User for colord"
+ },
+ "geoclue": {
+ "dir": "/var/lib/geoclue",
+ "gid": 997,
+ "uid": 997,
+ "shell": "/sbin/nologin",
+ "gecos": "User for geoclue"
+ },
+ "rpc": {
+ "dir": "/var/lib/rpcbind",
+ "gid": 32,
+ "uid": 32,
+ "shell": "/sbin/nologin",
+ "gecos": "Rpcbind Daemon"
+ },
+ "rpcuser": {
+ "dir": "/var/lib/nfs",
+ "gid": 29,
+ "uid": 29,
+ "shell": "/sbin/nologin",
+ "gecos": "RPC Service User"
+ },
+ "nfsnobody": {
+ "dir": "/var/lib/nfs",
+ "gid": 65534,
+ "uid": 65534,
+ "shell": "/sbin/nologin",
+ "gecos": "Anonymous NFS User"
+ },
+ "qemu": {
+ "dir": "/",
+ "gid": 107,
+ "uid": 107,
+ "shell": "/sbin/nologin",
+ "gecos": "qemu user"
+ },
+ "rtkit": {
+ "dir": "/proc",
+ "gid": 172,
+ "uid": 172,
+ "shell": "/sbin/nologin",
+ "gecos": "RealtimeKit"
+ },
+ "radvd": {
+ "dir": "/",
+ "gid": 75,
+ "uid": 75,
+ "shell": "/sbin/nologin",
+ "gecos": "radvd user"
+ },
+ "tss": {
+ "dir": "/dev/null",
+ "gid": 59,
+ "uid": 59,
+ "shell": "/sbin/nologin",
+ "gecos": "Account used by the trousers package to sandbox the tcsd daemon"
+ },
+ "unbound": {
+ "dir": "/etc/unbound",
+ "gid": 995,
+ "uid": 996,
+ "shell": "/sbin/nologin",
+ "gecos": "Unbound DNS resolver"
+ },
+ "openvpn": {
+ "dir": "/etc/openvpn",
+ "gid": 994,
+ "uid": 995,
+ "shell": "/sbin/nologin",
+ "gecos": "OpenVPN"
+ },
+ "saslauth": {
+ "dir": "/run/saslauthd",
+ "gid": 76,
+ "uid": 994,
+ "shell": "/sbin/nologin",
+ "gecos": "\"Saslauthd user\""
+ },
+ "avahi": {
+ "dir": "/var/run/avahi-daemon",
+ "gid": 70,
+ "uid": 70,
+ "shell": "/sbin/nologin",
+ "gecos": "Avahi mDNS/DNS-SD Stack"
+ },
+ "pulse": {
+ "dir": "/var/run/pulse",
+ "gid": 992,
+ "uid": 993,
+ "shell": "/sbin/nologin",
+ "gecos": "PulseAudio System Daemon"
+ },
+ "gdm": {
+ "dir": "/var/lib/gdm",
+ "gid": 42,
+ "uid": 42,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "gnome-initial-setup": {
+ "dir": "/run/gnome-initial-setup/",
+ "gid": 990,
+ "uid": 992,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "nm-openconnect": {
+ "dir": "/",
+ "gid": 989,
+ "uid": 991,
+ "shell": "/sbin/nologin",
+ "gecos": "NetworkManager user for OpenConnect"
+ },
+ "sshd": {
+ "dir": "/var/empty/sshd",
+ "gid": 74,
+ "uid": 74,
+ "shell": "/sbin/nologin",
+ "gecos": "Privilege-separated SSH"
+ },
+ "chrony": {
+ "dir": "/var/lib/chrony",
+ "gid": 988,
+ "uid": 990,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "tcpdump": {
+ "dir": "/",
+ "gid": 72,
+ "uid": 72,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "some_user": {
+ "dir": "/home/some_user",
+ "gid": 1000,
+ "uid": 1000,
+ "shell": "/bin/bash",
+ "gecos": "some_user"
+ },
+ "systemd-journal-gateway": {
+ "dir": "/var/log/journal",
+ "gid": 191,
+ "uid": 191,
+ "shell": "/sbin/nologin",
+ "gecos": "Journal Gateway"
+ },
+ "postgres": {
+ "dir": "/var/lib/pgsql",
+ "gid": 26,
+ "uid": 26,
+ "shell": "/bin/bash",
+ "gecos": "PostgreSQL Server"
+ },
+ "dockerroot": {
+ "dir": "/var/lib/docker",
+ "gid": 977,
+ "uid": 984,
+ "shell": "/sbin/nologin",
+ "gecos": "Docker User"
+ },
+ "apache": {
+ "dir": "/usr/share/httpd",
+ "gid": 48,
+ "uid": 48,
+ "shell": "/sbin/nologin",
+ "gecos": "Apache"
+ },
+ "systemd-network": {
+ "dir": "/",
+ "gid": 974,
+ "uid": 982,
+ "shell": "/sbin/nologin",
+ "gecos": "systemd Network Management"
+ },
+ "systemd-resolve": {
+ "dir": "/",
+ "gid": 973,
+ "uid": 981,
+ "shell": "/sbin/nologin",
+ "gecos": "systemd Resolver"
+ },
+ "systemd-bus-proxy": {
+ "dir": "/",
+ "gid": 972,
+ "uid": 980,
+ "shell": "/sbin/nologin",
+ "gecos": "systemd Bus Proxy"
+ },
+ "systemd-journal-remote": {
+ "dir": "//var/log/journal/remote",
+ "gid": 970,
+ "uid": 979,
+ "shell": "/sbin/nologin",
+ "gecos": "Journal Remote"
+ },
+ "systemd-journal-upload": {
+ "dir": "//var/log/journal/upload",
+ "gid": 969,
+ "uid": 978,
+ "shell": "/sbin/nologin",
+ "gecos": "Journal Upload"
+ },
+ "setroubleshoot": {
+ "dir": "/var/lib/setroubleshoot",
+ "gid": 967,
+ "uid": 977,
+ "shell": "/sbin/nologin",
+ "gecos": ""
+ },
+ "oprofile": {
+ "dir": "/var/lib/oprofile",
+ "gid": 16,
+ "uid": 16,
+ "shell": "/sbin/nologin",
+ "gecos": "Special user account to be used by OProfile"
+ }
+ },
+ "group": {
+ "root": {
+ "gid": 0,
+ "members": [
+
+ ]
+ },
+ "bin": {
+ "gid": 1,
+ "members": [
+
+ ]
+ },
+ "daemon": {
+ "gid": 2,
+ "members": [
+
+ ]
+ },
+ "sys": {
+ "gid": 3,
+ "members": [
+
+ ]
+ },
+ "adm": {
+ "gid": 4,
+ "members": [
+ "logcheck"
+ ]
+ },
+ "tty": {
+ "gid": 5,
+ "members": [
+
+ ]
+ },
+ "disk": {
+ "gid": 6,
+ "members": [
+
+ ]
+ },
+ "lp": {
+ "gid": 7,
+ "members": [
+
+ ]
+ },
+ "mem": {
+ "gid": 8,
+ "members": [
+
+ ]
+ },
+ "kmem": {
+ "gid": 9,
+ "members": [
+
+ ]
+ },
+ "wheel": {
+ "gid": 10,
+ "members": [
+
+ ]
+ },
+ "cdrom": {
+ "gid": 11,
+ "members": [
+
+ ]
+ },
+ "mail": {
+ "gid": 12,
+ "members": [
+
+ ]
+ },
+ "man": {
+ "gid": 15,
+ "members": [
+
+ ]
+ },
+ "dialout": {
+ "gid": 18,
+ "members": [
+ "lirc"
+ ]
+ },
+ "floppy": {
+ "gid": 19,
+ "members": [
+
+ ]
+ },
+ "games": {
+ "gid": 20,
+ "members": [
+
+ ]
+ },
+ "tape": {
+ "gid": 30,
+ "members": [
+
+ ]
+ },
+ "video": {
+ "gid": 39,
+ "members": [
+
+ ]
+ },
+ "ftp": {
+ "gid": 50,
+ "members": [
+
+ ]
+ },
+ "lock": {
+ "gid": 54,
+ "members": [
+ "lirc"
+ ]
+ },
+ "audio": {
+ "gid": 63,
+ "members": [
+
+ ]
+ },
+ "nobody": {
+ "gid": 99,
+ "members": [
+
+ ]
+ },
+ "users": {
+ "gid": 100,
+ "members": [
+
+ ]
+ },
+ "utmp": {
+ "gid": 22,
+ "members": [
+
+ ]
+ },
+ "utempter": {
+ "gid": 35,
+ "members": [
+
+ ]
+ },
+ "avahi-autoipd": {
+ "gid": 170,
+ "members": [
+
+ ]
+ },
+ "systemd-journal": {
+ "gid": 190,
+ "members": [
+
+ ]
+ },
+ "dbus": {
+ "gid": 81,
+ "members": [
+
+ ]
+ },
+ "polkitd": {
+ "gid": 999,
+ "members": [
+
+ ]
+ },
+ "abrt": {
+ "gid": 173,
+ "members": [
+
+ ]
+ },
+ "dip": {
+ "gid": 40,
+ "members": [
+
+ ]
+ },
+ "usbmuxd": {
+ "gid": 113,
+ "members": [
+
+ ]
+ },
+ "colord": {
+ "gid": 998,
+ "members": [
+
+ ]
+ },
+ "geoclue": {
+ "gid": 997,
+ "members": [
+
+ ]
+ },
+ "ssh_keys": {
+ "gid": 996,
+ "members": [
+
+ ]
+ },
+ "rpc": {
+ "gid": 32,
+ "members": [
+
+ ]
+ },
+ "rpcuser": {
+ "gid": 29,
+ "members": [
+
+ ]
+ },
+ "nfsnobody": {
+ "gid": 65534,
+ "members": [
+
+ ]
+ },
+ "kvm": {
+ "gid": 36,
+ "members": [
+ "qemu"
+ ]
+ },
+ "qemu": {
+ "gid": 107,
+ "members": [
+
+ ]
+ },
+ "rtkit": {
+ "gid": 172,
+ "members": [
+
+ ]
+ },
+ "radvd": {
+ "gid": 75,
+ "members": [
+
+ ]
+ },
+ "tss": {
+ "gid": 59,
+ "members": [
+
+ ]
+ },
+ "unbound": {
+ "gid": 995,
+ "members": [
+
+ ]
+ },
+ "openvpn": {
+ "gid": 994,
+ "members": [
+
+ ]
+ },
+ "saslauth": {
+ "gid": 76,
+ "members": [
+
+ ]
+ },
+ "avahi": {
+ "gid": 70,
+ "members": [
+
+ ]
+ },
+ "brlapi": {
+ "gid": 993,
+ "members": [
+
+ ]
+ },
+ "pulse": {
+ "gid": 992,
+ "members": [
+
+ ]
+ },
+ "pulse-access": {
+ "gid": 991,
+ "members": [
+
+ ]
+ },
+ "gdm": {
+ "gid": 42,
+ "members": [
+
+ ]
+ },
+ "gnome-initial-setup": {
+ "gid": 990,
+ "members": [
+
+ ]
+ },
+ "nm-openconnect": {
+ "gid": 989,
+ "members": [
+
+ ]
+ },
+ "sshd": {
+ "gid": 74,
+ "members": [
+
+ ]
+ },
+ "slocate": {
+ "gid": 21,
+ "members": [
+
+ ]
+ },
+ "chrony": {
+ "gid": 988,
+ "members": [
+
+ ]
+ },
+ "tcpdump": {
+ "gid": 72,
+ "members": [
+
+ ]
+ },
+ "some_user": {
+ "gid": 1000,
+ "members": [
+ "some_user"
+ ]
+ },
+ "docker": {
+ "gid": 986,
+ "members": [
+ "some_user"
+ ]
+ }
+ },
+ "c": {
+ "gcc": {
+ "target": "x86_64-redhat-linux",
+ "configured_with": "../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux",
+ "thread_model": "posix",
+ "description": "gcc version 6.3.1 20161221 (Red Hat 6.3.1-1) (GCC) ",
+ "version": "6.3.1"
+ },
+ "glibc": {
+ "version": "2.24",
+ "description": "GNU C Library (GNU libc) stable release version 2.24, by Roland McGrath et al."
+ }
+ },
+ "lua": {
+ "version": "5.3.4"
+ },
+ "ruby": {
+ "platform": "x86_64-linux",
+ "version": "2.3.3",
+ "release_date": "2016-11-21",
+ "target": "x86_64-redhat-linux-gnu",
+ "target_cpu": "x86_64",
+ "target_vendor": "redhat",
+ "target_os": "linux",
+ "host": "x86_64-redhat-linux-gnu",
+ "host_cpu": "x86_64",
+ "host_os": "linux-gnu",
+ "host_vendor": "redhat",
+ "bin_dir": "/usr/bin",
+ "ruby_bin": "/usr/bin/ruby",
+ "gems_dir": "/home/some_user/.gem/ruby",
+ "gem_bin": "/usr/bin/gem"
+ }
+ },
+ "command": {
+ "ps": "ps -ef"
+ },
+ "root_group": "root",
+ "fips": {
+ "kernel": {
+ "enabled": false
+ }
+ },
+ "hostname": "myhostname",
+ "machinename": "myhostname",
+ "fqdn": "myhostname",
+ "domain": null,
+ "machine_id": "1234567abcede123456123456123456a",
+ "privateaddress": "192.168.1.100",
+ "keys": {
+ "ssh": {
+
+ }
+ },
+ "time": {
+ "timezone": "EDT"
+ },
+ "sessions": {
+ "by_session": {
+ "1918": {
+ "session": "1918",
+ "uid": "1000",
+ "user": "some_user",
+ "seat": null
+ },
+ "5": {
+ "session": "5",
+ "uid": "1000",
+ "user": "some_user",
+ "seat": "seat0"
+ },
+ "3": {
+ "session": "3",
+ "uid": "0",
+ "user": "root",
+ "seat": "seat0"
+ }
+ },
+ "by_user": {
+ "some_user": [
+ {
+ "session": "1918",
+ "uid": "1000",
+ "user": "some_user",
+ "seat": null
+ },
+ {
+ "session": "5",
+ "uid": "1000",
+ "user": "some_user",
+ "seat": "seat0"
+ }
+ ],
+ "root": [
+ {
+ "session": "3",
+ "uid": "0",
+ "user": "root",
+ "seat": "seat0"
+ }
+ ]
+ }
+ },
+ "hostnamectl": {
+ "static_hostname": "myhostname",
+ "icon_name": "computer-laptop",
+ "chassis": "laptop",
+ "machine_id": "24dc16bd7694404c825b517ab46d9d6b",
+ "machine_id": "12345123451234512345123451242323",
+ "boot_id": "3d5d5512341234123412341234123423",
+ "operating_system": "Fedora 25 (Workstation Edition)",
+ "cpe_os_name": "cpe",
+ "kernel": "Linux 4.9.14-200.fc25.x86_64",
+ "architecture": "x86-64"
+ },
+ "block_device": {
+ "dm-1": {
+ "size": "104857600",
+ "removable": "0",
+ "rotational": "0",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "loop1": {
+ "size": "209715200",
+ "removable": "0",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "sr0": {
+ "size": "2097151",
+ "removable": "1",
+ "model": "DVD-RAM UJ8E2",
+ "rev": "SB01",
+ "state": "running",
+ "timeout": "30",
+ "vendor": "MATSHITA",
+ "queue_depth": "1",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "dm-2": {
+ "size": "378093568",
+ "removable": "0",
+ "rotational": "0",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "loop2": {
+ "size": "4194304",
+ "removable": "0",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "dm-0": {
+ "size": "16138240",
+ "removable": "0",
+ "rotational": "0",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "loop0": {
+ "size": "1024000",
+ "removable": "0",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "sda": {
+ "size": "500118192",
+ "removable": "0",
+ "model": "SAMSUNG MZ7TD256",
+ "rev": "2L5Q",
+ "state": "running",
+ "timeout": "30",
+ "vendor": "ATA",
+ "queue_depth": "31",
+ "rotational": "0",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "dm-5": {
+ "size": "20971520",
+ "removable": "0",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ },
+ "dm-3": {
+ "size": "209715200",
+ "removable": "0",
+ "rotational": "1",
+ "physical_block_size": "512",
+ "logical_block_size": "512"
+ }
+ },
+ "sysconf": {
+ "LINK_MAX": 65000,
+ "_POSIX_LINK_MAX": 65000,
+ "MAX_CANON": 255,
+ "_POSIX_MAX_CANON": 255,
+ "MAX_INPUT": 255,
+ "_POSIX_MAX_INPUT": 255,
+ "NAME_MAX": 255,
+ "_POSIX_NAME_MAX": 255,
+ "PATH_MAX": 4096,
+ "_POSIX_PATH_MAX": 4096,
+ "PIPE_BUF": 4096,
+ "_POSIX_PIPE_BUF": 4096,
+ "SOCK_MAXBUF": null,
+ "_POSIX_ASYNC_IO": null,
+ "_POSIX_CHOWN_RESTRICTED": 1,
+ "_POSIX_NO_TRUNC": 1,
+ "_POSIX_PRIO_IO": null,
+ "_POSIX_SYNC_IO": null,
+ "_POSIX_VDISABLE": 0,
+ "ARG_MAX": 2097152,
+ "ATEXIT_MAX": 2147483647,
+ "CHAR_BIT": 8,
+ "CHAR_MAX": 127,
+ "CHAR_MIN": -128,
+ "CHILD_MAX": 62844,
+ "CLK_TCK": 100,
+ "INT_MAX": 2147483647,
+ "INT_MIN": -2147483648,
+ "IOV_MAX": 1024,
+ "LOGNAME_MAX": 256,
+ "LONG_BIT": 64,
+ "MB_LEN_MAX": 16,
+ "NGROUPS_MAX": 65536,
+ "NL_ARGMAX": 4096,
+ "NL_LANGMAX": 2048,
+ "NL_MSGMAX": 2147483647,
+ "NL_NMAX": 2147483647,
+ "NL_SETMAX": 2147483647,
+ "NL_TEXTMAX": 2147483647,
+ "NSS_BUFLEN_GROUP": 1024,
+ "NSS_BUFLEN_PASSWD": 1024,
+ "NZERO": 20,
+ "OPEN_MAX": 1024,
+ "PAGESIZE": 4096,
+ "PAGE_SIZE": 4096,
+ "PASS_MAX": 8192,
+ "PTHREAD_DESTRUCTOR_ITERATIONS": 4,
+ "PTHREAD_KEYS_MAX": 1024,
+ "PTHREAD_STACK_MIN": 16384,
+ "PTHREAD_THREADS_MAX": null,
+ "SCHAR_MAX": 127,
+ "SCHAR_MIN": -128,
+ "SHRT_MAX": 32767,
+ "SHRT_MIN": -32768,
+ "SSIZE_MAX": 32767,
+ "TTY_NAME_MAX": 32,
+ "TZNAME_MAX": 6,
+ "UCHAR_MAX": 255,
+ "UINT_MAX": 4294967295,
+ "UIO_MAXIOV": 1024,
+ "ULONG_MAX": 18446744073709551615,
+ "USHRT_MAX": 65535,
+ "WORD_BIT": 32,
+ "_AVPHYS_PAGES": 955772,
+ "_NPROCESSORS_CONF": 8,
+ "_NPROCESSORS_ONLN": 8,
+ "_PHYS_PAGES": 4027635,
+ "_POSIX_ARG_MAX": 2097152,
+ "_POSIX_ASYNCHRONOUS_IO": 200809,
+ "_POSIX_CHILD_MAX": 62844,
+ "_POSIX_FSYNC": 200809,
+ "_POSIX_JOB_CONTROL": 1,
+ "_POSIX_MAPPED_FILES": 200809,
+ "_POSIX_MEMLOCK": 200809,
+ "_POSIX_MEMLOCK_RANGE": 200809,
+ "_POSIX_MEMORY_PROTECTION": 200809,
+ "_POSIX_MESSAGE_PASSING": 200809,
+ "_POSIX_NGROUPS_MAX": 65536,
+ "_POSIX_OPEN_MAX": 1024,
+ "_POSIX_PII": null,
+ "_POSIX_PII_INTERNET": null,
+ "_POSIX_PII_INTERNET_DGRAM": null,
+ "_POSIX_PII_INTERNET_STREAM": null,
+ "_POSIX_PII_OSI": null,
+ "_POSIX_PII_OSI_CLTS": null,
+ "_POSIX_PII_OSI_COTS": null,
+ "_POSIX_PII_OSI_M": null,
+ "_POSIX_PII_SOCKET": null,
+ "_POSIX_PII_XTI": null,
+ "_POSIX_POLL": null,
+ "_POSIX_PRIORITIZED_IO": 200809,
+ "_POSIX_PRIORITY_SCHEDULING": 200809,
+ "_POSIX_REALTIME_SIGNALS": 200809,
+ "_POSIX_SAVED_IDS": 1,
+ "_POSIX_SELECT": null,
+ "_POSIX_SEMAPHORES": 200809,
+ "_POSIX_SHARED_MEMORY_OBJECTS": 200809,
+ "_POSIX_SSIZE_MAX": 32767,
+ "_POSIX_STREAM_MAX": 16,
+ "_POSIX_SYNCHRONIZED_IO": 200809,
+ "_POSIX_THREADS": 200809,
+ "_POSIX_THREAD_ATTR_STACKADDR": 200809,
+ "_POSIX_THREAD_ATTR_STACKSIZE": 200809,
+ "_POSIX_THREAD_PRIORITY_SCHEDULING": 200809,
+ "_POSIX_THREAD_PRIO_INHERIT": 200809,
+ "_POSIX_THREAD_PRIO_PROTECT": 200809,
+ "_POSIX_THREAD_ROBUST_PRIO_INHERIT": null,
+ "_POSIX_THREAD_ROBUST_PRIO_PROTECT": null,
+ "_POSIX_THREAD_PROCESS_SHARED": 200809,
+ "_POSIX_THREAD_SAFE_FUNCTIONS": 200809,
+ "_POSIX_TIMERS": 200809,
+ "TIMER_MAX": null,
+ "_POSIX_TZNAME_MAX": 6,
+ "_POSIX_VERSION": 200809,
+ "_T_IOV_MAX": null,
+ "_XOPEN_CRYPT": 1,
+ "_XOPEN_ENH_I18N": 1,
+ "_XOPEN_LEGACY": 1,
+ "_XOPEN_REALTIME": 1,
+ "_XOPEN_REALTIME_THREADS": 1,
+ "_XOPEN_SHM": 1,
+ "_XOPEN_UNIX": 1,
+ "_XOPEN_VERSION": 700,
+ "_XOPEN_XCU_VERSION": 4,
+ "_XOPEN_XPG2": 1,
+ "_XOPEN_XPG3": 1,
+ "_XOPEN_XPG4": 1,
+ "BC_BASE_MAX": 99,
+ "BC_DIM_MAX": 2048,
+ "BC_SCALE_MAX": 99,
+ "BC_STRING_MAX": 1000,
+ "CHARCLASS_NAME_MAX": 2048,
+ "COLL_WEIGHTS_MAX": 255,
+ "EQUIV_CLASS_MAX": null,
+ "EXPR_NEST_MAX": 32,
+ "LINE_MAX": 2048,
+ "POSIX2_BC_BASE_MAX": 99,
+ "POSIX2_BC_DIM_MAX": 2048,
+ "POSIX2_BC_SCALE_MAX": 99,
+ "POSIX2_BC_STRING_MAX": 1000,
+ "POSIX2_CHAR_TERM": 200809,
+ "POSIX2_COLL_WEIGHTS_MAX": 255,
+ "POSIX2_C_BIND": 200809,
+ "POSIX2_C_DEV": 200809,
+ "POSIX2_C_VERSION": 200809,
+ "POSIX2_EXPR_NEST_MAX": 32,
+ "POSIX2_FORT_DEV": null,
+ "POSIX2_FORT_RUN": null,
+ "_POSIX2_LINE_MAX": 2048,
+ "POSIX2_LINE_MAX": 2048,
+ "POSIX2_LOCALEDEF": 200809,
+ "POSIX2_RE_DUP_MAX": 32767,
+ "POSIX2_SW_DEV": 200809,
+ "POSIX2_UPE": null,
+ "POSIX2_VERSION": 200809,
+ "RE_DUP_MAX": 32767,
+ "PATH": "/usr/bin",
+ "CS_PATH": "/usr/bin",
+ "LFS_CFLAGS": null,
+ "LFS_LDFLAGS": null,
+ "LFS_LIBS": null,
+ "LFS_LINTFLAGS": null,
+ "LFS64_CFLAGS": "-D_LARGEFILE64_SOURCE",
+ "LFS64_LDFLAGS": null,
+ "LFS64_LIBS": null,
+ "LFS64_LINTFLAGS": "-D_LARGEFILE64_SOURCE",
+ "_XBS5_WIDTH_RESTRICTED_ENVS": "XBS5_LP64_OFF64",
+ "XBS5_WIDTH_RESTRICTED_ENVS": "XBS5_LP64_OFF64",
+ "_XBS5_ILP32_OFF32": null,
+ "XBS5_ILP32_OFF32_CFLAGS": null,
+ "XBS5_ILP32_OFF32_LDFLAGS": null,
+ "XBS5_ILP32_OFF32_LIBS": null,
+ "XBS5_ILP32_OFF32_LINTFLAGS": null,
+ "_XBS5_ILP32_OFFBIG": null,
+ "XBS5_ILP32_OFFBIG_CFLAGS": null,
+ "XBS5_ILP32_OFFBIG_LDFLAGS": null,
+ "XBS5_ILP32_OFFBIG_LIBS": null,
+ "XBS5_ILP32_OFFBIG_LINTFLAGS": null,
+ "_XBS5_LP64_OFF64": 1,
+ "XBS5_LP64_OFF64_CFLAGS": "-m64",
+ "XBS5_LP64_OFF64_LDFLAGS": "-m64",
+ "XBS5_LP64_OFF64_LIBS": null,
+ "XBS5_LP64_OFF64_LINTFLAGS": null,
+ "_XBS5_LPBIG_OFFBIG": null,
+ "XBS5_LPBIG_OFFBIG_CFLAGS": null,
+ "XBS5_LPBIG_OFFBIG_LDFLAGS": null,
+ "XBS5_LPBIG_OFFBIG_LIBS": null,
+ "XBS5_LPBIG_OFFBIG_LINTFLAGS": null,
+ "_POSIX_V6_ILP32_OFF32": null,
+ "POSIX_V6_ILP32_OFF32_CFLAGS": null,
+ "POSIX_V6_ILP32_OFF32_LDFLAGS": null,
+ "POSIX_V6_ILP32_OFF32_LIBS": null,
+ "POSIX_V6_ILP32_OFF32_LINTFLAGS": null,
+ "_POSIX_V6_WIDTH_RESTRICTED_ENVS": "POSIX_V6_LP64_OFF64",
+ "POSIX_V6_WIDTH_RESTRICTED_ENVS": "POSIX_V6_LP64_OFF64",
+ "_POSIX_V6_ILP32_OFFBIG": null,
+ "POSIX_V6_ILP32_OFFBIG_CFLAGS": null,
+ "POSIX_V6_ILP32_OFFBIG_LDFLAGS": null,
+ "POSIX_V6_ILP32_OFFBIG_LIBS": null,
+ "POSIX_V6_ILP32_OFFBIG_LINTFLAGS": null,
+ "_POSIX_V6_LP64_OFF64": 1,
+ "POSIX_V6_LP64_OFF64_CFLAGS": "-m64",
+ "POSIX_V6_LP64_OFF64_LDFLAGS": "-m64",
+ "POSIX_V6_LP64_OFF64_LIBS": null,
+ "POSIX_V6_LP64_OFF64_LINTFLAGS": null,
+ "_POSIX_V6_LPBIG_OFFBIG": null,
+ "POSIX_V6_LPBIG_OFFBIG_CFLAGS": null,
+ "POSIX_V6_LPBIG_OFFBIG_LDFLAGS": null,
+ "POSIX_V6_LPBIG_OFFBIG_LIBS": null,
+ "POSIX_V6_LPBIG_OFFBIG_LINTFLAGS": null,
+ "_POSIX_V7_ILP32_OFF32": null,
+ "POSIX_V7_ILP32_OFF32_CFLAGS": null,
+ "POSIX_V7_ILP32_OFF32_LDFLAGS": null,
+ "POSIX_V7_ILP32_OFF32_LIBS": null,
+ "POSIX_V7_ILP32_OFF32_LINTFLAGS": null,
+ "_POSIX_V7_WIDTH_RESTRICTED_ENVS": "POSIX_V7_LP64_OFF64",
+ "POSIX_V7_WIDTH_RESTRICTED_ENVS": "POSIX_V7_LP64_OFF64",
+ "_POSIX_V7_ILP32_OFFBIG": null,
+ "POSIX_V7_ILP32_OFFBIG_CFLAGS": null,
+ "POSIX_V7_ILP32_OFFBIG_LDFLAGS": null,
+ "POSIX_V7_ILP32_OFFBIG_LIBS": null,
+ "POSIX_V7_ILP32_OFFBIG_LINTFLAGS": null,
+ "_POSIX_V7_LP64_OFF64": 1,
+ "POSIX_V7_LP64_OFF64_CFLAGS": "-m64",
+ "POSIX_V7_LP64_OFF64_LDFLAGS": "-m64",
+ "POSIX_V7_LP64_OFF64_LIBS": null,
+ "POSIX_V7_LP64_OFF64_LINTFLAGS": null,
+ "_POSIX_V7_LPBIG_OFFBIG": null,
+ "POSIX_V7_LPBIG_OFFBIG_CFLAGS": null,
+ "POSIX_V7_LPBIG_OFFBIG_LDFLAGS": null,
+ "POSIX_V7_LPBIG_OFFBIG_LIBS": null,
+ "POSIX_V7_LPBIG_OFFBIG_LINTFLAGS": null,
+ "_POSIX_ADVISORY_INFO": 200809,
+ "_POSIX_BARRIERS": 200809,
+ "_POSIX_BASE": null,
+ "_POSIX_C_LANG_SUPPORT": null,
+ "_POSIX_C_LANG_SUPPORT_R": null,
+ "_POSIX_CLOCK_SELECTION": 200809,
+ "_POSIX_CPUTIME": 200809,
+ "_POSIX_THREAD_CPUTIME": 200809,
+ "_POSIX_DEVICE_SPECIFIC": null,
+ "_POSIX_DEVICE_SPECIFIC_R": null,
+ "_POSIX_FD_MGMT": null,
+ "_POSIX_FIFO": null,
+ "_POSIX_PIPE": null,
+ "_POSIX_FILE_ATTRIBUTES": null,
+ "_POSIX_FILE_LOCKING": null,
+ "_POSIX_FILE_SYSTEM": null,
+ "_POSIX_MONOTONIC_CLOCK": 200809,
+ "_POSIX_MULTI_PROCESS": null,
+ "_POSIX_SINGLE_PROCESS": null,
+ "_POSIX_NETWORKING": null,
+ "_POSIX_READER_WRITER_LOCKS": 200809,
+ "_POSIX_SPIN_LOCKS": 200809,
+ "_POSIX_REGEXP": 1,
+ "_REGEX_VERSION": null,
+ "_POSIX_SHELL": 1,
+ "_POSIX_SIGNALS": null,
+ "_POSIX_SPAWN": 200809,
+ "_POSIX_SPORADIC_SERVER": null,
+ "_POSIX_THREAD_SPORADIC_SERVER": null,
+ "_POSIX_SYSTEM_DATABASE": null,
+ "_POSIX_SYSTEM_DATABASE_R": null,
+ "_POSIX_TIMEOUTS": 200809,
+ "_POSIX_TYPED_MEMORY_OBJECTS": null,
+ "_POSIX_USER_GROUPS": null,
+ "_POSIX_USER_GROUPS_R": null,
+ "POSIX2_PBS": null,
+ "POSIX2_PBS_ACCOUNTING": null,
+ "POSIX2_PBS_LOCATE": null,
+ "POSIX2_PBS_TRACK": null,
+ "POSIX2_PBS_MESSAGE": null,
+ "SYMLOOP_MAX": null,
+ "STREAM_MAX": 16,
+ "AIO_LISTIO_MAX": null,
+ "AIO_MAX": null,
+ "AIO_PRIO_DELTA_MAX": 20,
+ "DELAYTIMER_MAX": 2147483647,
+ "HOST_NAME_MAX": 64,
+ "LOGIN_NAME_MAX": 256,
+ "MQ_OPEN_MAX": null,
+ "MQ_PRIO_MAX": 32768,
+ "_POSIX_DEVICE_IO": null,
+ "_POSIX_TRACE": null,
+ "_POSIX_TRACE_EVENT_FILTER": null,
+ "_POSIX_TRACE_INHERIT": null,
+ "_POSIX_TRACE_LOG": null,
+ "RTSIG_MAX": 32,
+ "SEM_NSEMS_MAX": null,
+ "SEM_VALUE_MAX": 2147483647,
+ "SIGQUEUE_MAX": 62844,
+ "FILESIZEBITS": 64,
+ "POSIX_ALLOC_SIZE_MIN": 4096,
+ "POSIX_REC_INCR_XFER_SIZE": null,
+ "POSIX_REC_MAX_XFER_SIZE": null,
+ "POSIX_REC_MIN_XFER_SIZE": 4096,
+ "POSIX_REC_XFER_ALIGN": 4096,
+ "SYMLINK_MAX": null,
+ "GNU_LIBC_VERSION": "glibc 2.24",
+ "GNU_LIBPTHREAD_VERSION": "NPTL 2.24",
+ "POSIX2_SYMLINKS": 1,
+ "LEVEL1_ICACHE_SIZE": 32768,
+ "LEVEL1_ICACHE_ASSOC": 8,
+ "LEVEL1_ICACHE_LINESIZE": 64,
+ "LEVEL1_DCACHE_SIZE": 32768,
+ "LEVEL1_DCACHE_ASSOC": 8,
+ "LEVEL1_DCACHE_LINESIZE": 64,
+ "LEVEL2_CACHE_SIZE": 262144,
+ "LEVEL2_CACHE_ASSOC": 8,
+ "LEVEL2_CACHE_LINESIZE": 64,
+ "LEVEL3_CACHE_SIZE": 6291456,
+ "LEVEL3_CACHE_ASSOC": 12,
+ "LEVEL3_CACHE_LINESIZE": 64,
+ "LEVEL4_CACHE_SIZE": 0,
+ "LEVEL4_CACHE_ASSOC": 0,
+ "LEVEL4_CACHE_LINESIZE": 0,
+ "IPV6": 200809,
+ "RAW_SOCKETS": 200809,
+ "_POSIX_IPV6": 200809,
+ "_POSIX_RAW_SOCKETS": 200809
+ },
+ "init_package": "systemd",
+ "shells": [
+ "/bin/sh",
+ "/bin/bash",
+ "/sbin/nologin",
+ "/usr/bin/sh",
+ "/usr/bin/bash",
+ "/usr/sbin/nologin",
+ "/usr/bin/zsh",
+ "/bin/zsh"
+ ],
+ "ohai_time": 1492535225.41052,
+ "cloud_v2": null,
+ "cloud": null
+}
+''' # noqa
+
+
+class TestOhaiCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'ohai']
+ valid_subsets = ['ohai']
+ fact_namespace = 'ansible_ohai'
+ collector_class = OhaiFactCollector
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 10,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value='/not/actually/ohai')
+ mock_module.run_command = Mock(return_value=(0, ohai_json_output, ''))
+ return mock_module
+
+ @patch('ansible.module_utils.facts.other.ohai.OhaiFactCollector.get_ohai_output')
+ def test_bogus_json(self, mock_get_ohai_output):
+ module = self._mock_module()
+
+ # bogus json
+ mock_get_ohai_output.return_value = '{'
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict, {})
+
+ @patch('ansible.module_utils.facts.other.ohai.OhaiFactCollector.run_ohai')
+ def test_ohai_non_zero_return_code(self, mock_run_ohai):
+ module = self._mock_module()
+
+ # bogus json
+ mock_run_ohai.return_value = (1, '{}', '')
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+
+ # This assumes no 'ohai' entry at all is correct
+ self.assertNotIn('ohai', facts_dict)
+ self.assertEqual(facts_dict, {})
diff --git a/test/units/module_utils/facts/system/__init__.py b/test/units/module_utils/facts/system/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/system/__init__.py
diff --git a/test/units/module_utils/facts/system/distribution/__init__.py b/test/units/module_utils/facts/system/distribution/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/__init__.py
diff --git a/test/units/module_utils/facts/system/distribution/conftest.py b/test/units/module_utils/facts/system/distribution/conftest.py
new file mode 100644
index 0000000..d27b97f
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/conftest.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+
+from units.compat.mock import Mock
+
+
+@pytest.fixture
+def mock_module():
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': ['all'],
+ 'gather_timeout': 5,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value=None)
+ return mock_module
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/almalinux_8_3_beta.json b/test/units/module_utils/facts/system/distribution/fixtures/almalinux_8_3_beta.json
new file mode 100644
index 0000000..2d8df50
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/almalinux_8_3_beta.json
@@ -0,0 +1,53 @@
+{
+ "name": "AlmaLinux 8.3",
+ "distro": {
+ "codename": "Purple Manul",
+ "id": "almalinux",
+ "name": "AlmaLinux",
+ "version": "8.3",
+ "version_best": "8.3",
+ "lsb_release_info": {
+ "lsb_version": ":core-4.1-amd64:core-4.1-noarch",
+ "distributor_id": "AlmaLinux",
+ "description": "AlmaLinux release 8.3 Beta (Purple Manul)",
+ "release": "8.3",
+ "codename": "PurpleManul"
+ },
+ "os_release_info": {
+ "name": "AlmaLinux",
+ "version": "8.3 (Purple Manul)",
+ "id": "almalinux",
+ "id_like": "rhel centos fedora",
+ "version_id": "8.3",
+ "platform_id": "platform:el8",
+ "pretty_name": "AlmaLinux 8.3 Beta (Purple Manul)",
+ "ansi_color": "0;34",
+ "cpe_name": "cpe:/o:almalinux:almalinux:8.3:beta",
+ "home_url": "https://almalinux.org/",
+ "bug_report_url": "https://bugs.almalinux.org/",
+ "almalinux_mantisbt_project": "AlmaLinux-8",
+ "almalinux_mantisbt_project_version": "8",
+ "codename": "Purple Manul"
+ }
+ },
+ "input": {
+ "/etc/centos-release": "AlmaLinux release 8.3 Beta (Purple Manul)\n",
+ "/etc/redhat-release": "AlmaLinux release 8.3 Beta (Purple Manul)\n",
+ "/etc/system-release": "AlmaLinux release 8.3 Beta (Purple Manul)\n",
+ "/etc/os-release": "NAME=\"AlmaLinux\"\nVERSION=\"8.3 (Purple Manul)\"\nID=\"almalinux\"\nID_LIKE=\"rhel centos fedora\"\nVERSION_ID=\"8.3\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"AlmaLinux 8.3 Beta (Purple Manul)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:almalinux:almalinux:8.3:beta\"\nHOME_URL=\"https://almalinux.org/\"\nBUG_REPORT_URL=\"https://bugs.almalinux.org/\"\n\nALMALINUX_MANTISBT_PROJECT=\"AlmaLinux-8\" \nALMALINUX_MANTISBT_PROJECT_VERSION=\"8\" \n\n",
+ "/usr/lib/os-release": "NAME=\"AlmaLinux\"\nVERSION=\"8.3 (Purple Manul)\"\nID=\"almalinux\"\nID_LIKE=\"rhel centos fedora\"\nVERSION_ID=\"8.3\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"AlmaLinux 8.3 Beta (Purple Manul)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:almalinux:almalinux:8.3:beta\"\nHOME_URL=\"https://almalinux.org/\"\nBUG_REPORT_URL=\"https://bugs.almalinux.org/\"\n\nALMALINUX_MANTISBT_PROJECT=\"AlmaLinux-8\" \nALMALINUX_MANTISBT_PROJECT_VERSION=\"8\" \n\n"
+ },
+ "platform.dist": [
+ "almalinux",
+ "8.3",
+ "Purple Manul"
+ ],
+ "result": {
+ "distribution": "AlmaLinux",
+ "distribution_version": "8.3",
+ "distribution_release": "Purple Manul",
+ "distribution_major_version": "8",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.18.0-240.el8.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2.json b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2.json
new file mode 100644
index 0000000..d98070e
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2.json
@@ -0,0 +1,39 @@
+{
+ "platform.dist": [
+ "amzn",
+ "2",
+ ""
+ ],
+ "input": {
+ "/etc/os-release": "NAME=\"Amazon Linux\"\nVERSION=\"2\"\nID=\"amzn\"\nID_LIKE=\"centos rhel fedora\"\nVERSION_ID=\"2\"\nPRETTY_NAME=\"Amazon Linux 2\"\nANSI_COLOR=\"0;33\"\nCPE_NAME=\"cpe:2.3:o:amazon:amazon_linux:2\"\nHOME_URL=\"https://amazonlinux.com/\"\n",
+ "/etc/system-release": "Amazon Linux release 2 (Karoo)\n"
+ },
+ "name": "Amazon 2",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Amazon",
+ "distribution_major_version": "2",
+ "os_family": "RedHat",
+ "distribution_version": "2"
+ },
+ "distro": {
+ "id": "amzn",
+ "name": "Amazon Linux",
+ "version": "2",
+ "codename": "",
+ "version_best": "2",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "ansi_color": "0;33",
+ "id_like": "centos rhel fedora",
+ "version_id": "2",
+ "pretty_name": "Amazon Linux 2",
+ "name": "Amazon Linux",
+ "version": "2",
+ "home_url": "https://amazonlinux.com/",
+ "id": "amzn",
+ "cpe_name": "cpe:2.3:o:amazon:amazon_linux:2"
+ }
+ },
+ "platform.release": "4.14.181-142.260.amzn2.x86_64"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2016.03.json b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2016.03.json
new file mode 100644
index 0000000..38449e4
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2016.03.json
@@ -0,0 +1,40 @@
+{
+ "name": "Amazon 2016.03",
+ "platform.release": "4.14.94-73.73.amzn1.x86_64",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Amazon",
+ "distribution_major_version": "2016",
+ "distribution_minor_version": "03",
+ "os_family": "RedHat",
+ "distribution_version": "2016.03"
+ },
+ "platform.dist": [
+ "amzn",
+ "2016.03",
+ ""
+ ],
+ "input": {
+ "/etc/os-release": "NAME=\"Amazon Linux AMI\"\nVERSION=\"2016.03\"\nID=\"amzn\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"2016.03\"\nPRETTY_NAME=\"Amazon Linux AMI 2016.03\"\nANSI_COLOR=\"0;33\"\nCPE_NAME=\"cpe:/o:amazon:linux:2016.03:ga\"\nHOME_URL=\"http://aws.amazon.com/amazon-linux-ami/\"\n",
+ "/etc/system-release": "Amazon Linux AMI release 2016.03\n"
+ },
+ "distro": {
+ "version_best": "2016.03",
+ "os_release_info": {
+ "name": "Amazon Linux AMI",
+ "ansi_color": "0;33",
+ "id_like": "rhel fedora",
+ "version_id": "2016.03",
+ "pretty_name": "Amazon Linux AMI 2016.03",
+ "version": "2016.03",
+ "home_url": "http://aws.amazon.com/amazon-linux-ami/",
+ "cpe_name": "cpe:/o:amazon:linux:2016.03:ga",
+ "id": "amzn"
+ },
+ "version": "2016.03",
+ "codename": "",
+ "lsb_release_info": {},
+ "id": "amzn",
+ "name": "Amazon Linux AMI"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2018.03.json b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2018.03.json
new file mode 100644
index 0000000..2461e72
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2018.03.json
@@ -0,0 +1,40 @@
+{
+ "name": "Amazon 2018.03",
+ "platform.release": "4.14.94-73.73.amzn1.x86_64",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Amazon",
+ "distribution_major_version": "2018",
+ "distribution_minor_version": "03",
+ "os_family": "RedHat",
+ "distribution_version": "2018.03"
+ },
+ "platform.dist": [
+ "amzn",
+ "2018.03",
+ ""
+ ],
+ "input": {
+ "/etc/os-release": "NAME=\"Amazon Linux AMI\"\nVERSION=\"2018.03\"\nID=\"amzn\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"2018.03\"\nPRETTY_NAME=\"Amazon Linux AMI 2018.03\"\nANSI_COLOR=\"0;33\"\nCPE_NAME=\"cpe:/o:amazon:linux:2018.03:ga\"\nHOME_URL=\"http://aws.amazon.com/amazon-linux-ami/\"\n",
+ "/etc/system-release": "Amazon Linux AMI release 2018.03\n"
+ },
+ "distro": {
+ "version_best": "2018.03",
+ "os_release_info": {
+ "name": "Amazon Linux AMI",
+ "ansi_color": "0;33",
+ "id_like": "rhel fedora",
+ "version_id": "2018.03",
+ "pretty_name": "Amazon Linux AMI 2018.03",
+ "version": "2018.03",
+ "home_url": "http://aws.amazon.com/amazon-linux-ami/",
+ "cpe_name": "cpe:/o:amazon:linux:2018.03:ga",
+ "id": "amzn"
+ },
+ "version": "2018.03",
+ "codename": "",
+ "lsb_release_info": {},
+ "id": "amzn",
+ "name": "Amazon Linux AMI"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2_karoo.json b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2_karoo.json
new file mode 100644
index 0000000..e430ff6
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_2_karoo.json
@@ -0,0 +1,34 @@
+{
+ "platform.dist": [
+ "",
+ "",
+ ""
+ ],
+ "input": {
+ "/etc/system-release": "Amazon Linux release 2 (Karoo)",
+ "/etc/os-release": ""
+ },
+ "name": "Amazon Linux 2 - Karoo",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Amazon",
+ "distribution_major_version": "2",
+ "os_family": "RedHat",
+ "distribution_version": "2"
+ },
+ "distro": {
+ "id": "amzn",
+ "version": "2",
+ "codename": "",
+ "os_release_info": {
+ "name": "Amazon Linux AMI",
+ "ansi_color": "0;33",
+ "id_like": "rhel fedora",
+ "version_id": "2",
+ "pretty_name": "Amazon Linux release 2 (Karoo)",
+ "version": "2",
+ "home_url": "https://amazonlinux.com/",
+ "id": "amzn"
+ }
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_release_2.json b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_release_2.json
new file mode 100644
index 0000000..9fa6090
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/amazon_linux_release_2.json
@@ -0,0 +1,34 @@
+{
+ "platform.dist": [
+ "",
+ "",
+ ""
+ ],
+ "input": {
+ "/etc/system-release": "Amazon Linux release 2",
+ "/etc/os-release": ""
+ },
+ "name": "Amazon Linux 2",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Amazon",
+ "distribution_major_version": "2",
+ "os_family": "RedHat",
+ "distribution_version": "2"
+ },
+ "distro": {
+ "id": "amzn",
+ "version": "2",
+ "codename": "",
+ "os_release_info": {
+ "name": "Amazon Linux AMI",
+ "ansi_color": "0;33",
+ "id_like": "rhel fedora",
+ "version_id": "2",
+ "pretty_name": "Amazon Linux release 2",
+ "version": "2",
+ "home_url": "",
+ "id": "amzn"
+ }
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_na.json b/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_na.json
new file mode 100644
index 0000000..88d9ad8
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_na.json
@@ -0,0 +1,24 @@
+{
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "arch",
+ "name": "Arch Linux",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Arch Linux\"\nPRETTY_NAME=\"Arch Linux\"\nID=arch\nID_LIKE=archlinux\nANSI_COLOR=\"0;36\"\nHOME_URL=\"https://www.archlinux.org/\"\nSUPPORT_URL=\"https://bbs.archlinux.org/\"\nBUG_REPORT_URL=\"https://bugs.archlinux.org/\"\n\n",
+ "/etc/arch-release": ""
+ },
+ "name": "Arch Linux NA",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Archlinux",
+ "distribution_major_version": "NA",
+ "os_family": "Archlinux",
+ "distribution_version": "NA"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_no_arch-release_na.json b/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_no_arch-release_na.json
new file mode 100644
index 0000000..a24bb3a
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/arch_linux_no_arch-release_na.json
@@ -0,0 +1,23 @@
+{
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "arch",
+ "name": "Arch Linux",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Arch Linux\"\nPRETTY_NAME=\"Arch Linux\"\nID=arch\nID_LIKE=archlinux\nANSI_COLOR=\"0;36\"\nHOME_URL=\"https://www.archlinux.org/\"\nSUPPORT_URL=\"https://bbs.archlinux.org/\"\nBUG_REPORT_URL=\"https://bugs.archlinux.org/\"\n\n"
+ },
+ "name": "Arch Linux no arch-release NA",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Archlinux",
+ "distribution_major_version": "NA",
+ "os_family": "Archlinux",
+ "distribution_version": "NA"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/archlinux_rolling.json b/test/units/module_utils/facts/system/distribution/fixtures/archlinux_rolling.json
new file mode 100644
index 0000000..8f35636
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/archlinux_rolling.json
@@ -0,0 +1,31 @@
+{
+ "name": "Archlinux rolling",
+ "distro": {
+ "codename": "n/a",
+ "id": "arch",
+ "name": "Arch",
+ "version": "rolling",
+ "version_best": "rolling",
+ "lsb_release_info": {
+ "lsb_version": "1.4",
+ "distributor_id": "Arch",
+ "description": "Arch Linux",
+ "release": "rolling",
+ "codename": "n/a"
+ },
+ "os_release_info": {}
+ },
+ "input": {
+ "/etc/arch-release": "Arch Linux release\n",
+ "/etc/lsb-release": "LSB_VERSION=1.4\nDISTRIB_ID=Arch\nDISTRIB_RELEASE=rolling\nDISTRIB_DESCRIPTION=\"Arch Linux\"\n",
+ "/usr/lib/os-release": "NAME=\"Arch Linux\"\nPRETTY_NAME=\"Arch Linux\"\nID=arch\nBUILD_ID=rolling\nANSI_COLOR=\"0;36\"\nHOME_URL=\"https://www.archlinux.org/\"\nDOCUMENTATION_URL=\"https://wiki.archlinux.org/\"\nSUPPORT_URL=\"https://bbs.archlinux.org/\"\nBUG_REPORT_URL=\"https://bugs.archlinux.org/\"\nLOGO=archlinux\n"
+ },
+ "platform.dist": ["arch", "rolling", "n/a"],
+ "result": {
+ "distribution": "Archlinux",
+ "distribution_version": "rolling",
+ "distribution_release": "n/a",
+ "distribution_major_version": "rolling",
+ "os_family": "Archlinux"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/centos_6.7.json b/test/units/module_utils/facts/system/distribution/fixtures/centos_6.7.json
new file mode 100644
index 0000000..c99a073
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/centos_6.7.json
@@ -0,0 +1,31 @@
+{
+ "name": "CentOS 6.7",
+ "platform.dist": ["centos", "6.7", "Final"],
+ "distro": {
+ "codename": "Final",
+ "id": "centos",
+ "name": "CentOS Linux",
+ "version": "6.7",
+ "version_best": "6.7",
+ "os_release_info": {},
+ "lsb_release_info": {
+ "release": "6.7",
+ "codename": "Final",
+ "distributor_id": "CentOS",
+ "lsb_version": ":base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch",
+ "description": "CentOS release 6.7 (Final)"
+ }
+ },
+ "input": {
+ "/etc/redhat-release": "CentOS release 6.7 (Final)\n",
+ "/etc/lsb-release": "LSB_VERSION=base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch\n",
+ "/etc/system-release": "CentOS release 6.7 (Final)\n"
+ },
+ "result": {
+ "distribution_release": "Final",
+ "distribution": "CentOS",
+ "distribution_major_version": "6",
+ "os_family": "RedHat",
+ "distribution_version": "6.7"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/centos_8_1.json b/test/units/module_utils/facts/system/distribution/fixtures/centos_8_1.json
new file mode 100644
index 0000000..338959b
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/centos_8_1.json
@@ -0,0 +1,54 @@
+{
+ "name": "CentOS 8.1",
+ "distro": {
+ "codename": "Core",
+ "id": "centos",
+ "name": "CentOS Linux",
+ "version": "8",
+ "version_best": "8.1.1911",
+ "lsb_release_info": {
+ "lsb_version": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch",
+ "distributor_id": "CentOS",
+ "description": "CentOS Linux release 8.1.1911 (Core)",
+ "release": "8.1.1911",
+ "codename": "Core"
+ },
+ "os_release_info": {
+ "name": "CentOS Linux",
+ "version": "8 (Core)",
+ "id": "centos",
+ "id_like": "rhel fedora",
+ "version_id": "8",
+ "platform_id": "platform:el8",
+ "pretty_name": "CentOS Linux 8 (Core)",
+ "ansi_color": "0;31",
+ "cpe_name": "cpe:/o:centos:centos:8",
+ "home_url": "https://www.centos.org/",
+ "bug_report_url": "https://bugs.centos.org/",
+ "centos_mantisbt_project": "CentOS-8",
+ "centos_mantisbt_project_version": "8",
+ "redhat_support_product": "centos",
+ "redhat_support_product_version": "8",
+ "codename": "Core"
+ }
+ },
+ "input": {
+ "/etc/centos-release": "CentOS Linux release 8.1.1911 (Core) \n",
+ "/etc/redhat-release": "CentOS Linux release 8.1.1911 (Core) \n",
+ "/etc/system-release": "CentOS Linux release 8.1.1911 (Core) \n",
+ "/etc/os-release": "NAME=\"CentOS Linux\"\nVERSION=\"8 (Core)\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"CentOS Linux 8 (Core)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:8\"\nHOME_URL=\"https://www.centos.org/\"\nBUG_REPORT_URL=\"https://bugs.centos.org/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-8\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"8\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\n\n"
+ },
+ "platform.dist": [
+ "centos",
+ "8",
+ "Core"
+ ],
+ "result": {
+ "distribution": "CentOS",
+ "distribution_version": "8.1",
+ "distribution_release": "Core",
+ "distribution_major_version": "8",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.18.0-147.el8.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/centos_stream_8.json b/test/units/module_utils/facts/system/distribution/fixtures/centos_stream_8.json
new file mode 100644
index 0000000..1e4166b
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/centos_stream_8.json
@@ -0,0 +1,46 @@
+{
+ "name": "CentOS 8",
+ "distro": {
+ "codename": "",
+ "id": "centos",
+ "name": "CentOS Stream",
+ "version": "8",
+ "version_best": "8",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "CentOS Stream",
+ "version": "8",
+ "id": "centos",
+ "id_like": "rhel fedora",
+ "version_id": "8",
+ "platform_id": "platform:el8",
+ "pretty_name": "CentOS Stream 8",
+ "ansi_color": "0;31",
+ "cpe_name": "cpe:/o:centos:centos:8",
+ "home_url": "https://centos.org/",
+ "bug_report_url": "https://bugzilla.redhat.com/",
+ "redhat_support_product": "Red Hat Enterprise Linux 8",
+ "redhat_support_product_version": "CentOS Stream"
+ }
+ },
+ "input": {
+ "/etc/centos-release": "CentOS Stream release 8\n",
+ "/etc/redhat-release": "CentOS Stream release 8\n",
+ "/etc/system-release": "CentOS Stream release 8\n",
+ "/etc/os-release": "NAME=\"CentOS Stream\"\nVERSION=\"8\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"CentOS Stream 8\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:8\"\nHOME_URL=\"https://centos.org/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux 8\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"CentOS Stream\"\n",
+ "/usr/lib/os-release": "NAME=\"CentOS Stream\"\nVERSION=\"8\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"CentOS Stream 8\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:8\"\nHOME_URL=\"https://centos.org/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux 8\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"CentOS Stream\"\n"
+ },
+ "platform.dist": [
+ "centos",
+ "8",
+ ""
+ ],
+ "result": {
+ "distribution": "CentOS",
+ "distribution_version": "8",
+ "distribution_release": "Stream",
+ "distribution_major_version": "8",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.18.0-257.el8.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_26580.json b/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_26580.json
new file mode 100644
index 0000000..1a99a86
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_26580.json
@@ -0,0 +1,24 @@
+{
+ "platform.dist": ["Clear Linux OS", "26580", "clear-linux-os"],
+ "distro": {
+ "codename": "",
+ "id": "clear-linux-os",
+ "name": "Clear Linux OS",
+ "version": "26580",
+ "version_best": "26580",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Clear Linux OS\"\nVERSION=1\nID=clear-linux-os\nID_LIKE=clear-linux-os\nVERSION_ID=26580\nPRETTY_NAME=\"Clear Linux OS\"\nANSI_COLOR=\"1;35\"\nHOME_URL=\"https://clearlinux.org\"\nSUPPORT_URL=\"https://clearlinux.org\"\nBUG_REPORT_URL=\"mailto:dev@lists.clearlinux.org\"\nPRIVACY_POLICY_URL=\"http://www.intel.com/privacy\"",
+ "/usr/lib/os-release": "NAME=\"Clear Linux OS\"\nVERSION=1\nID=clear-linux-os\nID_LIKE=clear-linux-os\nVERSION_ID=26580\nPRETTY_NAME=\"Clear Linux OS\"\nANSI_COLOR=\"1;35\"\nHOME_URL=\"https://clearlinux.org\"\nSUPPORT_URL=\"https://clearlinux.org\"\nBUG_REPORT_URL=\"mailto:dev@lists.clearlinux.org\"\nPRIVACY_POLICY_URL=\"http://www.intel.com/privacy\""
+ },
+ "name": "ClearLinux 26580",
+ "result": {
+ "distribution_release": "clear-linux-os",
+ "distribution": "Clear Linux OS",
+ "distribution_major_version": "26580",
+ "os_family": "ClearLinux",
+ "distribution_version": "26580"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_28120.json b/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_28120.json
new file mode 100644
index 0000000..30b7668
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/clearlinux_28120.json
@@ -0,0 +1,24 @@
+{
+ "platform.dist": ["Clear Linux OS", "28120", "clear-linux-os"],
+ "distro": {
+ "codename": "",
+ "id": "clear-linux-os",
+ "name": "Clear Linux OS",
+ "version": "28120",
+ "version_best": "28120",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Clear Linux OS\"\nVERSION=1\nID=clear-linux-os\nID_LIKE=clear-linux-os\nVERSION_ID=28120\nPRETTY_NAME=\"Clear Linux OS\"\nANSI_COLOR=\"1;35\"\nHOME_URL=\"https://clearlinux.org\"\nSUPPORT_URL=\"https://clearlinux.org\"\nBUG_REPORT_URL=\"mailto:dev@lists.clearlinux.org\"\nPRIVACY_POLICY_URL=\"http://www.intel.com/privacy\"",
+ "/usr/lib/os-release": "NAME=\"Clear Linux OS\"\nVERSION=1\nID=clear-linux-os\nID_LIKE=clear-linux-os\nVERSION_ID=28120\nPRETTY_NAME=\"Clear Linux OS\"\nANSI_COLOR=\"1;35\"\nHOME_URL=\"https://clearlinux.org\"\nSUPPORT_URL=\"https://clearlinux.org\"\nBUG_REPORT_URL=\"mailto:dev@lists.clearlinux.org\"\nPRIVACY_POLICY_URL=\"http://www.intel.com/privacy\""
+ },
+ "name": "ClearLinux 28120",
+ "result": {
+ "distribution_release": "clear-linux-os",
+ "distribution": "Clear Linux OS",
+ "distribution_major_version": "28120",
+ "os_family": "ClearLinux",
+ "distribution_version": "28120"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/core_os_1911.5.0.json b/test/units/module_utils/facts/system/distribution/fixtures/core_os_1911.5.0.json
new file mode 100644
index 0000000..af43704
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/core_os_1911.5.0.json
@@ -0,0 +1,23 @@
+{
+ "name": "Core OS",
+ "input": {
+ "/usr/lib/os-release": "NAME=\"Container Linux by CoreOS\"\nID=coreos\nVERSION=1911.5.0\nVERSION_ID=1911.5.0\nBUILD_ID=2018-12-15-2317\nPRETTY_NAME=\"Container Linux by CoreOS 1911.5.0 (Rhyolite)\"\nANSI_COLOR=\"38;5;75\"\nHOME_URL=\"https://coreos.com/\"\nBUG_REPORT_URL=\"https://issues.coreos.com\"\nCOREOS_BOARD=\"amd64-usr\"",
+ "/etc/lsb-release": "DISTRIB_ID=CoreOS\nDISTRIB_RELEASE=1911.5.0\nDISTRIB_CODENAME=\"Rhyolite\"\nDISTRIB_DESCRIPTION=\"CoreOS 1911.5.0 (Rhyolite)\""
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "Rhyolite",
+ "id": "coreos",
+ "name": "CoreOS",
+ "version": "1911.5.0",
+ "version_best": "1911.5.0",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "platform.release": "",
+ "result": {
+ "distribution": "Coreos",
+ "distribution_major_version": "1911",
+ "distribution_version": "1911.5.0"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/core_os_976.0.0.json b/test/units/module_utils/facts/system/distribution/fixtures/core_os_976.0.0.json
new file mode 100644
index 0000000..ccd06d9
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/core_os_976.0.0.json
@@ -0,0 +1,23 @@
+{
+ "name": "Core OS",
+ "input": {
+ "/etc/os-release": "NAME=CoreOS\nID=coreos\nVERSION=976.0.0\nVERSION_ID=976.0.0\nBUILD_ID=2016-03-03-2324\nPRETTY_NAME=\"CoreOS 976.0.0 (Coeur Rouge)\"\nANSI_COLOR=\"1;32\"\nHOME_URL=\"https://coreos.com/\"\nBUG_REPORT_URL=\"https://github.com/coreos/bugs/issues\"",
+ "/etc/lsb-release": "DISTRIB_ID=CoreOS\nDISTRIB_RELEASE=976.0.0\nDISTRIB_CODENAME=\"Coeur Rouge\"\nDISTRIB_DESCRIPTION=\"CoreOS 976.0.0 (Coeur Rouge)\""
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "Coeur Rouge",
+ "id": "coreos",
+ "name": "CoreOS",
+ "version": "976.0.0",
+ "version_best": "976.0.0",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "platform.release": "",
+ "result": {
+ "distribution": "CoreOS",
+ "distribution_major_version": "976",
+ "distribution_version": "976.0.0"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_2.5.4.json b/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_2.5.4.json
new file mode 100644
index 0000000..ad9c3f7
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_2.5.4.json
@@ -0,0 +1,23 @@
+{
+ "name": "Cumulus Linux 2.5.4",
+ "input": {
+ "/etc/os-release": "NAME=\"Cumulus Linux\"\nVERSION_ID=2.5.4\nVERSION=\"2.5.4-6dc6e80-201510091936-build\"\nPRETTY_NAME=\"Cumulus Linux\"\nID=cumulus-linux\nID_LIKE=debian\nCPE_NAME=cpe:/o:cumulusnetworks:cumulus_linux:2.5.4-6dc6e80-201510091936-build\nHOME_URL=\"http://www.cumulusnetworks.com/\"\nSUPPORT_URL=\"http://support.cumulusnetworks.com/\""
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "cumulus-linux",
+ "name": "Cumulus Linux",
+ "version": "2.5.4",
+ "version_best": "2.5.4",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Cumulus Linux",
+ "distribution_major_version": "2",
+ "distribution_release": "2.5.4-6dc6e80-201510091936-build",
+ "os_family": "Debian",
+ "distribution_version": "2.5.4"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_3.7.3.json b/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_3.7.3.json
new file mode 100644
index 0000000..ec44af1
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/cumulus_linux_3.7.3.json
@@ -0,0 +1,23 @@
+{
+ "name": "Cumulus Linux 3.7.3",
+ "input": {
+ "/etc/os-release": "NAME=\"Cumulus Linux\"\nVERSION_ID=3.7.3\nVERSION=\"Cumulus Linux 3.7.3\"\nPRETTY_NAME=\"Cumulus Linux\"\nID=cumulus-linux\nID_LIKE=debian\nCPE_NAME=cpe:/o:cumulusnetworks:cumulus_linux:3.7.3\nHOME_URL=\"http://www.cumulusnetworks.com/\"\nSUPPORT_URL=\"http://support.cumulusnetworks.com/\""
+ },
+ "platform.dist": ["debian", "8.11", ""],
+ "distro": {
+ "codename": "",
+ "id": "cumulus-linux",
+ "name": "Cumulus Linux",
+ "version": "3.7.3",
+ "version_best": "3.7.3",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Cumulus Linux",
+ "distribution_major_version": "3",
+ "distribution_release": "Cumulus Linux 3.7.3",
+ "os_family": "Debian",
+ "distribution_version": "3.7.3"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/debian_10.json b/test/units/module_utils/facts/system/distribution/fixtures/debian_10.json
new file mode 100644
index 0000000..5ac3f45
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/debian_10.json
@@ -0,0 +1,42 @@
+{
+ "name": "Debian 10",
+ "distro": {
+ "codename": "buster",
+ "id": "debian",
+ "name": "Debian GNU/Linux",
+ "version": "10",
+ "version_best": "10",
+ "lsb_release_info": {
+ "distributor_id": "Debian",
+ "description": "Debian GNU/Linux 10 (buster)",
+ "release": "10",
+ "codename": "buster"
+ },
+ "os_release_info": {
+ "pretty_name": "Debian GNU/Linux 10 (buster)",
+ "name": "Debian GNU/Linux",
+ "version_id": "10",
+ "version": "10 (buster)",
+ "version_codename": "buster",
+ "id": "debian",
+ "home_url": "https://www.debian.org/",
+ "support_url": "https://www.debian.org/support",
+ "bug_report_url": "https://bugs.debian.org/",
+ "codename": "buster"
+ }
+ },
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Debian GNU/Linux 10 (buster)\"\nNAME=\"Debian GNU/Linux\"\nVERSION_ID=\"10\"\nVERSION=\"10 (buster)\"\nVERSION_CODENAME=buster\nID=debian\nHOME_URL=\"https://www.debian.org/\"\nSUPPORT_URL=\"https://www.debian.org/support\"\nBUG_REPORT_URL=\"https://bugs.debian.org/\"\n",
+ "/usr/lib/os-release": "PRETTY_NAME=\"Debian GNU/Linux 10 (buster)\"\nNAME=\"Debian GNU/Linux\"\nVERSION_ID=\"10\"\nVERSION=\"10 (buster)\"\nVERSION_CODENAME=buster\nID=debian\nHOME_URL=\"https://www.debian.org/\"\nSUPPORT_URL=\"https://www.debian.org/support\"\nBUG_REPORT_URL=\"https://bugs.debian.org/\"\n",
+ "/etc/debian_version": "10.7\n"
+ },
+ "platform.dist": ["debian", "10", "buster"],
+ "result": {
+ "distribution": "Debian",
+ "distribution_version": "10",
+ "distribution_release": "buster",
+ "distribution_major_version": "10",
+ "distribution_minor_version": "7",
+ "os_family": "Debian"
+ }
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/debian_7.9.json b/test/units/module_utils/facts/system/distribution/fixtures/debian_7.9.json
new file mode 100644
index 0000000..894c942
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/debian_7.9.json
@@ -0,0 +1,39 @@
+{
+ "name": "Debian 7.9",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Debian GNU/Linux 7 (wheezy)\"\nNAME=\"Debian GNU/Linux\"\nVERSION_ID=\"7\"\nVERSION=\"7 (wheezy)\"\nID=debian\nANSI_COLOR=\"1;31\"\nHOME_URL=\"http://www.debian.org/\"\nSUPPORT_URL=\"http://www.debian.org/support/\"\nBUG_REPORT_URL=\"http://bugs.debian.org/\""
+ },
+ "platform.dist": ["debian", "7.9", ""],
+ "distro": {
+ "codename": "wheezy",
+ "id": "debian",
+ "name": "Debian GNU/Linux",
+ "version": "7",
+ "version_best": "7.9",
+ "os_release_info": {
+ "name": "Debian GNU/Linux",
+ "ansi_color": "1;31",
+ "support_url": "http://www.debian.org/support/",
+ "version_id": "7",
+ "bug_report_url": "http://bugs.debian.org/",
+ "pretty_name": "Debian GNU/Linux 7 (wheezy)",
+ "version": "7 (wheezy)",
+ "codename": "wheezy",
+ "home_url": "http://www.debian.org/",
+ "id": "debian"
+ },
+ "lsb_release_info": {
+ "release": "7.9",
+ "codename": "wheezy",
+ "distributor_id": "Debian",
+ "description": "Debian GNU/Linux 7.9 (wheezy)"
+ }
+ },
+ "result": {
+ "distribution": "Debian",
+ "distribution_major_version": "7",
+ "distribution_release": "wheezy",
+ "os_family": "Debian",
+ "distribution_version": "7.9"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/debian_stretch_sid.json b/test/units/module_utils/facts/system/distribution/fixtures/debian_stretch_sid.json
new file mode 100644
index 0000000..2338830
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/debian_stretch_sid.json
@@ -0,0 +1,36 @@
+{
+ "name": "Debian stretch/sid",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Debian GNU/Linux stretch/sid\"\nNAME=\"Debian GNU/Linux\"\nID=debian\nHOME_URL=\"https://www.debian.org/\"\nSUPPORT_URL=\"https://www.debian.org/support\"\nBUG_REPORT_URL=\"https://bugs.debian.org/\"",
+ "/etc/debian_version": "stretch/sid\n"
+ },
+ "platform.dist": ["debian", "stretch/sid", ""],
+ "distro": {
+ "codename": "stretch",
+ "id": "debian",
+ "name": "Debian GNU/Linux",
+ "version": "9",
+ "version_best": "9.8",
+ "lsb_release_info": {
+ "release": "unstable",
+ "codename": "sid",
+ "distributor_id": "Debian",
+ "description": "Debian GNU/Linux stretch/sid"
+ },
+ "os_release_info": {
+ "name": "Debian GNU/Linux",
+ "support_url": "https://www.debian.org/support",
+ "bug_report_url": "https://bugs.debian.org/",
+ "pretty_name": "Debian GNU/Linux stretch/sid",
+ "home_url": "https://www.debian.org/",
+ "id": "debian"
+ }
+ },
+ "result": {
+ "distribution": "Debian",
+ "distribution_major_version": "9",
+ "distribution_release": "stretch",
+ "os_family": "Debian",
+ "distribution_version": "9.8"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/deepin_20.4.json b/test/units/module_utils/facts/system/distribution/fixtures/deepin_20.4.json
new file mode 100644
index 0000000..ca5d50d
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/deepin_20.4.json
@@ -0,0 +1,29 @@
+{
+ "name": "Deepin 20.4",
+ "distro": {
+ "codename": "apricot",
+ "id": "Deepin",
+ "name": "Deepin",
+ "version": "20.4",
+ "version_best": "20.4",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Deepin 20.4\"\nNAME=\"Deepin\"\nVERSION_ID=\"20.4\"\nVERSION=\"20.4\"\nVERSION_CODENAME=\"apricot\"\nID=Deepin\nHOME_URL=\"https://www.deepin.org/\"\nBUG_REPORT_URL=\"https://bbs.deepin.org/\"\n",
+ "/etc/lsb-release": "DISTRIB_ID=Deepin\nDISTRIB_RELEASE=20.4\nDISTRIB_DESCRIPTION=\"Deepin 20.4\"\nDISTRIB_CODENAME=apricot\n",
+ "/usr/lib/os-release": "PRETTY_NAME=\"Deepin 20.4\"\nNAME=\"Deepin\"\nVERSION_ID=\"20.4\"\nVERSION=\"20.4\"\nVERSION_CODENAME=\"apricot\"\nID=Deepin\nHOME_URL=\"https://www.deepin.org/\"\nBUG_REPORT_URL=\"https://bbs.deepin.org/\"\n"
+ },
+ "platform.dist": [
+ "Deepin",
+ "20.4",
+ "apricot"
+ ],
+ "result": {
+ "distribution": "Deepin",
+ "distribution_version": "20.4",
+ "distribution_release": "apricot",
+ "distribution_major_version": "20",
+ "os_family": "Debian"
+ }
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/devuan.json b/test/units/module_utils/facts/system/distribution/fixtures/devuan.json
new file mode 100644
index 0000000..d02fc2e
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/devuan.json
@@ -0,0 +1,23 @@
+{
+ "name": "Devuan",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Devuan GNU/Linux ascii\"\nNAME=\"Devuan GNU/Linux\"\nID=devuan\nHOME_URL=\"https://www.devuan.org/\"\nSUPPORT_URL=\"https://devuan.org/os/community\"\nBUG_REPORT_URL=\"https://bugs.devuan.org/\""
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "devuan",
+ "name": "Devuan GNU/Linux",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Devuan",
+ "distribution_major_version": "NA",
+ "distribution_release": "ascii",
+ "os_family": "Debian",
+ "distribution_version": "NA"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.2.2.json b/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.2.2.json
new file mode 100644
index 0000000..5b99a48
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.2.2.json
@@ -0,0 +1,25 @@
+{
+ "name": "DragonFly v5.2.0-RELEASE #3",
+ "input": {},
+ "platform.system": "DragonFly",
+ "platform.release": "5.2-RELEASE",
+ "command_output": {
+ "/sbin/sysctl -n kern.version": "DragonFly v5.2.0-RELEASE #1: Mon Apr 9 00:17:53 EDT 2018\nroot@www.shiningsilence.com:/usr/obj/home/justin/release/5_2/sys/X86_64_GENERIC"
+ },
+ "distro": {
+ "codename": "",
+ "id": "dragonfly",
+ "name": "DragonFly",
+ "version": "5.2",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "DragonFly",
+ "distribution_major_version": "5",
+ "distribution_release": "5.2-RELEASE",
+ "os_family": "DragonFly",
+ "distribution_version": "5.2.0"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.6.2.json b/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.6.2.json
new file mode 100644
index 0000000..90ec620
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/dragonfly_5.6.2.json
@@ -0,0 +1,25 @@
+{
+ "name": "DragonFly v5.6.2-RELEASE #3",
+ "input": {},
+ "platform.system": "DragonFly",
+ "platform.release": "5.6-RELEASE",
+ "command_output": {
+ "/sbin/sysctl -n kern.version": "DragonFly v5.6.2-RELEASE #3: Sat Aug 10 10:28:36 EDT 2019\nroot@www.shiningsilence.com:/usr/obj/home/justin/release/5_6/sys/X86_64_GENERIC"
+ },
+ "distro": {
+ "codename": "",
+ "id": "dragonfly",
+ "name": "DragonFly",
+ "version": "5.2",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "DragonFly",
+ "distribution_major_version": "5",
+ "distribution_release": "5.6-RELEASE",
+ "os_family": "DragonFly",
+ "distribution_version": "5.6.2"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/eurolinux_8.5.json b/test/units/module_utils/facts/system/distribution/fixtures/eurolinux_8.5.json
new file mode 100644
index 0000000..add1b73
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/eurolinux_8.5.json
@@ -0,0 +1,46 @@
+{
+ "name": "EuroLinux 8.5",
+ "distro": {
+ "codename": "Tirana",
+ "id": "eurolinux",
+ "name": "EuroLinux",
+ "version": "8.5",
+ "version_best": "8.5",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "EuroLinux",
+ "version": "8.5 (Tirana)",
+ "id": "eurolinux",
+ "id_like": "rhel fedora centos",
+ "version_id": "8.5",
+ "platform_id": "platform:el8",
+ "pretty_name": "EuroLinux 8.5 (Tirana)",
+ "ansi_color": "0;34",
+ "cpe_name": "cpe:/o:eurolinux:eurolinux:8",
+ "home_url": "https://www.euro-linux.com/",
+ "bug_report_url": "https://github.com/EuroLinux/eurolinux-distro-bugs-and-rfc/",
+ "redhat_support_product": "EuroLinux",
+ "redhat_support_product_version": "8",
+ "codename": "Tirana"
+ }
+ },
+ "input": {
+ "/etc/redhat-release": "EuroLinux release 8.5 (Tirana) \n",
+ "/etc/system-release": "EuroLinux release 8.5 (Tirana) \n",
+ "/etc/os-release": "NAME=\"EuroLinux\"\nVERSION=\"8.5 (Tirana)\"\nID=\"eurolinux\"\nID_LIKE=\"rhel fedora centos\"\nVERSION_ID=\"8.5\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"EuroLinux 8.5 (Tirana)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:eurolinux:eurolinux:8\"\nHOME_URL=\"https://www.euro-linux.com/\"\nBUG_REPORT_URL=\"https://github.com/EuroLinux/eurolinux-distro-bugs-and-rfc/\"\nREDHAT_SUPPORT_PRODUCT=\"EuroLinux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\n",
+ "/usr/lib/os-release": "NAME=\"EuroLinux\"\nVERSION=\"8.5 (Tirana)\"\nID=\"eurolinux\"\nID_LIKE=\"rhel fedora centos\"\nVERSION_ID=\"8.5\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"EuroLinux 8.5 (Tirana)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:eurolinux:eurolinux:8\"\nHOME_URL=\"https://www.euro-linux.com/\"\nBUG_REPORT_URL=\"https://github.com/EuroLinux/eurolinux-distro-bugs-and-rfc/\"\nREDHAT_SUPPORT_PRODUCT=\"EuroLinux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\n"
+ },
+ "platform.dist": [
+ "eurolinux",
+ "8.5",
+ "Tirana"
+ ],
+ "result": {
+ "distribution": "EuroLinux",
+ "distribution_version": "8.5",
+ "distribution_release": "Tirana",
+ "distribution_major_version": "8",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.18.0-348.2.1.el8_5.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/fedora_22.json b/test/units/module_utils/facts/system/distribution/fixtures/fedora_22.json
new file mode 100644
index 0000000..cec68d4
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/fedora_22.json
@@ -0,0 +1,25 @@
+{
+ "name": "Fedora 22",
+ "platform.dist": ["fedora", "22", "Twenty Two"],
+ "distro": {
+ "codename": "Twenty Two",
+ "id": "fedora",
+ "name": "Fedora",
+ "version": "22",
+ "version_best": "22",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/redhat-release": "Fedora release 22 (Twenty Two)\n",
+ "/etc/os-release": "NAME=Fedora\nVERSION=\"22 (Twenty Two)\"\nID=fedora\nVERSION_ID=22\nPRETTY_NAME=\"Fedora 22 (Twenty Two)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:fedoraproject:fedora:22\"\nHOME_URL=\"https://fedoraproject.org/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_BUGZILLA_PRODUCT=\"Fedora\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=22\nREDHAT_SUPPORT_PRODUCT=\"Fedora\"\nREDHAT_SUPPORT_PRODUCT_VERSION=22\nPRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy\n",
+ "/etc/system-release": "Fedora release 22 (Twenty Two)\n"
+ },
+ "result": {
+ "distribution_release": "Twenty Two",
+ "distribution": "Fedora",
+ "distribution_major_version": "22",
+ "os_family": "RedHat",
+ "distribution_version": "22"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/fedora_25.json b/test/units/module_utils/facts/system/distribution/fixtures/fedora_25.json
new file mode 100644
index 0000000..70b5bc3
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/fedora_25.json
@@ -0,0 +1,25 @@
+{
+ "platform.dist": ["fedora", "25", "Rawhide"],
+ "distro": {
+ "codename": "Rawhide",
+ "id": "fedora",
+ "name": "Fedora",
+ "version": "25",
+ "version_best": "25",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/redhat-release": "Fedora release 25 (Rawhide)\n",
+ "/etc/os-release": "NAME=Fedora\nVERSION=\"25 (Workstation Edition)\"\nID=fedora\nVERSION_ID=25\nPRETTY_NAME=\"Fedora 25 (Workstation Edition)\"\nANSI_COLOR=\"0;34\"\nCPE_NAME=\"cpe:/o:fedoraproject:fedora:25\"\nHOME_URL=\"https://fedoraproject.org/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_BUGZILLA_PRODUCT=\"Fedora\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=rawhide\nREDHAT_SUPPORT_PRODUCT=\"Fedora\"\nREDHAT_SUPPORT_PRODUCT_VERSION=rawhide\nPRIVACY_POLICY_URL=https://fedoraproject.org/wiki/Legal:PrivacyPolicy\nVARIANT=\"Workstation Edition\"\nVARIANT_ID=workstation\n",
+ "/etc/system-release": "Fedora release 25 (Rawhide)\n"
+ },
+ "name": "Fedora 25",
+ "result": {
+ "distribution_release": "Rawhide",
+ "distribution": "Fedora",
+ "distribution_major_version": "25",
+ "os_family": "RedHat",
+ "distribution_version": "25"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/fedora_31.json b/test/units/module_utils/facts/system/distribution/fixtures/fedora_31.json
new file mode 100644
index 0000000..e6d905e
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/fedora_31.json
@@ -0,0 +1,55 @@
+{
+ "name": "Fedora 31",
+ "distro": {
+ "codename": "",
+ "id": "fedora",
+ "name": "Fedora",
+ "version": "31",
+ "version_best": "31",
+ "lsb_release_info": {
+ "lsb_version": ":core-4.1-amd64:core-4.1-noarch",
+ "distributor_id": "Fedora",
+ "description": "Fedora release 31 (Thirty One)",
+ "release": "31",
+ "codename": "ThirtyOne"
+ },
+ "os_release_info": {
+ "name": "Fedora",
+ "version": "31 (Workstation Edition)",
+ "id": "fedora",
+ "version_id": "31",
+ "version_codename": "",
+ "platform_id": "platform:f31",
+ "pretty_name": "Fedora 31 (Workstation Edition)",
+ "ansi_color": "0;34",
+ "logo": "fedora-logo-icon",
+ "cpe_name": "cpe:/o:fedoraproject:fedora:31",
+ "home_url": "https://fedoraproject.org/",
+ "documentation_url": "https://docs.fedoraproject.org/en-US/fedora/f31/system-administrators-guide/",
+ "support_url": "https://fedoraproject.org/wiki/Communicating_and_getting_help",
+ "bug_report_url": "https://bugzilla.redhat.com/",
+ "redhat_bugzilla_product": "Fedora",
+ "redhat_bugzilla_product_version": "31",
+ "redhat_support_product": "Fedora",
+ "redhat_support_product_version": "31",
+ "privacy_policy_url": "https://fedoraproject.org/wiki/Legal:PrivacyPolicy",
+ "variant": "Workstation Edition",
+ "variant_id": "workstation",
+ "codename": ""
+ }
+ },
+ "input": {
+ "/etc/redhat-release": "Fedora release 31 (Thirty One)\n",
+ "/etc/system-release": "Fedora release 31 (Thirty One)\n",
+ "/etc/os-release": "NAME=Fedora\nVERSION=\"31 (Workstation Edition)\"\nID=fedora\nVERSION_ID=31\nVERSION_CODENAME=\"\"\nPLATFORM_ID=\"platform:f31\"\nPRETTY_NAME=\"Fedora 31 (Workstation Edition)\"\nANSI_COLOR=\"0;34\"\nLOGO=fedora-logo-icon\nCPE_NAME=\"cpe:/o:fedoraproject:fedora:31\"\nHOME_URL=\"https://fedoraproject.org/\"\nDOCUMENTATION_URL=\"https://docs.fedoraproject.org/en-US/fedora/f31/system-administrators-guide/\"\nSUPPORT_URL=\"https://fedoraproject.org/wiki/Communicating_and_getting_help\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_BUGZILLA_PRODUCT=\"Fedora\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=31\nREDHAT_SUPPORT_PRODUCT=\"Fedora\"\nREDHAT_SUPPORT_PRODUCT_VERSION=31\nPRIVACY_POLICY_URL=\"https://fedoraproject.org/wiki/Legal:PrivacyPolicy\"\nVARIANT=\"Workstation Edition\"\nVARIANT_ID=workstation\n",
+ "/usr/lib/os-release": "NAME=Fedora\nVERSION=\"31 (Workstation Edition)\"\nID=fedora\nVERSION_ID=31\nVERSION_CODENAME=\"\"\nPLATFORM_ID=\"platform:f31\"\nPRETTY_NAME=\"Fedora 31 (Workstation Edition)\"\nANSI_COLOR=\"0;34\"\nLOGO=fedora-logo-icon\nCPE_NAME=\"cpe:/o:fedoraproject:fedora:31\"\nHOME_URL=\"https://fedoraproject.org/\"\nDOCUMENTATION_URL=\"https://docs.fedoraproject.org/en-US/fedora/f31/system-administrators-guide/\"\nSUPPORT_URL=\"https://fedoraproject.org/wiki/Communicating_and_getting_help\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\nREDHAT_BUGZILLA_PRODUCT=\"Fedora\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=31\nREDHAT_SUPPORT_PRODUCT=\"Fedora\"\nREDHAT_SUPPORT_PRODUCT_VERSION=31\nPRIVACY_POLICY_URL=\"https://fedoraproject.org/wiki/Legal:PrivacyPolicy\"\nVARIANT=\"Workstation Edition\"\nVARIANT_ID=workstation\n"
+ },
+ "platform.dist": ["fedora", "31", ""],
+ "result": {
+ "distribution": "Fedora",
+ "distribution_version": "31",
+ "distribution_release": "",
+ "distribution_major_version": "31",
+ "os_family": "RedHat"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/flatcar_3139.2.0.json b/test/units/module_utils/facts/system/distribution/fixtures/flatcar_3139.2.0.json
new file mode 100644
index 0000000..3cd7fa7
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/flatcar_3139.2.0.json
@@ -0,0 +1,43 @@
+{
+ "name": "Flatcar Container Linux by Kinvolk 3139.2.0",
+ "distro": {
+ "codename": "",
+ "id": "flatcar",
+ "name": "Flatcar Container Linux by Kinvolk",
+ "version": "3139.2.0",
+ "version_best": "3139.2.0",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "Flatcar Container Linux by Kinvolk",
+ "id": "flatcar",
+ "id_like": "coreos",
+ "version": "3139.2.0",
+ "version_id": "3139.2.0",
+ "build_id": "2022-04-05-1803",
+ "pretty_name": "Flatcar Container Linux by Kinvolk 3139.2.0 (Oklo)",
+ "ansi_color": "38;5;75",
+ "home_url": "https://flatcar-linux.org/",
+ "bug_report_url": "https://issues.flatcar-linux.org",
+ "flatcar_board": "amd64-usr",
+ "cpe_name": "cpe:2.3:o:flatcar-linux:flatcar_linux:3139.2.0:*:*:*:*:*:*:*"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Flatcar Container Linux by Kinvolk\"\nID=flatcar\nID_LIKE=coreos\nVERSION=3139.2.0\nVERSION_ID=3139.2.0\nBUILD_ID=2022-04-05-1803\nPRETTY_NAME=\"Flatcar Container Linux by Kinvolk 3139.2.0 (Oklo)\"\nANSI_COLOR=\"38;5;75\"\nHOME_URL=\"https://flatcar-linux.org/\"\nBUG_REPORT_URL=\"https://issues.flatcar-linux.org\"\nFLATCAR_BOARD=\"amd64-usr\"\nCPE_NAME=\"cpe:2.3:o:flatcar-linux:flatcar_linux:3139.2.0:*:*:*:*:*:*:*\"\n",
+ "/etc/lsb-release": "DISTRIB_ID=\"Flatcar Container Linux by Kinvolk\"\nDISTRIB_RELEASE=3139.2.0\nDISTRIB_CODENAME=\"Oklo\"\nDISTRIB_DESCRIPTION=\"Flatcar Container Linux by Kinvolk 3139.2.0 (Oklo)\"\n",
+ "/usr/lib/os-release": "NAME=\"Flatcar Container Linux by Kinvolk\"\nID=flatcar\nID_LIKE=coreos\nVERSION=3139.2.0\nVERSION_ID=3139.2.0\nBUILD_ID=2022-04-05-1803\nPRETTY_NAME=\"Flatcar Container Linux by Kinvolk 3139.2.0 (Oklo)\"\nANSI_COLOR=\"38;5;75\"\nHOME_URL=\"https://flatcar-linux.org/\"\nBUG_REPORT_URL=\"https://issues.flatcar-linux.org\"\nFLATCAR_BOARD=\"amd64-usr\"\nCPE_NAME=\"cpe:2.3:o:flatcar-linux:flatcar_linux:3139.2.0:*:*:*:*:*:*:*\"\n"
+ },
+ "platform.dist": [
+ "flatcar",
+ "3139.2.0",
+ ""
+ ],
+ "result": {
+ "distribution": "Flatcar",
+ "distribution_version": "3139.2.0",
+ "distribution_release": "NA",
+ "distribution_major_version": "3139",
+ "os_family": "Flatcar"
+ },
+ "platform.release": "5.15.32-flatcar"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/kali_2019.1.json b/test/units/module_utils/facts/system/distribution/fixtures/kali_2019.1.json
new file mode 100644
index 0000000..096b66f
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/kali_2019.1.json
@@ -0,0 +1,25 @@
+{
+ "name": "Kali 2019.1",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Kali GNU/Linux Rolling\"\nNAME=\"Kali GNU/Linux\"\nID=kali\nVERSION=\"2019.1\"\nVERSION_ID=\"2019.1\"\nID_LIKE=debian\nANSI_COLOR=\"1;31\"\nHOME_URL=\"https://www.kali.org/\"\nSUPPORT_URL=\"https://forums.kali.org/\"\nBUG_REPORT_URL=\"https://bugs.kali.org/\"\n",
+ "/etc/lsb-release": "DISTRIB_ID=Kali\nDISTRIB_RELEASE=kali-rolling\nDISTRIB_CODENAME=kali-rolling\nDISTRIB_DESCRIPTION=\"Kali GNU/Linux Rolling\"\n",
+ "/usr/lib/os-release": "PRETTY_NAME=\"Kali GNU/Linux Rolling\"\nNAME=\"Kali GNU/Linux\"\nID=kali\nVERSION=\"2019.1\"\nVERSION_ID=\"2019.1\"\nID_LIKE=debian\nANSI_COLOR=\"1;31\"\nHOME_URL=\"https://www.kali.org/\"\nSUPPORT_URL=\"https://forums.kali.org/\"\nBUG_REPORT_URL=\"https://bugs.kali.org/\"\n"
+ },
+ "platform.dist": ["kali", "2019.1", ""],
+ "distro": {
+ "codename": "kali-rolling",
+ "id": "kali",
+ "name": "Kali GNU/Linux Rolling",
+ "version": "2019.1",
+ "version_best": "2019.1",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Kali",
+ "distribution_version": "2019.1",
+ "distribution_release": "kali-rolling",
+ "distribution_major_version": "2019",
+ "os_family": "Debian"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/kde_neon_16.04.json b/test/units/module_utils/facts/system/distribution/fixtures/kde_neon_16.04.json
new file mode 100644
index 0000000..5ff59c7
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/kde_neon_16.04.json
@@ -0,0 +1,42 @@
+{
+ "platform.dist": ["neon", "16.04", "xenial"],
+ "distro": {
+ "codename": "xenial",
+ "id": "neon",
+ "name": "KDE neon",
+ "version": "16.04",
+ "version_best": "16.04",
+ "os_release_info": {
+ "support_url": "http://help.ubuntu.com/",
+ "version_codename": "xenial",
+ "pretty_name": "Ubuntu 16.04.6 LTS",
+ "home_url": "http://www.ubuntu.com/",
+ "bug_report_url": "http://bugs.launchpad.net/ubuntu/",
+ "version": "16.04.6 LTS (Xenial Xerus)",
+ "version_id": "16.04",
+ "id": "ubuntu",
+ "ubuntu_codename": "xenial",
+ "codename": "xenial",
+ "name": "Ubuntu",
+ "id_like": "debian"
+ },
+ "lsb_release_info": {
+ "description": "Ubuntu 16.04.6 LTS",
+ "release": "16.04",
+ "distributor_id": "Ubuntu",
+ "codename": "xenial"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"KDE neon\"\nVERSION=\"5.8\"\nID=neon\nID_LIKE=\"ubuntu debian\"\nPRETTY_NAME=\"KDE neon User Edition 5.8\"\nVERSION_ID=\"16.04\"\nHOME_URL=\"http://neon.kde.org/\"\nSUPPORT_URL=\"http://neon.kde.org/\"\nBUG_REPORT_URL=\"http://bugs.kde.org/\"\nVERSION_CODENAME=xenial\nUBUNTU_CODENAME=xenial\n",
+ "/etc/lsb-release": "DISTRIB_ID=neon\nDISTRIB_RELEASE=16.04\nDISTRIB_CODENAME=xenial\nDISTRIB_DESCRIPTION=\"KDE neon User Edition 5.8\"\n"
+ },
+ "name": "KDE neon 16.04",
+ "result": {
+ "distribution_release": "xenial",
+ "distribution": "KDE neon",
+ "distribution_major_version": "16",
+ "os_family": "Debian",
+ "distribution_version": "16.04"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/kylin_linux_advanced_server_v10.json b/test/units/module_utils/facts/system/distribution/fixtures/kylin_linux_advanced_server_v10.json
new file mode 100644
index 0000000..e929b5a
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/kylin_linux_advanced_server_v10.json
@@ -0,0 +1,38 @@
+{
+ "name": "Kylin Linux Advanced Server V10",
+ "distro": {
+ "codename": "Sword",
+ "id": "kylin",
+ "name": "Kylin Linux Advanced Server",
+ "version": "V10",
+ "version_best": "V10",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "Kylin Linux Advanced Server",
+ "version": "V10 (Sword)",
+ "id": "kylin",
+ "version_id": "V10",
+ "pretty_name": "Kylin Linux Advanced Server V10 (Sword)",
+ "ansi_color": "0;31",
+ "codename": "Sword"
+ }
+ },
+ "input": {
+ "/etc/system-release": "Kylin Linux Advanced Server release V10 (Sword)\n",
+ "/etc/os-release": "NAME=\"Kylin Linux Advanced Server\"\nVERSION=\"V10 (Sword)\"\nID=\"kylin\"\nVERSION_ID=\"V10\"\nPRETTY_NAME=\"Kylin Linux Advanced Server V10 (Sword)\"\nANSI_COLOR=\"0;31\"\n\n",
+ "/etc/lsb-release": "DISTRIB_ID=Kylin\nDISTRIB_RELEASE=V10\nDISTRIB_CODENAME=juniper\nDISTRIB_DESCRIPTION=\"Kylin V10\"\nDISTRIB_KYLIN_RELEASE=V10\nDISTRIB_VERSION_TYPE=enterprise\nDISTRIB_VERSION_MODE=normal\n"
+ },
+ "platform.dist": [
+ "kylin",
+ "V10",
+ "Sword"
+ ],
+ "result": {
+ "distribution": "Kylin Linux Advanced Server",
+ "distribution_version": "V10",
+ "distribution_release": "Sword",
+ "distribution_major_version": "V10",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.19.90-24.4.v2101.ky10.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_18.2.json b/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_18.2.json
new file mode 100644
index 0000000..74e628e
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_18.2.json
@@ -0,0 +1,25 @@
+{
+ "platform.dist": ["linuxmint", "18.2", "sonya"],
+ "input": {
+ "/etc/os-release": "NAME=\"Linux Mint\"\nVERSION=\"18.2 (Sonya)\"\nID=linuxmint\nID_LIKE=ubuntu\nPRETTY_NAME=\"Linux Mint 18.2\"\nVERSION_ID=\"18.2\"\nHOME_URL=\"http://www.linuxmint.com/\"\nSUPPORT_URL=\"http://forums.linuxmint.com/\"\nBUG_REPORT_URL=\"http://bugs.launchpad.net/linuxmint/\"\nVERSION_CODENAME=sonya\nUBUNTU_CODENAME=xenial\n",
+ "/usr/lib/os-release": "NAME=\"Linux Mint\"\nVERSION=\"18.2 (Sonya)\"\nID=linuxmint\nID_LIKE=ubuntu\nPRETTY_NAME=\"Linux Mint 18.2\"\nVERSION_ID=\"18.2\"\nHOME_URL=\"http://www.linuxmint.com/\"\nSUPPORT_URL=\"http://forums.linuxmint.com/\"\nBUG_REPORT_URL=\"http://bugs.launchpad.net/linuxmint/\"\nVERSION_CODENAME=sonya\nUBUNTU_CODENAME=xenial\n",
+ "/etc/lsb-release": "DISTRIB_ID=LinuxMint\nDISTRIB_RELEASE=18.2\nDISTRIB_CODENAME=sonya\nDISTRIB_DESCRIPTION=\"Linux Mint 18.2 Sonya\"\n"
+ },
+ "result": {
+ "distribution_release": "sonya",
+ "distribution": "Linux Mint",
+ "distribution_major_version": "18",
+ "os_family": "Debian",
+ "distribution_version": "18.2"
+ },
+ "name": "Linux Mint 18.2",
+ "distro": {
+ "codename": "sonya",
+ "version": "18.2",
+ "id": "linuxmint",
+ "version_best": "18.2",
+ "name": "Linux Mint",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_19.1.json b/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_19.1.json
new file mode 100644
index 0000000..7712856
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/linux_mint_19.1.json
@@ -0,0 +1,24 @@
+{
+ "platform.dist": ["linuxmint", "19.1", "tessa"],
+ "input": {
+ "/usr/lib/os-release": "NAME=\"Linux Mint\"\nVERSION=\"19.1 (Tessa)\"\nID=linuxmint\nID_LIKE=ubuntu\nPRETTY_NAME=\"Linux Mint 19.1\"\nVERSION_ID=\"19.1\"\nHOME_URL=\"https://www.linuxmint.com/\"\nSUPPORT_URL=\"https://forums.ubuntu.com/\"\nBUG_REPORT_URL=\"http: //linuxmint-troubleshooting-guide.readthedocs.io/en/latest/\"\nPRIVACY_POLICY_URL=\"https://www.linuxmint.com/\"\nVERSION_CODENAME=tessa\nUBUNTU_CODENAME=bionic\n",
+ "/etc/lsb-release": "DISTRIB_ID=LinuxMint\nDISTRIB_RELEASE=19.1\nDISTRIB_CODENAME=tessa\nDISTRIB_DESCRIPTION=\"Linux Mint 19.1 Tessa\"\n"
+ },
+ "result": {
+ "distribution_release": "tessa",
+ "distribution": "Linux Mint",
+ "distribution_major_version": "19",
+ "os_family": "Debian",
+ "distribution_version": "19.1"
+ },
+ "name": "Linux Mint 19.1",
+ "distro": {
+ "codename": "tessa",
+ "version": "19.1",
+ "id": "linuxmint",
+ "version_best": "19.1",
+ "name": "Linux Mint",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/netbsd_8.2.json b/test/units/module_utils/facts/system/distribution/fixtures/netbsd_8.2.json
new file mode 100644
index 0000000..65c4ed6
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/netbsd_8.2.json
@@ -0,0 +1,25 @@
+{
+ "name": "NetBSD 8.2 (GENERIC) #0",
+ "input": {},
+ "platform.system": "NetBSD",
+ "platform.release": "8.2",
+ "command_output": {
+ "/sbin/sysctl -n kern.version": "NetBSD 8.2 (GENERIC) #0: Tue Mar 31 05:08:40 UTC 2020\n mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC"
+ },
+ "distro": {
+ "codename": "",
+ "id": "netbsd",
+ "name": "NetBSD",
+ "version": "8.2",
+ "version_best": "8.2",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "NetBSD",
+ "distribution_major_version": "8",
+ "distribution_release": "8.2",
+ "os_family": "NetBSD",
+ "distribution_version": "8.2"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/nexenta_3.json b/test/units/module_utils/facts/system/distribution/fixtures/nexenta_3.json
new file mode 100644
index 0000000..bdc942b
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/nexenta_3.json
@@ -0,0 +1,25 @@
+{
+ "name": "Nexenta 3",
+ "uname_v": "NexentaOS_134f",
+ "result": {
+ "distribution_release": "Open Storage Appliance v3.1.6",
+ "distribution": "Nexenta",
+ "os_family": "Solaris",
+ "distribution_version": "3.1.6"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "platform.release:": "",
+ "input": {
+ "/etc/release": " Open Storage Appliance v3.1.6\n Copyright (c) 2014 Nexenta Systems, Inc. All Rights Reserved.\n Copyright (c) 2011 Oracle. All Rights Reserved.\n Use is subject to license terms.\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/nexenta_4.json b/test/units/module_utils/facts/system/distribution/fixtures/nexenta_4.json
new file mode 100644
index 0000000..d24e9bc
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/nexenta_4.json
@@ -0,0 +1,24 @@
+{
+ "name": "Nexenta 4",
+ "uname_v": "NexentaOS_4:cd604cd066",
+ "result": {
+ "distribution_release": "Open Storage Appliance 4.0.3-FP2",
+ "distribution": "Nexenta",
+ "os_family": "Solaris",
+ "distribution_version": "4.0.3-FP2"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " Open Storage Appliance 4.0.3-FP2\n Copyright (c) 2014 Nexenta Systems, Inc. All Rights Reserved.\n Copyright (c) 2010 Oracle. All Rights Reserved.\n Use is subject to license terms.\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/omnios.json b/test/units/module_utils/facts/system/distribution/fixtures/omnios.json
new file mode 100644
index 0000000..8bb2b44
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/omnios.json
@@ -0,0 +1,24 @@
+{
+ "name": "OmniOS",
+ "uname_v": "omnios-10b9c79",
+ "result": {
+ "distribution_release": "OmniOS v11 r151012",
+ "distribution": "OmniOS",
+ "os_family": "Solaris",
+ "distribution_version": "r151012"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " OmniOS v11 r151012\n Copyright 2014 OmniTI Computer Consulting, Inc. All rights reserved.\n Use is subject to license terms.\n\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/openeuler_20.03.json b/test/units/module_utils/facts/system/distribution/fixtures/openeuler_20.03.json
new file mode 100644
index 0000000..8310386
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/openeuler_20.03.json
@@ -0,0 +1,28 @@
+{
+ "platform.dist": [
+ "openeuler",
+ "20.03",
+ "LTS"
+ ],
+ "input": {
+ "/etc/os-release": "NAME=\"openEuler\"\nVERSION=\"20.03 (LTS)\"\nID=\"openEuler\"\nVERSION_ID=\"20.03\"\nPRETTY_NAME=\"openEuler 20.03 (LTS)\"\nANSI_COLOR=\"0;31\"\n\n",
+ "/etc/system-release": "openEuler release 20.03 (LTS)\n"
+ },
+ "result": {
+ "distribution_release": "LTS",
+ "distribution": "openEuler",
+ "distribution_major_version": "20",
+ "os_family": "RedHat",
+ "distribution_version": "20.03"
+ },
+ "name": "openEuler 20.03",
+ "distro": {
+ "codename": "LTS",
+ "version": "20.03",
+ "id": "openeuler",
+ "version_best": "20.03",
+ "name": "openEuler",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/openindiana.json b/test/units/module_utils/facts/system/distribution/fixtures/openindiana.json
new file mode 100644
index 0000000..a055bb0
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/openindiana.json
@@ -0,0 +1,24 @@
+{
+ "name": "OpenIndiana",
+ "uname_v": "oi_151a9",
+ "result": {
+ "distribution_release": "OpenIndiana Development oi_151.1.9 X86 (powered by illumos)",
+ "distribution": "OpenIndiana",
+ "os_family": "Solaris",
+ "distribution_version": "oi_151a9"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " OpenIndiana Development oi_151.1.9 X86 (powered by illumos)\n Copyright 2011 Oracle and/or its affiliates. All rights reserved.\n Use is subject to license terms.\n Assembled 17 January 2014\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/opensuse_13.2.json b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_13.2.json
new file mode 100644
index 0000000..76d3a33
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_13.2.json
@@ -0,0 +1,24 @@
+{
+ "name": "openSUSE 13.2",
+ "input": {
+ "/etc/SuSE-release": "openSUSE 13.2 (x86_64)\nVERSION = 13.2\nCODENAME = Harlequin\n# /etc/SuSE-release is deprecated and will be removed in the future, use /etc/os-release instead",
+ "/etc/os-release": "NAME=openSUSE\nVERSION=\"13.2 (Harlequin)\"\nVERSION_ID=\"13.2\"\nPRETTY_NAME=\"openSUSE 13.2 (Harlequin) (x86_64)\"\nID=opensuse\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:opensuse:13.2\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://opensuse.org/\"\nID_LIKE=\"suse\""
+ },
+ "platform.dist": ["SuSE", "13.2", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "opensuse-harlequin",
+ "name": "openSUSE Harlequin",
+ "version": "13.2",
+ "version_best": "13.2",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "openSUSE",
+ "distribution_major_version": "13",
+ "distribution_release": "2",
+ "os_family": "Suse",
+ "distribution_version": "13.2"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.0.json b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.0.json
new file mode 100644
index 0000000..54f1265
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.0.json
@@ -0,0 +1,23 @@
+{
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "opensuse-leap",
+ "name": "openSUSE Leap",
+ "version": "15.0",
+ "version_best": "15.0",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"openSUSE Leap\"\n# VERSION=\"15.0\"\nID=opensuse-leap\nID_LIKE=\"suse opensuse\"\nVERSION_ID=\"15.0\"\nPRETTY_NAME=\"openSUSE Leap 15.0\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:leap:15.0\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://www.opensuse.org/\"\n"
+ },
+ "name": "openSUSE Leap 15.0",
+ "result": {
+ "distribution_release": "0",
+ "distribution": "openSUSE Leap",
+ "distribution_major_version": "15",
+ "os_family": "Suse",
+ "distribution_version": "15.0"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.1.json b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.1.json
new file mode 100644
index 0000000..d029423
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_15.1.json
@@ -0,0 +1,36 @@
+{
+ "name": "openSUSE Leap 15.1",
+ "distro": {
+ "codename": "",
+ "id": "opensuse-leap",
+ "name": "openSUSE Leap",
+ "version": "15.1",
+ "version_best": "15.1",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "openSUSE Leap",
+ "version": "15.1",
+ "codename": "",
+ "id": "opensuse-leap",
+ "id_like": "suse opensuse",
+ "version_id": "15.1",
+ "pretty_name": "openSUSE Leap 15.1",
+ "ansi_color": "0;32",
+ "cpe_name": "cpe:/o:opensuse:leap:15.1",
+ "bug_report_url": "https://bugs.opensuse.org",
+ "home_url": "https://www.opensuse.org/"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"openSUSE Leap\"\nVERSION=\"15.1\"\nID=\"opensuse-leap\"\nID_LIKE=\"suse opensuse\"\nVERSION_ID=\"15.1\"\nPRETTY_NAME=\"openSUSE Leap 15.1\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:leap:15.1\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://www.opensuse.org/\"\n",
+ "/usr/lib/os-release": "NAME=\"openSUSE Leap\"\nVERSION=\"15.1\"\nID=\"opensuse-leap\"\nID_LIKE=\"suse opensuse\"\nVERSION_ID=\"15.1\"\nPRETTY_NAME=\"openSUSE Leap 15.1\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:leap:15.1\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://www.opensuse.org/\"\n"
+ },
+ "platform.dist": ["opensuse-leap", "15.1", ""],
+ "result": {
+ "distribution": "openSUSE Leap",
+ "distribution_version": "15.1",
+ "distribution_release": "1",
+ "distribution_major_version": "15",
+ "os_family": "Suse"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_42.1.json b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_42.1.json
new file mode 100644
index 0000000..2142932
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_leap_42.1.json
@@ -0,0 +1,24 @@
+{
+ "name": "openSUSE Leap 42.1",
+ "input": {
+ "/etc/os-release": "NAME=\"openSUSE Leap\"\nVERSION=\"42.1\"\nVERSION_ID=\"42.1\"\nPRETTY_NAME=\"openSUSE Leap 42.1 (x86_64)\"\nID=opensuse\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:opensuse:42.1\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://opensuse.org/\"\nID_LIKE=\"suse\"",
+ "/etc/SuSE-release": "openSUSE 42.1 (x86_64)\nVERSION = 42.1\nCODENAME = Malachite\n# /etc/SuSE-release is deprecated and will be removed in the future, use /etc/os-release instead"
+ },
+ "platform.dist": ["SuSE", "42.1", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "opensuse-leap",
+ "name": "openSUSE Leap",
+ "version": "42.1",
+ "version_best": "42.1",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "openSUSE Leap",
+ "distribution_major_version": "42",
+ "distribution_release": "1",
+ "os_family": "Suse",
+ "distribution_version": "42.1"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/opensuse_tumbleweed_20160917.json b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_tumbleweed_20160917.json
new file mode 100644
index 0000000..db1a26c
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/opensuse_tumbleweed_20160917.json
@@ -0,0 +1,23 @@
+{
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "opensuse-tumbleweed",
+ "name": "openSUSE Tumbleweed",
+ "version": "20160917",
+ "version_best": "20160917",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"openSUSE Tumbleweed\"\n# VERSION=\"20160917\"\nID=opensuse\nID_LIKE=\"suse\"\nVERSION_ID=\"20160917\"\nPRETTY_NAME=\"openSUSE Tumbleweed\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:opensuse:tumbleweed:20160917\"\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\nHOME_URL=\"https://www.opensuse.org/\"\n"
+ },
+ "name": "openSUSE Tumbleweed 20160917",
+ "result": {
+ "distribution_release": "",
+ "distribution": "openSUSE Tumbleweed",
+ "distribution_major_version": "20160917",
+ "os_family": "Suse",
+ "distribution_version": "20160917"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/osmc.json b/test/units/module_utils/facts/system/distribution/fixtures/osmc.json
new file mode 100644
index 0000000..98a4923
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/osmc.json
@@ -0,0 +1,23 @@
+{
+ "name": "OSMC",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Open Source Media Center\"\nNAME=\"OSMC\"\nVERSION=\"March 2022\"\nVERSION_ID=\"2022.03-1\"\nID=osmc\nID_LIKE=debian\nANSI_COLOR=\"1;31\"\nHOME_URL=\"https://www.osmc.tv\"\nSUPPORT_URL=\"https://www.osmc.tv\"\nBUG_REPORT_URL=\"https://www.osmc.tv\""
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "osmc",
+ "name": "OSMC",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "OSMC",
+ "distribution_major_version": "NA",
+ "distribution_release": "NA",
+ "os_family": "Debian",
+ "distribution_version": "March 2022"
+ }
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/pardus_19.1.json b/test/units/module_utils/facts/system/distribution/fixtures/pardus_19.1.json
new file mode 100644
index 0000000..daf8f6e
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/pardus_19.1.json
@@ -0,0 +1,41 @@
+{
+ "name": "Pardus GNU/Linux 19.1",
+ "distro": {
+ "codename": "ondokuz",
+ "id": "pradus",
+ "name": "Pardus GNU/Linux",
+ "version": "19.1",
+ "version_best": "19.1",
+ "lsb_release_info": {
+ "release": "19.1",
+ "codename": "ondokuz",
+ "distributor_id": "Pardus",
+ "description": "Pardus GNU/Linux Ondokuz"
+ },
+ "os_release_info": {
+ "pardus_codename": "ondokuz",
+ "name": "Pardus GNU/Linux",
+ "version_codename": "ondokuz",
+ "id_like": "debian",
+ "version_id": "19.1",
+ "bug_report_url": "https://talep.pardus.org.tr/",
+ "pretty_name": "Pardus GNU/Linux Ondokuz",
+ "version": "19.1 (Ondokuz)",
+ "codename": "ondokuz",
+ "home_url": "https://www.pardus.org.tr/",
+ "id": "pardus",
+ "support_url": "https://forum.pardus.org.tr/"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Pardus GNU/Linux\"\nVERSION=\"19.1 (Ondokuz)\"\nID=pardus\nID_LIKE=debian\nPRETTY_NAME=\"Pardus GNU/Linux Ondokuz\"\nVERSION_ID=\"19.1\"\nHOME_URL=\"https://www.pardus.org.tr/\"\nSUPPORT_URL=\"https://forum.pardus.org.tr/\"\nBUG_REPORT_URL=\"https://talep.pardus.org.tr/\"\nVERSION_CODENAME=ondokuz\nPARDUS_CODENAME=ondokuz"
+ },
+ "platform.dist": ["debian", "10.0", ""],
+ "result": {
+ "distribution": "Pardus GNU/Linux",
+ "distribution_version": "19.1",
+ "distribution_release": "ondokuz",
+ "distribution_major_version": "19",
+ "os_family": "Debian"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/parrot_4.8.json b/test/units/module_utils/facts/system/distribution/fixtures/parrot_4.8.json
new file mode 100644
index 0000000..fd10ff6
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/parrot_4.8.json
@@ -0,0 +1,25 @@
+{
+ "name": "Parrot 4.8",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"Parrot GNU/Linux 4.8\"\nNAME=\"Parrot GNU/Linux\"\nID=parrot\nVERSION=\"4.8\"\nVERSION_ID=\"4.8\"\nID_LIKE=debian\nHOME_URL=\"https://www.parrotlinux.org/\"\nSUPPORT_URL=\"https://community.parrotlinux.org/\"\nBUG_REPORT_URL=\"https://nest.parrot.sh/\"\n",
+ "/etc/lsb-release": "DISTRIB_ID=Parrot\nDISTRIB_RELEASE=4.8\nDISTRIB_CODENAME=rolling\nDISTRIB_DESCRIPTION=\"Parrot 4.8\"\n",
+ "/usr/lib/os-release": "PRETTY_NAME=\"Parrot GNU/Linux 4.8\"\nNAME=\"Parrot GNU/Linux\"\nID=parrot\nVERSION=\"4.8\"\nVERSION_ID=\"4.8\"\nID_LIKE=debian\nHOME_URL=\"https://www.parrotlinux.org/\"\nSUPPORT_URL=\"https://community.parrotlinux.org/\"\nBUG_REPORT_URL=\"https://nest.parrot.sh/\"\n"
+ },
+ "platform.dist": ["parrot", "4.8", ""],
+ "distro": {
+ "codename": "rolling",
+ "id": "parrot",
+ "name": "Parrot GNU/Linux",
+ "version": "4.8",
+ "version_best": "4.8",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Parrot",
+ "distribution_version": "4.8",
+ "distribution_release": "rolling",
+ "distribution_major_version": "4",
+ "os_family": "Debian"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/pop_os_20.04.json b/test/units/module_utils/facts/system/distribution/fixtures/pop_os_20.04.json
new file mode 100644
index 0000000..d3184ef
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/pop_os_20.04.json
@@ -0,0 +1,29 @@
+{
+ "name": "Pop!_OS 20.04",
+ "distro": {
+ "codename": "focal",
+ "id": "pop",
+ "name": "Pop!_OS",
+ "version": "20.04",
+ "version_best": "20.04",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Pop!_OS\"\nVERSION=\"20.04\"\nID=pop\nID_LIKE=\"ubuntu debian\"\nPRETTY_NAME=\"Pop!_OS 20.04\"\nVERSION_ID=\"20.04\"\nHOME_URL=\"https://system76.com/pop\"\nSUPPORT_URL=\"http://support.system76.com\"\nBUG_REPORT_URL=\"https://github.com/pop-os/pop/issues\"\nPRIVACY_POLICY_URL=\"https://system76.com/privacy\"\nVERSION_CODENAME=focal\nUBUNTU_CODENAME=focal\nLOGO=distributor-logo-pop-os\n",
+ "/etc/lsb-release": "DISTRIB_ID=Pop\nDISTRIB_RELEASE=20.04\nDISTRIB_CODENAME=focal\nDISTRIB_DESCRIPTION=\"Pop!_OS 20.04\"\n",
+ "/usr/lib/os-release": "NAME=\"Pop!_OS\"\nVERSION=\"20.04\"\nID=pop\nID_LIKE=\"ubuntu debian\"\nPRETTY_NAME=\"Pop!_OS 20.04\"\nVERSION_ID=\"20.04\"\nHOME_URL=\"https://system76.com/pop\"\nSUPPORT_URL=\"http://support.system76.com\"\nBUG_REPORT_URL=\"https://github.com/pop-os/pop/issues\"\nPRIVACY_POLICY_URL=\"https://system76.com/privacy\"\nVERSION_CODENAME=focal\nUBUNTU_CODENAME=focal\nLOGO=distributor-logo-pop-os\n"
+ },
+ "platform.dist": [
+ "pop",
+ "20.04",
+ "focal"
+ ],
+ "result": {
+ "distribution": "Pop!_OS",
+ "distribution_version": "20.04",
+ "distribution_release": "focal",
+ "distribution_major_version": "20",
+ "os_family": "Debian"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/redhat_6.7.json b/test/units/module_utils/facts/system/distribution/fixtures/redhat_6.7.json
new file mode 100644
index 0000000..27a77d0
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/redhat_6.7.json
@@ -0,0 +1,25 @@
+{
+ "name": "RedHat 6.7",
+ "platform.dist": ["redhat", "6.7", "Santiago"],
+ "distro": {
+ "codename": "Santiago",
+ "id": "rhel",
+ "name": "RedHat Enterprise Linux",
+ "version": "6.7",
+ "version_best": "6.7",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/redhat-release": "Red Hat Enterprise Linux Server release 6.7 (Santiago)\n",
+ "/etc/lsb-release": "LSB_VERSION=base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch\n",
+ "/etc/system-release": "Red Hat Enterprise Linux Server release 6.7 (Santiago)\n"
+ },
+ "result": {
+ "distribution_release": "Santiago",
+ "distribution": "RedHat",
+ "distribution_major_version": "6",
+ "os_family": "RedHat",
+ "distribution_version": "6.7"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.2.json b/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.2.json
new file mode 100644
index 0000000..3900f82
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.2.json
@@ -0,0 +1,25 @@
+{
+ "name": "RedHat 7.2",
+ "platform.dist": ["redhat", "7.2", "Maipo"],
+ "distro": {
+ "codename": "Maipo",
+ "id": "rhel",
+ "name": "RedHat Enterprise Linux",
+ "version": "7.2",
+ "version_best": "7.2",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/redhat-release": "Red Hat Enterprise Linux Server release 7.2 (Maipo)\n",
+ "/etc/os-release": "NAME=\"Red Hat Enterprise Linux Server\"\nVERSION=\"7.2 (Maipo)\"\nID=\"rhel\"\nID_LIKE=\"fedora\"\nVERSION_ID=\"7.2\"\nPRETTY_NAME=\"Red Hat Enterprise Linux Server 7.2 (Maipo)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:redhat:enterprise_linux:7.2:GA:server\"\nHOME_URL=\"https://www.redhat.com/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n\nREDHAT_BUGZILLA_PRODUCT=\"Red Hat Enterprise Linux 7\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=7.2\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"7.2\"\n",
+ "/etc/system-release": "Red Hat Enterprise Linux Server release 7.2 (Maipo)\n"
+ },
+ "result": {
+ "distribution_release": "Maipo",
+ "distribution": "RedHat",
+ "distribution_major_version": "7",
+ "os_family": "RedHat",
+ "distribution_version": "7.2"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.7.json b/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.7.json
new file mode 100644
index 0000000..b240efc
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/redhat_7.7.json
@@ -0,0 +1,43 @@
+{
+ "name": "RedHat 7.7",
+ "distro": {
+ "codename": "Maipo",
+ "id": "rhel",
+ "name": "Red Hat Enterprise Linux Server",
+ "version": "7.7",
+ "version_best": "7.7",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "Red Hat Enterprise Linux Server",
+ "version": "7.7 (Maipo)",
+ "id": "rhel",
+ "id_like": "fedora",
+ "variant": "Server",
+ "variant_id": "server",
+ "version_id": "7.7",
+ "pretty_name": "Red Hat Enterprise Linux Server 7.7 (Maipo)",
+ "ansi_color": "0;31",
+ "cpe_name": "cpe:/o:redhat:enterprise_linux:7.7:GA:server",
+ "home_url": "https://www.redhat.com/",
+ "bug_report_url": "https://bugzilla.redhat.com/",
+ "redhat_bugzilla_product": "Red Hat Enterprise Linux 7",
+ "redhat_bugzilla_product_version": "7.7",
+ "redhat_support_product": "Red Hat Enterprise Linux",
+ "redhat_support_product_version": "7.7",
+ "codename": "Maipo"
+ }
+ },
+ "input": {
+ "/etc/redhat-release": "Red Hat Enterprise Linux Server release 7.7 (Maipo)\n",
+ "/etc/system-release": "Red Hat Enterprise Linux Server release 7.7 (Maipo)\n",
+ "/etc/os-release": "NAME=\"Red Hat Enterprise Linux Server\"\nVERSION=\"7.7 (Maipo)\"\nID=\"rhel\"\nID_LIKE=\"fedora\"\nVARIANT=\"Server\"\nVARIANT_ID=\"server\"\nVERSION_ID=\"7.7\"\nPRETTY_NAME=\"Red Hat Enterprise Linux Server 7.7 (Maipo)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:redhat:enterprise_linux:7.7:GA:server\"\nHOME_URL=\"https://www.redhat.com/\"\nBUG_REPORT_URL=\"https://bugzilla.redhat.com/\"\n\nREDHAT_BUGZILLA_PRODUCT=\"Red Hat Enterprise Linux 7\"\nREDHAT_BUGZILLA_PRODUCT_VERSION=7.7\nREDHAT_SUPPORT_PRODUCT=\"Red Hat Enterprise Linux\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"7.7\"\n"
+ },
+ "platform.dist": ["rhel", "7.7", "Maipo"],
+ "result": {
+ "distribution": "RedHat",
+ "distribution_version": "7.7",
+ "distribution_release": "Maipo",
+ "distribution_major_version": "7",
+ "os_family": "RedHat"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/rockylinux_8_3.json b/test/units/module_utils/facts/system/distribution/fixtures/rockylinux_8_3.json
new file mode 100644
index 0000000..8c3ff76
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/rockylinux_8_3.json
@@ -0,0 +1,46 @@
+{
+ "name": "Rocky 8.3",
+ "distro": {
+ "codename": "",
+ "id": "rocky",
+ "name": "Rocky Linux",
+ "version": "8.3",
+ "version_best": "8.3",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "Rocky Linux",
+ "version": "8.3",
+ "id": "rocky",
+ "id_like": "rhel fedora",
+ "version_id": "8.3",
+ "platform_id": "platform:el8",
+ "pretty_name": "Rocky Linux 8.3",
+ "ansi_color": "0;31",
+ "cpe_name": "cpe:/o:rocky:rocky:8",
+ "home_url": "https://rockylinux.org/",
+ "bug_report_url": "https://bugs.rockylinux.org/",
+ "rocky_support_product": "Rocky Linux",
+ "rocky_support_product_version": "8"
+ }
+ },
+ "input": {
+ "/etc/redhat-release": "Rocky Linux release 8.3\n",
+ "/etc/system-release": "Rocky Linux release 8.3\n",
+ "/etc/rocky-release": "Rocky Linux release 8.3\n",
+ "/etc/os-release": "NAME=\"Rocky Linux\"\nVERSION=\"8.3\"\nID=\"rocky\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8.3\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"Rocky Linux 8.3\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:rocky:rocky:8\"\nHOME_URL=\"https://rockylinux.org/\"\nBUG_REPORT_URL=\"https://bugs.rockylinux.org/\"\nROCKY_SUPPORT_PRODUCT=\"Rocky Linux\"\nROCKY_SUPPORT_PRODUCT_VERSION=\"8\"\n",
+ "/usr/lib/os-release": "NAME=\"Rocky Linux\"\nVERSION=\"8.3\"\nID=\"rocky\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8.3\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"Rocky Linux 8.3\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:rocky:rocky:8\"\nHOME_URL=\"https://rockylinux.org/\"\nBUG_REPORT_URL=\"https://bugs.rockylinux.org/\"\nROCKY_SUPPORT_PRODUCT=\"Rocky Linux\"\nROCKY_SUPPORT_PRODUCT_VERSION=\"8\"\n"
+ },
+ "platform.dist": [
+ "rocky",
+ "8.3",
+ ""
+ ],
+ "result": {
+ "distribution": "Rocky",
+ "distribution_version": "8.3",
+ "distribution_release": "NA",
+ "distribution_major_version": "8",
+ "os_family": "RedHat"
+ },
+ "platform.release": "4.18.0-240.22.1.el8.x86_64"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/sles_11.3.json b/test/units/module_utils/facts/system/distribution/fixtures/sles_11.3.json
new file mode 100644
index 0000000..be71f1c
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/sles_11.3.json
@@ -0,0 +1,23 @@
+{
+ "name": "SLES 11.3",
+ "input": {
+ "/etc/SuSE-release": "SUSE Linux Enterprise Server 11 (x86_64)\nVERSION = 11\nPATCHLEVEL = 3"
+ },
+ "platform.dist": ["SuSE", "11", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "sles",
+ "name": "SUSE Linux Enterprise Server",
+ "version": "11",
+ "version_best": "11",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "SLES",
+ "distribution_major_version": "11",
+ "distribution_release": "3",
+ "os_family": "Suse",
+ "distribution_version": "11.3"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/sles_11.4.json b/test/units/module_utils/facts/system/distribution/fixtures/sles_11.4.json
new file mode 100644
index 0000000..3e4012a
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/sles_11.4.json
@@ -0,0 +1,24 @@
+{
+ "name": "SLES 11.4",
+ "input": {
+ "/etc/SuSE-release": "\nSUSE Linux Enterprise Server 11 (x86_64)\nVERSION = 11\nPATCHLEVEL = 4",
+ "/etc/os-release": "NAME=\"SLES\"\nVERSION=\"11.4\"\nVERSION_ID=\"11.4\"\nPRETTY_NAME=\"SUSE Linux Enterprise Server 11 SP4\"\nID=\"sles\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:suse:sles:11:4\""
+ },
+ "platform.dist": ["SuSE", "11", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "sles",
+ "name": "SUSE Linux Enterprise Server",
+ "version": "11.4",
+ "version_best": "11.4",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "SLES",
+ "distribution_major_version": "11",
+ "distribution_release": "4",
+ "os_family": "Suse",
+ "distribution_version": "11.4"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp0.json b/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp0.json
new file mode 100644
index 0000000..e84bbe5
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp0.json
@@ -0,0 +1,24 @@
+{
+ "name": "SLES 12 SP0",
+ "input": {
+ "/etc/SuSE-release": "\nSUSE Linux Enterprise Server 12 (x86_64)\nVERSION = 12\nPATCHLEVEL = 0\n# This file is deprecated and will be removed in a future service pack or release.\n# Please check /etc/os-release for details about this release.",
+ "/etc/os-release": "NAME=\"SLES\"\nVERSION=\"12\"\nVERSION_ID=\"12\"\nPRETTY_NAME=\"SUSE Linux Enterprise Server 12\"\nID=\"sles\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:suse:sles:12\""
+ },
+ "platform.dist": ["SuSE", "12", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "sles",
+ "name": "SUSE Linux Enterprise Server",
+ "version": "12",
+ "version_best": "12",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "SLES",
+ "distribution_major_version": "12",
+ "distribution_release": "0",
+ "os_family": "Suse",
+ "distribution_version": "12"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp1.json b/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp1.json
new file mode 100644
index 0000000..c78d53d
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/sles_12_sp1.json
@@ -0,0 +1,24 @@
+{
+ "name": "SLES 12 SP1",
+ "input": {
+ "/etc/SuSE-release": "\nSUSE Linux Enterprise Server 12 (x86_64)\nVERSION = 12\nPATCHLEVEL = 0\n# This file is deprecated and will be removed in a future service pack or release.\n# Please check /etc/os-release for details about this release.",
+ "/etc/os-release": "NAME=\"SLES\"\nVERSION=\"12-SP1\"\nVERSION_ID=\"12.1\"\nPRETTY_NAME=\"SUSE Linux Enterprise Server 12 SP1\"\nID=\"sles\"\nANSI_COLOR=\"0;32\"\nCPE_NAME=\"cpe:/o:suse:sles:12:sp1\""
+ },
+ "platform.dist": ["SuSE", "12", "x86_64"],
+ "distro": {
+ "codename": "",
+ "id": "sles",
+ "name": "SUSE Linux Enterprise Server",
+ "version": "12.1",
+ "version_best": "12.1",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "SLES",
+ "distribution_major_version": "12",
+ "distribution_release": "1",
+ "os_family": "Suse",
+ "distribution_version": "12.1"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/smartos_global_zone.json b/test/units/module_utils/facts/system/distribution/fixtures/smartos_global_zone.json
new file mode 100644
index 0000000..ae01a10
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/smartos_global_zone.json
@@ -0,0 +1,24 @@
+{
+ "name": "SmartOS Global Zone",
+ "uname_v": "joyent_20160330T234717Z",
+ "result": {
+ "distribution_release": "SmartOS 20160330T234717Z x86_64",
+ "distribution": "SmartOS",
+ "os_family": "Solaris",
+ "distribution_version": "joyent_20160330T234717Z"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " SmartOS 20160330T234717Z x86_64\n Copyright 2010 Sun Microsystems, Inc. All Rights Reserved.\n Copyright 2010-2012 Joyent, Inc. All Rights Reserved.\n Use is subject to license terms.\n\n Built with the following components:\n\n[\n { \"repo\": \"smartos-live\", \"branch\": \"release-20160331\", \"rev\": \"a77c410f2afe6dc9853a915733caec3609cc50f1\", \"commit_date\": \"1459340323\", \"url\": \"git@github.com:joyent/smartos-live.git\" }\n , { \"repo\": \"illumos-joyent\", \"branch\": \"release-20160331\", \"rev\": \"ab664c06caf06e9ce7586bff956e7709df1e702e\", \"commit_date\": \"1459362533\", \"url\": \"/root/data/jenkins/workspace/smartos/MG/build/illumos-joyent\" }\n , { \"repo\": \"illumos-extra\", \"branch\": \"release-20160331\", \"rev\": \"cc723855bceace3df7860b607c9e3827d47e0ff4\", \"commit_date\": \"1458153188\", \"url\": \"/root/data/jenkins/workspace/smartos/MG/build/illumos-extra\" }\n , { \"repo\": \"kvm\", \"branch\": \"release-20160331\", \"rev\": \"a8befd521c7e673749c64f118585814009fe4b73\", \"commit_date\": \"1450081968\", \"url\": \"/root/data/jenkins/workspace/smartos/MG/build/illumos-kvm\" }\n , { \"repo\": \"kvm-cmd\", \"branch\": \"release-20160331\", \"rev\": \"c1a197c8e4582c68739ab08f7e3198b2392c9820\", \"commit_date\": \"1454723558\", \"url\": \"/root/data/jenkins/workspace/smartos/MG/build/illumos-kvm-cmd\" }\n , { \"repo\": \"mdata-client\", \"branch\": \"release-20160331\", \"rev\": \"58158c44603a3316928975deccc5d10864832770\", \"commit_date\": \"1429917227\", \"url\": \"/root/data/jenkins/workspace/smartos/MG/build/mdata-client\" }\n]\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/smartos_zone.json b/test/units/module_utils/facts/system/distribution/fixtures/smartos_zone.json
new file mode 100644
index 0000000..8f20113
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/smartos_zone.json
@@ -0,0 +1,25 @@
+{
+ "name": "SmartOS Zone",
+ "uname_v": "joyent_20160330T234717Z",
+ "result": {
+ "distribution_release": "SmartOS x86_64",
+ "distribution": "SmartOS",
+ "os_family": "Solaris",
+ "distribution_version": "14.3.0"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " SmartOS x86_64\n Copyright 2010 Sun Microsystems, Inc. All Rights Reserved.\n Copyright 2010-2013 Joyent, Inc. All Rights Reserved.\n Use is subject to license terms.\n See joyent_20141002T182809Z for assembly date and time.\n",
+ "/etc/product": "Name: Joyent Instance\nImage: base64 14.3.0\nDocumentation: http://wiki.joyent.com/jpc2/Base+Instance\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/smgl_na.json b/test/units/module_utils/facts/system/distribution/fixtures/smgl_na.json
new file mode 100644
index 0000000..f3436b8
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/smgl_na.json
@@ -0,0 +1,23 @@
+{
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "smgl",
+ "name": "Source Mage GNU/Linux",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/sourcemage-release": "Source Mage GNU/Linux x86_64-pc-linux-gnu\nInstalled from tarball using chroot image (Grimoire 0.61-rc) on Thu May 17 17:31:37 UTC 2012\n"
+ },
+ "name": "SMGL NA",
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "SMGL",
+ "distribution_major_version": "NA",
+ "os_family": "SMGL",
+ "distribution_version": "NA"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/solaris_10.json b/test/units/module_utils/facts/system/distribution/fixtures/solaris_10.json
new file mode 100644
index 0000000..de1dbdc
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/solaris_10.json
@@ -0,0 +1,25 @@
+{
+ "name": "Solaris 10",
+ "uname_r": "5.10",
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " Oracle Solaris 10 1/13 s10x_u11wos_24a X86\n Copyright (c) 1983, 2013, Oracle and/or its affiliates. All rights reserved.\n Assembled 17 January 2013\n"
+ },
+ "platform.system": "SunOS",
+ "result": {
+ "distribution_release": "Oracle Solaris 10 1/13 s10x_u11wos_24a X86",
+ "distribution": "Solaris",
+ "os_family": "Solaris",
+ "distribution_major_version": "10",
+ "distribution_version": "10"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.3.json b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.3.json
new file mode 100644
index 0000000..056abe4
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.3.json
@@ -0,0 +1,25 @@
+{
+ "name": "Solaris 11.3",
+ "uname_r": "5.11",
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " Oracle Solaris 11.3 X86\n Copyright (c) 1983, 2018, Oracle and/or its affiliates. All rights reserved.\n Assembled 09 May 2018\n"
+ },
+ "platform.system": "SunOS",
+ "result": {
+ "distribution_release": "Oracle Solaris 11.3 X86",
+ "distribution": "Solaris",
+ "os_family": "Solaris",
+ "distribution_major_version": "11",
+ "distribution_version": "11.3"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.4.json b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.4.json
new file mode 100644
index 0000000..462d550
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.4.json
@@ -0,0 +1,35 @@
+{
+ "name": "Solaris 11.4",
+ "uname_r": "5.11",
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {
+ "support_url": "https://support.oracle.com/",
+ "name": "Oracle Solaris",
+ "pretty_name": "Oracle Solaris 11.4",
+ "version": "11.4",
+ "id": "solaris",
+ "version_id": "11.4",
+ "build_id": "11.4.0.0.1.15.0",
+ "home_url": "https://www.oracle.com/solaris/",
+ "cpe_name": "cpe:/o:oracle:solaris:11:4"
+ },
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " Oracle Solaris 11.4 SPARC\n Copyright (c) 1983, 2018, Oracle and/or its affiliates. All rights reserved.\n Assembled 14 September 2018\n"
+ },
+ "platform.system": "SunOS",
+ "result": {
+ "distribution_release": "Oracle Solaris 11.4 SPARC",
+ "distribution": "Solaris",
+ "os_family": "Solaris",
+ "distribution_major_version": "11",
+ "distribution_version": "11.4"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.json b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.json
new file mode 100644
index 0000000..749b8bc
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/solaris_11.json
@@ -0,0 +1,26 @@
+{
+ "name": "Solaris 11",
+ "uname_v": "11.0",
+ "uname_r": "5.11",
+ "result": {
+ "distribution_release": "Oracle Solaris 11 11/11 X86",
+ "distribution": "Solaris",
+ "os_family": "Solaris",
+ "distribution_major_version": "11",
+ "distribution_version": "11"
+ },
+ "platform.dist": ["", "", ""],
+ "distro": {
+ "codename": "",
+ "id": "",
+ "name": "",
+ "version": "",
+ "version_best": "",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/release": " Oracle Solaris 11 11/11 X86\n Copyright (c) 1983, 2011, Oracle and/or its affiliates. All rights reserved.\n Assembled 18 October 2011\n"
+ },
+ "platform.system": "SunOS"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/steamos_2.0.json b/test/units/module_utils/facts/system/distribution/fixtures/steamos_2.0.json
new file mode 100644
index 0000000..7cb9c12
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/steamos_2.0.json
@@ -0,0 +1,40 @@
+{
+ "name": "SteamOS 2.0",
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"SteamOS GNU/Linux 2.0 (brewmaster)\"\nNAME=\"SteamOS GNU/Linux\"\nVERSION_ID=\"2\"\nVERSION=\"2 (brewmaster)\"\nID=steamos\nID_LIKE=debian\nHOME_URL=\"http://www.steampowered.com/\"\nSUPPORT_URL=\"http://support.steampowered.com/\"\nBUG_REPORT_URL=\"http://support.steampowered.com/\"",
+ "/etc/lsb-release": "DISTRIB_ID=SteamOS\nDISTRIB_RELEASE=2.0\nDISTRIB_CODENAME=brewmaster\nDISTRIB_DESCRIPTION=\"SteamOS 2.0\""
+ },
+ "platform.dist": ["Steamos", "2.0", "brewmaster"],
+ "distro": {
+ "codename": "brewmaster",
+ "id": "steamos",
+ "name": "SteamOS GNU/Linux",
+ "version": "2.0",
+ "version_best": "2.0",
+ "os_release_info": {
+ "bug_report_url": "http://support.steampowered.com/",
+ "id_like": "debian",
+ "version_id": "2",
+ "pretty_name": "SteamOS GNU/Linux 2.0 (brewmaster)",
+ "version": "2 (brewmaster)",
+ "home_url": "http://www.steampowered.com/",
+ "name": "SteamOS GNU/Linux",
+ "support_url": "http://support.steampowered.com/",
+ "codename": "brewmaster",
+ "id": "steamos"
+ },
+ "lsb_release_info": {
+ "codename": "brewmaster",
+ "description": "SteamOS 2.0",
+ "distributor_id": "SteamOS",
+ "release": "2.0"
+ }
+ },
+ "result": {
+ "distribution": "SteamOS",
+ "distribution_major_version": "2",
+ "distribution_release": "brewmaster",
+ "os_family": "Debian",
+ "distribution_version": "2.0"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/tencentos_3_1.json b/test/units/module_utils/facts/system/distribution/fixtures/tencentos_3_1.json
new file mode 100644
index 0000000..f1051dd
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/tencentos_3_1.json
@@ -0,0 +1,50 @@
+{
+ "name": "TencentOS 3.1",
+ "distro": {
+ "codename": "Final",
+ "id": "tencentos",
+ "name": "TencentOS Server",
+ "version": "3.1",
+ "version_best": "3.1",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "TencentOS Server",
+ "version": "3.1 (Final)",
+ "id": "tencentos",
+ "id_like": "rhel fedora centos",
+ "version_id": "3.1",
+ "platform_id": "platform:el8",
+ "pretty_name": "TencentOS Server 3.1 (Final)",
+ "ansi_color": "0;31",
+ "cpe_name": "cpe:/o:tencentos:tencentos:3",
+ "home_url": "https://tlinux.qq.com/",
+ "bug_report_url": "https://tlinux.qq.com/",
+ "centos_mantisbt_project": "CentOS-8",
+ "centos_mantisbt_project_version": "8",
+ "redhat_support_product": "centos",
+ "redhat_support_product_version": "8",
+ "name_orig": "CentOS Linux",
+ "codename": "Final"
+ }
+ },
+ "input": {
+ "/etc/centos-release": "NAME=\"TencentOS Server\"\nVERSION=\"3.1 (Final)\"\nID=\"tencentos\"\nID_LIKE=\"rhel fedora centos\"\nVERSION_ID=\"3.1\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"TencentOS Server 3.1 (Final)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:tencentos:tencentos:3\"\nHOME_URL=\"https://tlinux.qq.com/\"\nBUG_REPORT_URL=\"https://tlinux.qq.com/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-8\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"8\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\nNAME_ORIG=\"CentOS Linux\"\n",
+ "/etc/redhat-release": "CentOS Linux release 8.4.2105 (Core)\n",
+ "/etc/system-release": "NAME=\"TencentOS Server\"\nVERSION=\"3.1 (Final)\"\nID=\"tencentos\"\nID_LIKE=\"rhel fedora centos\"\nVERSION_ID=\"3.1\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"TencentOS Server 3.1 (Final)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:tencentos:tencentos:3\"\nHOME_URL=\"https://tlinux.qq.com/\"\nBUG_REPORT_URL=\"https://tlinux.qq.com/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-8\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"8\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\nNAME_ORIG=\"CentOS Linux\"\n",
+ "/etc/os-release": "NAME=\"TencentOS Server\"\nVERSION=\"3.1 (Final)\"\nID=\"tencentos\"\nID_LIKE=\"rhel fedora centos\"\nVERSION_ID=\"3.1\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"TencentOS Server 3.1 (Final)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:tencentos:tencentos:3\"\nHOME_URL=\"https://tlinux.qq.com/\"\nBUG_REPORT_URL=\"https://tlinux.qq.com/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-8\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"8\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"8\"\nNAME_ORIG=\"CentOS Linux\"\n",
+ "/usr/lib/os-release": "NAME=\"CentOS Linux\"\nVERSION=\"8\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"8\"\nPLATFORM_ID=\"platform:el8\"\nPRETTY_NAME=\"CentOS Linux 8\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:8\"\nHOME_URL=\"https://centos.org/\"\nBUG_REPORT_URL=\"https://bugs.centos.org/\"\nCENTOS_MANTISBT_PROJECT=\"CentOS-8\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"8\"\n"
+ },
+ "platform.dist": [
+ "tencentos",
+ "3.1",
+ "Final"
+ ],
+ "result": {
+ "distribution": "TencentOS",
+ "distribution_version": "3.1",
+ "distribution_release": "Final",
+ "distribution_major_version": "3",
+ "os_family": "RedHat"
+ },
+ "platform.release": "5.4.32-19-0001"
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/truenas_12.0rc1.json b/test/units/module_utils/facts/system/distribution/fixtures/truenas_12.0rc1.json
new file mode 100644
index 0000000..9a9efe3
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/truenas_12.0rc1.json
@@ -0,0 +1,39 @@
+{
+ "name": "FreeBSD 12.2",
+ "distro": {
+ "codename": "",
+ "id": "freebsd",
+ "name": "FreeBSD",
+ "version": "12.2",
+ "version_best": "12.2",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "FreeBSD",
+ "version": "12.2-PRERELEASE",
+ "version_id": "12.2",
+ "id": "freebsd",
+ "ansi_color": "0;31",
+ "pretty_name": "FreeBSD 12.2-PRERELEASE",
+ "cpe_name": "cpe:/o:freebsd:freebsd:12.2",
+ "home_url": "https://FreeBSD.org/",
+ "bug_report_url": "https://bugs.FreeBSD.org/"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=FreeBSD\nVERSION=12.2-PRERELEASE\nVERSION_ID=12.2\nID=freebsd\nANSI_COLOR=\"0;31\"\nPRETTY_NAME=\"FreeBSD 12.2-PRERELEASE\"\nCPE_NAME=cpe:/o:freebsd:freebsd:12.2\nHOME_URL=https://FreeBSD.org/\nBUG_REPORT_URL=https://bugs.FreeBSD.org/\n"
+ },
+ "platform.dist": [
+ "freebsd",
+ "12.2",
+ ""
+ ],
+ "result": {
+ "distribution": "FreeBSD",
+ "distribution_version": "12.2",
+ "distribution_release": "12.2-PRERELEASE",
+ "distribution_major_version": "12",
+ "os_family": "FreeBSD"
+ },
+ "platform.system": "FreeBSD",
+ "platform.release": "12.2-PRERELEASE"
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_10.04_guess.json b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_10.04_guess.json
new file mode 100644
index 0000000..38a6040
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_10.04_guess.json
@@ -0,0 +1,23 @@
+{
+ "name": "Ubuntu 10.04 guess",
+ "input": {
+ "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=10.04\nDISTRIB_CODENAME=lucid\nDISTRIB_DESCRIPTION=\"Ubuntu 10.04.4 LTS"
+ },
+ "platform.dist": ["Ubuntu", "10.04", "lucid"],
+ "distro": {
+ "codename": "lucid",
+ "id": "ubuntu",
+ "name": "Ubuntu",
+ "version": "10.04",
+ "version_best": "10.04.1",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Ubuntu",
+ "distribution_major_version": "10",
+ "distribution_release": "lucid",
+ "os_family": "Debian",
+ "distribution_version": "10.04"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_12.04.json b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_12.04.json
new file mode 100644
index 0000000..01203b5
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_12.04.json
@@ -0,0 +1,24 @@
+{
+ "name": "Ubuntu 12.04",
+ "input": {
+ "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=12.04\nDISTRIB_CODENAME=precise\nDISTRIB_DESCRIPTION=\"Ubuntu 12.04.5 LTS\"",
+ "/etc/os-release": "NAME=\"Ubuntu\"\nVERSION=\"12.04.5 LTS, Precise Pangolin\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu precise (12.04.5 LTS)\"\nVERSION_ID=\"12.04\""
+ },
+ "platform.dist": ["Ubuntu", "12.04", "precise"],
+ "distro": {
+ "codename": "precise",
+ "id": "ubuntu",
+ "name": "Ubuntu",
+ "version": "12.04",
+ "version_best": "12.04.5",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Ubuntu",
+ "distribution_major_version": "12",
+ "distribution_release": "precise",
+ "os_family": "Debian",
+ "distribution_version": "12.04"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_14.04.json b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_14.04.json
new file mode 100644
index 0000000..5d5af0a
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_14.04.json
@@ -0,0 +1,24 @@
+{
+ "name": "Ubuntu 14.04",
+ "input": {
+ "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=14.04\nDISTRIB_CODENAME=trusty\nDISTRIB_DESCRIPTION=\"Ubuntu 14.04.4 LTS\"",
+ "/etc/os-release": "NAME=\"Ubuntu\"\nVERSION=\"14.04.4 LTS, Trusty Tahr\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu 14.04.4 LTS\"\nVERSION_ID=\"14.04\"\nHOME_URL=\"http://www.ubuntu.com/\"\nSUPPORT_URL=\"http://help.ubuntu.com/\"\nBUG_REPORT_URL=\"http://bugs.launchpad.net/ubuntu/\""
+ },
+ "platform.dist": ["Ubuntu", "14.04", "trusty"],
+ "distro": {
+ "codename": "trusty",
+ "id": "ubuntu",
+ "name": "Ubuntu",
+ "version": "14.04",
+ "version_best": "14.04.4",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "result": {
+ "distribution": "Ubuntu",
+ "distribution_major_version": "14",
+ "distribution_release": "trusty",
+ "os_family": "Debian",
+ "distribution_version": "14.04"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_16.04.json b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_16.04.json
new file mode 100644
index 0000000..f8f50a9
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_16.04.json
@@ -0,0 +1,24 @@
+{
+ "platform.dist": ["Ubuntu", "16.04", "xenial"],
+ "distro": {
+ "codename": "xenial",
+ "id": "ubuntu",
+ "name": "Ubuntu",
+ "version": "16.04",
+ "version_best": "16.04.6",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Ubuntu\"\nVERSION=\"16.04 LTS (Xenial Xerus)\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu 16.04 LTS\"\nVERSION_ID=\"16.04\"\nHOME_URL=\"http://www.ubuntu.com/\"\nSUPPORT_URL=\"http://help.ubuntu.com/\"\nBUG_REPORT_URL=\"http://bugs.launchpad.net/ubuntu/\"\nUBUNTU_CODENAME=xenial\n",
+ "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=16.04\nDISTRIB_CODENAME=xenial\nDISTRIB_DESCRIPTION=\"Ubuntu 16.04 LTS\"\n"
+ },
+ "name": "Ubuntu 16.04",
+ "result": {
+ "distribution_release": "xenial",
+ "distribution": "Ubuntu",
+ "distribution_major_version": "16",
+ "os_family": "Debian",
+ "distribution_version": "16.04"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_18.04.json b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_18.04.json
new file mode 100644
index 0000000..12d15b5
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/ubuntu_18.04.json
@@ -0,0 +1,39 @@
+{
+ "name": "Ubuntu 18.04",
+ "distro": {
+ "codename": "bionic",
+ "id": "ubuntu",
+ "name": "Ubuntu",
+ "version": "18.04",
+ "version_best": "18.04.3",
+ "lsb_release_info": {},
+ "os_release_info": {
+ "name": "Ubuntu",
+ "version": "18.04.3 LTS (Bionic Beaver)",
+ "id": "ubuntu",
+ "id_like": "debian",
+ "pretty_name": "Ubuntu 18.04.3 LTS",
+ "version_id": "18.04",
+ "home_url": "https://www.ubuntu.com/",
+ "support_url": "https://help.ubuntu.com/",
+ "bug_report_url": "https://bugs.launchpad.net/ubuntu/",
+ "privacy_policy_url": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
+ "version_codename": "bionic",
+ "ubuntu_codename": "bionic",
+ "codename": "bionic"
+ }
+ },
+ "input": {
+ "/etc/os-release": "NAME=\"Ubuntu\"\nVERSION=\"18.04.3 LTS (Bionic Beaver)\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu 18.04.3 LTS\"\nVERSION_ID=\"18.04\"\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nVERSION_CODENAME=bionic\nUBUNTU_CODENAME=bionic\n",
+ "/etc/lsb-release": "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=18.04\nDISTRIB_CODENAME=bionic\nDISTRIB_DESCRIPTION=\"Ubuntu 18.04.3 LTS\"\n",
+ "/usr/lib/os-release": "NAME=\"Ubuntu\"\nVERSION=\"18.04.3 LTS (Bionic Beaver)\"\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\"Ubuntu 18.04.3 LTS\"\nVERSION_ID=\"18.04\"\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nVERSION_CODENAME=bionic\nUBUNTU_CODENAME=bionic\n"
+ },
+ "platform.dist": ["ubuntu", "18.04", "bionic"],
+ "result": {
+ "distribution": "Ubuntu",
+ "distribution_version": "18.04",
+ "distribution_release": "bionic",
+ "distribution_major_version": "18",
+ "os_family": "Debian"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/uos_20.json b/test/units/module_utils/facts/system/distribution/fixtures/uos_20.json
new file mode 100644
index 0000000..d51f62d
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/uos_20.json
@@ -0,0 +1,29 @@
+{
+ "name": "Uos 20",
+ "distro": {
+ "codename": "fou",
+ "id": "Uos",
+ "name": "Uos",
+ "version": "20",
+ "version_best": "20",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/os-release": "PRETTY_NAME=\"UnionTech OS Server 20\"\nNAME=\"UnionTech OS Server 20\"\nVERSION_ID=\"20\"\nVERSION=\"20\"\nID=UOS\nHOME_URL=\"https://www.chinauos.com/\"\nBUG_REPORT_URL=\"https://bbs.chinauos.com/\"\nVERSION_CODENAME=fou",
+ "/etc/lsb-release": "DISTRIB_ID=uos\nDISTRIB_RELEASE=20\nDISTRIB_DESCRIPTION=\"UnionTech OS Server 20\"\nDISTRIB_CODENAME=fou\n",
+ "/usr/lib/os-release": "PRETTY_NAME=\"UnionTech OS Server 20\"\nNAME=\"UnionTech OS Server 20\"\nVERSION_ID=\"20\"\nVERSION=\"20\"\nID=UOS\nHOME_URL=\"https://www.chinauos.com/\"\nBUG_REPORT_URL=\"https://bbs.chinauos.com/\"\nVERSION_CODENAME=fou"
+ },
+ "platform.dist": [
+ "uos",
+ "20",
+ "fou"
+ ],
+ "result": {
+ "distribution": "Uos",
+ "distribution_version": "20",
+ "distribution_release": "fou",
+ "distribution_major_version": "20",
+ "os_family": "Debian"
+ }
+}
diff --git a/test/units/module_utils/facts/system/distribution/fixtures/virtuozzo_7.3.json b/test/units/module_utils/facts/system/distribution/fixtures/virtuozzo_7.3.json
new file mode 100644
index 0000000..d9c2f47
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/fixtures/virtuozzo_7.3.json
@@ -0,0 +1,25 @@
+{
+ "name": "Virtuozzo 7.3",
+ "platform.dist": ["redhat", "7.3", ""],
+ "distro": {
+ "codename": "",
+ "id": "virtuozzo",
+ "name": "Virtuozzo Linux",
+ "version": "7.3",
+ "version_best": "7.3",
+ "os_release_info": {},
+ "lsb_release_info": {}
+ },
+ "input": {
+ "/etc/redhat-release": "Virtuozzo Linux release 7.3\n",
+ "/etc/os-release": "NAME=\"Virtuozzo\"\nVERSION=\"7.0.3\"\nID=\"virtuozzo\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"7\"\nPRETTY_NAME=\"Virtuozzo release 7.0.3\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:virtuozzoproject:vz:7\"\nHOME_URL=\"http://www.virtuozzo.com\"\nBUG_REPORT_URL=\"https://bugs.openvz.org/\"\n",
+ "/etc/system-release": "Virtuozzo release 7.0.3 (640)\n"
+ },
+ "result": {
+ "distribution_release": "NA",
+ "distribution": "Virtuozzo",
+ "distribution_major_version": "7",
+ "os_family": "RedHat",
+ "distribution_version": "7.3"
+ }
+} \ No newline at end of file
diff --git a/test/units/module_utils/facts/system/distribution/test_distribution_sles4sap.py b/test/units/module_utils/facts/system/distribution/test_distribution_sles4sap.py
new file mode 100644
index 0000000..ab465ea
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/test_distribution_sles4sap.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright: (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 pytest
+
+from ansible.module_utils.facts.system.distribution import DistributionFiles
+
+
+@pytest.mark.parametrize('realpath', ('SUSE_SLES_SAP.prod', 'SLES_SAP.prod'))
+def test_distribution_sles4sap_suse_sles_sap(mock_module, mocker, realpath):
+ mocker.patch('os.path.islink', return_value=True)
+ mocker.patch('os.path.realpath', return_value='/etc/products.d/' + realpath)
+
+ test_input = {
+ 'name': 'SUSE',
+ 'path': '',
+ 'data': 'suse',
+ 'collected_facts': None,
+ }
+
+ test_result = (
+ True,
+ {
+ 'distribution': 'SLES_SAP',
+ }
+ )
+
+ distribution = DistributionFiles(module=mock_module())
+ assert test_result == distribution.parse_distribution_file_SUSE(**test_input)
diff --git a/test/units/module_utils/facts/system/distribution/test_distribution_version.py b/test/units/module_utils/facts/system/distribution/test_distribution_version.py
new file mode 100644
index 0000000..a990274
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/test_distribution_version.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2017 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 glob
+import json
+import os
+import pytest
+from itertools import product
+
+from ansible.module_utils.six.moves import builtins
+
+# the module we are actually testing (sort of)
+from ansible.module_utils.facts.system.distribution import DistributionFactCollector
+
+# to generate the testcase data, you can use the script gen_distribution_version_testcase.py in hacking/tests
+TESTSETS = []
+
+for datafile in glob.glob(os.path.join(os.path.dirname(__file__), 'fixtures/*.json')):
+ with open(os.path.join(os.path.dirname(__file__), '%s' % datafile)) as f:
+ TESTSETS.append(json.loads(f.read()))
+
+
+@pytest.mark.parametrize("stdin, testcase", product([{}], TESTSETS), ids=lambda x: x.get('name'), indirect=['stdin'])
+def test_distribution_version(am, mocker, testcase):
+ """tests the distribution parsing code of the Facts class
+
+ testsets have
+ * a name (for output/debugging only)
+ * input files that are faked
+ * those should be complete and also include "irrelevant" files that might be mistaken as coming from other distributions
+ * all files that are not listed here are assumed to not exist at all
+ * the output of ansible.module_utils.distro.linux_distribution() [called platform.dist() for historical reasons]
+ * results for the ansible variables distribution* and os_family
+
+ """
+
+ # prepare some mock functions to get the testdata in
+ def mock_get_file_content(fname, default=None, strip=True):
+ """give fake content if it exists, otherwise pretend the file is empty"""
+ data = default
+ if fname in testcase['input']:
+ # for debugging
+ print('faked %s for %s' % (fname, testcase['name']))
+ data = testcase['input'][fname].strip()
+ if strip and data is not None:
+ data = data.strip()
+ return data
+
+ def mock_get_file_lines(fname, strip=True):
+ """give fake lines if file exists, otherwise return empty list"""
+ data = mock_get_file_content(fname=fname, strip=strip)
+ if data:
+ return [data]
+ return []
+
+ def mock_get_uname(am, flags):
+ if '-v' in flags:
+ return testcase.get('uname_v', None)
+ elif '-r' in flags:
+ return testcase.get('uname_r', None)
+ else:
+ return None
+
+ def mock_file_exists(fname, allow_empty=False):
+ if fname not in testcase['input']:
+ return False
+
+ if allow_empty:
+ return True
+ return bool(len(testcase['input'][fname]))
+
+ def mock_platform_system():
+ return testcase.get('platform.system', 'Linux')
+
+ def mock_platform_release():
+ return testcase.get('platform.release', '')
+
+ def mock_platform_version():
+ return testcase.get('platform.version', '')
+
+ def mock_distro_name():
+ return testcase['distro']['name']
+
+ def mock_distro_id():
+ return testcase['distro']['id']
+
+ def mock_distro_version(best=False):
+ if best:
+ return testcase['distro']['version_best']
+ return testcase['distro']['version']
+
+ def mock_distro_codename():
+ return testcase['distro']['codename']
+
+ def mock_distro_os_release_info():
+ return testcase['distro']['os_release_info']
+
+ def mock_distro_lsb_release_info():
+ return testcase['distro']['lsb_release_info']
+
+ def mock_open(filename, mode='r'):
+ if filename in testcase['input']:
+ file_object = mocker.mock_open(read_data=testcase['input'][filename]).return_value
+ file_object.__iter__.return_value = testcase['input'][filename].splitlines(True)
+ else:
+ file_object = real_open(filename, mode)
+ return file_object
+
+ def mock_os_path_is_file(filename):
+ if filename in testcase['input']:
+ return True
+ return False
+
+ def mock_run_command_output(v, command):
+ ret = (0, '', '')
+ if 'command_output' in testcase:
+ ret = (0, testcase['command_output'].get(command, ''), '')
+ return ret
+
+ mocker.patch('ansible.module_utils.facts.system.distribution.get_file_content', mock_get_file_content)
+ mocker.patch('ansible.module_utils.facts.system.distribution.get_file_lines', mock_get_file_lines)
+ mocker.patch('ansible.module_utils.facts.system.distribution.get_uname', mock_get_uname)
+ mocker.patch('ansible.module_utils.facts.system.distribution._file_exists', mock_file_exists)
+ mocker.patch('ansible.module_utils.distro.name', mock_distro_name)
+ mocker.patch('ansible.module_utils.distro.id', mock_distro_id)
+ mocker.patch('ansible.module_utils.distro.version', mock_distro_version)
+ mocker.patch('ansible.module_utils.distro.codename', mock_distro_codename)
+ mocker.patch(
+ 'ansible.module_utils.common.sys_info.distro.os_release_info',
+ mock_distro_os_release_info)
+ mocker.patch(
+ 'ansible.module_utils.common.sys_info.distro.lsb_release_info',
+ mock_distro_lsb_release_info)
+ mocker.patch('os.path.isfile', mock_os_path_is_file)
+ mocker.patch('platform.system', mock_platform_system)
+ mocker.patch('platform.release', mock_platform_release)
+ mocker.patch('platform.version', mock_platform_version)
+ mocker.patch('ansible.module_utils.basic.AnsibleModule.run_command', mock_run_command_output)
+
+ real_open = builtins.open
+ mocker.patch.object(builtins, 'open', new=mock_open)
+
+ # run Facts()
+ distro_collector = DistributionFactCollector()
+ generated_facts = distro_collector.collect(am)
+
+ # compare with the expected output
+
+ # testcase['result'] has a list of variables and values it expects Facts() to set
+ for key, val in testcase['result'].items():
+ assert key in generated_facts
+ msg = 'Comparing value of %s on %s, should: %s, is: %s' %\
+ (key, testcase['name'], val, generated_facts[key])
+ assert generated_facts[key] == val, msg
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
new file mode 100644
index 0000000..c095756
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_ClearLinux.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.facts.system.distribution import DistributionFiles
+
+
+@pytest.fixture
+def test_input():
+ return {
+ 'name': 'Clearlinux',
+ 'path': '/usr/lib/os-release',
+ 'collected_facts': None,
+ }
+
+
+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()
+
+ result = (
+ True,
+ {
+ 'distribution': 'Clear Linux OS',
+ 'distribution_major_version': '28120',
+ 'distribution_release': 'clear-linux-os',
+ 'distribution_version': '28120'
+ }
+ )
+
+ distribution = DistributionFiles(module=mock_module())
+ assert result == distribution.parse_distribution_file_ClearLinux(**test_input)
+
+
+@pytest.mark.parametrize('distro_file', ('CoreOS', 'LinuxMint'))
+def test_parse_distribution_file_clear_linux_no_match(mock_module, distro_file, test_input):
+ """
+ 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()
+
+ result = (False, {})
+
+ distribution = DistributionFiles(module=mock_module())
+ assert result == distribution.parse_distribution_file_ClearLinux(**test_input)
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
new file mode 100644
index 0000000..53fd4ea
--- /dev/null
+++ b/test/units/module_utils/facts/system/distribution/test_parse_distribution_file_Slackware.py
@@ -0,0 +1,37 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019 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 pytest
+
+from ansible.module_utils.facts.system.distribution import DistributionFiles
+
+
+@pytest.mark.parametrize(
+ ('distro_file', 'expected_version'),
+ (
+ ('Slackware', '14.1'),
+ ('SlackwareCurrent', '14.2+'),
+ )
+)
+def test_parse_distribution_file_slackware(mock_module, distro_file, expected_version):
+ test_input = {
+ 'name': 'Slackware',
+ 'data': open(os.path.join(os.path.dirname(__file__), '../../fixtures/distribution_files', distro_file)).read(),
+ 'path': '/etc/os-release',
+ 'collected_facts': None,
+ }
+
+ result = (
+ True,
+ {
+ 'distribution': 'Slackware',
+ 'distribution_version': expected_version
+ }
+ )
+ distribution = DistributionFiles(module=mock_module())
+ assert result == distribution.parse_distribution_file_Slackware(**test_input)
diff --git a/test/units/module_utils/facts/system/test_cmdline.py b/test/units/module_utils/facts/system/test_cmdline.py
new file mode 100644
index 0000000..59cfd11
--- /dev/null
+++ b/test/units/module_utils/facts/system/test_cmdline.py
@@ -0,0 +1,67 @@
+# unit tests for ansible system cmdline fact collectors
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2018, 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 pytest
+from ansible.module_utils.facts.system.cmdline import CmdLineFactCollector
+
+test_data = [
+ (
+ "crashkernel=auto rd.lvm.lv=fedora_test-elementary-os/root rd.lvm.lv=fedora_test-elementary-os/swap rhgb quiet",
+ {
+ 'crashkernel': 'auto',
+ 'quiet': True,
+ 'rd.lvm.lv': [
+ 'fedora_test-elementary-os/root',
+ 'fedora_test-elementary-os/swap',
+ ],
+ 'rhgb': True
+ }
+ ),
+ (
+ "root=/dev/mapper/vg_ssd-root ro rd.lvm.lv=fedora_xenon/root rd.lvm.lv=fedora_xenon/swap rhgb quiet "
+ "resume=/dev/mapper/fedora_xenon-swap crashkernel=128M zswap.enabled=1",
+ {
+ 'crashkernel': '128M',
+ 'quiet': True,
+ 'rd.lvm.lv': [
+ 'fedora_xenon/root',
+ 'fedora_xenon/swap'
+ ],
+ 'resume': '/dev/mapper/fedora_xenon-swap',
+ 'rhgb': True,
+ 'ro': True,
+ 'root': '/dev/mapper/vg_ssd-root',
+ 'zswap.enabled': '1'
+ }
+ ),
+ (
+ "rhgb",
+ {
+ "rhgb": True
+ }
+ ),
+ (
+ "root=/dev/mapper/vg_ssd-root",
+ {
+ 'root': '/dev/mapper/vg_ssd-root',
+ }
+ ),
+ (
+ "",
+ {},
+ )
+]
+
+test_ids = ['lvm_1', 'lvm_2', 'single_without_equal_sign', 'single_with_equal_sign', 'blank_cmdline']
+
+
+@pytest.mark.parametrize("cmdline, cmdline_dict", test_data, ids=test_ids)
+def test_cmd_line_factor(cmdline, cmdline_dict):
+ cmdline_facter = CmdLineFactCollector()
+ parsed_cmdline = cmdline_facter._parse_proc_cmdline_facts(data=cmdline)
+ assert parsed_cmdline == cmdline_dict
diff --git a/test/units/module_utils/facts/system/test_lsb.py b/test/units/module_utils/facts/system/test_lsb.py
new file mode 100644
index 0000000..e2ed2ec
--- /dev/null
+++ b/test/units/module_utils/facts/system/test_lsb.py
@@ -0,0 +1,108 @@
+# unit tests for ansible system lsb fact collectors
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat.mock import Mock, patch
+
+from .. base import BaseFactsTest
+
+from ansible.module_utils.facts.system.lsb import LSBFactCollector
+
+
+lsb_release_a_fedora_output = '''
+LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
+Distributor ID: Fedora
+Description: Fedora release 25 (Twenty Five)
+Release: 25
+Codename: TwentyFive
+''' # noqa
+
+# FIXME: a
+etc_lsb_release_ubuntu14 = '''DISTRIB_ID=Ubuntu
+DISTRIB_RELEASE=14.04
+DISTRIB_CODENAME=trusty
+DISTRIB_DESCRIPTION="Ubuntu 14.04.3 LTS"
+'''
+etc_lsb_release_no_decimal = '''DISTRIB_ID=AwesomeOS
+DISTRIB_RELEASE=11
+DISTRIB_CODENAME=stonehenge
+DISTRIB_DESCRIPTION="AwesomeÖS 11"
+'''
+
+
+class TestLSBFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'lsb']
+ valid_subsets = ['lsb']
+ fact_namespace = 'ansible_lsb'
+ collector_class = LSBFactCollector
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 10,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value='/usr/bin/lsb_release')
+ mock_module.run_command = Mock(return_value=(0, lsb_release_a_fedora_output, ''))
+ return mock_module
+
+ def test_lsb_release_bin(self):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['lsb']['release'], '25')
+ self.assertEqual(facts_dict['lsb']['id'], 'Fedora')
+ self.assertEqual(facts_dict['lsb']['description'], 'Fedora release 25 (Twenty Five)')
+ self.assertEqual(facts_dict['lsb']['codename'], 'TwentyFive')
+ self.assertEqual(facts_dict['lsb']['major_release'], '25')
+
+ def test_etc_lsb_release(self):
+ module = self._mock_module()
+ module.get_bin_path = Mock(return_value=None)
+ with patch('ansible.module_utils.facts.system.lsb.os.path.exists',
+ return_value=True):
+ with patch('ansible.module_utils.facts.system.lsb.get_file_lines',
+ return_value=etc_lsb_release_ubuntu14.splitlines()):
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['lsb']['release'], '14.04')
+ self.assertEqual(facts_dict['lsb']['id'], 'Ubuntu')
+ self.assertEqual(facts_dict['lsb']['description'], 'Ubuntu 14.04.3 LTS')
+ self.assertEqual(facts_dict['lsb']['codename'], 'trusty')
+
+ def test_etc_lsb_release_no_decimal_release(self):
+ module = self._mock_module()
+ module.get_bin_path = Mock(return_value=None)
+ with patch('ansible.module_utils.facts.system.lsb.os.path.exists',
+ return_value=True):
+ with patch('ansible.module_utils.facts.system.lsb.get_file_lines',
+ return_value=etc_lsb_release_no_decimal.splitlines()):
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['lsb']['release'], '11')
+ self.assertEqual(facts_dict['lsb']['id'], 'AwesomeOS')
+ self.assertEqual(facts_dict['lsb']['description'], 'AwesomeÖS 11')
+ self.assertEqual(facts_dict['lsb']['codename'], 'stonehenge')
diff --git a/test/units/module_utils/facts/system/test_user.py b/test/units/module_utils/facts/system/test_user.py
new file mode 100644
index 0000000..5edfe14
--- /dev/null
+++ b/test/units/module_utils/facts/system/test_user.py
@@ -0,0 +1,40 @@
+# unit tests for ansible system lsb fact collectors
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.module_utils.facts.system.user import UserFactCollector
+
+import os
+
+
+def test_logname():
+ """ Test if ``UserFactCollector`` still works with LOGNAME set """
+ collector = UserFactCollector()
+
+ unmodified_facts = collector.collect()
+ # Change logname env var and check if the collector still finds
+ # the pw entry.
+ os.environ["LOGNAME"] = "NONEXISTINGUSERDONTEXISTPLEASE"
+ modified_facts = collector.collect()
+
+ # Set logname should be different to the real name.
+ assert unmodified_facts['user_id'] != modified_facts['user_id']
+ # Actual UID is the same.
+ assert unmodified_facts['user_uid'] == modified_facts['user_uid']
diff --git a/test/units/module_utils/facts/test_ansible_collector.py b/test/units/module_utils/facts/test_ansible_collector.py
new file mode 100644
index 0000000..47d88df
--- /dev/null
+++ b/test/units/module_utils/facts/test_ansible_collector.py
@@ -0,0 +1,524 @@
+# -*- coding: utf-8 -*-
+#
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+# for testing
+from units.compat import unittest
+from units.compat.mock import Mock, patch
+
+from ansible.module_utils.facts import collector
+from ansible.module_utils.facts import ansible_collector
+from ansible.module_utils.facts import namespace
+
+from ansible.module_utils.facts.other.facter import FacterFactCollector
+from ansible.module_utils.facts.other.ohai import OhaiFactCollector
+
+from ansible.module_utils.facts.system.apparmor import ApparmorFactCollector
+from ansible.module_utils.facts.system.caps import SystemCapabilitiesFactCollector
+from ansible.module_utils.facts.system.date_time import DateTimeFactCollector
+from ansible.module_utils.facts.system.env import EnvFactCollector
+from ansible.module_utils.facts.system.distribution import DistributionFactCollector
+from ansible.module_utils.facts.system.dns import DnsFactCollector
+from ansible.module_utils.facts.system.fips import FipsFactCollector
+from ansible.module_utils.facts.system.local import LocalFactCollector
+from ansible.module_utils.facts.system.lsb import LSBFactCollector
+from ansible.module_utils.facts.system.pkg_mgr import PkgMgrFactCollector, OpenBSDPkgMgrFactCollector
+from ansible.module_utils.facts.system.platform import PlatformFactCollector
+from ansible.module_utils.facts.system.python import PythonFactCollector
+from ansible.module_utils.facts.system.selinux import SelinuxFactCollector
+from ansible.module_utils.facts.system.service_mgr import ServiceMgrFactCollector
+from ansible.module_utils.facts.system.user import UserFactCollector
+
+# from ansible.module_utils.facts.hardware.base import HardwareCollector
+from ansible.module_utils.facts.network.base import NetworkCollector
+from ansible.module_utils.facts.virtual.base import VirtualCollector
+
+ALL_COLLECTOR_CLASSES = \
+ [PlatformFactCollector,
+ DistributionFactCollector,
+ SelinuxFactCollector,
+ ApparmorFactCollector,
+ SystemCapabilitiesFactCollector,
+ FipsFactCollector,
+ PkgMgrFactCollector,
+ OpenBSDPkgMgrFactCollector,
+ ServiceMgrFactCollector,
+ LSBFactCollector,
+ DateTimeFactCollector,
+ UserFactCollector,
+ LocalFactCollector,
+ EnvFactCollector,
+ DnsFactCollector,
+ PythonFactCollector,
+ # FIXME: re-enable when hardware doesnt Hardware() doesnt munge self.facts
+ # HardwareCollector
+ NetworkCollector,
+ VirtualCollector,
+ OhaiFactCollector,
+ FacterFactCollector]
+
+
+def mock_module(gather_subset=None,
+ filter=None):
+ if gather_subset is None:
+ gather_subset = ['all', '!facter', '!ohai']
+ if filter is None:
+ filter = '*'
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': gather_subset,
+ 'gather_timeout': 5,
+ 'filter': filter}
+ mock_module.get_bin_path = Mock(return_value=None)
+ return mock_module
+
+
+def _collectors(module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ gather_subset = module.params.get('gather_subset')
+ if all_collector_classes is None:
+ all_collector_classes = ALL_COLLECTOR_CLASSES
+ if minimal_gather_subset is None:
+ minimal_gather_subset = frozenset([])
+
+ collector_classes = \
+ collector.collector_classes_from_gather_subset(all_collector_classes=all_collector_classes,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=gather_subset)
+
+ collectors = []
+ for collector_class in collector_classes:
+ collector_obj = collector_class()
+ collectors.append(collector_obj)
+
+ # Add a collector that knows what gather_subset we used so it it can provide a fact
+ collector_meta_data_collector = \
+ ansible_collector.CollectorMetaDataCollector(gather_subset=gather_subset,
+ module_setup=True)
+ collectors.append(collector_meta_data_collector)
+
+ return collectors
+
+
+ns = namespace.PrefixFactNamespace('ansible_facts', 'ansible_')
+
+
+# FIXME: this is brute force, but hopefully enough to get some refactoring to make facts testable
+class TestInPlace(unittest.TestCase):
+ def _mock_module(self, gather_subset=None):
+ return mock_module(gather_subset=gather_subset)
+
+ def _collectors(self, module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ return _collectors(module=module,
+ all_collector_classes=all_collector_classes,
+ minimal_gather_subset=minimal_gather_subset)
+
+ def test(self):
+ gather_subset = ['all']
+ mock_module = self._mock_module(gather_subset=gather_subset)
+ all_collector_classes = [EnvFactCollector]
+ collectors = self._collectors(mock_module,
+ all_collector_classes=all_collector_classes)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=collectors,
+ namespace=ns)
+
+ res = fact_collector.collect(module=mock_module)
+ self.assertIsInstance(res, dict)
+ self.assertIn('env', res)
+ self.assertIn('gather_subset', res)
+ self.assertEqual(res['gather_subset'], ['all'])
+
+ def test1(self):
+ gather_subset = ['all']
+ mock_module = self._mock_module(gather_subset=gather_subset)
+ collectors = self._collectors(mock_module)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=collectors,
+ namespace=ns)
+
+ res = fact_collector.collect(module=mock_module)
+ self.assertIsInstance(res, dict)
+ # just assert it's not almost empty
+ # with run_command and get_file_content mock, many facts are empty, like network
+ self.assertGreater(len(res), 20)
+
+ def test_empty_all_collector_classes(self):
+ mock_module = self._mock_module()
+ all_collector_classes = []
+
+ collectors = self._collectors(mock_module,
+ all_collector_classes=all_collector_classes)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=collectors,
+ namespace=ns)
+
+ res = fact_collector.collect()
+ self.assertIsInstance(res, dict)
+ # just assert it's not almost empty
+ self.assertLess(len(res), 3)
+
+# def test_facts_class(self):
+# mock_module = self._mock_module()
+# Facts(mock_module)
+
+# def test_facts_class_load_on_init_false(self):
+# mock_module = self._mock_module()
+# Facts(mock_module, load_on_init=False)
+# # FIXME: assert something
+
+
+class TestCollectedFacts(unittest.TestCase):
+ gather_subset = ['all', '!facter', '!ohai']
+ min_fact_count = 30
+ max_fact_count = 1000
+
+ # TODO: add ansible_cmdline, ansible_*_pubkey* back when TempFactCollector goes away
+ expected_facts = ['date_time',
+ 'user_id', 'distribution',
+ 'gather_subset', 'module_setup',
+ 'env']
+ not_expected_facts = ['facter', 'ohai']
+
+ collected_facts = {}
+
+ def _mock_module(self, gather_subset=None):
+ return mock_module(gather_subset=self.gather_subset)
+
+ @patch('platform.system', return_value='Linux')
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value='systemd')
+ def setUp(self, mock_gfc, mock_ps):
+ mock_module = self._mock_module()
+ collectors = self._collectors(mock_module)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=collectors,
+ namespace=ns)
+ self.facts = fact_collector.collect(module=mock_module,
+ collected_facts=self.collected_facts)
+
+ def _collectors(self, module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ return _collectors(module=module,
+ all_collector_classes=all_collector_classes,
+ minimal_gather_subset=minimal_gather_subset)
+
+ def test_basics(self):
+ self._assert_basics(self.facts)
+
+ def test_expected_facts(self):
+ self._assert_expected_facts(self.facts)
+
+ def test_not_expected_facts(self):
+ self._assert_not_expected_facts(self.facts)
+
+ def _assert_basics(self, facts):
+ self.assertIsInstance(facts, dict)
+ # just assert it's not almost empty
+ self.assertGreaterEqual(len(facts), self.min_fact_count)
+ # and that is not huge number of keys
+ self.assertLess(len(facts), self.max_fact_count)
+
+ # everything starts with ansible_ namespace
+ def _assert_ansible_namespace(self, facts):
+
+ # FIXME: kluge for non-namespace fact
+ facts.pop('module_setup', None)
+ facts.pop('gather_subset', None)
+
+ for fact_key in facts:
+ self.assertTrue(fact_key.startswith('ansible_'),
+ 'The fact name "%s" does not startwith "ansible_"' % fact_key)
+
+ def _assert_expected_facts(self, facts):
+
+ facts_keys = sorted(facts.keys())
+ for expected_fact in self.expected_facts:
+ self.assertIn(expected_fact, facts_keys)
+
+ def _assert_not_expected_facts(self, facts):
+
+ facts_keys = sorted(facts.keys())
+ for not_expected_fact in self.not_expected_facts:
+ self.assertNotIn(not_expected_fact, facts_keys)
+
+
+class ProvidesOtherFactCollector(collector.BaseFactCollector):
+ name = 'provides_something'
+ _fact_ids = set(['needed_fact'])
+
+ def collect(self, module=None, collected_facts=None):
+ return {'needed_fact': 'THE_NEEDED_FACT_VALUE'}
+
+
+class RequiresOtherFactCollector(collector.BaseFactCollector):
+ name = 'requires_something'
+
+ def collect(self, module=None, collected_facts=None):
+ collected_facts = collected_facts or {}
+ fact_dict = {}
+ fact_dict['needed_fact'] = collected_facts['needed_fact']
+ fact_dict['compound_fact'] = "compound-%s" % collected_facts['needed_fact']
+ return fact_dict
+
+
+class ConCatFactCollector(collector.BaseFactCollector):
+ name = 'concat_collected'
+
+ def collect(self, module=None, collected_facts=None):
+ collected_facts = collected_facts or {}
+ fact_dict = {}
+ con_cat_list = []
+ for key, value in collected_facts.items():
+ con_cat_list.append(value)
+
+ fact_dict['concat_fact'] = '-'.join(con_cat_list)
+ return fact_dict
+
+
+class TestCollectorDepsWithFilter(unittest.TestCase):
+ gather_subset = ['all', '!facter', '!ohai']
+
+ def _mock_module(self, gather_subset=None, filter=None):
+ return mock_module(gather_subset=self.gather_subset,
+ filter=filter)
+
+ def setUp(self):
+ self.mock_module = self._mock_module()
+ self.collectors = self._collectors(mock_module)
+
+ def _collectors(self, module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ return [ProvidesOtherFactCollector(),
+ RequiresOtherFactCollector()]
+
+ def test_no_filter(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'])
+
+ facts_dict = self._collect(_mock_module)
+
+ expected = {'needed_fact': 'THE_NEEDED_FACT_VALUE',
+ 'compound_fact': 'compound-THE_NEEDED_FACT_VALUE'}
+
+ self.assertEqual(expected, facts_dict)
+
+ def test_with_filter_on_compound_fact(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'],
+ filter='compound_fact')
+
+ facts_dict = self._collect(_mock_module)
+
+ expected = {'compound_fact': 'compound-THE_NEEDED_FACT_VALUE'}
+
+ self.assertEqual(expected, facts_dict)
+
+ def test_with_filter_on_needed_fact(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'],
+ filter='needed_fact')
+
+ facts_dict = self._collect(_mock_module)
+
+ expected = {'needed_fact': 'THE_NEEDED_FACT_VALUE'}
+
+ self.assertEqual(expected, facts_dict)
+
+ def test_with_filter_on_compound_gather_compound(self):
+ _mock_module = mock_module(gather_subset=['!all', '!any', 'compound_fact'],
+ filter='compound_fact')
+
+ facts_dict = self._collect(_mock_module)
+
+ expected = {'compound_fact': 'compound-THE_NEEDED_FACT_VALUE'}
+
+ self.assertEqual(expected, facts_dict)
+
+ def test_with_filter_no_match(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'],
+ filter='ansible_this_doesnt_exist')
+
+ facts_dict = self._collect(_mock_module)
+
+ expected = {}
+ self.assertEqual(expected, facts_dict)
+
+ def test_concat_collector(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'])
+
+ _collectors = self._collectors(_mock_module)
+ _collectors.append(ConCatFactCollector())
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=_collectors,
+ namespace=ns,
+ filter_spec=_mock_module.params['filter'])
+
+ collected_facts = {}
+ facts_dict = fact_collector.collect(module=_mock_module,
+ collected_facts=collected_facts)
+ self.assertIn('concat_fact', facts_dict)
+ self.assertTrue('THE_NEEDED_FACT_VALUE' in facts_dict['concat_fact'])
+
+ def test_concat_collector_with_filter_on_concat(self):
+ _mock_module = mock_module(gather_subset=['all', '!facter', '!ohai'],
+ filter='concat_fact')
+
+ _collectors = self._collectors(_mock_module)
+ _collectors.append(ConCatFactCollector())
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=_collectors,
+ namespace=ns,
+ filter_spec=_mock_module.params['filter'])
+
+ collected_facts = {}
+ facts_dict = fact_collector.collect(module=_mock_module,
+ collected_facts=collected_facts)
+ self.assertIn('concat_fact', facts_dict)
+ self.assertTrue('THE_NEEDED_FACT_VALUE' in facts_dict['concat_fact'])
+ self.assertTrue('compound' in facts_dict['concat_fact'])
+
+ def _collect(self, _mock_module, collected_facts=None):
+ _collectors = self._collectors(_mock_module)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=_collectors,
+ namespace=ns,
+ filter_spec=_mock_module.params['filter'])
+ facts_dict = fact_collector.collect(module=_mock_module,
+ collected_facts=collected_facts)
+ return facts_dict
+
+
+class ExceptionThrowingCollector(collector.BaseFactCollector):
+ def collect(self, module=None, collected_facts=None):
+ raise Exception('A collector failed')
+
+
+class TestExceptionCollectedFacts(TestCollectedFacts):
+
+ def _collectors(self, module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ collectors = _collectors(module=module,
+ all_collector_classes=all_collector_classes,
+ minimal_gather_subset=minimal_gather_subset)
+
+ c = [ExceptionThrowingCollector()] + collectors
+ return c
+
+
+class TestOnlyExceptionCollector(TestCollectedFacts):
+ expected_facts = []
+ min_fact_count = 0
+
+ def _collectors(self, module,
+ all_collector_classes=None,
+ minimal_gather_subset=None):
+ return [ExceptionThrowingCollector()]
+
+
+class TestMinimalCollectedFacts(TestCollectedFacts):
+ gather_subset = ['!all']
+ min_fact_count = 1
+ max_fact_count = 10
+ expected_facts = ['gather_subset',
+ 'module_setup']
+ not_expected_facts = ['lsb']
+
+
+class TestFacterCollectedFacts(TestCollectedFacts):
+ gather_subset = ['!all', 'facter']
+ min_fact_count = 1
+ max_fact_count = 10
+ expected_facts = ['gather_subset',
+ 'module_setup']
+ not_expected_facts = ['lsb']
+
+
+class TestOhaiCollectedFacts(TestCollectedFacts):
+ gather_subset = ['!all', 'ohai']
+ min_fact_count = 1
+ max_fact_count = 10
+ expected_facts = ['gather_subset',
+ 'module_setup']
+ not_expected_facts = ['lsb']
+
+
+class TestPkgMgrFacts(TestCollectedFacts):
+ gather_subset = ['pkg_mgr']
+ min_fact_count = 1
+ max_fact_count = 20
+ expected_facts = ['gather_subset',
+ 'module_setup',
+ 'pkg_mgr']
+ collected_facts = {
+ "ansible_distribution": "Fedora",
+ "ansible_distribution_major_version": "28",
+ "ansible_os_family": "RedHat"
+ }
+
+
+class TestPkgMgrOSTreeFacts(TestPkgMgrFacts):
+ @patch(
+ 'ansible.module_utils.facts.system.pkg_mgr.os.path.exists',
+ side_effect=lambda x: x == '/run/ostree-booted')
+ def _recollect_facts(self, distribution, version, mock_exists):
+ self.collected_facts['ansible_distribution'] = distribution
+ self.collected_facts['ansible_distribution_major_version'] = \
+ str(version)
+ # Recollect facts
+ self.setUp()
+ self.assertIn('pkg_mgr', self.facts)
+ self.assertEqual(self.facts['pkg_mgr'], 'atomic_container')
+
+ def test_is_rhel_edge_ostree(self):
+ self._recollect_facts('RedHat', 8)
+
+ def test_is_fedora_ostree(self):
+ self._recollect_facts('Fedora', 33)
+
+
+class TestOpenBSDPkgMgrFacts(TestPkgMgrFacts):
+ def test_is_openbsd_pkg(self):
+ self.assertIn('pkg_mgr', self.facts)
+ self.assertEqual(self.facts['pkg_mgr'], 'openbsd_pkg')
+
+ def setUp(self):
+ self.patcher = patch('platform.system')
+ mock_platform = self.patcher.start()
+ mock_platform.return_value = 'OpenBSD'
+
+ mock_module = self._mock_module()
+ collectors = self._collectors(mock_module)
+
+ fact_collector = \
+ ansible_collector.AnsibleFactCollector(collectors=collectors,
+ namespace=ns)
+ self.facts = fact_collector.collect(module=mock_module)
+
+ def tearDown(self):
+ self.patcher.stop()
diff --git a/test/units/module_utils/facts/test_collector.py b/test/units/module_utils/facts/test_collector.py
new file mode 100644
index 0000000..4fc4bc5
--- /dev/null
+++ b/test/units/module_utils/facts/test_collector.py
@@ -0,0 +1,563 @@
+# This file is part of Ansible
+# -*- coding: utf-8 -*-
+#
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from collections import defaultdict
+import pprint
+
+# for testing
+from units.compat import unittest
+
+from ansible.module_utils.facts import collector
+
+from ansible.module_utils.facts import default_collectors
+
+
+class TestFindCollectorsForPlatform(unittest.TestCase):
+ def test(self):
+ compat_platforms = [{'system': 'Generic'}]
+ res = collector.find_collectors_for_platform(default_collectors.collectors,
+ compat_platforms)
+ for coll_class in res:
+ self.assertIn(coll_class._platform, ('Generic'))
+
+ def test_linux(self):
+ compat_platforms = [{'system': 'Linux'}]
+ res = collector.find_collectors_for_platform(default_collectors.collectors,
+ compat_platforms)
+ for coll_class in res:
+ self.assertIn(coll_class._platform, ('Linux'))
+
+ def test_linux_or_generic(self):
+ compat_platforms = [{'system': 'Generic'}, {'system': 'Linux'}]
+ res = collector.find_collectors_for_platform(default_collectors.collectors,
+ compat_platforms)
+ for coll_class in res:
+ self.assertIn(coll_class._platform, ('Generic', 'Linux'))
+
+
+class TestSelectCollectorNames(unittest.TestCase):
+
+ def _assert_equal_detail(self, obj1, obj2, msg=None):
+ msg = 'objects are not equal\n%s\n\n!=\n\n%s' % (pprint.pformat(obj1), pprint.pformat(obj2))
+ return self.assertEqual(obj1, obj2, msg)
+
+ def test(self):
+ collector_names = ['distribution', 'all_ipv4_addresses',
+ 'local', 'pkg_mgr']
+ all_fact_subsets = self._all_fact_subsets()
+ res = collector.select_collector_classes(collector_names,
+ all_fact_subsets)
+
+ expected = [default_collectors.DistributionFactCollector,
+ default_collectors.PkgMgrFactCollector]
+
+ self._assert_equal_detail(res, expected)
+
+ def test_default_collectors(self):
+ platform_info = {'system': 'Generic'}
+ compat_platforms = [platform_info]
+ collectors_for_platform = collector.find_collectors_for_platform(default_collectors.collectors,
+ compat_platforms)
+
+ all_fact_subsets, aliases_map = collector.build_fact_id_to_collector_map(collectors_for_platform)
+
+ all_valid_subsets = frozenset(all_fact_subsets.keys())
+ collector_names = collector.get_collector_names(valid_subsets=all_valid_subsets,
+ aliases_map=aliases_map,
+ platform_info=platform_info)
+ complete_collector_names = collector._solve_deps(collector_names, all_fact_subsets)
+
+ dep_map = collector.build_dep_data(complete_collector_names, all_fact_subsets)
+
+ ordered_deps = collector.tsort(dep_map)
+ ordered_collector_names = [x[0] for x in ordered_deps]
+
+ res = collector.select_collector_classes(ordered_collector_names,
+ all_fact_subsets)
+
+ self.assertTrue(res.index(default_collectors.ServiceMgrFactCollector) >
+ res.index(default_collectors.DistributionFactCollector),
+ res)
+ self.assertTrue(res.index(default_collectors.ServiceMgrFactCollector) >
+ res.index(default_collectors.PlatformFactCollector),
+ res)
+
+ def _all_fact_subsets(self, data=None):
+ all_fact_subsets = defaultdict(list)
+ _data = {'pkg_mgr': [default_collectors.PkgMgrFactCollector],
+ 'distribution': [default_collectors.DistributionFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector]}
+ data = data or _data
+ for key, value in data.items():
+ all_fact_subsets[key] = value
+ return all_fact_subsets
+
+
+class TestGetCollectorNames(unittest.TestCase):
+ def test_none(self):
+ res = collector.get_collector_names()
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set([]))
+
+ def test_empty_sets(self):
+ res = collector.get_collector_names(valid_subsets=frozenset([]),
+ minimal_gather_subset=frozenset([]),
+ gather_subset=[])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set([]))
+
+ def test_empty_valid_and_min_with_all_gather_subset(self):
+ res = collector.get_collector_names(valid_subsets=frozenset([]),
+ minimal_gather_subset=frozenset([]),
+ gather_subset=['all'])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set([]))
+
+ def test_one_valid_with_all_gather_subset(self):
+ valid_subsets = frozenset(['my_fact'])
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=frozenset([]),
+ gather_subset=['all'])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set(['my_fact']))
+
+ def _compare_res(self, gather_subset1, gather_subset2,
+ valid_subsets=None, min_subset=None):
+
+ valid_subsets = valid_subsets or frozenset()
+ minimal_gather_subset = min_subset or frozenset()
+
+ res1 = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=gather_subset1)
+
+ res2 = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=gather_subset2)
+
+ return res1, res2
+
+ def test_not_all_other_order(self):
+ valid_subsets = frozenset(['min_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['min_fact'])
+
+ res1, res2 = self._compare_res(['!all', 'whatever'],
+ ['whatever', '!all'],
+ valid_subsets=valid_subsets,
+ min_subset=minimal_gather_subset)
+ self.assertEqual(res1, res2)
+ self.assertEqual(res1, set(['min_fact', 'whatever']))
+
+ def test_not_all_other_order_min(self):
+ valid_subsets = frozenset(['min_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['min_fact'])
+
+ res1, res2 = self._compare_res(['!min_fact', 'whatever'],
+ ['whatever', '!min_fact'],
+ valid_subsets=valid_subsets,
+ min_subset=minimal_gather_subset)
+ self.assertEqual(res1, res2)
+ self.assertEqual(res1, set(['whatever']))
+
+ def test_one_minimal_with_all_gather_subset(self):
+ my_fact = 'my_fact'
+ valid_subsets = frozenset([my_fact])
+ minimal_gather_subset = valid_subsets
+
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['all'])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set(['my_fact']))
+
+ def test_with_all_gather_subset(self):
+ valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['my_fact'])
+
+ # even with '!all', the minimal_gather_subset should be returned
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['all'])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set(['my_fact', 'something_else', 'whatever']))
+
+ def test_one_minimal_with_not_all_gather_subset(self):
+ valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['my_fact'])
+
+ # even with '!all', the minimal_gather_subset should be returned
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['!all'])
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set(['my_fact']))
+
+ def test_gather_subset_excludes(self):
+ valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['min_fact', 'min_another'])
+
+ # even with '!all', the minimal_gather_subset should be returned
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ # gather_subset=set(['all', '!my_fact', '!whatever']))
+ # gather_subset=['all', '!my_fact', '!whatever'])
+ gather_subset=['!min_fact', '!whatever'])
+ self.assertIsInstance(res, set)
+ # min_another is in minimal_gather_subset, so always returned
+ self.assertEqual(res, set(['min_another']))
+
+ def test_gather_subset_excludes_ordering(self):
+ valid_subsets = frozenset(['my_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['my_fact'])
+
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['!all', 'whatever'])
+ self.assertIsInstance(res, set)
+ # excludes are higher precedence than includes, so !all excludes everything
+ # and then minimal_gather_subset is added. so '!all', 'other' == '!all'
+ self.assertEqual(res, set(['my_fact', 'whatever']))
+
+ def test_gather_subset_excludes_min(self):
+ valid_subsets = frozenset(['min_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['min_fact'])
+
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['whatever', '!min'])
+ self.assertIsInstance(res, set)
+ # excludes are higher precedence than includes, so !all excludes everything
+ # and then minimal_gather_subset is added. so '!all', 'other' == '!all'
+ self.assertEqual(res, set(['whatever']))
+
+ def test_gather_subset_excludes_min_and_all(self):
+ valid_subsets = frozenset(['min_fact', 'something_else', 'whatever'])
+ minimal_gather_subset = frozenset(['min_fact'])
+
+ res = collector.get_collector_names(valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['whatever', '!all', '!min'])
+ self.assertIsInstance(res, set)
+ # excludes are higher precedence than includes, so !all excludes everything
+ # and then minimal_gather_subset is added. so '!all', 'other' == '!all'
+ self.assertEqual(res, set(['whatever']))
+
+ def test_invaid_gather_subset(self):
+ valid_subsets = frozenset(['my_fact', 'something_else'])
+ minimal_gather_subset = frozenset(['my_fact'])
+
+ self.assertRaisesRegex(TypeError,
+ r'Bad subset .* given to Ansible.*allowed\:.*all,.*my_fact.*',
+ collector.get_collector_names,
+ valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=['my_fact', 'not_a_valid_gather_subset'])
+
+
+class TestFindUnresolvedRequires(unittest.TestCase):
+ def test(self):
+ names = ['network', 'virtual', 'env']
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+ res = collector.find_unresolved_requires(names, all_fact_subsets)
+ # pprint.pprint(res)
+
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set(['platform', 'distribution']))
+
+ def test_resolved(self):
+ names = ['network', 'virtual', 'env', 'platform', 'distribution']
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'distribution': [default_collectors.DistributionFactCollector],
+ 'platform': [default_collectors.PlatformFactCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+ res = collector.find_unresolved_requires(names, all_fact_subsets)
+ # pprint.pprint(res)
+
+ self.assertIsInstance(res, set)
+ self.assertEqual(res, set())
+
+
+class TestBuildDepData(unittest.TestCase):
+ def test(self):
+ names = ['network', 'virtual', 'env']
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+ res = collector.build_dep_data(names, all_fact_subsets)
+
+ # pprint.pprint(dict(res))
+ self.assertIsInstance(res, defaultdict)
+ self.assertEqual(dict(res),
+ {'network': set(['platform', 'distribution']),
+ 'virtual': set(),
+ 'env': set()})
+
+
+class TestSolveDeps(unittest.TestCase):
+ def test_no_solution(self):
+ unresolved = set(['required_thing1', 'required_thing2'])
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+
+ self.assertRaises(collector.CollectorNotFoundError,
+ collector._solve_deps,
+ unresolved,
+ all_fact_subsets)
+
+ def test(self):
+ unresolved = set(['env', 'network'])
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector],
+ 'platform': [default_collectors.PlatformFactCollector],
+ 'distribution': [default_collectors.DistributionFactCollector]}
+ res = collector.resolve_requires(unresolved, all_fact_subsets)
+
+ res = collector._solve_deps(unresolved, all_fact_subsets)
+
+ self.assertIsInstance(res, set)
+ for goal in unresolved:
+ self.assertIn(goal, res)
+
+
+class TestResolveRequires(unittest.TestCase):
+ def test_no_resolution(self):
+ unresolved = ['required_thing1', 'required_thing2']
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+ self.assertRaisesRegex(collector.UnresolvedFactDep,
+ 'unresolved fact dep.*required_thing2',
+ collector.resolve_requires,
+ unresolved, all_fact_subsets)
+
+ def test(self):
+ unresolved = ['env', 'network']
+ all_fact_subsets = {'env': [default_collectors.EnvFactCollector],
+ 'network': [default_collectors.LinuxNetworkCollector],
+ 'virtual': [default_collectors.LinuxVirtualCollector]}
+ res = collector.resolve_requires(unresolved, all_fact_subsets)
+ for goal in unresolved:
+ self.assertIn(goal, res)
+
+ def test_exception(self):
+ unresolved = ['required_thing1']
+ all_fact_subsets = {}
+ try:
+ collector.resolve_requires(unresolved, all_fact_subsets)
+ except collector.UnresolvedFactDep as exc:
+ self.assertIn(unresolved[0], '%s' % exc)
+
+
+class TestTsort(unittest.TestCase):
+ def test(self):
+ dep_map = {'network': set(['distribution', 'platform']),
+ 'virtual': set(),
+ 'platform': set(['what_platform_wants']),
+ 'what_platform_wants': set(),
+ 'network_stuff': set(['network'])}
+
+ res = collector.tsort(dep_map)
+ # pprint.pprint(res)
+
+ self.assertIsInstance(res, list)
+ names = [x[0] for x in res]
+ self.assertTrue(names.index('network_stuff') > names.index('network'))
+ self.assertTrue(names.index('platform') > names.index('what_platform_wants'))
+ self.assertTrue(names.index('network') > names.index('platform'))
+
+ def test_cycles(self):
+ dep_map = {'leaf1': set(),
+ 'leaf2': set(),
+ 'node1': set(['node2']),
+ 'node2': set(['node3']),
+ 'node3': set(['node1'])}
+
+ self.assertRaises(collector.CycleFoundInFactDeps,
+ collector.tsort,
+ dep_map)
+
+ def test_just_nodes(self):
+ dep_map = {'leaf1': set(),
+ 'leaf4': set(),
+ 'leaf3': set(),
+ 'leaf2': set()}
+
+ res = collector.tsort(dep_map)
+ self.assertIsInstance(res, list)
+ names = [x[0] for x in res]
+ # not a lot to assert here, any order of the
+ # results is valid
+ self.assertEqual(set(names), set(dep_map.keys()))
+
+ def test_self_deps(self):
+ dep_map = {'node1': set(['node1']),
+ 'node2': set(['node2'])}
+ self.assertRaises(collector.CycleFoundInFactDeps,
+ collector.tsort,
+ dep_map)
+
+ def test_unsolvable(self):
+ dep_map = {'leaf1': set(),
+ 'node2': set(['leaf2'])}
+
+ res = collector.tsort(dep_map)
+ self.assertIsInstance(res, list)
+ names = [x[0] for x in res]
+ self.assertEqual(set(names), set(dep_map.keys()))
+
+ def test_chain(self):
+ dep_map = {'leaf1': set(['leaf2']),
+ 'leaf2': set(['leaf3']),
+ 'leaf3': set(['leaf4']),
+ 'leaf4': set(),
+ 'leaf5': set(['leaf1'])}
+ res = collector.tsort(dep_map)
+ self.assertIsInstance(res, list)
+ names = [x[0] for x in res]
+ self.assertEqual(set(names), set(dep_map.keys()))
+
+ def test_multi_pass(self):
+ dep_map = {'leaf1': set(),
+ 'leaf2': set(['leaf3', 'leaf1', 'leaf4', 'leaf5']),
+ 'leaf3': set(['leaf4', 'leaf1']),
+ 'leaf4': set(['leaf1']),
+ 'leaf5': set(['leaf1'])}
+ res = collector.tsort(dep_map)
+ self.assertIsInstance(res, list)
+ names = [x[0] for x in res]
+ self.assertEqual(set(names), set(dep_map.keys()))
+ self.assertTrue(names.index('leaf1') < names.index('leaf2'))
+ for leaf in ('leaf2', 'leaf3', 'leaf4', 'leaf5'):
+ self.assertTrue(names.index('leaf1') < names.index(leaf))
+
+
+class TestCollectorClassesFromGatherSubset(unittest.TestCase):
+ maxDiff = None
+
+ def _classes(self,
+ all_collector_classes=None,
+ valid_subsets=None,
+ minimal_gather_subset=None,
+ gather_subset=None,
+ gather_timeout=None,
+ platform_info=None):
+ platform_info = platform_info or {'system': 'Linux'}
+ return collector.collector_classes_from_gather_subset(all_collector_classes=all_collector_classes,
+ valid_subsets=valid_subsets,
+ minimal_gather_subset=minimal_gather_subset,
+ gather_subset=gather_subset,
+ gather_timeout=gather_timeout,
+ platform_info=platform_info)
+
+ def test_no_args(self):
+ res = self._classes()
+ self.assertIsInstance(res, list)
+ self.assertEqual(res, [])
+
+ def test_not_all(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['!all'])
+ self.assertIsInstance(res, list)
+ self.assertEqual(res, [])
+
+ def test_all(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['all'])
+ self.assertIsInstance(res, list)
+
+ def test_hardware(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['hardware'])
+ self.assertIsInstance(res, list)
+ self.assertIn(default_collectors.PlatformFactCollector, res)
+ self.assertIn(default_collectors.LinuxHardwareCollector, res)
+
+ self.assertTrue(res.index(default_collectors.LinuxHardwareCollector) >
+ res.index(default_collectors.PlatformFactCollector))
+
+ def test_network(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['network'])
+ self.assertIsInstance(res, list)
+ self.assertIn(default_collectors.DistributionFactCollector, res)
+ self.assertIn(default_collectors.PlatformFactCollector, res)
+ self.assertIn(default_collectors.LinuxNetworkCollector, res)
+
+ self.assertTrue(res.index(default_collectors.LinuxNetworkCollector) >
+ res.index(default_collectors.PlatformFactCollector))
+ self.assertTrue(res.index(default_collectors.LinuxNetworkCollector) >
+ res.index(default_collectors.DistributionFactCollector))
+
+ # self.assertEqual(set(res, [default_collectors.DistributionFactCollector,
+ # default_collectors.PlatformFactCollector,
+ # default_collectors.LinuxNetworkCollector])
+
+ def test_env(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['env'])
+ self.assertIsInstance(res, list)
+ self.assertEqual(res, [default_collectors.EnvFactCollector])
+
+ def test_facter(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=set(['env', 'facter']))
+ self.assertIsInstance(res, list)
+ self.assertEqual(set(res),
+ set([default_collectors.EnvFactCollector,
+ default_collectors.FacterFactCollector]))
+
+ def test_facter_ohai(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=set(['env', 'facter', 'ohai']))
+ self.assertIsInstance(res, list)
+ self.assertEqual(set(res),
+ set([default_collectors.EnvFactCollector,
+ default_collectors.FacterFactCollector,
+ default_collectors.OhaiFactCollector]))
+
+ def test_just_facter(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=set(['facter']))
+ self.assertIsInstance(res, list)
+ self.assertEqual(set(res),
+ set([default_collectors.FacterFactCollector]))
+
+ def test_collector_specified_multiple_times(self):
+ res = self._classes(all_collector_classes=default_collectors.collectors,
+ gather_subset=['platform', 'all', 'machine'])
+ self.assertIsInstance(res, list)
+ self.assertIn(default_collectors.PlatformFactCollector,
+ res)
+
+ def test_unknown_collector(self):
+ # something claims 'unknown_collector' is a valid gather_subset, but there is
+ # no FactCollector mapped to 'unknown_collector'
+ self.assertRaisesRegex(TypeError,
+ r'Bad subset.*unknown_collector.*given to Ansible.*allowed\:.*all,.*env.*',
+ self._classes,
+ all_collector_classes=default_collectors.collectors,
+ gather_subset=['env', 'unknown_collector'])
diff --git a/test/units/module_utils/facts/test_collectors.py b/test/units/module_utils/facts/test_collectors.py
new file mode 100644
index 0000000..c480602
--- /dev/null
+++ b/test/units/module_utils/facts/test_collectors.py
@@ -0,0 +1,510 @@
+# unit tests for ansible fact collectors
+# -*- coding: utf-8 -*-
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import pytest
+
+from units.compat.mock import Mock, patch
+
+from . base import BaseFactsTest
+
+from ansible.module_utils.facts import collector
+
+from ansible.module_utils.facts.system.apparmor import ApparmorFactCollector
+from ansible.module_utils.facts.system.caps import SystemCapabilitiesFactCollector
+from ansible.module_utils.facts.system.cmdline import CmdLineFactCollector
+from ansible.module_utils.facts.system.distribution import DistributionFactCollector
+from ansible.module_utils.facts.system.dns import DnsFactCollector
+from ansible.module_utils.facts.system.env import EnvFactCollector
+from ansible.module_utils.facts.system.fips import FipsFactCollector
+from ansible.module_utils.facts.system.pkg_mgr import PkgMgrFactCollector, OpenBSDPkgMgrFactCollector
+from ansible.module_utils.facts.system.platform import PlatformFactCollector
+from ansible.module_utils.facts.system.python import PythonFactCollector
+from ansible.module_utils.facts.system.selinux import SelinuxFactCollector
+from ansible.module_utils.facts.system.service_mgr import ServiceMgrFactCollector
+from ansible.module_utils.facts.system.ssh_pub_keys import SshPubKeyFactCollector
+from ansible.module_utils.facts.system.user import UserFactCollector
+
+from ansible.module_utils.facts.virtual.base import VirtualCollector
+from ansible.module_utils.facts.network.base import NetworkCollector
+from ansible.module_utils.facts.hardware.base import HardwareCollector
+
+
+class CollectorException(Exception):
+ pass
+
+
+class ExceptionThrowingCollector(collector.BaseFactCollector):
+ name = 'exc_throwing'
+
+ def __init__(self, collectors=None, namespace=None, exception=None):
+ super(ExceptionThrowingCollector, self).__init__(collectors, namespace)
+ self._exception = exception or CollectorException('collection failed')
+
+ def collect(self, module=None, collected_facts=None):
+ raise self._exception
+
+
+class TestExceptionThrowingCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['exc_throwing']
+ valid_subsets = ['exc_throwing']
+ collector_class = ExceptionThrowingCollector
+
+ def test_collect(self):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ self.assertRaises(CollectorException,
+ fact_collector.collect,
+ module=module,
+ collected_facts=self.collected_facts)
+
+ def test_collect_with_namespace(self):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ self.assertRaises(CollectorException,
+ fact_collector.collect_with_namespace,
+ module=module,
+ collected_facts=self.collected_facts)
+
+
+class TestApparmorFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'apparmor']
+ valid_subsets = ['apparmor']
+ fact_namespace = 'ansible_apparmor'
+ collector_class = ApparmorFactCollector
+
+ def test_collect(self):
+ facts_dict = super(TestApparmorFacts, self).test_collect()
+ self.assertIn('status', facts_dict['apparmor'])
+
+
+class TestCapsFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'caps']
+ valid_subsets = ['caps']
+ fact_namespace = 'ansible_system_capabilities'
+ collector_class = SystemCapabilitiesFactCollector
+
+ def _mock_module(self):
+ mock_module = Mock()
+ mock_module.params = {'gather_subset': self.gather_subset,
+ 'gather_timeout': 10,
+ 'filter': '*'}
+ mock_module.get_bin_path = Mock(return_value='/usr/sbin/capsh')
+ mock_module.run_command = Mock(return_value=(0, 'Current: =ep', ''))
+ return mock_module
+
+
+class TestCmdLineFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'cmdline']
+ valid_subsets = ['cmdline']
+ fact_namespace = 'ansible_cmdline'
+ collector_class = CmdLineFactCollector
+
+ def test_parse_proc_cmdline_uefi(self):
+ uefi_cmdline = r'initrd=\70ef65e1a04a47aea04f7b5145ea3537\4.10.0-19-generic\initrd root=UUID=50973b75-4a66-4bf0-9764-2b7614489e64 ro quiet'
+ expected = {'initrd': r'\70ef65e1a04a47aea04f7b5145ea3537\4.10.0-19-generic\initrd',
+ 'root': 'UUID=50973b75-4a66-4bf0-9764-2b7614489e64',
+ 'quiet': True,
+ 'ro': True}
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector._parse_proc_cmdline(uefi_cmdline)
+
+ self.assertDictEqual(facts_dict, expected)
+
+ def test_parse_proc_cmdline_fedora(self):
+ cmdline_fedora = r'BOOT_IMAGE=/vmlinuz-4.10.16-200.fc25.x86_64 root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root rd.luks.uuid=luks-c80b7537-358b-4a07-b88c-c59ef187479b rd.lvm.lv=fedora/swap rhgb quiet LANG=en_US.UTF-8' # noqa
+
+ expected = {'BOOT_IMAGE': '/vmlinuz-4.10.16-200.fc25.x86_64',
+ 'LANG': 'en_US.UTF-8',
+ 'quiet': True,
+ 'rd.luks.uuid': 'luks-c80b7537-358b-4a07-b88c-c59ef187479b',
+ 'rd.lvm.lv': 'fedora/swap',
+ 'rhgb': True,
+ 'ro': True,
+ 'root': '/dev/mapper/fedora-root'}
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector._parse_proc_cmdline(cmdline_fedora)
+
+ self.assertDictEqual(facts_dict, expected)
+
+ def test_parse_proc_cmdline_dup_console(self):
+ example = r'BOOT_IMAGE=/boot/vmlinuz-4.4.0-72-generic root=UUID=e12e46d9-06c9-4a64-a7b3-60e24b062d90 ro console=tty1 console=ttyS0'
+
+ # FIXME: Two 'console' keywords? Using a dict for the fact value here loses info. Currently the 'last' one wins
+ expected = {'BOOT_IMAGE': '/boot/vmlinuz-4.4.0-72-generic',
+ 'root': 'UUID=e12e46d9-06c9-4a64-a7b3-60e24b062d90',
+ 'ro': True,
+ 'console': 'ttyS0'}
+
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector._parse_proc_cmdline(example)
+
+ # TODO: fails because we lose a 'console'
+ self.assertDictEqual(facts_dict, expected)
+
+
+class TestDistributionFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'distribution']
+ valid_subsets = ['distribution']
+ fact_namespace = 'ansible_distribution'
+ collector_class = DistributionFactCollector
+
+
+class TestDnsFacts(BaseFactsTest):
+
+ __test__ = True
+ gather_subset = ['!all', 'dns']
+ valid_subsets = ['dns']
+ fact_namespace = 'ansible_dns'
+ collector_class = DnsFactCollector
+
+
+class TestEnvFacts(BaseFactsTest):
+
+ __test__ = True
+ gather_subset = ['!all', 'env']
+ valid_subsets = ['env']
+ fact_namespace = 'ansible_env'
+ collector_class = EnvFactCollector
+
+ def test_collect(self):
+ facts_dict = super(TestEnvFacts, self).test_collect()
+ self.assertIn('HOME', facts_dict['env'])
+
+
+class TestFipsFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'fips']
+ valid_subsets = ['fips']
+ fact_namespace = 'ansible_fips'
+ collector_class = FipsFactCollector
+
+
+class TestHardwareCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'hardware']
+ valid_subsets = ['hardware']
+ fact_namespace = 'ansible_hardware'
+ collector_class = HardwareCollector
+ collected_facts = {'ansible_architecture': 'x86_64'}
+
+
+class TestNetworkCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'network']
+ valid_subsets = ['network']
+ fact_namespace = 'ansible_network'
+ collector_class = NetworkCollector
+
+
+class TestPkgMgrFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'pkg_mgr']
+ valid_subsets = ['pkg_mgr']
+ fact_namespace = 'ansible_pkgmgr'
+ collector_class = PkgMgrFactCollector
+ collected_facts = {
+ "ansible_distribution": "Fedora",
+ "ansible_distribution_major_version": "28",
+ "ansible_os_family": "RedHat"
+ }
+
+ 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)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+
+
+class TestMacOSXPkgMgrFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'pkg_mgr']
+ valid_subsets = ['pkg_mgr']
+ fact_namespace = 'ansible_pkgmgr'
+ collector_class = PkgMgrFactCollector
+ collected_facts = {
+ "ansible_distribution": "MacOSX",
+ "ansible_distribution_major_version": "11",
+ "ansible_os_family": "Darwin"
+ }
+
+ @patch('ansible.module_utils.facts.system.pkg_mgr.os.path.exists', side_effect=lambda x: x == '/opt/homebrew/bin/brew')
+ def test_collect_opt_homebrew(self, p_exists):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+ self.assertEqual(facts_dict['pkg_mgr'], 'homebrew')
+
+ @patch('ansible.module_utils.facts.system.pkg_mgr.os.path.exists', side_effect=lambda x: x == '/usr/local/bin/brew')
+ def test_collect_usr_homebrew(self, p_exists):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+ self.assertEqual(facts_dict['pkg_mgr'], 'homebrew')
+
+ @patch('ansible.module_utils.facts.system.pkg_mgr.os.path.exists', side_effect=lambda x: x == '/opt/local/bin/port')
+ def test_collect_macports(self, p_exists):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+ self.assertEqual(facts_dict['pkg_mgr'], 'macports')
+
+
+def _sanitize_os_path_apt_get(path):
+ if path == '/usr/bin/apt-get':
+ return True
+ else:
+ return False
+
+
+class TestPkgMgrFactsAptFedora(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'pkg_mgr']
+ valid_subsets = ['pkg_mgr']
+ fact_namespace = 'ansible_pkgmgr'
+ collector_class = PkgMgrFactCollector
+ collected_facts = {
+ "ansible_distribution": "Fedora",
+ "ansible_distribution_major_version": "28",
+ "ansible_os_family": "RedHat",
+ "ansible_pkg_mgr": "apt"
+ }
+
+ @patch('ansible.module_utils.facts.system.pkg_mgr.os.path.exists', side_effect=_sanitize_os_path_apt_get)
+ def test_collect(self, mock_os_path_exists):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+
+
+class TestOpenBSDPkgMgrFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'pkg_mgr']
+ valid_subsets = ['pkg_mgr']
+ fact_namespace = 'ansible_pkgmgr'
+ collector_class = OpenBSDPkgMgrFactCollector
+
+ 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)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertIn('pkg_mgr', facts_dict)
+ self.assertEqual(facts_dict['pkg_mgr'], 'openbsd_pkg')
+
+
+class TestPlatformFactCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'platform']
+ valid_subsets = ['platform']
+ fact_namespace = 'ansible_platform'
+ collector_class = PlatformFactCollector
+
+
+class TestPythonFactCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'python']
+ valid_subsets = ['python']
+ fact_namespace = 'ansible_python'
+ collector_class = PythonFactCollector
+
+
+class TestSelinuxFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'selinux']
+ valid_subsets = ['selinux']
+ fact_namespace = 'ansible_selinux'
+ collector_class = SelinuxFactCollector
+
+ def test_no_selinux(self):
+ with patch('ansible.module_utils.facts.system.selinux.HAVE_SELINUX', False):
+ module = self._mock_module()
+ fact_collector = self.collector_class()
+ 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):
+ __test__ = True
+ gather_subset = ['!all', 'service_mgr']
+ valid_subsets = ['service_mgr']
+ fact_namespace = 'ansible_service_mgr'
+ collector_class = ServiceMgrFactCollector
+
+ # TODO: dedupe some of this test code
+
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+ @patch('ansible.module_utils.facts.system.service_mgr.ServiceMgrFactCollector.is_systemd_managed', return_value=False)
+ @patch('ansible.module_utils.facts.system.service_mgr.ServiceMgrFactCollector.is_systemd_managed_offline', return_value=False)
+ @patch('ansible.module_utils.facts.system.service_mgr.os.path.exists', return_value=False)
+ @pytest.mark.skip(reason='faulty test')
+ def test_service_mgr_runit_one(self, mock_gfc, mock_ism, mock_ismo, mock_ope):
+ # no /proc/1/comm, ps returns non-0
+ # should fallback to 'service'
+ module = self._mock_module()
+ module.run_command = Mock(return_value=(1, '', 'wat'))
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['service_mgr'], 'service')
+
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+ def test_no_proc1_ps_random_init(self, mock_gfc):
+ # no /proc/1/comm, ps returns '/sbin/sys11' which we dont know
+ # should end up return 'sys11'
+ module = self._mock_module()
+ module.run_command = Mock(return_value=(0, '/sbin/sys11', ''))
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['service_mgr'], 'sys11')
+
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+ @patch('ansible.module_utils.facts.system.service_mgr.ServiceMgrFactCollector.is_systemd_managed', return_value=False)
+ @patch('ansible.module_utils.facts.system.service_mgr.ServiceMgrFactCollector.is_systemd_managed_offline', return_value=False)
+ @patch('ansible.module_utils.facts.system.service_mgr.os.path.exists', return_value=False)
+ @pytest.mark.skip(reason='faulty test')
+ def test_service_mgr_runit_two(self, mock_gfc, mock_ism, mock_ismo, mock_ope):
+ # no /proc/1/comm, ps fails, distro and system are clowncar
+ # should end up return 'sys11'
+ module = self._mock_module()
+ module.run_command = Mock(return_value=(1, '', ''))
+ collected_facts = {'distribution': 'clowncar',
+ 'system': 'ClownCarOS'}
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module,
+ collected_facts=collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['service_mgr'], 'service')
+
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value='runit-init')
+ @patch('ansible.module_utils.facts.system.service_mgr.os.path.islink', side_effect=lambda x: x == '/sbin/init')
+ @patch('ansible.module_utils.facts.system.service_mgr.os.readlink', side_effect=lambda x: '/sbin/runit-init' if x == '/sbin/init' else '/bin/false')
+ def test_service_mgr_runit(self, mock_gfc, mock_opl, mock_orl):
+ # /proc/1/comm contains 'runit-init', ps fails, service manager is runit
+ # should end up return 'runit'
+ module = self._mock_module()
+ module.run_command = Mock(return_value=(1, '', ''))
+ collected_facts = {'ansible_system': 'Linux'}
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module,
+ collected_facts=collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['service_mgr'], 'runit')
+
+ @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+ @patch('ansible.module_utils.facts.system.service_mgr.os.path.islink', side_effect=lambda x: x == '/sbin/init')
+ @patch('ansible.module_utils.facts.system.service_mgr.os.readlink', side_effect=lambda x: '/sbin/runit-init' if x == '/sbin/init' else '/bin/false')
+ def test_service_mgr_runit_no_comm(self, mock_gfc, mock_opl, mock_orl):
+ # no /proc/1/comm, ps returns 'COMMAND\n', service manager is runit
+ # should end up return 'runit'
+ module = self._mock_module()
+ module.run_command = Mock(return_value=(1, 'COMMAND\n', ''))
+ collected_facts = {'ansible_system': 'Linux'}
+ fact_collector = self.collector_class()
+ facts_dict = fact_collector.collect(module=module,
+ collected_facts=collected_facts)
+ self.assertIsInstance(facts_dict, dict)
+ self.assertEqual(facts_dict['service_mgr'], 'runit')
+
+ # TODO: reenable these tests when we can mock more easily
+
+# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+# def test_sunos_fallback(self, mock_gfc):
+# # no /proc/1/comm, ps fails, 'system' is SunOS
+# # should end up return 'smf'?
+# module = self._mock_module()
+# # FIXME: the result here is a kluge to at least cover more of service_mgr.collect
+# # TODO: remove
+# # FIXME: have to force a pid for results here to get into any of the system/distro checks
+# module.run_command = Mock(return_value=(1, ' 37 ', ''))
+# collected_facts = {'system': 'SunOS'}
+# fact_collector = self.collector_class(module=module)
+# facts_dict = fact_collector.collect(collected_facts=collected_facts)
+# print('facts_dict: %s' % facts_dict)
+# self.assertIsInstance(facts_dict, dict)
+# self.assertEqual(facts_dict['service_mgr'], 'smf')
+
+# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+# def test_aix_fallback(self, mock_gfc):
+# # no /proc/1/comm, ps fails, 'system' is SunOS
+# # should end up return 'smf'?
+# module = self._mock_module()
+# module.run_command = Mock(return_value=(1, '', ''))
+# collected_facts = {'system': 'AIX'}
+# fact_collector = self.collector_class(module=module)
+# facts_dict = fact_collector.collect(collected_facts=collected_facts)
+# print('facts_dict: %s' % facts_dict)
+# self.assertIsInstance(facts_dict, dict)
+# self.assertEqual(facts_dict['service_mgr'], 'src')
+
+# @patch('ansible.module_utils.facts.system.service_mgr.get_file_content', return_value=None)
+# def test_linux_fallback(self, mock_gfc):
+# # no /proc/1/comm, ps fails, 'system' is SunOS
+# # should end up return 'smf'?
+# module = self._mock_module()
+# module.run_command = Mock(return_value=(1, ' 37 ', ''))
+# collected_facts = {'system': 'Linux'}
+# fact_collector = self.collector_class(module=module)
+# facts_dict = fact_collector.collect(collected_facts=collected_facts)
+# print('facts_dict: %s' % facts_dict)
+# self.assertIsInstance(facts_dict, dict)
+# self.assertEqual(facts_dict['service_mgr'], 'sdfadf')
+
+
+class TestSshPubKeyFactCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'ssh_pub_keys']
+ valid_subsets = ['ssh_pub_keys']
+ fact_namespace = 'ansible_ssh_pub_leys'
+ collector_class = SshPubKeyFactCollector
+
+
+class TestUserFactCollector(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'user']
+ valid_subsets = ['user']
+ fact_namespace = 'ansible_user'
+ collector_class = UserFactCollector
+
+
+class TestVirtualFacts(BaseFactsTest):
+ __test__ = True
+ gather_subset = ['!all', 'virtual']
+ valid_subsets = ['virtual']
+ fact_namespace = 'ansible_virtual'
+ collector_class = VirtualCollector
diff --git a/test/units/module_utils/facts/test_date_time.py b/test/units/module_utils/facts/test_date_time.py
new file mode 100644
index 0000000..6abc36a
--- /dev/null
+++ b/test/units/module_utils/facts/test_date_time.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Copyright (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 pytest
+import datetime
+import string
+import time
+
+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)
+
+
+@pytest.fixture
+def fake_now(monkeypatch):
+ """
+ Patch `datetime.datetime.fromtimestamp()`, `datetime.datetime.utcfromtimestamp()`,
+ 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 _time():
+ return EPOCH_TS
+
+ monkeypatch.setattr(date_time.datetime, 'datetime', FakeNow)
+ monkeypatch.setattr(time, 'time', _time)
+
+
+@pytest.fixture
+def fake_date_facts(fake_now):
+ """Return a predictable instance of collected date_time facts."""
+
+ collector = date_time.DateTimeFactCollector()
+ data = collector.collect()
+
+ return data
+
+
+@pytest.mark.parametrize(
+ ('fact_name', 'fact_value'),
+ (
+ ('year', '2020'),
+ ('month', '07'),
+ ('weekday', 'Saturday'),
+ ('weekday_number', '6'),
+ ('weeknumber', '27'),
+ ('day', '11'),
+ ('hour', '12'),
+ ('minute', '34'),
+ ('second', '56'),
+ ('date', '2020-07-11'),
+ ('time', '12:34:56'),
+ ('iso8601_basic', '20200711T123456124356'),
+ ('iso8601_basic_short', '20200711T123456'),
+ ('iso8601_micro', '2020-07-11T02:34:56.124356Z'),
+ ('iso8601', '2020-07-11T02:34:56Z'),
+ ),
+)
+def test_date_time_facts(fake_date_facts, fact_name, fact_value):
+ assert fake_date_facts['date_time'][fact_name] == fact_value
+
+
+def test_date_time_epoch(fake_date_facts):
+ """Test that format of returned epoch value is correct"""
+
+ assert fake_date_facts['date_time']['epoch'].isdigit()
+ assert len(fake_date_facts['date_time']['epoch']) == 10 # This length will not change any time soon
+ assert fake_date_facts['date_time']['epoch_int'].isdigit()
+ assert len(fake_date_facts['date_time']['epoch_int']) == 10 # This length will not change any time soon
+
+
+@pytest.mark.parametrize('fact_name', ('tz', 'tz_dst'))
+def test_date_time_tz(fake_date_facts, fact_name):
+ """
+ Test the returned value for timezone consists of only uppercase
+ letters and is the expected length.
+ """
+
+ assert fake_date_facts['date_time'][fact_name].isupper()
+ assert 2 <= len(fake_date_facts['date_time'][fact_name]) <= 5
+ assert not set(fake_date_facts['date_time'][fact_name]).difference(set(string.ascii_uppercase))
+
+
+def test_date_time_tz_offset(fake_date_facts):
+ """
+ Test that the timezone offset begins with a `+` or `-` and ends with a
+ series of integers.
+ """
+
+ assert fake_date_facts['date_time']['tz_offset'][0] in ['-', '+']
+ assert fake_date_facts['date_time']['tz_offset'][1:].isdigit()
+ assert len(fake_date_facts['date_time']['tz_offset']) == 5
diff --git a/test/units/module_utils/facts/test_facts.py b/test/units/module_utils/facts/test_facts.py
new file mode 100644
index 0000000..c794f03
--- /dev/null
+++ b/test/units/module_utils/facts/test_facts.py
@@ -0,0 +1,646 @@
+# This file is part of Ansible
+# -*- coding: utf-8 -*-
+#
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+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 Mock, patch
+
+from ansible.module_utils import facts
+from ansible.module_utils.facts import hardware
+from ansible.module_utils.facts import network
+from ansible.module_utils.facts import virtual
+
+
+class BaseTestFactsPlatform(unittest.TestCase):
+ platform_id = 'Generic'
+ fact_class = hardware.base.Hardware
+ collector_class = None
+
+ """Verify that the automagic in Hardware.__new__ selects the right subclass."""
+ @patch('platform.system')
+ def test_new(self, mock_platform):
+ if not self.fact_class:
+ pytest.skip('This platform (%s) does not have a fact_class.' % self.platform_id)
+ mock_platform.return_value = self.platform_id
+ inst = self.fact_class(module=Mock(), load_on_init=False)
+ self.assertIsInstance(inst, self.fact_class)
+ self.assertEqual(inst.platform, self.platform_id)
+
+ def test_subclass(self):
+ if not self.fact_class:
+ pytest.skip('This platform (%s) does not have a fact_class.' % self.platform_id)
+ # 'Generic' will try to map to platform.system() that we are not mocking here
+ if self.platform_id == 'Generic':
+ return
+ inst = self.fact_class(module=Mock(), load_on_init=False)
+ self.assertIsInstance(inst, self.fact_class)
+ self.assertEqual(inst.platform, self.platform_id)
+
+ def test_collector(self):
+ if not self.collector_class:
+ pytest.skip('This test class needs to be updated to specify collector_class')
+ inst = self.collector_class()
+ self.assertIsInstance(inst, self.collector_class)
+ self.assertEqual(inst._platform, self.platform_id)
+
+
+class TestLinuxFactsPlatform(BaseTestFactsPlatform):
+ platform_id = 'Linux'
+ fact_class = hardware.linux.LinuxHardware
+ collector_class = hardware.linux.LinuxHardwareCollector
+
+
+class TestHurdFactsPlatform(BaseTestFactsPlatform):
+ platform_id = 'GNU'
+ fact_class = hardware.hurd.HurdHardware
+ collector_class = hardware.hurd.HurdHardwareCollector
+
+
+class TestSunOSHardware(BaseTestFactsPlatform):
+ platform_id = 'SunOS'
+ fact_class = hardware.sunos.SunOSHardware
+ collector_class = hardware.sunos.SunOSHardwareCollector
+
+
+class TestOpenBSDHardware(BaseTestFactsPlatform):
+ platform_id = 'OpenBSD'
+ fact_class = hardware.openbsd.OpenBSDHardware
+ collector_class = hardware.openbsd.OpenBSDHardwareCollector
+
+
+class TestFreeBSDHardware(BaseTestFactsPlatform):
+ platform_id = 'FreeBSD'
+ fact_class = hardware.freebsd.FreeBSDHardware
+ collector_class = hardware.freebsd.FreeBSDHardwareCollector
+
+
+class TestDragonFlyHardware(BaseTestFactsPlatform):
+ platform_id = 'DragonFly'
+ fact_class = None
+ collector_class = hardware.dragonfly.DragonFlyHardwareCollector
+
+
+class TestNetBSDHardware(BaseTestFactsPlatform):
+ platform_id = 'NetBSD'
+ fact_class = hardware.netbsd.NetBSDHardware
+ collector_class = hardware.netbsd.NetBSDHardwareCollector
+
+
+class TestAIXHardware(BaseTestFactsPlatform):
+ platform_id = 'AIX'
+ fact_class = hardware.aix.AIXHardware
+ collector_class = hardware.aix.AIXHardwareCollector
+
+
+class TestHPUXHardware(BaseTestFactsPlatform):
+ platform_id = 'HP-UX'
+ fact_class = hardware.hpux.HPUXHardware
+ collector_class = hardware.hpux.HPUXHardwareCollector
+
+
+class TestDarwinHardware(BaseTestFactsPlatform):
+ platform_id = 'Darwin'
+ fact_class = hardware.darwin.DarwinHardware
+ collector_class = hardware.darwin.DarwinHardwareCollector
+
+
+class TestGenericNetwork(BaseTestFactsPlatform):
+ platform_id = 'Generic'
+ fact_class = network.base.Network
+
+
+class TestHurdPfinetNetwork(BaseTestFactsPlatform):
+ platform_id = 'GNU'
+ fact_class = network.hurd.HurdPfinetNetwork
+ collector_class = network.hurd.HurdNetworkCollector
+
+
+class TestLinuxNetwork(BaseTestFactsPlatform):
+ platform_id = 'Linux'
+ fact_class = network.linux.LinuxNetwork
+ collector_class = network.linux.LinuxNetworkCollector
+
+
+class TestGenericBsdIfconfigNetwork(BaseTestFactsPlatform):
+ platform_id = 'Generic_BSD_Ifconfig'
+ fact_class = network.generic_bsd.GenericBsdIfconfigNetwork
+ collector_class = None
+
+
+class TestHPUXNetwork(BaseTestFactsPlatform):
+ platform_id = 'HP-UX'
+ fact_class = network.hpux.HPUXNetwork
+ collector_class = network.hpux.HPUXNetworkCollector
+
+
+class TestDarwinNetwork(BaseTestFactsPlatform):
+ platform_id = 'Darwin'
+ fact_class = network.darwin.DarwinNetwork
+ collector_class = network.darwin.DarwinNetworkCollector
+
+
+class TestFreeBSDNetwork(BaseTestFactsPlatform):
+ platform_id = 'FreeBSD'
+ fact_class = network.freebsd.FreeBSDNetwork
+ collector_class = network.freebsd.FreeBSDNetworkCollector
+
+
+class TestDragonFlyNetwork(BaseTestFactsPlatform):
+ platform_id = 'DragonFly'
+ fact_class = network.dragonfly.DragonFlyNetwork
+ collector_class = network.dragonfly.DragonFlyNetworkCollector
+
+
+class TestAIXNetwork(BaseTestFactsPlatform):
+ platform_id = 'AIX'
+ fact_class = network.aix.AIXNetwork
+ collector_class = network.aix.AIXNetworkCollector
+
+
+class TestNetBSDNetwork(BaseTestFactsPlatform):
+ platform_id = 'NetBSD'
+ fact_class = network.netbsd.NetBSDNetwork
+ collector_class = network.netbsd.NetBSDNetworkCollector
+
+
+class TestOpenBSDNetwork(BaseTestFactsPlatform):
+ platform_id = 'OpenBSD'
+ fact_class = network.openbsd.OpenBSDNetwork
+ collector_class = network.openbsd.OpenBSDNetworkCollector
+
+
+class TestSunOSNetwork(BaseTestFactsPlatform):
+ platform_id = 'SunOS'
+ fact_class = network.sunos.SunOSNetwork
+ collector_class = network.sunos.SunOSNetworkCollector
+
+
+class TestLinuxVirtual(BaseTestFactsPlatform):
+ platform_id = 'Linux'
+ fact_class = virtual.linux.LinuxVirtual
+ collector_class = virtual.linux.LinuxVirtualCollector
+
+
+class TestFreeBSDVirtual(BaseTestFactsPlatform):
+ platform_id = 'FreeBSD'
+ fact_class = virtual.freebsd.FreeBSDVirtual
+ collector_class = virtual.freebsd.FreeBSDVirtualCollector
+
+
+class TestNetBSDVirtual(BaseTestFactsPlatform):
+ platform_id = 'NetBSD'
+ fact_class = virtual.netbsd.NetBSDVirtual
+ collector_class = virtual.netbsd.NetBSDVirtualCollector
+
+
+class TestOpenBSDVirtual(BaseTestFactsPlatform):
+ platform_id = 'OpenBSD'
+ fact_class = virtual.openbsd.OpenBSDVirtual
+ collector_class = virtual.openbsd.OpenBSDVirtualCollector
+
+
+class TestHPUXVirtual(BaseTestFactsPlatform):
+ platform_id = 'HP-UX'
+ fact_class = virtual.hpux.HPUXVirtual
+ collector_class = virtual.hpux.HPUXVirtualCollector
+
+
+class TestSunOSVirtual(BaseTestFactsPlatform):
+ platform_id = 'SunOS'
+ fact_class = virtual.sunos.SunOSVirtual
+ collector_class = virtual.sunos.SunOSVirtualCollector
+
+
+LSBLK_OUTPUT = b"""
+/dev/sda
+/dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0
+/dev/sda2 66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK
+/dev/mapper/fedora_dhcp129--186-swap eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d
+/dev/mapper/fedora_dhcp129--186-root d34cf5e3-3449-4a6c-8179-a1feb2bca6ce
+/dev/mapper/fedora_dhcp129--186-home 2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d
+/dev/sr0
+/dev/loop0 0f031512-ab15-497d-9abd-3a512b4a9390
+/dev/loop1 7c1b0f30-cf34-459f-9a70-2612f82b870a
+/dev/loop9 0f031512-ab15-497d-9abd-3a512b4a9390
+/dev/loop9 7c1b4444-cf34-459f-9a70-2612f82b870a
+/dev/mapper/docker-253:1-1050967-pool
+/dev/loop2
+/dev/mapper/docker-253:1-1050967-pool
+"""
+
+LSBLK_OUTPUT_2 = b"""
+/dev/sda
+/dev/sda1 32caaec3-ef40-4691-a3b6-438c3f9bc1c0
+/dev/sda2 66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK
+/dev/mapper/fedora_dhcp129--186-swap eae6059d-2fbe-4d1c-920d-a80bbeb1ac6d
+/dev/mapper/fedora_dhcp129--186-root d34cf5e3-3449-4a6c-8179-a1feb2bca6ce
+/dev/mapper/fedora_dhcp129--186-home 2d3e4853-fa69-4ccf-8a6a-77b05ab0a42d
+/dev/mapper/an-example-mapper with a space in the name 84639acb-013f-4d2f-9392-526a572b4373
+/dev/sr0
+/dev/loop0 0f031512-ab15-497d-9abd-3a512b4a9390
+"""
+
+LSBLK_UUIDS = {'/dev/sda1': '66Ojcd-ULtu-1cZa-Tywo-mx0d-RF4O-ysA9jK'}
+
+UDEVADM_UUID = 'N/A'
+
+MTAB = r"""
+sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
+proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
+devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=8044400k,nr_inodes=2011100,mode=755 0 0
+securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
+tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0
+devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
+tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0
+tmpfs /sys/fs/cgroup tmpfs ro,seclabel,nosuid,nodev,noexec,mode=755 0 0
+cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
+pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0
+cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
+cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
+cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
+cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
+cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
+cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
+cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpu,cpuacct 0 0
+cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
+cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
+cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_cls,net_prio 0 0
+configfs /sys/kernel/config configfs rw,relatime 0 0
+/dev/mapper/fedora_dhcp129--186-root / ext4 rw,seclabel,relatime,data=ordered 0 0
+selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
+systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=24,pgrp=1,timeout=0,minproto=5,maxproto=5,direct 0 0
+debugfs /sys/kernel/debug debugfs rw,seclabel,relatime 0 0
+hugetlbfs /dev/hugepages hugetlbfs rw,seclabel,relatime 0 0
+tmpfs /tmp tmpfs rw,seclabel 0 0
+mqueue /dev/mqueue mqueue rw,seclabel,relatime 0 0
+/dev/loop0 /var/lib/machines btrfs rw,seclabel,relatime,space_cache,subvolid=5,subvol=/ 0 0
+/dev/sda1 /boot ext4 rw,seclabel,relatime,data=ordered 0 0
+/dev/mapper/fedora_dhcp129--186-home /home ext4 rw,seclabel,relatime,data=ordered 0 0
+tmpfs /run/user/1000 tmpfs rw,seclabel,nosuid,nodev,relatime,size=1611044k,mode=700,uid=1000,gid=1000 0 0
+gvfsd-fuse /run/user/1000/gvfs fuse.gvfsd-fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0
+grimlock.g.a: /home/adrian/sshfs-grimlock fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:test_path/path_with'single_quotes /home/adrian/sshfs-grimlock-single-quote fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:path_with'single_quotes /home/adrian/sshfs-grimlock-single-quote-2 fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+grimlock.g.a:/mnt/data/foto's /home/adrian/fotos fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
+\\Windows\share /data/ cifs credentials=/root/.creds 0 0
+"""
+
+MTAB_ENTRIES = [
+ [
+ 'sysfs',
+ '/sys',
+ 'sysfs',
+ 'rw,seclabel,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ ['proc', '/proc', 'proc', 'rw,nosuid,nodev,noexec,relatime', '0', '0'],
+ [
+ 'devtmpfs',
+ '/dev',
+ 'devtmpfs',
+ 'rw,seclabel,nosuid,size=8044400k,nr_inodes=2011100,mode=755',
+ '0',
+ '0'
+ ],
+ [
+ 'securityfs',
+ '/sys/kernel/security',
+ 'securityfs',
+ 'rw,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/dev/shm', 'tmpfs', 'rw,seclabel,nosuid,nodev', '0', '0'],
+ [
+ 'devpts',
+ '/dev/pts',
+ 'devpts',
+ 'rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/run', 'tmpfs', 'rw,seclabel,nosuid,nodev,mode=755', '0', '0'],
+ [
+ 'tmpfs',
+ '/sys/fs/cgroup',
+ 'tmpfs',
+ 'ro,seclabel,nosuid,nodev,noexec,mode=755',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/systemd',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd',
+ '0',
+ '0'
+ ],
+ [
+ 'pstore',
+ '/sys/fs/pstore',
+ 'pstore',
+ 'rw,seclabel,nosuid,nodev,noexec,relatime',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/devices',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,devices',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/freezer',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,freezer',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/memory',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,memory',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/pids',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,pids',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/blkio',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,blkio',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/cpuset',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,cpuset',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/cpu,cpuacct',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,cpu,cpuacct',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/hugetlb',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,hugetlb',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/perf_event',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,perf_event',
+ '0',
+ '0'
+ ],
+ [
+ 'cgroup',
+ '/sys/fs/cgroup/net_cls,net_prio',
+ 'cgroup',
+ 'rw,nosuid,nodev,noexec,relatime,net_cls,net_prio',
+ '0',
+ '0'
+ ],
+ ['configfs', '/sys/kernel/config', 'configfs', 'rw,relatime', '0', '0'],
+ [
+ '/dev/mapper/fedora_dhcp129--186-root',
+ '/',
+ 'ext4',
+ 'rw,seclabel,relatime,data=ordered',
+ '0',
+ '0'
+ ],
+ ['selinuxfs', '/sys/fs/selinux', 'selinuxfs', 'rw,relatime', '0', '0'],
+ [
+ 'systemd-1',
+ '/proc/sys/fs/binfmt_misc',
+ 'autofs',
+ 'rw,relatime,fd=24,pgrp=1,timeout=0,minproto=5,maxproto=5,direct',
+ '0',
+ '0'
+ ],
+ ['debugfs', '/sys/kernel/debug', 'debugfs', 'rw,seclabel,relatime', '0', '0'],
+ [
+ 'hugetlbfs',
+ '/dev/hugepages',
+ 'hugetlbfs',
+ 'rw,seclabel,relatime',
+ '0',
+ '0'
+ ],
+ ['tmpfs', '/tmp', 'tmpfs', 'rw,seclabel', '0', '0'],
+ ['mqueue', '/dev/mqueue', 'mqueue', 'rw,seclabel,relatime', '0', '0'],
+ [
+ '/dev/loop0',
+ '/var/lib/machines',
+ 'btrfs',
+ 'rw,seclabel,relatime,space_cache,subvolid=5,subvol=/',
+ '0',
+ '0'
+ ],
+ ['/dev/sda1', '/boot', 'ext4', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ # A 'none' fstype
+ ['/dev/sdz3', '/not/a/real/device', 'none', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ # lets assume this is a bindmount
+ ['/dev/sdz4', '/not/a/real/bind_mount', 'ext4', 'rw,seclabel,relatime,data=ordered', '0', '0'],
+ [
+ '/dev/mapper/fedora_dhcp129--186-home',
+ '/home',
+ 'ext4',
+ 'rw,seclabel,relatime,data=ordered',
+ '0',
+ '0'
+ ],
+ [
+ 'tmpfs',
+ '/run/user/1000',
+ 'tmpfs',
+ 'rw,seclabel,nosuid,nodev,relatime,size=1611044k,mode=700,uid=1000,gid=1000',
+ '0',
+ '0'
+ ],
+ [
+ 'gvfsd-fuse',
+ '/run/user/1000/gvfs',
+ 'fuse.gvfsd-fuse',
+ 'rw,nosuid,nodev,relatime,user_id=1000,group_id=1000',
+ '0',
+ '0'
+ ],
+ ['fusectl', '/sys/fs/fuse/connections', 'fusectl', 'rw,relatime', '0', '0'],
+ # Mount path with space in the name
+ # The space is encoded as \040 since the fields in /etc/mtab are space-delimeted
+ ['/dev/sdz9', r'/mnt/foo\040bar', 'ext4', 'rw,relatime', '0', '0'],
+ ['\\\\Windows\\share', '/data/', 'cifs', 'credentials=/root/.creds', '0', '0'],
+]
+
+BIND_MOUNTS = ['/not/a/real/bind_mount']
+
+with open(os.path.join(os.path.dirname(__file__), 'fixtures/findmount_output.txt')) as f:
+ FINDMNT_OUTPUT = f.read()
+
+
+class TestFactsLinuxHardwareGetMountFacts(unittest.TestCase):
+
+ # FIXME: mock.patch instead
+ def setUp(self):
+ # The @timeout tracebacks if there isn't a GATHER_TIMEOUT is None (the default until get_all_facts sets it via global)
+ facts.GATHER_TIMEOUT = 10
+
+ def tearDown(self):
+ facts.GATHER_TIMEOUT = None
+
+ # The Hardware subclasses freakout if instaniated directly, so
+ # mock platform.system and inst Hardware() so we get a LinuxHardware()
+ # we can test.
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._mtab_entries', return_value=MTAB_ENTRIES)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._find_bind_mounts', return_value=BIND_MOUNTS)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._lsblk_uuid', return_value=LSBLK_UUIDS)
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._udevadm_uuid', return_value=UDEVADM_UUID)
+ def test_get_mount_facts(self,
+ mock_lsblk_uuid,
+ mock_find_bind_mounts,
+ mock_mtab_entries,
+ mock_udevadm_uuid):
+ module = Mock()
+ # Returns a LinuxHardware-ish
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+
+ # Nothing returned, just self.facts modified as a side effect
+ mount_facts = lh.get_mount_facts()
+ self.assertIsInstance(mount_facts, dict)
+ self.assertIn('mounts', mount_facts)
+ self.assertIsInstance(mount_facts['mounts'], list)
+ self.assertIsInstance(mount_facts['mounts'][0], dict)
+
+ # Find mounts with space in the mountpoint path
+ mounts_with_space = [x for x in mount_facts['mounts'] if ' ' in x['mount']]
+ self.assertEqual(len(mounts_with_space), 1)
+ self.assertEqual(mounts_with_space[0]['mount'], '/mnt/foo bar')
+
+ @patch('ansible.module_utils.facts.hardware.linux.get_file_content', return_value=MTAB)
+ def test_get_mtab_entries(self, mock_get_file_content):
+
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ mtab_entries = lh._mtab_entries()
+ self.assertIsInstance(mtab_entries, list)
+ self.assertIsInstance(mtab_entries[0], list)
+ self.assertEqual(len(mtab_entries), 39)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(0, FINDMNT_OUTPUT, ''))
+ def test_find_bind_mounts(self, mock_run_findmnt):
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ # If bind_mounts becomes another seq type, feel free to change
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 1)
+ self.assertIn('/not/a/real/bind_mount', bind_mounts)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_findmnt', return_value=(37, '', ''))
+ def test_find_bind_mounts_non_zero(self, mock_run_findmnt):
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 0)
+
+ def test_find_bind_mounts_no_findmnts(self):
+ module = Mock()
+ module.get_bin_path = Mock(return_value=None)
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ bind_mounts = lh._find_bind_mounts()
+
+ self.assertIsInstance(bind_mounts, set)
+ self.assertEqual(len(bind_mounts), 0)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT, ''))
+ def test_lsblk_uuid(self, mock_run_lsblk):
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertIn(b'/dev/loop9', lsblk_uuids)
+ self.assertIn(b'/dev/sda1', lsblk_uuids)
+ self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(37, LSBLK_OUTPUT, ''))
+ def test_lsblk_uuid_non_zero(self, mock_run_lsblk):
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertEqual(len(lsblk_uuids), 0)
+
+ def test_lsblk_uuid_no_lsblk(self):
+ module = Mock()
+ module.get_bin_path = Mock(return_value=None)
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertEqual(len(lsblk_uuids), 0)
+
+ @patch('ansible.module_utils.facts.hardware.linux.LinuxHardware._run_lsblk', return_value=(0, LSBLK_OUTPUT_2, ''))
+ def test_lsblk_uuid_dev_with_space_in_name(self, mock_run_lsblk):
+ module = Mock()
+ lh = hardware.linux.LinuxHardware(module=module, load_on_init=False)
+ lsblk_uuids = lh._lsblk_uuid()
+ self.assertIsInstance(lsblk_uuids, dict)
+ self.assertIn(b'/dev/loop0', lsblk_uuids)
+ self.assertIn(b'/dev/sda1', lsblk_uuids)
+ self.assertEqual(lsblk_uuids[b'/dev/mapper/an-example-mapper with a space in the name'], b'84639acb-013f-4d2f-9392-526a572b4373')
+ self.assertEqual(lsblk_uuids[b'/dev/sda1'], b'32caaec3-ef40-4691-a3b6-438c3f9bc1c0')
diff --git a/test/units/module_utils/facts/test_sysctl.py b/test/units/module_utils/facts/test_sysctl.py
new file mode 100644
index 0000000..c369b61
--- /dev/null
+++ b/test/units/module_utils/facts/test_sysctl.py
@@ -0,0 +1,251 @@
+# This file is part of Ansible
+# -*- coding: utf-8 -*-
+#
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Make coding more python3-ish
+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 ansible.module_utils.facts.sysctl import get_sysctl
+
+
+# `sysctl hw` on an openbsd machine
+OPENBSD_SYSCTL_HW = """
+hw.machine=amd64
+hw.model=AMD EPYC Processor (with IBPB)
+hw.ncpu=1
+hw.byteorder=1234
+hw.pagesize=4096
+hw.disknames=cd0:,sd0:9e1bd96cb20ab429,fd0:
+hw.diskcount=3
+hw.sensors.viomb0.raw0=0 (desired)
+hw.sensors.viomb0.raw1=0 (current)
+hw.cpuspeed=3394
+hw.vendor=QEMU
+hw.product=Standard PC (i440FX + PIIX, 1996)
+hw.version=pc-i440fx-5.1
+hw.uuid=5833415a-eefc-964f-a306-fa434d44d117
+hw.physmem=1056804864
+hw.usermem=1056792576
+hw.ncpufound=1
+hw.allowpowerdown=1
+hw.smt=0
+hw.ncpuonline=1
+"""
+
+# partial output of `sysctl kern` on an openbsd machine
+# for testing multiline parsing
+OPENBSD_SYSCTL_KERN_PARTIAL = """
+kern.ostype=OpenBSD
+kern.osrelease=6.7
+kern.osrevision=202005
+kern.version=OpenBSD 6.7 (GENERIC) #179: Thu May 7 11:02:37 MDT 2020
+ deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC
+
+kern.maxvnodes=12447
+kern.maxproc=1310
+kern.maxfiles=7030
+kern.argmax=524288
+kern.securelevel=1
+kern.hostname=openbsd67.vm.home.elrod.me
+kern.hostid=0
+kern.clockrate=tick = 10000, tickadj = 40, hz = 100, profhz = 100, stathz = 100
+kern.posix1version=200809
+"""
+
+# partial output of `sysctl vm` on Linux. The output has tabs in it and Linux
+# sysctl has spaces around the =
+LINUX_SYSCTL_VM_PARTIAL = """
+vm.dirty_background_ratio = 10
+vm.dirty_bytes = 0
+vm.dirty_expire_centisecs = 3000
+vm.dirty_ratio = 20
+vm.dirty_writeback_centisecs = 500
+vm.dirtytime_expire_seconds = 43200
+vm.extfrag_threshold = 500
+vm.hugetlb_shm_group = 0
+vm.laptop_mode = 0
+vm.legacy_va_layout = 0
+vm.lowmem_reserve_ratio = 256 256 32 0
+vm.max_map_count = 65530
+vm.min_free_kbytes = 22914
+vm.min_slab_ratio = 5
+"""
+
+# partial output of `sysctl vm` on macOS. The output is colon-separated.
+MACOS_SYSCTL_VM_PARTIAL = """
+vm.loadavg: { 1.28 1.18 1.13 }
+vm.swapusage: total = 2048.00M used = 1017.50M free = 1030.50M (encrypted)
+vm.cs_force_kill: 0
+vm.cs_force_hard: 0
+vm.cs_debug: 0
+vm.cs_debug_fail_on_unsigned_code: 0
+vm.cs_debug_unsigned_exec_failures: 0
+vm.cs_debug_unsigned_mmap_failures: 0
+vm.cs_all_vnodes: 0
+vm.cs_system_enforcement: 1
+vm.cs_process_enforcement: 0
+vm.cs_enforcement_panic: 0
+vm.cs_library_validation: 0
+vm.global_user_wire_limit: 3006477107
+"""
+
+# Invalid/bad output
+BAD_SYSCTL = """
+this.output.is.invalid
+it.has.no.equals.sign.or.colon
+so.it.should.fail.to.parse
+"""
+
+# Mixed good/bad output
+GOOD_BAD_SYSCTL = """
+bad.output.here
+hw.smt=0
+and.bad.output.here
+"""
+
+
+class TestSysctlParsingInFacts(unittest.TestCase):
+
+ def test_get_sysctl_missing_binary(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/usr/sbin/sysctl'
+ module.run_command.side_effect = ValueError
+ self.assertRaises(ValueError, get_sysctl, module, ['vm'])
+
+ def test_get_sysctl_nonzero_rc(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/usr/sbin/sysctl'
+ module.run_command.return_value = (1, '', '')
+ sysctl = get_sysctl(module, ['hw'])
+ self.assertEqual(sysctl, {})
+
+ def test_get_sysctl_command_error(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/usr/sbin/sysctl'
+ for err in (IOError, OSError):
+ module.reset_mock()
+ module.run_command.side_effect = err('foo')
+ sysctl = get_sysctl(module, ['hw'])
+ module.warn.assert_called_once_with('Unable to read sysctl: foo')
+ self.assertEqual(sysctl, {})
+
+ def test_get_sysctl_all_invalid_output(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/sbin/sysctl'
+ module.run_command.return_value = (0, BAD_SYSCTL, '')
+ sysctl = get_sysctl(module, ['hw'])
+ module.run_command.assert_called_once_with(['/sbin/sysctl', 'hw'])
+ lines = [l for l in BAD_SYSCTL.splitlines() if l]
+ for call in module.warn.call_args_list:
+ self.assertIn('Unable to split sysctl line', call[0][0])
+ self.assertEqual(module.warn.call_count, len(lines))
+ self.assertEqual(sysctl, {})
+
+ def test_get_sysctl_mixed_invalid_output(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/sbin/sysctl'
+ module.run_command.return_value = (0, GOOD_BAD_SYSCTL, '')
+ sysctl = get_sysctl(module, ['hw'])
+ module.run_command.assert_called_once_with(['/sbin/sysctl', 'hw'])
+ bad_lines = ['bad.output.here', 'and.bad.output.here']
+ for call in module.warn.call_args_list:
+ self.assertIn('Unable to split sysctl line', call[0][0])
+ self.assertEqual(module.warn.call_count, 2)
+ self.assertEqual(sysctl, {'hw.smt': '0'})
+
+ def test_get_sysctl_openbsd_hw(self):
+ expected_lines = [l for l in OPENBSD_SYSCTL_HW.splitlines() if l]
+ module = MagicMock()
+ module.get_bin_path.return_value = '/sbin/sysctl'
+ module.run_command.return_value = (0, OPENBSD_SYSCTL_HW, '')
+ sysctl = get_sysctl(module, ['hw'])
+ module.run_command.assert_called_once_with(['/sbin/sysctl', 'hw'])
+ self.assertEqual(len(sysctl), len(expected_lines))
+ self.assertEqual(sysctl['hw.machine'], 'amd64') # first line
+ self.assertEqual(sysctl['hw.smt'], '0') # random line
+ self.assertEqual(sysctl['hw.ncpuonline'], '1') # last line
+ # weird chars in value
+ self.assertEqual(
+ sysctl['hw.disknames'],
+ 'cd0:,sd0:9e1bd96cb20ab429,fd0:')
+ # more symbols/spaces in value
+ self.assertEqual(
+ sysctl['hw.product'],
+ 'Standard PC (i440FX + PIIX, 1996)')
+
+ def test_get_sysctl_openbsd_kern(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/sbin/sysctl'
+ module.run_command.return_value = (0, OPENBSD_SYSCTL_KERN_PARTIAL, '')
+ sysctl = get_sysctl(module, ['kern'])
+ module.run_command.assert_called_once_with(['/sbin/sysctl', 'kern'])
+ self.assertEqual(
+ len(sysctl),
+ len(
+ [l for l
+ in OPENBSD_SYSCTL_KERN_PARTIAL.splitlines()
+ if l.startswith('kern')]))
+ self.assertEqual(sysctl['kern.ostype'], 'OpenBSD') # first line
+ self.assertEqual(sysctl['kern.maxproc'], '1310') # random line
+ self.assertEqual(sysctl['kern.posix1version'], '200809') # last line
+ # multiline
+ self.assertEqual(
+ sysctl['kern.version'],
+ 'OpenBSD 6.7 (GENERIC) #179: Thu May 7 11:02:37 MDT 2020\n '
+ 'deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC')
+ # more symbols/spaces in value
+ self.assertEqual(
+ sysctl['kern.clockrate'],
+ 'tick = 10000, tickadj = 40, hz = 100, profhz = 100, stathz = 100')
+
+ def test_get_sysctl_linux_vm(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/usr/sbin/sysctl'
+ module.run_command.return_value = (0, LINUX_SYSCTL_VM_PARTIAL, '')
+ sysctl = get_sysctl(module, ['vm'])
+ module.run_command.assert_called_once_with(['/usr/sbin/sysctl', 'vm'])
+ self.assertEqual(
+ len(sysctl),
+ len([l for l in LINUX_SYSCTL_VM_PARTIAL.splitlines() if l]))
+ self.assertEqual(sysctl['vm.dirty_background_ratio'], '10')
+ self.assertEqual(sysctl['vm.laptop_mode'], '0')
+ self.assertEqual(sysctl['vm.min_slab_ratio'], '5')
+ # tabs
+ self.assertEqual(sysctl['vm.lowmem_reserve_ratio'], '256\t256\t32\t0')
+
+ def test_get_sysctl_macos_vm(self):
+ module = MagicMock()
+ module.get_bin_path.return_value = '/usr/sbin/sysctl'
+ module.run_command.return_value = (0, MACOS_SYSCTL_VM_PARTIAL, '')
+ sysctl = get_sysctl(module, ['vm'])
+ module.run_command.assert_called_once_with(['/usr/sbin/sysctl', 'vm'])
+ self.assertEqual(
+ len(sysctl),
+ len([l for l in MACOS_SYSCTL_VM_PARTIAL.splitlines() if l]))
+ self.assertEqual(sysctl['vm.loadavg'], '{ 1.28 1.18 1.13 }')
+ self.assertEqual(
+ sysctl['vm.swapusage'],
+ 'total = 2048.00M used = 1017.50M free = 1030.50M (encrypted)')
diff --git a/test/units/module_utils/facts/test_timeout.py b/test/units/module_utils/facts/test_timeout.py
new file mode 100644
index 0000000..2adbc4a
--- /dev/null
+++ b/test/units/module_utils/facts/test_timeout.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# (c) 2017, Toshio Kuratomi <tkuratomi@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+import time
+
+import pytest
+
+from ansible.module_utils.facts import timeout
+
+
+@pytest.fixture
+def set_gather_timeout_higher():
+ default_timeout = timeout.GATHER_TIMEOUT
+ timeout.GATHER_TIMEOUT = 5
+ yield
+ timeout.GATHER_TIMEOUT = default_timeout
+
+
+@pytest.fixture
+def set_gather_timeout_lower():
+ default_timeout = timeout.GATHER_TIMEOUT
+ timeout.GATHER_TIMEOUT = 2
+ yield
+ timeout.GATHER_TIMEOUT = default_timeout
+
+
+@timeout.timeout
+def sleep_amount_implicit(amount):
+ # implicit refers to the lack of argument to the decorator
+ time.sleep(amount)
+ return 'Succeeded after {0} sec'.format(amount)
+
+
+@timeout.timeout(timeout.DEFAULT_GATHER_TIMEOUT + 5)
+def sleep_amount_explicit_higher(amount):
+ # explicit refers to the argument to the decorator
+ time.sleep(amount)
+ return 'Succeeded after {0} sec'.format(amount)
+
+
+@timeout.timeout(2)
+def sleep_amount_explicit_lower(amount):
+ # explicit refers to the argument to the decorator
+ time.sleep(amount)
+ return 'Succeeded after {0} sec'.format(amount)
+
+
+#
+# Tests for how the timeout decorator is specified
+#
+
+def test_defaults_still_within_bounds():
+ # If the default changes outside of these bounds, some of the tests will
+ # no longer test the right thing. Need to review and update the timeouts
+ # in the other tests if this fails
+ assert timeout.DEFAULT_GATHER_TIMEOUT >= 4
+
+
+def test_implicit_file_default_succeeds():
+ # amount checked must be less than DEFAULT_GATHER_TIMEOUT
+ assert sleep_amount_implicit(1) == 'Succeeded after 1 sec'
+
+
+def test_implicit_file_default_timesout(monkeypatch):
+ monkeypatch.setattr(timeout, 'DEFAULT_GATHER_TIMEOUT', 1)
+ # sleep_time is greater than the default
+ sleep_time = timeout.DEFAULT_GATHER_TIMEOUT + 1
+ with pytest.raises(timeout.TimeoutError):
+ assert sleep_amount_implicit(sleep_time) == '(Not expected to succeed)'
+
+
+def test_implicit_file_overridden_succeeds(set_gather_timeout_higher):
+ # Set sleep_time greater than the default timeout and less than our new timeout
+ sleep_time = 3
+ assert sleep_amount_implicit(sleep_time) == 'Succeeded after {0} sec'.format(sleep_time)
+
+
+def test_implicit_file_overridden_timesout(set_gather_timeout_lower):
+ # Set sleep_time greater than our new timeout but less than the default
+ sleep_time = 3
+ with pytest.raises(timeout.TimeoutError):
+ assert sleep_amount_implicit(sleep_time) == '(Not expected to Succeed)'
+
+
+def test_explicit_succeeds(monkeypatch):
+ monkeypatch.setattr(timeout, 'DEFAULT_GATHER_TIMEOUT', 1)
+ # Set sleep_time greater than the default timeout and less than our new timeout
+ sleep_time = 2
+ assert sleep_amount_explicit_higher(sleep_time) == 'Succeeded after {0} sec'.format(sleep_time)
+
+
+def test_explicit_timeout():
+ # Set sleep_time greater than our new timeout but less than the default
+ sleep_time = 3
+ with pytest.raises(timeout.TimeoutError):
+ assert sleep_amount_explicit_lower(sleep_time) == '(Not expected to succeed)'
+
+
+#
+# Test that exception handling works
+#
+
+@timeout.timeout(1)
+def function_times_out():
+ time.sleep(2)
+
+
+# This is just about the same test as function_times_out but uses a separate process which is where
+# we normally have our timeouts. It's more of an integration test than a unit test.
+@timeout.timeout(1)
+def function_times_out_in_run_command(am):
+ am.run_command([sys.executable, '-c', 'import time ; time.sleep(2)'])
+
+
+@timeout.timeout(1)
+def function_other_timeout():
+ raise TimeoutError('Vanilla Timeout')
+
+
+@timeout.timeout(1)
+def function_raises():
+ 1 / 0
+
+
+@timeout.timeout(1)
+def function_catches_all_exceptions():
+ try:
+ time.sleep(10)
+ except BaseException:
+ raise RuntimeError('We should not have gotten here')
+
+
+def test_timeout_raises_timeout():
+ with pytest.raises(timeout.TimeoutError):
+ assert function_times_out() == '(Not expected to succeed)'
+
+
+@pytest.mark.parametrize('stdin', ({},), indirect=['stdin'])
+def test_timeout_raises_timeout_integration_test(am):
+ with pytest.raises(timeout.TimeoutError):
+ assert function_times_out_in_run_command(am) == '(Not expected to succeed)'
+
+
+def test_timeout_raises_other_exception():
+ with pytest.raises(ZeroDivisionError):
+ assert function_raises() == '(Not expected to succeed)'
+
+
+def test_exception_not_caught_by_called_code():
+ with pytest.raises(timeout.TimeoutError):
+ assert function_catches_all_exceptions() == '(Not expected to succeed)'
diff --git a/test/units/module_utils/facts/test_utils.py b/test/units/module_utils/facts/test_utils.py
new file mode 100644
index 0000000..28cb5d3
--- /dev/null
+++ b/test/units/module_utils/facts/test_utils.py
@@ -0,0 +1,39 @@
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat import unittest
+from units.compat.mock import patch
+
+from ansible.module_utils.facts import utils
+
+
+class TestGetMountSize(unittest.TestCase):
+ def test(self):
+ mount_info = utils.get_mount_size('/dev/null/not/a/real/mountpoint')
+ self.assertIsInstance(mount_info, dict)
+
+ def test_proc(self):
+ mount_info = utils.get_mount_size('/proc')
+ self.assertIsInstance(mount_info, dict)
+
+ @patch('ansible.module_utils.facts.utils.os.statvfs', side_effect=OSError('intentionally induced os error'))
+ def test_oserror_on_statvfs(self, mock_statvfs):
+ mount_info = utils.get_mount_size('/dev/null/doesnt/matter')
+ self.assertIsInstance(mount_info, dict)
+ self.assertDictEqual(mount_info, {})
diff --git a/test/units/module_utils/facts/virtual/__init__.py b/test/units/module_utils/facts/virtual/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/facts/virtual/__init__.py
diff --git a/test/units/module_utils/facts/virtual/test_linux.py b/test/units/module_utils/facts/virtual/test_linux.py
new file mode 100644
index 0000000..7c13299
--- /dev/null
+++ b/test/units/module_utils/facts/virtual/test_linux.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Copyright (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
+
+from ansible.module_utils.facts.virtual import linux
+
+
+def mock_os_path_is_file_docker(filename):
+ if filename in ('/.dockerenv', '/.dockerinit'):
+ return True
+ return False
+
+
+def test_get_virtual_facts_docker(mocker):
+ mocker.patch('os.path.exists', mock_os_path_is_file_docker)
+
+ module = mocker.Mock()
+ module.run_command.return_value = (0, '', '')
+ inst = linux.LinuxVirtual(module)
+ facts = inst.get_virtual_facts()
+
+ expected = {
+ 'virtualization_role': 'guest',
+ 'virtualization_tech_host': set(),
+ 'virtualization_type': 'docker',
+ 'virtualization_tech_guest': set(['docker', 'container']),
+ }
+
+ assert facts == expected
+
+
+def test_get_virtual_facts_bhyve(mocker):
+ mocker.patch('os.path.exists', return_value=False)
+ mocker.patch('ansible.module_utils.facts.virtual.linux.get_file_content', return_value='')
+ mocker.patch('ansible.module_utils.facts.virtual.linux.get_file_lines', return_value=[])
+
+ module = mocker.Mock()
+ module.run_command.return_value = (0, 'BHYVE\n', '')
+ inst = linux.LinuxVirtual(module)
+
+ facts = inst.get_virtual_facts()
+ expected = {
+ 'virtualization_role': 'guest',
+ 'virtualization_tech_host': set(),
+ 'virtualization_type': 'bhyve',
+ 'virtualization_tech_guest': set(['bhyve']),
+ }
+
+ assert facts == expected
diff --git a/test/units/module_utils/json_utils/__init__.py b/test/units/module_utils/json_utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/json_utils/__init__.py
diff --git a/test/units/module_utils/json_utils/test_filter_non_json_lines.py b/test/units/module_utils/json_utils/test_filter_non_json_lines.py
new file mode 100644
index 0000000..b5b9499
--- /dev/null
+++ b/test/units/module_utils/json_utils/test_filter_non_json_lines.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# (c) 2016, Matt Davis <mdavis@ansible.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from units.compat import unittest
+from ansible.module_utils.json_utils import _filter_non_json_lines
+
+
+class TestAnsibleModuleExitJson(unittest.TestCase):
+ single_line_json_dict = u"""{"key": "value", "olá": "mundo"}"""
+ single_line_json_array = u"""["a","b","c"]"""
+ multi_line_json_dict = u"""{
+"key":"value"
+}"""
+ multi_line_json_array = u"""[
+"a",
+"b",
+"c"]"""
+
+ all_inputs = [
+ single_line_json_dict,
+ single_line_json_array,
+ multi_line_json_dict,
+ multi_line_json_array
+ ]
+
+ junk = [u"single line of junk", u"line 1/2 of junk\nline 2/2 of junk"]
+
+ unparsable_cases = (
+ u'No json here',
+ u'"olá": "mundo"',
+ u'{"No json": "ending"',
+ u'{"wrong": "ending"]',
+ u'["wrong": "ending"}',
+ )
+
+ def test_just_json(self):
+ for i in self.all_inputs:
+ filtered, warnings = _filter_non_json_lines(i)
+ self.assertEqual(filtered, i)
+ self.assertEqual(warnings, [])
+
+ def test_leading_junk(self):
+ for i in self.all_inputs:
+ for j in self.junk:
+ filtered, warnings = _filter_non_json_lines(j + "\n" + i)
+ self.assertEqual(filtered, i)
+ self.assertEqual(warnings, [])
+
+ def test_trailing_junk(self):
+ for i in self.all_inputs:
+ for j in self.junk:
+ filtered, warnings = _filter_non_json_lines(i + "\n" + j)
+ self.assertEqual(filtered, i)
+ self.assertEqual(warnings, [u"Module invocation had junk after the JSON data: %s" % j.strip()])
+
+ def test_leading_and_trailing_junk(self):
+ for i in self.all_inputs:
+ for j in self.junk:
+ filtered, warnings = _filter_non_json_lines("\n".join([j, i, j]))
+ self.assertEqual(filtered, i)
+ self.assertEqual(warnings, [u"Module invocation had junk after the JSON data: %s" % j.strip()])
+
+ def test_unparsable_filter_non_json_lines(self):
+ for i in self.unparsable_cases:
+ self.assertRaises(
+ ValueError,
+ _filter_non_json_lines,
+ data=i
+ )
diff --git a/test/units/module_utils/parsing/test_convert_bool.py b/test/units/module_utils/parsing/test_convert_bool.py
new file mode 100644
index 0000000..2c5f812
--- /dev/null
+++ b/test/units/module_utils/parsing/test_convert_bool.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2017 Ansible Project
+# License: GNU General Public License v3 or later (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
+
+import pytest
+
+from ansible.module_utils.parsing.convert_bool import boolean
+
+
+class TestBoolean:
+ def test_bools(self):
+ assert boolean(True) is True
+ assert boolean(False) is False
+
+ def test_none(self):
+ with pytest.raises(TypeError):
+ assert boolean(None, strict=True) is False
+ assert boolean(None, strict=False) is False
+
+ def test_numbers(self):
+ assert boolean(1) is True
+ assert boolean(0) is False
+ assert boolean(0.0) is False
+
+# Current boolean() doesn't consider these to be true values
+# def test_other_numbers(self):
+# assert boolean(2) is True
+# assert boolean(-1) is True
+# assert boolean(0.1) is True
+
+ def test_strings(self):
+ assert boolean("true") is True
+ assert boolean("TRUE") is True
+ assert boolean("t") is True
+ assert boolean("yes") is True
+ assert boolean("y") is True
+ assert boolean("on") is True
+
+ def test_junk_values_nonstrict(self):
+ assert boolean("flibbity", strict=False) is False
+ assert boolean(42, strict=False) is False
+ assert boolean(42.0, strict=False) is False
+ assert boolean(object(), strict=False) is False
+
+ def test_junk_values_strict(self):
+ with pytest.raises(TypeError):
+ assert boolean("flibbity", strict=True) is False
+
+ with pytest.raises(TypeError):
+ assert boolean(42, strict=True) is False
+
+ with pytest.raises(TypeError):
+ assert boolean(42.0, strict=True) is False
+
+ with pytest.raises(TypeError):
+ assert boolean(object(), strict=True) is False
diff --git a/test/units/module_utils/test_api.py b/test/units/module_utils/test_api.py
new file mode 100644
index 0000000..f7e768a
--- /dev/null
+++ b/test/units/module_utils/test_api.py
@@ -0,0 +1,121 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2020, Abhijeet Kasurde <akasurde@redhat.com>
+# Copyright: (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
+
+
+from ansible.module_utils.api import rate_limit, retry, retry_with_delays_and_condition
+
+import pytest
+
+
+class CustomException(Exception):
+ pass
+
+
+class CustomBaseException(BaseException):
+ pass
+
+
+class TestRateLimit:
+
+ def test_ratelimit(self):
+ @rate_limit(rate=1, rate_limit=1)
+ def login_database():
+ return "success"
+ r = login_database()
+
+ assert r == 'success'
+
+
+class TestRetry:
+
+ def test_no_retry_required(self):
+ @retry(retries=4, retry_pause=2)
+ def login_database():
+ login_database.counter += 1
+ return 'success'
+
+ login_database.counter = 0
+ r = login_database()
+
+ assert r == 'success'
+ assert login_database.counter == 1
+
+ def test_catch_exception(self):
+
+ @retry(retries=1)
+ def login_database():
+ return 'success'
+
+ with pytest.raises(Exception, match="Retry"):
+ login_database()
+
+ def test_no_retries(self):
+
+ @retry()
+ def login_database():
+ assert False, "Should not execute"
+
+ login_database()
+
+
+class TestRetryWithDelaysAndCondition:
+
+ def test_empty_retry_iterator(self):
+ @retry_with_delays_and_condition(backoff_iterator=[])
+ def login_database():
+ login_database.counter += 1
+
+ login_database.counter = 0
+ r = login_database()
+ assert login_database.counter == 1
+
+ def test_no_retry_exception(self):
+ @retry_with_delays_and_condition(
+ backoff_iterator=[1],
+ should_retry_error=lambda x: False,
+ )
+ def login_database():
+ login_database.counter += 1
+ if login_database.counter == 1:
+ raise CustomException("Error")
+
+ login_database.counter = 0
+ with pytest.raises(CustomException, match="Error"):
+ login_database()
+ assert login_database.counter == 1
+
+ def test_no_retry_baseexception(self):
+ @retry_with_delays_and_condition(
+ backoff_iterator=[1],
+ should_retry_error=lambda x: True, # Retry all exceptions inheriting from Exception
+ )
+ def login_database():
+ login_database.counter += 1
+ if login_database.counter == 1:
+ # Raise an exception inheriting from BaseException
+ raise CustomBaseException("Error")
+
+ login_database.counter = 0
+ with pytest.raises(CustomBaseException, match="Error"):
+ login_database()
+ assert login_database.counter == 1
+
+ def test_retry_exception(self):
+ @retry_with_delays_and_condition(
+ backoff_iterator=[1],
+ should_retry_error=lambda x: isinstance(x, CustomException),
+ )
+ def login_database():
+ login_database.counter += 1
+ if login_database.counter == 1:
+ raise CustomException("Retry")
+ return 'success'
+
+ login_database.counter = 0
+ assert login_database() == 'success'
+ assert login_database.counter == 2
diff --git a/test/units/module_utils/test_connection.py b/test/units/module_utils/test_connection.py
new file mode 100644
index 0000000..bd0285b
--- /dev/null
+++ b/test/units/module_utils/test_connection.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Matt Martz <matt@sivel.net>
+# 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
+
+from ansible.module_utils import connection
+
+import pytest
+
+
+def test_set_options_credential_exposure():
+ def send(data):
+ return '{'
+
+ c = connection.Connection(connection.__file__)
+ c.send = send
+ with pytest.raises(connection.ConnectionError) as excinfo:
+ c._exec_jsonrpc('set_options', become_pass='password')
+
+ assert 'password' not in str(excinfo.value)
diff --git a/test/units/module_utils/test_distro.py b/test/units/module_utils/test_distro.py
new file mode 100644
index 0000000..bec127a
--- /dev/null
+++ b/test/units/module_utils/test_distro.py
@@ -0,0 +1,39 @@
+
+# (c) 2018 Adrian Likins <alikins@redhat.com>
+# Copyright (c) 2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+# or
+# Apache License v2.0 (see http://www.apache.org/licenses/LICENSE-2.0)
+#
+# Dual licensed so any test cases could potentially be included by the upstream project
+# that module_utils/distro.py is from (https://github.com/nir0s/distro)
+
+
+# Note that nir0s/distro has many more tests in it's test suite. The tests here are
+# primarily for testing the vendoring.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from ansible.module_utils import distro
+from ansible.module_utils.six import string_types
+
+
+# Generic test case with minimal assertions about specific returned values.
+class TestDistro():
+ # should run on any platform without errors, even if non-linux without any
+ # useful info to return
+ def test_info(self):
+ info = distro.info()
+ assert isinstance(info, dict), \
+ 'distro.info() returned %s (%s) which is not a dist' % (info, type(info))
+
+ def test_id(self):
+ id = distro.id()
+ assert isinstance(id, string_types), 'distro.id() returned %s (%s) which is not a string' % (id, type(id))
+
+ def test_opensuse_leap_id(self):
+ name = distro.name()
+ if name == 'openSUSE Leap':
+ id = distro.id()
+ assert id == 'opensuse', "OpenSUSE Leap did not return 'opensuse' as id"
diff --git a/test/units/module_utils/urls/__init__.py b/test/units/module_utils/urls/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/units/module_utils/urls/__init__.py
diff --git a/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem
new file mode 100644
index 0000000..fcc6f7a
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha256.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBjzCCATWgAwIBAgIQeNQTxkMgq4BF9tKogIGXUTAKBggqhkjOPQQ
+DAjAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MDMxN1
+oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM
+BMGByqGSM49AgEGCCqGSM49AwEHA0IABDAfXTLOaC3ElgErlgk2tBlM
+wf9XmGlGBw4vBtMJap1hAqbsdxFm6rhK3QU8PFFpv8Z/AtRG7ba3UwQ
+prkssClejZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg
+EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB
+gNVHQ4EFgQUnFDE8824TYAiBeX4fghEEg33UgYwCgYIKoZIzj0EAwID
+SAAwRQIhAK3rXA4/0i6nm/U7bi6y618Ci2Is8++M3tYIXnEsA7zSAiA
+w2s6bJoI+D7Xaey0Hp0gkks9z55y976keIEI+n3qkzw==
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem
new file mode 100644
index 0000000..1b45be5
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/ecdsa_sha512.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIIBjjCCATWgAwIBAgIQHVj2AGEwd6pOOSbcf0skQDAKBggqhkjOPQQ
+DBDAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA3NTUzOV
+oXDTE4MDUzMDA4MTUzOVowFTETMBEGA1UEAwwKU0VSVkVSMjAxNjBZM
+BMGByqGSM49AgEGCCqGSM49AwEHA0IABL8d9S++MFpfzeH8B3vG/PjA
+AWg8tGJVgsMw9nR+OfC9ltbTUwhB+yPk3JPcfW/bqsyeUgq4//LhaSp
+lOWFNaNqjZzBlMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBg
+EFBQcDAgYIKwYBBQUHAwEwFQYDVR0RBA4wDIIKU0VSVkVSMjAxNjAdB
+gNVHQ4EFgQUKUkCgLlxoeai0EtQrZth1/BSc5kwCgYIKoZIzj0EAwQD
+RwAwRAIgRrV7CLpDG7KueyFA3ZDced9dPOcv2Eydx/hgrfxYEcYCIBQ
+D35JvzmqU05kSFV5eTvkhkaDObd7V55vokhm31+Li
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem
new file mode 100644
index 0000000..fcbe01f
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha256.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJn4wPgYJKoZIhvcNAQEK
+MDGgDTALBglghkgBZQMEAgGhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIBogQC
+AgDeMBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyNloXDTIxMDkw
+NDE4NTMyNlowGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANN++3POgcKcPILMdHWIEPiVENtKoJQ8iqKKeL+/
+j5oUULVuIn15H/RYMNFmStRIvj0dIL1JAq4W411wG2Tf/6niU2YSKPOAOtrVREef
+gNvMZ06TYlC8UcGCLv4dBkU3q/FELV66lX9x6LcVwf2f8VWfDg4VNuwyg/eQUIgc
+/yd5VV+1VXTf39QufVV+/hOtPptu+fBKOIuiuKm6FIqroqLri0Ysp6tWrSd7P6V4
+6zT2yd17981vaEv5Zek2t39PoLYzJb3rvqQmumgFBIUQ1eMPLFCXX8YYYC/9ByK3
+mdQaEnkD2eIOARLnojr2A228EgPpdM8phQkDzeWeYnhLiysCAwEAAaNJMEcwCQYD
+VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R
+BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC
+AaEaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgGiBAICAN4DggEBAA66cbtiysjq
+sCaDiYyRWCS9af2DGxJ6NAyku2aX+xgmRQzUzFAN5TihcPau+zzpk2zQKHDVMhNx
+ouhTkIe6mq9KegpUuesWwkJ5KEeuBT949tIru2wAtlSVDvDcau5T9pRI/RLlnsWg
+0sWaUAh/ujL+VKk6AanC4MRV69llwJcAVxlS/tYjwC74Dr8tMT1TQcVDvywB85e9
+mA3uz8mGKfiMk2TKD6+6on0UwBMB8NbKSB5bcgp+CJ2ceeblmCOgOcOcV5PCGoFj
+fgAppr7HjfNPYaIV5l59LfKo2Bj9kXPMqA6/D4gJ3hwoJdY/NOtuNyk8cxWMnWUe
++E2Mm6ZnB3Y=
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem
new file mode 100644
index 0000000..add3109
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa-pss_sha512.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDZDCCAhugAwIBAgIUbo9YpgkeniC5jRmOgFYX3mcVJoEwPgYJKoZIhvcNAQEK
+MDGgDTALBglghkgBZQMEAgOhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIDogQC
+AgC+MBMxETAPBgNVBAMMCE9NSSBSb290MB4XDTIwMDkwNDE4NTMyN1oXDTIxMDkw
+NDE4NTMyN1owGDEWMBQGA1UEAwwNREMwMS5vbWkudGVzdDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANZMAyRDBn54RfveeVtikepqsyKVKooAc471snl5
+mEEeo6ZvlOrK1VGGmo/VlF4R9iW6f5iqxG792KXk+lDtx8sbapZWk/aQa+6I9wml
+p17ocW4Otl7XyQ74UTQlxmrped0rgOk+I2Wu3IC7k64gmf/ZbL9mYN/+v8TlYYyO
+l8DQbO61XWOJpWt7yf18OxZtPcHH0dkoTEyIxIQcv6FDFNvPjmJzubpDgsfnly7R
+C0Rc2yPU702vmAfF0SGQbd6KoXUqlfy26C85vU0Fqom1Qo22ehKrfU50vZrXdaJ2
+gX14pm2kuubMjHtX/+bhNyWTxq4anCOl9/aoObZCM1D3+Y8CAwEAAaNJMEcwCQYD
+VR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGAYDVR0R
+BBEwD4INREMwMS5vbWkudGVzdDA+BgkqhkiG9w0BAQowMaANMAsGCWCGSAFlAwQC
+A6EaMBgGCSqGSIb3DQEBCDALBglghkgBZQMEAgOiBAICAL4DggEBAHgTDTn8onIi
+XFLZ3sWJ5xCBvXypqC37dKALvXxNSo75SQZpOioG4GSdW3zjJWCiudGs7BselkFv
+sHK7+5sLKTl1RvxeUoyTxnPZZmVlD3yLq8JBPxu5NScvcRwAcgm3stkq0irRnh7M
+4Clw6oSKCKI7Lc3gnbvR3QLSYHeZpUcQgVCad6O/Hi+vxFMJT8PVigG0YUoTW010
+pDpi5uh18RxCqRJnnEC7aDrVarxD9aAvqp1wqwWShfP4FZ9m57DH81RTGD2ZzGgP
+MsZU5JHVYKkO7IKKIBKuLu+O+X2aZZ4OMlMNBt2DUIJGzEBYV41+3TST9bBPD8xt
+AAIFCBcgUYY=
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem
new file mode 100644
index 0000000..6671b73
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_md5.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQJzshhViMG5hLHIJHxa+TcTANBgkqhkiG9w0
+BAQQFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
+MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN9N5GAzI7uq
+AVlI6vUqhY5+EZWCWWGRwR3FT2DEXE5++AiJxXO0i0ZfAkLu7UggtBe
+QwVNkaPD27EYzVUhy1iDo37BrFcLNpfjsjj8wVjaSmQmqvLvrvEh/BT
+C5SBgDrk2+hiMh9PrpJoB3QAMDinz5aW0rEXMKitPBBiADrczyYrliF
+AlEU6pTlKEKDUAeP7dKOBlDbCYvBxKnR3ddVH74I5T2SmNBq5gzkbKP
+nlCXdHLZSh74USu93rKDZQF8YzdTO5dcBreJDJsntyj1o49w9WCt6M7
++pg6vKvE+tRbpCm7kXq5B9PDi42Nb6//MzNaMYf9V7v5MHapvVSv3+y
+sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBTh4L2Clr9ber6yfY3JFS3wiECL4DANBgkqhkiG9w0BAQQ
+FAAOCAQEA0JK/SL7SP9/nvqWp52vnsxVefTFehThle5DLzagmms/9gu
+oSE2I9XkQIttFMprPosaIZWt7WP42uGcZmoZOzU8kFFYJMfg9Ovyca+
+gnG28jDUMF1E74KrC7uynJiQJ4vPy8ne7F3XJ592LsNJmK577l42gAW
+u08p3TvEJFNHy2dBk/IwZp0HIPr9+JcPf7v0uL6lK930xHJHP56XLzN
+YG8vCMpJFR7wVZp3rXkJQUy3GxyHPJPjS8S43I9j+PoyioWIMEotq2+
+q0IpXU/KeNFkdGV6VPCmzhykijExOMwO6doUzIUM8orv9jYLHXYC+i6
+IFKSb6runxF1MAik+GCSA==
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem
new file mode 100644
index 0000000..2ed2b45
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0
+BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
+MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt
+D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw
+K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ
+j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1
+zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu
+oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46
+efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp
+ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0
+FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj
+gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM
+sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT
+nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY
+dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx
+sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0
+OI7elR0nJ0peai30eMpQQ=='
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem
new file mode 100644
index 0000000..de21a67
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha1.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQJg/Mf5sR55xApJRK+kabbTANBgkqhkiG9w0
+BAQUFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
+MxNloXDTE4MDUzMDA4MjMxNlowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPKwYikjbzL
+Lo6JtS6cyytdMMjSrggDoTnRUKauC5/izoYJd+2YVR5YqnluBJZpoFp
+hkCgFFohUOU7qUsI1SkuGnjI8RmWTrrDsSy62BrfX+AXkoPlXo6IpHz
+HaEPxjHJdUACpn8QVWTPmdAhwTwQkeUutrm3EOVnKPX4bafNYeAyj7/
+AGEplgibuXT4/ehbzGKOkRN3ds/pZuf0xc4Q2+gtXn20tQIUt7t6iwh
+nEWjIgopFL/hX/r5q5MpF6stc1XgIwJjEzqMp76w/HUQVqaYneU4qSG
+f90ANK/TQ3aDbUNtMC/ULtIfHqHIW4POuBYXaWBsqalJL2VL3YYkKTU
+sCAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBS1jgojcjPu9vqeP1uSKuiIonGwAjANBgkqhkiG9w0BAQU
+FAAOCAQEAKjHL6k5Dv/Zb7dvbYEZyx0wVhjHkCTpT3xstI3+TjfAFsu
+3zMmyFqFqzmr4pWZ/rHc3ObD4pEa24kP9hfB8nmr8oHMLebGmvkzh5h
+0GYc4dIH7Ky1yfQN51hi7/X5iN7jnnBoCJTTlgeBVYDOEBXhfXi3cLT
+u3d7nz2heyNq07gFP8iN7MfqdPZndVDYY82imLgsgar9w5d+fvnYM+k
+XWItNNCUH18M26Obp4Es/Qogo/E70uqkMHost2D+tww/7woXi36X3w/
+D2yBDyrJMJKZLmDgfpNIeCimncTOzi2IhzqJiOY/4XPsVN/Xqv0/dzG
+TDdI11kPLq4EiwxvPanCg==
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem
new file mode 100644
index 0000000..fb17018
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha256.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQWkeAtqoFg6pNWF7xC4YXhTANBgkqhkiG9w0
+BAQsFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUyNzA5MD
+I0NFoXDTE4MDUyNzA5MjI0NFowFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALIPKM5uykFy
+NmVoLyvPSXGk15ZDqjYi3AbUxVFwCkVImqhefLATit3PkTUYFtAT+TC
+AwK2E4lOu1XHM+Tmp2KIOnq2oUR8qMEvfxYThEf1MHxkctFljFssZ9N
+vASDD4lzw8r0Bhl+E5PhR22Eu1Wago5bvIldojkwG+WBxPQv3ZR546L
+MUZNaBXC0RhuGj5w83lbVz75qM98wvv1ekfZYAP7lrVyHxqCTPDomEU
+I45tQQZHCZl5nRx1fPCyyYfcfqvFlLWD4Q3PZAbnw6mi0MiWJbGYKME
+1XGicjqyn/zM9XKA1t/JzChS2bxf6rsyA9I7ibdRHUxsm1JgKry2jfW
+0CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBQabLGWg1sn7AXPwYPyfE0ER921ZDANBgkqhkiG9w0BAQs
+FAAOCAQEAnRohyl6ZmOsTWCtxOJx5A8yr//NweXKwWWmFQXRmCb4bMC
+xhD4zqLDf5P6RotGV0I/SHvqz+pAtJuwmr+iyAF6WTzo3164LCfnQEu
+psfrrfMkf3txgDwQkA0oPAw3HEwOnR+tzprw3Yg9x6UoZEhi4XqP9AX
+R49jU92KrNXJcPlz5MbkzNo5t9nr2f8q39b5HBjaiBJxzdM1hxqsbfD
+KirTYbkUgPlVOo/NDmopPPb8IX8ubj/XETZG2jixD0zahgcZ1vdr/iZ
++50WSXKN2TAKBO2fwoK+2/zIWrGRxJTARfQdF+fGKuj+AERIFNh88HW
+xSDYjHQAaFMcfdUpa9GGQ==
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem
new file mode 100644
index 0000000..c17f9ff
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha384.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQEmj1prSSQYRL2zYBEjsm5jANBgkqhkiG9w0
+BAQwFADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
+MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKsK5NvHi4xO
+081fRLMmPqKsKaHvXgPRykLA0SmKxpGJHfTAZzxojHVeVwOm87IvQj2
+JUh/yrRwSi5Oqrvqx29l2IC/qQt2xkAQsO51/EWkMQ5OSJsl1MN3NXW
+eRTKVoUuJzBs8XLmeraxQcBPyyLhq+WpMl/Q4ZDn1FrUEZfxV0POXgU
+dI3ApuQNRtJOb6iteBIoQyMlnof0RswBUnkiWCA/+/nzR0j33j47IfL
+nkmU4RtqkBlO13f6+e1GZ4lEcQVI2yZq4Zgu5VVGAFU2lQZ3aEVMTu9
+8HEqD6heyNp2on5G/K/DCrGWYCBiASjnX3wiSz0BYv8f3HhCgIyVKhJ
+8CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBQS/SI61S2UE8xwSgHxbkCTpZXo4TANBgkqhkiG9w0BAQw
+FAAOCAQEAMVV/WMXd9w4jtDfSrIsKaWKGtHtiMPpAJibXmSakBRwLOn
+5ZGXL2bWI/Ac2J2Y7bSzs1im2ifwmEqwzzqnpVKShIkZmtij0LS0SEr
+6Fw5IrK8tD6SH+lMMXUTvp4/lLQlgRCwOWxry/YhQSnuprx8IfSPvil
+kwZ0Ysim4Aa+X5ojlhHpWB53edX+lFrmR1YWValBnQ5DvnDyFyLR6II
+Ialp4vmkzI9e3/eOgSArksizAhpXpC9dxQBiHXdhredN0X+1BVzbgzV
+hQBEwgnAIPa+B68oDILaV0V8hvxrP6jFM4IrKoGS1cq0B+Ns0zkG7ZA
+2Q0W+3nVwSxIr6bd6hw7g==
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem b/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem
new file mode 100644
index 0000000..2ed2b45
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/cbt/rsa_sha512.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQUDHcKGevZohJV+TkIIYC1DANBgkqhkiG9w0
+BAQ0FADAVMRMwEQYDVQQDDApTRVJWRVIyMDE2MB4XDTE3MDUzMDA4MD
+MxN1oXDTE4MDUzMDA4MjMxN1owFTETMBEGA1UEAwwKU0VSVkVSMjAxN
+jCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKr9bo/XXvHt
+D6Qnhb1wyLg9lDQxxe/enH49LQihtVTZMwGf2010h81QrRUe/bkHTvw
+K22s2lqj3fUpGxtEbYFWLAHxv6IFnIKd+Zi1zaCPGfas9ekqCSj3vZQ
+j7lCJVGUGuuqnSDvsed6g2Pz/g6mJUa+TzjxN+8wU5oj5YVUK+aing1
+zPSA2MDCfx3+YzjxVwNoGixOz6Yx9ijT4pUsAYQAf1o9R+6W1/IpGgu
+oax714QILT9heqIowwlHzlUZc1UAYs0/JA4CbDZaw9hlJyzMqe/aE46
+efqPDOpO3vCpOSRcSyzh02WijPvEEaPejQRWg8RX93othZ615MT7dqp
+ECAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGA
+QUFBwMCBggrBgEFBQcDATAVBgNVHREEDjAMggpTRVJWRVIyMDE2MB0G
+A1UdDgQWBBTgod3R6vejt6kOASAApA19xIG6kTANBgkqhkiG9w0BAQ0
+FAAOCAQEAVfz0okK2bh3OQE8cWNbJ5PjJRSAJEqVUvYaTlS0Nqkyuaj
+gicP3hb/pF8FvaVaB6r7LqgBxyW5NNL1xwdNLt60M2zaULL6Fhm1vzM
+sSMc2ynkyN4++ODwii674YcQAnkUh+ZGIx+CTdZBWJfVM9dZb7QjgBT
+nVukeFwN2EOOBSpiQSBpcoeJEEAq9csDVRhEfcB8Wtz7TTItgOVsilY
+dQY56ON5XszjCki6UA3GwdQbBEHjWF2WERqXWrojrSSNOYDvxM5mrEx
+sG1npzUTsaIr9w8ty1beh/2aToCMREvpiPFOXnVV/ovHMU1lFQTNeQ0
+OI7elR0nJ0peai30eMpQQ=='
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/client.key b/test/units/module_utils/urls/fixtures/client.key
new file mode 100644
index 0000000..0e90d95
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/client.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTyiVxrsSyZ+Qr
+iMT6sFYCqQtkLqlIWfbpTg9B6fZc793uoMzLUGq3efiZUhhxI78dQ3gNPgs1sK3W
+heFpk1n4IL8ll1MS1uJKk2vYqzZVhjgcvQpeV9gm7bt0ndPzGj5h4fh7proPntSy
+eBvMKVoqTT7tEnapRKy3anbwRPgTt7B5jEvJkPazuIc+ooMsYOHWfvj4oVsev0N2
+SsP0o6cHcsRujFMhz/JTJ1STQxacaVuyKpXacX7Eu1MJgGt/jU/QKNREcV9LdneO
+NgqY9tNv0h+9s7DfHYXm8U3POr+bdcW6Yy4791KGCaUNtiNqT1lvu/4yd4WRkXbF
+Fm5hJUUpAgMBAAECggEBAJYOac1MSK0nEvENbJM6ERa9cwa+UM6kf176IbFP9XAP
+u6zxXWjIR3RMBSmMkyjGbQhs30hypzqZPfH61aUZ8+rsOMKHnyKAAcFZBlZzqIGc
+IXGrNwd1Mf8S/Xg4ww1BkOWFV6s0jCu5G3Z/xyI2Ql4qcOVD6bMwpzclRbQjCand
+dvqyCdMD0sRDyeOIK5hBhUY60JnWbMCu6pBU+qPoRukbRieaeDLIN1clwEqIQV78
+LLnv4n9fuGozH0JdHHfyXFytCgIJvEspZUja/5R4orADhr3ZB010RLzYvs2ndE3B
+4cF9RgxspJZeJ/P+PglViZuzj37pXy+7GAcJLR9ka4kCgYEA/l01XKwkCzMgXHW4
+UPgl1+on42BsN7T9r3S5tihOjHf4ZJWkgYzisLVX+Nc1oUI3HQfM9PDJZXMMNm7J
+ZRvERcopU26wWqr6CFPblGv8oqXHqcpeta8i3xZKoPASsTW6ssuPCEajiLZbQ1rH
+H/HP+OZIVLM/WCPgA2BckTU9JnsCgYEA1SbXllXnlwGqmjitmY1Z07rUxQ3ah/fB
+iccbbg3E4onontYXIlI5zQms3u+qBdi0ZuwaDm5Y4BetOq0a3UyxAsugqVFnzTba
+1w/sFb3fw9KeQ/il4CXkbq87nzJfDmEyqHGCCYXbijHBxnq99PkqwVpaAhHHEW0m
+vWyMUvPRY6sCgYAbtUWR0cKfYbNdvwkT8OQWcBBmSWOgcdvMmBd+y0c7L/pj4pUn
+85PiEe8CUVcrOM5OIEJoUC5wGacz6r+PfwXTYGE+EGmvhr5z18aslVLQ2OQ2D7Bf
+dDOFP6VjgKNYoHS0802iZid8RfkNDj9wsGOqRlOMvnXhAQ9u7rlGrBj8LwKBgFfo
+ph99nH8eE9N5LrfWoUZ+loQS258aInsFYB26lgnsYMEpgO8JxIb4x5BGffPdVUHh
+fDmZbxQ1D5/UhvDgUVzayI8sYMg1KHpsOa0Z2zCzK8zSvu68EgNISCm3J5cRpUft
+UHlG+K19KfMG6lMfdG+8KMUTuetI/iI/o3wOzLvzAoGAIrOh30rHt8wit7ELARyx
+wPkp2ARYXrKfX3NES4c67zSAi+3dCjxRqywqTI0gLicyMlj8zEu9YE9Ix/rl8lRZ
+nQ9LZmqv7QHzhLTUCPGgZYnemvBzo7r0eW8Oag52dbcJO6FBszfWrxskm/fX25Rb
+WPxih2vdRy814dNPW25rgdw=
+-----END PRIVATE KEY-----
diff --git a/test/units/module_utils/urls/fixtures/client.pem b/test/units/module_utils/urls/fixtures/client.pem
new file mode 100644
index 0000000..c8c7b82
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/client.pem
@@ -0,0 +1,81 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4099 (0x1003)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=North Carolina, L=Durham, O=Ansible, CN=ansible.http.tests
+ Validity
+ Not Before: Mar 21 18:22:47 2018 GMT
+ Not After : Mar 18 18:22:47 2028 GMT
+ Subject: C=US, ST=North Carolina, O=Ansible, CN=client.ansible.http.tests
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d3:ca:25:71:ae:c4:b2:67:e4:2b:88:c4:fa:b0:
+ 56:02:a9:0b:64:2e:a9:48:59:f6:e9:4e:0f:41:e9:
+ f6:5c:ef:dd:ee:a0:cc:cb:50:6a:b7:79:f8:99:52:
+ 18:71:23:bf:1d:43:78:0d:3e:0b:35:b0:ad:d6:85:
+ e1:69:93:59:f8:20:bf:25:97:53:12:d6:e2:4a:93:
+ 6b:d8:ab:36:55:86:38:1c:bd:0a:5e:57:d8:26:ed:
+ bb:74:9d:d3:f3:1a:3e:61:e1:f8:7b:a6:ba:0f:9e:
+ d4:b2:78:1b:cc:29:5a:2a:4d:3e:ed:12:76:a9:44:
+ ac:b7:6a:76:f0:44:f8:13:b7:b0:79:8c:4b:c9:90:
+ f6:b3:b8:87:3e:a2:83:2c:60:e1:d6:7e:f8:f8:a1:
+ 5b:1e:bf:43:76:4a:c3:f4:a3:a7:07:72:c4:6e:8c:
+ 53:21:cf:f2:53:27:54:93:43:16:9c:69:5b:b2:2a:
+ 95:da:71:7e:c4:bb:53:09:80:6b:7f:8d:4f:d0:28:
+ d4:44:71:5f:4b:76:77:8e:36:0a:98:f6:d3:6f:d2:
+ 1f:bd:b3:b0:df:1d:85:e6:f1:4d:cf:3a:bf:9b:75:
+ c5:ba:63:2e:3b:f7:52:86:09:a5:0d:b6:23:6a:4f:
+ 59:6f:bb:fe:32:77:85:91:91:76:c5:16:6e:61:25:
+ 45:29
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ AF:F3:E5:2A:EB:CF:C7:7E:A4:D6:49:92:F9:29:EE:6A:1B:68:AB:0F
+ X509v3 Authority Key Identifier:
+ keyid:13:2E:30:F0:04:EA:41:5F:B7:08:BD:34:31:D7:11:EA:56:A6:99:F0
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 29:62:39:25:79:58:eb:a4:b3:0c:ea:aa:1d:2b:96:7c:6e:10:
+ ce:16:07:b7:70:7f:16:da:fd:20:e6:a2:d9:b4:88:e0:f9:84:
+ 87:f8:b0:0d:77:8b:ae:27:f5:ee:e6:4f:86:a1:2d:74:07:7c:
+ c7:5d:c2:bd:e4:70:e7:42:e4:14:ee:b9:b7:63:b8:8c:6d:21:
+ 61:56:0b:96:f6:15:ba:7a:ae:80:98:ac:57:99:79:3d:7a:a9:
+ d8:26:93:30:17:53:7c:2d:02:4b:64:49:25:65:e7:69:5a:08:
+ cf:84:94:8e:6a:42:a7:d1:4f:ba:39:4b:7c:11:67:31:f7:1b:
+ 2b:cd:79:c2:28:4d:d9:88:66:d6:7f:56:4c:4b:37:d1:3d:a8:
+ d9:4a:6b:45:1d:4d:a7:12:9f:29:77:6a:55:c1:b5:1d:0e:a5:
+ b9:4f:38:16:3c:7d:85:ae:ff:23:34:c7:2c:f6:14:0f:55:ef:
+ b8:00:89:f1:b2:8a:75:15:41:81:72:d0:43:a6:86:d1:06:e6:
+ ce:81:7e:5f:33:e6:f4:19:d6:70:00:ba:48:6e:05:fd:4c:3c:
+ c3:51:1b:bd:43:1a:24:c5:79:ea:7a:f0:85:a5:40:10:85:e9:
+ 23:09:09:80:38:9d:bc:81:5e:59:8c:5a:4d:58:56:b9:71:c2:
+ 78:cd:f3:b0
+-----BEGIN CERTIFICATE-----
+MIIDuTCCAqGgAwIBAgICEAMwDQYJKoZIhvcNAQELBQAwZjELMAkGA1UEBhMCVVMx
+FzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMQ8wDQYDVQQHDAZEdXJoYW0xEDAOBgNV
+BAoMB0Fuc2libGUxGzAZBgNVBAMMEmFuc2libGUuaHR0cC50ZXN0czAeFw0xODAz
+MjExODIyNDdaFw0yODAzMTgxODIyNDdaMFwxCzAJBgNVBAYTAlVTMRcwFQYDVQQI
+DA5Ob3J0aCBDYXJvbGluYTEQMA4GA1UECgwHQW5zaWJsZTEiMCAGA1UEAwwZY2xp
+ZW50LmFuc2libGUuaHR0cC50ZXN0czCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBANPKJXGuxLJn5CuIxPqwVgKpC2QuqUhZ9ulOD0Hp9lzv3e6gzMtQard5
++JlSGHEjvx1DeA0+CzWwrdaF4WmTWfggvyWXUxLW4kqTa9irNlWGOBy9Cl5X2Cbt
+u3Sd0/MaPmHh+Humug+e1LJ4G8wpWipNPu0SdqlErLdqdvBE+BO3sHmMS8mQ9rO4
+hz6igyxg4dZ++PihWx6/Q3ZKw/SjpwdyxG6MUyHP8lMnVJNDFpxpW7IqldpxfsS7
+UwmAa3+NT9Ao1ERxX0t2d442Cpj202/SH72zsN8dhebxTc86v5t1xbpjLjv3UoYJ
+pQ22I2pPWW+7/jJ3hZGRdsUWbmElRSkCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFK/z5Srrz8d+pNZJkvkp7mobaKsPMB8GA1UdIwQYMBaAFBMuMPAE6kFftwi9
+NDHXEepWppnwMA0GCSqGSIb3DQEBCwUAA4IBAQApYjkleVjrpLMM6qodK5Z8bhDO
+Fge3cH8W2v0g5qLZtIjg+YSH+LANd4uuJ/Xu5k+GoS10B3zHXcK95HDnQuQU7rm3
+Y7iMbSFhVguW9hW6eq6AmKxXmXk9eqnYJpMwF1N8LQJLZEklZedpWgjPhJSOakKn
+0U+6OUt8EWcx9xsrzXnCKE3ZiGbWf1ZMSzfRPajZSmtFHU2nEp8pd2pVwbUdDqW5
+TzgWPH2Frv8jNMcs9hQPVe+4AInxsop1FUGBctBDpobRBubOgX5fM+b0GdZwALpI
+bgX9TDzDURu9QxokxXnqevCFpUAQhekjCQmAOJ28gV5ZjFpNWFa5ccJ4zfOw
+-----END CERTIFICATE-----
diff --git a/test/units/module_utils/urls/fixtures/client.txt b/test/units/module_utils/urls/fixtures/client.txt
new file mode 100644
index 0000000..380330f
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/client.txt
@@ -0,0 +1,3 @@
+client.pem and client.key were retrieved from httptester docker image:
+
+ansible/ansible@sha256:fa5def8c294fc50813af131c0b5737594d852abac9cbe7ba38e17bf1c8476f3f
diff --git a/test/units/module_utils/urls/fixtures/multipart.txt b/test/units/module_utils/urls/fixtures/multipart.txt
new file mode 100644
index 0000000..c80a1b8
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/multipart.txt
@@ -0,0 +1,166 @@
+--===============3996062709511591449==
+Content-Type: text/plain
+Content-Disposition: form-data; name="file1"; filename="fake_file1.txt"
+
+file_content_1
+--===============3996062709511591449==
+Content-Type: text/html
+Content-Disposition: form-data; name="file2"; filename="fake_file2.html"
+
+<html></html>
+--===============3996062709511591449==
+Content-Type: application/json
+Content-Disposition: form-data; name="file3"; filename="fake_file3.json"
+
+{"foo": "bar"}
+--===============3996062709511591449==
+Content-Transfer-Encoding: base64
+Content-Type: text/plain
+Content-Disposition: form-data; name="file4"; filename="client.pem"
+
+Q2VydGlmaWNhdGU6CiAgICBEYXRhOgogICAgICAgIFZlcnNpb246IDMgKDB4MikKICAgICAgICBT
+ZXJpYWwgTnVtYmVyOiA0MDk5ICgweDEwMDMpCiAgICBTaWduYXR1cmUgQWxnb3JpdGhtOiBzaGEy
+NTZXaXRoUlNBRW5jcnlwdGlvbgogICAgICAgIElzc3VlcjogQz1VUywgU1Q9Tm9ydGggQ2Fyb2xp
+bmEsIEw9RHVyaGFtLCBPPUFuc2libGUsIENOPWFuc2libGUuaHR0cC50ZXN0cwogICAgICAgIFZh
+bGlkaXR5CiAgICAgICAgICAgIE5vdCBCZWZvcmU6IE1hciAyMSAxODoyMjo0NyAyMDE4IEdNVAog
+ICAgICAgICAgICBOb3QgQWZ0ZXIgOiBNYXIgMTggMTg6MjI6NDcgMjAyOCBHTVQKICAgICAgICBT
+dWJqZWN0OiBDPVVTLCBTVD1Ob3J0aCBDYXJvbGluYSwgTz1BbnNpYmxlLCBDTj1jbGllbnQuYW5z
+aWJsZS5odHRwLnRlc3RzCiAgICAgICAgU3ViamVjdCBQdWJsaWMgS2V5IEluZm86CiAgICAgICAg
+ICAgIFB1YmxpYyBLZXkgQWxnb3JpdGhtOiByc2FFbmNyeXB0aW9uCiAgICAgICAgICAgICAgICBQ
+dWJsaWMtS2V5OiAoMjA0OCBiaXQpCiAgICAgICAgICAgICAgICBNb2R1bHVzOgogICAgICAgICAg
+ICAgICAgICAgIDAwOmQzOmNhOjI1OjcxOmFlOmM0OmIyOjY3OmU0OjJiOjg4OmM0OmZhOmIwOgog
+ICAgICAgICAgICAgICAgICAgIDU2OjAyOmE5OjBiOjY0OjJlOmE5OjQ4OjU5OmY2OmU5OjRlOjBm
+OjQxOmU5OgogICAgICAgICAgICAgICAgICAgIGY2OjVjOmVmOmRkOmVlOmEwOmNjOmNiOjUwOjZh
+OmI3Ojc5OmY4Ojk5OjUyOgogICAgICAgICAgICAgICAgICAgIDE4OjcxOjIzOmJmOjFkOjQzOjc4
+OjBkOjNlOjBiOjM1OmIwOmFkOmQ2Ojg1OgogICAgICAgICAgICAgICAgICAgIGUxOjY5OjkzOjU5
+OmY4OjIwOmJmOjI1Ojk3OjUzOjEyOmQ2OmUyOjRhOjkzOgogICAgICAgICAgICAgICAgICAgIDZi
+OmQ4OmFiOjM2OjU1Ojg2OjM4OjFjOmJkOjBhOjVlOjU3OmQ4OjI2OmVkOgogICAgICAgICAgICAg
+ICAgICAgIGJiOjc0OjlkOmQzOmYzOjFhOjNlOjYxOmUxOmY4OjdiOmE2OmJhOjBmOjllOgogICAg
+ICAgICAgICAgICAgICAgIGQ0OmIyOjc4OjFiOmNjOjI5OjVhOjJhOjRkOjNlOmVkOjEyOjc2OmE5
+OjQ0OgogICAgICAgICAgICAgICAgICAgIGFjOmI3OjZhOjc2OmYwOjQ0OmY4OjEzOmI3OmIwOjc5
+OjhjOjRiOmM5OjkwOgogICAgICAgICAgICAgICAgICAgIGY2OmIzOmI4Ojg3OjNlOmEyOjgzOjJj
+OjYwOmUxOmQ2OjdlOmY4OmY4OmExOgogICAgICAgICAgICAgICAgICAgIDViOjFlOmJmOjQzOjc2
+OjRhOmMzOmY0OmEzOmE3OjA3OjcyOmM0OjZlOjhjOgogICAgICAgICAgICAgICAgICAgIDUzOjIx
+OmNmOmYyOjUzOjI3OjU0OjkzOjQzOjE2OjljOjY5OjViOmIyOjJhOgogICAgICAgICAgICAgICAg
+ICAgIDk1OmRhOjcxOjdlOmM0OmJiOjUzOjA5OjgwOjZiOjdmOjhkOjRmOmQwOjI4OgogICAgICAg
+ICAgICAgICAgICAgIGQ0OjQ0OjcxOjVmOjRiOjc2Ojc3OjhlOjM2OjBhOjk4OmY2OmQzOjZmOmQy
+OgogICAgICAgICAgICAgICAgICAgIDFmOmJkOmIzOmIwOmRmOjFkOjg1OmU2OmYxOjRkOmNmOjNh
+OmJmOjliOjc1OgogICAgICAgICAgICAgICAgICAgIGM1OmJhOjYzOjJlOjNiOmY3OjUyOjg2OjA5
+OmE1OjBkOmI2OjIzOjZhOjRmOgogICAgICAgICAgICAgICAgICAgIDU5OjZmOmJiOmZlOjMyOjc3
+Ojg1OjkxOjkxOjc2OmM1OjE2OjZlOjYxOjI1OgogICAgICAgICAgICAgICAgICAgIDQ1OjI5CiAg
+ICAgICAgICAgICAgICBFeHBvbmVudDogNjU1MzcgKDB4MTAwMDEpCiAgICAgICAgWDUwOXYzIGV4
+dGVuc2lvbnM6CiAgICAgICAgICAgIFg1MDl2MyBCYXNpYyBDb25zdHJhaW50czogCiAgICAgICAg
+ICAgICAgICBDQTpGQUxTRQogICAgICAgICAgICBOZXRzY2FwZSBDb21tZW50OiAKICAgICAgICAg
+ICAgICAgIE9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlCiAgICAgICAgICAgIFg1MDl2MyBT
+dWJqZWN0IEtleSBJZGVudGlmaWVyOiAKICAgICAgICAgICAgICAgIEFGOkYzOkU1OjJBOkVCOkNG
+OkM3OjdFOkE0OkQ2OjQ5OjkyOkY5OjI5OkVFOjZBOjFCOjY4OkFCOjBGCiAgICAgICAgICAgIFg1
+MDl2MyBBdXRob3JpdHkgS2V5IElkZW50aWZpZXI6IAogICAgICAgICAgICAgICAga2V5aWQ6MTM6
+MkU6MzA6RjA6MDQ6RUE6NDE6NUY6Qjc6MDg6QkQ6MzQ6MzE6RDc6MTE6RUE6NTY6QTY6OTk6RjAK
+CiAgICBTaWduYXR1cmUgQWxnb3JpdGhtOiBzaGEyNTZXaXRoUlNBRW5jcnlwdGlvbgogICAgICAg
+ICAyOTo2MjozOToyNTo3OTo1ODplYjphNDpiMzowYzplYTphYToxZDoyYjo5Njo3Yzo2ZToxMDoK
+ICAgICAgICAgY2U6MTY6MDc6Yjc6NzA6N2Y6MTY6ZGE6ZmQ6MjA6ZTY6YTI6ZDk6YjQ6ODg6ZTA6
+Zjk6ODQ6CiAgICAgICAgIDg3OmY4OmIwOjBkOjc3OjhiOmFlOjI3OmY1OmVlOmU2OjRmOjg2OmEx
+OjJkOjc0OjA3OjdjOgogICAgICAgICBjNzo1ZDpjMjpiZDplNDo3MDplNzo0MjplNDoxNDplZTpi
+OTpiNzo2MzpiODo4Yzo2ZDoyMToKICAgICAgICAgNjE6NTY6MGI6OTY6ZjY6MTU6YmE6N2E6YWU6
+ODA6OTg6YWM6NTc6OTk6Nzk6M2Q6N2E6YTk6CiAgICAgICAgIGQ4OjI2OjkzOjMwOjE3OjUzOjdj
+OjJkOjAyOjRiOjY0OjQ5OjI1OjY1OmU3OjY5OjVhOjA4OgogICAgICAgICBjZjo4NDo5NDo4ZTo2
+YTo0MjphNzpkMTo0ZjpiYTozOTo0Yjo3YzoxMTo2NzozMTpmNzoxYjoKICAgICAgICAgMmI6Y2Q6
+Nzk6YzI6Mjg6NGQ6ZDk6ODg6NjY6ZDY6N2Y6NTY6NGM6NGI6Mzc6ZDE6M2Q6YTg6CiAgICAgICAg
+IGQ5OjRhOjZiOjQ1OjFkOjRkOmE3OjEyOjlmOjI5Ojc3OjZhOjU1OmMxOmI1OjFkOjBlOmE1Ogog
+ICAgICAgICBiOTo0ZjozODoxNjozYzo3ZDo4NTphZTpmZjoyMzozNDpjNzoyYzpmNjoxNDowZjo1
+NTplZjoKICAgICAgICAgYjg6MDA6ODk6ZjE6YjI6OGE6NzU6MTU6NDE6ODE6NzI6ZDA6NDM6YTY6
+ODY6ZDE6MDY6ZTY6CiAgICAgICAgIGNlOjgxOjdlOjVmOjMzOmU2OmY0OjE5OmQ2OjcwOjAwOmJh
+OjQ4OjZlOjA1OmZkOjRjOjNjOgogICAgICAgICBjMzo1MToxYjpiZDo0MzoxYToyNDpjNTo3OTpl
+YTo3YTpmMDo4NTphNTo0MDoxMDo4NTplOToKICAgICAgICAgMjM6MDk6MDk6ODA6Mzg6OWQ6YmM6
+ODE6NWU6NTk6OGM6NWE6NGQ6NTg6NTY6Yjk6NzE6YzI6CiAgICAgICAgIDc4OmNkOmYzOmIwCi0t
+LS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlEdVRDQ0FxR2dBd0lCQWdJQ0VBTXdEUVlKS29a
+SWh2Y05BUUVMQlFBd1pqRUxNQWtHQTFVRUJoTUNWVk14CkZ6QVZCZ05WQkFnTURrNXZjblJvSUVO
+aGNtOXNhVzVoTVE4d0RRWURWUVFIREFaRWRYSm9ZVzB4RURBT0JnTlYKQkFvTUIwRnVjMmxpYkdV
+eEd6QVpCZ05WQkFNTUVtRnVjMmxpYkdVdWFIUjBjQzUwWlhOMGN6QWVGdzB4T0RBegpNakV4T0RJ
+eU5EZGFGdzB5T0RBek1UZ3hPREl5TkRkYU1Gd3hDekFKQmdOVkJBWVRBbFZUTVJjd0ZRWURWUVFJ
+CkRBNU9iM0owYUNCRFlYSnZiR2x1WVRFUU1BNEdBMVVFQ2d3SFFXNXphV0pzWlRFaU1DQUdBMVVF
+QXd3WlkyeHAKWlc1MExtRnVjMmxpYkdVdWFIUjBjQzUwWlhOMGN6Q0NBU0l3RFFZSktvWklodmNO
+QVFFQkJRQURnZ0VQQURDQwpBUW9DZ2dFQkFOUEtKWEd1eExKbjVDdUl4UHF3VmdLcEMyUXVxVWha
+OXVsT0QwSHA5bHp2M2U2Z3pNdFFhcmQ1CitKbFNHSEVqdngxRGVBMCtDeld3cmRhRjRXbVRXZmdn
+dnlXWFV4TFc0a3FUYTlpck5sV0dPQnk5Q2w1WDJDYnQKdTNTZDAvTWFQbUhoK0h1bXVnK2UxTEo0
+Rzh3cFdpcE5QdTBTZHFsRXJMZHFkdkJFK0JPM3NIbU1TOG1ROXJPNApoejZpZ3l4ZzRkWisrUGlo
+V3g2L1EzWkt3L1NqcHdkeXhHNk1VeUhQOGxNblZKTkRGcHhwVzdJcWxkcHhmc1M3ClV3bUFhMytO
+VDlBbzFFUnhYMHQyZDQ0MkNwajIwMi9TSDcyenNOOGRoZWJ4VGM4NnY1dDF4YnBqTGp2M1VvWUoK
+cFEyMkkycFBXVys3L2pKM2haR1Jkc1VXYm1FbFJTa0NBd0VBQWFON01Ia3dDUVlEVlIwVEJBSXdB
+REFzQmdsZwpoa2dCaHZoQ0FRMEVIeFlkVDNCbGJsTlRUQ0JIWlc1bGNtRjBaV1FnUTJWeWRHbG1h
+V05oZEdVd0hRWURWUjBPCkJCWUVGSy96NVNycno4ZCtwTlpKa3ZrcDdtb2JhS3NQTUI4R0ExVWRJ
+d1FZTUJhQUZCTXVNUEFFNmtGZnR3aTkKTkRIWEVlcFdwcG53TUEwR0NTcUdTSWIzRFFFQkN3VUFB
+NElCQVFBcFlqa2xlVmpycExNTTZxb2RLNVo4YmhETwpGZ2UzY0g4VzJ2MGc1cUxadElqZytZU0gr
+TEFOZDR1dUovWHU1aytHb1MxMEIzekhYY0s5NUhEblF1UVU3cm0zClk3aU1iU0ZoVmd1VzloVzZl
+cTZBbUt4WG1YazllcW5ZSnBNd0YxTjhMUUpMWkVrbFplZHBXZ2pQaEpTT2FrS24KMFUrNk9VdDhF
+V2N4OXhzcnpYbkNLRTNaaUdiV2YxWk1TemZSUGFqWlNtdEZIVTJuRXA4cGQycFZ3YlVkRHFXNQpU
+emdXUEgyRnJ2OGpOTWNzOWhRUFZlKzRBSW54c29wMUZVR0JjdEJEcG9iUkJ1Yk9nWDVmTStiMEdk
+WndBTHBJCmJnWDlURHpEVVJ1OVF4b2t4WG5xZXZDRnBVQVFoZWtqQ1FtQU9KMjhnVjVaakZwTldG
+YTVjY0o0emZPdwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+
+--===============3996062709511591449==
+Content-Transfer-Encoding: base64
+Content-Type: application/octet-stream
+Content-Disposition: form-data; name="file5"; filename="client.key"
+
+LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZB
+QVNDQktjd2dnU2pBZ0VBQW9JQkFRRFR5aVZ4cnNTeVorUXIKaU1UNnNGWUNxUXRrTHFsSVdmYnBU
+ZzlCNmZaYzc5M3VvTXpMVUdxM2VmaVpVaGh4STc4ZFEzZ05QZ3Mxc0szVwpoZUZwazFuNElMOGxs
+MU1TMXVKS2sydllxelpWaGpnY3ZRcGVWOWdtN2J0MG5kUHpHajVoNGZoN3Byb1BudFN5CmVCdk1L
+Vm9xVFQ3dEVuYXBSS3kzYW5id1JQZ1R0N0I1akV2SmtQYXp1SWMrb29Nc1lPSFdmdmo0b1ZzZXYw
+TjIKU3NQMG82Y0hjc1J1akZNaHovSlRKMVNUUXhhY2FWdXlLcFhhY1g3RXUxTUpnR3QvalUvUUtO
+UkVjVjlMZG5lTwpOZ3FZOXROdjBoKzlzN0RmSFlYbThVM1BPcitiZGNXNll5NDc5MUtHQ2FVTnRp
+TnFUMWx2dS80eWQ0V1JrWGJGCkZtNWhKVVVwQWdNQkFBRUNnZ0VCQUpZT2FjMU1TSzBuRXZFTmJK
+TTZFUmE5Y3dhK1VNNmtmMTc2SWJGUDlYQVAKdTZ6eFhXaklSM1JNQlNtTWt5akdiUWhzMzBoeXB6
+cVpQZkg2MWFVWjgrcnNPTUtIbnlLQUFjRlpCbFp6cUlHYwpJWEdyTndkMU1mOFMvWGc0d3cxQmtP
+V0ZWNnMwakN1NUczWi94eUkyUWw0cWNPVkQ2Yk13cHpjbFJiUWpDYW5kCmR2cXlDZE1EMHNSRHll
+T0lLNWhCaFVZNjBKbldiTUN1NnBCVStxUG9SdWtiUmllYWVETElOMWNsd0VxSVFWNzgKTExudjRu
+OWZ1R296SDBKZEhIZnlYRnl0Q2dJSnZFc3BaVWphLzVSNG9yQURocjNaQjAxMFJMell2czJuZEUz
+Qgo0Y0Y5Umd4c3BKWmVKL1ArUGdsVmladXpqMzdwWHkrN0dBY0pMUjlrYTRrQ2dZRUEvbDAxWEt3
+a0N6TWdYSFc0ClVQZ2wxK29uNDJCc043VDlyM1M1dGloT2pIZjRaSldrZ1l6aXNMVlgrTmMxb1VJ
+M0hRZk05UERKWlhNTU5tN0oKWlJ2RVJjb3BVMjZ3V3FyNkNGUGJsR3Y4b3FYSHFjcGV0YThpM3ha
+S29QQVNzVFc2c3N1UENFYWppTFpiUTFySApIL0hQK09aSVZMTS9XQ1BnQTJCY2tUVTlKbnNDZ1lF
+QTFTYlhsbFhubHdHcW1qaXRtWTFaMDdyVXhRM2FoL2ZCCmljY2JiZzNFNG9ub250WVhJbEk1elFt
+czN1K3FCZGkwWnV3YURtNVk0QmV0T3EwYTNVeXhBc3VncVZGbnpUYmEKMXcvc0ZiM2Z3OUtlUS9p
+bDRDWGticTg3bnpKZkRtRXlxSEdDQ1lYYmlqSEJ4bnE5OVBrcXdWcGFBaEhIRVcwbQp2V3lNVXZQ
+Ulk2c0NnWUFidFVXUjBjS2ZZYk5kdndrVDhPUVdjQkJtU1dPZ2Nkdk1tQmQreTBjN0wvcGo0cFVu
+Cjg1UGlFZThDVVZjck9NNU9JRUpvVUM1d0dhY3o2citQZndYVFlHRStFR212aHI1ejE4YXNsVkxR
+Mk9RMkQ3QmYKZERPRlA2VmpnS05Zb0hTMDgwMmlaaWQ4UmZrTkRqOXdzR09xUmxPTXZuWGhBUTl1
+N3JsR3JCajhMd0tCZ0ZmbwpwaDk5bkg4ZUU5TjVMcmZXb1VaK2xvUVMyNThhSW5zRllCMjZsZ25z
+WU1FcGdPOEp4SWI0eDVCR2ZmUGRWVUhoCmZEbVpieFExRDUvVWh2RGdVVnpheUk4c1lNZzFLSHBz
+T2EwWjJ6Q3pLOHpTdnU2OEVnTklTQ20zSjVjUnBVZnQKVUhsRytLMTlLZk1HNmxNZmRHKzhLTVVU
+dWV0SS9pSS9vM3dPekx2ekFvR0FJck9oMzBySHQ4d2l0N0VMQVJ5eAp3UGtwMkFSWVhyS2ZYM05F
+UzRjNjd6U0FpKzNkQ2p4UnF5d3FUSTBnTGljeU1sajh6RXU5WUU5SXgvcmw4bFJaCm5ROUxabXF2
+N1FIemhMVFVDUEdnWlluZW12QnpvN3IwZVc4T2FnNTJkYmNKTzZGQnN6ZldyeHNrbS9mWDI1UmIK
+V1B4aWgydmRSeTgxNGROUFcyNXJnZHc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
+
+--===============3996062709511591449==
+Content-Transfer-Encoding: base64
+Content-Type: text/plain
+Content-Disposition: form-data; name="file6"; filename="client.txt"
+
+Y2xpZW50LnBlbSBhbmQgY2xpZW50LmtleSB3ZXJlIHJldHJpZXZlZCBmcm9tIGh0dHB0ZXN0ZXIg
+ZG9ja2VyIGltYWdlOgoKYW5zaWJsZS9hbnNpYmxlQHNoYTI1NjpmYTVkZWY4YzI5NGZjNTA4MTNh
+ZjEzMWMwYjU3Mzc1OTRkODUyYWJhYzljYmU3YmEzOGUxN2JmMWM4NDc2ZjNmCg==
+
+--===============3996062709511591449==
+Content-Type: text/plain
+Content-Disposition: form-data; name="form_field_1"
+
+form_value_1
+--===============3996062709511591449==
+Content-Type: application/octet-stream
+Content-Disposition: form-data; name="form_field_2"
+
+form_value_2
+--===============3996062709511591449==
+Content-Type: text/html
+Content-Disposition: form-data; name="form_field_3"
+
+<html></html>
+--===============3996062709511591449==
+Content-Type: application/json
+Content-Disposition: form-data; name="form_field_4"
+
+{"foo": "bar"}
+--===============3996062709511591449==--
diff --git a/test/units/module_utils/urls/fixtures/netrc b/test/units/module_utils/urls/fixtures/netrc
new file mode 100644
index 0000000..8f12717
--- /dev/null
+++ b/test/units/module_utils/urls/fixtures/netrc
@@ -0,0 +1,3 @@
+machine ansible.com
+login user
+password passwd
diff --git a/test/units/module_utils/urls/test_RedirectHandlerFactory.py b/test/units/module_utils/urls/test_RedirectHandlerFactory.py
new file mode 100644
index 0000000..7bbe4b5
--- /dev/null
+++ b/test/units/module_utils/urls/test_RedirectHandlerFactory.py
@@ -0,0 +1,140 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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
+
+
+from ansible.module_utils.urls import HAS_SSLCONTEXT, RedirectHandlerFactory, urllib_request, urllib_error
+from ansible.module_utils.six import StringIO
+
+import pytest
+
+
+@pytest.fixture
+def urllib_req():
+ req = urllib_request.Request(
+ 'https://ansible.com/'
+ )
+ return req
+
+
+@pytest.fixture
+def request_body():
+ return StringIO('TESTS')
+
+
+def test_no_redirs(urllib_req, request_body):
+ handler = RedirectHandlerFactory('none', False)
+ inst = handler()
+ with pytest.raises(urllib_error.HTTPError):
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+
+def test_urllib2_redir(urllib_req, request_body, mocker):
+ redir_request_mock = mocker.patch('ansible.module_utils.urls.urllib_request.HTTPRedirectHandler.redirect_request')
+
+ handler = RedirectHandlerFactory('urllib2', False)
+ inst = handler()
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+ redir_request_mock.assert_called_once_with(inst, urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+
+def test_all_redir(urllib_req, request_body, mocker):
+ req_mock = mocker.patch('ansible.module_utils.urls.RequestWithMethod')
+ handler = RedirectHandlerFactory('all', False)
+ inst = handler()
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+ req_mock.assert_called_once_with('https://docs.ansible.com/', data=None, headers={}, method='GET', origin_req_host='ansible.com', unverifiable=True)
+
+
+def test_all_redir_post(request_body, mocker):
+ handler = RedirectHandlerFactory('all', False)
+ inst = handler()
+
+ req = urllib_request.Request(
+ 'https://ansible.com/',
+ 'POST'
+ )
+
+ req_mock = mocker.patch('ansible.module_utils.urls.RequestWithMethod')
+ inst.redirect_request(req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+ req_mock.assert_called_once_with('https://docs.ansible.com/', data=None, headers={}, method='GET', origin_req_host='ansible.com', unverifiable=True)
+
+
+def test_redir_headers_removal(urllib_req, request_body, mocker):
+ req_mock = mocker.patch('ansible.module_utils.urls.RequestWithMethod')
+ handler = RedirectHandlerFactory('all', False)
+ inst = handler()
+
+ urllib_req.headers = {
+ 'Content-Type': 'application/json',
+ 'Content-Length': 100,
+ 'Foo': 'bar',
+ }
+
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+ req_mock.assert_called_once_with('https://docs.ansible.com/', data=None, headers={'Foo': 'bar'}, method='GET', origin_req_host='ansible.com',
+ unverifiable=True)
+
+
+def test_redir_url_spaces(urllib_req, request_body, mocker):
+ req_mock = mocker.patch('ansible.module_utils.urls.RequestWithMethod')
+ handler = RedirectHandlerFactory('all', False)
+ inst = handler()
+
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/foo bar')
+
+ req_mock.assert_called_once_with('https://docs.ansible.com/foo%20bar', data=None, headers={}, method='GET', origin_req_host='ansible.com',
+ unverifiable=True)
+
+
+def test_redir_safe(urllib_req, request_body, mocker):
+ req_mock = mocker.patch('ansible.module_utils.urls.RequestWithMethod')
+ handler = RedirectHandlerFactory('safe', False)
+ inst = handler()
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+ req_mock.assert_called_once_with('https://docs.ansible.com/', data=None, headers={}, method='GET', origin_req_host='ansible.com', unverifiable=True)
+
+
+def test_redir_safe_not_safe(request_body):
+ handler = RedirectHandlerFactory('safe', False)
+ inst = handler()
+
+ req = urllib_request.Request(
+ 'https://ansible.com/',
+ 'POST'
+ )
+
+ with pytest.raises(urllib_error.HTTPError):
+ inst.redirect_request(req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+
+def test_redir_no_error_on_invalid(urllib_req, request_body):
+ handler = RedirectHandlerFactory('invalid', False)
+ inst = handler()
+
+ with pytest.raises(urllib_error.HTTPError):
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+
+def test_redir_validate_certs(urllib_req, request_body, mocker):
+ opener_mock = mocker.patch('ansible.module_utils.urls.urllib_request._opener')
+ handler = RedirectHandlerFactory('all', True)
+ inst = handler()
+ inst.redirect_request(urllib_req, request_body, 301, '301 Moved Permanently', {}, 'https://docs.ansible.com/')
+
+ assert opener_mock.add_handler.call_count == int(not HAS_SSLCONTEXT)
+
+
+def test_redir_http_error_308_urllib2(urllib_req, request_body, mocker):
+ redir_mock = mocker.patch.object(urllib_request.HTTPRedirectHandler, 'redirect_request')
+ handler = RedirectHandlerFactory('urllib2', False)
+ inst = handler()
+
+ inst.redirect_request(urllib_req, request_body, 308, '308 Permanent Redirect', {}, 'https://docs.ansible.com/')
+
+ assert redir_mock.call_count == 1
diff --git a/test/units/module_utils/urls/test_Request.py b/test/units/module_utils/urls/test_Request.py
new file mode 100644
index 0000000..d2c4ea3
--- /dev/null
+++ b/test/units/module_utils/urls/test_Request.py
@@ -0,0 +1,467 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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 datetime
+import os
+
+from ansible.module_utils.urls import (Request, open_url, urllib_request, HAS_SSLCONTEXT, cookiejar, RequestWithMethod,
+ UnixHTTPHandler, UnixHTTPSConnection, httplib)
+from ansible.module_utils.urls import SSLValidationHandler, HTTPSClientAuthHandler, RedirectHandlerFactory
+
+import pytest
+from units.compat.mock import call
+
+
+if HAS_SSLCONTEXT:
+ import ssl
+
+
+@pytest.fixture
+def urlopen_mock(mocker):
+ return mocker.patch('ansible.module_utils.urls.urllib_request.urlopen')
+
+
+@pytest.fixture
+def install_opener_mock(mocker):
+ return mocker.patch('ansible.module_utils.urls.urllib_request.install_opener')
+
+
+def test_Request_fallback(urlopen_mock, install_opener_mock, mocker):
+ here = os.path.dirname(__file__)
+ pem = os.path.join(here, 'fixtures/client.pem')
+
+ cookies = cookiejar.CookieJar()
+ request = Request(
+ headers={'foo': 'bar'},
+ use_proxy=False,
+ force=True,
+ timeout=100,
+ validate_certs=False,
+ url_username='user',
+ url_password='passwd',
+ http_agent='ansible-tests',
+ force_basic_auth=True,
+ follow_redirects='all',
+ client_cert='/tmp/client.pem',
+ client_key='/tmp/client.key',
+ cookies=cookies,
+ unix_socket='/foo/bar/baz.sock',
+ ca_path=pem,
+ ciphers=['ECDHE-RSA-AES128-SHA256'],
+ use_netrc=True,
+ )
+ fallback_mock = mocker.spy(request, '_fallback')
+
+ r = request.open('GET', 'https://ansible.com')
+
+ calls = [
+ call(None, False), # use_proxy
+ call(None, True), # force
+ call(None, 100), # timeout
+ call(None, False), # validate_certs
+ call(None, 'user'), # url_username
+ call(None, 'passwd'), # url_password
+ 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, cookies), # cookies
+ call(None, '/foo/bar/baz.sock'), # unix_socket
+ call(None, pem), # ca_path
+ call(None, None), # unredirected_headers
+ call(None, True), # auto_decompress
+ call(None, ['ECDHE-RSA-AES128-SHA256']), # ciphers
+ call(None, True), # use_netrc
+ ]
+ fallback_mock.assert_has_calls(calls)
+
+ assert fallback_mock.call_count == 18 # All but headers use fallback
+
+ args = urlopen_mock.call_args[0]
+ assert args[1] is None # data, this is handled in the Request not urlopen
+ assert args[2] == 100 # timeout
+
+ req = args[0]
+ assert req.headers == {
+ 'Authorization': b'Basic dXNlcjpwYXNzd2Q=',
+ 'Cache-control': 'no-cache',
+ 'Foo': 'bar',
+ 'User-agent': 'ansible-tests'
+ }
+ assert req.data is None
+ assert req.get_method() == 'GET'
+
+
+def test_Request_open(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/')
+ args = urlopen_mock.call_args[0]
+ assert args[1] is None # data, this is handled in the Request not urlopen
+ assert args[2] == 10 # timeout
+
+ req = args[0]
+ assert req.headers == {}
+ assert req.data is None
+ assert req.get_method() == 'GET'
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ if not HAS_SSLCONTEXT:
+ expected_handlers = (
+ SSLValidationHandler,
+ RedirectHandlerFactory(), # factory, get handler
+ )
+ else:
+ expected_handlers = (
+ RedirectHandlerFactory(), # factory, get handler
+ )
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, SSLValidationHandler) or handler.__class__.__name__ == 'RedirectHandler':
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == len(expected_handlers)
+
+
+def test_Request_open_http(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://ansible.com/')
+ args = urlopen_mock.call_args[0]
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, SSLValidationHandler):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 0
+
+
+def test_Request_open_unix_socket(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://ansible.com/', unix_socket='/foo/bar/baz.sock')
+ args = urlopen_mock.call_args[0]
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, UnixHTTPHandler):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 1
+
+
+def test_Request_open_https_unix_socket(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/', unix_socket='/foo/bar/baz.sock')
+ args = urlopen_mock.call_args[0]
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, HTTPSClientAuthHandler):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 1
+
+ inst = found_handlers[0]._build_https_connection('foo')
+ assert isinstance(inst, UnixHTTPSConnection)
+
+
+def test_Request_open_ftp(urlopen_mock, install_opener_mock, mocker):
+ mocker.patch('ansible.module_utils.urls.ParseResultDottedDict.as_list', side_effect=AssertionError)
+
+ # Using ftp scheme should prevent the AssertionError side effect to fire
+ r = Request().open('GET', 'ftp://foo@ansible.com/')
+
+
+def test_Request_open_headers(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://ansible.com/', headers={'Foo': 'bar'})
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert req.headers == {'Foo': 'bar'}
+
+
+def test_Request_open_username(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://ansible.com/', url_username='user')
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ expected_handlers = (
+ urllib_request.HTTPBasicAuthHandler,
+ urllib_request.HTTPDigestAuthHandler,
+ )
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, expected_handlers):
+ found_handlers.append(handler)
+ assert len(found_handlers) == 2
+ assert found_handlers[0].passwd.passwd[None] == {(('ansible.com', '/'),): ('user', None)}
+
+
+def test_Request_open_username_in_url(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://user2@ansible.com/')
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ expected_handlers = (
+ urllib_request.HTTPBasicAuthHandler,
+ urllib_request.HTTPDigestAuthHandler,
+ )
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, expected_handlers):
+ found_handlers.append(handler)
+ assert found_handlers[0].passwd.passwd[None] == {(('ansible.com', '/'),): ('user2', '')}
+
+
+def test_Request_open_username_force_basic(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://ansible.com/', url_username='user', url_password='passwd', force_basic_auth=True)
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ expected_handlers = (
+ urllib_request.HTTPBasicAuthHandler,
+ urllib_request.HTTPDigestAuthHandler,
+ )
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, expected_handlers):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 0
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q='
+
+
+def test_Request_open_auth_in_netloc(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'http://user:passwd@ansible.com/')
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert req.get_full_url() == 'http://ansible.com/'
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ expected_handlers = (
+ urllib_request.HTTPBasicAuthHandler,
+ urllib_request.HTTPDigestAuthHandler,
+ )
+
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, expected_handlers):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 2
+
+
+def test_Request_open_netrc(urlopen_mock, install_opener_mock, monkeypatch):
+ here = os.path.dirname(__file__)
+
+ monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc'))
+ r = Request().open('GET', 'http://ansible.com/')
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert req.headers.get('Authorization') == b'Basic dXNlcjpwYXNzd2Q='
+
+ r = Request().open('GET', 'http://foo.ansible.com/')
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert 'Authorization' not in req.headers
+
+ monkeypatch.setenv('NETRC', os.path.join(here, 'fixtures/netrc.nonexistant'))
+ r = Request().open('GET', 'http://ansible.com/')
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+ assert 'Authorization' not in req.headers
+
+
+def test_Request_open_no_proxy(urlopen_mock, install_opener_mock, mocker):
+ build_opener_mock = mocker.patch('ansible.module_utils.urls.urllib_request.build_opener')
+
+ r = Request().open('GET', 'http://ansible.com/', use_proxy=False)
+
+ handlers = build_opener_mock.call_args[0]
+ found_handlers = []
+ for handler in handlers:
+ if isinstance(handler, urllib_request.ProxyHandler):
+ found_handlers.append(handler)
+
+ assert len(found_handlers) == 1
+
+
+@pytest.mark.skipif(not HAS_SSLCONTEXT, reason="requires SSLContext")
+def test_Request_open_no_validate_certs(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/', validate_certs=False)
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ ssl_handler = None
+ for handler in handlers:
+ if isinstance(handler, HTTPSClientAuthHandler):
+ ssl_handler = handler
+ break
+
+ assert ssl_handler is not None
+
+ inst = ssl_handler._build_https_connection('foo')
+ assert isinstance(inst, httplib.HTTPSConnection)
+
+ context = ssl_handler._context
+ # Differs by Python version
+ # assert context.protocol == ssl.PROTOCOL_SSLv23
+ if ssl.OP_NO_SSLv2:
+ assert context.options & ssl.OP_NO_SSLv2
+ assert context.options & ssl.OP_NO_SSLv3
+ assert context.verify_mode == ssl.CERT_NONE
+ assert context.check_hostname is False
+
+
+def test_Request_open_client_cert(urlopen_mock, install_opener_mock):
+ here = os.path.dirname(__file__)
+
+ client_cert = os.path.join(here, 'fixtures/client.pem')
+ client_key = os.path.join(here, 'fixtures/client.key')
+
+ r = Request().open('GET', 'https://ansible.com/', client_cert=client_cert, client_key=client_key)
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ ssl_handler = None
+ for handler in handlers:
+ if isinstance(handler, HTTPSClientAuthHandler):
+ ssl_handler = handler
+ break
+
+ assert ssl_handler is not None
+
+ 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
+
+
+def test_Request_open_cookies(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/', cookies=cookiejar.CookieJar())
+
+ opener = install_opener_mock.call_args[0][0]
+ handlers = opener.handlers
+
+ cookies_handler = None
+ for handler in handlers:
+ if isinstance(handler, urllib_request.HTTPCookieProcessor):
+ cookies_handler = handler
+ break
+
+ assert cookies_handler is not None
+
+
+def test_Request_open_invalid_method(urlopen_mock, install_opener_mock):
+ r = Request().open('UNKNOWN', 'https://ansible.com/')
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+
+ assert req.data is None
+ assert req.get_method() == 'UNKNOWN'
+ # assert r.status == 504
+
+
+def test_Request_open_custom_method(urlopen_mock, install_opener_mock):
+ r = Request().open('DELETE', 'https://ansible.com/')
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+
+ assert isinstance(req, RequestWithMethod)
+
+
+def test_Request_open_user_agent(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/', http_agent='ansible-tests')
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+
+ assert req.headers.get('User-agent') == 'ansible-tests'
+
+
+def test_Request_open_force(urlopen_mock, install_opener_mock):
+ r = Request().open('GET', 'https://ansible.com/', force=True, last_mod_time=datetime.datetime.now())
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+
+ assert req.headers.get('Cache-control') == 'no-cache'
+ assert 'If-modified-since' not in req.headers
+
+
+def test_Request_open_last_mod(urlopen_mock, install_opener_mock):
+ now = datetime.datetime.now()
+ r = Request().open('GET', 'https://ansible.com/', last_mod_time=now)
+
+ args = urlopen_mock.call_args[0]
+ req = args[0]
+
+ assert req.headers.get('If-modified-since') == now.strftime('%a, %d %b %Y %H:%M:%S GMT')
+
+
+def test_Request_open_headers_not_dict(urlopen_mock, install_opener_mock):
+ with pytest.raises(ValueError):
+ Request().open('GET', 'https://ansible.com/', headers=['bob'])
+
+
+def test_Request_init_headers_not_dict(urlopen_mock, install_opener_mock):
+ with pytest.raises(ValueError):
+ Request(headers=['bob'])
+
+
+@pytest.mark.parametrize('method,kwargs', [
+ ('get', {}),
+ ('options', {}),
+ ('head', {}),
+ ('post', {'data': None}),
+ ('put', {'data': None}),
+ ('patch', {'data': None}),
+ ('delete', {}),
+])
+def test_methods(method, kwargs, mocker):
+ expected = method.upper()
+ open_mock = mocker.patch('ansible.module_utils.urls.Request.open')
+ request = Request()
+ getattr(request, method)('https://ansible.com')
+ open_mock.assert_called_once_with(expected, 'https://ansible.com', **kwargs)
+
+
+def test_open_url(urlopen_mock, install_opener_mock, mocker):
+ req_mock = mocker.patch('ansible.module_utils.urls.Request.open')
+ open_url('https://ansible.com/')
+ req_mock.assert_called_once_with('GET', 'https://ansible.com/', data=None, headers=None, use_proxy=True,
+ force=False, last_mod_time=None, timeout=10, validate_certs=True,
+ url_username=None, url_password=None, http_agent=None,
+ force_basic_auth=False, follow_redirects='urllib2',
+ client_cert=None, client_key=None, cookies=None, use_gssapi=False,
+ unix_socket=None, ca_path=None, unredirected_headers=None, decompress=True,
+ ciphers=None, use_netrc=True)
diff --git a/test/units/module_utils/urls/test_RequestWithMethod.py b/test/units/module_utils/urls/test_RequestWithMethod.py
new file mode 100644
index 0000000..0510519
--- /dev/null
+++ b/test/units/module_utils/urls/test_RequestWithMethod.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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
+
+from ansible.module_utils.urls import RequestWithMethod
+
+
+def test_RequestWithMethod():
+ get = RequestWithMethod('https://ansible.com/', 'GET')
+ assert get.get_method() == 'GET'
+
+ post = RequestWithMethod('https://ansible.com/', 'POST', data='foo', headers={'Bar': 'baz'})
+ assert post.get_method() == 'POST'
+ assert post.get_full_url() == 'https://ansible.com/'
+ assert post.data == 'foo'
+ assert post.headers == {'Bar': 'baz'}
+
+ none = RequestWithMethod('https://ansible.com/', '')
+ assert none.get_method() == 'GET'
diff --git a/test/units/module_utils/urls/test_channel_binding.py b/test/units/module_utils/urls/test_channel_binding.py
new file mode 100644
index 0000000..ea9cd01
--- /dev/null
+++ b/test/units/module_utils/urls/test_channel_binding.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# (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 base64
+import os.path
+import pytest
+
+from ansible.module_utils import urls
+
+
+@pytest.mark.skipif(not urls.HAS_CRYPTOGRAPHY, reason='Requires cryptography to be installed')
+@pytest.mark.parametrize('certificate, expected', [
+ ('rsa_md5.pem', b'\x23\x34\xB8\x47\x6C\xBF\x4E\x6D'
+ b'\xFC\x76\x6A\x5D\x5A\x30\xD6\x64'
+ b'\x9C\x01\xBA\xE1\x66\x2A\x5C\x3A'
+ b'\x13\x02\xA9\x68\xD7\xC6\xB0\xF6'),
+ ('rsa_sha1.pem', b'\x14\xCF\xE8\xE4\xB3\x32\xB2\x0A'
+ b'\x34\x3F\xC8\x40\xB1\x8F\x9F\x6F'
+ b'\x78\x92\x6A\xFE\x7E\xC3\xE7\xB8'
+ b'\xE2\x89\x69\x61\x9B\x1E\x8F\x3E'),
+ ('rsa_sha256.pem', b'\x99\x6F\x3E\xEA\x81\x2C\x18\x70'
+ b'\xE3\x05\x49\xFF\x9B\x86\xCD\x87'
+ b'\xA8\x90\xB6\xD8\xDF\xDF\x4A\x81'
+ b'\xBE\xF9\x67\x59\x70\xDA\xDB\x26'),
+ ('rsa_sha384.pem', b'\x34\xF3\x03\xC9\x95\x28\x6F\x4B'
+ b'\x21\x4A\x9B\xA6\x43\x5B\x69\xB5'
+ b'\x1E\xCF\x37\x58\xEA\xBC\x2A\x14'
+ b'\xD7\xA4\x3F\xD2\x37\xDC\x2B\x1A'
+ b'\x1A\xD9\x11\x1C\x5C\x96\x5E\x10'
+ b'\x75\x07\xCB\x41\x98\xC0\x9F\xEC'),
+ ('rsa_sha512.pem', b'\x55\x6E\x1C\x17\x84\xE3\xB9\x57'
+ b'\x37\x0B\x7F\x54\x4F\x62\xC5\x33'
+ b'\xCB\x2C\xA5\xC1\xDA\xE0\x70\x6F'
+ b'\xAE\xF0\x05\x44\xE1\xAD\x2B\x76'
+ b'\xFF\x25\xCF\xBE\x69\xB1\xC4\xE6'
+ b'\x30\xC3\xBB\x02\x07\xDF\x11\x31'
+ b'\x4C\x67\x38\xBC\xAE\xD7\xE0\x71'
+ b'\xD7\xBF\xBF\x2C\x9D\xFA\xB8\x5D'),
+ ('rsa-pss_sha256.pem', b'\xF2\x31\xE6\xFF\x3F\x9E\x16\x1B'
+ b'\xC2\xDC\xBB\x89\x8D\x84\x47\x4E'
+ b'\x58\x9C\xD7\xC2\x7A\xDB\xEF\x8B'
+ b'\xD9\xC0\xC0\x68\xAF\x9C\x36\x6D'),
+ ('rsa-pss_sha512.pem', b'\x85\x85\x19\xB9\xE1\x0F\x23\xE2'
+ b'\x1D\x2C\xE9\xD5\x47\x2A\xAB\xCE'
+ b'\x42\x0F\xD1\x00\x75\x9C\x53\xA1'
+ b'\x7B\xB9\x79\x86\xB2\x59\x61\x27'),
+ ('ecdsa_sha256.pem', b'\xFE\xCF\x1B\x25\x85\x44\x99\x90'
+ b'\xD9\xE3\xB2\xC9\x2D\x3F\x59\x7E'
+ b'\xC8\x35\x4E\x12\x4E\xDA\x75\x1D'
+ b'\x94\x83\x7C\x2C\x89\xA2\xC1\x55'),
+ ('ecdsa_sha512.pem', b'\xE5\xCB\x68\xB2\xF8\x43\xD6\x3B'
+ b'\xF4\x0B\xCB\x20\x07\x60\x8F\x81'
+ b'\x97\x61\x83\x92\x78\x3F\x23\x30'
+ b'\xE5\xEF\x19\xA5\xBD\x8F\x0B\x2F'
+ b'\xAA\xC8\x61\x85\x5F\xBB\x63\xA2'
+ b'\x21\xCC\x46\xFC\x1E\x22\x6A\x07'
+ b'\x24\x11\xAF\x17\x5D\xDE\x47\x92'
+ b'\x81\xE0\x06\x87\x8B\x34\x80\x59'),
+])
+def test_cbt_with_cert(certificate, expected):
+ with open(os.path.join(os.path.dirname(__file__), 'fixtures', 'cbt', certificate)) as fd:
+ cert_der = base64.b64decode("".join([l.strip() for l in fd.readlines()[1:-1]]))
+
+ actual = urls.get_channel_binding_cert_hash(cert_der)
+ assert actual == expected
+
+
+def test_cbt_no_cryptography(monkeypatch):
+ monkeypatch.setattr(urls, 'HAS_CRYPTOGRAPHY', False)
+ assert urls.get_channel_binding_cert_hash(None) is None
diff --git a/test/units/module_utils/urls/test_fetch_file.py b/test/units/module_utils/urls/test_fetch_file.py
new file mode 100644
index 0000000..ed11227
--- /dev/null
+++ b/test/units/module_utils/urls/test_fetch_file.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+# Copyright: Contributors to the 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
+
+from ansible.module_utils.urls import fetch_file
+
+import pytest
+from units.compat.mock import MagicMock
+
+
+class FakeTemporaryFile:
+ def __init__(self, name):
+ self.name = name
+
+
+@pytest.mark.parametrize(
+ 'url, prefix, suffix, expected', (
+ ('http://ansible.com/foo.tar.gz?foo=%s' % ('bar' * 100), 'foo', '.tar.gz', 'foo.tar.gz'),
+ ('https://www.gnu.org/licenses/gpl-3.0.txt', 'gpl-3.0', '.txt', 'gpl-3.0.txt'),
+ ('http://pyyaml.org/download/libyaml/yaml-0.2.5.tar.gz', 'yaml-0.2.5', '.tar.gz', 'yaml-0.2.5.tar.gz'),
+ (
+ 'https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz',
+ 'geckodriver-v0.26.0-linux64',
+ '.tar.gz',
+ 'geckodriver-v0.26.0-linux64.tar.gz'
+ ),
+ )
+)
+def test_file_multiple_extensions(mocker, url, prefix, suffix, expected):
+ module = mocker.Mock()
+ module.tmpdir = '/tmp'
+ module.add_cleanup_file = mocker.Mock(side_effect=AttributeError('raised intentionally'))
+
+ mock_NamedTemporaryFile = mocker.patch('ansible.module_utils.urls.tempfile.NamedTemporaryFile',
+ return_value=FakeTemporaryFile(os.path.join(module.tmpdir, expected)))
+
+ with pytest.raises(AttributeError, match='raised intentionally'):
+ fetch_file(module, url)
+
+ mock_NamedTemporaryFile.assert_called_with(dir=module.tmpdir, prefix=prefix, suffix=suffix, delete=False)
diff --git a/test/units/module_utils/urls/test_fetch_url.py b/test/units/module_utils/urls/test_fetch_url.py
new file mode 100644
index 0000000..5bfd66a
--- /dev/null
+++ b/test/units/module_utils/urls/test_fetch_url.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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 socket
+import sys
+
+from ansible.module_utils.six import StringIO
+from ansible.module_utils.six.moves.http_cookiejar import Cookie
+from ansible.module_utils.six.moves.http_client import HTTPMessage
+from ansible.module_utils.urls import fetch_url, urllib_error, ConnectionError, NoSSLError, httplib
+
+import pytest
+from units.compat.mock import MagicMock
+
+
+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 open_url_mock(mocker):
+ return mocker.patch('ansible.module_utils.urls.open_url')
+
+
+@pytest.fixture
+def fake_ansible_module():
+ return FakeAnsibleModule()
+
+
+class FakeAnsibleModule:
+ def __init__(self):
+ 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)
+
+
+def test_fetch_url_no_urlparse(mocker, fake_ansible_module):
+ mocker.patch('ansible.module_utils.urls.HAS_URLPARSE', new=False)
+
+ with pytest.raises(FailJson):
+ fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+
+def test_fetch_url(open_url_mock, fake_ansible_module):
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ dummy, kwargs = open_url_mock.call_args
+
+ open_url_mock.assert_called_once_with('http://ansible.com/', client_cert=None, client_key=None, cookies=kwargs['cookies'], data=None,
+ follow_redirects='urllib2', force=False, force_basic_auth='', headers=None,
+ http_agent='ansible-httpget', last_mod_time=None, method=None, timeout=10, url_password='', url_username='',
+ use_proxy=True, validate_certs=True, use_gssapi=False, unix_socket=None, ca_path=None, unredirected_headers=None,
+ decompress=True, ciphers=None, use_netrc=True)
+
+
+def test_fetch_url_params(open_url_mock, fake_ansible_module):
+ fake_ansible_module.params = {
+ 'validate_certs': False,
+ 'url_username': 'user',
+ 'url_password': 'passwd',
+ 'http_agent': 'ansible-test',
+ 'force_basic_auth': True,
+ 'follow_redirects': 'all',
+ 'client_cert': 'client.pem',
+ 'client_key': 'client.key',
+ }
+
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ dummy, kwargs = open_url_mock.call_args
+
+ open_url_mock.assert_called_once_with('http://ansible.com/', client_cert='client.pem', client_key='client.key', cookies=kwargs['cookies'], data=None,
+ follow_redirects='all', force=False, force_basic_auth=True, headers=None,
+ http_agent='ansible-test', last_mod_time=None, method=None, timeout=10, url_password='passwd', url_username='user',
+ use_proxy=True, validate_certs=False, use_gssapi=False, unix_socket=None, ca_path=None, unredirected_headers=None,
+ decompress=True, ciphers=None, use_netrc=True)
+
+
+def test_fetch_url_cookies(mocker, fake_ansible_module):
+ def make_cookies(*args, **kwargs):
+ cookies = kwargs['cookies']
+ r = MagicMock()
+ try:
+ r.headers = HTTPMessage()
+ add_header = r.headers.add_header
+ except TypeError:
+ # PY2
+ r.headers = HTTPMessage(StringIO())
+ add_header = r.headers.addheader
+ r.info.return_value = r.headers
+ for name, value in (('Foo', 'bar'), ('Baz', 'qux')):
+ cookie = Cookie(
+ version=0,
+ name=name,
+ value=value,
+ port=None,
+ port_specified=False,
+ domain="ansible.com",
+ domain_specified=True,
+ domain_initial_dot=False,
+ path="/",
+ path_specified=True,
+ secure=False,
+ expires=None,
+ discard=False,
+ comment=None,
+ comment_url=None,
+ rest=None
+ )
+ cookies.set_cookie(cookie)
+ add_header('Set-Cookie', '%s=%s' % (name, value))
+
+ return r
+
+ mocker = mocker.patch('ansible.module_utils.urls.open_url', new=make_cookies)
+
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert info['cookies'] == {'Baz': 'qux', 'Foo': 'bar'}
+
+ if sys.version_info < (3, 11):
+ # Python sorts cookies in order of most specific (ie. longest) path first
+ # items with the same path are reversed from response order
+ assert info['cookies_string'] == 'Baz=qux; Foo=bar'
+ else:
+ # Python 3.11 and later preserve the Set-Cookie order.
+ # See: https://github.com/python/cpython/pull/22745/
+ assert info['cookies_string'] == 'Foo=bar; Baz=qux'
+
+ # The key here has a `-` as opposed to what we see in the `uri` module that converts to `_`
+ # Note: this is response order, which differs from cookies_string
+ assert info['set-cookie'] == 'Foo=bar, Baz=qux'
+
+
+def test_fetch_url_nossl(open_url_mock, fake_ansible_module, mocker):
+ mocker.patch('ansible.module_utils.urls.get_distribution', return_value='notredhat')
+
+ open_url_mock.side_effect = NoSSLError
+ with pytest.raises(FailJson) as excinfo:
+ fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert 'python-ssl' not in excinfo.value.kwargs['msg']
+
+ mocker.patch('ansible.module_utils.urls.get_distribution', return_value='redhat')
+
+ open_url_mock.side_effect = NoSSLError
+ with pytest.raises(FailJson) as excinfo:
+ fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert 'python-ssl' in excinfo.value.kwargs['msg']
+ assert 'http://ansible.com/' == excinfo.value.kwargs['url']
+ assert excinfo.value.kwargs['status'] == -1
+
+
+def test_fetch_url_connectionerror(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = ConnectionError('TESTS')
+ with pytest.raises(FailJson) as excinfo:
+ fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert excinfo.value.kwargs['msg'] == 'TESTS'
+ assert 'http://ansible.com/' == excinfo.value.kwargs['url']
+ assert excinfo.value.kwargs['status'] == -1
+
+ open_url_mock.side_effect = ValueError('TESTS')
+ with pytest.raises(FailJson) as excinfo:
+ fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert excinfo.value.kwargs['msg'] == 'TESTS'
+ assert 'http://ansible.com/' == excinfo.value.kwargs['url']
+ assert excinfo.value.kwargs['status'] == -1
+
+
+def test_fetch_url_httperror(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = urllib_error.HTTPError(
+ 'http://ansible.com/',
+ 500,
+ 'Internal Server Error',
+ {'Content-Type': 'application/json'},
+ StringIO('TESTS')
+ )
+
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+
+ assert info == {'msg': 'HTTP Error 500: Internal Server Error', 'body': 'TESTS',
+ 'status': 500, 'url': 'http://ansible.com/', 'content-type': 'application/json'}
+
+
+def test_fetch_url_urlerror(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = urllib_error.URLError('TESTS')
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+ assert info == {'msg': 'Request failed: <urlopen error TESTS>', 'status': -1, 'url': 'http://ansible.com/'}
+
+
+def test_fetch_url_socketerror(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = socket.error('TESTS')
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+ assert info == {'msg': 'Connection failure: TESTS', 'status': -1, 'url': 'http://ansible.com/'}
+
+
+def test_fetch_url_exception(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = Exception('TESTS')
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+ exception = info.pop('exception')
+ assert info == {'msg': 'An unknown error occurred: TESTS', 'status': -1, 'url': 'http://ansible.com/'}
+ assert "Exception: TESTS" in exception
+
+
+def test_fetch_url_badstatusline(open_url_mock, fake_ansible_module):
+ open_url_mock.side_effect = httplib.BadStatusLine('TESTS')
+ r, info = fetch_url(fake_ansible_module, 'http://ansible.com/')
+ assert info == {'msg': 'Connection failure: connection was closed before a valid response was received: TESTS', 'status': -1, 'url': 'http://ansible.com/'}
diff --git a/test/units/module_utils/urls/test_generic_urlparse.py b/test/units/module_utils/urls/test_generic_urlparse.py
new file mode 100644
index 0000000..7753726
--- /dev/null
+++ b/test/units/module_utils/urls/test_generic_urlparse.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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
+
+from ansible.module_utils.urls import generic_urlparse
+from ansible.module_utils.six.moves.urllib.parse import urlparse, urlunparse
+
+
+def test_generic_urlparse():
+ url = 'https://ansible.com/blog'
+ parts = urlparse(url)
+ generic_parts = generic_urlparse(parts)
+ assert generic_parts.as_list() == list(parts)
+
+ assert urlunparse(generic_parts.as_list()) == url
+
+
+def test_generic_urlparse_netloc():
+ url = 'https://ansible.com:443/blog'
+ parts = urlparse(url)
+ generic_parts = generic_urlparse(parts)
+ assert generic_parts.hostname == parts.hostname
+ assert generic_parts.hostname == 'ansible.com'
+ assert generic_parts.port == 443
+ assert urlunparse(generic_parts.as_list()) == url
+
+
+def test_generic_urlparse_no_netloc():
+ url = 'https://user:passwd@ansible.com:443/blog'
+ parts = list(urlparse(url))
+ generic_parts = generic_urlparse(parts)
+ assert generic_parts.hostname == 'ansible.com'
+ assert generic_parts.port == 443
+ assert generic_parts.username == 'user'
+ assert generic_parts.password == 'passwd'
+ assert urlunparse(generic_parts.as_list()) == url
+
+
+def test_generic_urlparse_no_netloc_no_auth():
+ url = 'https://ansible.com:443/blog'
+ parts = list(urlparse(url))
+ generic_parts = generic_urlparse(parts)
+ assert generic_parts.username is None
+ assert generic_parts.password is None
+
+
+def test_generic_urlparse_no_netloc_no_host():
+ url = '/blog'
+ parts = list(urlparse(url))
+ generic_parts = generic_urlparse(parts)
+ assert generic_parts.username is None
+ assert generic_parts.password is None
+ assert generic_parts.port is None
+ assert generic_parts.hostname == ''
diff --git a/test/units/module_utils/urls/test_gzip.py b/test/units/module_utils/urls/test_gzip.py
new file mode 100644
index 0000000..c684032
--- /dev/null
+++ b/test/units/module_utils/urls/test_gzip.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# (c) 2021 Matt Martz <matt@sivel.net>
+# 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 gzip
+import io
+import sys
+
+try:
+ from urllib.response import addinfourl
+except ImportError:
+ from urllib import addinfourl
+
+from ansible.module_utils.six import PY3
+from ansible.module_utils.six.moves import http_client
+from ansible.module_utils.urls import GzipDecodedReader, Request
+
+import pytest
+
+
+def compress(data):
+ buf = io.BytesIO()
+ try:
+ f = gzip.GzipFile(fileobj=buf, mode='wb')
+ f.write(data)
+ finally:
+ f.close()
+ return buf.getvalue()
+
+
+class Sock(io.BytesIO):
+ def makefile(self, *args, **kwds):
+ return self
+
+
+@pytest.fixture
+def urlopen_mock(mocker):
+ return mocker.patch('ansible.module_utils.urls.urllib_request.urlopen')
+
+
+JSON_DATA = b'{"foo": "bar", "baz": "qux", "sandwich": "ham", "tech_level": "pickle", "pop": "corn", "ansible": "awesome"}'
+
+
+RESP = b'''HTTP/1.1 200 OK
+Content-Type: application/json; charset=utf-8
+Set-Cookie: foo
+Set-Cookie: bar
+Content-Length: 108
+
+%s''' % JSON_DATA
+
+GZIP_RESP = b'''HTTP/1.1 200 OK
+Content-Type: application/json; charset=utf-8
+Set-Cookie: foo
+Set-Cookie: bar
+Content-Encoding: gzip
+Content-Length: 100
+
+%s''' % compress(JSON_DATA)
+
+
+def test_Request_open_gzip(urlopen_mock):
+ h = http_client.HTTPResponse(
+ Sock(GZIP_RESP),
+ method='GET',
+ )
+ h.begin()
+
+ if PY3:
+ urlopen_mock.return_value = h
+ else:
+ urlopen_mock.return_value = addinfourl(
+ h.fp,
+ h.msg,
+ 'http://ansible.com/',
+ h.status,
+ )
+ urlopen_mock.return_value.msg = h.reason
+
+ r = Request().open('GET', 'https://ansible.com/')
+ assert isinstance(r.fp, GzipDecodedReader)
+ assert r.read() == JSON_DATA
+
+
+def test_Request_open_not_gzip(urlopen_mock):
+ h = http_client.HTTPResponse(
+ Sock(RESP),
+ method='GET',
+ )
+ h.begin()
+
+ if PY3:
+ urlopen_mock.return_value = h
+ else:
+ urlopen_mock.return_value = addinfourl(
+ h.fp,
+ h.msg,
+ 'http://ansible.com/',
+ h.status,
+ )
+ urlopen_mock.return_value.msg = h.reason
+
+ r = Request().open('GET', 'https://ansible.com/')
+ assert not isinstance(r.fp, GzipDecodedReader)
+ assert r.read() == JSON_DATA
+
+
+def test_Request_open_decompress_false(urlopen_mock):
+ h = http_client.HTTPResponse(
+ Sock(RESP),
+ method='GET',
+ )
+ h.begin()
+
+ if PY3:
+ urlopen_mock.return_value = h
+ else:
+ urlopen_mock.return_value = addinfourl(
+ h.fp,
+ h.msg,
+ 'http://ansible.com/',
+ h.status,
+ )
+ urlopen_mock.return_value.msg = h.reason
+
+ r = Request().open('GET', 'https://ansible.com/', decompress=False)
+ assert not isinstance(r.fp, GzipDecodedReader)
+ assert r.read() == JSON_DATA
+
+
+def test_GzipDecodedReader_no_gzip(monkeypatch, mocker):
+ monkeypatch.delitem(sys.modules, 'gzip')
+ monkeypatch.delitem(sys.modules, 'ansible.module_utils.urls')
+
+ orig_import = __import__
+
+ def _import(*args):
+ if args[0] == 'gzip':
+ raise ImportError
+ return orig_import(*args)
+
+ if PY3:
+ mocker.patch('builtins.__import__', _import)
+ else:
+ mocker.patch('__builtin__.__import__', _import)
+
+ mod = __import__('ansible.module_utils.urls').module_utils.urls
+ assert mod.HAS_GZIP is False
+ pytest.raises(mod.MissingModuleError, mod.GzipDecodedReader, None)
diff --git a/test/units/module_utils/urls/test_prepare_multipart.py b/test/units/module_utils/urls/test_prepare_multipart.py
new file mode 100644
index 0000000..226d9ed
--- /dev/null
+++ b/test/units/module_utils/urls/test_prepare_multipart.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+# (c) 2020 Matt Martz <matt@sivel.net>
+# 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
+
+from io import StringIO
+
+from email.message import Message
+
+import pytest
+
+from ansible.module_utils.urls import prepare_multipart
+
+
+def test_prepare_multipart():
+ fixture_boundary = b'===============3996062709511591449=='
+
+ here = os.path.dirname(__file__)
+ multipart = os.path.join(here, 'fixtures/multipart.txt')
+
+ client_cert = os.path.join(here, 'fixtures/client.pem')
+ client_key = os.path.join(here, 'fixtures/client.key')
+ client_txt = os.path.join(here, 'fixtures/client.txt')
+ fields = {
+ 'form_field_1': 'form_value_1',
+ 'form_field_2': {
+ 'content': 'form_value_2',
+ },
+ 'form_field_3': {
+ 'content': '<html></html>',
+ 'mime_type': 'text/html',
+ },
+ 'form_field_4': {
+ 'content': '{"foo": "bar"}',
+ 'mime_type': 'application/json',
+ },
+ 'file1': {
+ 'content': 'file_content_1',
+ 'filename': 'fake_file1.txt',
+ },
+ 'file2': {
+ 'content': '<html></html>',
+ 'mime_type': 'text/html',
+ 'filename': 'fake_file2.html',
+ },
+ 'file3': {
+ 'content': '{"foo": "bar"}',
+ 'mime_type': 'application/json',
+ 'filename': 'fake_file3.json',
+ },
+ 'file4': {
+ 'filename': client_cert,
+ 'mime_type': 'text/plain',
+ },
+ 'file5': {
+ 'filename': client_key,
+ 'mime_type': 'application/octet-stream'
+ },
+ 'file6': {
+ 'filename': client_txt,
+ },
+ }
+
+ content_type, b_data = prepare_multipart(fields)
+
+ headers = Message()
+ headers['Content-Type'] = content_type
+ assert headers.get_content_type() == 'multipart/form-data'
+ boundary = headers.get_boundary()
+ assert boundary is not None
+
+ with open(multipart, 'rb') as f:
+ b_expected = f.read().replace(fixture_boundary, boundary.encode())
+
+ # Depending on Python version, there may or may not be a trailing newline
+ assert b_data.rstrip(b'\r\n') == b_expected.rstrip(b'\r\n')
+
+
+def test_wrong_type():
+ pytest.raises(TypeError, prepare_multipart, 'foo')
+ pytest.raises(TypeError, prepare_multipart, {'foo': None})
+
+
+def test_empty():
+ pytest.raises(ValueError, prepare_multipart, {'foo': {}})
+
+
+def test_unknown_mime(mocker):
+ fields = {'foo': {'filename': 'foo.boom', 'content': 'foo'}}
+ mocker.patch('mimetypes.guess_type', return_value=(None, None))
+ content_type, b_data = prepare_multipart(fields)
+ assert b'Content-Type: application/octet-stream' in b_data
+
+
+def test_bad_mime(mocker):
+ fields = {'foo': {'filename': 'foo.boom', 'content': 'foo'}}
+ mocker.patch('mimetypes.guess_type', side_effect=TypeError)
+ content_type, b_data = prepare_multipart(fields)
+ assert b'Content-Type: application/octet-stream' in b_data
diff --git a/test/units/module_utils/urls/test_split.py b/test/units/module_utils/urls/test_split.py
new file mode 100644
index 0000000..7fd5fc1
--- /dev/null
+++ b/test/units/module_utils/urls/test_split.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# Copyright: Contributors to the 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 pytest
+
+from ansible.module_utils.urls import _split_multiext
+
+
+@pytest.mark.parametrize(
+ 'name, expected',
+ (
+ ('', ('', '')),
+ ('a', ('a', '')),
+ ('file.tar', ('file', '.tar')),
+ ('file.tar.', ('file.tar.', '')),
+ ('file.hidden', ('file.hidden', '')),
+ ('file.tar.gz', ('file', '.tar.gz')),
+ ('yaml-0.2.5.tar.gz', ('yaml-0.2.5', '.tar.gz')),
+ ('yaml-0.2.5.zip', ('yaml-0.2.5', '.zip')),
+ ('yaml-0.2.5.zip.hidden', ('yaml-0.2.5.zip.hidden', '')),
+ ('geckodriver-v0.26.0-linux64.tar', ('geckodriver-v0.26.0-linux64', '.tar')),
+ ('/var/lib/geckodriver-v0.26.0-linux64.tar', ('/var/lib/geckodriver-v0.26.0-linux64', '.tar')),
+ ('https://acme.com/drivers/geckodriver-v0.26.0-linux64.tar', ('https://acme.com/drivers/geckodriver-v0.26.0-linux64', '.tar')),
+ ('https://acme.com/drivers/geckodriver-v0.26.0-linux64.tar.bz', ('https://acme.com/drivers/geckodriver-v0.26.0-linux64', '.tar.bz')),
+ )
+)
+def test__split_multiext(name, expected):
+ assert expected == _split_multiext(name)
+
+
+@pytest.mark.parametrize(
+ 'args, expected',
+ (
+ (('base-v0.26.0-linux64.tar.gz', 4, 4), ('base-v0.26.0-linux64.tar.gz', '')),
+ (('base-v0.26.0.hidden', 1, 7), ('base-v0.26', '.0.hidden')),
+ (('base-v0.26.0.hidden', 3, 4), ('base-v0.26.0.hidden', '')),
+ (('base-v0.26.0.hidden.tar', 1, 7), ('base-v0.26.0', '.hidden.tar')),
+ (('base-v0.26.0.hidden.tar.gz', 1, 7), ('base-v0.26.0.hidden', '.tar.gz')),
+ (('base-v0.26.0.hidden.tar.gz', 4, 7), ('base-v0.26.0.hidden.tar.gz', '')),
+ )
+)
+def test__split_multiext_min_max(args, expected):
+ assert expected == _split_multiext(*args)
+
+
+@pytest.mark.parametrize(
+ 'kwargs, expected', (
+ (({'name': 'base-v0.25.0.tar.gz', 'count': 1}), ('base-v0.25.0.tar', '.gz')),
+ (({'name': 'base-v0.25.0.tar.gz', 'count': 2}), ('base-v0.25.0', '.tar.gz')),
+ (({'name': 'base-v0.25.0.tar.gz', 'count': 3}), ('base-v0.25.0', '.tar.gz')),
+ (({'name': 'base-v0.25.0.tar.gz', 'count': 4}), ('base-v0.25.0', '.tar.gz')),
+ (({'name': 'base-v0.25.foo.tar.gz', 'count': 3}), ('base-v0.25', '.foo.tar.gz')),
+ (({'name': 'base-v0.25.foo.tar.gz', 'count': 4}), ('base-v0', '.25.foo.tar.gz')),
+ )
+)
+def test__split_multiext_count(kwargs, expected):
+ assert expected == _split_multiext(**kwargs)
+
+
+@pytest.mark.parametrize(
+ 'name',
+ (
+ list(),
+ tuple(),
+ dict(),
+ set(),
+ 1.729879,
+ 247,
+ )
+)
+def test__split_multiext_invalid(name):
+ with pytest.raises((TypeError, AttributeError)):
+ _split_multiext(name)
diff --git a/test/units/module_utils/urls/test_urls.py b/test/units/module_utils/urls/test_urls.py
new file mode 100644
index 0000000..69c1b82
--- /dev/null
+++ b/test/units/module_utils/urls/test_urls.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# (c) 2018 Matt Martz <matt@sivel.net>
+# 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
+
+from ansible.module_utils import urls
+from ansible.module_utils._text import to_native
+
+import pytest
+
+
+def test_build_ssl_validation_error(mocker):
+ mocker.patch.object(urls, 'HAS_SSLCONTEXT', new=False)
+ mocker.patch.object(urls, 'HAS_URLLIB3_PYOPENSSLCONTEXT', new=False)
+ mocker.patch.object(urls, 'HAS_URLLIB3_SSL_WRAP_SOCKET', new=False)
+ with pytest.raises(urls.SSLValidationError) as excinfo:
+ urls.build_ssl_validation_error('hostname', 'port', 'paths', exc=None)
+
+ assert 'python >= 2.7.9' in to_native(excinfo.value)
+ assert 'the python executable used' in to_native(excinfo.value)
+ assert 'urllib3' in to_native(excinfo.value)
+ assert 'python >= 2.6' in to_native(excinfo.value)
+ assert 'validate_certs=False' in to_native(excinfo.value)
+
+ mocker.patch.object(urls, 'HAS_SSLCONTEXT', new=True)
+ with pytest.raises(urls.SSLValidationError) as excinfo:
+ urls.build_ssl_validation_error('hostname', 'port', 'paths', exc=None)
+
+ assert 'validate_certs=False' in to_native(excinfo.value)
+
+ mocker.patch.object(urls, 'HAS_SSLCONTEXT', new=False)
+ mocker.patch.object(urls, 'HAS_URLLIB3_PYOPENSSLCONTEXT', new=True)
+ mocker.patch.object(urls, 'HAS_URLLIB3_SSL_WRAP_SOCKET', new=True)
+
+ mocker.patch.object(urls, 'HAS_SSLCONTEXT', new=True)
+ with pytest.raises(urls.SSLValidationError) as excinfo:
+ urls.build_ssl_validation_error('hostname', 'port', 'paths', exc=None)
+
+ assert 'urllib3' not in to_native(excinfo.value)
+
+ with pytest.raises(urls.SSLValidationError) as excinfo:
+ urls.build_ssl_validation_error('hostname', 'port', 'paths', exc='BOOM')
+
+ assert 'BOOM' in to_native(excinfo.value)
+
+
+def test_maybe_add_ssl_handler(mocker):
+ mocker.patch.object(urls, 'HAS_SSL', new=False)
+ with pytest.raises(urls.NoSSLError):
+ urls.maybe_add_ssl_handler('https://ansible.com/', True)
+
+ mocker.patch.object(urls, 'HAS_SSL', new=True)
+ url = 'https://user:passwd@ansible.com/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == 'ansible.com'
+ assert handler.port == 443
+
+ url = 'https://ansible.com:4433/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == 'ansible.com'
+ assert handler.port == 4433
+
+ url = 'https://user:passwd@ansible.com:4433/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == 'ansible.com'
+ assert handler.port == 4433
+
+ url = 'https://ansible.com/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == 'ansible.com'
+ assert handler.port == 443
+
+ url = 'http://ansible.com/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler is None
+
+ url = 'https://[2a00:16d8:0:7::205]:4443/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == '2a00:16d8:0:7::205'
+ assert handler.port == 4443
+
+ url = 'https://[2a00:16d8:0:7::205]/'
+ handler = urls.maybe_add_ssl_handler(url, True)
+ assert handler.hostname == '2a00:16d8:0:7::205'
+ assert handler.port == 443
+
+
+def test_basic_auth_header():
+ header = urls.basic_auth_header('user', 'passwd')
+ assert header == b'Basic dXNlcjpwYXNzd2Q='
+
+
+def test_ParseResultDottedDict():
+ url = 'https://ansible.com/blog'
+ parts = urls.urlparse(url)
+ dotted_parts = urls.ParseResultDottedDict(parts._asdict())
+ assert parts[0] == dotted_parts.scheme
+
+ assert dotted_parts.as_list() == list(parts)
+
+
+def test_unix_socket_patch_httpconnection_connect(mocker):
+ unix_conn = mocker.patch.object(urls.UnixHTTPConnection, 'connect')
+ conn = urls.httplib.HTTPConnection('ansible.com')
+ with urls.unix_socket_patch_httpconnection_connect():
+ conn.connect()
+ assert unix_conn.call_count == 1