Merge pull request #329 from Exiv2/test_suite_improvements

Test suite improvements:
- the variables defined in the config file are now accessible inside the system_tests namespace
- a new function path was added, that converts unix paths to Windows paths on Windows
- hooks were added to the test suite and are used to remove the usage of $cat
This commit is contained in:
D4N 2018-05-29 10:59:11 +02:00 committed by GitHub
commit 2ab4f72c89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 245 additions and 95 deletions

View File

@ -43,7 +43,6 @@ build_script:
- cmd: unit_tests.exe
- cmd: cd ../../tests/
- cmd: set EXIV2_EXT=.exe
- cmd: set EXIV2_CAT=type
- cmd: c:\Python36\python.exe runner.py -v
cache:

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
import system_tests
import os.path
from system_tests import CaseMeta, path, check_no_ASAN_UBSAN_errors
class TestFirstPoC(metaclass=system_tests.CaseMeta):
class TestFirstPoC(metaclass=CaseMeta):
"""
Regression test for the bug described in:
https://github.com/Exiv2/exiv2/issues/283
@ -16,16 +16,15 @@ class TestFirstPoC(metaclass=system_tests.CaseMeta):
Here we want to also check that the two last lines of got_stderr have the expected_stderr
"""
system_tests.check_no_ASAN_UBSAN_errors(self, i, command, got_stderr, expected_stderr)
check_no_ASAN_UBSAN_errors(self, i, command, got_stderr, expected_stderr)
self.assertListEqual(expected_stderr.splitlines(), got_stderr.splitlines()[-2:])
filename = os.path.join("$data_path", "pocIssue283.jpg")
filename = path("$data_path/pocIssue283.jpg")
commands = ["$exiv2 $filename"]
stdout = [""]
stderr = [
"""$exiv2_exception_message """ + filename + """:
"""$exiv2_exception_message $filename:
$kerCorruptedMetadata
"""]
compare_stderr = check_no_ASAN_UBSAN_errors
retval = [1]

View File

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
import system_tests
from system_tests import CaseMeta, path
class CanonEOSM100(metaclass=system_tests.CaseMeta):
class CanonEOSM100(metaclass=CaseMeta):
filename = "$data_path/exiv2-pr317.exv"
filename = path("$data_path/exiv2-pr317.exv")
commands = ["$exiv2 -pa --grep model/i $filename"]
stdout = ["""Exif.Image.Model Ascii 15 Canon EOS M100
Exif.Canon.ModelID Long 1 EOS M100
Exif.Photo.LensModel Ascii 29 EF-M15-45mm f/3.5-6.3 IS STM
"""
]
]
stderr = [""]
retval = [0]
retval = [0]

View File

@ -1,33 +1,34 @@
# -*- coding: utf-8 -*-
import system_tests
import os.path
from system_tests import DeleteFiles, CopyFiles, CaseMeta, path
@system_tests.DeleteFiles("$xmpname")
@system_tests.CopyFiles("$data_path/exiv2-empty.jpg")
class AdobeXmpNamespace(metaclass=system_tests.CaseMeta):
@DeleteFiles("$xmpname")
@CopyFiles("$data_path/exiv2-empty.jpg")
class AdobeXmpNamespace(metaclass=CaseMeta):
url = "http://dev.exiv2.org/issues/751"
filename = os.path.join("$data_path", "exiv2-empty_copy.jpg")
xmpname = os.path.join("$data_path", "exiv2-empty_copy.xmp")
filename = path("$data_path/exiv2-empty_copy.jpg")
xmpname = path("$data_path/exiv2-empty_copy.xmp")
commands = [
"""$exiv2 -v -M"reg imageapp orig/" -M "set Xmp.imageapp.uuid abcd" $filename""",
"$exiv2 -f -eX $filename",
"$cat $xmpname",
"""$exiv2 -v -M"reg imageapp dest/" -M "set Xmp.imageapp.uuid abcd" $filename""",
"$exiv2 -f -eX $filename",
"$cat $xmpname",
]
stdout = [
"""File 1/1: $filename
Reg imageapp="orig/"
Set Xmp.imageapp.uuid "abcd" (XmpText)
""",
"",
def post_command_hook(self, i, command):
def read_xmpfile():
with open(self.xmpname, "r", encoding='utf-8') as xmp:
return xmp.read(-1)
if i == 2 or i == 4:
self.assertMultiLineEqual(self.xmp_packets[i//2 - 1], read_xmpfile())
xmp_packets = [
"""<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
@ -37,11 +38,6 @@ Set Xmp.imageapp.uuid "abcd" (XmpText)
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>""",
"""File 1/1: $filename
Reg imageapp="dest/"
Set Xmp.imageapp.uuid "abcd" (XmpText)
""",
"",
"""<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
@ -51,16 +47,26 @@ Set Xmp.imageapp.uuid "abcd" (XmpText)
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>"""
]
stdout = [
"""File 1/1: $filename
Reg imageapp="orig/"
Set Xmp.imageapp.uuid "abcd" (XmpText)
""",
"",
"""File 1/1: $filename
Reg imageapp="dest/"
Set Xmp.imageapp.uuid "abcd" (XmpText)
""",
"",
]
stderr = [
"",
"",
"",
"""Warning: Updating namespace URI for imageapp from orig/ to dest/
""",
"""Warning: Updating namespace URI for imageapp from dest/ to orig/
""",
""
]
retval = [0] * 6
retval = [0] * 4

View File

@ -1,19 +1,18 @@
# -*- coding: utf-8 -*-
import system_tests
import os
from system_tests import DeleteFiles, CopyFiles, CaseMeta, path
@system_tests.DeleteFiles("$xmpfile")
@system_tests.CopyFiles("$data_path/exiv2-empty.jpg")
class WrongXmpTypeForNestedXmpKeys(metaclass=system_tests.CaseMeta):
@DeleteFiles("$xmpfile")
@CopyFiles("$data_path/exiv2-empty.jpg")
class WrongXmpTypeForNestedXmpKeys(metaclass=CaseMeta):
url = "http://dev.exiv2.org/issues/$num"
num = 799
cmdfile = os.path.join("$data_path", "bug$num.cmd")
cmdfile = path("$data_path/bug$num.cmd")
filename_common = os.path.join("$data_path", "exiv2-empty_copy")
filename_common = path("$data_path/exiv2-empty_copy")
filename = "$filename_common.jpg"
xmpfile = "$filename_common.xmp"
@ -21,50 +20,13 @@ class WrongXmpTypeForNestedXmpKeys(metaclass=system_tests.CaseMeta):
"$exiv2 -v -m $cmdfile $filename",
"$exiv2 -v -pa $filename",
"$exiv2 -f -eX $filename",
"$cat $xmpfile",
]
stdout = [
"""File 1/1: $filename
Set Xmp.MP.RegionInfo/MPRI:Regions "" (XmpBag)
Set Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle "0.11, 0.22, 0.33, 0.44" (XmpText)
Set Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:PersonDisplayName "Baby Gnu" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:w "1600" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:h "800" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:unit "pixel" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList "" (XmpBag)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name "Baby Gnu" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Type "Face" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:x "0.275312" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:y "0.3775" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:w "0.164375" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:h "0.28125" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit "normalized" (XmpText)
""",
"""File 1/1: $filename
Xmp.MP.RegionInfo XmpText 0 type="Struct"
Xmp.MP.RegionInfo/MPRI:Regions XmpText 0 type="Bag"
Xmp.MP.RegionInfo/MPRI:Regions[1] XmpText 0 type="Struct"
Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle XmpText 22 0.11, 0.22, 0.33, 0.44
Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:PersonDisplayName XmpText 8 Baby Gnu
Xmp.mwg-rs.Regions XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:w XmpText 4 1600
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:h XmpText 3 800
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:unit XmpText 5 pixel
Xmp.mwg-rs.Regions/mwg-rs:RegionList XmpText 0 type="Bag"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1] XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name XmpText 8 Baby Gnu
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Type XmpText 4 Face
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:x XmpText 8 0.275312
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:y XmpText 6 0.3775
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:w XmpText 8 0.164375
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:h XmpText 7 0.28125
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit XmpText 10 normalized
""",
"",
"""<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
def post_tests_hook(self):
with open(self.xmpfile, "r", encoding='utf-8') as xmp_file:
self.assertMultiLineEqual(self.xmp_packet, xmp_file.read(-1))
xmp_packet = """<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 4.4.0-Exiv2">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
@ -109,6 +71,47 @@ Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit XmpText 10 n
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>"""
stdout = [
"""File 1/1: $filename
Set Xmp.MP.RegionInfo/MPRI:Regions "" (XmpBag)
Set Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle "0.11, 0.22, 0.33, 0.44" (XmpText)
Set Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:PersonDisplayName "Baby Gnu" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:w "1600" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:h "800" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:unit "pixel" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList "" (XmpBag)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name "Baby Gnu" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Type "Face" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:x "0.275312" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:y "0.3775" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:w "0.164375" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:h "0.28125" (XmpText)
Set Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit "normalized" (XmpText)
""",
"""File 1/1: $filename
Xmp.MP.RegionInfo XmpText 0 type="Struct"
Xmp.MP.RegionInfo/MPRI:Regions XmpText 0 type="Bag"
Xmp.MP.RegionInfo/MPRI:Regions[1] XmpText 0 type="Struct"
Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:Rectangle XmpText 22 0.11, 0.22, 0.33, 0.44
Xmp.MP.RegionInfo/MPRI:Regions[1]/MPReg:PersonDisplayName XmpText 8 Baby Gnu
Xmp.mwg-rs.Regions XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:w XmpText 4 1600
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:h XmpText 3 800
Xmp.mwg-rs.Regions/mwg-rs:AppliedToDimensions/stDim:unit XmpText 5 pixel
Xmp.mwg-rs.Regions/mwg-rs:RegionList XmpText 0 type="Bag"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1] XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Name XmpText 8 Baby Gnu
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Type XmpText 4 Face
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area XmpText 0 type="Struct"
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:x XmpText 8 0.275312
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:y XmpText 6 0.3775
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:w XmpText 8 0.164375
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:h XmpText 7 0.28125
Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit XmpText 10 normalized
""",
"",
]
stderr = [
@ -117,6 +120,5 @@ Xmp.mwg-rs.Regions/mwg-rs:RegionList[1]/mwg-rs:Area/stArea:unit XmpText 10 n
$filename: No IPTC data found in the file
""",
"",
""
]
retval = [0] * 4
retval = [0] * 3

View File

@ -220,10 +220,14 @@ following a `$` with variables either defined in this class alongside (like
configuration file. Please note that defining a variable with the same name as a
variable in the suite's configuration file will result in an error (otherwise
one of the variables would take precedence leading to unexpected results). The
substitution of values is performed using the template module from Python's
string library via `safe_substitute`.
variables defined in the test suites configuration file are also available in
the `system_tests` namespace. In the above example it would be therefore
possible to access `abort_exit_value` via `system_tests.abort_exit_value`
(please be aware that all values will be strings though).
In the above example the command would thus expand to:
The substitution of values is performed using the template module from Python's
string library via `safe_substitute`. In the above example the command would
thus expand to:
``` shell
/path/to/the/dir/build/bin/binary -c /path/to/the/dir/conf/main.cfg -i invalid_input_file
```
@ -232,14 +236,23 @@ and similarly for `stdout` and `stderr`.
Once the substitution is performed, each command is run using Python's
`subprocess` module, its output is compared to the values in `stdout` and
`stderr` and its return value to `retval`. Please note that for portability
reasons the subprocess module is run with `shell=False`, thus shell expansions
or pipes will not work.
reasons the subprocess module is run with `shell=False`, thus shell expansions,
pipes and redirections into files will not work.
As the test cases are implemented in Python, one can take full advantage of
Python for the construction of the necessary lists. For example when 10 commands
should be run and all return 0, one can write `retval = 10 * [0]` instead of
writing 0 ten times. The same is of course possible for strings.
### Multiline strings
It is generally recommended to use Python's multiline strings (strings starting
and ending with three `"` instead of one `"`) for the elements of the `commands`
list, especially when the commands include `"` or escape sequences. Proper
escaping is tricky to get right in a platform independent way, as it depends on
the terminal that is used. Using multiline strings circumvents this issue.
There are however some peculiarities with multiline strings in Python. Normal
strings start and end with a single `"` but multiline strings start with three
`"`. Also, while the variable names must be indented, new lines in multiline
@ -267,6 +280,31 @@ as the indentation might have suggested.
Also note that in this example the string will not be terminated with a newline
character. To achieve that put the `"""` on the following line.
### Paths
Some test cases require the specification of paths (e.g. to the location of test
cases). This can be problematic when working with the Windows operating system,
as it sometimes exhibits problems with `/` as path separators instead of `\`,
which cannot be used on every other platform.
This can be circumvented by creating the paths via `os.path.join`, but that is
quite verbose. A slightly simpler alternative is the function `path` from
`system_tests` which converts all `/` inside your string into the platform's
default path separator:
``` python
# -*- coding: utf-8 -*-
from system_tests import CaseMeta, path
class AnInformativeName(metaclass=CaseMeta):
filename = path("$path_to_test_files/invalid_input_file")
# the rest of your test case
```
## Advanced test cases
@ -321,6 +359,7 @@ the test suite features a decorator which creates a copy of the supplied files
and deletes the copies after the test ran.
Example:
``` python
# -*- coding: utf-8 -*-
@ -358,10 +397,12 @@ cases, one can customize how stdout and stderr checked for errors.
The `system_tests.Case` class has two public functions for the check of stdout &
stderr: `compare_stdout` & `compare_stderr`. They have the following interface:
``` python
compare_stdout(self, i, command, got_stdout, expected_stdout)
compare_stderr(self, i, command, got_stderr, expected_stderr)
```
with the parameters:
- i: index of the command in the `commands` list
- command: a string of the actually invoked command
@ -382,6 +423,7 @@ errors from AddressSanitizer and undefined behavior sanitizer are not present in
the obtained output to standard error **and nothing else**. This is useful for
test cases where stderr is filled with warnings that are not worth being tracked
by the test suite. It can be used in the following way:
``` python
# -*- coding: utf-8 -*-
@ -411,6 +453,7 @@ variable substitution using the test suite's configuration file.
Unfortunately, it has to run in a class member function. The `setUp()` function
can be used for this, as it is run before each test. For example like this:
``` python
class SomeName(metaclass=system_tests.CaseMeta):
@ -425,6 +468,7 @@ This example will work, as the test runner reads the data for `commands`,
`stderr`, `stdout` and `retval` from the class instance. What however will not
work is creating a new member in `setUp()` and trying to use it as a variable
for expansion, like this:
``` python
class SomeName(metaclass=system_tests.CaseMeta):
@ -451,6 +495,53 @@ class SomeName(metaclass=system_tests.CaseMeta):
will result in `another_string` being "foo" and not "bar".
### Hooks
The `Case` class provides two hooks that are run after each command and after
all commands, respectively. The hook which is run after each successful command
has the following signature:
``` Python
post_command_hook(self, i, command)
```
with the following parameters:
- `i`: index of the command in the `commands` list
- `command`: a string of the actually invoked command
The hook which is run after all test takes no parameters except `self`:
``` Python
post_tests_hook(self)
```
By default, these hooks do nothing. They can be used to implement custom checks
after certain commands, e.g. to check if a file was created. Such a test can be
implemented as follows:
``` Python
# -*- coding: utf-8 -*-
import system_tests
class AnInformativeName(metaclass=system_tests.CaseMeta):
filename = "input_file"
output = "out"
commands = ["$binary -o output -i $filename"]
retval = [0]
stdout = [""]
stderr = [""]
output_contents = """Hello World!
"""
def post_tests_hook(self):
with open(self.output, "r") as out:
self.assertMultiLineEqual(self.output_contents, out.read(-1))
```
### Possible pitfalls
- Do not provide a custom `setUpClass()` function for the test

View File

@ -4,11 +4,9 @@ timeout: 1
[ENV]
exiv2_path: EXIV2_PATH
binary_extension: EXIV2_EXT
cat: EXIV2_CAT
[ENV fallback]
exiv2_path: ../build/bin
cat: cat
[paths]
exiv2: ${ENV:exiv2_path}/exiv2${ENV:binary_extension}
@ -29,4 +27,3 @@ addition_overflow_message: Overflow in addition
exiv2_exception_message: Exiv2 exception in print action for file
exiv2_overflow_exception_message: std::overflow_error exception in print action for file
exception_in_extract: Exiv2 exception in extract action for file
cat: ${ENV:cat}

View File

@ -177,6 +177,12 @@ def configure_suite(config_file):
)
_parameters[key] = abs_path
for key in _parameters:
if key in globals():
raise ValueError("Variable name {!s} already used.")
globals()[key] = _parameters[key]
class FileDecoratorBase(object):
"""
@ -468,6 +474,24 @@ class DeleteFiles(FileDecoratorBase):
return expanded_file_name
def path(path_string):
r"""
Converts a path which uses ``/`` as a separator into a path which uses the
path separator of the current operating system.
Example
-------
>>> import platform
>>> sep = "\\" if platform.system() == "Windows" else "/"
>>> path("a/b") == "a" + sep + "b"
True
>>> path("a/more/complex/path") == sep.join(['a', 'more', 'complex', 'path'])
True
"""
return os.path.join(*path_string.split('/'))
def test_run(self):
"""
This function reads in the members commands, retval, stdout, stderr and runs
@ -560,6 +584,10 @@ def test_run(self):
retval, proc.returncode, msg="Return value does not match"
)
self.post_command_hook(i, command)
self.post_tests_hook()
class Case(unittest.TestCase):
"""
@ -622,6 +650,34 @@ class Case(unittest.TestCase):
return string.Template(str(unexpanded_string))\
.safe_substitute(**self.variable_dict)
def post_command_hook(self, i, command):
""" Function that is run after the successful execution of one command.
It is invoked with the following parameters:
i - the index of the current command that is run in self.commands
command - the command that was run
It should return nothing.
This function can be overridden to perform additional checks after the
command ran, for instance it can check whether files were created.
The default implementation does nothing.
"""
pass
def post_tests_hook(self):
"""
Function that is run after the successful execution all commands. It
should return nothing.
This function can be overridden to run additional checks that only make
sense after all commands ran.
The default implementation does nothing.
"""
pass
class CaseMeta(type):
""" System tests generation metaclass.