proboscis-1.2.6.0/00007550000000_j0000000000012116673365013370 5ustar root00000000000000proboscis-1.2.6.0/PKG-INFO00006440000000_j0000000137512116673365014473 0ustar root00000000000000Metadata-Version: 1.0 Name: proboscis Version: 1.2.6.0 Summary: Extends Nose with certain TestNG like features. Home-page: https://github.com/rackspace/python-proboscis Author: Rackspace Author-email: tim.simpson@rackspace.com License: Apache Description: Proboscis is a Python test framework that extends Python's built-in unittest module and Nose with features from TestNG. Keywords: nose test testng Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python proboscis-1.2.6.0/proboscis/00007550000000_j0000000000012116673365015373 5ustar root00000000000000proboscis-1.2.6.0/proboscis/__init__.py0000644 3._j0000000267111727303055021036 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Extension for Nose to facilitate higher level testing. Changes how tests are discovered by forcing them to use a common decorator which contains useful metadata such as whether or not they have dependencies on other tests. This allows larger processes or stories to be modelled and run as tests. Much of this functionality was "inspired" by TestNG. """ from proboscis.core import ProboscisTestMethodClassNotDecorated from proboscis.dependencies import SkipTest from proboscis.case import TestPlan from proboscis.case import TestProgram from proboscis.core import TestRegistry from proboscis.decorators import after_class from proboscis.decorators import before_class from proboscis.decorators import factory from proboscis.decorators import register from proboscis.decorators import test from proboscis.case import TestResult proboscis-1.2.6.0/proboscis/asserts.py0000644 3._j0000001720512116670632020763 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Assert functions with a parameter order of actual_value, expected_value. This module contains many stand-ins for functions in Nose.tools. It is also a clone of TestNG's Assert class with the static methods changed to functions, and the term "equals" changed to simply "equal" to be more Pythonic. There are also a few original assertions methods and the class Check. This module should be preferred when Nose is not always available. """ import sys import traceback from proboscis import compatability ASSERTION_ERROR=AssertionError # Setting this causes stack traces shown by unittest and nose to stop before # this moudle. It feels dirty but modifying the traceback is even worse. __unittest = True def assert_equal(actual, expected, message=None): """Asserts that the two values are equal. :param actual: The actual value. :param expected: The expected value. :param message: A message to show in the event of a failure. """ #TODO: assert equal with dictionaries, arrays, etc if actual == expected: return if not message: try: message = "%s != %s" % (actual, expected) except Exception: message = "The actual value did not equal the expected one." raise ASSERTION_ERROR(message) def assert_false(condition, message=None): """Asserts that the given condition is false. :param condition: Must be true. :param message: A message to show in the event of failure. """ if condition: if not message: message = "Condition was True." raise ASSERTION_ERROR(message) def assert_is(actual, expected, message=None): """Asserts that the two variables share the same identity. :param actual: A variable which has the actual identity. :param expected: The variable which has the expected variable. :param message: A message to show in the event of failure. """ #TODO: assert equal with dictionaries, arrays, etc if actual is expected: return if not message: try: message = "%s is not %s" % (actual, expected) except Exception: message = "The actual value is not the expected one." raise ASSERTION_ERROR(message) def assert_is_none(value, message=None): """Asserts that the given value is None. :param value: The value which is tested for nothingness. :param message: A message to show in the event of failure. """ #TODO: assert equal with dictionaries, arrays, etc if value is None: return if not message: try: message = "%s is not None" % value except Exception: message = "The value is not None." raise ASSERTION_ERROR(message) def assert_is_not(actual, expected, message=None): """Asserts that the two variables has different identities. :param actual: A variable which has the actual identity. :param expected: A variable which has the expected identity. :param message: The assertion message if the variables share an identity. """ #TODO: assert equal with dictionaries, arrays, etc if actual is not expected: return if not message: try: message = "%s is %s" % (actual, expected) except Exception: message = "The actual value is the expected one." raise ASSERTION_ERROR(message) def assert_is_not_none(value, message=None): """Asserts that a value is anything other than None. :param value: A variable which is expected to be anything other than None. :param message: The assertion message if the variable is None. """ #TODO: assert equal with dictionaries, arrays, etc if value is not None: return if not message: message = "The value is None." raise ASSERTION_ERROR(message) def assert_not_equal(actual, expected, message=None): """Asserts that the two values are not equal. :param actual: The actual value. :param expected: The expected value. :param message: The assertion message if the variables are equal. """ if (actual != expected) and not (actual == expected): return if not message: try: message = "%s == %s" % (actual, expected) except Exception: message = "The actual value equalled the expected one." raise ASSERTION_ERROR(message) def assert_true(condition, message=None): """Asserts that the given value is True. :param condition: A value that must be True. :param message: The assertion message if the value is not True. """ if not condition: if not message: message = "Condition was False." raise ASSERTION_ERROR(message) def assert_raises(exception_type, function, *args, **kwargs): """Calls function and fails the test if an exception is not raised. Unlike nose.Tool's assert_raises or TestCase.assertRaises the given exception type must match the exactly: if the raised exception is a subclass the test will fail. For example, it fails if the exception_type param is "Exception" but "RuntimeException" is raised. To be less demanding use assert_raises_instance. :param exception_type: The exact type of exception to be raised. :param function: The function to call, followed by its arguments. """ actual_exception = compatability.capture_exception( lambda : function(*args, **kwargs), exception_type) if actual_exception is None: fail("Expected an exception of type %s to be raised." % exception_type) elif type(actual_exception) != exception_type: _a, _b, tb = sys.exc_info() info = traceback.format_list(traceback.extract_tb(tb)) fail("Expected a raised exception of type %s, but found type %s. " "%s" % (exception_type, type(actual_exception), info)) return actual_exception def assert_raises_instance(exception_type, function, *args, **kwargs): """Calls function and fails the test if an exception is not raised. The exception thrown must only be an instance of the given type. This means if "Exception" is expected but "RuntimeException" is raised the test will still pass. For a stricter function see assert_raises. :param exception_type: The expected exception type. :param function: The function to call, followed by its arguments. """ actual_exception = compatability.capture_exception( lambda : function(*args, **kwargs), exception_type) if actual_exception is None: fail("Expected an exception of type %s to be raised." % exception_type) def fail(message): """Fails a test. :param message: The message to display. Unlike the other functions in this module the message argument is required. """ if not message: message = "Test failure." raise ASSERTION_ERROR(message) from proboscis.check import Check __all__ = [ 'assert_equal', 'assert_false', 'assert_is', 'assert_is_none', 'assert_is_not', 'assert_is_not_none', 'assert_not_equal', 'assert_true', 'assert_raises', 'assert_raises_instance', 'fail', ] proboscis-1.2.6.0/proboscis/case.py0000644 3._j0000005414212116670632020213 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Creates TestCases from a list of TestEntries. This module mainly exists to translate Proboscis classes and concepts into the unittest equivalents. """ import os import pydoc import types import unittest import sys from collections import deque from functools import wraps from proboscis import compatability from proboscis import dependencies from proboscis import SkipTest from proboscis.sorting import TestGraph from proboscis.core import TestMethodClassEntry from proboscis.decorators import DEFAULT_REGISTRY # This is here so Proboscis own test harness can change it while still calling # TestProgram normally. Its how the examples are tested. OVERRIDE_DEFAULT_STREAM = None class TestPlan(object): """Grabs information from the TestRegistry and creates a test plan.""" def __init__(self, groups, test_entries, factories): test_cases = self.create_cases(test_entries, factories) graph = TestGraph(groups, test_entries, test_cases) self.tests = graph.sort() @staticmethod def create_from_registry(registry): """Returns a sorted TestPlan from a TestRegistry instance.""" return TestPlan(registry.groups, registry.tests, registry.factories) @staticmethod def create_cases_from_instance(factory, instance): if isinstance(instance, type): raise RuntimeError("Factory %s returned type %s (rather than an " "instance), which is not allowed." % (factory, instance)) if isinstance(instance, types.MethodType): home = compatability.get_method_function(instance) elif isinstance(instance, types.FunctionType): home = instance else: home = type(instance) if issubclass(home, unittest.TestCase): raise RuntimeError("Factory %s returned a unittest.TestCase " "instance %s, which is not legal.") try: entry = home._proboscis_entry_ except AttributeError: raise RuntimeError("Factory method %s returned an instance %s " "which was not tagged as a Proboscis TestEntry." % (factory, instance)) entry.mark_as_used_by_factory() # Don't iterate this since a # function is creating it. if entry.is_child: raise RuntimeError("Function %s, which exists as a bound method " "in a decorated class may not be returned from a factory." % instance) # There is potentially an issue in that a different Registry might # register an entry, and we could then read that in with a factory. # Later the entry would not be found in the dictionary of entries. if isinstance(instance, types.MethodType): try: state = TestMethodState(instance.im_self) except AttributeError: raise RuntimeError("Only bound methods may be returned from " "factories. %s is not bound." % instance) else: state = TestMethodState(entry, instance) return TestPlan._create_test_cases_for_entry(entry, state) @staticmethod def create_cases(test_entries, factories): tests = [] entries = {} for factory in factories: list = factory() for item in list: cases = TestPlan.create_cases_from_instance(factory, item) tests += cases for entry in test_entries: if not entry.is_child and not entry.used_by_factory: test_cases = TestPlan._create_test_cases_for_entry(entry) entries[entry] = test_cases tests += test_cases return tests @staticmethod def _create_test_cases_for_entry(entry, state=None): """Processes a test case entry.""" if not hasattr(entry, 'children'): # function or unittest.TestCase return [TestCase(entry)] state = state or TestMethodState(entry) cases = [] for child_entry in entry.children: case = TestCase(child_entry, state=state) cases.append(case) return cases def create_test_suite(self, config, loader): """Transforms the plan into a Nose test suite.""" creator = TestSuiteCreator(loader) if dependencies.use_nose: from nose.suite import ContextSuiteFactory suite = ContextSuiteFactory(config)([]) else: suite = unittest.TestSuite() for case in self.tests: if case.entry.info.enabled and case.entry.home is not None: tests = creator.loadTestsFromTestEntry(case) for test in tests: suite.addTest(test) return suite def filter(self, group_names=None, classes=None, functions=None): """Whittles down test list to those matching criteria.""" test_homes = [] classes = classes or [] functions = functions or [] for cls in classes: test_homes.append(cls) for function in functions: test_homes.append(function) group_names = group_names or [] filtered_list = [] while self.tests: case = self.tests.pop() if case.entry.contains(group_names, test_homes): filtered_list.append(case) # Add any groups this depends on so they will run as well. for group_name in case.entry.info.depends_on_groups: if not group_name in group_names: group_names.append(group_name) for test_home in case.entry.info.depends_on: if not test_home in test_homes: test_homes.append(test_home) self.tests = list(reversed(filtered_list)) class TestCase(object): """Represents an instance of a TestEntry. This class is also used to store status information, such as the dependent TestEntry objects (discovered when this test is sorted) and any failure in the dependencies of this test (used to raise SkipTest if needed). There may be multiple TestCase instances for each TestEntry instance. """ def __init__(self, entry, state=None): self.entry = entry self.dependents = [] # This is populated when we sort the tests. self.dependency_failure = None self.state = state def check_dependencies(self, test_self): """If a dependency has failed, SkipTest is raised.""" if self.dependency_failure is not None and \ self.dependency_failure != self and not self.entry.info.always_run: home = self.dependency_failure.entry.home dependencies.skip_test(test_self, "Failure in %s" % home) def fail_test(self, dependency_failure=None): """Called when this entry fails to notify dependents.""" if not dependency_failure: dependency_failure = self if not self.dependency_failure: # Do NOT overwrite the first cause self.dependency_failure = dependency_failure for dependent in self.dependents: if dependent.critical: dependent.case.fail_test( dependency_failure=dependency_failure) def write_doc(self, file): file.write(str(self.entry.home) + "\n") doc = pydoc.getdoc(self.entry.home) if doc: file.write(doc + "\n") for field in str(self.entry.info).split(', '): file.write("\t" + field + "\n") def __repr__(self): return "TestCase(" + repr(self.entry.home) + ", " + \ repr(self.entry.info) + ", " + object.__repr__(self) + ")" def __str__(self): return "Home = " + str(self.entry.home) + ", Info(" + \ str(self.entry.info) + ")" class TestResultListener(): """Implements methods of TestResult to be informed of test failures.""" def __init__(self, chain_to_cls): self.chain_to_cls = chain_to_cls def addError(self, test, err): self.onError(test) self.chain_to_cls.addError(self, test, err) def addFailure(self, test, err): self.onError(test) self.chain_to_cls.addFailure(self, test, err) def onError(self, test): """Notify a test entry and its dependents of failure.""" if dependencies.use_nose: root = test.test else: root = test if hasattr(root, "__proboscis_case__"): case = root.__proboscis_case__ case.fail_test() class TestResult(TestResultListener, dependencies.TextTestResult): """Adds Proboscis skip on dependency failure functionality. Extends either Nose or unittest's TextTestResult class. If a program needs to use its own TestResult class it must inherit from this class and call "onError" at the start of both the addError and addFailure functions, passing the "test" parameter, to keep Proboscis's skip on depdendency failure functionality. """ # I had issues extending TextTestResult directly so resorted to this. def __init__(self, *args, **kwargs): TestResultListener.__init__(self, dependencies.TextTestResult) dependencies.TextTestResult.__init__(self, *args, **kwargs) def test_runner_cls(wrapped_cls, cls_name): """Creates a test runner class which uses Proboscis TestResult.""" new_dict = wrapped_cls.__dict__.copy() if dependencies.use_nose: def cb_make_result(self): return TestResult(self.stream, self.descriptions, self.verbosity, self.config) else: def cb_make_result(self): return TestResult(self.stream, self.descriptions, self.verbosity) new_dict["_makeResult"] = cb_make_result return type(cls_name, (wrapped_cls,), new_dict) def skippable_func(test_case, func): """Gives free functions a Nose independent way of skipping a test. The unittest module TestCase class has a skipTest method, but to run it you need access to the TestCase class. This wraps the runTest method of the underlying unittest.TestCase subclass to invoke the skipTest method if it catches the SkipTest exception. """ s_func = None if dependencies.use_nose: s_func = func else: @wraps(func) def skip_capture_func(): st = compatability.capture_exception(func, SkipTest) if st is not None: dependencies.skip_test(test_case, st.message) s_func = skip_capture_func @wraps(s_func) def testng_method_mistake_capture_func(): compatability.capture_type_error(s_func) return testng_method_mistake_capture_func class FunctionTest(unittest.FunctionTestCase): """Wraps a single function as a test runnable by unittest / nose.""" def __init__(self, test_case): func = test_case.entry.home _old_setup = None if hasattr(func, 'setup'): # Don't destroy nose-style setup _old_setup = func.setup def cb_check(cb_self=None): test_case.check_dependencies(self) if _old_setup is not None: _old_setup() self.__proboscis_case__ = test_case sfunc = skippable_func(self, func) unittest.FunctionTestCase.__init__(self, testFunc=sfunc, setUp=cb_check) class TestMethodState(object): """Manages a test class instance used by one or more test methods.""" def __init__(self, entry, instance=None): self.entry = entry # This would be a simple "isinstance" but due to the reloading mania # needed for Proboscis's documentation tests it has to be a bit # weirder. if not str(type(self.entry)) == str(TestMethodClassEntry): raise RuntimeError("%s is not a TestMethodClassEntry but is a %s." % (self.entry, type(self.entry))) self.instance = instance def get_state(self): if not self.instance: self.instance = self.entry.home() return self.instance class MethodTest(unittest.FunctionTestCase): """Wraps a method as a test runnable by unittest.""" def __init__(self, test_case): assert test_case.state is not None #TODO: Figure out how to attach calls to BeforeMethod and BeforeClass, # AfterMethod and AfterClass. It should be easy enough to # just find them using the TestEntry parent off test_case Entrty. def cb_check(cb_self=None): test_case.check_dependencies(self) @wraps(test_case.entry.home) def func(self=None): # Called by FunctionTestCase func = test_case.entry.home func(test_case.state.get_state()) self.__proboscis_case__ = test_case sfunc = skippable_func(self, func) unittest.FunctionTestCase.__init__(self, testFunc=sfunc, setUp=cb_check) def decorate_class(setUp_method=None, tearDown_method=None): """Inserts method calls in the setUp / tearDown methods of a class.""" def return_method(cls): """Returns decorated class.""" new_dict = cls.__dict__.copy() if setUp_method: if hasattr(cls, "setUp"): @wraps(setUp_method) def _setUp(self): setUp_method(self) cls.setUp(self) else: @wraps(setUp_method) def _setUp(self): setUp_method(self) new_dict["setUp"] = _setUp if tearDown_method: if hasattr(cls, "tearDown"): @wraps(tearDown_method) def _tearDown(self): tearDown_method(self) cls.setUp(self) else: @wraps(tearDown_method) def _tearDown(self): tearDown_method(self) new_dict["tearDown"] = _tearDown return type(cls.__name__, (cls,), new_dict) return return_method class TestSuiteCreator(object): """Turns Proboscis test cases into elements to be run by unittest.""" def __init__(self, loader): self.loader = loader def loadTestsFromTestEntry(self, test_case): """Wraps a test class in magic so it will skip on dependency failures. Decorates the testEntry class's setUp method to raise SkipTest if tests this test was dependent on failed or had errors. """ home = test_case.entry.home if home is None: return [] if isinstance(home, type): return self.wrap_unittest_test_case_class(test_case) if isinstance(home, types.FunctionType): if home._proboscis_entry_.is_child: return self.wrap_method(test_case) else: return self.wrap_function(test_case) raise RuntimeError("Unknown test type:" + str(type(home))) def wrap_function(self, test_case): return [FunctionTest(test_case)] def wrap_method(self, test_case): return [MethodTest(test_case)] def wrap_unittest_test_case_class(self, test_case): original_cls = test_case.entry.home def cb_check(cb_self): test_case.check_dependencies(cb_self) testCaseClass = decorate_class(setUp_method=cb_check)(original_cls) testCaseNames = self.loader.getTestCaseNames(testCaseClass) if not testCaseNames and hasattr(testCaseClass, 'runTest'): testCaseNames = ['runTest'] suite = [] if issubclass(original_cls, unittest.TestCase): for name in testCaseNames: test_instance = testCaseClass(name) setattr(test_instance, "__proboscis_case__", test_case) suite.append(test_instance) return suite class TestProgram(dependencies.TestProgram): """Use this to run Proboscis. Translates the Proboscis test registry into types used by Nose or unittest in order to run the program. Most arguments to this are simply passed to Nose or unittest's TestProgram class. For most cases using the default arguments works fine. :param registry: The test registry to use. If unset uses the default global registry. :param groups: A list of strings representing the groups of tests to run. The list is added to by parsing the argv argument. If unset then all groups are run. :param testLoader: The test loader. By default, its unittest.TestLoader. :param config: The config passed to Nose or unittest.TestProgram. The config determines things such as plugins or output streams, so it may be necessary to create this for advanced use cases. :param plugins: Nose plugins. Similar to config it may be necessary to set this in an advanced setup. :param env: By default is os.environ. This is used only by Nose. :param testRunner: By default Proboscis uses its own. If this is set however care must be taken to avoid breaking Proboscis's automatic skipping of tests on dependency failures. In particular, _makeResult must return a subclass of proboscis.TestResult which calls proboscis.TestResult.onError at the start of the addFailure and addError methods. :param stream: By default this is standard out. :param argv: By default this is sys.argv. Proboscis parses this for the --group argument. """ def __init__(self, registry=DEFAULT_REGISTRY, groups=None, testLoader=None, config=None, plugins=None, env=None, testRunner=None, stream=None, argv=None, *args, **kwargs): groups = groups or [] argv = argv or sys.argv argv = self.extract_groups_from_argv(argv, groups) if "suite" in kwargs: raise ValueError("'suite' is not a valid argument, as Proboscis " \ "creates the suite.") self.__loader = testLoader or unittest.TestLoader() if OVERRIDE_DEFAULT_STREAM: stream = OVERRIDE_DEFAULT_STREAM if env is None: env = os.environ if dependencies.use_nose and config is None: config = self.makeConfig(env, plugins) if not stream: stream = config.stream stream = stream or sys.stdout if testRunner is None: runner_cls = test_runner_cls(dependencies.TextTestRunner, "ProboscisTestRunner") if dependencies.use_nose: testRunner = runner_cls(stream, verbosity=3, # config.verbosity, config=config) else: testRunner = runner_cls(stream, verbosity=3) #registry.sort() self.plan = TestPlan.create_from_registry(registry) if len(groups) > 0: self.plan.filter(group_names=groups) self.cases = self.plan.tests if "--show-plan" in argv: self.__run = self.show_plan else: self.__suite = self.create_test_suite_from_entries(config, self.cases) def run(): if dependencies.use_nose: dependencies.TestProgram.__init__( self, suite=self.__suite, config=config, env=env, plugins=plugins, testLoader=testLoader, # Pass arg, not what we create testRunner=testRunner, argv=argv, *args, **kwargs ) else: dependencies.TestProgram.__init__( self, suite=self.__suite, config=config, testLoader=testLoader, # Pass arg, not what we create testRunner=testRunner, argv=argv, *args, **kwargs ) self.__run = run def create_test_suite_from_entries(self, config, cases): """Creates a suite runnable by unittest.""" return self.plan.create_test_suite(config, self.__loader) def extract_groups_from_argv(self, argv, groups): """Given argv, records the "--group" options. :param argv: A list of arguments, such as sys.argv. :param groups: A list of strings for each group to run which is added to. Returns a copy of param argv with the --group options removed. This is useful if argv needs to be passed to another program such as Nose. """ new_argv = [argv[0]] for arg in argv[1:]: if arg[:8] == "--group=": groups.append(arg[8:]) else: new_argv.append(arg) return new_argv def run_and_exit(self): """Calls unittest or Nose to run all tests. unittest will call sys.exit on completion. """ self.__run() def show_plan(self): """Prints information on test entries and the order they will run.""" print(" * * * Test Plan * * *") for case in self.cases: case.write_doc(sys.stdout) @property def test_suite(self): return self.__suite proboscis-1.2.6.0/proboscis/check.py0000664 3._j0000001224711776650564020374 0ustar tim.simpson00000000000000# Copyright (c) 2012 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Like asserts, but does not raise an exception until the end of a block.""" import traceback from proboscis.asserts import assert_equal from proboscis.asserts import assert_not_equal from proboscis.asserts import assert_is from proboscis.asserts import assert_is_not from proboscis.asserts import assert_is_not_none from proboscis.asserts import assert_false from proboscis.asserts import assert_true from proboscis.asserts import assert_raises from proboscis.asserts import assert_raises_instance from proboscis.asserts import assert_is_none from proboscis.asserts import ASSERTION_ERROR from proboscis.compatability import capture_exception from proboscis.compatability import raise_with_traceback from proboscis.asserts import fail def get_stack_trace_of_caller(level_up): """Gets the stack trace at the point of the caller.""" level_up += 1 st = traceback.extract_stack() caller_index = len(st) - level_up if caller_index < 0: caller_index = 0 new_st = st[0:caller_index] return new_st class Check(object): """Used to test and record multiple failing asserts in a single function. Usually its best to write numerous small methods with a single assert in each one, but sometimes this ideal isn't possible and multiple asserts must be made in the same function. In some cases, such as when all properties of a returned object are being interrogated, these asserts do not depend on each other and having the test stop after the first one can be a bother if the task was run on a CI server or somewhere else. This class solves that by saving any assert failures and raising one giant assert at the end of a with block. To use it, write something like: .. code-block:: python some_obj = ... with Check() as check: check.equal(some_obj.a, "A") check.equal(some_obj.b, "B") check.equal(some_obj.c, "C") At the end of the with block if any checks failed one assertion will be raised containing inside it the stack traces for each assertion. If instances are not used in a with block any failed assert will raise instantly. """ def __init__(self): self.messages = [] self.odd = True self.protected = False def _add_exception(self, _type, value, tb): """Takes an exception, and adds it as a string.""" if self.odd: prefix = "* " else: prefix = "- " start = "Check failure! Traceback:" middle = prefix.join(traceback.format_list(tb)) end = '\n'.join(traceback.format_exception_only(_type, value)) msg = '\n'.join([start, middle, end]) self.messages.append(msg) self.odd = not self.odd def _run_assertion(self, assert_func, *args, **kwargs): """ Runs an assertion method, but catches any failure and adds it as a string to the messages list. """ if self.protected: def func(): assert_func(*args, **kwargs) ae = capture_exception(func, ASSERTION_ERROR) if ae is not None: st = get_stack_trace_of_caller(2) self._add_exception(ASSERTION_ERROR, ae, st) else: assert_func(*args, **kwargs) def __enter__(self): self.protected = True return self def __exit__(self, _type, value, tb): self.protected = False if len(self.messages) == 0: final_message = None else: final_message = '\n'.join(self.messages) if _type is not None: # An error occurred if len(self.messages) == 0: raise_with_traceback(_type, value, tb) self._add_exception(_type, value, traceback.extract_tb(tb)) if len(self.messages) != 0: final_message = '\n'.join(self.messages) raise ASSERTION_ERROR(final_message) def add_assert_method(name, func): def f(self, *args, **kwargs): self._run_assertion(func, *args, **kwargs) f.__doc__ = "Identical to %s." % func.__name__ setattr(Check, name, f) add_assert_method("equal", assert_equal) add_assert_method("not_equal", assert_not_equal) add_assert_method("false", assert_false) add_assert_method("true", assert_true) add_assert_method("is_same", assert_is) add_assert_method("is_none", assert_is_none) add_assert_method("is_not", assert_is_not) add_assert_method("is_not_none", assert_is_not_none) add_assert_method("raises", assert_raises) add_assert_method("raises_instance", assert_raises_instance) add_assert_method("fail", fail) proboscis-1.2.6.0/proboscis/compatability/00007550000000_j0000000000012116673365020234 5ustar root00000000000000proboscis-1.2.6.0/proboscis/compatability/__init__.py0000644 3._j0000000361412116670632023676 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import inspect import sys import types if sys.version_info >= (2, 6): from proboscis.compatability.exceptions_2_6 import capture_exception from proboscis.compatability.exceptions_2_6 import capture_type_error else: from proboscis.compatability.exceptions_2_5 import capture_exception from proboscis.compatability.exceptions_2_5 import capture_type_error if sys.version_info >= (3, 0): import imp reload = imp.reload from proboscis.compatability.raise_3_x import raise_with_traceback def get_class_methods(cls): members = inspect.getmembers(cls, inspect.isfunction) return [member[1] for member in members] def get_method_function(method): return method else: reload = reload from proboscis.compatability.raise_2_x import raise_with_traceback def get_class_methods(cls): members = inspect.getmembers(cls, inspect.ismethod) return [member[1] for member in members] def get_method_function(method): return method.im_func _IS_JYTHON = "Java" in str(sys.version) or hasattr(sys, 'JYTHON_JAR') def is_jython(): return _IS_JYTHON def supports_time_out(): if is_jython(): return False try: import signal return True except ImportError: return False proboscis-1.2.6.0/proboscis/compatability/exceptions_2_5.py0000664 3._j0000000224212116672244024764 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def capture_exception(body_func, *except_type): try: body_func() return None except except_type, e: return e def capture_type_error(func): try: func() except TypeError, te: msg = str(te) if ("takes exactly 1 argument" in msg and "(0 given)" in msg) \ or "instance as first argument (got nothing instead)" in msg: from proboscis.core import ProboscisTestMethodClassNotDecorated raise ProboscisTestMethodClassNotDecorated() else: raise proboscis-1.2.6.0/proboscis/compatability/exceptions_2_6.py0000644 3._j0000000235212116670632024764 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def capture_exception(body_func, except_type): try: body_func() return None except except_type as e: return e def capture_type_error(func): try: func() except TypeError as te: msg = str(te) if ("takes exactly 1 argument" in msg and "(0 given)" in msg) \ or ("instance as first argument (got nothing instead)" in msg) \ or ("missing 1 required positional argument" in msg): from proboscis.core import ProboscisTestMethodClassNotDecorated raise ProboscisTestMethodClassNotDecorated() else: raise proboscis-1.2.6.0/proboscis/compatability/raise_2_x.py0000664 3._j0000000132311776650564024024 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def raise_with_traceback(_type, exception, traceback): raise _type, exception, traceback proboscis-1.2.6.0/proboscis/compatability/raise_3_x.py0000664 3._j0000000135111776650564024026 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def raise_with_traceback(_type, exception, traceback): exception.__traceback__ = traceback raise exception proboscis-1.2.6.0/proboscis/core.py0000644 3._j0000003652112116670632020231 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Contains Proboscis-centric concepts.""" import inspect import types import unittest from proboscis import compatability class ProboscisTestMethodClassNotDecorated(Exception): """ This denotes a very common error that seems somewhat unavoidable due to the fact it isn't possible to know if a method is bound or not in Python when you decorate it. """ def __init__(self): super(Exception, self).__init__(self, "Proboscis attempted to run what looks like a bound method " "requiring a self argument as a free-standing function. Did you " "forget to put a @test decorator on the method's class?") class TestGroup(object): """Represents a group of tests. Think of test groups as tags on a blog. A test case may belong to multiple groups, and one group may have multiple test cases. """ def __init__(self, name): self.name = name self.entries = [] def add_entry(self, entry): """Adds a TestEntry to this group.""" self.entries.append(entry) def transform_depends_on_target(target): if isinstance(target, types.MethodType): return compatability.get_method_function(target) else: return target class TestEntryInfo: """Represents metadata attached to some kind of test code.""" def __init__(self, groups=None, depends_on=None, depends_on_classes=None, depends_on_groups=None, enabled=None, always_run=False, runs_after_groups=None, runs_after=None, run_before_class=False, run_after_class=False): groups = groups or [] depends_on_list = depends_on or [] depends_on_classes = depends_on_classes or [] depends_on_groups = depends_on_groups or [] runs_after = runs_after or [] runs_after_groups = runs_after_groups or [] self.groups = groups self.depends_on = set(transform_depends_on_target(target) for target in depends_on_list) for cls in depends_on_classes: self.depends_on.add(cls) self.runs_after_groups = runs_after_groups self.depends_on_groups = depends_on_groups self.enabled_was_specified = enabled is not None if enabled is None: enabled = True self.enabled = enabled self.always_run = always_run self.inherit_groups = False self.before_class = run_before_class self.after_class = run_after_class self.runs_after = set(transform_depends_on_target(target) for target in runs_after) if run_before_class and run_after_class: raise RuntimeError("It is illegal to set 'before_class' and " "'after_class' to True.") def inherit(self, parent_entry): """The main use case is a method inheriting from a class decorator. Returns the groups this entry was added to. """ added_groups = [] for group in parent_entry.groups: if group not in self.groups: self.groups.append(group) added_groups.append(group) for item in parent_entry.depends_on_groups: if item not in self.depends_on_groups: self.depends_on_groups.append(item) for item in parent_entry.depends_on: if item not in self.depends_on: self.depends_on.add(item) for item in parent_entry.runs_after: if item not in self.runs_after: self.runs_after.add(item) for item in parent_entry.runs_after_groups: if item not in self.runs_after_groups: self.runs_after_groups.append(item) if parent_entry.enabled_was_specified and \ not self.enabled_was_specified: self.enabled = parent_entry.enabled if parent_entry.always_run: self.always_run = True return added_groups def __repr__(self): return "TestEntryInfo(groups=" + str(self.groups) + \ ", depends_on=" + str(self.depends_on) + \ ", depends_on_groups=" + str(self.depends_on_groups) + \ ", enabled=" + str(self.enabled) + \ ", runs_after=" + str(self.runs_after) + ")" def __str__(self): return "groups = [" + ",".join(self.groups) + ']' \ ", enabled = " + str(self.enabled) + \ ", depends_on_groups = " + str(self.depends_on_groups) + \ ", depends_on = " + str(self.depends_on) + \ ", runs_after = " + str(self.runs_after) class TestEntry(object): """Represents a function, method, or unittest.TestCase and its info.""" def __init__(self, home, info): self.home = home self.homes = set([home]) self.info = info self.__method_cls = None self.__method = None self.__used_by_factory = False for dep_list in (self.info.depends_on, self.info.runs_after): for dep in dep_list: if dep is self.home: raise RuntimeError("TestEntry depends on its own class:" + str(self)) for dependency_group in self.info.depends_on_groups: for my_group in self.info.groups: if my_group == dependency_group: raise RuntimeError("TestEntry depends on a group it " \ "itself belongs to: " + str(self)) def contains(self, group_names, classes): """True if this belongs to any of the given groups or classes.""" for group_name in group_names: if group_name in self.info.groups: return True for cls in classes: if cls == self.home: return True if hasattr(self, 'parent'): return self.parent.contains_shallow(group_names, classes) return False @property def is_child(self): """True if this entry nests under a class (is a method).""" return self.__method is not None def mark_as_child(self, method, cls): """Marks this as a child so it won't be iterated as a top-level item. Needed for TestMethods. In Python we decorate functions, not methods, as the decorator doesn't know if a function is a method until later. So we end up storing entries in the Registry's list, but may only want to iterate through these from the parent onward. Finding each item in the list would be a waste of time, so instead we just mark them as such and ignore them during iteration. """ self.__method = method self.__method_cls = cls self.homes = set([self.home, cls]) def mark_as_used_by_factory(self): """If a Factory returns an instance of a class, the class will not also be run by Proboscis the usual way (only factory created instances will run). """ self.__used_by_factory = True @property def method(self): """Returns the method represented by this test, if any. If this is not None, the underlying function will be the same as 'home'. """ return self.__method @property def used_by_factory(self): """True if instances of this are returned by a @factory.""" return self.__used_by_factory def __repr__(self): return "TestEntry(" + repr(self.home) + ", " + \ repr(self.info) + ", " + object.__repr__(self) + ")" def __str__(self): return "Home = " + str(self.home) + ", Info(" + str(self.info) + ")" class TestMethodClassEntry(TestEntry): """A special kind of entry which references a class and a list of entries. The class is the class which owns the test methods, and the entries are the entries for those methods. """ def __init__(self, home, info, children): super(TestMethodClassEntry, self).__init__(home, info) self.children = children for child in self.children: child.parent = self def contains(self, group_names, classes): """True if this belongs to any of the given groups or classes.""" if self.contains_shallow(group_names, classes): return True for entry in self.children: if entry.contains(group_names, []): return True return False def contains_shallow(self, group_names, classes): return super(TestMethodClassEntry, self).contains(group_names, classes) class TestRegistry(object): """Stores test information. All of Proboscis's decorators (@test, @before_class, etc) and the register function use a default instance of this class, however its also possible to instantiate multiple copies and add tests to them directly. """ def __init__(self): self.reset() def _change_function_to_method(self, method, cls, cls_info): """Add an entry to a method by altering its function entry.""" function = compatability.get_method_function(method) method_entry = function._proboscis_entry_ method_entry.mark_as_child(method, cls) new_groups = method_entry.info.inherit(cls_info) for group_name in new_groups: group = self.get_group(group_name) group.add_entry(method_entry) return method_entry def ensure_group_exists(self, group_name): """Adds the group to the registry if it does not exist. :param group_name: The group to create. """ if not group_name in self.groups: self.groups[group_name] = TestGroup(group_name) def get_group(self, group_name): """Returns a TestGroup given its name. :param group_name: Group to return. """ self.ensure_group_exists(group_name) return self.groups[group_name] @staticmethod def _mark_home_with_entry(entry): """Store the entry inside the function or class it represents. This way, non-unittest.TestCase classes can later find information on the methods they own, and so that info can be discovered for the instances returned by factories. """ if entry.home is not None: if hasattr(entry.home, '_proboscis_entry_'): # subclasses will get this attribute from their parents. # This if statement is necessary because factories may create # multiple entries per test method. if entry.home._proboscis_entry_.home == entry.home: raise RuntimeError("A test decorator or registration was " "applied twice to the class or function %s." % entry.home) # Assign reference so factories can discover it using an instance. entry.home._proboscis_entry_ = entry def register(self, test_home=None, **kwargs): """Registers a bit of code (or nothing) to be run / ordered as a test. Registering a test with nothing allows for the creation of groups of groups, which can be useful for organization. When proboscis.register is called it chains to this method bound to the global default registry. """ info = TestEntryInfo(**kwargs) if test_home is None: return self._register_empty_test_case(info) elif isinstance(test_home, types.FunctionType): return self._register_func(test_home, info) elif issubclass(test_home, unittest.TestCase): return self._register_unittest_test_case(test_home, info) else: return self._register_test_class(test_home, info) def register_factory(self, func): """Turns a function into a Proboscis test instance factory. A factory returns a list of test class instances. Proboscis runs all factories at start up and sorts the instances like normal tests. :param func: the function to be added. """ self.factories.append(func) def _register_empty_test_case(self, info): """Registers an 'empty' test.""" self._register_simple_entry(None, info) return None def _register_unittest_test_case(self, test_cls, info): """Registers a unittest.TestCase.""" entry = self._register_simple_entry(test_cls, info) return entry.home def _register_func(self, func, info): """Registers a function.""" entry = self._register_simple_entry(func, info) return entry.home def _register_entry(self, entry): """Adds an entry to this Registry's list and may also create groups.""" info = entry.info for group_name in info.groups: group = self.get_group(group_name) group.add_entry(entry) for group_name in info.depends_on_groups: self.ensure_group_exists(group_name) if entry.home: if not entry.home in self.classes: self.classes[entry.home] = [] self.classes[entry.home].append(entry) self._mark_home_with_entry(entry) self.tests.append(entry) def _register_simple_entry(self, test_home, info): """Registers a unitttest style test entry.""" entry = TestEntry(test_home, info) self._register_entry(entry) return entry def _register_test_class(self, cls, info): """Registers the methods within a class.""" test_entries = [] methods = compatability.get_class_methods(cls) before_class_methods = [] after_class_methods = [] for method in methods: func = compatability.get_method_function(method) if hasattr(func, "_proboscis_entry_"): entry = self._change_function_to_method(method, cls, info) test_entries.append(entry) if entry.info.before_class: before_class_methods.append(entry) elif entry.info.after_class: after_class_methods.append(entry) for before_entry in before_class_methods: for test_entry in test_entries: if not test_entry.info.before_class: test_entry.info.depends_on.add(before_entry.home) for after_entry in after_class_methods: for test_entry in test_entries: if not test_entry.info.after_class: after_entry.info.depends_on.add(test_entry.home) entry = TestMethodClassEntry(cls, info, test_entries) self._register_entry(entry) return entry.home def reset(self): """Wipes the registry.""" self.tests = [] self.groups = {} self.classes = {} self.factories = [] proboscis-1.2.6.0/proboscis/decorators.py0000644 3._j0000001303012116670632021434 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Decorators useful to the tests.""" from functools import wraps from proboscis.asserts import assert_raises_instance from proboscis import compatability from proboscis.core import TestRegistry DEFAULT_REGISTRY = TestRegistry() def expect_exception(exception_type): """Decorates a test method to show it expects an exception to be raised.""" def return_method(method): @wraps(method) def new_method(*args, **kwargs): assert_raises_instance(exception_type, method, *args, **kwargs) return new_method return return_method class TimeoutError(RuntimeError): """Thrown when a method has exceeded the time allowed.""" pass def time_out(time): """Raises TimeoutError if the decorated method does not finish in time.""" if not compatability.supports_time_out(): raise ImportError("time_out not supported for this version of Python.") import signal def cb_timeout(signum, frame): raise TimeoutError("Time out after waiting " + str(time) + " seconds.") def return_method(func): """Turns function into decorated function.""" @wraps(func) def new_method(*kargs, **kwargs): previous_handler = signal.signal(signal.SIGALRM, cb_timeout) try: signal.alarm(time) return func(*kargs, **kwargs) finally: signal.alarm(0) signal.signal(signal.SIGALRM, previous_handler) return new_method return return_method def register(**kwargs): """Registers a test in proboscis's default registry. :param home: The target class or function. This also allows all of the parameters used by the @test decorator. This function works differently than a decorator as it allows the class or function which is being registered to appear in the same call as all of the options. Its designed to make it easier to register class or functions with Proboscis after they're defined. """ DEFAULT_REGISTRY.register(**kwargs) def test(home=None, **kwargs): """Decorates a test class or function to cause Proboscis to run it. The behavior differs depending the target: - If put on a stand-alone function, the function will run by itself. - If put on a class inheriting unittest.TestCase, then the class will run just like a normal unittest class by using the method names and instantiate a new instance of the class for each test method. - If the class does not inherit from unittest.TestCase, the class will be instantiated once and this instance will be passed to each method decorated with @test (this increases encapsulation over using class fields as the instance can not be accessed outside of its methods). Note that due to how decorators work its impossible to know if a function is or is not part of a class; thus if a class method is decorated with test but its class is not then ProboscisTestMethodNotDecorated will be raised. :param groups: A list of strings representing the groups this test method or class belongs to. By default this is an empty list. :param depends_on: A list of test functions or classes which must run before this test. By default this is an empty list. :param depends_on_groups: A list of strings each naming a group that must run before this test. By default this is an empty list. :param enabled: By default, true. If set to false this test will not run. :param always_run: If true this test will run even if the tests listed in depends_on or depends_on_groups have failed. """ if home: return DEFAULT_REGISTRY.register(home, **kwargs) else: def cb_method(home_2): return DEFAULT_REGISTRY.register(home_2, **kwargs) return cb_method def before_class(home=None, **kwargs): """Like @test but indicates this should run before other class methods. All of the arguments sent to @test work with this decorator as well. """ kwargs.update({'run_before_class':True}) return test(home=home, **kwargs) def after_class(home=None, **kwargs): """Like @test but indicates this should run after other class methods. All of the arguments sent to @test work with this decorator as well. This will be skipped if a class method test fails; set always_run if that is not desired. See `issue #5 `__. """ kwargs.update({'run_after_class':True}) return test(home=home, **kwargs) def factory(func=None, **kwargs): """Decorates a function which returns new instances of Test classes.""" if func: return DEFAULT_REGISTRY.register_factory(func) else: raise ValueError("Arguments not supported on factories.") proboscis-1.2.6.0/proboscis/dependencies.py0000644 3._j0000000576612001021034021711 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Dynamically selected dependencies for Proboscis. If Nose is installed, Proboscis will use it. Otherwise Proboscis will use the default unittest framework, in order to function in IronPython and Jython. It also supports Python 2.5 in order to work with Jython. """ try: from nose.plugins.skip import SkipTest as ExternalSkipTest from nose.core import TestProgram from nose.core import TextTestResult from nose.core import TextTestRunner use_nose = True def skip_test(test_self, message): raise SkipTest(message) except ImportError: import unittest from unittest import TextTestRunner use_nose = False # In 2.7 unittest.TestCase has a skipTest method. def skip_test(test_self, message): try: test_self.skipTest(message) except AttributeError: # For versions prior to 2.5. raise AssertionError("SKIPPED:%s" % message) class TestProgram(unittest.TestProgram): def __init__(self, suite, config=None, *args, **kwargs): self.suite_arg = suite class StubLoader(object): def loadTestsFromModule(*args, **kwargs): return self.suite_arg self.test = suite if 'testLoader' not in kwargs or kwargs['testLoader'] is None: kwargs['testLoader'] = StubLoader() super(TestProgram, self).__init__(*args, **kwargs) def createTests(self): self.test = self.suite_arg class TextTestResult(unittest._TextTestResult): def __init__(self, stream, descriptions, verbosity, config=None, errorClasses=None): super(TextTestResult, self).__init__(stream, descriptions, verbosity); class ExternalSkipTest(Exception): def __init__(self, message): super(ExternalSkipTest, self).__init__(self, message) self.message = message def __str__(self): return self.message # Doing it this way so I won't change Nose's pydoc. class SkipTest(ExternalSkipTest): """ Raise this to skip a test. If Nose is available its SkipTest is used. Otherwise Proboscis creates its own which class that calls unittest.TestCase.skipTest. If that method isn't available (anything under 2.7) then skipping does not work and test errors are presented. """ pass proboscis-1.2.6.0/proboscis/sorting.py0000664 3._j0000001230011776650564020772 0ustar tim.simpson00000000000000# Copyright (c) 2011 Rackspace # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ This module is home to Proboscis's sorting algorithms. """ from collections import deque class Dependent(object): def __init__(self, case, critical): self.case = case self.critical = critical class TestNode: """Representation of a TestEntry used in sorting.""" def __init__(self, case): self.case = case self.dependencies = [] self.dependents = [] def add_dependency(self, node, is_critical): """Adds a bidirectional link between this node and a dependency. This also informs the dependency TestEntry of its dependent. It is intuitive to specify dependencies when writing tests, so we have to wait until this phase to determine the dependents of the TestEntry. """ # TODO: Could this be sped up by using a set? if node in self.dependencies: return self.dependencies.append(node) node.dependents.append(self) node.case.dependents.append(Dependent(self.case, is_critical)) @property def has_no_dependencies(self): return len(self.dependencies) == 0 def pop_dependent(self): """Removes and returns a dependent from this nodes dependent list. This act of destruction is one reason why this second representation of a TestEntry is necessary. """ dependent = self.dependents.pop() dependent.dependencies.remove(self) return dependent class TestGraph: """Used to sort the tests in a registry in the correct order. As it sorts, it also adds dependent information to the TestEntries, which means calling it twice messes stuff up. """ def __init__(self, groups, entries, cases): self.nodes = [] self.entries = entries self.groups = groups for case in cases: self.nodes.append(TestNode(case)) for node in self.nodes: n_info = node.case.entry.info for dependency_group in n_info.runs_after_groups: d_group_nodes = self.nodes_for_group(dependency_group) for dependency_group_node in d_group_nodes: node.add_dependency(dependency_group_node, False) for dependency_group in n_info.depends_on_groups: d_group_nodes = self.nodes_for_group(dependency_group) for dependency_group_node in d_group_nodes: node.add_dependency(dependency_group_node, True) for dependency in n_info.runs_after: d_nodes = self.nodes_for_class_or_function(dependency) for dependency_node in d_nodes: node.add_dependency(dependency_node, False) for dependency in n_info.depends_on: d_nodes = self.nodes_for_class_or_function(dependency) for dependency_node in d_nodes: node.add_dependency(dependency_node, True) def nodes_for_class_or_function(self, test_home): """Returns nodes attached to the given class.""" search_homes = [test_home] if hasattr(test_home, '_proboscis_entry_'): if hasattr(test_home._proboscis_entry_, 'children'): children = test_home._proboscis_entry_.children search_homes += [child.home for child in children] search_set = set(search_homes) return (n for n in self.nodes \ if search_set.intersection(n.case.entry.homes)) def nodes_for_group(self, group_name): """Returns nodes attached to the given group.""" group = self.groups[group_name] entries = group.entries return [node for node in self.nodes if node.case.entry in entries] def sort(self): """Returns a sorted list of entries. Dismantles this graph's list of nodes and adds dependent information to the list of TestEntries (in other words, don't call this twice). """ independent_nodes = deque((n for n in self.nodes if n.has_no_dependencies)) ordered_nodes = [] # The new list while independent_nodes: i_node = independent_nodes.popleft() ordered_nodes.append(i_node) while i_node.dependents: d_node = i_node.pop_dependent() if d_node.has_no_dependencies: independent_nodes.appendleft(d_node) # Search for a cycle for node in self.nodes: if not node.has_no_dependencies: raise RuntimeError("Cycle found on node " + str(node.case)) return list((n.case for n in ordered_nodes)) proboscis-1.2.6.0/proboscis.egg-info/00007550000000_j0000000000012116673365017065 5ustar root00000000000000proboscis-1.2.6.0/proboscis.egg-info/dependency_links.txt0000644 3._j0000000000112116673365024466 0ustar tim.simpson00000000000000 proboscis-1.2.6.0/proboscis.egg-info/PKG-INFO0000644 3._j0000000137512116673365021523 0ustar tim.simpson00000000000000Metadata-Version: 1.0 Name: proboscis Version: 1.2.6.0 Summary: Extends Nose with certain TestNG like features. Home-page: https://github.com/rackspace/python-proboscis Author: Rackspace Author-email: tim.simpson@rackspace.com License: Apache Description: Proboscis is a Python test framework that extends Python's built-in unittest module and Nose with features from TestNG. Keywords: nose test testng Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python proboscis-1.2.6.0/proboscis.egg-info/SOURCES.txt0000644 3._j0000000101412116673365022300 0ustar tim.simpson00000000000000README.rst setup.cfg setup.py proboscis/__init__.py proboscis/asserts.py proboscis/case.py proboscis/check.py proboscis/core.py proboscis/decorators.py proboscis/dependencies.py proboscis/sorting.py proboscis.egg-info/PKG-INFO proboscis.egg-info/SOURCES.txt proboscis.egg-info/dependency_links.txt proboscis.egg-info/top_level.txt proboscis/compatability/__init__.py proboscis/compatability/exceptions_2_5.py proboscis/compatability/exceptions_2_6.py proboscis/compatability/raise_2_x.py proboscis/compatability/raise_3_x.pyproboscis-1.2.6.0/proboscis.egg-info/top_level.txt0000644 3._j0000000001212116673365023143 0ustar tim.simpson00000000000000proboscis proboscis-1.2.6.0/README.rst0000644 3._j0000001040612116670632016405 0ustar tim.simpson00000000000000Proboscis ================ Proboscis is a Python test framework that extends Python's built-in unittest module and `Nose`_ with features from `TestNG`_. .. _Nose: http://readthedocs.org/docs/nose/en/latest/ .. _TestNG: http://testng.org/doc/index.html `Click here to read the full docs`_. .. _`Click here to read the full docs`: http://packages.python.org/proboscis/ Features -------- - Uses decorators instead of naming conventions. - Allows for TestNG style test methods, in which a class is initialized once, as an alternative to using class fields (see the example below). - Allows for explicit `test dependencies`_ and skipping of dependent tests on failures. - Runs xUnit style clases if desired or needed for backwards compatability. - Uses Nose if available (but doesn't require it), and works with many of its plugins. - Runs in `IronPython`_ and `Jython`_ (although if you're targetting the JVM you should consider using TestNG instead)! .. _`test dependencies`: http://beust.com/weblog/2004/08/18/using-annotation-inheritance-for-testing/ .. _IronPython: http://ironpython.net/ .. _Jython: http://www.jython.org/ Updates ------- Version 1.2.6.0 ~~~~~~~~~~~~~~~ - Proboscis now works with Python 3! Version 1.2.5.3 ~~~~~~~~~~~~~~~ - Fixed bug in runs_after_groups inheritance. - Allow "import *" from proboscis asserts. Version 1.2.5.2 ~~~~~~~~~~~~~~~ - Fixed a bug that prevented some Nose plugins from working. Version 1.2.5.1 ~~~~~~~~~~~~~~~ - Implemented test decorator property "runs_after", which affects only the order of test runs. If a test noted by "runs_after" fails, the test method or class targeted by the decorator will *not* be skipped. If a group is run, tests which are listed in "runs_after" will not implicitly be run as well. - Added 'fail' method to Checker class. - Using tox discovered some issues with Jython compatability. Version 1.2.4 ~~~~~~~~~~~~~ - Added a missing parameter to a format string error message. - Fixed bug where the enabled property was not being inherited by class methods. - Added a Check class to allow testing multiple assertions in a with block. Example ------- This example tests an external web service by creating an admin user and updating the profile picture. :: @test(groups=["service.initialization"]) def make_sure_service_is_up(): # No point in proceeding if the service isn't responding. assert_true(service_module.ping(service_config)) @test(groups=["service.tests"], depends_on_groups=["service.initialization"]) class AdminTest(object): @before_class def create_admin_user(self): self.client = service_module.ServiceClient(service_config) self.admin = self.client.create_admin_user("boss") @test def check_for_defaults(self): assert_equals("default.jpg", self.admin.get_profile_image()) @test(depends_on=check_for_defaults) def change_picture(self): self.admin.set_profile_image("spam.jpg") assert_equals("spam.jpg", self.admin.get_profile_image()) # Put other tests against admin user here... @after_class def destroy_admin_user(self): self.client.delete_user(self.admin) Here, the variable "admin" is created only once, similar to TestNG. If the xUnit style is preferred or needed for backwards compatability the following code will create the admin variable once for each test function: :: @test(groups=["service.tests"], depends_on_groups=["service.initialization"]) class AdminTest(unittest.TestCase): def setUp(self): self.client = service_module.ServiceClient(service_config) self.admin = self.client.create_admin_user("boss") def test_change_picture(self): assert_equals("default.jpg", self.admin.get_profile_image()) self.admin.set_profile_image("spam.jpg") assert_equals("spam.jpg", self.admin.get_profile_image()) # Put other tests against admin user here... def tearDown(self): self.client.delete_user(self.admin) Though this version of AdminTest runs like an xUnit test, it still runs after the "service.initialization" group. For more info see the `full docs`_. .. _`full docs`: http://packages.python.org/proboscis/ proboscis-1.2.6.0/setup.cfg00006440000000_j0000000027012116673365015210 0ustar root00000000000000[build_sphinx] all_files = 1 build-dir = docs/build source-dir = docs/source [upload_sphinx] upload-dir = docs/_build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 proboscis-1.2.6.0/setup.py0000644 3._j0000000357212116670632016436 0ustar tim.simpson00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os from setuptools import setup def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name="proboscis", version="1.2.6.0", author='Rackspace', author_email='tim.simpson@rackspace.com', description="Extends Nose with certain TestNG like features.", keywords="nose test testng", long_description="Proboscis is a Python test framework that extends " "Python's built-in unittest module and Nose with " "features from TestNG.", url='https://github.com/rackspace/python-proboscis', license='Apache', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', ], py_modules=[], packages=['proboscis', 'proboscis.compatability'], scripts=[], tests_require=["nose"], test_suite="nose.collector" )