3
3
import re
4
4
import textwrap
5
5
from collections import OrderedDict
6
+ from itertools import chain
7
+ from operator import attrgetter
8
+ from typing import List , Type
9
+
10
+ from attr import attrs , attrib , Factory
6
11
7
12
from . import types , exceptions
8
13
11
16
STEP_PREFIXES = [
12
17
("Feature: " , types .FEATURE ),
13
18
("Scenario Outline: " , types .SCENARIO_OUTLINE ),
14
- ("Examples: Vertical" , types .EXAMPLES_VERTICAL ),
19
+ ("Examples: Vertical" , types .EXAMPLES_VERTICAL_LEGACY ),
20
+ ("Examples Transposed:" , types .EXAMPLES_TRANSPOSED ),
15
21
("Examples:" , types .EXAMPLES ),
16
22
("Scenario: " , types .SCENARIO ),
17
23
("Background:" , types .BACKGROUND ),
@@ -142,8 +148,7 @@ def parse_feature(basedir, filename, encoding="utf-8"):
142
148
clean_line ,
143
149
filename ,
144
150
)
145
-
146
- prev_mode = mode
151
+ prev_prev_mode , prev_mode = prev_mode , mode
147
152
148
153
# Remove Feature, Given, When, Then, And
149
154
keyword , parsed_line = parse_line (clean_line )
@@ -154,19 +159,35 @@ def parse_feature(basedir, filename, encoding="utf-8"):
154
159
feature .background = Background (feature = feature , line_number = line_number )
155
160
elif mode == types .EXAMPLES :
156
161
mode = types .EXAMPLES_HEADERS
157
- (scenario or feature ).examples .line_number = line_number
158
- elif mode == types .EXAMPLES_VERTICAL :
159
- mode = types .EXAMPLE_LINE_VERTICAL
160
- (scenario or feature ).examples .line_number = line_number
162
+ _ , example_table_name = parse_line (clean_line )
163
+ obj = scenario or feature
164
+ obj .examples .add_example_table (UsualExampleTable )
165
+ obj .examples .last_example_table .name = example_table_name or None
166
+ obj .examples .last_example_table .line_number = line_number
167
+ if prev_prev_mode == types .TAG :
168
+ obj .examples .last_example_table .tags = get_tags (prev_line )
169
+ elif (
170
+ mode == types .EXAMPLES_TRANSPOSED or mode == types .EXAMPLES_VERTICAL_LEGACY
171
+ ): # Deprecated; Has to be removed in future versions
172
+ mode = types .EXAMPLE_LINE_TRANSPOSED
173
+ _ , example_table_name = parse_line (clean_line )
174
+ obj = scenario or feature
175
+ obj .examples .add_example_table (TransposedExampleTable )
176
+ obj .examples .last_example_table .name = example_table_name or None
177
+ obj .examples .last_example_table .line_number = line_number
178
+ if prev_prev_mode == types .TAG :
179
+ obj .examples .last_example_table .tags = get_tags (prev_line )
161
180
elif mode == types .EXAMPLES_HEADERS :
162
- (scenario or feature ).examples .set_param_names ([l for l in split_line (parsed_line ) if l ])
181
+ (scenario or feature ).examples .last_example_table . set_param_names ([l for l in split_line (parsed_line ) if l ])
163
182
mode = types .EXAMPLE_LINE
164
183
elif mode == types .EXAMPLE_LINE :
165
- (scenario or feature ).examples .add_example ([l for l in split_line (stripped_line )])
166
- elif mode == types .EXAMPLE_LINE_VERTICAL :
184
+ example_table : UsualExampleTable = (scenario or feature ).examples .last_example_table
185
+ example_table .add_example ([l for l in split_line (stripped_line )])
186
+ elif mode == types .EXAMPLE_LINE_TRANSPOSED :
167
187
param_line_parts = [l for l in split_line (stripped_line )]
168
188
try :
169
- (scenario or feature ).examples .add_example_row (param_line_parts [0 ], param_line_parts [1 :])
189
+ example_table : TransposedExampleTable = (scenario or feature ).examples .last_example_table
190
+ example_table .add_example_row (param_line_parts [0 ], param_line_parts [1 :])
170
191
except exceptions .ExamplesNotValidError as exc :
171
192
if scenario :
172
193
raise exceptions .FeatureError (
@@ -267,12 +288,20 @@ def params(self):
267
288
268
289
def get_example_params (self ):
269
290
"""Get example parameter names."""
270
- return set (self .examples .example_params + self .feature .examples .example_params )
291
+ return set (
292
+ chain (
293
+ * map (
294
+ attrgetter ("example_params" ),
295
+ chain (self .examples .example_tables , self .feature .examples .example_tables ),
296
+ ),
297
+ )
298
+ )
271
299
272
300
def get_params (self , builtin = False ):
273
301
"""Get converted example params."""
274
302
for examples in [self .feature .examples , self .examples ]:
275
- yield examples .get_params (self .example_converters , builtin = builtin )
303
+ for example_table in examples .example_tables :
304
+ yield example_table .tags , example_table .get_params (self .example_converters , builtin = builtin )
276
305
277
306
def validate (self ):
278
307
"""Validate the scenario.
@@ -373,17 +402,28 @@ def add_step(self, step):
373
402
self .steps .append (step )
374
403
375
404
405
+ @attrs
376
406
class Examples :
407
+ example_tables : List ["ExampleTable" ] = attrib (default = Factory (list ))
408
+
409
+ def add_example_table (self , builder : Type ["ExampleTable" ]):
410
+ self .example_tables .append (builder ())
411
+
412
+ @property
413
+ def last_example_table (self ) -> "ExampleTable" :
414
+ return self .example_tables [- 1 ]
377
415
416
+
417
+ @attrs
418
+ class ExampleTable (object ):
378
419
"""Example table."""
379
420
380
- def __init__ (self ):
381
- """Initialize examples instance."""
382
- self .example_params = []
383
- self .examples = []
384
- self .vertical_examples = []
385
- self .line_number = None
386
- self .name = None
421
+ example_params = attrib (default = Factory (list ))
422
+ examples = attrib (default = Factory (list ))
423
+
424
+ line_number = attrib (default = None )
425
+ name = attrib (default = None )
426
+ tags = attrib (default = Factory (list ))
387
427
388
428
def set_param_names (self , keys ):
389
429
"""Set parameter names.
@@ -392,12 +432,28 @@ def set_param_names(self, keys):
392
432
"""
393
433
self .example_params = [str (key ) for key in keys ]
394
434
395
- def add_example (self , values ):
396
- """Add example .
435
+ def get_params (self , converters , builtin = False ):
436
+ """Get scenario pytest parametrization table .
397
437
398
- :param values : `list ` of `string` parameter values.
438
+ :param converters : `dict ` of converter functions to convert parameter values
399
439
"""
400
- self .examples .append (values )
440
+ params = []
441
+
442
+ for example in self .examples :
443
+ example = list (example )
444
+ for index , param in enumerate (self .example_params ):
445
+ raw_value = example [index ]
446
+ if converters and param in converters :
447
+ value = converters [param ](raw_value )
448
+ if not builtin or value .__class__ .__module__ in {"__builtin__" , "builtins" }:
449
+ example [index ] = value
450
+ params .append (example )
451
+ return self .example_params , params
452
+
453
+
454
+ @attrs
455
+ class TransposedExampleTable (ExampleTable ):
456
+ example_param_values = attrib (default = Factory (list ))
401
457
402
458
def add_example_row (self , param , values ):
403
459
"""Add example row.
@@ -410,39 +466,24 @@ def add_example_row(self, param, values):
410
466
f"""Example rows should contain unique parameters. "{ param } " appeared more than once"""
411
467
)
412
468
self .example_params .append (param )
413
- self .vertical_examples .append (values )
469
+ self .example_param_values .append (values )
414
470
415
471
def get_params (self , converters , builtin = False ):
416
472
"""Get scenario pytest parametrization table.
417
473
418
474
:param converters: `dict` of converter functions to convert parameter values
419
475
"""
420
- param_count = len (self .example_params )
421
- if self .vertical_examples and not self .examples :
422
- for value_index in range (len (self .vertical_examples [0 ])):
423
- example = []
424
- for param_index in range (param_count ):
425
- example .append (self .vertical_examples [param_index ][value_index ])
426
- self .examples .append (example )
427
-
428
- if self .examples :
429
- params = []
430
- for example in self .examples :
431
- example = list (example )
432
- for index , param in enumerate (self .example_params ):
433
- raw_value = example [index ]
434
- if converters and param in converters :
435
- value = converters [param ](raw_value )
436
- if not builtin or value .__class__ .__module__ in {"__builtin__" , "builtins" }:
437
- example [index ] = value
438
- params .append (example )
439
- return [self .example_params , params ]
440
- else :
441
- return []
476
+ self .examples = list (zip (* self .example_param_values ))
477
+ return super ().get_params (converters , builtin = builtin )
478
+
442
479
443
- def __bool__ (self ):
444
- """Bool comparison."""
445
- return bool (self .vertical_examples or self .examples )
480
+ class UsualExampleTable (ExampleTable ):
481
+ def add_example (self , values ):
482
+ """Add example.
483
+
484
+ :param values: `list` of `string` parameter values.
485
+ """
486
+ self .examples .append (values )
446
487
447
488
448
489
def get_tags (line ):
0 commit comments