diff options
-rw-r--r-- | tests/__init__.py (renamed from tests/path/to/leaf) | 0 | ||||
-rw-r--r-- | tests/report_tests/__init__.py | 1 | ||||
-rw-r--r-- | tests/report_tests/basic_report_tests.py | 80 | ||||
-rw-r--r-- | tests/report_tests/plugin_tests/__init__.py (renamed from tests/ziptest) | 0 | ||||
-rw-r--r-- | tests/report_tests/plugin_tests/networking.py | 31 | ||||
-rw-r--r-- | tests/report_tests/report_with_mask.py | 69 | ||||
-rwxr-xr-x | tests/simple.sh | 272 | ||||
-rw-r--r-- | tests/sos_tests.py | 490 | ||||
-rw-r--r-- | tests/unittests/__init__.py | 0 | ||||
-rw-r--r-- | tests/unittests/archive_tests.py (renamed from tests/archive_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/cleaner_tests.py (renamed from tests/cleaner_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/importer_tests.py (renamed from tests/importer_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/option_tests.py (renamed from tests/option_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/path/to/leaf | 0 | ||||
-rw-r--r-- | tests/unittests/plugin_tests.py (renamed from tests/plugin_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/policy_tests.py (renamed from tests/policy_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/report_tests.py (renamed from tests/report_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/sosreport_pexpect.py (renamed from tests/sosreport_pexpect.py) | 0 | ||||
-rw-r--r-- | tests/unittests/tail_test.txt (renamed from tests/tail_test.txt) | 0 | ||||
-rw-r--r-- | tests/unittests/test.txt (renamed from tests/test.txt) | 0 | ||||
-rw-r--r-- | tests/unittests/utilities_tests.py (renamed from tests/utilities_tests.py) | 0 | ||||
-rw-r--r-- | tests/unittests/ziptest | 0 | ||||
l--------- | tests/unittests/ziptest_link (renamed from tests/ziptest_link) | 0 | ||||
-rw-r--r-- | tests/vendor_tests/__init__.py | 0 | ||||
-rw-r--r-- | tests/vendor_tests/redhat/__init__.py | 0 | ||||
-rw-r--r-- | tests/vendor_tests/redhat/rhbz1928628.py | 44 |
26 files changed, 715 insertions, 272 deletions
diff --git a/tests/path/to/leaf b/tests/__init__.py index e69de29b..e69de29b 100644 --- a/tests/path/to/leaf +++ b/tests/__init__.py diff --git a/tests/report_tests/__init__.py b/tests/report_tests/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/report_tests/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/report_tests/basic_report_tests.py b/tests/report_tests/basic_report_tests.py new file mode 100644 index 00000000..0cf5bd4e --- /dev/null +++ b/tests/report_tests/basic_report_tests.py @@ -0,0 +1,80 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + + +from sos_tests import StageOneReportTest + + +class NormalSoSReport(StageOneReportTest): + """ + :avocado: tags=stageone + """ + + sos_cmd = '-vvv --label thisismylabel' + + def test_debug_in_logs_verbose(self): + self.assertSosLogContains('DEBUG') + + def test_postproc_called(self): + self.assertSosLogContains('substituting scrpath') + + def test_label_applied_to_archive(self): + self.assertTrue('thisismylabel' in self.archive) + + def test_free_symlink_created(self): + self.assertFileCollected('free') + + +class RestrictedSoSReport(StageOneReportTest): + """ + :avocado: tags=stageone + """ + + sos_cmd = '-o kernel,host,sudo,hardware,dbus,x11 --no-env-var --no-report -t1 --no-postproc' + + def test_no_env_vars_collected(self): + self.assertFileNotCollected('environment') + + def test_no_reports_generated(self): + self.assertFileNotCollected('sos_reports/sos.html') + self.assertFileNotCollected('sos_reports/sos.json') + self.assertFileNotCollected('sos_reports/sos.txt') + + def test_was_single_threaded_run(self): + self.assertOutputNotContains('Finishing plugins') + + def test_postproc_not_called(self): + self.assertOutputNotContains('substituting') + + def test_only_selected_plugins_run(self): + self.assertOnlyPluginsIncluded(['kernel', 'host', 'sudo', 'hardware', 'dbus', 'x11']) + + +class DisabledCollectionsReport(StageOneReportTest): + """ + :avocado: tags=stageone + """ + + sos_cmd = "-n networking,system,logs --skip-files=/etc/fstab --skip-commands='journalctl*'" + + def test_plugins_disabled(self): + self.assertPluginNotIncluded('networking') + self.assertPluginNotIncluded('system') + self.assertPluginNotIncluded('logs') + + def test_skipped_plugins_have_no_dir(self): + self.assertFileNotCollected('sos_commands/networking/') + self.assertFileNotCollected('sos_commands/system/') + self.assertFileNotCollected('sos_commands/logs/') + + def test_skip_files_working(self): + self.assertFileNotCollected('/etc/fstab') + + def test_skip_commands_working(self): + self.assertFileGlobNotInArchive('sos_commands/*/journalctl*') + diff --git a/tests/ziptest b/tests/report_tests/plugin_tests/__init__.py index e69de29b..e69de29b 100644 --- a/tests/ziptest +++ b/tests/report_tests/plugin_tests/__init__.py diff --git a/tests/report_tests/plugin_tests/networking.py b/tests/report_tests/plugin_tests/networking.py new file mode 100644 index 00000000..f6cfa2d8 --- /dev/null +++ b/tests/report_tests/plugin_tests/networking.py @@ -0,0 +1,31 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + + +from sos_tests import StageOneReportTest + + +class NetworkingPluginTest(StageOneReportTest): + """ + Basic tests to ensure proper collection from the networking plugins + + :avocado: tags=stageone + """ + + sos_cmd = '-o networking' + + def test_common_files_collected(self): + self.assertFileCollected('/etc/resolv.conf') + self.assertFileCollected('/etc/hosts') + + def test_ip_addr_symlink_created(self): + self.assertFileCollected('ip_addr') + + def test_forbidden_globs_skipped(self): + self.assertFileGlobNotInArchive('/proc/net/rpc/*/channel') + self.assertFileGlobNotInArchive('/proc/net/rpc/*/flush') diff --git a/tests/report_tests/report_with_mask.py b/tests/report_tests/report_with_mask.py new file mode 100644 index 00000000..a62888ae --- /dev/null +++ b/tests/report_tests/report_with_mask.py @@ -0,0 +1,69 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + +from sos_tests import StageOneReportTest + +import re + + +class ReportWithMask(StageOneReportTest): + """Testing around basic --clean/--mask usage and expectations + + :avocado: tags=stageone + """ + + sos_cmd = '--mask -o host,networking' + + def test_mask_was_run(self): + self.assertOutputContains('Beginning obfuscation') + self.assertOutputContains('Obfuscation completed') + + def test_private_map_was_generated(self): + self.assertOutputContains('A mapping of obfuscated elements is available at') + map_file = re.findall('/.*sosreport-.*-private_map', self.cmd_output.stdout)[-1] + self.assertFileExists(map_file) + + def test_tarball_named_obfuscated(self): + self.assertTrue('obfuscated' in self.archive) + + def test_localhost_was_obfuscated(self): + self.assertFileHasContent('/etc/hostname', 'host0') + + def test_ip_address_was_obfuscated(self): + # Note: do not test for starting with the 100.* block here, as test + # machines may have /32 addresses. Instead, test that the actual + # IP address is not present + self.assertFileNotHasContent('ip_addr', self.sysinfo['pre']['networking']['ip_addr']) + + def test_loopback_was_not_obfuscated(self): + self.assertFileHasContent('ip_addr', '127.0.0.1/8') + + def test_mac_addrs_were_obfuscated(self): + content = self.get_file_content('sos_commands/networking/ip_maddr_show') + for line in content.splitlines(): + if line.strip().startswith('link'): + mac = line.strip().split()[1] + assert mac.startswith('53:4f:53'), "Found unobfuscated mac addr %s" % mac + + +class ReportWithCleanedKeywords(StageOneReportTest): + """Testing for obfuscated keywords provided by the user + + :avocado: tags=stageone + """ + + sos_cmd = '--clean -o filesys,kernel --keywords=fstab,Linux' + + # Ok, sort of cheesy here but this does actually test filename changes on + # a file common to all distros + def test_filename_obfuscated(self): + self.assertFileNotCollected('/etc/fstab') + self.assertFileGlobInArchive('/etc/obfuscatedword*') + + def test_keyword_obfuscated_in_file(self): + self.assertFileNotHasContent('sos_commands/kernel/uname_-a', 'Linux') diff --git a/tests/simple.sh b/tests/simple.sh deleted file mode 100755 index 3eb123c1..00000000 --- a/tests/simple.sh +++ /dev/null @@ -1,272 +0,0 @@ -#!/bin/bash -# This file is part of the sos project: https://github.com/sosreport/sos -# -# This copyrighted material is made available to anyone wishing to use, -# modify, copy, or redistribute it subject to the terms and conditions of -# version 2 of the GNU General Public License. -# -# See the LICENSE file in the source distribution for further information. -# A quick port of the travis tests to bash, requires root -# TODO -# * look into using a framework.. -# * why --dry-run fails? -# * why --experimental fails? -# * make it better validate archives and contents - -PYTHON=${1:-/usr/bin/python3} -SOSPATH=${2:-./bin/sos report --batch --tmp-dir=/var/tmp } - -NUMOFFAILURES=0 -summary="\nSummary\n" -FAIL_LIST="" - -run_expecting_success () { - #$1 - is command options - #$2 - kind of check to do, so far only extract - FAIL=false - # Make sure clean - rm -f /dev/shm/stderr /dev/shm/stdout /var/tmp/sosreport*.tar.* - rm -rf /var/tmp/sosreport_test/ - - start=`date +%s` - echo "######### RUNNING $1 #########" - $PYTHON $SOSPATH $1 2> /dev/shm/stderr 1> /dev/shm/stdout - - if [ $? -eq 0 ]; then - echo "### Success" - else - echo "!!! FAILED !!!" - add_failure "$1 failed during execution" - fi - - end=`date +%s` - runtime=$((end-start)) - echo "#### Sos Total time (seconds):" $runtime - - if [ -s /dev/shm/stderr ]; then - add_failure "test generated stderr output, see above" - echo "### start stderr" - cat /dev/shm/stderr - echo "### end stderr" - fi - - echo "### start stdout" - cat /dev/shm/stdout - echo "### end stdout" - - if [ "extract" = "$2" ]; then - echo "### start extraction" - rm -f /var/tmp/sosreport*sha256 - mkdir /var/tmp/sosreport_test/ - tar xfa /var/tmp/sosreport*.tar* -C /var/tmp/sosreport_test --strip-components=1 - if [ -s /var/tmp/sosreport_test/sos_logs/*errors.txt ]; then - FAIL=true - echo "!!! FAILED !!!" - add_failure "Test $1 generated errors" - echo "#### *errors.txt output" - ls -alh /var/tmp/sosreport_test/sos_logs/ - cat /var/tmp/sosreport_test/sos_logs/*errors.txt - fi - echo "### stop extraction" - fi - - echo "######### DONE WITH $1 #########" - - if $FAIL; then - NUMOFFAILURES=$(($NUMOFFAILURES + 1)) - return 1 - else - return 0 - fi -} - -update_summary () { - size="$(grep Size /dev/shm/stdout)" - size="$(echo "${size:-"Size 0.00MiB"}")" - summary="${summary} \n failures ${FAIL} \t time ${runtime} \t ${size} \t ${1} " -} - -update_failures () { - if $FAIL; then - NUMOFFAILURES=$(($NUMOFFAILURES + 1)) - fi -} - -add_failure () { - FAIL=true - echo "!!! TEST FAILED: $1 !!!" - FAIL_LIST="${FAIL_LIST}\n \t ${FUNCNAME[1]}: \t\t ${1}" -} - -# Test a no frills run with verbosity and make sure the expected items exist -test_normal_report () { - cmd="-vvv" - # get a list of initial kmods loaded - kmods=( $(lsmod | cut -f1 -d ' ' | sort) ) - run_expecting_success "$cmd" extract - if [ $? -eq 0 ]; then - if [ ! -f /var/tmp/sosreport_test/sos_reports/sos.html ]; then - add_failure "did not generate html reports" - fi - if [ ! -f /var/tmp/sosreport_test/sos_reports/manifest.json ]; then - add_failure "did not generate manifest.json" - fi - if [ ! -f /var/tmp/sosreport_test/free ]; then - add_failure "did not create free symlink in archive root" - fi - if [ ! "$(grep "DEBUG" /var/tmp/sosreport_test/sos_logs/sos.log)" ]; then - add_failure "did not find debug logging when using -vvv" - fi - # new list, see if we added any - new_kmods=( $(lsmod | cut -f1 -d ' ' | sort) ) - if [ "$(printf '%s\n' "${kmods[@]}" "${new_kmods[@]}" | sort | uniq -u)" ]; then - add_failure "new kernel modules loaded during execution" - echo "$(printf '%s\n' "${kmods[@]}" "${new_kmods[@]}" | sort | uniq -u)" - fi - update_failures - update_summary "$cmd" - fi -} - -# Test for correctly skipping html generation, and label setting -test_noreport_label_only () { - cmd="--no-report --label TEST -o hardware" - run_expecting_success "$cmd" extract - if [ $? -eq 0 ]; then - if [ -f /var/tmp/sosreport_test/sos_reports/sos.html ]; then - add_failure "html report generated when --no-report used" - fi - if [ ! $(grep /var/tmp/sosreport-*TEST* /dev/shm/stdout) ]; then - add_failure "no label set on archive" - fi - count=$(find /var/tmp/sosreport_test/sos_commands/* -type d | wc -l) - if [[ "$count" -gt 1 ]]; then - add_failure "more than one plugin ran when using -o hardware" - fi - update_failures - fi - update_summary "$cmd" -} - -# test using mask -test_mask () { - cmd="--mask" - run_expecting_success "$cmd" extract - if [ $? -eq 0 ]; then - if [ ! $(grep host0 /var/tmp/sosreport_test/hostname) ]; then - add_failure "hostname not obfuscated with --mask" - fi - # we don't yet support binary obfuscation, so skip binary matches - if [ "$(grep -rI `hostname` /var/tmp/sosreport_test/*)" ]; then - add_failure "hostname not obfuscated in all places" - echo "$(grep -rI `hostname` /var/tmp/sosreport_test/*)" - fi - # only tests first interface - mac_addr=$(cat /sys/class/net/$(ip route show default | awk '/default/ {print $5}')/address) - if [ "$(grep -rI $mac_addr /var/tmp/sosreport_test/*)" ]; then - add_failure "MAC address not obfuscated in all places" - echo "$(grep -rI $mac_addr /var/tmp/sosreport_test/*)" - fi - # only tests first interface - ip_addr=$(ip route show default | awk '/default/ {print $3}') - if [ "$(grep -rI $ip_addr /var/tmp/sosreport_test/*)" ]; then - add_failure "IP address not obfuscated in all places" - echo "$(grep -rI $ip_addr /var/tmp/sosreport_test/*)" - fi - update_failures - fi - update_summary "$cmd" -} - -# test log-size, env vars, and compression type -test_logsize_env_gzip () { - cmd="--log-size 0 --no-env-vars -z gzip" - run_expecting_success "$cmd" extract - if [ $? -eq 0 ]; then - if [ -f /var/tmp/sosreport_test/environment ]; then - add_failure "env vars captured when using --no-env-vars" - fi - if [ ! $(grep /var/tmp/sosreport*.gz /dev/shm/stdout) ]; then - add_failure "archive was not gzip compressed using -z gzip" - fi - update_failures - fi - update_summary "$cmd" -} - -# test plugin enablement, plugopts and at the same time ensure our list option parsing is working -test_enable_opts_postproc () { - cmd="-e opencl -v -k kernel.with-timer,libraries.ldconfigv --no-postproc" - run_expecting_success "$cmd" extract - if [ $? -eq 0 ]; then - if [ ! "$(grep "opencl" /dev/shm/stdout)" ]; then - add_failure "force enabled plugin opencl did not run" - fi - if [ ! -f /var/tmp/sosreport_test/proc/timer* ]; then - add_failure "/proc/timer* not captured when using -k kernel.with-timer" - fi - if [ ! -f /var/tmp/sosreport_test/sos_commands/libraries/ldconfig_-v* ]; then - add_failure "ldconfig -v not captured when using -k libraries.ldconfigv" - fi - if [ "$(grep "substituting" /var/tmp/sosreport_test/sos_logs/sos.log)" ]; then - add_failure "post-processing ran while using --no-post-proc" - fi - - update_failures - update_summary "$cmd" - fi -} - -# test if --build and --threads work properly -test_build_threads () { - cmd="--build -t1 -o host,kernel,filesys,hardware,date,logs" - run_expecting_success "$cmd" - if [ $? -eq 0 ]; then - if [ ! "$(grep "Your sosreport build tree" /dev/shm/stdout)" ]; then - add_failure "did not save the build tree" - fi - if [ $(grep "Finishing plugins" /dev/shm/stdout) ]; then - add_failure "did not limit threads when using --threads 1" - fi - update_failures - update_summary "$cmd" - fi -} - -# If /etc/sos/sos.conf doesn't exist let's just make it -if [ -f /etc/sos/sos.conf ]; then - echo "/etc/sos/sos.conf already exists" -else - echo "Creating /etc/sos.conf" - mkdir /etc/sos - touch /etc/sos/sos.conf -fi - - -# Runs not generating sosreports -run_expecting_success " -l"; update_summary "List plugins" -run_expecting_success " --list-presets"; update_summary "List presets" -run_expecting_success " --list-profiles"; update_summary "List profiles" - -# Runs generating sosreports -# TODO: -# - find a way to test if --since is working -test_build_threads -test_normal_report -test_enable_opts_postproc -test_noreport_label_only -test_logsize_env_gzip -test_mask - -echo -e $summary - -if [ $NUMOFFAILURES -gt 0 ]; then - echo -e "\nTests Failed: $NUMOFFAILURES\nFailures within each test:" - echo -e $FAIL_LIST - exit 1 -else - echo "Everything worked!" - exit 0 -fi - -# vim: set et ts=4 sw=4 : diff --git a/tests/sos_tests.py b/tests/sos_tests.py new file mode 100644 index 00000000..8da0195d --- /dev/null +++ b/tests/sos_tests.py @@ -0,0 +1,490 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + + +from avocado.core.exceptions import TestSkipError +from avocado import Test +from avocado.utils import archive, process +from fnmatch import fnmatch + +import glob +import json +import os +import pickle +import socket +import re + +SOS_TEST_DIR = os.path.dirname(os.path.realpath(__file__)) +SOS_BIN = os.path.realpath(os.path.join(SOS_TEST_DIR, '../bin/sos')) + + +def skipIf(cond, message=None): + def decorator(function): + def wrapper(self, *args, **kwargs): + if callable(cond): + if cond(self): + raise TestSkipError(message) + elif cond: + raise TestSkipError(message) + return wrapper + return decorator + + +class BaseSoSTest(Test): + """Base class for all our test classes to build off of. + + Subclasses avocado.Test and then adds wrappers and helper methods that are + needed across sos components. Component specific test classes should in + turn subclass ``BaseSoSTest`` rather than ``avocado.Test`` directly + """ + + _klass_name = None + _tmpdir = None + sos_cmd = '' + + @property + def klass_name(self): + if not self._klass_name: + self._klass_name = os.path.basename(__file__) + '.' + self.__class__.__name__ + return self._klass_name + + @property + def tmpdir(self): + if not self._tmpdir: + self._tmpdir = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR') + self.klass_name + return self._tmpdir + + def generate_sysinfo(self): + """Collects some basic information about the system for later reference + in individual tests + """ + sysinfo = {} + + # get kernel modules + mods = [] + _out = process.run('lsmod').stdout.decode() + for line in _out.splitlines()[1:]: + mods.append(line.split()[0]) + # this particular kmod is both innocuous and unpredictable in terms of + # pre-loading even within the same distribution. For now, turn a blind + # eye to it with regards to the "no new kmods loaded" perspective + if 'binfmt_misc' in mods: + mods.remove('binfmt_misc') + sysinfo['modules'] = sorted(mods, key=str.lower) + + # get networking info + hostname = socket.gethostname() + ip_addr = socket.gethostbyname(hostname) + sysinfo['networking'] = {} + sysinfo['networking']['hostname'] = hostname + sysinfo['networking']['ip_addr'] = ip_addr + + return sysinfo + + def _write_file_to_tmpdir(self, fname, content): + """Write the given content to fname within the test's tmpdir + """ + fname = os.path.join(self.tmpdir, fname) + if isinstance(content, bytes): + content = content.decode() + with open(fname, 'w') as wfile: + wfile.write(content) + + def read_file_from_tmpdir(self, fname): + fname = os.path.join(self.tmpdir, fname) + with open(fname, 'r') as tfile: + return tfile.read() + return '' + + def _write_sysinfo(self, fname): + """Get the current state of sysinfo and write it into our shared + tempdir so it can be loaded in setUp() later + + :param fname: The name of the file to be written in the tempdir + :type fname: ``str`` + """ + sysinfo = self.generate_sysinfo() + self._write_file_to_tmpdir(fname, json.dumps(sysinfo)) + + def _read_sysinfo(self, fname): + sysinfo = {} + content = self.read_file_from_tmpdir(fname) + if content: + sysinfo = json.loads(content) + return sysinfo + + def set_pre_sysinfo(self): + self._write_sysinfo('pre_sysinfo') + + def get_pre_sysinfo(self): + return self._read_sysinfo('pre_sysinfo') + + def set_post_sysinfo(self): + self._write_sysinfo('post_sysinfo') + + def get_post_sysinfo(self): + return self._read_sysinfo('post_sysinfo') + + def get_sysinfo(self): + sinfo = { + 'pre': self.get_pre_sysinfo(), + 'post': self.get_post_sysinfo() + } + return sinfo + + def assertFileExists(self, fname): + """Asserts that fname exists on the filesystem""" + assert os.path.exists(fname), "%s does not exist" % fname + + def assertFileNotExists(self, fname): + """Asserts that fname does not exist on the filesystem""" + assert not os.path.exists(fname), "%s exists" % fname + + +class BaseSoSReportTest(BaseSoSTest): + """This is the class to use for building sos report tests with. + + An instance of this test is expected to set at minimum a ``sos_cmd`` class + attribute that represets the options handed to a specific execution of an + sos command. This should be anything following ``sos report --batch``. + + """ + + archive = None + _manifest = None + + + @property + def manifest(self): + if self._manifest is None: + try: + content = self.read_file_from_tmpdir(self.get_name_in_archive('sos_reports/manifest.json')) + self._manifest = json.loads(content) + except Exception: + self._manifest = '' + self.warn('Could not load manifest for test') + return self._manifest + + def _extract_archive(self, arc_path): + """Extract an archive to the temp directory + """ + _extract_path = self._get_extracted_tarball_path() + try: + archive.extract(arc_path, _extract_path) + self.archive_path = self._get_archive_path() + except Exception as err: + self.cancel("Could not extract archive: %s" % err) + + def _get_extracted_tarball_path(self): + """Based on the klass id setup earlier, provide a name to extract the + archive to within the tmpdir + """ + return os.path.join(self.tmpdir, "sosreport-%s" % self.__class__.__name__) + + + def _execute_sos_cmd(self): + """Run the sos command for this test case, and extract it + """ + _cmd = '%s report --batch --tmp-dir %s %s' + exec_cmd = _cmd % (SOS_BIN, self.tmpdir, self.sos_cmd) + self.cmd_output = process.run(exec_cmd, timeout=300) + with open(os.path.join(self.tmpdir, 'output'), 'wb') as pfile: + pickle.dump(self.cmd_output, pfile) + self.cmd_output.stdout = self.cmd_output.stdout.decode() + self.cmd_output.stderr = self.cmd_output.stderr.decode() + self.archive = re.findall('/.*sosreport-.*tar.*', self.cmd_output.stdout)[-1] + if self.archive: + self._extract_archive(self.archive) + + + def _setup_tmpdir(self): + if not os.path.isdir(self.tmpdir): + os.mkdir(self.tmpdir) + + def _get_archive_path(self): + return glob.glob(self._get_extracted_tarball_path() + '/sosreport*')[0] + + def setup_mocking(self): + """Since we need to use setUp() in our overrides of avocado.Test, + provide an alternate method for test cases that subclass BaseSoSTest + to use. + """ + pass + + def setUp(self): + """Execute and extract the sos report to our temporary location, then + call sos_setup() for individual test case setup and/or mocking. + """ + # check to prevent multiple setUp() runs + if not os.path.isdir(self.tmpdir): + # setup our class-shared tmpdir + self._setup_tmpdir() + + # do our mocking called for in sos_setup + self.setup_mocking() + + # gather some pre-execution information + self.set_pre_sysinfo() + + # run the sos command for this test case + self._execute_sos_cmd() + self.set_post_sysinfo() + else: + with open(os.path.join(self.tmpdir, 'output'), 'rb') as pfile: + self.cmd_output = pickle.load(pfile) + if isinstance(self.cmd_output.stdout, bytes): + self.cmd_output.stdout = self.cmd_output.stdout.decode() + self.cmd_output.stderr = self.cmd_output.stderr.decode() + for f in os.listdir(self.tmpdir): + if fnmatch(f, 'sosreport*.tar.??'): + self.archive = os.path.join(self.tmpdir, f) + break + self.sysinfo = self.get_sysinfo() + self.archive_path = self._get_archive_path() + + def get_name_in_archive(self, fname): + """Get the full path to fname as it (would) exist in the archive + """ + return os.path.join(self.archive_path, fname.lstrip('/')) + + def get_file_content(self, fname): + """Reads the content of fname from within the archive and returns it + + :param fname: The name of the file + :type fname: ``str`` + + :returns: Content of fname + :rtype: ``str`` + """ + content = '' + with open(self.get_name_in_archive(fname), 'r') as gfile: + content = gfile.read() + return content + + def assertFileCollected(self, fname): + """Ensure that a given fname is in the extracted archive if it exists + on the host system + + :param fname: The name of the file within the archive + :type fname: ``str`` + """ + if os.path.exists(fname): + self.assertFileExists(self.get_name_in_archive(fname)) + assert True + + def assertFileNotCollected(self, fname): + """Ensure that a given fname is NOT in the extracted archive + + :param fname: The name of the file within the archive + :type fname: ``str`` + """ + self.assertFileNotExists(self.get_name_in_archive(fname)) + + def assertFileGlobInArchive(self, fname): + """Ensure that at least one file in the archive matches a given fname + glob + + :param fname: The glob to match filenames of + :type fname: ``str`` + """ + files = glob.glob(os.path.join(self.archive_path, fname.lstrip('/'))) + assert files, "No files matching %s found" % fname + + def assertFileGlobNotInArchive(self, fname): + """Ensure that there are NO files in the archive matching a given fname + glob + + :param fname: The glob to match filename(s) of + :type fname: ``str`` + """ + files = glob.glob(os.path.join(self.tmpdir, fname.lstrip('/'))) + self.log.debug(files) + assert not files, "Found files in archive matching %s: %s" % (fname, files) + + def assertFileHasContent(self, fname, content): + """Ensure that the given file fname contains the given content + + :param fname: The name of the file + :type fname: ``str`` + + :param content: The content to match + :type content: ``str`` + """ + matched = False + fname = self.get_name_in_archive(fname) + self.assertFileExists(fname) + with open(fname, 'r') as lfile: + _contents = lfile.read() + for line in _contents.splitlines(): + if re.match(".*%s.*" % content, line, re.I): + matched = True + break + assert matched, "Content '%s' does not appear in %s\n%s" % (content, fname, _contents) + + def assertFileNotHasContent(self, fname, content): + """Ensure that the file file fname does NOT contain the given content + + :param fname: The name of the file + :type fname: ``str`` + + :param content: The content to (not) match + :type content: ``str`` + """ + matched = False + fname = self.get_name_in_archive(fname) + with open(fname, 'r') as mfile: + for line in mfile.read().splitlines(): + if re.match(".*%s.*" % content, line, re.I): + matched = True + break + assert not matched, "Content '%s' appears in file %s" % (content, fname) + + def assertSosLogContains(self, content): + """Ensure that the given content string exists in sos.log + """ + self.assertFileHasContent('sos_logs/sos.log', content) + + def assertSosLogNotContains(self, content): + """Ensure that the given content string does NOT exist in sos.log + """ + self.assertFileNotHasContent('sos_logs/sos.log', content) + + def assertOutputContains(self, content): + """Ensure that stdout did contain the given content string + + :param content: The string that should not be in stdout + :type content: ``str`` + """ + assert content in self.cmd_output.stdout, 'Content string not in output' + + def assertOutputNotContains(self, content): + """Ensure that stdout did NOT contain the given content string + + :param content: The string that should not be in stdout + :type content: ``str`` + """ + assert not re.match(".*%s.*" % content, self.cmd_output.stdout), "String '%s' present in stdout" % content + + def assertPluginIncluded(self, plugin): + """Ensure that the specified plugin did run for the sos execution + + Note that this relies on manifest.json being successfully created + + :param plugin: The name of the plugin + :type plugin: `` str`` + """ + if not self.manifest: + self.error("No manifest found, cannot check for %s execution" % plugin) + assert plugin in self.manifest['components']['report']['plugins'].keys(), 'Plugin not recorded in manifest' + + def assertPluginNotIncluded(self, plugin): + """Ensure that the specified plugin did NOT run for the sos execution + Note that this relies on manifest.json being successfully created + + :param plugin: The name of the plugin + :type plugin: `` str`` + """ + if not self.manifest: + self.error("No manifest found, cannot check for %s execution" % plugin) + assert plugin not in self.manifest['components']['report']['plugins'].keys(), 'Plugin is recorded in manifest' + + def assertOnlyPluginsIncluded(self, plugins): + """Ensure that only the specified plugins are in the manifest + + :param plugins: The plugin names + :type plugins: ``str`` or ``list`` of strings + """ + if not self.manifest: + self.error("No manifest found, cannot check for %s execution" % plugins) + if isinstance(plugins, str): + plugins = [plugins] + _executed = self.manifest['components']['report']['plugins'].keys() + + # test that all requested plugins did run + for i in plugins: + assert i in _executed, "Requested plugin '%s' did not run" % i + + # test that no unrequested plugins ran + for j in _executed: + assert j in plugins, "Unrequested plugin '%s' ran as well" % j + +class StageOneReportTest(BaseSoSReportTest): + """This is the test class to subclass for all Stage One (no mocking) tests + within the sos test suite. + + In addition to any test_* methods defined in the test cases that subclass + this, the methods defined here will ALSO run, to ensure basic consistency + across test cases + + NOTE: You MUST replace the following line in the docstring of your own + test cases, as otherwise the test will be disabled. This line is here to + prevent this base class from being treated as a valid test case. Also, if + you add any tests to this base class, make sure to add a line such as + ':avocado: tags=stageone' to ensure the base tests run with new test cases + + :avocado: disable + :avocado: tags=stageone + """ + + sos_cmd = '' + + def test_archive_created(self): + """Ensure that the archive tarball was created and has the right owner + + :avocado: tags=stageone + """ + self.assertFileExists(self.archive) + self.assertTrue(os.stat(self.archive).st_uid == 0) + + def test_no_new_kmods_loaded(self): + """Ensure that no additional kernel modules have been loaded during an + execution of a test + + :avocado: tags=stageone + """ + self.assertCountEqual(self.sysinfo['pre']['modules'], + self.sysinfo['post']['modules']) + + def test_archive_has_sos_dirs(self): + """Ensure that we have the expected directory layout with in the + archive + + :avocado: tags=stageone + """ + self.assertFileCollected('sos_commands') + self.assertFileCollected('sos_logs') + + def test_manifest_created(self): + """ + :avocado: tags=stageone + """ + self.assertFileCollected('sos_reports/manifest.json') + + @skipIf(lambda x: '--no-report' in x.sos_cmd, '--no-report used in command') + def test_html_reports_created(self): + """ + :avocado: tags=stageone + """ + self.assertFileCollected('sos_reports/sos.html') + + def test_no_exceptions_during_execution(self): + """ + :avocado: tags=stageone + """ + self.assertSosLogNotContains('caught exception in plugin') + self.assertFileGlobNotInArchive('sos_logs/*-plugin-errors.txt') + + def test_no_ip_changes(self): + """ + :avocado: tags=stageone + """ + # I.E. make sure we didn't cause any NIC flaps that for some reason + # resulted in a new primary IP address. TODO: build this out to make + # sure this IP is still bound to the same NIC + self.assertEqual(self.sysinfo['pre']['networking']['ip_addr'], + self.sysinfo['post']['networking']['ip_addr']) diff --git a/tests/unittests/__init__.py b/tests/unittests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/unittests/__init__.py diff --git a/tests/archive_tests.py b/tests/unittests/archive_tests.py index 320006d0..320006d0 100644 --- a/tests/archive_tests.py +++ b/tests/unittests/archive_tests.py diff --git a/tests/cleaner_tests.py b/tests/unittests/cleaner_tests.py index 5510dd80..5510dd80 100644 --- a/tests/cleaner_tests.py +++ b/tests/unittests/cleaner_tests.py diff --git a/tests/importer_tests.py b/tests/unittests/importer_tests.py index a2dddaba..a2dddaba 100644 --- a/tests/importer_tests.py +++ b/tests/unittests/importer_tests.py diff --git a/tests/option_tests.py b/tests/unittests/option_tests.py index 58f54e94..58f54e94 100644 --- a/tests/option_tests.py +++ b/tests/unittests/option_tests.py diff --git a/tests/unittests/path/to/leaf b/tests/unittests/path/to/leaf new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/unittests/path/to/leaf diff --git a/tests/plugin_tests.py b/tests/unittests/plugin_tests.py index 2f362c94..2f362c94 100644 --- a/tests/plugin_tests.py +++ b/tests/unittests/plugin_tests.py diff --git a/tests/policy_tests.py b/tests/unittests/policy_tests.py index 6d0c42b9..6d0c42b9 100644 --- a/tests/policy_tests.py +++ b/tests/unittests/policy_tests.py diff --git a/tests/report_tests.py b/tests/unittests/report_tests.py index bb059012..bb059012 100644 --- a/tests/report_tests.py +++ b/tests/unittests/report_tests.py diff --git a/tests/sosreport_pexpect.py b/tests/unittests/sosreport_pexpect.py index 3614fa5b..3614fa5b 100644 --- a/tests/sosreport_pexpect.py +++ b/tests/unittests/sosreport_pexpect.py diff --git a/tests/tail_test.txt b/tests/unittests/tail_test.txt index 8def0f72..8def0f72 100644 --- a/tests/tail_test.txt +++ b/tests/unittests/tail_test.txt diff --git a/tests/test.txt b/tests/unittests/test.txt index d95f3ad1..d95f3ad1 100644 --- a/tests/test.txt +++ b/tests/unittests/test.txt diff --git a/tests/utilities_tests.py b/tests/unittests/utilities_tests.py index 64be9f1e..64be9f1e 100644 --- a/tests/utilities_tests.py +++ b/tests/unittests/utilities_tests.py diff --git a/tests/unittests/ziptest b/tests/unittests/ziptest new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/unittests/ziptest diff --git a/tests/ziptest_link b/tests/unittests/ziptest_link index e99bb13c..e99bb13c 120000 --- a/tests/ziptest_link +++ b/tests/unittests/ziptest_link diff --git a/tests/vendor_tests/__init__.py b/tests/vendor_tests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/vendor_tests/__init__.py diff --git a/tests/vendor_tests/redhat/__init__.py b/tests/vendor_tests/redhat/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/vendor_tests/redhat/__init__.py diff --git a/tests/vendor_tests/redhat/rhbz1928628.py b/tests/vendor_tests/redhat/rhbz1928628.py new file mode 100644 index 00000000..00746658 --- /dev/null +++ b/tests/vendor_tests/redhat/rhbz1928628.py @@ -0,0 +1,44 @@ +# This file is part of the sos project: https://github.com/sosreport/sos +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# version 2 of the GNU General Public License. +# +# See the LICENSE file in the source distribution for further information. + + +from report_tests.plugin_tests.networking import NetworkingPluginTest + + +class rhbz1928628(NetworkingPluginTest): + """ + Only collect an eeprom dump when requested, as otherwise this can cause + NIC flaps. + + https://bugzilla.redhat.com/show_bug.cgi?id=1928628 + + :avocado: enable + :avocado: tags=stageone + """ + + sos_cmd = '-o networking' + + def test_eeprom_dump_not_collected(self): + self.assertFileGlobNotInArchive('sos_commands/networking/ethtool_-e*') + + +class rhbz1928628Enabled(NetworkingPluginTest): + """ + Enable the option to perform eeprom collection. + + WARNING: it has been noted (via this rhbz) that certain NICs may pause + during this collection + + :avocado: enable + :avocado: tags=stageone + """ + + sos_cmd = '-o networking -k networking.eepromdump=on' + + def test_eeprom_dump_collected(self): + self.assertFileGlobInArchive('sos_commands/networking/ethtool_-e*') |