diff options
author | Jake Hunsaker <jhunsake@redhat.com> | 2021-03-24 17:40:49 -0400 |
---|---|---|
committer | Jake Hunsaker <jhunsake@redhat.com> | 2021-04-15 11:33:03 -0400 |
commit | e753d9c35054cc5e0430a5e739036e5f88635b15 (patch) | |
tree | 2d37de1f8e1c4c989d9154bff30edb595ef845b1 /tests/sos_tests.py | |
parent | 3bb56538d1f9734ad09d88751256fd1b1301f471 (diff) | |
download | sos-e753d9c35054cc5e0430a5e739036e5f88635b15.tar.gz |
[tests] Add StageTwoReportTest as a base test class
This commits adds a `StageTwoReportTest` from which tests that need to
do "light" mocking should be defined. As the name implies, this is the
base class for our Stage 2 tests.
"Light" mocking for our purposes is currently defined as dropping files
in locations on the test system's filesystem, which may include
replacing binaries, and installing packages for the durtation of the
test case. At the end of the test, packages are uninstalled and mocked
files are removed. In the event that a file already exists at a given
location on the test system, that file is temporarily renamed with a
'.sostesting' extension. On test clean up, once mocked files are
removed, any files renamed previously are moved back to their original
location/name.
Mocking is controlled automatically during test setup, and is based upon
test class attributes, similar to how plugin enablement is dependent
upon plugin class attributes. For Stage 2 tests cases, this includes:
`files` - a list of file names. These names should represent the
"real" location on the filesystem where the mocked
files will be placed. The mocked content should be
placed under a similar file path under tests/test_data
E.G. using `files = ['/etc/foo/bar']` will place a
copy of `tests/test_data/etc/foo/bar` under
`/etc/foo/bar` on the test system.
`packages` - a dict of packages to install. The dict keys map to
the distribution the package names are used for,
e.g. `rhel` or `ubuntu`. The values are lists of
package names, optionally with version included.
This commit includes just the framework for these tests, and no actual
Stage 2 tests are included as of yet.
Related: #2431
Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
Diffstat (limited to 'tests/sos_tests.py')
-rw-r--r-- | tests/sos_tests.py | 218 |
1 files changed, 214 insertions, 4 deletions
diff --git a/tests/sos_tests.py b/tests/sos_tests.py index 8da0195d..72d6c244 100644 --- a/tests/sos_tests.py +++ b/tests/sos_tests.py @@ -9,17 +9,21 @@ from avocado.core.exceptions import TestSkipError from avocado import Test -from avocado.utils import archive, process +from avocado.utils import archive, process, distro, software_manager from fnmatch import fnmatch import glob import json import os import pickle +import shutil import socket import re SOS_TEST_DIR = os.path.dirname(os.path.realpath(__file__)) +SOS_REPO_ROOT = os.path.realpath(os.path.join(SOS_TEST_DIR, '../')) +SOS_PLUGIN_DIR = os.path.realpath(os.path.join(SOS_REPO_ROOT, 'sos/report/plugins')) +SOS_TEST_DATA_DIR = os.path.realpath(os.path.join(SOS_TEST_DIR, 'test_data')) SOS_BIN = os.path.realpath(os.path.join(SOS_TEST_DIR, '../bin/sos')) @@ -97,8 +101,11 @@ class BaseSoSTest(Test): def read_file_from_tmpdir(self, fname): fname = os.path.join(self.tmpdir, fname) - with open(fname, 'r') as tfile: - return tfile.read() + try: + with open(fname, 'r') as tfile: + return tfile.read() + except Exception: + pass return '' def _write_sysinfo(self, fname): @@ -225,7 +232,7 @@ class BaseSoSReportTest(BaseSoSTest): # setup our class-shared tmpdir self._setup_tmpdir() - # do our mocking called for in sos_setup + # do mocking called for in stage 2+ tests self.setup_mocking() # gather some pre-execution information @@ -354,6 +361,16 @@ class BaseSoSReportTest(BaseSoSTest): """ self.assertFileNotHasContent('sos_logs/sos.log', content) + def assertSosUILogContains(self, content): + """Ensure that the given content string exists in ui.log + """ + self.assertFileHasContent('sos_logs/ui.log', content) + + def assertSosUILogNotContains(self, content): + """Ensure that the given content string does NOT exist in ui.log + """ + self.assertFileNotHasContent('sos_logs/ui.log', content) + def assertOutputContains(self, content): """Ensure that stdout did contain the given content string @@ -488,3 +505,196 @@ class StageOneReportTest(BaseSoSReportTest): # sure this IP is still bound to the same NIC self.assertEqual(self.sysinfo['pre']['networking']['ip_addr'], self.sysinfo['post']['networking']['ip_addr']) + + +class StageTwoReportTest(BaseSoSReportTest): + """This is the testing class to subclass when light mocking is needed to + perform the test. + + Light mocking for our uses is restricted to dropping files in well-known + locations, temporarily replacing binaries, and installing packages. + + Note: Stage 2 tests should NOT be run on any system that is considered + either production, or is a workstation that cannot be easily re-imaged or + re-deployed. While efforts are taken to ensure that systems are left in + their original state after mocking tests are done, the assumption is that + these tests are being run on "throw-away" test systems where it does not + matter if that original state is indeed attained or not. + + This kind of mocking is described in the class attributes as follows for + each test case that is a Stage 2 test: + + files - a list containing the files to drop on the test system's real + filesystem. Mocked files should be placed in the same locations + under tests/test_data + + packages - a dict where the keys are the distribution names (e.g. 'rhel', + 'ubuntu') and the values are the package names optionally with + version + + install_plugins - a list containing the names of test plugins to be dropped + inside the test repo for testing specific use cases. + The list values are strings that match the test plugin's + filename, and test plugins should be placed under + tests/test_data/fake_plugins + + :avocado: disable + :avocado: tags=stagetwo + """ + + sos_cmd = '' + files = [] + packages = {} + install_plugins = [] + _created_files = [] + + def setUp(self): + self.local_dist = distro.detect().name + self.end_of_test_case = False + # seems awkward, but check_installed() and remove() are not exposed + # together with install_distro_packages() + self.installer = software_manager + self.sm = self.installer.SoftwareManager() + + keys = self.packages.keys() + # allow for single declaration of packages for the RH family + # for our purposes centos == rhel here + if 'fedora' in keys and 'rhel' not in keys: + self.packages['rhel'] = self.packages['fedora'] + elif 'rhel' in keys and 'fedora' not in keys: + self.packages['fedora'] = self.packages['rhel'] + if 'rhel' in keys: + self.packages['centos'] = self.packages['rhel'] + + super(StageTwoReportTest, self).setUp() + + def tearDown(self): + if self.end_of_test_case: + self.teardown_mocking() + + def teardown_mocking(self): + """Undo any and all mocked setup that we did for tests + """ + self.teardown_mocked_packages() + self.teardown_mocked_files() + self.teardown_mocked_plugins() + + def setup_mocking(self): + """Main entrypoint for setting up our mocking for the test""" + self.setup_mocked_packages() + self.setup_mocked_files() + self.setup_mocked_plugins() + + def setup_mocked_plugins(self): + """Drop any plugins specified from tests/test_data/fake_plugins into + the test repo root (as created by CirrusCI). + """ + _installed = [] + for plug in self.install_plugins: + if not plug.endswith('.py'): + plug += '.py' + fake_plug = os.path.join(SOS_TEST_DATA_DIR, 'fake_plugins', plug) + if os.path.exists(fake_plug): + shutil.copy(fake_plug, SOS_PLUGIN_DIR) + _installed.append(os.path.realpath(os.path.join(SOS_PLUGIN_DIR, plug))) + self._write_file_to_tmpdir('mocked_plugins', json.dumps(_installed)) + + def teardown_mocked_plugins(self): + """Remove any test plugins dropped into the repo during setup + """ + _plugins = self.read_file_from_tmpdir('mocked_plugins') + if not _plugins: + return + _plugins = json.loads(_plugins) + for plug in _plugins: + os.remove(plug) + + def setup_mocked_packages(self): + """Install any required packages using avocado's software manager + abstraction + """ + if self.local_dist in self.packages: + # remove any packages already locally installed, as otherwise + # our call to SoftwareManager will return False + self._strip_installed_packages() + if not self.packages[self.local_dist]: + return + installed = self.installer.install_distro_packages(self.packages) + if not installed: + raise("Unable to install requested packages %" + % ', '.join(pkg for pkg in self.packages[self.local_dist])) + # save installed package list to our tmpdir to be removed later + self._write_file_to_tmpdir('mocked_packages', json.dumps(self.packages[self.local_dist])) + + def _strip_installed_packages(self): + """For the list of packages given for a test, if any of the packages + already exist on the test system, remove them from the list of packages + to be installed. + """ + for pkg in self.packages[self.local_dist]: + if self.sm.check_installed(pkg): + self.packages[self.local_dist].remove(pkg) + + def teardown_mocked_packages(self): + """Uninstall any packages that we installed for this test + """ + pkgs = self.read_file_from_tmpdir('mocked_packages') + if not pkgs: + return + pkgs = json.loads(pkgs) + for pkg in pkgs: + self.sm.remove(pkg) + + def setup_mocked_files(self): + """Place any requested files from under tests/test_data into "proper" + locations on the test system's filesystem. + + If any of these files already exist, rename the existing copy with a + '.sostesting' extension, so we can easily undo any changes after the + test(s) have run. + """ + for mfile in self.files: + dir_added = False + if os.path.exists(mfile): + os.rename(mfile, mfile + '.sostesting') + _dir = os.path.split(mfile)[0] + if not os.path.exists(_dir): + os.makedirs(_dir) + self._created_files.append(_dir) + dir_added = True + _test_file = os.path.join(SOS_TEST_DIR, 'test_data', mfile.lstrip('/')) + shutil.copy(_test_file, mfile) + if not dir_added: + self._created_files.append(mfile) + if self._created_files: + self._write_file_to_tmpdir('mocked_files', json.dumps(self._created_files)) + + def teardown_mocked_files(self): + """Remove any mocked files from the test system's filesystem, and + if applicable, restore previously moved files + """ + _files = self.read_file_from_tmpdir('mocked_files') + if not _files: + return + _files = json.loads(_files) + for mocked in _files: + if os.path.isdir(mocked): + shutil.rmtree(mocked) + else: + os.remove(mocked) + if os.path.exists(mocked + '.sostesting'): + os.rename(mocked + '.sostesting', mocked) + + def test_archive_created(self): + """Ensure that the archive tarball was created and has the right owner + + :avocado: tags=stagetwo + """ + # kind of a hack, but since avocado test order is predicatable, we can + # use this to avoid calling setUp() and tearDown() at each test_ method + # for stagetwo like we use the tmpdir for stageone. + # THIS TEST MUST ALWAYS BE DEFINED LAST IN THIS CLASS FOR THIS TO WORK + self.end_of_test_case = True + + self.assertFileExists(self.archive) + self.assertTrue(os.stat(self.archive).st_uid == 0) |