testomaton 0.2.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1193 @@
1
+ Metadata-Version: 2.1
2
+ Name: testomaton
3
+ Version: 0.2.2
4
+ Summary: Model based combinatorial test data generator
5
+ Home-page: https://bitbucket.org/testify-no/tomato
6
+ Author: Patryk Chamuczyński, Testify AS
7
+ Author-email: p.chamuczynski@testify.no
8
+ License: GNU Affero General Public License v3 or later (AGPLv3+)
9
+ Keywords: testing pairwise test_generation
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Environment :: Console
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Classifier: Programming Language :: Python :: 3
18
+ Requires-Python: >=3.6
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: python-sat
22
+ Requires-Dist: pyparsing
23
+ Requires-Dist: pyaml
24
+ Requires-Dist: regex
25
+
26
+ # Introduction
27
+ Testomaton is a suite of tools for combinatoric testing. It consists of __tomato__ - a combinatoric test generator and two tools used for postprocessing tomato's output - __beaver__ and __jigsaw__.
28
+
29
+ # Table of context
30
+ - [Introduction](#introduction)
31
+ - [Table of context](#table-of-context)
32
+ - [Installing testomaton](#installing-testomaton)
33
+ - [Workflow](#workflow)
34
+ - [Combinatoric test generation with tomato](#combinatoric-test-generation-with-tomato)
35
+ - [Using tomato with examples in this document](#using-tomato-with-examples-in-this-document)
36
+ - [Model syntax](#model-syntax)
37
+ - [General rules](#general-rules)
38
+ - [Labels](#labels)
39
+ - [Function](#function)
40
+ - [Global parameters](#global-parameters)
41
+ - [Function parameters](#function-parameters)
42
+ - [The `parameter` element](#the-parameter-element)
43
+ - [Leaf parameters](#leaf-parameters)
44
+ - [Structures](#structures)
45
+ - [Linked parameters](#linked-parameters)
46
+ - [Output parameters](#output-parameters)
47
+ - [Choices](#choices)
48
+ - [Model logic](#model-logic)
49
+ - [Constraints](#constraints)
50
+ - [Invariants](#invariants)
51
+ - [Implication](#implication)
52
+ - [Assignments](#assignments)
53
+ - [Aliases](#aliases)
54
+ - [Statements](#statements)
55
+ - [Primitive statement](#primitive-statement)
56
+ - [Operations on statements](#operations-on-statements)
57
+ - [Generating tests with tomato](#generating-tests-with-tomato)
58
+ - [Generators](#generators)
59
+ - [Cartesian generator](#cartesian-generator)
60
+ - [Random generator](#random-generator)
61
+ - [NWise generator](#nwise-generator)
62
+ - [Constraints manipulation](#constraints-manipulation)
63
+ - [Filtering parsed elements](#filtering-parsed-elements)
64
+ - [Format of the output](#format-of-the-output)
65
+ - [Validating tests](#validating-tests)
66
+ - [Using tomato in test code](#using-tomato-in-test-code)
67
+ - [Test postprocessing](#test-postprocessing)
68
+ - [Beaver](#beaver)
69
+ - [Jigsaw](#jigsaw)
70
+
71
+
72
+ # Installing testomaton
73
+
74
+ Testomaton is hosted in the pypi package index, so installing it is as simple as:
75
+
76
+ ```bash
77
+ $ pip3 install testomaton
78
+ ```
79
+
80
+ Verify the installation by:
81
+ ```bash
82
+ $ tomato --version
83
+ tomato 0.3.0
84
+ ```
85
+
86
+ This document will explain main features and options of tools from testomaton suite. The full and up-to-date description is available in the tool's help text. Type
87
+ ```bash
88
+ $ tomato --help
89
+ $ beaver --help
90
+ $ jigsaw --help
91
+ ```
92
+ to know more.
93
+
94
+
95
+ # Workflow
96
+ ╔═════════╗ ╔══════════╗
97
+ ┌───────┐ ║ ║ .csv ║ beaver ║ ┌───────┐
98
+ │ model │╶─────►║ tomato ║╶───────►║ + ║╶──────►│ tests │
99
+ | (yaml)| ║ ║ ║ jigsaw ║ | (.csv)|
100
+ └───────┘ ╚═════════╝ ╚════════╤═╝ └───────┘
101
+ ▲ │
102
+ │ │
103
+ └──────┘
104
+ intermediate
105
+ .csv
106
+
107
+ The tools are organized so that they can be used in a pipeline. Tomato's input is a yaml file that describes a model of a test function.
108
+ The function contains parameters with the values that they can take and constraints that describe dependencies between the parameters.
109
+ Tomato parses the model and generates an .csv file that contains combinations of the values of the input parameters,
110
+ so that they validate the constraints and provide requested coverage (for example pairwise).
111
+ The output of tomato is in the .csv (comma separated values) format. The output of tomato can be provided to beaver and jigsaw.
112
+ Both accept .csv format on their input and both produce .csv on the output, so they can be used (multiple times at once) in a pipeline.
113
+ Beaver is a simple tool that replaces elements in a .csv line that have format of `@python EXPR` with the result of the expression evaluation.
114
+ Jigsaw is a simple .csv manipulation util that can be used to add, remove, replace or swap columns.
115
+
116
+
117
+ # Combinatoric test generation with tomato
118
+
119
+ Tomato is the core of the testomaton suite. Simplifying, it reads a model of a test function and generates rows of tests.
120
+ The model is defined in a yaml format and provides the description of what values can be assigned to individual parameters of the function.
121
+
122
+ ┌───────────────┐
123
+ │ test function │
124
+ └───────┬───────┘
125
+ ┌───────────────────┬───────────┴──┬────────────────┐
126
+ X1 X2 [...] Xn
127
+ ┌────┬──┴─┬────┐ ┌────┬──┴─┬────┐ ┌────┬──┴─┬────┐
128
+ x11 x12 [...] x1m x21 x22 [...] x2m xn1 xn2 [...] xnm
129
+
130
+ Additionaly for the definition of possible function's input, the model describes the relationships between the parameters in the form of constraints
131
+ that are logical expressions defining invariants that must be always fulfilled in the generated tests. For example the expression
132
+
133
+ "IF 'X1' IS 'x11' THEN 'X2' NOT IN ['x21', 'x23']"
134
+ indicates, that in tests where the value of the parameter `X1` is `x11`, the value of the parameter `X2` mustn't be `x21` or `x23`.
135
+ The language of the model and the constraints allows for easily defining more complex relationships than this.
136
+
137
+ The simplest form of a function model would be:
138
+ ```yaml
139
+ functions:
140
+ - function: duel
141
+ parameters:
142
+ - parameter: good guy
143
+ choices: [Peter, Susan, Edmund, Lucy]
144
+ - parameter: bad guy
145
+ choices: [Jadis, Maugrim]
146
+ - parameter: location
147
+ choices: [White Castle, Cair Paravel]
148
+ ```
149
+ It defines a function of three parameters (`good guy`, `bad guy`, `location`) that can take values from the sets defined as their __choices__
150
+ (`[Peter, Susan, Edmund, Lucy]`, `[Jadis, Maugrim]` and `[White Castle, Cair Paravel]` respectively).
151
+
152
+ ## Using tomato with examples in this document
153
+ If called without a filename, tomato will read the model from the standard input.
154
+ Using tomato without any additonal arguments will make it generate pairwise combinations from the first function defined in the model.
155
+ So, to test the examples from this document without having to write them to a file, start tomato:
156
+ ```
157
+ $ tomato
158
+ Reading model from stdin
159
+ ```
160
+ And then copy&paste the model to stdin:
161
+ ```
162
+ $ tomato
163
+ Reading model from stdin
164
+ functions:
165
+ - function: duel
166
+ parameters:
167
+ - parameter: good guy
168
+ choices: [Peter, Susan, Edmund, Lucy]
169
+ - parameter: bad guy
170
+ choices: [Jadis, Maugrim]
171
+ - parameter: location
172
+ choices: [White Castle, Cair Paravel]
173
+ ```
174
+ After pasting the model an `EOF` character must be sent (_Ctrl+D_ on Linux and Mac, _Ctrl+Z_ on Windows). An additional newline may also be required in some cases (press _Enter_).
175
+
176
+ Providing the model above to tomato will result in generation of a nice test suite with pairwise coverage, for example:
177
+
178
+ ```bash
179
+ good guy,bad guy,location
180
+ Peter,Maugrim,White Castle
181
+ Peter,Jadis,Cair Paravel
182
+ Lucy,Maugrim,Cair Paravel
183
+ Lucy,Jadis,White Castle
184
+ Susan,Jadis,White Castle
185
+ Susan,Maugrim,Cair Paravel
186
+ Edmund,Maugrim,White Castle
187
+ Edmund,Jadis,Cair Paravel
188
+ ```
189
+ Since the pairwise generation algorithm has a partially random nature, the output you see may be slightly different from the one above.
190
+
191
+ Pairwise generation is possible for functions with at least 3 paranmeters, so all the examples below will have at least 3 parameters, even if that is not required to illustrate something.
192
+
193
+ ## Model syntax
194
+ The format of tomato's input is a yaml file that consists an arbitrary number of functions.
195
+ Tomato processes only one function at a time, but it may be useful to group many functions in the same file for organizational reasons or if they reuse some of the same parameters.
196
+ The top elements of the yaml file may only be `functions` and `global parameters`, like described below:
197
+
198
+ ```yaml
199
+ global parameters:
200
+ # List of parameters that can be referred to from the functions defined below. Using global parameters enhances maintenance and keeps the file size smaller.
201
+
202
+ functions:
203
+ # the 'functions' element must contain a list of function elements that describe individual functions in the model. The name of the function is the defined by the value of the 'function' tag.
204
+ - function: F1
205
+ # definition of F1
206
+ - function: F2
207
+ # [...]
208
+ ```
209
+ Only the `functions` element is required. It must contain a list with at least one `function` element.
210
+
211
+ ### General rules
212
+ There are very few restrictions about naming of model elements:
213
+
214
+ - names cannot contain double colons (`::`), and they can't start or end with a colon `:`
215
+ - a name cannot be an empty string
216
+ - a name cannot start or end with a whitespace character (e.g. ` name` will not be allowed)
217
+ - a name must not contain line breaks
218
+ - names must be unique on the same level of model hierarchy (for example all global parameters must have different names)
219
+ - names on different levels may have the same names (for example nested choices)
220
+ - be careful with yaml constants that will be evaluate by python yaml package on the parsing level. For example in `choice: no`, the choice name will be evaluated to `False`. To ensure correct interpretation, it is recommended to surround names with quotes.
221
+
222
+ Other rules are:
223
+
224
+ - values of all elements in the model must be one line. The only exception is a value of an expression of elements that define function or structure logic.
225
+
226
+ ### Labels
227
+ Every element of the model (ie. `function`, `parameter`, `linked parameter`, `output parameter`, `choice`, `assignment` or `constraint`) may optionally contain a `labels` element.
228
+ Labels are defined by a flow of strings. General usage of labels is to filter elements of the model that are parsed.
229
+
230
+ Whenever allowed types of elements are mentioned in this document, they may always be extended by a `labels` tag, unless explicitly forbidden.
231
+
232
+ ```yaml
233
+ labels: [label, other label]
234
+ ```
235
+
236
+ ### Function
237
+ A `function` is the only allowed element of `functions` list. A `function` definition contains two parts: `parameters` and `logic`.
238
+ The `parameters` element enumerates the function's parameters while `logic` describes relations between them. For example:
239
+
240
+ ```yaml
241
+ functions:
242
+ - function: character
243
+ parameters:
244
+ - parameter: name
245
+ choices: [Peter, Susan, Edmund, Lucy]
246
+ - parameter: gender
247
+ choices: [M, F]
248
+ - parameter: weapon
249
+ choices: [sword, bow, dagger]
250
+
251
+ logic:
252
+ - alias: male
253
+ expression: "'gender' IS 'M'"
254
+ - constraint: naming males
255
+ expression: "IF 'male' THEN 'name' IN ['Peter', 'Edmund']"
256
+ - constraint: naming females
257
+ expression: "IF NOT 'male' THEN 'name' IN ['Susan', 'Lucy']"
258
+ ```
259
+ Here we have a definition of a function 'character' that has three parameters: `name`, `gender` and `weapon` plus logic that define dependencies between the parameters `name` and `gender`.
260
+
261
+ ### Global parameters
262
+ Parameters that are used multiple times inside the same file (may be the same function, or many different functions) may be defined as global and then linked from the functions.
263
+ This allows easier maintenance and keeping the model size compact. One global parameter may be also linked by another global parameter, or a nested parameter.
264
+ The `global parameters` section is optional. If it is defined, it must contain a list of parameters. The elements of the `global parameters` list must either `parameter` or `linked parameter`.
265
+ Unlike parameters of functions, global parameters cannot be of a `output parameter` type. Their exact syntax is the same as respective function parameters and is explained later in this document.
266
+
267
+ ```yaml
268
+ global parameters:
269
+ - parameter: Weapon
270
+ choices: [sword, bow, dagger]
271
+
272
+ functions:
273
+ - function: duel
274
+ parameters:
275
+ - parameter: good guy
276
+ choices: [Peter, Susan, Edmund, Lucy]
277
+ - linked parameter: good guy's weapon
278
+ linked to: Weapon
279
+ - parameter: bad guy
280
+ choices: [Jadis, Maugrim, a giant]
281
+ - linked parameter: bad guy's weapon
282
+ linked to: Weapon
283
+ ```
284
+
285
+ ### Function parameters
286
+ The elements of the `parameters` list of a `function` element define function parameters. The list may contain `parameter`, `linked parameter` or `output parameter` types.
287
+ We already saw a `parameter` and a `linked parameter`. An `output parameter` is a parameter that is not considered during generation of input combinations. Its value is either defined by default or may be modified by an `assignment` in the `logic` section.
288
+
289
+ ```yaml
290
+ functions:
291
+ - function: character
292
+ parameters:
293
+ - parameter: name
294
+ choices: [Peter, Lucy, Aslan]
295
+ - parameter: job
296
+ choices: [king, kid]
297
+ - output parameter: number of legs
298
+ default value: 2
299
+
300
+ logic:
301
+ - constraint: Aslan's job
302
+ expression: "IF 'name' IS 'Aslan' THEN 'job' IS 'king'"
303
+ - assignment: legs
304
+ expression: "'name' IS 'Aslan' => 'number of legs' = 4"
305
+ ```
306
+
307
+ ### The `parameter` element
308
+ A `parameter` element defines function input parameter. There are two ways a parameter may be defined, whether it is a global parameter or defined in a function:
309
+ 1. as a leaf parameter, that contain a list of choices that represent a value that the parameter can take
310
+ 2. as a list of subparameters and the logic that connects them.
311
+ It is not allowed for a parameter to define both choices and parameters. A parameter that contains a `choices` element is called a _leaf parameter_.
312
+ Parameters with nested parameters are known as _structures_.
313
+
314
+ #### Leaf parameters
315
+ Leaf parameters represent the actual input parameter of the function. All the parameters used in the examples above were leaf parameters.
316
+ A leaf parameter may only contain a single `choices` element that define values taken by the parameter.
317
+ There are two ways to define choices of a parameter. One way using yaml's flow notation, the other with a list and explicit choice definition. The list may contain only `choice` elements.
318
+
319
+ ```yaml
320
+ functions:
321
+ - function: character
322
+ parameters:
323
+ - parameter: name
324
+ choices: [Peter, Susan, Lucy, Edmund]
325
+ - parameter: location
326
+ choices: [Cair Paravel, Stone Table, Lantern Waste]
327
+ - parameter: weapon
328
+ choices:
329
+ - choice: sword
330
+ - choice: bow
331
+ - choice: dagger
332
+ ```
333
+
334
+ #### Structures
335
+ Structures are a way to logically group some parameters together. All elements of a structure will be treated as individual parameters, but grouping them allows reusing and defining constraints for them.
336
+ A parameter (global or not) that is a structure may contain only `parameters` and `logic` elements:
337
+
338
+ ```yaml
339
+ global parameters:
340
+ - parameter: Weapon
341
+ choices: [sword, bow, dagger]
342
+ - parameter: Character
343
+ parameters:
344
+ - parameter: name
345
+ choices: [Peter, Susan, Edmund, Lucy]
346
+ - parameter: gender
347
+ choices: [F, M]
348
+ - linked parameter: weapon
349
+ linked to: Weapon
350
+ logic:
351
+ - alias: male
352
+ expression: "'gender' IS 'M'"
353
+ - constraint: M name
354
+ expression: "IF 'male' THEN 'name' IN ['Peter', 'Edmund']"
355
+ - constraint: F name
356
+ expression: "IF NOT 'male' THEN 'name' IN ['Susan', 'Lucy']"
357
+
358
+ functions:
359
+ - function: duel
360
+ parameters:
361
+ - linked parameter: contestant 1
362
+ linked to: Character
363
+ - linked parameter: contestant 2
364
+ linked to: Character
365
+
366
+ logic:
367
+ - constraint: F not against F
368
+ expression: "IF 'contestant 1::gender' IS 'F' THEN 'contestant 2::gender' IS NOT 'F'"
369
+ - constraint: M not against M
370
+ expression: "IF 'contestant 1::gender' IS 'M' THEN 'contestant 2::gender' IS NOT 'M'"
371
+ ```
372
+
373
+ Note that subparameters of a structure may also be linked parameters. However, they may not be `output parameters`.
374
+ If logic is defined for a structure that is a global parameter, it will be applied for all parameters that link to it, unless explicitly disabled in the link.
375
+ Note that `alias` elements of the structure cannot be directly used in the function that instantiates the structure.
376
+
377
+ ### Linked parameters
378
+ A linked parameter is a parameter that is a copy of a global parameter. Linked parameters have a mandatory field `linked to` that defines the target of the link.
379
+ Linked parameters may link to a structure or to a leaf parameter, but must always link to a parameter that is directly defined as a global parameter.
380
+ It means that a linked parameter may not link to a subparameter of a global structure.
381
+ Optionally, linked parameters may have `constraints whitelist` or `constraints blacklist` element that defines what constraints of the link shall be considered in the linked parameter.
382
+ Obviously, these fields are mutually exclusive (a parameter cannot have both a whitelist and a blacklist). Elements of the lists are names of the constraints that should be filtered, separated by a comma (`,`).
383
+ Note that although a comma is allowed when naming or labelling a constraint (or anything else), using it may have unexpected consequences and make the model not work as intended. Using quotes when defining white and blacklists may help here.
384
+ As mentioned above, parts of structures may also be defined as linked parameters.
385
+
386
+
387
+ ```yaml
388
+ global parameters:
389
+ - parameter: Good guy
390
+ parameters:
391
+ - parameter: name
392
+ choices: [Peter, Susan, Edmund, Lucy]
393
+ - parameter: gender
394
+ choices: [F, M]
395
+ - parameter: weapon
396
+ choices: [sword, bow, dagger]
397
+ logic:
398
+ - alias: male
399
+ expression: "'gender' IS 'M'"
400
+ - constraint: M name
401
+ expression: "IF 'male' THEN 'name' IN ['Peter', 'Edmund']"
402
+ - constraint: F name
403
+ expression: "IF NOT 'male' THEN 'name' IN ['Susan', 'Lucy']"
404
+ - constraint: male weapon
405
+ expression: "IF NOT 'male' THEN 'weapon' IS NOT 'sword'"
406
+ functions:
407
+ - function: duel
408
+ parameters:
409
+ - linked parameter: hero
410
+ linked to: Good guy
411
+ constraints whitelist: M name, F name
412
+ - parameter: bad guy
413
+ choices: [Jadis, Maugrim]
414
+ ```
415
+ In the example above we define a parameter `hero` that links to a global parameter `Good guy`.
416
+ Although the `Good guy` structure defines a constraint that prevents `Susan` and `Lucy` from using a `sword`, we decide not to use that constraint in the linked parameter.
417
+ The same effect can be obtained by defining a blacklist:
418
+ ` constraints blacklist: male weapon `.
419
+
420
+ ### Output parameters
421
+ Output parameters are only allowed as top parameters of a function. They are not allowed to be defined as global parameters or subparameters of structures.
422
+ Output parameters are parameters that do not take part in generating tests. Technically, they have only one choice that is always selected, so they do not have impact on the size of the generated suite.
423
+ Their value, however may be changed to an arbitrary value depending on the values of input parameters. This can be defined by assignments is function logic.
424
+ Output parameters have one required element that is `default value`. This element defines the value that is assigned to the parameter in case that no assignment can be applied.
425
+
426
+ ```yaml
427
+ functions:
428
+ - function: duel
429
+ parameters:
430
+ - parameter: Good guy
431
+ choices: [Peter, Susan, Edmund, Lucy]
432
+ - parameter: Bad guy
433
+ choices: [Jadis, Maugrim]
434
+ - parameter: location
435
+ choices: [White Castle, Cair Paravel]
436
+ - output parameter: result
437
+ default value: Good guy wins
438
+ logic:
439
+ - assignment: Jadis wins in her castle
440
+ expression: "IF 'Bad guy' IS 'Jadis' AND 'location' IS 'White Castle' THEN 'result'= 'Jadis wins'"
441
+ ```
442
+
443
+ ### Choices
444
+ Choices represent values that can be taken by parameters. Choices can be defined simply as a flow (`choices: [sword, bow, dagger]`) or as a list of `choice` elements.
445
+ If a choice is defined explicitly in a list it may optionally contain a `value` element. Choice's value is the actual value that will be used in the result of the generation (but it is also possible to call tomato with `--use-choice-names` switch to generate tests containing choice names instead their values).
446
+ If the value is not provided, it is derived from the name. The same is applied to a flow syntax. In this case, both name and value will be taken from the flow.
447
+ Using choice name different from the value may be handy if the choice is used in the logic, but the value we want to define is very long, so repeating it in the constraint expression would be tedious. Also some names are not allowed (like strings containing only spaces), but this restriction does not apply to the choice's values. The only restriction about choice value is that it must be a single line.
448
+ As all choices are internally converted to a string, surrounding the values with quotes is a nice precaution to avoid unexpected behaviour (like parsing `yes` as `True`).
449
+ It is perfectly legal to have two choices on the same level of hierarchy (children of the same parent) with the same value, although it is not allowed for them to have the same name.
450
+
451
+ Similarily to parameters, choices can also be nested. Instead of providing a value of a choice, subchoices can be defined, using `choices` element.
452
+ A choice that has `choices` element is called _abstract choice_, otherwise the choice is a _leaf choice_. A single choice may not have both `value` and `choices` elements.
453
+ Using abstract choices is a handy way to group choices together and simplifying constraints. There is no limit for the nesting levels for choices.
454
+
455
+
456
+ ```yaml
457
+ functions:
458
+ - function: character
459
+ parameters:
460
+ - parameter: name
461
+ choices:
462
+ - choice: male
463
+ choices: [Peter, Edmund]
464
+ - choice: female
465
+ choices: [Susan, Lucy]
466
+ - parameter: gender
467
+ choices:
468
+ - choice: F
469
+ value: female
470
+ - choice: M
471
+ value: male
472
+ - parameter: weapon
473
+ choices: [sword, bow, dagger]
474
+ logic:
475
+ - alias: male
476
+ expression: "'gender' IS 'M'"
477
+ - constraint: female name and weapon
478
+ expression: "IF NOT 'male' THEN 'name' IS 'female' AND 'weapon' IS NOT 'sword'"
479
+ - constraint: male name
480
+ expression: "IF 'male' THEN 'name' IS 'male'"
481
+ ```
482
+ __IMPORTANT:__ No constraint is implicitly derived from naming the model elements. Having abstract choices named `male` and `female` has nothing to do with the values or names of the choices of the `gender` parameter.
483
+ The same with the alias `male` - it is just a coincidence that there is an abstract choice of the `name` parameter with the same name. Every constraint must be defined explicitly.
484
+
485
+ ### Model logic
486
+
487
+ Model logic defines set of constraints that define allowed combinations of input parameters and assignments to define values of output parameters. Logic may be defined for a function or for a structure.
488
+ Logic of a structure defines dependencies between the parameters of the sturcture. Logic of a function defines rules for all parameters of the function.
489
+
490
+ The `logic` element of a function is a list of elements that can be `alias`, `constraint` of `assignment`.
491
+ Logic of a structure may not contain `assignment` elements, as they define values of output parameters which are not alllowed in structures.
492
+
493
+ All elements of the list defined by `logic` contain required `expression` element that defines the actual expression of the element. The value of the expression element must always be surrounded by double quotes.
494
+ The syntax of the expressions for different elements may differ a bit and is explained below.
495
+
496
+ ```yaml
497
+ [...]
498
+ - constraint: NAME
499
+ expression: "EXPRESSION"
500
+ ```
501
+
502
+ ### Constraints
503
+
504
+ A constraint defines an expression that must be fulfilled by all tests generated from the model. The value of `expression` in the constraint may be defined as _Invariant_ or _Implication_.
505
+
506
+ #### Invariants
507
+
508
+ Invariant is an expression type in form of a single _statement_ that must always hold in the generated tests, for example:
509
+
510
+ ```yaml
511
+ functions:
512
+ - function: duel
513
+ parameters:
514
+ - parameter: good guy
515
+ choices: [Peter, Susan, Edmund, Lucy]
516
+ - parameter: bad guy
517
+ choices: [Jadis, Maugrim]
518
+ - parameter: location
519
+ choices: [Cair Paravel, White Castle]
520
+ logic:
521
+ - constraint: location
522
+ expression: "'bad guy' IS 'Jadis' OR 'location' IS 'Cair Paravel'"
523
+ ```
524
+
525
+ The expression defined by the constraint `location` must hold for all tests. This means that in all generated tests, the value of parameter `bad guy` will be `Jadis`, or the value of parameter `location` will be `Cair Paravel`. Note that there is no restriction that prevents both `bad guy` be `Jadis` and `location` be `Cair Paravel`
526
+
527
+ #### Implication
528
+ An implication is an expression in a form `"IF CONDITION THEN RESULT"` (or alternatrive notation `"CONDITION => RESULT"`), where `CONDITION` and `RESULT` are statements with the same syntax as in the invariant version of the constrait. Implications define constraints that require that for each tests where the `CONDITION` part is fulfilled, the `RESULT` part must also be true.
529
+
530
+ ```yaml
531
+ functions:
532
+ - function: duel
533
+ parameters:
534
+ - parameter: good guy
535
+ choices: [Peter, Susan, Edmund, Lucy]
536
+ - parameter: bad guy
537
+ choices: [Jadis, Maugrim]
538
+ - parameter: location
539
+ choices: [Cair Paravel, White Castle]
540
+ logic:
541
+ - constraint: location
542
+ expression: "'bad guy' IS 'Jadis' => 'location' IS NOT 'Cair Paravel'"
543
+ ```
544
+ In this example, whenever the value of `bad guy` is `Jadis`, the location will always be `White Castle`, unlike like in the previous example when `Jadis` and `Cair Paravel` could coexist.
545
+
546
+ Invariants and implications are in fact different forms of the same logic semantics. Using implications is introduced for convenient notation, but in the end all implications may be reduced to invariants, because any expression `"IF A THEN B"` can also be noted as `"NOT A OR B"`
547
+
548
+ ### Assignments
549
+ Assignments are defining values of output parameters in the tests. The syntax of an assignment is `"IF CONDITION THEN ASSIGNMENTS_LIST"` (or alternatively `"CONDITION => ASSIGNMENT_LIST"`),
550
+ where `CONDITION` is a statement and `ASSIGNMENT_LIST` is a comma separated list of assignments of values to choisen output parameters, for example: `'parameter name 1'='value', 'parameter value 2' = 'other value'`.
551
+ The value of an output parameters is set to its default value, unless the combination of parameters of the generated test fulfill the statement defined in the `CONDIDTION`. The value used in the assignment is arbitrary and does not need to be declared anywhere else in the model. There are no restrictions for values used in assignments other than for values used for choices.
552
+
553
+ ```yaml
554
+ functions:
555
+ - function: duel
556
+ parameters:
557
+ - parameter: Good guy
558
+ choices: [Peter, Susan, Edmund, Lucy]
559
+ - parameter: Bad guy
560
+ choices: [Jadis, Maugrim]
561
+ - parameter: location
562
+ choices: [White Castle, Cair Paravel]
563
+ - output parameter: result
564
+ default value: Good guy wins
565
+ - output parameter: duration
566
+ default value: 1 minute
567
+ logic:
568
+ - assignment: Jadis wins in her castle
569
+ expression: "IF 'Bad guy' IS 'Jadis' AND 'location' IS 'White Castle' THEN 'result'= 'Jadis wins', 'duration'='10 minutes'"
570
+ - assignment: Maugrim fights 5 minutes
571
+ expression: "'Bad guy' IS 'Maugrim' => 'duration'='5 minutes'"
572
+ ```
573
+ It is technically allowed to define two different assignments for the same condition, but the result of such operation are undefined. Tomato will not warn if that happens.
574
+
575
+ ### Aliases
576
+ Aliases are macros that allow defining short names for long statements used in constraints and assignments. Aliases may be then used by their names in other statements.
577
+
578
+ ```yaml
579
+ functions:
580
+ - function: duel
581
+ parameters:
582
+ - parameter: Good guy
583
+ choices: [Peter, Susan, Edmund, Lucy]
584
+ - parameter: Bad guy
585
+ choices: [Jadis, Maugrim]
586
+ - parameter: location
587
+ choices: [White Castle, Cair Paravel]
588
+ - output parameter: duration
589
+ default value: 5 minutes
590
+ logic:
591
+ - alias: Lucy against Jadis
592
+ expression: "'Good guy' IS 'Lucy' AND 'Bad guy' IS 'Jadis'"
593
+ - constraint: Lucy against Jadis in Cair Paravel
594
+ expression: "IF 'Lucy against Jadis' THEN 'location' IS 'Cair Paravel'"
595
+ - assignment: Lucy against Jadis duel duration
596
+ expression: "IF 'Lucy against Jadis' THEN 'duration'='10 minutes'"
597
+ ```
598
+
599
+ ### Statements
600
+ A _statement_ is a core concept in the model logic. Statements define invariants, coditions and results of implications and conditions of assignments.
601
+ Statements may also be assigned to aliases. Statements are built from _primitive statements_ using logical operations like `AND`, `OR`, `NOT` and grouping those in parentheses.
602
+
603
+ #### Primitive statement
604
+ A primitive statement is a building block of all statements. A primitive statement may have following forms:
605
+
606
+ `'PARAMETER' IS/IS NOT 'CHOICE'`
607
+ This statement defines a situation that a given `CHOICE` has been assigned (or not) to the `PARAMETER`.
608
+ Both parameter and the choice are defined by their names, so the description is not ambiguous even if there exist two choices with the same value.
609
+ Both `PARAMETER` and `CHOICE` may refer to lower level in hierarchy if a nested parameter or choice is used. In this case, `::` is used to separate names on individual levels.
610
+
611
+ `'PARAMETER' IN/NOT IN ['CHOICE 1', 'CHOICE 2', ...]`
612
+ This statement defines a situation when a value of a parameter is defined by one of the choices on the list (or not belong to the list, if using `NOT IN`). It is equivalent to expression `'PARAMETER' IS 'CHOICE 1' OR 'PARAMETER IS 'CHOICE 2' OR...`
613
+
614
+ The names of all elements (parameters and chocies) from the model mentioned in primitive statements must always be surrounded by single quotes.
615
+
616
+ ```yaml
617
+ global parameters:
618
+ - parameter: Good guy
619
+ parameters:
620
+ - parameter: name
621
+ choices: [Peter, Susan, Edmund, Lucy]
622
+ - parameter: weapon
623
+ choices:
624
+ - choice: bow
625
+ - choice: bladed
626
+ choices: [sword, dagger]
627
+ functions:
628
+ - function: duel
629
+ parameters:
630
+ - linked parameter: our protagonist
631
+ linked to: Good guy
632
+ - parameter: bad guy
633
+ parameters:
634
+ - parameter: name
635
+ choices: [Jadis, Maugrim]
636
+ - parameter: weapon
637
+ choices: [wand, claws]
638
+ logic:
639
+ - constraint: Maugrim fights with claws
640
+ expression: "'bad guy::name' IS 'Maugrim' => 'bad guy::weapon' IS 'claws'"
641
+ - constraint: Lucy can't use sword against Maugrim
642
+ expression: "IF 'our protagonist::name' IS 'Lucy' AND 'bad guy::name' IS 'Maugrim' THEN 'our protagonist::weapon' IN ['bow', 'bladed::dagger']"
643
+ ```
644
+ If referring to a name of a parameter is part of a structure defined as a global parameter,
645
+ we use the name of the linking parameter as the top element in the hierarchy and then follow it with the names of elements of the structure (`'our protagonist::weapon'`).
646
+
647
+ #### Operations on statements
648
+ A statement is recursively defined using primitive statements and operations on those, using `AND`, `OR` and `NOT` operators.
649
+ It is possible to group statements together using parentheses to ensure operation priorities. The order of operations is following:
650
+
651
+ - `()` parentheses have always highest priorities
652
+ - `NOT STATEMENT` negates the `STATEMENT`
653
+ - `STATEMENT AND/OR STATEMENT` defines logical AND and OR operations.
654
+
655
+ The operations are executed from left to right, so such statement:
656
+ ```
657
+ STATEMENT_1 OR NOT STATEMENT_2 AND STATEMENT_3
658
+ ```
659
+ Is equivalent to:
660
+ ```
661
+ (STATEMENT_1 OR (NOT STATEMENT_2)) AND STATEMENT_3
662
+ ```
663
+ rather than:
664
+ ```
665
+ STATEMENT_1 OR ((NOT STATEMENT_2) AND STATEMENT_3)
666
+ ```
667
+ or
668
+ ```
669
+ STATEMENT_1 OR (NOT (STATEMENT_2 AND STATEMENT_3))
670
+ ```
671
+ Using parentheses is recommended to keep the notation unambiguous.
672
+
673
+ ## Generating tests with tomato
674
+
675
+ The main function of tomato is generating tests. Tomato will read the model from a file or, if the file is not provided, from standard input.
676
+ The input file is the only positional argument of tomato. The two following commands will have the same effect:
677
+
678
+ ```bash
679
+ $ tomato model.yaml
680
+ ```
681
+ and
682
+ ```bash
683
+ $ cat model.yaml | tomato
684
+ ```
685
+ Tomato reads the model and generate lines of test that are rows of a csv file with individul tests. The output is sent to standard output.
686
+ Any errors or other text that is not a test is sent to the error output.
687
+ The `Reading model from stdin` text that is printed when tomato is started with without defining the input file, is an example of this.
688
+
689
+ If more than one function is defined in the model, tomato will generate tests for the first of them. The function may be selected using `-f|--function` argument.
690
+ If your function has white characters in the name, use quotes (e.g. `$ tomato -f 'my function'`)
691
+
692
+ ### Generators
693
+ There are three main types of generation algorithms used by tomato: _cartesian_, _random_ and _nwise_ that may be optionally customized with some additional options.
694
+ By default, the nwise algorithm is used with the parameter N set to 2, which means that the tool will generate tests with pairwise coverage.
695
+
696
+ ### Cartesian generator
697
+ The cartesian generator is the simplest generator that will output all possible combinations of parameter values that are valid according to defined constraints.
698
+ This parameter does not take any additional options.
699
+
700
+ ### Random generator
701
+ The random generator will generate rows with parameter values selected randomly. The `--length` switch will define the number of generated tests.
702
+ The default value `0` used for length is default and makes tomato generate tests until all valid combinatins were generated.
703
+
704
+ Using `--duplicates` switch will cause that two identical test may be generated. Therefore, using `--duplicates` without limiting the length will make tomato generate tests forever (which may be useful in some scenarios).
705
+
706
+ The `--adaptive` switch will make tomato generate tests that are as different from tests already generated as possible. The metric of how different two tests are is the Hamming distance (number of elements that differ). For each step, tomato will look up to max 100 tests back and calculate a test that differs the most from all of them.
707
+
708
+ ### NWise generator
709
+ The NWise generator generates tests that cover all _n-tuples_ of the space of all possible tests.
710
+ For example, for default value n=2 (pairwise coverage) it will cover all possible pairs of values of the input parameters.
711
+ Take this model as an example:
712
+
713
+ ```yaml
714
+ functions:
715
+ - function: duel
716
+ parameters:
717
+ - parameter: good guy
718
+ choices: [Peter, Susan, Edmund, Lucy]
719
+ - parameter: weapon
720
+ choices: [sword, bow, dagger]
721
+ - parameter: bad guy
722
+ choices: [Jadis, Maugrim]
723
+ ```
724
+ Tomato will generate following tests for it (using default arguments):
725
+ ```bash
726
+ good guy,weapon,bad guy
727
+ Susan,bow,Maugrim
728
+ Edmund,sword,Jadis
729
+ Lucy,sword,Maugrim
730
+ Peter,dagger,Maugrim
731
+ Peter,bow,Jadis
732
+ Edmund,bow,Maugrim
733
+ Susan,sword,Jadis
734
+ Edmund,dagger,Jadis
735
+ Susan,dagger,Maugrim
736
+ Lucy,dagger,Jadis
737
+ Peter,sword,Jadis
738
+ Lucy,bow,Jadis
739
+ ```
740
+ If you look at the output you will notice that all possible combinations of pairs of parameters are covered: `Lucy` fights using a `bow`, `Edmund` agains `Maugrim`, `Jadis` against a `sword` etc. This allows significantly reducing the number of tests that are needed to achieve relatively good coverage.
741
+
742
+ The nwise algorithm can be customized with parameters `-n` that defines the size of tuples that must be covered (n=3 will cover all possible triplets etc.).
743
+
744
+ The parameter `--coverage` defines percentage of tuples that must be covered.
745
+
746
+ The way the nwise algorithm works is by building a set of all possible n-tuples that validate the constraints and building tests for individual tuples. It tries to build such a test that covers as many tuples in the set as possible. After the test is constructed, covered tuples are removed from the set. The algorithm repeats until the set of uncovered tuples is empty.
747
+
748
+ The working of the algorithm is demonstrated if tomato is used with `--demo-level` parameter 1, 2 or 3.
749
+ The higher the value is, the more intermediate info is printed (on the error output) and the longer the algorithm waits on each step.
750
+
751
+ ### Constraints manipulation
752
+ Tomato can be used with parameters that allow to tune how constraints in the model are used. By default tomato will straightforward apply all constraints and assignments that are defined in the model.
753
+ But it may be useful to ignore them with `--ignore-constraints` and `--ignore-assignments` option.
754
+ Using the option `--negate-constrainst` will generate only tests that violate at least one constraint defined in the model, while `--invert-constraints` will produce only such tests that violate all defined constraints.
755
+
756
+ ### Filtering parsed elements
757
+ Sometimes the same model can be used to generate tests for different applications.
758
+ In some situations the applications differ only in a small detail (by not using some of the parameters or choices or using different constraints).
759
+ It is possible to reuse the same model with restricting the parsed elements using whitelists or blacklists. Take this:
760
+
761
+ ```yaml
762
+ functions:
763
+ - function: duel
764
+ parameters:
765
+ - parameter: good guy
766
+ choices:
767
+ - choice: Peter
768
+ - choice: Susan
769
+ - choice: Edmund
770
+ - choice: Lucy
771
+ - choice: Mr. Tumnus
772
+ labels: [sidekick]
773
+ - parameter: bad guy
774
+ choices:
775
+ - choice: Jadis
776
+ - choice: Maugrim
777
+ - choice: a Giant
778
+ labels: [sidekick]
779
+ - parameter: location
780
+ choices: [White Castle, Cair Paravel]
781
+ ```
782
+ When we generate tests without any additional parameters, we will get something like this:
783
+ ```
784
+ good guy,bad guy,location
785
+ Edmund,Jadis,Cair Paravel
786
+ Peter,a Giant,Cair Paravel
787
+ Lucy,Jadis,White Castle
788
+ Mr. Tumnus,Maugrim,Cair Paravel
789
+ Lucy,Maugrim,White Castle
790
+ Lucy,a Giant,Cair Paravel
791
+ Mr. Tumnus,Jadis,White Castle
792
+ Susan,a Giant,White Castle
793
+ Peter,Jadis,White Castle
794
+ Edmund,Maugrim,White Castle
795
+ Susan,Jadis,Cair Paravel
796
+ Edmund,a Giant,Cair Paravel
797
+ Mr. Tumnus,a Giant,Cair Paravel
798
+ Peter,Maugrim,White Castle
799
+ Susan,Maugrim,Cair Paravel
800
+ ```
801
+ but it may be interested with generating tests only for limited parts of the model, so we may get rid of `Mr. Tumnus` as a `good guy` and `a Giant` as a `bad guy`:
802
+
803
+ ```bash
804
+ $ tomato --blacklist sidekick
805
+ [...] //pasted model
806
+ good guy,bad guy,location
807
+ Edmund,Maugrim,Cair Paravel
808
+ Peter,Jadis,Cair Paravel
809
+ Peter,Maugrim,White Castle
810
+ Lucy,Jadis,White Castle
811
+ Lucy,Maugrim,Cair Paravel
812
+ Susan,Maugrim,Cair Paravel
813
+ Edmund,Jadis,White Castle
814
+ Susan,Jadis,White Castle
815
+ ```
816
+
817
+ Using `--whitelist` and `--blacklist` will be applied to all types of model elements. But it is also possible to filter only certain type of elements from the model:
818
+
819
+ - `--input-whitelist`, `--input-blacklist` - aplied to parameters and choices
820
+ - `--parameters-whitelist`, `--parameters-blacklist` - aplied to parameters
821
+ - `--choices-whitelist`, `--choices-blacklist` - aplied to choices
822
+ - `--logic-whitelist`, `--logic-blacklist` - aplied to constraints and assignments
823
+ - `--constraints-whitelist`, `--constrainst-blacklist` - aplied to constraints
824
+ - `--assignments-whitelist`, `--assignments-blacklist` - aplied to assignments
825
+
826
+ The elements that are not parsed are not validated semantically, but they still must be valid in terms of yaml syntax.
827
+
828
+ ### Format of the output
829
+ By default, the first row printed by tomato will consists of names of all parameters by their full path (using `::` for nested parameters).
830
+ Then, the following rows will be filled with tests consisting values of choices that were used for the parameters, separated by a comma character. This may be tuned by using following arguments:
831
+
832
+ - `-H|--no-headrow` - do not print the head row with the parameter names,
833
+ - `--use-choice-names` - choice names will be used instead of values. Nested choices will have `::` between hierarchy levels,
834
+ - `-s|--separator SEPARATOR` - use `SEPARATOR` instead of `,`. May be useful if some of the choices contain `,`.
835
+
836
+ ## Validating tests
837
+ Tomato may also be used to validate tests using `-V|--validate-tests [TEST_FILE]` option. This is a useful feature in situations when one wants to define a model having some sample tests.
838
+ Tomato will parse the model, read the tests and print the tests that could be generated from the model unaltered on the standard output.
839
+ Tests that from different reasons could not be generated using the model will be printed on the error output with some comments and formatting.
840
+
841
+ When using the `-V|--validate-tests [TEST_FILE]` option, tomato will try to load tests from the file that is provided as the optional value. If the file is not provided, then tomato will read tests from standard input.
842
+ It is also possible that the model and tests are read from the standard input. In this case, the model must be providd first and separated from tests by a line that starts with three `-` characters (three dashes)
843
+
844
+ Most common situations when a test could not be generated from a given model include:
845
+
846
+ - the number of parameters (columns) is different in the test and in the model
847
+ - the names of the parameters do not match the values in the first row
848
+ - values of parameters do not correspond to defined choices
849
+ - input parameters do not fulfill defined constraints
850
+ - value of an output parameter is different that is defined by an assignment
851
+
852
+ Options that can be used for validation:
853
+ - `--exit-on-error` - exit on the first error. If this flag is not set, the program will continue to the next test or model element after an error.
854
+ - `-F|--no-error-formatting` - do not format error messages. If this flag is not set, the error messages are formatted to be more readable and distinguishable from valid tests.
855
+ - `-M|--no-error-messages` - do not print error messages. If this flag is set, only the tests that fail validation are printed on stderr, without any additional messages.
856
+ - `--duplicate-headrow` - print the headrow both on top of the valid tests and the tests that fail validation.
857
+
858
+ Lets save the model to a file `model.yaml`:
859
+ ```yaml
860
+ functions:
861
+ - function: duel
862
+ parameters:
863
+ - parameter: Good guy
864
+ choices: [Peter, Susan, Edmund, Lucy]
865
+ - parameter: Bad guy
866
+ choices: [Jadis, Maugrim]
867
+ - parameter: location
868
+ choices: [White Castle, Cair Paravel]
869
+ - output parameter: duration
870
+ default value: 5 minutes
871
+ logic:
872
+ - alias: Lucy against Jadis
873
+ expression: "'Good guy' IS 'Lucy' AND 'Bad guy' IS 'Jadis'"
874
+ - constraint: Lucy against Jadis in Cair Paravel
875
+ expression: "IF 'Lucy against Jadis' THEN 'location' IS 'Cair Paravel'"
876
+ - assignment: Lucy against Jadis duel duration
877
+ expression: "IF 'Lucy against Jadis' THEN 'duration'='10 minutes'"
878
+ ```
879
+
880
+ Now we will generate tests from this model ignoring all constraints and assignments and try to validate it, but considering the constraints:
881
+
882
+ ```bash
883
+ $ tomato ./model.yaml --ignore-constraints --ignore-assignments | tomato ./model.yaml -V --duplicate-headrow
884
+ ```
885
+ Tomato will repeat valid tests on the standard output, for example:
886
+ ```
887
+ Good guy,Bad guy,location,duration
888
+ Susan,Maugrim,White Castle,5 minutes
889
+ Edmund,Maugrim,Cair Paravel,5 minutes
890
+ Lucy,Maugrim,Cair Paravel,5 minutes
891
+ Edmund,Jadis,Cair Paravel,5 minutes
892
+ Peter,Jadis,Cair Paravel,5 minutes
893
+ Edmund,Jadis,White Castle,5 minutes
894
+ Peter,Maugrim,White Castle,5 minutes
895
+ Susan,Jadis,Cair Paravel,5 minutes
896
+ ```
897
+ and invalid tests, with comments on the error output, for example:
898
+
899
+ <pre style="color: red;">
900
+ Good guy,Bad guy,location,duration
901
+ Lucy,Jadis,White Castle,5 minutes
902
+ Test case does not satisfy the constraints
903
+ Lucy,Jadis,Cair Paravel,5 minutes
904
+ Output values not correct
905
+ Value of parameter duration should be 10 minutes
906
+ </pre>
907
+
908
+ Again, since the nwise algorithm is partly randomized, your results may be different.
909
+
910
+ ## Using tomato in test code
911
+
912
+ Tomato can be easily integrated with test frameworks like `pytest`. The model can be defined as a separate file, or directly in the test code:
913
+
914
+ ```python
915
+ import pytest
916
+ import subprocess
917
+ import os
918
+
919
+ def run_tomato_with_stdin(model: str):
920
+ result = subprocess.run(['tomato', '-H'], input=model.encode('utf-8'), stdout=subprocess.PIPE)
921
+ for line in [l for l in result.stdout.decode('utf-8').split('\n') if l != '']:
922
+ yield line.split(',')
923
+
924
+ model = """
925
+ functions:
926
+ - function: duel
927
+ parameters:
928
+ - parameter: good guy
929
+ choices: [Peter, Susan, Edmund, Lucy]
930
+ - parameter: weapon
931
+ choices: [sword, bow, dagger]
932
+ - parameter: bad guy
933
+ choices: [Jadis, Maugrim]
934
+ """
935
+
936
+
937
+ @pytest.mark.parametrize('good_guy, weapon, bad_guy',
938
+ run_tomato_with_stdin(model))
939
+ def test_function_with_string_model(good_guy, weapon, bad_guy):
940
+ print(f'\n{good_guy.strip()} fights with {bad_guy.strip()} using {weapon.strip()}')
941
+
942
+ ```
943
+
944
+ Save the file as `test_example.py` and run with `pytest -s` to see the program output:
945
+
946
+ ```bash
947
+ $ pytest ./test_example.py -s
948
+ [...]
949
+ collecting ... Reading model from stdin
950
+ collected 12tems
951
+
952
+ test_example.py Peter fights with Jadis using dagger
953
+ .Edmund fights with Jadis using sword
954
+ .Peter fights with Maugrim using sword
955
+ .Lucy fights with Jadis using bow
956
+ .Susan fights with Maugrim using bow
957
+ .Susan fights with Jadis using sword
958
+ .Lucy fights with Maugrim using dagger
959
+ .Susan fights with Maugrim using dagger
960
+ .Peter fights with Maugrim using bow
961
+ .Edmund fights with Maugrim using dagger
962
+ .Lucy fights with Jadis using sword
963
+ .Edmund fights with Maugrim using bow
964
+
965
+ =================== 12 passed in 0.15s ===================
966
+ ```
967
+ The function `test_function_with_string_model` is a parameterized test, where the parameters `good_guy`, `weapon` and `bad_guy` are provided by the `run_tomato_with_stdin` generator.
968
+ This function runs tomato as a subprocess and sends the model to its input while redirecting its output back to itself. Then tokenizes line by line and yields the parameters that are provided to the test.
969
+ Tomato is started with `-H` parameter to skip the first row that contains parameter names and provide only the actual values.
970
+
971
+ # Test postprocessing
972
+
973
+ Tests generated by tomato can be directly provided to other tools in the testomaton suite for postprocessing.
974
+ The default format of tomato output should be compatible with the input format of beaver and jigsaw, but remember to consistently use any modifiers, for example the separator.
975
+
976
+ ## Beaver
977
+
978
+ The input to beaver is a csv file. Beaver will process the input row by row and outputs them unchanged, unless it finds a value that starts with `@python` tag.
979
+ These values will be replaced by result of evaluation of what follows the tag. Before the evaluation, beaver will replace content of `{COLUMN}` by the content of that column.
980
+ `COLUMN` may be an index of a column in the file or name of the parameter that is defined in that column. Beaver will take the names of parameters from the first processed row.
981
+
982
+ Note that due to `@` having a special meaning in yaml, the content of the `winner name` parameter must be in quotes.
983
+
984
+ ```yaml
985
+ functions:
986
+ - function: duel
987
+ parameters:
988
+ - parameter: good guy
989
+ choices: [Peter, Susan, Edmund, Lucy]
990
+ - parameter: weapon
991
+ choices: [sword, bow, dagger]
992
+ - parameter: bad guy
993
+ choices: [Jadis, Maugrim]
994
+ - output parameter: good guy wins
995
+ default value: 'yes'
996
+ - output parameter: winner name
997
+ default value: '@python {good guy} if {good guy wins} == "yes" else {bad guy}'
998
+ logic:
999
+ - assignment: Peter with a bow
1000
+ expression: "IF 'good guy' IS 'Peter' AND 'weapon' IS 'bow' THEN 'good guy wins' = 'no'"
1001
+ ```
1002
+
1003
+ The model contains an output parameter `winner name`.
1004
+ The value of that parameter is defined as a python expression that depends on the value of other parameters: `good guy wins` determines the column from where the actual value is taken: from the column `good guy` or `bad guy`.
1005
+
1006
+ We can generate tests by tomato and provide it directly to beaver:
1007
+
1008
+ ```bash
1009
+ $ tomato | beaver
1010
+ [...]
1011
+ good guy,weapon,bad guy,good guy wins,winner name
1012
+ Edmund,bow,Jadis,yes,Edmund
1013
+ Peter,sword,Maugrim,yes,Peter
1014
+ Susan,sword,Jadis,yes,Susan
1015
+ Susan,dagger,Maugrim,yes,Susan
1016
+ Lucy,dagger,Jadis,yes,Lucy
1017
+ Peter,bow,Jadis,no,Jadis
1018
+ Edmund,sword,Maugrim,yes,Edmund
1019
+ Edmund,dagger,Jadis,yes,Edmund
1020
+ Lucy,sword,Maugrim,yes,Lucy
1021
+ Lucy,bow,Maugrim,yes,Lucy
1022
+ Peter,dagger,Maugrim,yes,Peter
1023
+ Susan,bow,Maugrim,yes,Susan
1024
+ ```
1025
+
1026
+
1027
+ Beaver can use all available functions of packages that are available on the host system. To import packages, use `-i|--imports IMPORTS` argument, where `IMPORTS` is a comma separated list od packages.
1028
+ Optionally the packages may be assigned with aliases using `as` keyword. Aliases may be useful is more than one package is imported with the same name. If using aliases, always surround the import with single quotes, eg. `-i 'random as rand','datetime as dt'`.
1029
+
1030
+ ```yaml
1031
+ functions:
1032
+ - function: duel
1033
+ parameters:
1034
+ - parameter: good guy
1035
+ choices: [Peter, Susan, Edmund, Lucy]
1036
+ - parameter: weapon
1037
+ choices: [sword, bow, dagger]
1038
+ - parameter: bad guy
1039
+ choices: [Jadis, Maugrim]
1040
+ - output parameter: good guy wins
1041
+ default value: '@python "yes" if rand.choice([True, False]) else "no"'
1042
+ - output parameter: winner name
1043
+ default value: '@python {good guy} if {good guy wins} == "yes" else {bad guy}'
1044
+ ```
1045
+ The value of `good guy wins` parameter is evaluated using `choice` function (nothing to do with tomato's choice) from the `random` package, but the package is imported as `rand`.
1046
+
1047
+ ```bash
1048
+ $ tomato | beaver -i 'random as rand'
1049
+ [...]
1050
+ good guy,weapon,bad guy,good guy wins,winner name
1051
+ Peter,dagger,Maugrim,no,Maugrim
1052
+ Susan,sword,Maugrim,no,Maugrim
1053
+ Peter,bow,Jadis,yes,Peter
1054
+ Edmund,dagger,Jadis,no,Jadis
1055
+ Lucy,dagger,Maugrim,no,Maugrim
1056
+ Peter,sword,Jadis,yes,Peter
1057
+ Edmund,sword,Maugrim,no,Maugrim
1058
+ Lucy,bow,Jadis,no,Jadis
1059
+ Susan,bow,Jadis,yes,Susan
1060
+ Susan,dagger,Maugrim,yes,Susan
1061
+ Lucy,sword,Jadis,no,Jadis
1062
+ Edmund,bow,Maugrim,no,Maugrim
1063
+ ```
1064
+
1065
+ It is possible to define own functions and use them in beaver. Own functions shoud be defined in a file that is then provided to beaver with `-m|--modules` argument.
1066
+ As with packages, the imported modules can be given aliases using `as` word.
1067
+
1068
+ Create such a file and save it as `module.py`:
1069
+
1070
+ ```python
1071
+ def determine_winner(fighter_1, fighter_2, weapon):
1072
+ if fighter_1 == 'Edmund' and weapon == 'dagger':
1073
+ return fighter_2
1074
+ return fighter_1
1075
+ ```
1076
+
1077
+ Then, let us provide following model to tomato and redirect the output to beaver that loads the `module.py` file:
1078
+
1079
+ ```yaml
1080
+ functions:
1081
+ - function: duel
1082
+ parameters:
1083
+ - parameter: good guy
1084
+ choices: [Peter, Susan, Edmund, Lucy]
1085
+ - parameter: weapon
1086
+ choices: [sword, bow, dagger]
1087
+ - parameter: bad guy
1088
+ choices: [Jadis, Maugrim]
1089
+ - output parameter: winner name
1090
+ default value: '@python mod.determine_winner({good guy}, {bad guy}, {weapon})'
1091
+ ```
1092
+ We will import the module `module.py` as `mod`.
1093
+ Because the value of the parameter `winner name` contain commas, we have to use an alternative separator for columns in the output of tomato and input of beaver.
1094
+ The output of beaver may use standard separator.
1095
+
1096
+ ```bash
1097
+ $ tomato -S '|' | beaver -m 'module.py as mod' -s '|' -S ','
1098
+ [...]
1099
+ good guy,weapon,bad guy,winner name
1100
+ Edmund,bow,Maugrim,Edmund
1101
+ Susan,sword,Jadis,Susan
1102
+ Lucy,sword,Maugrim,Lucy
1103
+ Peter,dagger,Jadis,Peter
1104
+ Susan,dagger,Maugrim,Susan
1105
+ Edmund,dagger,Jadis,Jadis
1106
+ Peter,sword,Maugrim,Peter
1107
+ Edmund,sword,Maugrim,Edmund
1108
+ Lucy,bow,Jadis,Lucy
1109
+ Lucy,dagger,Maugrim,Lucy
1110
+ Susan,bow,Maugrim,Susan
1111
+ Peter,bow,Jadis,Peter
1112
+ ```
1113
+
1114
+ It is also possible to define a module that import packages used in the expressions. Let us have following file as `imports.py`
1115
+
1116
+ ```python
1117
+ import random as rand
1118
+ ```
1119
+ and define following model:
1120
+ ```yaml
1121
+ functions:
1122
+ - function: duel
1123
+ parameters:
1124
+ - parameter: good guy
1125
+ choices: [Peter, Susan, Edmund, Lucy]
1126
+ - parameter: weapon
1127
+ choices: [sword, bow, dagger]
1128
+ - parameter: bad guy
1129
+ choices: [Jadis, Maugrim]
1130
+ - output parameter: winner
1131
+ default value: '@python rand.choice([{good guy}, {bad guy}])'
1132
+ ```
1133
+ Now we can import the module `imports.py` and use all packages that the module imports. This is a useful feature in case when we have to use many packages.
1134
+
1135
+ __IMPORTANT__: Beaver will happily evaluate any python code it is provided with. This can be malicious code that does damages to your computer. Never use python with input that you do not trust!
1136
+
1137
+ ## Jigsaw
1138
+ Jigsaw is a simple tool to manipulate csv files it gets on its input. It can add, remove, replace or swap two columns of the input file.
1139
+ The columns may be identified by their name (deined in the first row) or index. In case they are defined using the index, the indices start from 1. The value -1 identifies the last column.
1140
+
1141
+ Jigsaw may be useful tool if we want to provide the output of tomato directly to other tools, but need to slightly modify the format. Let's take the example model:
1142
+
1143
+ ```yaml
1144
+ functions:
1145
+ - function: duel
1146
+ parameters:
1147
+ - parameter: good guy
1148
+ choices: [Peter, Susan, Edmund, Lucy]
1149
+ - parameter: weapon
1150
+ choices: [sword, bow, dagger]
1151
+ - parameter: bad guy
1152
+ choices: [Jadis, Maugrim]
1153
+ - output parameter: good guy wins
1154
+ default value: 'yes'
1155
+ - output parameter: winner name
1156
+ default value: '@python {good guy} if {good guy wins} == "yes" else {bad guy}'
1157
+ - output parameter: loser name
1158
+ default value: '@python {bad guy} if {good guy wins} == "yes" else {good guy}'
1159
+ logic:
1160
+ - assignment: winner
1161
+ expression: "IF 'bad guy' IS 'Maugrim' AND 'weapon' IS 'sword' THEN 'good guy wins'='no'"
1162
+ ```
1163
+
1164
+ Lets imagine that we need to provide to our test application only a file with three columns: `winner name`, `loser name` and `weapon` in that order.
1165
+ We can easily use jigsaw to filter the unwanted columns and reorder them:
1166
+
1167
+ ```bash
1168
+ $ tomato | beaver | jigsaw -W 'winner name','loser name','weapon' -X 'weapon' 'bad guy'
1169
+ [...]
1170
+ loser name,winner name,weapon
1171
+ Maugrim,Lucy,dagger
1172
+ Maugrim,Susan,bow
1173
+ Jadis,Edmund,sword
1174
+ Jadis,Peter,bow
1175
+ Peter,Maugrim,sword
1176
+ Jadis,Lucy,sword
1177
+ Maugrim,Edmund,bow
1178
+ Jadis,Edmund,dagger
1179
+ Jadis,Susan,dagger
1180
+ Maugrim,Peter,dagger
1181
+ Maugrim,Lucy,bow
1182
+ Susan,Maugrim,sword
1183
+ ```
1184
+ The parameter `-W|--whitelist` defines the columns that should be printed. The parameter `-X` defines columns to be swapped. Note that you can use the names of the column that eventually will not be printed. Thi the example above, we swapped columns `weapon` and `bad guy`, but we printed only the first one. Other arguments that can be used with jigsaw:
1185
+
1186
+ - `-n [COLUMN_NAME]` - adds a column with line numbers. The optional value `COLUMN NAME` is the name of the column (used in the first row). Empty by default.
1187
+ - `-B|--blacklist` - blacklist of columns to be parsed
1188
+ - `-W|--whitelist` - whitelist of columns to be parsed
1189
+ - `-A <COLUMN|INDEX> <NEW_NAME> <VALUE>` - adds a column after the columns with name `COLUMN` or index `INDEX`. The column name will be defined by `NEW_NAME` and the values in all rows will be `VALUE`. Useful to add python expressions to the result of tomato output.
1190
+ - `-F <COLUMN|INDEX> <NEW_NAME> <VALUE>` - adds a column before the columns with name `COLUMN` or index `INDEX`. The column name will be defined by `NEW_NAME` and the values in all rows will be `VALUE`. Useful to add python expressions to the result of tomato output.
1191
+ - `-R COLUMN|INDEX NEW_NAME VALUE` replaces column identified by name of index by the `NEW_NAME` in the first row and `VALUE` in all other rows
1192
+ - `-X COLUMN|INDEX COLUMN|INDEX` - swaps two columns.
1193
+