mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Unit tests - Add a BaseTestCase which prepares and isolates the environment of each test
This commit is contained in:
parent
b250ded517
commit
74bca0e5bf
1 changed files with 161 additions and 0 deletions
161
test/basetest/__init__.py
Normal file
161
test/basetest/__init__.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Executed once before any test in the class"""
|
||||
cls._original_pwd = os.getcwd()
|
||||
cls._new_pwd = tempfile.mkdtemp()
|
||||
|
||||
# Chdir into the temporary folder
|
||||
os.chdir(cls._new_pwd)
|
||||
|
||||
# Change environment Prepare Env
|
||||
cls.prepareEnv()
|
||||
|
||||
# Make task available locally
|
||||
cls.prepareExecutable()
|
||||
|
||||
# Prepare the environment
|
||||
cls.prepare()
|
||||
|
||||
@classmethod
|
||||
def prepareEnv(cls):
|
||||
"""Ensure that any ENV variable used by task and setup in the
|
||||
environment doesn't affect the test
|
||||
"""
|
||||
try:
|
||||
del os.environ["TASKDATA"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
del os.environ["TASKRC"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def prepareExecutable(cls):
|
||||
"""Link the binary to be tested in the temporary folder"""
|
||||
src = os.path.join(cls._original_pwd, "..", "src", "task")
|
||||
os.symlink(src, "task")
|
||||
|
||||
@classmethod
|
||||
def prepare(cls):
|
||||
"""Additional setup, executed only once before any test
|
||||
|
||||
The current (temporary) work dir is available as cls._new_pwd
|
||||
and the original work dir as cls._original_pwd
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Executed once after all tests in the class"""
|
||||
# Finishing steps before removal of temp dir
|
||||
cls.finish()
|
||||
|
||||
# Finally remove whatever's left of the temporary folder
|
||||
os.chdir(cls._original_pwd)
|
||||
shutil.rmtree(cls._new_pwd)
|
||||
|
||||
@classmethod
|
||||
def finish(cls):
|
||||
"""Finishing steps, executed once after all tests are executed and
|
||||
before removal of the temporary directory
|
||||
|
||||
The current (temporary) work dir is available as cls._new_pwd
|
||||
and the original work dir as cls._original_pwd
|
||||
|
||||
Any files not removed here or via self.addCleanup() will be deleted
|
||||
once this function completes, as part of the removal of the temporary
|
||||
directory. Still it is good practice to explicitly clean your leftovers
|
||||
|
||||
NOTE: If setUpClass or any of prepare* methods fails, finish() will
|
||||
not get called.
|
||||
"""
|
||||
pass
|
||||
|
||||
def callTask(self, args, input=None, merge_streams=True):
|
||||
"""Invoke src/task with the given arguments
|
||||
|
||||
Use callTaskSuccess or callTaskError if you want exit_code to be tested
|
||||
automatically and fail if result is unexpected.
|
||||
|
||||
If you wish to pass instructions to task you can do so by providing a
|
||||
string via input. Such as input="add Hello task\n1 delete".
|
||||
|
||||
If merge_streams=True stdout and stderr will be merged into stdout.
|
||||
|
||||
Returns (exit_code, stdout, stderr)
|
||||
"""
|
||||
if merge_streams:
|
||||
stderr = STDOUT
|
||||
else:
|
||||
stderr = PIPE
|
||||
|
||||
if input is not None:
|
||||
stdin = PIPE
|
||||
else:
|
||||
stdin = None
|
||||
|
||||
command = ["./task"]
|
||||
command.extend(args)
|
||||
|
||||
p = Popen(command, stdin=stdin, stdout=PIPE, stderr=STDOUT)
|
||||
out, err = p.communicate(input)
|
||||
# In python3 we will be able use the following instead of the previous
|
||||
# line to avoid locking if task is unexpectedly waiting for input
|
||||
#try:
|
||||
# out, err = p.communicate(input, timeout=15)
|
||||
#except TimeoutExpired:
|
||||
# p.kill()
|
||||
# out, err = proc.communicate()
|
||||
|
||||
|
||||
return p.returncode, out, err
|
||||
|
||||
def callTaskSuccess(self, args, input=None, merge_streams=True):
|
||||
"""Invoke src/task with the given arguments and expect a zero exit
|
||||
code.
|
||||
Causes test to fail if exit_code != 0
|
||||
|
||||
If you wish to pass instructions to task you can do so by providing a
|
||||
string via input. Such as input="add Hello task\n1 delete".
|
||||
|
||||
If merge_streams=True stdout and stderr will be merged into stdout.
|
||||
|
||||
Returns (exit_code, stdout, stderr)
|
||||
"""
|
||||
out = self.callTask(args, input, merge_streams)
|
||||
|
||||
self.assertEqual(out[0], 0, "Task finished with non-zero ({0}) exit "
|
||||
"code".format(out[0]))
|
||||
return out
|
||||
|
||||
def callTaskError(self, args, input=None, merge_streams=True):
|
||||
"""Invoke src/task with the given arguments and expect a non-zero exit
|
||||
code.
|
||||
Causes test to fail if exit_code == 0
|
||||
|
||||
If you wish to pass instructions to task you can do so by providing a
|
||||
string via input. Such as input="add Hello task\n1 delete".
|
||||
|
||||
If merge_streams=True stdout and stderr will be merged into stdout.
|
||||
|
||||
Returns (exit_code, stdout, stderr)
|
||||
"""
|
||||
out = self.callTask(args, input, merge_streams)
|
||||
|
||||
self.assertNotEqual(out[0], 0, "Task finished with zero exit (0) code")
|
||||
return out
|
||||
|
||||
|
||||
# vim: ai sts=4 et sw=4
|
Loading…
Add table
Add a link
Reference in a new issue