Adds new Brew file runner.
* Generates suites from test-containing classes, unittest.TestSuite-derived classes (Fixtures), and opencafe DatasetList classes as defined in a flat text file called a 'brew' (runfile). * Adds brew package in cafe.drivers.unittest * Adds arguments, parser and runner modules in brew package. * Adds examples directory in repository root directory * Adds example brewfile in examples directory * Adds new 'cafe-brew' entry point to setup.py * Adds unittests for brew package Change-Id: I379c5b2c4bdf17bc49ce055ef4600b38a72a1140
This commit is contained in:
parent
b704b4a068
commit
75039a8cc4
|
@ -0,0 +1,94 @@
|
|||
import argparse
|
||||
import sys
|
||||
from cafe.drivers.unittest.arguments import ConfigAction, VerboseAction
|
||||
from cafe.drivers.base import print_exception, get_error
|
||||
|
||||
|
||||
class ArgumentParser(argparse.ArgumentParser):
|
||||
"""
|
||||
Parses all arguments.
|
||||
"""
|
||||
def __init__(self):
|
||||
desc = "Open Common Automation Framework Engine BrewFile Runner"
|
||||
usage_string = """
|
||||
cafe-brew <config> <runfile(s)>...
|
||||
[--failfast]
|
||||
[--dry-run]
|
||||
[--parallel=(class|test)]
|
||||
[--result=(json|xml)] [--result-directory=RESULT_DIRECTORY]
|
||||
[--verbose=VERBOSE]
|
||||
[--exit-on-error]
|
||||
[--workers=NUM]
|
||||
[--help]
|
||||
"""
|
||||
|
||||
super(ArgumentParser, self).__init__(
|
||||
usage=usage_string, description=desc)
|
||||
|
||||
self.prog = "Argument Parser"
|
||||
|
||||
self.add_argument(
|
||||
"config",
|
||||
action=ConfigAction,
|
||||
metavar="<config>",
|
||||
help="test config. Looks in the .opencafe/configs directory."
|
||||
"Example: bsl/uk.json")
|
||||
|
||||
self.add_argument(
|
||||
"runfiles",
|
||||
nargs="*",
|
||||
default=[],
|
||||
metavar="<runfiles>...",
|
||||
help="A list of paths to opencafe runfiles.")
|
||||
|
||||
self.add_argument(
|
||||
"--dry-run",
|
||||
action="store_true",
|
||||
help="dry run. Don't run tests just print them. Will run data"
|
||||
" generators.")
|
||||
|
||||
self.add_argument(
|
||||
"--failfast",
|
||||
action="store_true",
|
||||
help="fail fast")
|
||||
|
||||
self.add_argument(
|
||||
"--verbose", "-v",
|
||||
action=VerboseAction,
|
||||
choices=[1, 2, 3],
|
||||
default=2,
|
||||
type=int,
|
||||
help="Set unittest output verbosity")
|
||||
|
||||
self.add_argument(
|
||||
"--parallel",
|
||||
choices=["class", "test"],
|
||||
help="Runs test in parallel by class grouping or test")
|
||||
|
||||
self.add_argument(
|
||||
"--result",
|
||||
choices=["json", "xml", "subunit"],
|
||||
help="Generates a specified formatted result file")
|
||||
|
||||
self.add_argument(
|
||||
"--result-directory",
|
||||
default="./",
|
||||
metavar="RESULT_DIRECTORY",
|
||||
help="Directory for result file to be stored")
|
||||
|
||||
self.add_argument(
|
||||
"--workers",
|
||||
nargs="?",
|
||||
default=10,
|
||||
type=int,
|
||||
help="Set number of workers for --parallel option")
|
||||
|
||||
def error(self, message):
|
||||
self.print_usage(sys.stderr)
|
||||
print_exception("Argument Parser", message)
|
||||
exit(get_error())
|
||||
|
||||
def parse_args(self, *args, **kwargs):
|
||||
args = super(ArgumentParser, self).parse_args(*args, **kwargs)
|
||||
args.all_tags = False
|
||||
return args
|
|
@ -0,0 +1,348 @@
|
|||
import collections
|
||||
import importlib
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from collections import OrderedDict
|
||||
from six import string_types
|
||||
from six.moves import configparser
|
||||
from types import ModuleType
|
||||
import types
|
||||
from cafe.drivers.unittest.decorators import DataDrivenClass
|
||||
from cafe.common.reporting import cclogging
|
||||
|
||||
|
||||
RESERVED_SECTION_NAMES = [
|
||||
"defaults",
|
||||
"cli-defaults",
|
||||
]
|
||||
|
||||
FIXTURE_ATTR = 'fixture_class'
|
||||
DATASETLIST_ATTR = 'dsl'
|
||||
TESTCLASSES_ATTR = 'mixin_test_classes'
|
||||
BREW_SECTION_ATTR_LIST = [FIXTURE_ATTR, DATASETLIST_ATTR, TESTCLASSES_ATTR]
|
||||
REQUIRED_BREW_SECTION_ATTR_LIST = [FIXTURE_ATTR]
|
||||
|
||||
|
||||
class RunFileNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RunFileSectionCollisionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class RunFileIncompleteBrewError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MalformedClassImportPathError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ModuleNotImportableError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ClassNotImportableError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BrewMissingTestClassesError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _ImportablePathWrapper(object):
|
||||
"""
|
||||
Provides convenience methods for importing module and class object
|
||||
denoted by a class_import_path string.
|
||||
"""
|
||||
|
||||
def __init__(self, class_import_path):
|
||||
"""Accepts a dotted class path."""
|
||||
split_path = class_import_path.rsplit(".", 1)
|
||||
if len(split_path) != 2:
|
||||
raise MalformedClassImportPathError(
|
||||
"Path '{cip}' was malformed.\nA dotted path ending in a "
|
||||
"class name was expected.".format(cip=class_import_path))
|
||||
self.module_path = split_path[0]
|
||||
self.class_name = split_path[1]
|
||||
self._original_class_import_path = class_import_path
|
||||
self._module = None
|
||||
self._class = None
|
||||
|
||||
def __repr__(self):
|
||||
s = "{ip}.{cn}".format(ip=self.module_path, cn=self.class_name)
|
||||
return s
|
||||
|
||||
def import_module(self):
|
||||
"""Import the module at import_path and return the module object"""
|
||||
|
||||
if self._module is None:
|
||||
try:
|
||||
self._module = importlib.import_module(self.module_path)
|
||||
except ImportError as ie:
|
||||
msg = "Could not import module from path '{p}: {e}".format(
|
||||
p=self.module_path,
|
||||
e=str(ie))
|
||||
raise ModuleNotImportableError(msg)
|
||||
return self._module
|
||||
|
||||
def import_class(self):
|
||||
"""Import the module at import_path and extract the class_name class"""
|
||||
if self._class is None:
|
||||
try:
|
||||
self._class = getattr(self.import_module(), self.class_name)
|
||||
except AttributeError as err:
|
||||
msg = (
|
||||
"Could not import class '{c}' from path '{p}': {e}".format(
|
||||
c=self.class_name,
|
||||
p=self._original_class_import_path,
|
||||
e=str(err)))
|
||||
raise ClassNotImportableError(msg)
|
||||
return self._class
|
||||
|
||||
|
||||
class _Brew(object):
|
||||
"""
|
||||
Returns a module object containing all generated classes
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, name, fixture_class=None, dsl=None, mixin_test_classes=None):
|
||||
self.name = name
|
||||
self.fixture_class = None
|
||||
self.dsl = None
|
||||
self.mixin_test_classes = list()
|
||||
|
||||
if fixture_class is not None:
|
||||
self.fixture_class = _ImportablePathWrapper(fixture_class)
|
||||
|
||||
if dsl is not None:
|
||||
self.dsl = _ImportablePathWrapper(dsl)
|
||||
|
||||
if mixin_test_classes is not None:
|
||||
if (not isinstance(mixin_test_classes, collections.Iterable) or
|
||||
isinstance(mixin_test_classes, string_types)):
|
||||
raise BrewMissingTestClassesError(
|
||||
"Brew was instantiated with an uniterable "
|
||||
"mixin_test_classes object of type {t}".format(
|
||||
t=type(mixin_test_classes)))
|
||||
self.mixin_test_classes = [
|
||||
_ImportablePathWrapper(tc) for tc in mixin_test_classes]
|
||||
|
||||
self.automodule_name = "{name}_automodule".format(name=self.name)
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
"\n{name}:\n\t"
|
||||
"automodule: {automodule_name}\n\t"
|
||||
"{fixture_attr}: {fixture}\n\t"
|
||||
"{dsl_attr}: {dsl}\n\t"
|
||||
"{testclasses_attr}:\n{tcs}\n".format(
|
||||
fixture_attr=FIXTURE_ATTR,
|
||||
dsl_attr=DATASETLIST_ATTR,
|
||||
testclasses_attr=TESTCLASSES_ATTR,
|
||||
name=self.name,
|
||||
automodule_name=self.automodule_name,
|
||||
fixture=self.fixture_class,
|
||||
dsl=self.dsl,
|
||||
tcs='\n'.join(["\t\t{s}{t}".format(
|
||||
s=" " * 5, t=t) for t in self.mixin_test_classes])))
|
||||
|
||||
def _generate_module(self, module_name):
|
||||
"""Create the container module for autgenerated classes."""
|
||||
|
||||
return types.ModuleType(
|
||||
module_name,
|
||||
"This module was auto-generated as a container for BrewFile-driven"
|
||||
" classes generated at runtime.")
|
||||
|
||||
def _register_module(self, module_object):
|
||||
"""Add the module to sys.modules so that it's importable elsewhere."""
|
||||
sys.modules[module_object.__name__] = module_object
|
||||
|
||||
def _generate_test_class(self, module_name=None):
|
||||
"""
|
||||
Create the aggregate test class by generating a new class that inherits
|
||||
from the fixture and all the test classes.
|
||||
The generated class's __module__ attribute will be set to module_name
|
||||
"""
|
||||
|
||||
# Import and append the test class objects to the bases list
|
||||
bases = list()
|
||||
|
||||
# Make sure the fixture is first in the method resolution order
|
||||
if self.fixture_class is not None:
|
||||
bases.append(self.fixture_class.import_class())
|
||||
|
||||
for tc in self.mixin_test_classes:
|
||||
bases.append(tc.import_class())
|
||||
|
||||
# Create the new test class from the bases list and register it as a
|
||||
# member of the automodule so that it can be properly imported later
|
||||
# (type requires that bases be a tuple)
|
||||
|
||||
class_dict = {}
|
||||
if module_name:
|
||||
class_dict['__module__'] = module_name
|
||||
|
||||
return type(
|
||||
self.name, tuple(bases), class_dict)
|
||||
|
||||
def __call__(self):
|
||||
"""Generates a module to contain the generated test fixture and
|
||||
all data generated test classes.
|
||||
Returns the module object"""
|
||||
|
||||
# Generate the automodule
|
||||
automodule = self._generate_module(self.automodule_name)
|
||||
|
||||
# add it to sys.modules
|
||||
self._register_module(automodule)
|
||||
|
||||
# Generate the aggregate test class
|
||||
test_class = self._generate_test_class(
|
||||
module_name=self.automodule_name)
|
||||
|
||||
if self.dsl is not None:
|
||||
# Instantiate the DataDrivenClass decorator with an instance of the
|
||||
# dsl_class
|
||||
dsl_class = self.dsl.import_class()
|
||||
|
||||
# Generate final data driven class aggregate by decorating the
|
||||
# aggregate test_class with the the dsl_class-driven
|
||||
# DataDrivenClass decorator.
|
||||
test_class = DataDrivenClass(dsl_class())(test_class)
|
||||
|
||||
# Add the ddtest_class to the automodule
|
||||
setattr(automodule, test_class.__name__, test_class)
|
||||
|
||||
return automodule
|
||||
|
||||
|
||||
class BrewFile(object):
|
||||
|
||||
def __init__(self, files):
|
||||
"""Accepts mutiple (config-like) run files and generates a
|
||||
consolidated representation of them, enforcing rules during parsing.
|
||||
|
||||
A BrewFile is a SafeConfigParser file, except:
|
||||
|
||||
The section 'cli-defaults' is special and can only be used for
|
||||
defining defaults for optional command-line arguments.
|
||||
(NOTE: This feature is not yet implemented)
|
||||
|
||||
All keys in any given section must be unique.
|
||||
|
||||
All section names across all files passed into BrewFile must be
|
||||
unique, with the exception of 'defaults' and 'cli-defaults', which
|
||||
are special and not vetted.
|
||||
|
||||
The section 'cli-defaults' should only appear once across all
|
||||
files passed into BrewFile.
|
||||
"""
|
||||
|
||||
self._log = cclogging.getLogger(
|
||||
cclogging.get_object_namespace(self.__class__))
|
||||
self.files = files
|
||||
self._data = self._validate_runfiles(files)
|
||||
|
||||
def __repr__(self):
|
||||
files = self._files_string()
|
||||
brews = self._brews_string()
|
||||
return "Files:\n{files}Brews:{brews}".format(files=files, brews=brews)
|
||||
|
||||
@property
|
||||
def cli_defaults(self):
|
||||
return dict(self._data.items('cli-defaults'))
|
||||
|
||||
def _brews_string(self):
|
||||
sub_brews = "\n\t".join(
|
||||
["\n\t".join(str(b).splitlines()) for b in self.iterbrews()])
|
||||
return "{brews}\n".format(brews=sub_brews)
|
||||
|
||||
def brews_to_strings(self):
|
||||
return self._brews_string().splitlines()
|
||||
|
||||
def _files_string(self):
|
||||
return "{files}\n".format(
|
||||
files='\n'.join(["{space}{file}".format(
|
||||
space="\t", file=f)for f in self.files]))
|
||||
|
||||
def brew_list(self):
|
||||
return [
|
||||
s for s in self._data.sections()
|
||||
if s.lower() not in RESERVED_SECTION_NAMES]
|
||||
|
||||
def iterbrews(self):
|
||||
""" Iterates through runfile sections and yields each individual
|
||||
section as a Brew object. You have to call .brew() on the individual
|
||||
Brews to get them to generate a module that contains the aggregate
|
||||
test class, so these should be safe to store in a list regardless of
|
||||
dataset size.
|
||||
"""
|
||||
for s in self.brew_list():
|
||||
attr_dict = dict(name=s)
|
||||
for attr in BREW_SECTION_ATTR_LIST:
|
||||
try:
|
||||
attr_dict[attr] = self._data.get(s, attr)
|
||||
except Exception:
|
||||
attr_dict[attr] = None
|
||||
|
||||
attr_dict[TESTCLASSES_ATTR] = (attr_dict.get(
|
||||
TESTCLASSES_ATTR) or "").strip().splitlines()
|
||||
b = _Brew(**attr_dict)
|
||||
yield b
|
||||
|
||||
def brew_modules(self):
|
||||
"""Returns a list of generated modules each with mixin_test_classes
|
||||
inside, based on the BrewFile's contents. For a large enough dataset
|
||||
this could be memory intensive, so it's recommended to use iterbrews
|
||||
and yield the module/mixin_test_classes to your runner one at a time.
|
||||
"""
|
||||
modules = list()
|
||||
for brew in self.iterbrews():
|
||||
module = brew()
|
||||
modules.append(module)
|
||||
return modules
|
||||
|
||||
@staticmethod
|
||||
def _validate_runfiles(files):
|
||||
""" Enforces the BrewFile rules on all provided files.
|
||||
If all files pass validation, a SafeConfigParser is returned.
|
||||
"""
|
||||
# Validate the config files individually
|
||||
for f in files:
|
||||
|
||||
# Make sure the file is actually there since config parser
|
||||
# fails silently when loading non-existant files
|
||||
if not os.path.isfile(f):
|
||||
msg = "Could not locate file '{f}'".format(f=f)
|
||||
raise RunFileNotFoundError(msg)
|
||||
|
||||
# TODO: Add checks for duplicate sections among multiple files
|
||||
cfg = configparser.SafeConfigParser()
|
||||
cfg.read(f)
|
||||
|
||||
# Check for incomplete sections, excluding reserved
|
||||
# sections.
|
||||
for section in [
|
||||
s for s in cfg.sections()
|
||||
if s not in RESERVED_SECTION_NAMES]:
|
||||
for attr in REQUIRED_BREW_SECTION_ATTR_LIST:
|
||||
try:
|
||||
cfg.get(section, attr)
|
||||
except configparser.NoOptionError:
|
||||
msg = (
|
||||
"\nSection '{sec}' in runfile '{filename}' is "
|
||||
"missing the '{attr}' option".format(
|
||||
filename=f, sec=s, attr=attr))
|
||||
raise RunFileIncompleteBrewError(msg)
|
||||
|
||||
# config files are valid, return aggregate config parser object
|
||||
cfg = configparser.SafeConfigParser()
|
||||
cfg.read(files)
|
||||
return cfg
|
|
@ -0,0 +1,89 @@
|
|||
import os
|
||||
import time
|
||||
from cafe.configurator.managers import TestEnvManager
|
||||
from cafe.common.reporting import cclogging
|
||||
from cafe.drivers.base import print_exception, get_error
|
||||
from cafe.drivers.unittest.brew.arguments import ArgumentParser
|
||||
from cafe.drivers.unittest.brew.parser import BrewFile
|
||||
from cafe.drivers.unittest.runner_parallel import UnittestRunner
|
||||
from cafe.drivers.unittest.suite_builder import SuiteBuilder
|
||||
|
||||
|
||||
class BrewRunner(UnittestRunner):
|
||||
"""OpenCafe BrewFile Runner"""
|
||||
def __init__(self):
|
||||
self.print_mug()
|
||||
self.cl_args = ArgumentParser().parse_args()
|
||||
self.test_env = TestEnvManager(
|
||||
"", self.cl_args.config, test_repo_package_name="")
|
||||
self.test_env.test_data_directory = self.test_env.test_data_directory
|
||||
self.test_env.finalize()
|
||||
cclogging.init_root_log_handler()
|
||||
|
||||
# This is where things diverge from the regular parallel runner
|
||||
# Extract the runfile contents
|
||||
self._log = cclogging.getLogger(
|
||||
cclogging.get_object_namespace(self.__class__))
|
||||
self.datagen_start = time.time()
|
||||
self.run_file = BrewFile(self.cl_args.runfiles)
|
||||
|
||||
# TODO: Once the parallel_runner is changed to a yielding model,
|
||||
# change this to yielding brews instead of generating a list
|
||||
self.suites = SuiteBuilder(
|
||||
testrepos=self.run_file.brew_modules(),
|
||||
dry_run=self.cl_args.dry_run,
|
||||
exit_on_error=True).get_suites()
|
||||
|
||||
self.print_configuration(self.test_env, runfile=self.run_file)
|
||||
|
||||
def print_configuration(self, test_env, repos=None, runfile=None):
|
||||
"""Prints the config/logs/repo/data_directory"""
|
||||
print("=" * 150)
|
||||
print("Percolated Configuration")
|
||||
print("-" * 150)
|
||||
if runfile:
|
||||
print("BREW FILES........:")
|
||||
print("\t\t " + "\n\t\t ".join(runfile.files))
|
||||
if self.cl_args.verbose == 3:
|
||||
print("BREWS............:")
|
||||
print "\t" + "\n\t".join(runfile.brews_to_strings())
|
||||
if repos:
|
||||
print("BREWING FROM: ....: {0}".format(repos[0]))
|
||||
for repo in repos[1:]:
|
||||
print("{0}{1}".format(" " * 20, repo))
|
||||
self._log.debug(str(runfile))
|
||||
print("ENGINE CONFIG FILE: {0}".format(test_env.engine_config_path))
|
||||
print("TEST CONFIG FILE..: {0}".format(test_env.test_config_file_path))
|
||||
print("DATA DIRECTORY....: {0}".format(test_env.test_data_directory))
|
||||
print("LOG PATH..........: {0}".format(test_env.test_log_dir))
|
||||
print("=" * 150)
|
||||
|
||||
@staticmethod
|
||||
def print_mug():
|
||||
"""Prints the cafe mug"""
|
||||
print("""
|
||||
/~~~~~~~~~~~~~~~~~~~~~~~/|
|
||||
/ /######/ / |
|
||||
/ /______/ / |
|
||||
========================= /||
|
||||
|_______________________|/ ||
|
||||
| \****/ \__,,__/ ||
|
||||
|===\**/ __,,__ ||
|
||||
|______________\====/%____||
|
||||
| ___ /~~~~\ % / |
|
||||
_| |===|=== / \%_/ |
|
||||
| | |###| |########| | /
|
||||
|____\###/______\######/__|/
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
=== CAFE Brewfile Runner ===""")
|
||||
|
||||
|
||||
def entry_point():
|
||||
"""setup.py script entry point"""
|
||||
try:
|
||||
runner = BrewRunner()
|
||||
exit(runner.run())
|
||||
except KeyboardInterrupt:
|
||||
print_exception(
|
||||
"BrewRunner", "run", "Keyboard Interrupt, exiting...")
|
||||
os.killpg(0, 9)
|
|
@ -0,0 +1,7 @@
|
|||
[DoStuff_WithData]
|
||||
fixture_class=testrepo.fixtures.SomeTestFixtureA
|
||||
dsl=testrepo.dsl.SomeDatasetList
|
||||
mixin_test_classes=
|
||||
testrepo.test_mixins.TestMixinClass1
|
||||
testrepo.test_mixins.TestMixinClass2
|
||||
testrepo.test_mixins.TestMixinClass3
|
1
setup.py
1
setup.py
|
@ -72,6 +72,7 @@ setup(
|
|||
'console_scripts':
|
||||
['cafe-runner = cafe.drivers.unittest.runner:entry_point',
|
||||
'cafe-parallel = cafe.drivers.unittest.runner_parallel:entry_point',
|
||||
'cafe-brew = cafe.drivers.unittest.brew.runner:entry_point',
|
||||
'behave-runner = cafe.drivers.behave.runner:entry_point',
|
||||
'vows-runner = cafe.drivers.pyvows.runner:entry_point',
|
||||
'specter-runner = cafe.drivers.specter.runner:entry_point',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pip
|
||||
virtualenv
|
||||
tox
|
||||
mock
|
||||
flake8
|
||||
nose
|
||||
virtualenv
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import importlib
|
||||
import sys
|
||||
import unittest
|
||||
from cafe.drivers.unittest.brew.parser import (
|
||||
_ImportablePathWrapper, MalformedClassImportPathError,
|
||||
ModuleNotImportableError, ClassNotImportableError, _Brew,
|
||||
BrewMissingTestClassesError)
|
||||
|
||||
|
||||
class ImportablePathWrapper_Tests(unittest.TestCase):
|
||||
|
||||
def test_raises_MalformedClassImportPathError_on_malformed_path(self):
|
||||
self.assertRaises(
|
||||
MalformedClassImportPathError, _ImportablePathWrapper,
|
||||
'unittest/TestCase')
|
||||
|
||||
def test_raises_ClassNotImportableError_on_non_existant_class(self):
|
||||
i = _ImportablePathWrapper("collections.aaaaDoesNotExistaaa")
|
||||
self.assertRaises(ClassNotImportableError, i.import_class)
|
||||
|
||||
def test_raises_ModuleNotImportableError_on_non_existant_module(self):
|
||||
i = _ImportablePathWrapper("aaaa.aaaa.aaaa")
|
||||
self.assertRaises(ModuleNotImportableError, i.import_module)
|
||||
|
||||
def test_import_module_happy_path(self):
|
||||
i = _ImportablePathWrapper("collections.OrderedDict")
|
||||
m = i.import_module()
|
||||
self.assertEqual(type(m.OrderedDict()).__name__, 'OrderedDict')
|
||||
|
||||
def test_import_class_happy_path(self):
|
||||
i = _ImportablePathWrapper("collections.OrderedDict")
|
||||
c = i.import_class()
|
||||
self.assertEqual(type(c()).__name__, 'OrderedDict')
|
||||
|
||||
|
||||
class Brew_Tests(unittest.TestCase):
|
||||
test_fixture = "unittest.TestCase"
|
||||
test_class = "collections.OrderedDict"
|
||||
dsl = "cafe.drivers.unittest.datasets.DatasetList"
|
||||
module_name = "FakeBrew"
|
||||
|
||||
def test_init_name_only(self):
|
||||
try:
|
||||
_Brew(self.module_name)
|
||||
except:
|
||||
self.fail("Unable to instantiate Brew with only a name")
|
||||
|
||||
def test_call_brew_initialized_with_only_a_name(self):
|
||||
b = _Brew(self.module_name)
|
||||
module_ = b()
|
||||
new_name = "FakeBrew_automodule"
|
||||
self.assertEquals(module_.__name__, new_name)
|
||||
|
||||
def test_init_raises_BrewMissingTestClassesError_non_iterable(self):
|
||||
self.assertRaises(
|
||||
BrewMissingTestClassesError, _Brew, "FakeBrew",
|
||||
"collections.OrderedDict", "collections.OrderedDict", 1)
|
||||
|
||||
def test_init_raises_BrewMissingTestClassesError_string(self):
|
||||
self.assertRaises(
|
||||
BrewMissingTestClassesError, _Brew, "FakeBrew",
|
||||
"collections.OrderedDict", "collections.OrderedDict", "string")
|
||||
|
||||
def test_init_BrewMissingTestClassesError_bool_non_iterable(self):
|
||||
self.assertRaises(
|
||||
BrewMissingTestClassesError, _Brew, "FakeBrew",
|
||||
"collections.OrderedDict", "collections.OrderedDict", True)
|
||||
|
||||
def test_generate_module_correct_type(self):
|
||||
b = _Brew(
|
||||
self.module_name, self.test_fixture, self.dsl,
|
||||
[self.test_class])
|
||||
|
||||
m = b._generate_module(self.module_name)
|
||||
self.assertEqual(
|
||||
type(m).__name__, 'module',
|
||||
"_generate_module did not return a module")
|
||||
|
||||
def test_generate_module_correct_name(self):
|
||||
b = _Brew(
|
||||
self.module_name, self.test_fixture, self.dsl,
|
||||
[self.test_class])
|
||||
|
||||
m = b._generate_module(self.module_name)
|
||||
self.assertEqual(
|
||||
m.__name__, self.module_name,
|
||||
"_generate_module did not return a module with the correct name")
|
||||
|
||||
def test_module_registration(self):
|
||||
b = _Brew(
|
||||
self.module_name, self.test_fixture, self.dsl,
|
||||
[self.test_fixture])
|
||||
|
||||
m = b._generate_module(self.module_name)
|
||||
b._register_module(m)
|
||||
self.assertIn(
|
||||
self.module_name, sys.modules.keys(),
|
||||
"_register_module failed to register {} in sys.modules")
|
||||
|
||||
def test_registered_module_is_importable(self):
|
||||
b = _Brew(
|
||||
self.module_name, self.test_fixture, self.dsl,
|
||||
[self.test_fixture])
|
||||
|
||||
m = b._generate_module(self.module_name)
|
||||
b._register_module(m)
|
||||
try:
|
||||
importlib.import_module(m.__name__)
|
||||
except ImportError:
|
||||
self.fail("Unable to import registered module")
|
||||
|
||||
def test_generate_test_class_name_and_fixture_only(self):
|
||||
b = _Brew(self.module_name, fixture_class=self.test_fixture)
|
||||
gclass = b._generate_test_class()
|
||||
self.assertEqual(gclass.__name__, b.name)
|
||||
self.assertEqual(gclass.__name__, self.module_name)
|
||||
self.assertTrue(issubclass(gclass, unittest.TestCase))
|
Loading…
Reference in New Issue