Remove obsolete test code from setup.py
[cds-indico.git] / indico / testing / util.py
blob54e882965d38381d7864569c15c5a3bd7b2df8ee
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 import operator
18 import re
19 from itertools import product, imap
22 def bool_matrix(template, mask=None, expect=None):
23 """Creates a boolean matrix suitable for parametrized tests.
25 This function lets you create a boolean matrix with certain columns being
26 fixed to a static value or certain combinations being skipped. It also adds
27 a last column with a value that's either fixed or depending on the other values.
29 By default any `0` or `1` in the template results in its column being fixed
30 to that value while any `.` column is dynamic and is used when creating all
31 possible boolean values::
33 >>> bool_matrix('10..', expect=True)
34 ((True, False, True, True, True),
35 (True, False, True, False, True),
36 (True, False, False, True, True),
37 (True, False, False, False, True))
39 The `expect` param can be a boolean value if you always want the same value,
40 a tuple if you want only a single row to be true or a callable receiving the
41 whole row. In some cases the builtin callables `any` or `all` are appropriate
42 callables, in other cases a custom (lambda) function is necessary. You can also
43 pass the strings `any_dynamic` or `all_dynamic` which are similar to `any`/`all`
44 but only check entries which do not have a fixed value in the template.
46 In exclusion mode any row matching the template is skipped. It can be enabled
47 by prefixing the template with a `!` character::
49 >>> bool_matrix('!00.', expect=all)
50 ((True, True, True, True),
51 (True, True, False, False),
52 (True, False, True, False),
53 (True, False, False, False),
54 (False, True, True, False),
55 (False, True, False, False))
57 You can also combine both by using the default syntax and specifying the exclusion
58 mask separately::
60 >>> bool_matrix('..1.', mask='00..', expect=all)
61 ((True, True, True, True, True),
62 (True, True, True, False, False),
63 (True, False, True, True, False),
64 (True, False, True, False, False),
65 (False, True, True, True, False),
66 (False, True, True, False, False))
68 :param template: row template
69 :param expect: string, bool value, tuple or callable
70 :param mask: exclusion mask
71 """
72 template = template.replace(' ', '')
73 exclude_all = False
74 if template[0] == '!':
75 exclude_all = True
76 template = template[1:]
77 if mask:
78 if exclude_all:
79 raise ValueError('cannot combine ! with mask')
80 if len(mask) != len(template):
81 raise ValueError('mask length differs from template length')
82 if any(x != '.' and y != '.' for x, y in zip(template, mask)):
83 raise ValueError('mask cannot have a value for a fixed column')
84 else:
85 mask = '.' * len(template)
87 mapping = {'0': False, '1': True, '.': None}
88 template = tuple(imap(mapping.__getitem__, template))
89 mask = tuple(imap(mapping.__getitem__, mask))
90 # full truth table
91 iterable = product((True, False), repeat=len(template))
92 if exclude_all:
93 # only use rows which have values not matching the template
94 iterable = (x for x in iterable if any(x[i] != v for i, v in enumerate(template) if v is not None))
95 else:
96 # only use rows where all values match the template
97 iterable = (x for x in iterable if all(v is None or x[i] == v for i, v in enumerate(template)))
98 # exclude some rows
99 if any(x is not None for x in mask):
100 iterable = (x for x in iterable if any(x[i] != v for i, v in enumerate(mask) if v is not None))
101 # add the "expected" value which can depend on the other values
102 if expect is None:
103 pass
104 elif expect == 'any_dynamic':
105 iterable = (x + (any(y for i, y in enumerate(x) if template[i] is None),) for x in iterable)
106 elif expect == 'all_dynamic':
107 iterable = (x + (all(y for i, y in enumerate(x) if template[i] is None),) for x in iterable)
108 elif callable(expect):
109 iterable = (x + (expect(x),) for x in iterable)
110 elif isinstance(expect, (tuple, list)):
111 iterable = (x + (x == expect,) for x in iterable)
112 else:
113 iterable = (x + (expect,) for x in iterable)
114 matrix = tuple(iterable)
115 if not matrix:
116 raise ValueError('empty matrix')
117 return matrix
120 def extract_emails(smtp, required=True, count=None, one=False, regex=False, **kwargs):
121 """Extracts emails from an smtp outbox.
123 :param smtp: The `smtp` fixture from the testcase
124 :param required: Fail if no matching emails were found
125 :param count: Require exactly `count` emails to be found
126 :param one: Require exactly one email to be found
127 :param kwargs: Header values to match against
128 :return: list of emails, unless `one` is true in which
129 case the matching email or `None` is returned
131 if one:
132 if count is not None:
133 raise ValueError('Cannot specify both `count` and `one`')
134 count = 1
135 compare = re.search if regex else operator.eq
136 found = []
137 for mail in smtp.outbox:
138 for header, value in kwargs.iteritems():
139 if not compare(value, mail[header]):
140 break
141 else: # everything matched
142 found.append(mail)
143 found_set = set(found)
144 smtp.outbox = [mail for mail in smtp.outbox if mail not in found_set]
145 __tracebackhide__ = True
146 if required:
147 assert found, 'No matching emails found'
148 if count is not None:
149 assert len(found) == count, 'Expected {} emails, got {}'.format(count, len(found))
150 if one:
151 return found[0] if found else None
152 return found
155 def extract_logs(caplog, required=True, count=None, one=False, regex=False, **kwargs):
156 """Extracts log records from python's logging system.
158 :param caplog: The `caplog` fixture from the testcase
159 :param required: Fail if no matching records were found
160 :param count: Require exactly `count` records to be found
161 :param one: Require exactly one record to be found
162 :param kwargs: LogRecord attribute values to match against
163 :return: list of log records, unless `one` is true in which
164 case the matching record or `None` is returned
166 if one:
167 if count is not None:
168 raise ValueError('Cannot specify both `count` and `one`')
169 count = 1
170 compare = re.search if regex else operator.eq
171 found = []
172 for record in caplog.handler.records:
173 for key, value in kwargs.iteritems():
174 if not compare(value, getattr(record, key)):
175 break
176 else: # everything matched
177 found.append(record)
178 found_set = set(found)
179 caplog.handler.records = [record for record in caplog.handler.records if record not in found_set]
180 __tracebackhide__ = True
181 if required:
182 assert found, 'No matching records found'
183 if count is not None:
184 assert len(found) == count, 'Expected {} records, got {}'.format(count, len(found))
185 if one:
186 return found[0] if found else None
187 return found