tap_parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/README.md +30 -0
- data/REFERENCE.md +715 -0
- data/lib/tap_parser/version.rb +3 -0
- data/lib/tap_parser.rb +230 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 808b9b7b9ed16c12c3a5368714e6c8fb08cca00f5bfdd84d8ad504239c6b68a3
|
4
|
+
data.tar.gz: 7767a4fede220c8b04c8c92c8d40f1a9a2bd9cd7f17e10d18b429f10212fb2a5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f28b5a08f97aa2e0ce0115ce4ec8250953181fa537bb867122b4a40eca1539d36009916299aadffe4cec599769cfc4d76752a86ac5f5e6341e73d14d68dc1298
|
7
|
+
data.tar.gz: 448dbeef622785c86b57826251b9839d76617f6a19521d13a93c6397cf430b322b1b146959e93ae481c1ed14753ad0015e7354d5213b8bb6f3213a0ebb45ce31
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lib/arduino_ci.rb lib/**/*.rb
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
# TapParser Ruby gem (`tap_parser`)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/tap_parser.svg)](https://rubygems.org/gems/tap_parser)
|
4
|
+
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/tap_parser/0.1.0)
|
5
|
+
|
6
|
+
`tap_parser` is a Ruby gem that parses the [Test Anything Protocol](https://testanything.org/).
|
7
|
+
|
8
|
+
## Quick start
|
9
|
+
|
10
|
+
First `gem install tap_parser`. Then:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
require "TAPParser"
|
14
|
+
require "pathname"
|
15
|
+
require "json"
|
16
|
+
|
17
|
+
result_file = Pathname.new("/path/to/myfile.tap")
|
18
|
+
result = TAPParser.parse(result_file.basename, File.read(result_file).each_line)
|
19
|
+
puts JSON.pretty_generate(result, indent: " ")
|
20
|
+
```
|
21
|
+
|
22
|
+
|
23
|
+
## Author
|
24
|
+
|
25
|
+
This gem was written by Ian Katz (ianfixes@gmail.com) in 2023. It's released under the Apache 2.0 license.
|
26
|
+
|
27
|
+
|
28
|
+
## See Also
|
29
|
+
|
30
|
+
* [Contributing](CONTRIBUTING.md)
|
data/REFERENCE.md
ADDED
@@ -0,0 +1,715 @@
|
|
1
|
+
# Build / Test Behavior of Arduino CI
|
2
|
+
|
3
|
+
All tests are run via the same command: `bundle exec arduino_ci.rb`.
|
4
|
+
|
5
|
+
This script is responsible for detecting and running all unit tests, on every combination of Arduino platform and C++ compiler. This is followed by attempting to detect and build every example on every "default" Arduino platform.
|
6
|
+
|
7
|
+
As a prerequisite, all Arduino "default" platforms are installed if they are not already available.
|
8
|
+
|
9
|
+
These defaults are specified in [misc/default.yml](misc/default.yml). You are free to define new platforms and different compilers as you see fit, using your own project-specific overrides.
|
10
|
+
|
11
|
+
|
12
|
+
## Directly Overriding Build Behavior (short term use)
|
13
|
+
|
14
|
+
When testing locally, it's often advantageous to limit the number of tests that are performed to only those tests that relate to the work you're doing; you'll get a faster turnaround time in seeing the results. For a full listing, see `bundle exec arduino_ci.rb --help`.
|
15
|
+
|
16
|
+
|
17
|
+
### `--skip-unittests` option
|
18
|
+
|
19
|
+
This completely skips the unit testing portion of the CI script.
|
20
|
+
|
21
|
+
|
22
|
+
### `--skip-compilation` option (deprecated)
|
23
|
+
|
24
|
+
This completely skips the compilation tests (of library examples) portion of the CI script. It does not skip the compilation of unit tests.
|
25
|
+
|
26
|
+
|
27
|
+
### `--skip-examples-compilation` option
|
28
|
+
|
29
|
+
This completely skips the compilation tests (of library examples) portion of the CI script. It does not skip the compilation of unit tests.
|
30
|
+
|
31
|
+
|
32
|
+
### `--skip-library-properties` option
|
33
|
+
|
34
|
+
This completely skips validation of entries in `library.properties`.
|
35
|
+
|
36
|
+
|
37
|
+
### `--testfile-select` option
|
38
|
+
|
39
|
+
This allows a file (or glob) pattern to be executed in your tests directory, creating a whitelist of files to test. E.g. `--testfile-select=test_animal_*.cpp` would match `test_animal_cat.cpp` and `test_animal_dog.cpp` (testing only those) and not `test_plant_rose.cpp`.
|
40
|
+
|
41
|
+
|
42
|
+
### `--testfile-reject` option
|
43
|
+
|
44
|
+
This allows a file (or glob) pattern to be executed in your tests directory, creating a blacklist of files to skip. E.g. `--testfile-reject=test_animal_*.cpp` would match `test_animal_cat.cpp` and `test_animal_dog.cpp` (skipping those) and test only `test_plant_rose.cpp`, `test_plant_daisy.cpp`, etc.
|
45
|
+
|
46
|
+
|
47
|
+
### `--min-free-space` option
|
48
|
+
|
49
|
+
This specifies the minimum free SRAM memory for stack/heap, in bytes, that _must_ be leftover after compilation. This value applies globally -- to _all_ platforms that will be included in a test run.
|
50
|
+
|
51
|
+
|
52
|
+
### `CUSTOM_INIT_SCRIPT` environment variable
|
53
|
+
|
54
|
+
If set, testing will execute (using `/bin/sh`) the script referred to by this variable -- relative to the current working directory (i.e. the root directory of the library). The script will _run_ in the Arduino Libraries directory (changing to the Libraries directory, running the script, and returning to the individual library root afterward). This enables use cases like the GitHub action to install custom library versions (i.e. a version of a library that is different than what the library manager would automatically install by name) prior to CI test runs.
|
55
|
+
|
56
|
+
|
57
|
+
### `USE_SUBDIR` environment variable
|
58
|
+
|
59
|
+
If set, testing will be conducted in this subdirectory (relative to the working directory). This is for monorepos or other layouts where the library directory and project root directory are different.
|
60
|
+
|
61
|
+
|
62
|
+
### `EXPECT_UNITTESTS` environment variable
|
63
|
+
|
64
|
+
If set, testing will fail if no unit test files are detected (or if the directory does not exist). This is to avoid communicating a passing status in cases where a commit may have accidentally moved or deleted the test files.
|
65
|
+
|
66
|
+
|
67
|
+
### `EXPECT_EXAMPLES` environment variable
|
68
|
+
|
69
|
+
If set, testing will fail if no example sketches are detected. This is to avoid communicating a passing status in cases where a commit may have accidentally moved or deleted the examples.
|
70
|
+
|
71
|
+
|
72
|
+
## Indirectly Overriding Build Behavior (medium term use), and Advanced Options
|
73
|
+
|
74
|
+
For build behavior that you'd like to persist across commits (e.g. defining the set of platforms to test against, disabling a test that you expect to re-enable at some future point), a special configuration file called `.arduino-ci.yml` can be used. There are 3 places you can put them:
|
75
|
+
|
76
|
+
1. the root of your library
|
77
|
+
2. the `test/` directory
|
78
|
+
3. a subdirectory of `examples/`
|
79
|
+
|
80
|
+
`.arduino-ci.yml` files in `test/` or an example sketch take section-by-section precedence over a file in the library root, which takes precedence over the default configuration.
|
81
|
+
|
82
|
+
|
83
|
+
### Defining New Arduino Platforms
|
84
|
+
|
85
|
+
Arduino boards are typically named in the form `manufacturer:family:model`. These definitions are not arbitrary -- they are defined in an Arduino _package_. For all but the built-in packages, you will need a package URL. Here is Adafruit's: https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
|
86
|
+
|
87
|
+
Here is how you would declare a package that includes the `potato:salad` set of platforms (aka "board family") in your `.arduino-ci.yml`:
|
88
|
+
|
89
|
+
```yaml
|
90
|
+
packages:
|
91
|
+
potato:salad:
|
92
|
+
url: https://potato.github.io/arduino-board-index/package_salad_index.json
|
93
|
+
```
|
94
|
+
|
95
|
+
To define a platform called `bogo` that uses a board called `potato:salad:bogo` (based on the `potato:salad` family), set it up in the `plaforms:` section. Note that this will override any default configuration of `bogo` if it had existed in `arduino_ci`'s `misc/default.yml` file. If this board defines particular features in the compiler, you can set those here.
|
96
|
+
|
97
|
+
> Note that the platform names are arbitrary -- just keys in this yaml file and in the [`default.yml`](https://github.com/Arduino-CI/arduino_ci/blob/master/misc/default.yml) file included in this gem. That said, they are also case sensitive; defining the `bogo` platform will not let you refer to it as `Bogo` nor `BOGO`.
|
98
|
+
|
99
|
+
```yaml
|
100
|
+
platforms:
|
101
|
+
# our custom definition of the "bogo" platform
|
102
|
+
bogo:
|
103
|
+
board: potato:salad:bogo
|
104
|
+
package: potato:salad
|
105
|
+
gcc:
|
106
|
+
features:
|
107
|
+
- omit-frame-pointer # becomes -fomit-frame-pointer flag
|
108
|
+
defines:
|
109
|
+
- HAVE_THING # becomes -DHAVE_THING flag
|
110
|
+
warnings:
|
111
|
+
- no-implicit # becomes -Wno-implicit flag
|
112
|
+
flags:
|
113
|
+
- -foobar # becomes -foobar flag
|
114
|
+
|
115
|
+
# overriding the `zero` platform, to remove it completely
|
116
|
+
zero: ~
|
117
|
+
|
118
|
+
# redefine the existing esp8266
|
119
|
+
esp8266:
|
120
|
+
board: esp8266:esp8266:booo
|
121
|
+
package: esp8266:esp8266
|
122
|
+
gcc:
|
123
|
+
features:
|
124
|
+
defines:
|
125
|
+
warnings:
|
126
|
+
flags:
|
127
|
+
```
|
128
|
+
|
129
|
+
### Control How Examples Are Compiled
|
130
|
+
|
131
|
+
Put a file `.arduino-ci.yml` in each example directory where you require a different configuration than default.
|
132
|
+
The `compile:` section controls the platforms on which the compilation will be attempted, as well as any external libraries that must be installed and included. This works by _overriding_ portions of the default configuration.
|
133
|
+
|
134
|
+
> Note that the platform names _must_ match (case-sensitive) the platform names in the underlying [`default.yml`](https://github.com/Arduino-CI/arduino_ci/blob/master/misc/default.yml), or else match platforms that you have defined yourself in your `.arduino-ci.yml` override.
|
135
|
+
|
136
|
+
```yaml
|
137
|
+
compile:
|
138
|
+
# Choosing to run compilation tests on 2 different Arduino platforms
|
139
|
+
platforms:
|
140
|
+
- esp8266
|
141
|
+
- bogo
|
142
|
+
|
143
|
+
# Declaring Dependent Arduino Libraries (to be installed via the Arduino Library Manager)
|
144
|
+
libraries:
|
145
|
+
- "Adafruit FONA Library"
|
146
|
+
```
|
147
|
+
|
148
|
+
|
149
|
+
### Control How Unit Tests Are Compiled and Run
|
150
|
+
|
151
|
+
For your unit tests, in addition to setting specific libraries and platforms, you may filter the list of test files that are compiled and tested and choose additional compilers on which to run your tests.
|
152
|
+
|
153
|
+
Filtering your unit tests may help speed up targeted testing locally, but it is intended primarily as a means to temporarily disable tests between individual commits.
|
154
|
+
|
155
|
+
Furthermore, you can filter the files that will be included in the compilation step by specifying `exclude_dirs`. All cpp and header files in those directories will not be included in the compilation step, before the unittests are run.
|
156
|
+
|
157
|
+
```yaml
|
158
|
+
unittest:
|
159
|
+
|
160
|
+
# Exclude these directories from compilation
|
161
|
+
exclude_dirs:
|
162
|
+
- someDirectory
|
163
|
+
- someOtherDirectory
|
164
|
+
|
165
|
+
# Perform unit tests with these compilers (these are the binaries that will be called via the shell)
|
166
|
+
compilers:
|
167
|
+
- g++ # default
|
168
|
+
- g++-4.9
|
169
|
+
- g++-7
|
170
|
+
|
171
|
+
# Filter the list of test files in some way
|
172
|
+
testfiles:
|
173
|
+
# files matching this glob (executed inside the `test/` directory) will be whitelisted for testing
|
174
|
+
select:
|
175
|
+
- "*-*.*"
|
176
|
+
|
177
|
+
# files matching this glob will be blacklisted from testing
|
178
|
+
reject:
|
179
|
+
- "sam-squamsh.*"
|
180
|
+
|
181
|
+
# These dependent libraries will be installed
|
182
|
+
libraries:
|
183
|
+
- "abc123"
|
184
|
+
- "def456"
|
185
|
+
|
186
|
+
# each of these platforms will be used when compiling the unit tests
|
187
|
+
platforms:
|
188
|
+
- bogo
|
189
|
+
```
|
190
|
+
|
191
|
+
The expected number of tests will be the product of:
|
192
|
+
|
193
|
+
* Number of compilers defined
|
194
|
+
* Number of platforms defined
|
195
|
+
* Number of matching test files
|
196
|
+
|
197
|
+
|
198
|
+
## Writing Unit tests in `test/`
|
199
|
+
|
200
|
+
All `.cpp` files in the `test/` directory of your Arduino library are assumed to contain unit tests. Each and every one will be compiled and executed on its own.
|
201
|
+
|
202
|
+
|
203
|
+
### Most Basic Unit Test
|
204
|
+
|
205
|
+
The most basic unit test file is as follows:
|
206
|
+
|
207
|
+
```C++
|
208
|
+
#include <ArduinoUnitTests.h>
|
209
|
+
#include "../do-something.h"
|
210
|
+
|
211
|
+
unittest(your_test_name)
|
212
|
+
{
|
213
|
+
assertEqual(4, doSomething());
|
214
|
+
}
|
215
|
+
|
216
|
+
unittest_main()
|
217
|
+
```
|
218
|
+
|
219
|
+
This test defines one `unittest` (a macro provided by `ArduinoUnitTests.h`), called `your_test_name`, which makes some assertions on the target library. The `unittest_main()` is a macro for the `int main()` boilerplate required for unit testing.
|
220
|
+
|
221
|
+
### Assertions
|
222
|
+
|
223
|
+
The following assertion functions are available in unit tests.
|
224
|
+
|
225
|
+
```c++
|
226
|
+
assertEqual(expected, actual); // a == b
|
227
|
+
assertNotEqual(unwanted, actual); // a != b
|
228
|
+
assertComparativeEquivalent(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b))
|
229
|
+
assertComparativeNotEquivalent(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b))
|
230
|
+
assertLess(upperBound, actual); // a < b
|
231
|
+
assertMore(lowerBound, actual); // a > b
|
232
|
+
assertLessOrEqual(upperBound, actual); // a <= b
|
233
|
+
assertMoreOrEqual(lowerBound, actual); // a >= b
|
234
|
+
assertTrue(actual);
|
235
|
+
assertFalse(actual);
|
236
|
+
assertNull(actual);
|
237
|
+
|
238
|
+
// special cases for floats
|
239
|
+
assertEqualFloat(expected, actual, epsilon); // fabs(a - b) <= epsilon
|
240
|
+
assertNotEqualFloat(unwanted, actual, epsilon); // fabs(a - b) >= epsilon
|
241
|
+
assertInfinity(actual); // isinf(a)
|
242
|
+
assertNotInfinity(actual); // !isinf(a)
|
243
|
+
assertNAN(arg); // isnan(a)
|
244
|
+
assertNotNAN(arg); // !isnan(a)
|
245
|
+
```
|
246
|
+
|
247
|
+
These functions will report the result of the test to the console, and the testing will continue if they fail.
|
248
|
+
|
249
|
+
**If a test failure indicates that all subsequent tests will also fail** then it might be wiser to use _assure_ instead of _assert_ (e.g. `assureEqual(1, myVal)`). All of the above "assert" functions has a corresponding "assure" function; if the result is failure, the remaining tests in the unit test file are not run.
|
250
|
+
|
251
|
+
|
252
|
+
### Test Setup and Teardown
|
253
|
+
|
254
|
+
For steps that are common to all tests, setup and teardown functions may optionally be supplied.
|
255
|
+
|
256
|
+
```C++
|
257
|
+
#include <ArduinoUnitTests.h>
|
258
|
+
|
259
|
+
int* myNumber;
|
260
|
+
|
261
|
+
unittest_setup()
|
262
|
+
{
|
263
|
+
myNumber = new int(4);
|
264
|
+
}
|
265
|
+
|
266
|
+
unittest_teardown()
|
267
|
+
{
|
268
|
+
delete myNumber;
|
269
|
+
myNumber = NULL;
|
270
|
+
}
|
271
|
+
|
272
|
+
unittest(your_test_name)
|
273
|
+
{
|
274
|
+
assertEqual(4, *myNumber);
|
275
|
+
}
|
276
|
+
|
277
|
+
unittest_main()
|
278
|
+
```
|
279
|
+
|
280
|
+
|
281
|
+
# Build Scripts
|
282
|
+
|
283
|
+
For most build environments, the only script that need be executed by the CI system is
|
284
|
+
|
285
|
+
```shell
|
286
|
+
# simplest build script
|
287
|
+
bundle install
|
288
|
+
bundle exec arduino_ci.rb
|
289
|
+
```
|
290
|
+
|
291
|
+
However, more flexible usage is available:
|
292
|
+
|
293
|
+
### Custom Versions of external Arduino Libraries
|
294
|
+
|
295
|
+
Sometimes you need a fork of an Arduino library instead of the version that will be installed via their GUI. `arduino_ci.rb` won't overwrite existing downloaded libraries with fresh downloads, but it won't fetch the custom versions for you either.
|
296
|
+
|
297
|
+
If this is the behavior you need, `ensure_arduino_installation.rb` is for you. It ensures that an Arduino binary is available on the system.
|
298
|
+
|
299
|
+
```shell
|
300
|
+
# Example build script
|
301
|
+
bundle install
|
302
|
+
|
303
|
+
# ensure the Arduino installation -- creates the Library directory
|
304
|
+
bundle exec ensure_arduino_installation.rb
|
305
|
+
|
306
|
+
# manually install a custom library from a zip file
|
307
|
+
wget https://hosting.com/custom_library.zip
|
308
|
+
unzip -o custom_library.zip
|
309
|
+
mv custom_library $(bundle exec arduino_library_location.rb)
|
310
|
+
|
311
|
+
# manually install a custom library from a git repository
|
312
|
+
git clone https://repository.com/custom_library_repo.git
|
313
|
+
mv custom_library_repo $(bundle exec arduino_library_location.rb)
|
314
|
+
|
315
|
+
# now run CI
|
316
|
+
bundle exec arduino_ci.rb
|
317
|
+
```
|
318
|
+
|
319
|
+
Note the use of subshell to execute `bundle exec arduino_library_location.rb`. This command simply returns the directory in which Arduino Libraries are (or should be) installed.
|
320
|
+
|
321
|
+
|
322
|
+
# Mocks of Arduino Hardware Functions
|
323
|
+
|
324
|
+
Unless your library peforms something general (e.g. a mathematical or string function, a data structure like Queue, etc), you may need to ensure that your code interacts properly with the Arduino hardware. There are a series of mocks to assist in this.
|
325
|
+
|
326
|
+
## Using `GODMODE`
|
327
|
+
|
328
|
+
Complete control of the Arduino environment is available in your unit tests through a construct called `GODMODE()`.
|
329
|
+
|
330
|
+
```C++
|
331
|
+
unittest(example_godmode_stuff)
|
332
|
+
{
|
333
|
+
GodmodeState* state = GODMODE(); // get access to the state
|
334
|
+
state->reset(); // does a full reset of the state.
|
335
|
+
state->resetClock(); // - you can reset just the clock (to zero)
|
336
|
+
state->resetPins(); // - or just the pins
|
337
|
+
state->micros = 1; // manually set the clock such that micros() returns 1
|
338
|
+
state->digitalPin[4]; // tells you the commanded state of digital pin 4
|
339
|
+
state->digitalPin[4] = HIGH; // digitalRead(4) will now return HIGH
|
340
|
+
state->analogPin[3]; // tells you the commanded state of analog pin 3
|
341
|
+
state->analogPin[3] = 99; // analogRead(3) will now return 99
|
342
|
+
}
|
343
|
+
```
|
344
|
+
|
345
|
+
### Pin Histories
|
346
|
+
|
347
|
+
Of course, it's possible that your code might flip the bit more than once in a function. For that scenario, you may want to examine the history of a pin's commanded outputs:
|
348
|
+
|
349
|
+
```C++
|
350
|
+
unittest(pin_history)
|
351
|
+
{
|
352
|
+
GodmodeState* state = GODMODE();
|
353
|
+
int myPin = 3;
|
354
|
+
state->reset(); // pin will start LOW
|
355
|
+
digitalWrite(myPin, HIGH);
|
356
|
+
digitalWrite(myPin, LOW);
|
357
|
+
digitalWrite(myPin, LOW);
|
358
|
+
digitalWrite(myPin, HIGH);
|
359
|
+
digitalWrite(myPin, HIGH);
|
360
|
+
|
361
|
+
// pin history is queued in case we want to analyze it later.
|
362
|
+
// we expect 6 values in that queue (5 that we set plus one
|
363
|
+
// initial value), which we'll hard-code here for convenience.
|
364
|
+
// (we'll actually assert those 6 values in the next block)
|
365
|
+
assertEqual(6, state->digitalPin[1].queueSize());
|
366
|
+
bool expected[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH};
|
367
|
+
bool actual[6];
|
368
|
+
|
369
|
+
// convert history queue into an array so we can verify it.
|
370
|
+
// while we're at it, check that we received the amount of
|
371
|
+
// elements that we expected.
|
372
|
+
int numMoved = state->digitalPin[myPin].toArray(actual, 6);
|
373
|
+
assertEqual(6, numMoved);
|
374
|
+
|
375
|
+
// verify each element
|
376
|
+
for (int i = 0; i < 6; ++i) {
|
377
|
+
assertEqual(expected[i], actual[i]);
|
378
|
+
}
|
379
|
+
}
|
380
|
+
```
|
381
|
+
|
382
|
+
|
383
|
+
### Pin Futures
|
384
|
+
|
385
|
+
Reading the pin more than once per function is also a possibility. In that case, we want to queue up a few values for the `digitalRead` or `analogRead` to find.
|
386
|
+
|
387
|
+
```C++
|
388
|
+
unittest(pin_read_history)
|
389
|
+
{
|
390
|
+
GodmodeState* state = GODMODE();
|
391
|
+
state->reset();
|
392
|
+
|
393
|
+
int future[6] = {33, 22, 55, 11, 44, 66};
|
394
|
+
state->analogPin[1].fromArray(future, 6);
|
395
|
+
for (int i = 0; i < 6; ++i)
|
396
|
+
{
|
397
|
+
assertEqual(future[i], analogRead(1));
|
398
|
+
}
|
399
|
+
|
400
|
+
// for digital pins, we have the added possibility of specifying
|
401
|
+
// a stream of input bytes encoded as ASCII
|
402
|
+
bool bigEndian = true;
|
403
|
+
state->digitalPin[1].fromAscii("Yo", bigEndian);
|
404
|
+
|
405
|
+
// digital history as serial data, big-endian
|
406
|
+
bool expectedBits[16] = {
|
407
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
408
|
+
0, 1, 1, 0, 1, 1, 1, 1 // o
|
409
|
+
};
|
410
|
+
|
411
|
+
for (int i = 0; i < 16; ++i) {
|
412
|
+
assertEqual(expectedBits[i], digitalRead(1));
|
413
|
+
}
|
414
|
+
}
|
415
|
+
```
|
416
|
+
|
417
|
+
### Serial Data
|
418
|
+
|
419
|
+
Basic input and output verification of serial port data can be done as follows:
|
420
|
+
|
421
|
+
```c++
|
422
|
+
unittest(reading_writing_serial)
|
423
|
+
{
|
424
|
+
GodmodeState* state = GODMODE();
|
425
|
+
state->serialPort[0].dataIn = ""; // the queue of data waiting to be read
|
426
|
+
state->serialPort[0].dataOut = ""; // the history of data written
|
427
|
+
|
428
|
+
// When there is no data, nothing happens
|
429
|
+
assertEqual(-1, Serial.peek());
|
430
|
+
assertEqual("", state->serialPort[0].dataIn);
|
431
|
+
assertEqual("", state->serialPort[0].dataOut);
|
432
|
+
|
433
|
+
// if we put data on the input and peek at it, we see the value and it's not consumed
|
434
|
+
state->serialPort[0].dataIn = "a";
|
435
|
+
assertEqual('a', Serial.peek());
|
436
|
+
assertEqual("a", state->serialPort[0].dataIn);
|
437
|
+
assertEqual("", state->serialPort[0].dataOut);
|
438
|
+
|
439
|
+
// if we read the input, we see the value and it's consumed
|
440
|
+
assertEqual('a', Serial.read());
|
441
|
+
assertEqual("", state->serialPort[0].dataIn);
|
442
|
+
assertEqual("", state->serialPort[0].dataOut);
|
443
|
+
|
444
|
+
// when we write data, it shows up in the history -- the output buffer
|
445
|
+
Serial.write('b');
|
446
|
+
assertEqual("", state->serialPort[0].dataIn);
|
447
|
+
assertEqual("b", state->serialPort[0].dataOut);
|
448
|
+
|
449
|
+
// when we print more data, note that the history
|
450
|
+
// still contains the first thing we wrote
|
451
|
+
Serial.print("cdefg");
|
452
|
+
assertEqual("", state->serialPort[0].dataIn);
|
453
|
+
assertEqual("bcdefg", state->serialPort[0].dataOut);
|
454
|
+
}
|
455
|
+
```
|
456
|
+
|
457
|
+
A more complicated example: working with serial port IO. Let's say I have the following function:
|
458
|
+
|
459
|
+
```C++
|
460
|
+
void smartLightswitchSerialHandler(int pin) {
|
461
|
+
if (Serial.available() > 0) {
|
462
|
+
int incomingByte = Serial.read();
|
463
|
+
int val = incomingByte == '0' ? LOW : HIGH;
|
464
|
+
Serial.print("Ack ");
|
465
|
+
digitalWrite(pin, val);
|
466
|
+
Serial.print(String(pin));
|
467
|
+
Serial.print(" ");
|
468
|
+
Serial.print((char)incomingByte);
|
469
|
+
}
|
470
|
+
}
|
471
|
+
```
|
472
|
+
|
473
|
+
This function has 3 side effects: it drains the serial port's receive buffer, affects a pin, and puts data in the serial port's send buffer. Or, if the receive buffer is empty, it does nothing at all.
|
474
|
+
|
475
|
+
```C++
|
476
|
+
unittest(does_nothing_if_no_data)
|
477
|
+
{
|
478
|
+
// configure initial state
|
479
|
+
GodmodeState* state = GODMODE();
|
480
|
+
int myPin = 3;
|
481
|
+
state->serialPort[0].dataIn = "";
|
482
|
+
state->serialPort[0].dataOut = "";
|
483
|
+
state->digitalPin[myPin] = LOW;
|
484
|
+
|
485
|
+
// execute action
|
486
|
+
smartLightswitchSerialHandler(myPin);
|
487
|
+
|
488
|
+
// assess final state
|
489
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
490
|
+
assertEqual("", state->serialPort[0].dataIn);
|
491
|
+
assertEqual("", state->serialPort[0].dataOut);
|
492
|
+
}
|
493
|
+
|
494
|
+
unittest(two_flips)
|
495
|
+
{
|
496
|
+
GodmodeState* state = GODMODE();
|
497
|
+
int myPin = 3;
|
498
|
+
state->serialPort[0].dataIn = "10junk";
|
499
|
+
state->serialPort[0].dataOut = "";
|
500
|
+
state->digitalPin[myPin] = LOW;
|
501
|
+
smartLightswitchSerialHandler(myPin);
|
502
|
+
assertEqual(HIGH, state->digitalPin[myPin]);
|
503
|
+
assertEqual("0junk", state->serialPort[0].dataIn);
|
504
|
+
assertEqual("Ack 3 1", state->serialPort[0].dataOut);
|
505
|
+
|
506
|
+
state->serialPort[0].dataOut = "";
|
507
|
+
smartLightswitchSerialHandler(myPin);
|
508
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
509
|
+
assertEqual("junk", state->serialPort[0].dataIn);
|
510
|
+
assertEqual("Ack 3 0", state->serialPort[0].dataOut);
|
511
|
+
}
|
512
|
+
```
|
513
|
+
|
514
|
+
### Pin History as ASCII
|
515
|
+
|
516
|
+
|
517
|
+
For additional complexity, there are some cases where you want to use a pin as a serial port. There are history functions for that too.
|
518
|
+
|
519
|
+
```C++
|
520
|
+
int myPin = 3;
|
521
|
+
|
522
|
+
// digital history as serial data, big-endian
|
523
|
+
bool bigEndian = true;
|
524
|
+
bool binaryAscii[24] = {
|
525
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
526
|
+
0, 1, 1, 0, 0, 1, 0, 1, // e
|
527
|
+
0, 1, 1, 1, 0, 0, 1, 1 // s
|
528
|
+
};
|
529
|
+
|
530
|
+
// "send" these bits
|
531
|
+
for (int i = 0; i < 24; digitalWrite(myPin, binaryAscii[i++]));
|
532
|
+
|
533
|
+
// The first bit in the history is the initial value, which we will ignore
|
534
|
+
int offset = 1;
|
535
|
+
|
536
|
+
// We should be able to parse the bits as ascii
|
537
|
+
assertEqual("Yes", state->digitalPin[myPin].toAscii(offset, bigEndian));
|
538
|
+
```
|
539
|
+
|
540
|
+
Instead of queueing bits as ASCII for future use with `toAscii`, you can send those bits directly (and immediately) to the output using `outgoingFromAscii`. Likewise, you can reinterpret/examine (as ASCII) the bits you have previously queued up by calling `incomingToAscii` on the PinHistory object.
|
541
|
+
|
542
|
+
|
543
|
+
### Interactivity of "Devices" with Observers
|
544
|
+
|
545
|
+
Even pin history and input/output buffers aren't capable of testing interactive code. For example, queueing the canned responses from a serial device before the requests are even sent to it is not a sane test environment; the library under test will see the entire future waiting for it on the input pin instead of a buffer that fills and empties over time. This calls for something more complicated.
|
546
|
+
|
547
|
+
In this example, we create a simple class to emulate a Hayes modem. (For more information, dig into the `DataStreamObserver` code on which `DeviceUsingBytes` is based.
|
548
|
+
|
549
|
+
```c++
|
550
|
+
class FakeHayesModem : public DeviceUsingBytes {
|
551
|
+
public:
|
552
|
+
String mLast;
|
553
|
+
|
554
|
+
FakeHayesModem() : DeviceUsingBytes() {
|
555
|
+
mLast = "";
|
556
|
+
addResponseLine("AT", "OK");
|
557
|
+
addResponseLine("ATV1", "NO CARRIER");
|
558
|
+
}
|
559
|
+
virtual ~FakeHayesModem() {}
|
560
|
+
virtual void onMatchInput(String output) { mLast = output; }
|
561
|
+
};
|
562
|
+
|
563
|
+
unittest(modem_hardware)
|
564
|
+
{
|
565
|
+
GodmodeState* state = GODMODE();
|
566
|
+
state->reset();
|
567
|
+
FakeHayesModem m;
|
568
|
+
m.attach(&Serial);
|
569
|
+
|
570
|
+
Serial.write("AT\n");
|
571
|
+
assertEqual("AT\n", state->serialPort[0].dataOut);
|
572
|
+
assertEqual("OK\n", m.mLast);
|
573
|
+
}
|
574
|
+
```
|
575
|
+
|
576
|
+
Note that instead of setting `mLast = output` in the `onMatchInput()` function for test purposes, we could just as easily queue some bytes to state->serialPort[0].dataIn for the library under test to find on its next `peek()` or `read()`. Or we could execute some action on a digital or analog input pin; the possibilities are fairly endless in this regard, although you will have to define them yourself -- from scratch -- extending the `DataStreamObserver` class to emulate your physical device.
|
577
|
+
|
578
|
+
|
579
|
+
### Interrupts
|
580
|
+
|
581
|
+
Although ISRs should be tested directly (as their asynchronous nature is not mocked), the act of attaching or detaching an interrupt can be measured.
|
582
|
+
|
583
|
+
```C++
|
584
|
+
unittest(interrupt_attachment) {
|
585
|
+
GodmodeState *state = GODMODE();
|
586
|
+
state->reset();
|
587
|
+
assertFalse(state->interrupt[7].attached);
|
588
|
+
attachInterrupt(7, (void (*)(void))0, 3);
|
589
|
+
assertTrue(state->interrupt[7].attached);
|
590
|
+
assertEqual(state->interrupt[7].mode, 3);
|
591
|
+
detachInterrupt(7);
|
592
|
+
assertFalse(state->interrupt[7].attached);
|
593
|
+
}
|
594
|
+
```
|
595
|
+
|
596
|
+
|
597
|
+
### SPI
|
598
|
+
|
599
|
+
These basic mocks of SPI store the values in Strings.
|
600
|
+
|
601
|
+
```C++
|
602
|
+
unittest(spi) {
|
603
|
+
GodmodeState *state = GODMODE();
|
604
|
+
|
605
|
+
// 8-bit
|
606
|
+
state->reset();
|
607
|
+
state->spi.dataIn = "LMNO";
|
608
|
+
uint8_t out8 = SPI.transfer('a');
|
609
|
+
assertEqual("a", state->spi.dataOut);
|
610
|
+
assertEqual('L', out8);
|
611
|
+
assertEqual("MNO", state->spi.dataIn);
|
612
|
+
|
613
|
+
// 16-bit
|
614
|
+
union { uint16_t val; struct { char lsb; char msb; }; } in16, out16;
|
615
|
+
state->reset();
|
616
|
+
state->spi.dataIn = "LMNO";
|
617
|
+
in16.lsb = 'a';
|
618
|
+
in16.msb = 'b';
|
619
|
+
out16.val = SPI.transfer16(in16.val);
|
620
|
+
assertEqual("NO", state->spi.dataIn);
|
621
|
+
assertEqual('L', out16.lsb);
|
622
|
+
assertEqual('M', out16.msb);
|
623
|
+
assertEqual("ab", state->spi.dataOut);
|
624
|
+
|
625
|
+
// buffer
|
626
|
+
state->reset();
|
627
|
+
state->spi.dataIn = "LMNOP";
|
628
|
+
char inBuf[6] = "abcde";
|
629
|
+
SPI.transfer(inBuf, 4);
|
630
|
+
|
631
|
+
assertEqual("abcd", state->spi.dataOut);
|
632
|
+
assertEqual("LMNOe", String(inBuf));
|
633
|
+
}
|
634
|
+
```
|
635
|
+
|
636
|
+
### EEPROM
|
637
|
+
|
638
|
+
`EEPROM` is a global with a simple API to read and write bytes to persistent memory (like a tiny hard disk) given an `int` location. Since the Arduino core already provides this as a global, and the core API is sufficient for basic testing (read/write), there is no direct tie to the `GODMODE` API. (If you need more, such as a log of intermediate values, enter a feature request.)
|
639
|
+
|
640
|
+
```C++
|
641
|
+
unittest(eeprom)
|
642
|
+
{
|
643
|
+
uint8_t a;
|
644
|
+
// size
|
645
|
+
assertEqual(EEPROM_SIZE, EEPROM.length());
|
646
|
+
// initial values
|
647
|
+
a = EEPROM.read(0);
|
648
|
+
assertEqual(255, a);
|
649
|
+
// write and read
|
650
|
+
EEPROM.write(0, 24);
|
651
|
+
a = EEPROM.read(0);
|
652
|
+
assertEqual(24, a);
|
653
|
+
// update
|
654
|
+
EEPROM.write(1, 14);
|
655
|
+
EEPROM.update(1, 22);
|
656
|
+
a = EEPROM.read(1);
|
657
|
+
assertEqual(22, a);
|
658
|
+
// put and get
|
659
|
+
const float f1 = 0.025f;
|
660
|
+
float f2 = 0.0f;
|
661
|
+
EEPROM.put(5, f1);
|
662
|
+
assertEqual(0.0f, f2);
|
663
|
+
EEPROM.get(5, f2);
|
664
|
+
assertEqual(0.025f, f2);
|
665
|
+
// array access
|
666
|
+
int val = 10;
|
667
|
+
EEPROM[2] = val;
|
668
|
+
a = EEPROM[2];
|
669
|
+
assertEqual(10, a);
|
670
|
+
}
|
671
|
+
```
|
672
|
+
|
673
|
+
|
674
|
+
### Wire
|
675
|
+
|
676
|
+
This library allows communication with I2C / TWI devices.
|
677
|
+
|
678
|
+
The interface the library has been fully mocked, with the addition of several functions for debugging
|
679
|
+
|
680
|
+
* `Wire.resetMocks()`: Initializes all mocks, and for test repeatability should be called at the top of any unit tests that use Wire.
|
681
|
+
* `Wire.didBegin()`: returns whether `Wire.begin()` was called at any point
|
682
|
+
* `Wire.getMosi(address)`: returns a pointer to a `deque` that represents the history of data sent to `address`
|
683
|
+
* `Wire.getMiso(address)`: returns a pointer to a `deque` that defines what the master will read from `address` (i.e. for you to supply)
|
684
|
+
|
685
|
+
```c++
|
686
|
+
unittest(wire_basics) {
|
687
|
+
// ensure known starting state
|
688
|
+
Wire.resetMocks();
|
689
|
+
|
690
|
+
// in case you need to check that your library is properly calling .begin()
|
691
|
+
assertFalse(Wire.didBegin());
|
692
|
+
Wire.begin();
|
693
|
+
assertTrue(Wire.didBegin());
|
694
|
+
|
695
|
+
// pick a random device. master write buffer should be empty
|
696
|
+
const uint8_t randomSlaveAddr = 14;
|
697
|
+
deque<uint8_t>* mosi = Wire.getMosi(randomSlaveAddr);
|
698
|
+
assertEqual(0, mosi->size());
|
699
|
+
|
700
|
+
// write some random data to random device
|
701
|
+
const uint8_t randomData[] = { 0x07, 0x0E };
|
702
|
+
Wire.beginTransmission(randomSlaveAddr);
|
703
|
+
Wire.write(randomData[0]);
|
704
|
+
Wire.write(randomData[1]);
|
705
|
+
Wire.endTransmission();
|
706
|
+
|
707
|
+
// check master write buffer values
|
708
|
+
assertEqual(2, mosi->size());
|
709
|
+
assertEqual(randomData[0], mosi->front());
|
710
|
+
mosi->pop_front();
|
711
|
+
assertEqual(randomData[1], mosi->front());
|
712
|
+
mosi->pop_front();
|
713
|
+
assertEqual(0, mosi->size());
|
714
|
+
}
|
715
|
+
```
|
data/lib/tap_parser.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require "tap_parser/version"
|
3
|
+
|
4
|
+
# TapParser contains a module for parsing and serializing text that follows the Test Anything Protocol
|
5
|
+
#
|
6
|
+
# @author Ian Katz <ianfixes@gmail.com>
|
7
|
+
#
|
8
|
+
# @example Usage example
|
9
|
+
# require "TAPParser"
|
10
|
+
# require "pathname"
|
11
|
+
# require "json"
|
12
|
+
#
|
13
|
+
# result_file = Pathname.new("/path/to/myfile.tap")
|
14
|
+
# result = TAPParser.parse(result_file.basename, File.read(result_file).each_line)
|
15
|
+
# puts JSON.pretty_generate(result, indent: " ")
|
16
|
+
module TAPParser
|
17
|
+
TAP_TAB_WIDTH = 4
|
18
|
+
TAP_YAML_INDENT_WIDTH = 2
|
19
|
+
|
20
|
+
# regexes we will use, with capture groups we will also use
|
21
|
+
IS_TAP_VERSION = /^TAP version (\d+)/.freeze
|
22
|
+
IS_COMMENT = /^\s*#/.freeze
|
23
|
+
IS_BLANK = /^\s*$/.freeze
|
24
|
+
IS_CONTENT = /^(\s*)\S/.freeze
|
25
|
+
IS_TAP_PLAN = /^\s*1\.\.(\d+)/.freeze
|
26
|
+
IS_TEST = /
|
27
|
+
^ # start of line
|
28
|
+
(\s*) # any indentation
|
29
|
+
((not\ )?ok) # result. note the escaping of the space!
|
30
|
+
(\s+(\d+))? # optional test number
|
31
|
+
(\s+-(.*?))? # optional test description
|
32
|
+
\s*? # optional spacing
|
33
|
+
([^\\]\#.*?)? # optional directive, must start with space and hash
|
34
|
+
\s*$ # optional trailing whitespace
|
35
|
+
/x.freeze
|
36
|
+
IS_BAIL_OUT = /^Bail out!(\s*)(.+?)?\s*$/.freeze
|
37
|
+
IS_DIRECTIVE = /^[^\\]#\s+(SKIP|TODO)\s*(\S.*?)?\s*$/.freeze
|
38
|
+
IS_PRAGMA = /^\s*pragma ([+-])([a-zA-Z0-9_-]+)\s*$/.freeze
|
39
|
+
|
40
|
+
# detect whether this is the start of a YAML block at the beginning of YAML indentation
|
41
|
+
#
|
42
|
+
# @param expected_indentation [String] the expected indentation in spaces
|
43
|
+
# @return [bool]
|
44
|
+
def self.yaml_begin(expected_indentation)
|
45
|
+
yaml_indent = expected_indentation + (" " * TAP_YAML_INDENT_WIDTH)
|
46
|
+
/^#{yaml_indent}---(\s*)$/
|
47
|
+
end
|
48
|
+
|
49
|
+
# detect whether this is the start of a YAML block at the end of YAML indentation
|
50
|
+
#
|
51
|
+
# @param expected_indentation [String] the expected indentation in spaces
|
52
|
+
# @return [bool]
|
53
|
+
def self.yaml_end(expected_indentation)
|
54
|
+
yaml_indent = expected_indentation + (" " * TAP_YAML_INDENT_WIDTH)
|
55
|
+
/^#{yaml_indent}\.\.\.\s*$/
|
56
|
+
end
|
57
|
+
|
58
|
+
# Parse a TAP file into a hash of its contents
|
59
|
+
#
|
60
|
+
# @param source_description [String] the name of the input
|
61
|
+
# @param lines [Enumerator] A source of lines as strings
|
62
|
+
# @return [Hash] the structured content representing the input
|
63
|
+
def self.parse(source_description, lines)
|
64
|
+
@pragmas = nil
|
65
|
+
|
66
|
+
# Workaround: the internal pointer of my enumerator keeps resetting to zero!
|
67
|
+
# If you want something done right, do it yourself I guess :(
|
68
|
+
@line_enumerator = lines.each
|
69
|
+
|
70
|
+
# Take the next line of the input, and unescape slashes
|
71
|
+
#
|
72
|
+
# @return [String] The next line of input, advancing the internal enumerator
|
73
|
+
def self.enum_next
|
74
|
+
@line_enumerator.next.gsub("\\\\", "\\") # unescape as we go
|
75
|
+
rescue StopIteration
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# peek at the next line of input
|
80
|
+
#
|
81
|
+
# @return [String] The next line of input, without advancing the internal enumerator
|
82
|
+
def self.enum_peek
|
83
|
+
@line_enumerator.peek
|
84
|
+
rescue StopIteration
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# take all matching contiguous lines of input
|
89
|
+
#
|
90
|
+
# @yield [str] This block is invoked with a string argument
|
91
|
+
# @yieldparam str [String] a string of input
|
92
|
+
# @yieldreturn [bool] Whether the input should be sent to the consumer
|
93
|
+
# @return [Enumerator] An enumerator that wraps the input
|
94
|
+
def self.enum_take_while(&block)
|
95
|
+
Enumerator.new do |yielder|
|
96
|
+
loop do
|
97
|
+
x = enum_peek
|
98
|
+
break if x.nil?
|
99
|
+
break unless block.call(x)
|
100
|
+
|
101
|
+
enum_next
|
102
|
+
yielder << x
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# figure out the indentation level of lines in terms of number of tabs
|
108
|
+
#
|
109
|
+
# @param line [String] the input line
|
110
|
+
# @return [int] the number of conceptual "tabs" (via multiple spaces) at the begnning of the line
|
111
|
+
def self.indentation_of(line)
|
112
|
+
return nil if line.nil?
|
113
|
+
|
114
|
+
match = IS_CONTENT.match(line)
|
115
|
+
match.nil? ? 0 : ($1.length / TAP_TAB_WIDTH).to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.empty_as_nil(str)
|
119
|
+
str.nil? || str.empty? ? nil : str.strip
|
120
|
+
end
|
121
|
+
|
122
|
+
# recursive entry point: parse a TAP file into a hash of its contents
|
123
|
+
#
|
124
|
+
# @param parent_description [String] the name of the input
|
125
|
+
# @param current_indentation [Int] the number of tabs the input is expected to have
|
126
|
+
# @return [Hash] the structured content of the file
|
127
|
+
def self.parse_tests(parent_description, current_indentation)
|
128
|
+
expected_indentation = " " * current_indentation * TAP_TAB_WIDTH
|
129
|
+
protocol_version = nil
|
130
|
+
directives = nil
|
131
|
+
expected_tests = nil
|
132
|
+
encountered_tests = 0
|
133
|
+
tests = []
|
134
|
+
children = nil
|
135
|
+
|
136
|
+
loop do
|
137
|
+
# decide whether to recurse
|
138
|
+
this_line = enum_peek
|
139
|
+
children = parse_tests(nil, current_indentation + 1) if !this_line.nil? && indentation_of(this_line) > current_indentation
|
140
|
+
|
141
|
+
# consume input from wherever we left off
|
142
|
+
line = enum_next
|
143
|
+
next_line = enum_peek
|
144
|
+
|
145
|
+
# special case exit when subtests aren't "owned"
|
146
|
+
if line.nil?
|
147
|
+
tests << { children: children } unless children.nil?
|
148
|
+
break
|
149
|
+
end
|
150
|
+
|
151
|
+
# process the input
|
152
|
+
case line
|
153
|
+
when IS_TAP_VERSION
|
154
|
+
protocol_version = $1.to_i
|
155
|
+
when IS_COMMENT
|
156
|
+
# comment, ignore
|
157
|
+
when IS_TAP_PLAN
|
158
|
+
expected_tests = $1.to_i
|
159
|
+
when IS_BAIL_OUT
|
160
|
+
reason = empty_as_nil($2)
|
161
|
+
directives ||= {}
|
162
|
+
directives["bail out"] = reason
|
163
|
+
break
|
164
|
+
when IS_PRAGMA
|
165
|
+
action = $1 == '+' ? 'enable' : 'disable'
|
166
|
+
pragma_key = $2
|
167
|
+
@pragmas ||= {}
|
168
|
+
@pragmas[pragma_key] = action
|
169
|
+
when IS_TEST
|
170
|
+
encountered_tests += 1
|
171
|
+
result_str = $2
|
172
|
+
test_number = $4.nil? ? encountered_tests : $4.to_i
|
173
|
+
description = empty_as_nil($7)
|
174
|
+
directive_str = $8
|
175
|
+
|
176
|
+
# avoid doing gsub above because it screws up the $ variables
|
177
|
+
description = description.gsub("\\#", "#") unless description.nil?
|
178
|
+
|
179
|
+
test = {
|
180
|
+
number: test_number,
|
181
|
+
description: description,
|
182
|
+
ok: (result_str == 'ok'),
|
183
|
+
}
|
184
|
+
|
185
|
+
# add children if parsed, and reset
|
186
|
+
test[:children] = children unless children.nil?
|
187
|
+
children = nil
|
188
|
+
|
189
|
+
# find directives and add
|
190
|
+
unless directive_str.nil?
|
191
|
+
match = IS_DIRECTIVE.match(directive_str)
|
192
|
+
k = $1 # could be nil if not SKIP or TODO, so don't .to_sym here
|
193
|
+
v = empty_as_nil($2)&.gsub("\\#", "#")
|
194
|
+
test[:directives] = { k.to_sym => v } if match && !k.nil?
|
195
|
+
end
|
196
|
+
|
197
|
+
# find diagnostics and add
|
198
|
+
yaml_indent = expected_indentation + (" " * 2)
|
199
|
+
if yaml_begin(expected_indentation).match(next_line)
|
200
|
+
raw_yaml = enum_take_while { |l| !yaml_end(expected_indentation).match(l) }
|
201
|
+
yaml = raw_yaml.map { |l| l[yaml_indent.length..] }.to_a.join
|
202
|
+
test[:diagnostics] = YAML.safe_load(yaml)
|
203
|
+
enum_next # strip off "..."
|
204
|
+
end
|
205
|
+
|
206
|
+
# record the test
|
207
|
+
tests << test
|
208
|
+
end
|
209
|
+
|
210
|
+
# handle the end of recursion if the next line is de-indented.
|
211
|
+
# according to the spec, we can only go up one level at a time
|
212
|
+
next_line_indentation = indentation_of(next_line)
|
213
|
+
break if next_line.nil? || next_line_indentation < current_indentation
|
214
|
+
end
|
215
|
+
|
216
|
+
# summarize the entire operation
|
217
|
+
ret = {
|
218
|
+
tests: tests,
|
219
|
+
expected_tests: expected_tests
|
220
|
+
}
|
221
|
+
ret[:description] = parent_description unless parent_description.nil?
|
222
|
+
ret[:tap_version] = protocol_version if current_indentation.zero?
|
223
|
+
ret[:directives] = directives unless directives.nil?
|
224
|
+
ret[:pragmas] = @pragmas if current_indentation.zero? && !@pragmas.nil?
|
225
|
+
ret
|
226
|
+
end
|
227
|
+
|
228
|
+
parse_tests(source_description, 0)
|
229
|
+
end
|
230
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tap_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ian Katz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: ''
|
14
|
+
email:
|
15
|
+
- ianfixes@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".yardopts"
|
21
|
+
- README.md
|
22
|
+
- REFERENCE.md
|
23
|
+
- lib/tap_parser.rb
|
24
|
+
- lib/tap_parser/version.rb
|
25
|
+
homepage: http://github.com/ianfixes/tap_parser
|
26
|
+
licenses:
|
27
|
+
- Apache-2.0
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.0.3.1
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Parser for the Test Anything Protocol (TAP)
|
48
|
+
test_files: []
|