| 1 | r""" |
| 2 | Unit testing for Sage objects |
| 3 | """ |
| 4 | #***************************************************************************** |
| 5 | # Copyright (C) 2009 Nicolas M. Thiery <nthiery at users.sf.net> |
| 6 | # |
| 7 | # Distributed under the terms of the GNU General Public License (GPL) |
| 8 | # http://www.gnu.org/licenses/ |
| 9 | #****************************************************************************** |
| 10 | |
| 11 | ############################################################################## |
| 12 | |
| 13 | import unittest |
| 14 | |
| 15 | class TestSuite(object): |
| 16 | """ |
| 17 | Test suites for Sage objects. |
| 18 | |
| 19 | EXAMPLES:: |
| 20 | |
| 21 | sage: TestSuite(ZZ).run() |
| 22 | |
| 23 | No output means that all tests passed. Which tests? |
| 24 | In practice this calls all the methods ``._test_*`` of this |
| 25 | object, in alphabetic order:: |
| 26 | |
| 27 | sage: TestSuite(ZZ).run(verbose = True) |
| 28 | running ._test_not_implemented_methods() ... done |
| 29 | running ._test_pickling() ... done |
| 30 | |
| 31 | Those methods methods are typically implemented by abstract |
| 32 | super classes, in particular via categories, in order to |
| 33 | enforce standard behavior and API, or provide mathematical |
| 34 | sanity checks. For example if ``self`` is in the category of |
| 35 | finite semigroups, this checks that the multiplication is |
| 36 | associative (at least on some elements):: |
| 37 | |
| 38 | sage: S = FiniteSemigroups().example(alphabet = ('a', 'b')) # todo: not implemented (comes with the category patches) |
| 39 | sage: TestSuite(S).run(verbose = True) # todo: not implemented (comes with the category patches) |
| 40 | running ._test_an_element() ... done |
| 41 | running ._test_associativity() ... done |
| 42 | running ._test_element_pickling() ... done |
| 43 | running ._test_enumerated_set_contains() ... done |
| 44 | running ._test_enumerated_set_iter_cardinality() ... done |
| 45 | running ._test_enumerated_set_iter_list() ... done |
| 46 | running ._test_not_implemented_methods() ... done |
| 47 | running ._test_pickling() ... done |
| 48 | running ._test_some_elements() ... done |
| 49 | |
| 50 | The different test methods can be called independently:: |
| 51 | |
| 52 | sage: S._test_associativity() # todo: not implemented (comes with the category patches) |
| 53 | |
| 54 | When meaningful, one can further customize on which elements |
| 55 | the tests are run. Here, we use it to *prove* that the |
| 56 | multiplication is indeed associative, by runing the test on |
| 57 | all the elements:: |
| 58 | |
| 59 | sage: S._test_associativity(elements = S) # todo: not implemented (comes with the category patches) |
| 60 | |
| 61 | Adding a new test boils down to adding a new method in the |
| 62 | class of the object or any superclass (e.g. in a |
| 63 | category). This method should use the utility :meth:`._tester` |
| 64 | to handle standard options and report test failures. See the |
| 65 | code of :meth:`._test_an_element` for an example. |
| 66 | |
| 67 | Eventually, every implementation of a :class:`SageObject` |
| 68 | should run a :class:`TestSuite` on one of its instances in its doctest. |
| 69 | (replacing the current ``loads(dumps(x))` tests). |
| 70 | |
| 71 | TODO: |
| 72 | - allow for customized behavior in case of failing assertion |
| 73 | (warning, error, statistic accounting) |
| 74 | This involves reimplementing the method sfail / failIf |
| 75 | / ... of unittest.TestCase in InstanceTester |
| 76 | - Improve integration with doctests (statistics on failing/passing tests) |
| 77 | - Integration with unittest: |
| 78 | Make TestSuite inherit from unittest.TestSuite? |
| 79 | Make .run(...) accept a result object |
| 80 | - Add some standard option ``proof = True``, asking for the |
| 81 | test method to choose appropriately the elements so as to |
| 82 | prove the desired property. The test method may assume that |
| 83 | a parent implements properly all the super categories. For |
| 84 | example, the test_commutative method of the category |
| 85 | ``CommutativeSemigroups()`` may just check that the |
| 86 | provided generators commute, implicitly assuming that |
| 87 | generators indeed generate the semigroup (as required by |
| 88 | ``Semigroups()``). |
| 89 | """ |
| 90 | |
| 91 | def __init__(self, instance): |
| 92 | """ |
| 93 | TESTS:: |
| 94 | |
| 95 | sage: TestSuite(ZZ) |
| 96 | Test suite for Integer Ring |
| 97 | """ |
| 98 | self._instance = instance |
| 99 | |
| 100 | def __repr__(self): |
| 101 | """ |
| 102 | TESTS:: |
| 103 | |
| 104 | sage: TestSuite(ZZ) |
| 105 | Test suite for Integer Ring |
| 106 | """ |
| 107 | return "Test suite for %s"%self._instance |
| 108 | |
| 109 | |
| 110 | def run(self, category = None, **options): |
| 111 | """ |
| 112 | Run all the tests from this test suite: |
| 113 | |
| 114 | EXAMPLES:: |
| 115 | |
| 116 | sage: TestSuite(ZZ).run() |
| 117 | |
| 118 | We now use the ``verbose`` option:: |
| 119 | |
| 120 | sage: TestSuite(ZZ).run(verbose = True) |
| 121 | running ._test_not_implemented_methods() ... done |
| 122 | running ._test_pickling() ... done |
| 123 | |
| 124 | """ |
| 125 | tester = self._instance._tester(**options) |
| 126 | for method_name in dir(self._instance): |
| 127 | # Note: testunit usually looks for methods called test* , but we don't want to catch self.test() here! |
| 128 | if method_name[0:6] == "_test_": |
| 129 | # TODO: improve pretty printing |
| 130 | # could use the doc string of the test method |
| 131 | tester.info("running .%s() ..."%method_name, newline = False) |
| 132 | getattr(self._instance, method_name)(tester = tester) |
| 133 | tester.info("done") |
| 134 | |
| 135 | class InstanceTester(unittest.TestCase): |
| 136 | def __init__(self, instance, elements = None, verbose = False, **options): |
| 137 | """ |
| 138 | A gadget attached to an instance providing it with testing utilities. |
| 139 | |
| 140 | EXAMPLES:: |
| 141 | |
| 142 | sage: from sage.misc.sage_unittest import InstanceTester |
| 143 | sage: InstanceTester(instance = ZZ, verbose = True, elements = [1,2,3]) |
| 144 | Testing utilities for Integer Ring |
| 145 | |
| 146 | This is used by ``SageObject._tester``, which see:: |
| 147 | |
| 148 | sage: ZZ._tester() |
| 149 | Testing utilities for Integer Ring |
| 150 | |
| 151 | """ |
| 152 | self._instance = instance |
| 153 | self._verbose = verbose |
| 154 | self._elements = elements |
| 155 | |
| 156 | def runTest(self): |
| 157 | """ |
| 158 | Trivial implementation of :meth:`unittest.TestCase.runTest` to |
| 159 | please the super class :class:`TestCase`. That's the price to |
| 160 | pay for abusively inheriting from it. |
| 161 | |
| 162 | sage: from sage.misc.sage_unittest import InstanceTester |
| 163 | sage: tester = InstanceTester(ZZ, verbose = True) |
| 164 | sage: tester.runTest() |
| 165 | |
| 166 | """ |
| 167 | pass |
| 168 | |
| 169 | def info(self, message, newline = True): |
| 170 | """ |
| 171 | Displays user information |
| 172 | |
| 173 | EXAMPLES:: |
| 174 | |
| 175 | sage: from sage.misc.sage_unittest import InstanceTester |
| 176 | sage: tester = InstanceTester(ZZ, verbose = True) |
| 177 | |
| 178 | sage: tester.info("hello"); tester.info("world") |
| 179 | hello |
| 180 | world |
| 181 | |
| 182 | sage: tester = InstanceTester(ZZ, verbose = False) |
| 183 | sage: tester.info("hello"); tester.info("world") |
| 184 | |
| 185 | sage: tester = InstanceTester(ZZ, verbose = True) |
| 186 | sage: tester.info("hello", newline = False); tester.info("world") |
| 187 | hello world |
| 188 | |
| 189 | |
| 190 | """ |
| 191 | if self._verbose: |
| 192 | if newline: |
| 193 | print message |
| 194 | else: |
| 195 | print message, |
| 196 | |
| 197 | def __repr__(self): |
| 198 | """ |
| 199 | EXAMPLES:: |
| 200 | |
| 201 | sage: from sage.misc.sage_unittest import InstanceTester |
| 202 | sage: InstanceTester(ZZ, verbose = True) |
| 203 | Testing utilities for Integer Ring |
| 204 | |
| 205 | """ |
| 206 | return "Testing utilities for %s"%self._instance |
| 207 | |
| 208 | |
| 209 | def some_elements(self): |
| 210 | """ |
| 211 | Returns a list (or iterable) of elements of ``self`` on which |
| 212 | the tests should be run. This is only meaningful for container |
| 213 | objects like parents. |
| 214 | |
| 215 | By default, this calls :meth:`.some_elements`:: |
| 216 | |
| 217 | sage: from sage.misc.sage_unittest import InstanceTester |
| 218 | sage: class MyParent(Parent): |
| 219 | ... def some_elements(self): |
| 220 | ... return [1,2,3,4,5] |
| 221 | ... |
| 222 | sage: tester = InstanceTester(MyParent()) |
| 223 | sage: list(tester.some_elements()) |
| 224 | [1, 2, 3, 4, 5] |
| 225 | |
| 226 | sage: tester = InstanceTester(ZZ, elements = [1,3,5]) |
| 227 | sage: list(tester.some_elements()) |
| 228 | [1, 3, 5] |
| 229 | |
| 230 | """ |
| 231 | if self._elements is None: |
| 232 | return self._instance.some_elements() |
| 233 | else: |
| 234 | return self._elements |