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/>.
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
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
72 template
= template
.replace(' ', '')
74 if template
[0] == '!':
76 template
= template
[1:]
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')
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
))
91 iterable
= product((True, False), repeat
=len(template
))
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))
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
)))
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
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
)
113 iterable
= (x
+ (expect
,) for x
in iterable
)
114 matrix
= tuple(iterable
)
116 raise ValueError('empty 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
132 if count
is not None:
133 raise ValueError('Cannot specify both `count` and `one`')
135 compare
= re
.search
if regex
else operator
.eq
137 for mail
in smtp
.outbox
:
138 for header
, value
in kwargs
.iteritems():
139 if not compare(value
, mail
[header
]):
141 else: # everything matched
143 found_set
= set(found
)
144 smtp
.outbox
= [mail
for mail
in smtp
.outbox
if mail
not in found_set
]
145 __tracebackhide__
= True
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
))
151 return found
[0] if found
else None
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
167 if count
is not None:
168 raise ValueError('Cannot specify both `count` and `one`')
170 compare
= re
.search
if regex
else operator
.eq
172 for record
in caplog
.handler
.records
:
173 for key
, value
in kwargs
.iteritems():
174 if not compare(value
, getattr(record
, key
)):
176 else: # everything matched
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
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
))
186 return found
[0] if found
else None