Refactor `testprogram` framework to minimize explicit setup and improve reuse.

* Add a ``testprogram.TestProgramCase()`` to simplify test classes

  + Has ``setUp()`` and ``tearDown()`` to create ephemeral test directory
    (really should be moved to salttesting.TestCase)

  + Common ``assert_exit_status()`` for validating and reporting failures in
    exit status.

* Add missing ``testprogram.TestDaemonSaltProxy`` class

* Remove specific setting of ``program`` parameter in initialization of
  ``TestDaemonSaltMinion`` since ``testprogram.TestSaltProgramMeta`` already
  handles that unless a specific override is required.

* Automatically set ``user`` in config for master, minion and proxy test
  classes if a specific user is not specified.

* Automatically set ``PYTHONPATH`` as ``sys.path`` unless it is specified.
This commit is contained in:
Thayne Harbaugh 2016-06-02 16:51:54 -06:00
parent 7c6216c3fc
commit 68a9912d3a
2 changed files with 95 additions and 64 deletions

View file

@ -9,14 +9,13 @@
# Import python libs
from __future__ import absolute_import
import getpass
import os
import sys
import getpass
import platform
import yaml
import signal
import shutil
import tempfile
import logging
# Import Salt Testing libs
@ -34,28 +33,17 @@ log = logging.getLogger(__name__)
DEBUG = True
class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
class MinionTest(integration.ShellCase, testprogram.TestProgramCase, integration.ShellCaseCommonTestsMixIn):
'''
Various integration tests for the salt-minion executable.
'''
_call_binary_ = 'salt-minion'
_test_dir = None
_test_minions = (
'minion',
'subminion',
)
def setUp(self):
# Setup for scripts
self._test_dir = tempfile.mkdtemp(prefix='salt-testdaemon-')
def tearDown(self):
# shutdown for scripts
if self._test_dir and os.path.sep == self._test_dir[0]:
shutil.rmtree(self._test_dir)
self._test_dir = None
def test_issue_7754(self):
old_cwd = os.getcwd()
config_dir = os.path.join(integration.TMP, 'issue-7754')
@ -165,7 +153,6 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
for mname in minions:
minion = testprogram.TestDaemonSaltMinion(
name=mname,
config={'user': user},
parent_dir=self._test_dir,
)
# Call setup here to ensure config and script exist
@ -179,7 +166,6 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
sysconf_dir = os.path.dirname(_minions[0].config_dir)
cmd_env = {
'PATH': ':'.join([salt_call.script_dir, os.getenv('PATH')]),
'PYTHONPATH': ':'.join(sys.path),
'SALTMINION_DEBUG': '1' if DEBUG else '',
'SALTMINION_PYTHON': sys.executable,
'SALTMINION_SYSCONFDIR': sysconf_dir,
@ -219,6 +205,10 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
minions, _, init_script = self._initscript_setup(self._test_minions[:1])
try:
# These tests are grouped together, rather than split into individual test functions,
# because subsequent tests leverage the state from the previous test which minimizes
# setup for each test.
# I take visual readability with aligned columns over strict PEP8
# (bad-whitespace) Exactly one space required after comma
# pylint: disable=C0326
@ -254,28 +244,6 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
for minion in minions:
minion.shutdown()
def _assert_exit_status(self, status, ex_status, message=None, stdout=None, stderr=None):
'''
Helper function to verify exit status and emit failure information.
'''
ex_val = getattr(salt.defaults.exitcodes, ex_status)
_message = '' if not message else ' ({0})'.format(message)
_stdout = '' if not stdout else '\nstdout: {0}'.format('\nstdout: '.join(stdout))
_stderr = '' if not stderr else '\nstderr: {0}'.format('\nstderr: '.join(stderr))
self.assertEqual(
status,
ex_val,
'Exit status was {0}, must be {1} (salt.default.exitcodes.{2}){3}{4}{5}'.format(
status,
ex_val,
ex_status,
_message,
_stderr,
_stderr,
)
)
def test_exit_status_unknown_user(self):
'''
Ensure correct exit status when the minion is configured to run as an unknown user.
@ -283,12 +251,8 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
minion = testprogram.TestDaemonSaltMinion(
name='unknown_user',
program=os.path.join(integration.CODE_DIR, 'scripts', 'salt-minion'),
config={'user': 'unknown'},
parent_dir=self._test_dir,
env={
'PYTHONPATH': ':'.join(sys.path),
},
)
# Call setup here to ensure config and script exist
minion.setup()
@ -297,13 +261,12 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
catch_stderr=True,
with_retcode=True,
)
self._assert_exit_status(
status,
'EX_NOUSER',
self.assert_exit_status(
status, 'EX_NOUSER',
message='unknown user not on system',
stdout=stdout,
stderr=stderr
stdout=stdout, stderr=stderr
)
# minion.shutdown() should be unnecessary since the start-up should fail
# pylint: disable=invalid-name
def test_exit_status_unknown_argument(self):
@ -311,16 +274,9 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
Ensure correct exit status when an unknown argument is passed to salt-minion.
'''
user = getpass.getuser()
minion = testprogram.TestDaemonSaltMinion(
name='unknown_argument',
program=os.path.join(integration.CODE_DIR, 'scripts', 'salt-minion'),
config={'user': user},
parent_dir=self._test_dir,
env={
'PYTHONPATH': ':'.join(sys.path),
},
)
# Call setup here to ensure config and script exist
minion.setup()
@ -329,23 +285,21 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
catch_stderr=True,
with_retcode=True,
)
self._assert_exit_status(status, 'EX_USAGE', message='unknown argument', stdout=stdout, stderr=stderr)
self.assert_exit_status(
status, 'EX_USAGE',
message='unknown argument',
stdout=stdout, stderr=stderr
)
# minion.shutdown() should be unnecessary since the start-up should fail
def test_exit_status_correct_usage(self):
'''
Ensure correct exit status when salt-minion starts correctly.
'''
user = getpass.getuser()
minion = testprogram.TestDaemonSaltMinion(
name='correct_usage',
program=os.path.join(integration.CODE_DIR, 'scripts', 'salt-minion'),
config={'user': user},
parent_dir=self._test_dir,
env={
'PYTHONPATH': ':'.join(sys.path),
},
)
# Call setup here to ensure config and script exist
minion.setup()
@ -354,7 +308,11 @@ class MinionTest(integration.ShellCase, integration.ShellCaseCommonTestsMixIn):
catch_stderr=True,
with_retcode=True,
)
self._assert_exit_status(status, 'EX_OK', message='correct usage', stdout=stdout, stderr=stderr)
self.assert_exit_status(
status, 'EX_OK',
message='correct usage',
stdout=stdout, stderr=stderr
)
minion.shutdown()

