Source code for test_actions
"""
unittest.TestCase class providing common functionality for testing actions.
"""
import unittest
import os
import subprocess
import ost
# set verbosity level here, is propagated to all others
ost.PushVerbosityLevel(2)
[docs]class ActionTestCase(unittest.TestCase):
"""
Class to help developing actions. Comes with a lot of convenience wrappers
around what should be tested and serves as a recorder for test calls...
just for in two years when you come back to rewrite the whole action...
While inheriting this class, :attr:`pm_action` needs to be defined.
Otherwise the whole idea does not work.
.. attribute:: pm_bin
This is the path of the ``pm`` binary. Automatically set by calling
:meth:`~ActionTestCase.__init__` inside the initialisation of your class.
:type: :class:`str`
.. attribute:: pm_action
The action to be tested. Needs to be set by your initialisation routine,
**after** calling :meth:`~ActionTestCase.__init__` from here. Skip the
"pm-" in front of the action name.
:type: :class:`str`
"""
def __init__(self, *args, **kwargs):
"""
Convenience stuff for action testing.
"""
# Determining the pm binary to be called. Going hard-coded is a bad
# thing. But this is a unit test and we know where we are as all unit
# tests are run in "tests/MODULENAME" within build-folder.
bld_dir = os.path.abspath(os.path.dirname(os.path.dirname(os.getcwd())))
self.pm_bin = os.path.join(bld_dir, 'stage', 'bin', 'pm')
self.pm_action = None
unittest.TestCase.__init__(self, *args, **kwargs)
[docs] def RunAction(self, arguments, verbose=False):
"""
Call an action, return the exit status (``$?`` shell variable). May be
set to ``verbose`` to print the actions terminal output. The action to
be executed needs to be stored in :attr:`pm_action` first.
If in verbose mode, output to :file:`stdout` of the action will be
printed first followed by :file:`stderr`.
:param arguments: A list of arguments for the call.
:type arguments: :class:`list`
:param verbose: If ``True``, report output of the action.
:type verbose: :class:`bool`
:returns: The exit code of the action (:class:`int`).
"""
if not self.pm_action:
raise RuntimeError("A 'pm_action' attribute has to be defined by "+
"each subclass of 'test_actions.ActionTestCase'")
# run the action with arguments, wait for the job to finish and capture
# output
cmd = [self.pm_bin, self.pm_action] + arguments
job = subprocess.Popen(cmd,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
sout, serr = job.communicate()
if verbose:
ost.LogScript("stdout of '%s'" % ' '.join(cmd))
ost.LogScript("------")
lines = sout.splitlines()
for out_l in lines:
ost.LogScript(out_l)
ost.LogScript("------")
ost.LogError("stderr of '%s'" % ' '.join(cmd))
ost.LogError("------")
lines = serr.splitlines()
for err_l in lines:
ost.LogError(err_l)
ost.LogError("------")
return job.returncode
[docs] def RunExitStatusTest(self, exit_code, arguments, verbose=False):
"""
Run the action with given arguments and check the exit code.
:param exit_code: The expected return code, ``$?`` in a shell.
:type exit_code: :class:`int`
:param arguments: A list of arguments for the call.
:type arguments: :class:`list`
:param verbose: If ``True``, report output of the action.
:type verbose: :class:`bool`
"""
exit_code_run = self.RunAction(arguments, verbose=verbose)
self.assertEqual(exit_code, exit_code_run,
msg="Exit code of '%s' " % ' '.join([self.pm_bin,
self.pm_action]+
arguments)+
"is supposed to be '%d' " % exit_code+
"but returned as '%d'." % exit_code_run)
[docs] def testPMExists(self):
"""
This is an internal test, executed when the source code of the test
class is run as unit test. Verifies that :attr:`pm_bin` is an existing
file (also complains if a directory is found instead).
"""
self.assertEqual(os.path.isfile(self.pm_bin), True,
msg="Could not find 'pm' bin at '%s', " % self.pm_bin+
"cannot proceed unit tests.")
if __name__ == "__main__":
import sys
from ost import testutils
sys.dont_write_bytecode = True
testutils.RunTests()
# LocalWords: attr meth ActionTestCase init str stdout stderr param bool