tap_parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ ```
@@ -0,0 +1,3 @@
1
+ module TAPParser
2
+ VERSION = "0.1.0".freeze
3
+ end
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: []