View file

@ -8,6 +8,7 @@ import atexit
import copy
from datetime import datetime, timedelta
import errno
import getpass
import logging
import os
import shutil
@ -24,6 +25,8 @@ import salt.utils.process
import salt.utils.psutil_compat as psutils
from salt.defaults import exitcodes
from salttesting import TestCase
import integration
LOG = logging.getLogger(__name__)
@ -46,6 +49,8 @@ class TestProgram(object):
self.program = program or getattr(self, 'program', None)
self.name = name or getattr(self, 'name', None)
self.env = env or {}
if 'PYTHONPATH' not in self.env:
self.env['PYTHONPATH'] = ':'.join(sys.path)
self.shell = shell
self._parent_dir = parent_dir or None
self.clean_on_exit = clean_on_exit
@ -481,7 +486,11 @@ class TestDaemon(TestProgram):
if not os.path.exists(self.config_dir):
os.makedirs(self.config_dir)
with open(self.config_path, 'w') as cfo:
cfo.write(self.config_stringify())
cfg = self.config_stringify()
LOG.debug('Writing configuration for {0} to {1}:\n{2}'.format(
self.name, self.config_path, cfg
))
cfo.write(cfg)
cfo.flush()
def make_dirtree(self):
@ -601,6 +610,11 @@ class TestDaemonSaltMaster(TestSaltDaemon):
config_file = 'master'
def __init__(self, *args, **kwargs):
cfg = kwargs.setdefault('config', {})
_ = cfg.setdefault('user', getpass.getuser())
super(TestDaemonSaltMaster, self).__init__(*args, **kwargs)
class TestDaemonSaltMinion(TestSaltDaemon):
'''
@ -612,6 +626,11 @@ class TestDaemonSaltMinion(TestSaltDaemon):
}
config_file = 'minion'
def __init__(self, *args, **kwargs):
cfg = kwargs.setdefault('config', {})
_ = cfg.setdefault('user', getpass.getuser())
super(TestDaemonSaltMinion, self).__init__(*args, **kwargs)
class TestDaemonSaltApi(TestSaltDaemon):
'''
@ -625,3 +644,57 @@ class TestDaemonSaltSyndic(TestSaltDaemon):
Manager for salt-syndic daemon.
'''
pass
class TestDaemonSaltProxy(TestSaltDaemon):
'''
Manager for salt-proxy daemon.
'''
config_file = 'proxy'
def __init__(self, *args, **kwargs):
cfg = kwargs.setdefault('config', {})
_ = cfg.setdefault('user', getpass.getuser())
super(TestDaemonSaltProxy, self).__init__(*args, **kwargs)
class TestProgramCase(TestCase):
'''
Utilities for unit tests that use TestProgram()
'''
def setUp(self):
# Setup for scripts
if not getattr(self, '_test_dir', None):
self._test_dir = tempfile.mkdtemp(prefix='salt-testdaemon-')
super(TestProgramCase, self).setUp()
def tearDown(self):
# shutdown for scripts
if self._test_dir and os.path.sep == self._test_dir[0]:
shutil.rmtree(self._test_dir)
self._test_dir = None
super(TestProgramCase, self).tearDown()
def assert_exit_status(self, status, ex_status, message=None, stdout=None, stderr=None):
'''
Helper function to verify exit status and emit failure information.
'''
ex_val = getattr(exitcodes, ex_status)
_message = '' if not message else ' ({0})'.format(message)
_stdout = '' if not stdout else '\nstdout: {0}'.format('\nstdout: '.join(stdout))
_stderr = '' if not stderr else '\nstderr: {0}'.format('\nstderr: '.join(stderr))
self.assertEqual(
status,
ex_val,
'Exit status was {0}, must be {1} (salt.default.exitcodes.{2}){3}{4}{5}'.format(
status,
ex_val,
ex_status,
_message,
_stderr,
_stderr,
)
)