arduino_ci 0.2.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +49 -20
- data/REFERENCE.md +636 -0
- data/cpp/arduino/Arduino.h +1 -1
- data/cpp/arduino/AvrMath.h +117 -17
- data/cpp/arduino/Client.h +27 -0
- data/cpp/arduino/EEPROM.h +64 -0
- data/cpp/arduino/Godmode.cpp +11 -0
- data/cpp/arduino/Godmode.h +58 -9
- data/cpp/arduino/HardwareSerial.h +9 -28
- data/cpp/arduino/IPAddress.h +59 -0
- data/cpp/arduino/Print.h +9 -12
- data/cpp/arduino/Printable.h +8 -0
- data/cpp/arduino/SPI.h +11 -3
- data/cpp/arduino/Server.h +5 -0
- data/cpp/arduino/Udp.h +27 -0
- data/cpp/arduino/Wire.h +234 -0
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/arduino/ci/StreamTape.h +36 -0
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +427 -0
- data/exe/arduino_ci_remote.rb +2 -385
- data/exe/arduino_library_location.rb +2 -2
- data/exe/ensure_arduino_installation.rb +7 -1
- data/lib/arduino_ci.rb +1 -0
- data/lib/arduino_ci/arduino_backend.rb +222 -0
- data/lib/arduino_ci/arduino_downloader.rb +43 -73
- data/lib/arduino_ci/arduino_downloader_linux.rb +17 -55
- data/lib/arduino_ci/arduino_downloader_osx.rb +21 -33
- data/lib/arduino_ci/arduino_downloader_windows.rb +11 -53
- data/lib/arduino_ci/arduino_installation.rb +18 -80
- data/lib/arduino_ci/ci_config.rb +15 -9
- data/lib/arduino_ci/cpp_library.rb +266 -48
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +96 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +55 -4
- metadata +21 -87
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
- data/lib/arduino_ci/arduino_cmd.rb +0 -328
- data/lib/arduino_ci/arduino_cmd_linux.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_linux_builder.rb +0 -19
- data/lib/arduino_ci/arduino_cmd_osx.rb +0 -17
- data/lib/arduino_ci/arduino_cmd_windows.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6dab88af65d6e71fd9cd3e2f389340b184fb1fbb063ccb7a6743f893c47f690d
|
4
|
+
data.tar.gz: 267b58186eea10a1f52ff4c16838270437a7d52d8fd0a3681f84e770845efa0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8765d7d3c01fb5a4f9737604ee9ee33a020629e3e5d404403069ac346caad36f69a94893ca2db49cdc14bb50a1d5557d29cccd9b58022fb3d1a00cd9de348323
|
7
|
+
data.tar.gz: e537d36bd505b31630aae9e7359a71f82dbb07977bac93dbc4194d6da4112ca005e22b2e9051970d0d67afd4e5dcdabcd9a6dddda1ee399c0a7cfc01aecb631c
|
data/README.md
CHANGED
@@ -1,28 +1,32 @@
|
|
1
1
|
|
2
|
-
# ArduinoCI Ruby gem (`arduino_ci`)
|
2
|
+
# ArduinoCI Ruby gem (`arduino_ci`)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci)
|
4
|
+
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/1.1.0)
|
5
|
+
[![Gitter](https://badges.gitter.im/Arduino-CI/arduino_ci.svg)](https://gitter.im/Arduino-CI/arduino_ci?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
3
6
|
|
4
7
|
You want to run tests on your Arduino library (bonus: without hardware present), but the IDE doesn't support that. Arduino CI provides that ability.
|
5
8
|
|
6
9
|
You want to precisely replicate certain software states in your library, but you don't have sub-millisecond reflexes for physically faking the inputs, outputs, and serial port. Arduino CI fakes 100% of the physical input and output of an Arduino board, including the clock.
|
7
10
|
|
8
|
-
You want your Arduino library to be automatically built and tested every time someone contributes code to your project on GitHub, but the Arduino IDE lacks the ability to run unit tests. [Arduino CI](https://github.com/
|
11
|
+
You want your Arduino library to be automatically built and tested every time someone contributes code to your project on GitHub, but the Arduino IDE lacks the ability to run unit tests. [Arduino CI](https://github.com/Arduino-CI/arduino_ci) provides that ability.
|
12
|
+
|
13
|
+
`arduino_ci` is a cross-platform build/test system, consisting of a Ruby gem and a series of C++ mocks. It enables tests to be run both locally and as part of a CI service like GitHub Actions, TravisCI, Appveyor, etc. Any OS that can run the Arduino IDE can run `arduino_ci`.
|
9
14
|
|
10
|
-
`arduino_ci` is a cross-platform build/test system, consisting of a Ruby gem and a series of C++ mocks. It enables tests to be run both locally and as part of a CI service like Travis or Appveyor. Any OS that can run the Arduino IDE can run `arduino_ci`.
|
11
15
|
|
12
16
|
Platform | CI Status
|
13
17
|
---------|:---------
|
14
|
-
OSX | [![OSX Build Status](
|
15
|
-
Linux | [![Linux Build Status](
|
16
|
-
Windows | [![Windows Build status](https://
|
18
|
+
OSX | [![OSX Build Status](https://github.com/Arduino-CI/arduino_ci/workflows/macos/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=macos)
|
19
|
+
Linux | [![Linux Build Status](https://github.com/Arduino-CI/arduino_ci/workflows/linux/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=linux)
|
20
|
+
Windows | [![Windows Build status](https://github.com/Arduino-CI/arduino_ci/workflows/windows/badge.svg)](https://github.com/Arduino-CI/arduino_ci/actions?workflow=windows)
|
17
21
|
|
18
22
|
|
19
23
|
## Comparison to Other Arduino Testing Tools
|
20
24
|
|
21
25
|
| Project | CI | Builds Examples | Unittest | Arduino Mocks | Windows | OSX | Linux | License |
|
22
26
|
|-----------------------------------------------------------------------------|:--:|:---------------:|:--------:|:-------------:|:-------:|:---:|:-----:|:--------|
|
23
|
-
|[ArduinoCI](https://github.com/
|
27
|
+
|[ArduinoCI](https://github.com/Arduino-CI/arduino_ci) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |Free (Apache-2.0)|
|
24
28
|
|[ArduinoUnit](https://github.com/mmurdoch/arduinounit) | ❌ | ❌ | ⚠️ Hardware-based|❌ | ✅ | ✅ | ✅ |Free (MIT)|
|
25
|
-
|[Adafruit `
|
29
|
+
|[Adafruit `ci-arduino`](https://github.com/adafruit/ci-arduino)| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |Free (MIT)|
|
26
30
|
|[PlatformIO](https://platformio.org) | ✅ | ✅ | ⚠️ Paid only | ❌ | ✅ | ✅ | ✅ |⚠️ EULA|
|
27
31
|
|Official [Arduino IDE](https://www.arduino.cc/en/main/software) | ❌ | ⚠️ Manually | ❌ |N/A 😉| ✅ | ✅ | ✅ |Free (GPLv2)|
|
28
32
|
|
@@ -33,6 +37,9 @@ For a bare-bones example that you can copy from, see [SampleProjects/DoSomething
|
|
33
37
|
|
34
38
|
The complete set of C++ unit tests for the `arduino_ci` library itself are in the [SampleProjects/TestSomething](SampleProjects/TestSomething) project. The [test files](SampleProjects/TestSomething/test/) are named after the type of feature being tested.
|
35
39
|
|
40
|
+
> Arduino expects all libraries to be in a specific `Arduino/libraries` directory on your system. If your library is elsewhere, `arduino_ci` will _automatically_ create a symbolic link in the `libraries` directory that points to the directory of the project being tested. This simplifieds working with project dependencies, but **it can have unintended consequences on Windows systems** because [in some cases deleting a folder that contains a symbolic link to another folder can cause the _entire linked folder_ to be removed instead of just the link itself](https://superuser.com/a/306618).
|
41
|
+
>
|
42
|
+
> If you use a Windows system **it is recommended that you only run `arduino_ci` from project directories that are already inside the `libraries` directory**
|
36
43
|
|
37
44
|
### You Need Ruby and Bundler
|
38
45
|
|
@@ -82,10 +89,10 @@ vendor
|
|
82
89
|
|
83
90
|
### Installing the Dependencies
|
84
91
|
|
85
|
-
Fulfilling the `arduino_ci` library dependency is as easy as running
|
86
|
-
|
92
|
+
Fulfilling the `arduino_ci` library dependency is as easy as running either of these two commands:
|
87
93
|
```
|
88
|
-
$ bundle install
|
94
|
+
$ bundle install # adds packages to global library (may require admin rights)
|
95
|
+
$ bundle install --path vendor/bundle # adds packages to local library
|
89
96
|
```
|
90
97
|
|
91
98
|
|
@@ -94,15 +101,15 @@ $ bundle install
|
|
94
101
|
With that installed, just the following shell command each time you want the tests to execute:
|
95
102
|
|
96
103
|
```
|
97
|
-
$ bundle exec
|
104
|
+
$ bundle exec arduino_ci.rb
|
98
105
|
```
|
99
106
|
|
100
|
-
`
|
107
|
+
`arduino_ci.rb` is the main entry point for this library. This command will iterate over all the library's `examples/` and attempt to compile them. If you set up unit tests, it will run those as well.
|
101
108
|
|
102
109
|
|
103
110
|
### Reference
|
104
111
|
|
105
|
-
For more information on the usage of `
|
112
|
+
For more information on the usage of `arduino_ci.rb`, see [REFERENCE.md](REFERENCE.md). It contains information such as:
|
106
113
|
|
107
114
|
* How to configure build options (platforms to test, Arduino library dependencies to install) with an `.arduino-ci.yml` file
|
108
115
|
* Where to put unit test files
|
@@ -121,7 +128,29 @@ The following prerequisites must be fulfilled:
|
|
121
128
|
|
122
129
|
### Testing with remote CI
|
123
130
|
|
124
|
-
> **Note:** `
|
131
|
+
> **Note:** `arduino_ci.rb` expects to be run from the root directory of your Arduino project library.
|
132
|
+
|
133
|
+
|
134
|
+
#### GitHub Actions
|
135
|
+
|
136
|
+
GitHub Actions allows you to automate your workflows directly in GitHub.
|
137
|
+
No additional steps are needed.
|
138
|
+
Just create a YAML file with the information below in your repo under the `.github/workflows/` directory.
|
139
|
+
|
140
|
+
```yaml
|
141
|
+
on: [push, pull_request]
|
142
|
+
jobs:
|
143
|
+
runTest:
|
144
|
+
runs-on: ubuntu-latest
|
145
|
+
steps:
|
146
|
+
- uses: actions/checkout@v2
|
147
|
+
- uses: ruby/setup-ruby@v1
|
148
|
+
with:
|
149
|
+
ruby-version: 2.6
|
150
|
+
- run: |
|
151
|
+
bundle install
|
152
|
+
bundle exec arduino_ci_remote.rb
|
153
|
+
```
|
125
154
|
|
126
155
|
|
127
156
|
#### Travis CI
|
@@ -135,7 +164,7 @@ sudo: false
|
|
135
164
|
language: ruby
|
136
165
|
script:
|
137
166
|
- bundle install
|
138
|
-
- bundle exec
|
167
|
+
- bundle exec arduino_ci.rb
|
139
168
|
```
|
140
169
|
|
141
170
|
|
@@ -149,14 +178,15 @@ Next, you'll need this in `appveyor.yml` in your repo.
|
|
149
178
|
build: off
|
150
179
|
test_script:
|
151
180
|
- bundle install
|
152
|
-
- bundle exec
|
181
|
+
- bundle exec arduino_ci.rb
|
153
182
|
```
|
154
183
|
|
184
|
+
|
155
185
|
## Known Problems
|
156
186
|
|
157
187
|
* The Arduino library is not fully mocked.
|
158
188
|
* I don't have preprocessor defines for all the Arduino board flavors
|
159
|
-
* https://github.com/
|
189
|
+
* https://github.com/Arduino-CI/arduino_ci/issues
|
160
190
|
|
161
191
|
|
162
192
|
## Author
|
@@ -167,6 +197,5 @@ This gem was written by Ian Katz (ianfixes@gmail.com) in 2018. It's released un
|
|
167
197
|
## See Also
|
168
198
|
|
169
199
|
* [Contributing](CONTRIBUTING.md)
|
170
|
-
* [Adafruit/
|
200
|
+
* [Adafruit/ci-arduino](https://github.com/adafruit/ci-arduino) which inspired this project
|
171
201
|
* [mmurdoch/arduinounit](https://github.com/mmurdoch/arduinounit) from which the unit test macros were adopted
|
172
|
-
|
data/REFERENCE.md
ADDED
@@ -0,0 +1,636 @@
|
|
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 runing 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
|
+
### `--testfile-select` option
|
33
|
+
|
34
|
+
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`.
|
35
|
+
|
36
|
+
|
37
|
+
### `--testfile-reject` option
|
38
|
+
|
39
|
+
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.
|
40
|
+
|
41
|
+
|
42
|
+
### `EXPECT_UNITTESTS` environment variable
|
43
|
+
|
44
|
+
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.
|
45
|
+
|
46
|
+
|
47
|
+
### `EXPECT_EXAMPLES` environment variable
|
48
|
+
|
49
|
+
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.
|
50
|
+
|
51
|
+
|
52
|
+
## Indirectly Overriding Build Behavior (medium term use), and Advanced Options
|
53
|
+
|
54
|
+
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:
|
55
|
+
|
56
|
+
1. the root of your library
|
57
|
+
2. the `test/` directory
|
58
|
+
3. a subdirectory of `examples/`
|
59
|
+
|
60
|
+
`.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.
|
61
|
+
|
62
|
+
|
63
|
+
### Defining New Arduino Platforms
|
64
|
+
|
65
|
+
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
|
66
|
+
|
67
|
+
Here is how you would declare a package that includes the `potato:salad` set of platforms (aka "board family") in your `.arduino-ci.yml`:
|
68
|
+
|
69
|
+
```yaml
|
70
|
+
packages:
|
71
|
+
potato:salad:
|
72
|
+
url: https://potato.github.io/arduino-board-index/package_salad_index.json
|
73
|
+
```
|
74
|
+
|
75
|
+
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.
|
76
|
+
|
77
|
+
```yaml
|
78
|
+
platforms:
|
79
|
+
# our custom definition of the "bogo" platform
|
80
|
+
bogo:
|
81
|
+
board: potato:salad:bogo
|
82
|
+
package: potato:salad
|
83
|
+
gcc:
|
84
|
+
features:
|
85
|
+
- omit-frame-pointer # becomes -fomit-frame-pointer flag
|
86
|
+
defines:
|
87
|
+
- HAVE_THING # becomes -DHAVE_THING flag
|
88
|
+
warnings:
|
89
|
+
- no-implicit # becomes -Wno-implicit flag
|
90
|
+
flags:
|
91
|
+
- -foobar # becomes -foobar flag
|
92
|
+
|
93
|
+
# overriding the `zero` platform, to remove it completely
|
94
|
+
zero: ~
|
95
|
+
|
96
|
+
# redefine the existing esp8266
|
97
|
+
esp8266:
|
98
|
+
board: esp8266:esp8266:booo
|
99
|
+
package: esp8266:esp8266
|
100
|
+
gcc:
|
101
|
+
features:
|
102
|
+
defines:
|
103
|
+
warnings:
|
104
|
+
flags:
|
105
|
+
```
|
106
|
+
|
107
|
+
### Control How Examples Are Compiled
|
108
|
+
|
109
|
+
Put a file `.arduino-ci.yml` in each example directory where you require a different configuration than default.
|
110
|
+
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.
|
111
|
+
|
112
|
+
```yaml
|
113
|
+
compile:
|
114
|
+
# Choosing to run compilation tests on 2 different Arduino platforms
|
115
|
+
platforms:
|
116
|
+
- esp8266
|
117
|
+
- bogo
|
118
|
+
|
119
|
+
# Declaring Dependent Arduino Libraries (to be installed via the Arduino Library Manager)
|
120
|
+
libraries:
|
121
|
+
- "Adafruit FONA Library"
|
122
|
+
```
|
123
|
+
|
124
|
+
|
125
|
+
### Control How Unit Tests Are Compiled and Run
|
126
|
+
|
127
|
+
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.
|
128
|
+
|
129
|
+
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.
|
130
|
+
|
131
|
+
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.
|
132
|
+
|
133
|
+
```yaml
|
134
|
+
unittest:
|
135
|
+
|
136
|
+
# Exclude these directories from compilation
|
137
|
+
exclude_dirs:
|
138
|
+
- someDirectory
|
139
|
+
- someOtherDirectory
|
140
|
+
|
141
|
+
# Perform unit tests with these compilers (these are the binaries that will be called via the shell)
|
142
|
+
compilers:
|
143
|
+
- g++ # default
|
144
|
+
- g++-4.9
|
145
|
+
- g++-7
|
146
|
+
|
147
|
+
# Filter the list of test files in some way
|
148
|
+
testfiles:
|
149
|
+
# files matching this glob (executed inside the `test/` directory) will be whitelisted for testing
|
150
|
+
select:
|
151
|
+
- "*-*.*"
|
152
|
+
|
153
|
+
# files matching this glob will be blacklisted from testing
|
154
|
+
reject:
|
155
|
+
- "sam-squamsh.*"
|
156
|
+
|
157
|
+
# These dependent libraries will be installed
|
158
|
+
libraries:
|
159
|
+
- "abc123"
|
160
|
+
- "def456"
|
161
|
+
|
162
|
+
# each of these platforms will be used when compiling the unit tests
|
163
|
+
platforms:
|
164
|
+
- bogo
|
165
|
+
```
|
166
|
+
|
167
|
+
The expected number of tests will be the product of:
|
168
|
+
|
169
|
+
* Number of compilers defined
|
170
|
+
* Number of platforms defined
|
171
|
+
* Number of matching test files
|
172
|
+
|
173
|
+
|
174
|
+
## Writing Unit tests in `test/`
|
175
|
+
|
176
|
+
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.
|
177
|
+
|
178
|
+
|
179
|
+
### Most Basic Unit Test
|
180
|
+
|
181
|
+
The most basic unit test file is as follows:
|
182
|
+
|
183
|
+
```C++
|
184
|
+
#include <ArduinoUnitTests.h>
|
185
|
+
#include "../do-something.h"
|
186
|
+
|
187
|
+
unittest(your_test_name)
|
188
|
+
{
|
189
|
+
assertEqual(4, doSomething());
|
190
|
+
}
|
191
|
+
|
192
|
+
unittest_main()
|
193
|
+
```
|
194
|
+
|
195
|
+
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.
|
196
|
+
|
197
|
+
### Assertions
|
198
|
+
|
199
|
+
The following assertion functions are available in unit tests.
|
200
|
+
|
201
|
+
* `assertEqual(expected, actual)`
|
202
|
+
* `assertNotEqual(expected, actual)`
|
203
|
+
* `assertLess(expected, actual)`
|
204
|
+
* `assertMore(expected, actual)`
|
205
|
+
* `assertLessOrEqual(expected, actual)`
|
206
|
+
* `assertMoreOrEqual(expected, actual)`
|
207
|
+
* `assertTrue(actual)`
|
208
|
+
* `assertFalse(actual)`
|
209
|
+
* `assertNull(actual)`
|
210
|
+
|
211
|
+
These functions will report the result of the test to the console, and the testing will continue if they fail.
|
212
|
+
|
213
|
+
**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.
|
214
|
+
|
215
|
+
|
216
|
+
### Test Setup and Teardown
|
217
|
+
|
218
|
+
For steps that are common to all tests, setup and teardown functions may optionally be supplied.
|
219
|
+
|
220
|
+
```C++
|
221
|
+
#include <ArduinoUnitTests.h>
|
222
|
+
|
223
|
+
int* myNumber;
|
224
|
+
|
225
|
+
unittest_setup()
|
226
|
+
{
|
227
|
+
myNumber = new int(4);
|
228
|
+
}
|
229
|
+
|
230
|
+
unittest_teardown()
|
231
|
+
{
|
232
|
+
delete myNumber;
|
233
|
+
myNumber = NULL;
|
234
|
+
}
|
235
|
+
|
236
|
+
unittest(your_test_name)
|
237
|
+
{
|
238
|
+
assertEqual(4, *myNumber);
|
239
|
+
}
|
240
|
+
|
241
|
+
unittest_main()
|
242
|
+
```
|
243
|
+
|
244
|
+
|
245
|
+
# Build Scripts
|
246
|
+
|
247
|
+
For most build environments, the only script that need be executed by the CI system is
|
248
|
+
|
249
|
+
```shell
|
250
|
+
# simplest build script
|
251
|
+
bundle install
|
252
|
+
bundle exec arduino_ci.rb
|
253
|
+
```
|
254
|
+
|
255
|
+
However, more flexible usage is available:
|
256
|
+
|
257
|
+
### Custom Versions of external Arduino Libraries
|
258
|
+
|
259
|
+
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.
|
260
|
+
|
261
|
+
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.
|
262
|
+
|
263
|
+
```shell
|
264
|
+
# Example build script
|
265
|
+
bundle install
|
266
|
+
|
267
|
+
# ensure the Arduino installation -- creates the Library directory
|
268
|
+
bundle exec ensure_arduino_installation.rb
|
269
|
+
|
270
|
+
# manually install a custom library from a zip file
|
271
|
+
wget https://hosting.com/custom_library.zip
|
272
|
+
unzip -o custom_library.zip
|
273
|
+
mv custom_library $(bundle exec arduino_library_location.rb)
|
274
|
+
|
275
|
+
# manually install a custom library from a git repository
|
276
|
+
git clone https://repository.com/custom_library_repo.git
|
277
|
+
mv custom_library_repo $(bundle exec arduino_library_location.rb)
|
278
|
+
|
279
|
+
# now run CI
|
280
|
+
bundle exec arduino_ci.rb
|
281
|
+
```
|
282
|
+
|
283
|
+
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.
|
284
|
+
|
285
|
+
|
286
|
+
|
287
|
+
# Mocks of Arduino Hardware Functions
|
288
|
+
|
289
|
+
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.
|
290
|
+
|
291
|
+
## Using `GODMODE`
|
292
|
+
|
293
|
+
Complete control of the Arduino environment is available in your unit tests through a construct called `GODMODE()`.
|
294
|
+
|
295
|
+
```C++
|
296
|
+
unittest(example_godmode_stuff)
|
297
|
+
{
|
298
|
+
GodmodeState* state = GODMODE(); // get access to the state
|
299
|
+
state->reset(); // does a full reset of the state.
|
300
|
+
state->resetClock(); // - you can reset just the clock (to zero)
|
301
|
+
state->resetPins(); // - or just the pins
|
302
|
+
state->micros = 1; // manually set the clock such that micros() returns 1
|
303
|
+
state->digitalPin[4]; // tells you the commanded state of digital pin 4
|
304
|
+
state->digitalPin[4] = HIGH; // digitalRead(4) will now return HIGH
|
305
|
+
state->analogPin[3]; // tells you the commanded state of analog pin 3
|
306
|
+
state->analogPin[3] = 99; // analogRead(3) will now return 99
|
307
|
+
}
|
308
|
+
```
|
309
|
+
|
310
|
+
### Pin Histories
|
311
|
+
|
312
|
+
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:
|
313
|
+
|
314
|
+
```C++
|
315
|
+
unittest(pin_history)
|
316
|
+
{
|
317
|
+
GodmodeState* state = GODMODE();
|
318
|
+
int myPin = 3;
|
319
|
+
state->reset(); // pin will start LOW
|
320
|
+
digitalWrite(myPin, HIGH);
|
321
|
+
digitalWrite(myPin, LOW);
|
322
|
+
digitalWrite(myPin, LOW);
|
323
|
+
digitalWrite(myPin, HIGH);
|
324
|
+
digitalWrite(myPin, HIGH);
|
325
|
+
|
326
|
+
// pin history is queued in case we want to analyze it later.
|
327
|
+
// we expect 6 values in that queue (5 that we set plus one
|
328
|
+
// initial value), which we'll hard-code here for convenience.
|
329
|
+
// (we'll actually assert those 6 values in the next block)
|
330
|
+
assertEqual(6, state->digitalPin[1].queueSize));
|
331
|
+
bool expected[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH};
|
332
|
+
bool actual[6];
|
333
|
+
|
334
|
+
// convert history queue into an array so we can verify it.
|
335
|
+
// while we're at it, check that we received the amount of
|
336
|
+
// elements that we expected.
|
337
|
+
int numMoved = state->digitalPin[myPin].toArray(actual, 6);
|
338
|
+
assertEqual(6, numMoved);
|
339
|
+
|
340
|
+
// verify each element
|
341
|
+
for (int i = 0; i < 6; ++i) {
|
342
|
+
assertEqual(expected[i], actual[i]);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
```
|
346
|
+
|
347
|
+
|
348
|
+
### Pin Futures
|
349
|
+
|
350
|
+
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.
|
351
|
+
|
352
|
+
```C++
|
353
|
+
unittest(pin_read_history)
|
354
|
+
{
|
355
|
+
GodmodeState* state = GODMODE();
|
356
|
+
state->reset();
|
357
|
+
|
358
|
+
int future[6] = {33, 22, 55, 11, 44, 66};
|
359
|
+
state->analogPin[1].fromArray(future, 6);
|
360
|
+
for (int i = 0; i < 6; ++i)
|
361
|
+
{
|
362
|
+
assertEqual(future[i], analogRead(1));
|
363
|
+
}
|
364
|
+
|
365
|
+
// for digital pins, we have the added possibility of specifying
|
366
|
+
// a stream of input bytes encoded as ASCII
|
367
|
+
bool bigEndian = true;
|
368
|
+
state->digitalPin[1].fromAscii("Yo", bigEndian);
|
369
|
+
|
370
|
+
// digitial history as serial data, big-endian
|
371
|
+
bool expectedBits[16] = {
|
372
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
373
|
+
0, 1, 1, 0, 1, 1, 1, 1 // o
|
374
|
+
};
|
375
|
+
|
376
|
+
for (int i = 0; i < 16; ++i) {
|
377
|
+
assertEqual(expectedBits[i], digitalRead(1));
|
378
|
+
}
|
379
|
+
}
|
380
|
+
```
|
381
|
+
|
382
|
+
### Serial Data
|
383
|
+
|
384
|
+
Basic input and output verification of serial port data can be done as follows:
|
385
|
+
|
386
|
+
```c++
|
387
|
+
unittest(reading_writing_serial)
|
388
|
+
{
|
389
|
+
GodmodeState* state = GODMODE();
|
390
|
+
state->serialPort[0].dataIn = ""; // the queue of data waiting to be read
|
391
|
+
state->serialPort[0].dataOut = ""; // the history of data written
|
392
|
+
|
393
|
+
// When there is no data, nothing happens
|
394
|
+
assertEqual(-1, Serial.peek());
|
395
|
+
assertEqual("", state->serialPort[0].dataIn);
|
396
|
+
assertEqual("", state->serialPort[0].dataOut);
|
397
|
+
|
398
|
+
// if we put data on the input and peek at it, we see the value and it's not consumed
|
399
|
+
state->serialPort[0].dataIn = "a";
|
400
|
+
assertEqual('a', Serial.peek());
|
401
|
+
assertEqual("a", state->serialPort[0].dataIn);
|
402
|
+
assertEqual("", state->serialPort[0].dataOut);
|
403
|
+
|
404
|
+
// if we read the input, we see the value and it's consumed
|
405
|
+
assertEqual('a', Serial.read());
|
406
|
+
assertEqual("", state->serialPort[0].dataIn);
|
407
|
+
assertEqual("", state->serialPort[0].dataOut);
|
408
|
+
|
409
|
+
// when we write data, it shows up in the history -- the output buffer
|
410
|
+
Serial.write('b');
|
411
|
+
assertEqual("", state->serialPort[0].dataIn);
|
412
|
+
assertEqual("b", state->serialPort[0].dataOut);
|
413
|
+
|
414
|
+
// when we print more data, note that the history
|
415
|
+
// still contains the first thing we wrote
|
416
|
+
Serial.print("cdefg");
|
417
|
+
assertEqual("", state->serialPort[0].dataIn);
|
418
|
+
assertEqual("bcdefg", state->serialPort[0].dataOut);
|
419
|
+
}
|
420
|
+
```
|
421
|
+
|
422
|
+
A more complicated example: working with serial port IO. Let's say I have the following function:
|
423
|
+
|
424
|
+
```C++
|
425
|
+
void smartLightswitchSerialHandler(int pin) {
|
426
|
+
if (Serial.available() > 0) {
|
427
|
+
int incomingByte = Serial.read();
|
428
|
+
int val = incomingByte == '0' ? LOW : HIGH;
|
429
|
+
Serial.print("Ack ");
|
430
|
+
digitalWrite(pin, val);
|
431
|
+
Serial.print(String(pin));
|
432
|
+
Serial.print(" ");
|
433
|
+
Serial.print((char)incomingByte);
|
434
|
+
}
|
435
|
+
}
|
436
|
+
```
|
437
|
+
|
438
|
+
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.
|
439
|
+
|
440
|
+
```C++
|
441
|
+
unittest(does_nothing_if_no_data)
|
442
|
+
{
|
443
|
+
// configure initial state
|
444
|
+
GodmodeState* state = GODMODE();
|
445
|
+
int myPin = 3;
|
446
|
+
state->serialPort[0].dataIn = "";
|
447
|
+
state->serialPort[0].dataOut = "";
|
448
|
+
state->digitalPin[myPin] = LOW;
|
449
|
+
|
450
|
+
// execute action
|
451
|
+
smartLightswitchSerialHandler(myPin);
|
452
|
+
|
453
|
+
// assess final state
|
454
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
455
|
+
assertEqual("", state->serialPort[0].dataIn);
|
456
|
+
assertEqual("", state->serialPort[0].dataOut);
|
457
|
+
}
|
458
|
+
|
459
|
+
unittest(two_flips)
|
460
|
+
{
|
461
|
+
GodmodeState* state = GODMODE();
|
462
|
+
int myPin = 3;
|
463
|
+
state->serialPort[0].dataIn = "10junk";
|
464
|
+
state->serialPort[0].dataOut = "";
|
465
|
+
state->digitalPin[myPin] = LOW;
|
466
|
+
smartLightswitchSerialHandler(myPin);
|
467
|
+
assertEqual(HIGH, state->digitalPin[myPin]);
|
468
|
+
assertEqual("0junk", state->serialPort[0].dataIn);
|
469
|
+
assertEqual("Ack 3 1", state->serialPort[0].dataOut);
|
470
|
+
|
471
|
+
state->serialPort[0].dataOut = "";
|
472
|
+
smartLightswitchSerialHandler(myPin);
|
473
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
474
|
+
assertEqual("junk", state->serialPort[0].dataIn);
|
475
|
+
assertEqual("Ack 3 0", state->serialPort[0].dataOut);
|
476
|
+
}
|
477
|
+
```
|
478
|
+
|
479
|
+
### Pin History as ASCII
|
480
|
+
|
481
|
+
|
482
|
+
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.
|
483
|
+
|
484
|
+
```C++
|
485
|
+
int myPin = 3;
|
486
|
+
|
487
|
+
// digitial history as serial data, big-endian
|
488
|
+
bool bigEndian = true;
|
489
|
+
bool binaryAscii[24] = {
|
490
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
491
|
+
0, 1, 1, 0, 0, 1, 0, 1, // e
|
492
|
+
0, 1, 1, 1, 0, 0, 1, 1 // s
|
493
|
+
};
|
494
|
+
|
495
|
+
// "send" these bits
|
496
|
+
for (int i = 0; i < 24; digitalWrite(myPin, binaryAscii[i++]));
|
497
|
+
|
498
|
+
// The first bit in the history is the initial value, which we will ignore
|
499
|
+
int offset = 1;
|
500
|
+
|
501
|
+
// We should be able to parse the bits as ascii
|
502
|
+
assertEqual("Yes", state->digitalPin[myPin].toAscii(offset, bigEndian));
|
503
|
+
```
|
504
|
+
|
505
|
+
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.
|
506
|
+
|
507
|
+
|
508
|
+
### Interactivity of "Devices" with Observers
|
509
|
+
|
510
|
+
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.
|
511
|
+
|
512
|
+
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.
|
513
|
+
|
514
|
+
```c++
|
515
|
+
class FakeHayesModem : public DeviceUsingBytes {
|
516
|
+
public:
|
517
|
+
String mLast;
|
518
|
+
|
519
|
+
FakeHayesModem() : DeviceUsingBytes() {
|
520
|
+
mLast = "";
|
521
|
+
addResponseLine("AT", "OK");
|
522
|
+
addResponseLine("ATV1", "NO CARRIER");
|
523
|
+
}
|
524
|
+
virtual ~FakeHayesModem() {}
|
525
|
+
virtual void onMatchInput(String output) { mLast = output; }
|
526
|
+
};
|
527
|
+
|
528
|
+
unittest(modem_hardware)
|
529
|
+
{
|
530
|
+
GodmodeState* state = GODMODE();
|
531
|
+
state->reset();
|
532
|
+
FakeHayesModem m;
|
533
|
+
m.attach(&Serial);
|
534
|
+
|
535
|
+
Serial.write("AT\n");
|
536
|
+
assertEqual("AT\n", state->serialPort[0].dataOut);
|
537
|
+
assertEqual("OK\n", m.mLast);
|
538
|
+
}
|
539
|
+
```
|
540
|
+
|
541
|
+
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.
|
542
|
+
|
543
|
+
|
544
|
+
### Interrupts
|
545
|
+
|
546
|
+
Although ISRs should be tested directly (as their asynchronous nature is not mocked), the act of attaching or detaching an interrupt can be measured.
|
547
|
+
|
548
|
+
```C++
|
549
|
+
unittest(interrupt_attachment) {
|
550
|
+
GodmodeState *state = GODMODE();
|
551
|
+
state->reset();
|
552
|
+
assertFalse(state->interrupt[7].attached);
|
553
|
+
attachInterrupt(7, (void (*)(void))0, 3);
|
554
|
+
assertTrue(state->interrupt[7].attached);
|
555
|
+
assertEqual(state->interrupt[7].mode, 3);
|
556
|
+
detachInterrupt(7);
|
557
|
+
assertFalse(state->interrupt[7].attached);
|
558
|
+
}
|
559
|
+
```
|
560
|
+
|
561
|
+
|
562
|
+
### SPI
|
563
|
+
|
564
|
+
These basic mocks of SPI store the values in Strings.
|
565
|
+
|
566
|
+
```C++
|
567
|
+
unittest(spi) {
|
568
|
+
GodmodeState *state = GODMODE();
|
569
|
+
|
570
|
+
// 8-bit
|
571
|
+
state->reset();
|
572
|
+
state->spi.dataIn = "LMNO";
|
573
|
+
uint8_t out8 = SPI.transfer('a');
|
574
|
+
assertEqual("a", state->spi.dataOut);
|
575
|
+
assertEqual('L', out8);
|
576
|
+
assertEqual("MNO", state->spi.dataIn);
|
577
|
+
|
578
|
+
// 16-bit
|
579
|
+
union { uint16_t val; struct { char lsb; char msb; }; } in16, out16;
|
580
|
+
state->reset();
|
581
|
+
state->spi.dataIn = "LMNO";
|
582
|
+
in16.lsb = 'a';
|
583
|
+
in16.msb = 'b';
|
584
|
+
out16.val = SPI.transfer16(in16.val);
|
585
|
+
assertEqual("NO", state->spi.dataIn);
|
586
|
+
assertEqual('L', out16.lsb);
|
587
|
+
assertEqual('M', out16.msb);
|
588
|
+
assertEqual("ab", state->spi.dataOut);
|
589
|
+
|
590
|
+
// buffer
|
591
|
+
state->reset();
|
592
|
+
state->spi.dataIn = "LMNOP";
|
593
|
+
char inBuf[6] = "abcde";
|
594
|
+
SPI.transfer(inBuf, 4);
|
595
|
+
|
596
|
+
assertEqual("abcd", state->spi.dataOut);
|
597
|
+
assertEqual("LMNOe", String(inBuf));
|
598
|
+
}
|
599
|
+
```
|
600
|
+
|
601
|
+
### EEPROM
|
602
|
+
|
603
|
+
`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.)
|
604
|
+
|
605
|
+
```C++
|
606
|
+
unittest(eeprom)
|
607
|
+
{
|
608
|
+
uint8_t a;
|
609
|
+
// size
|
610
|
+
assertEqual(EEPROM_SIZE, EEPROM.length());
|
611
|
+
// initial values
|
612
|
+
a = EEPROM.read(0);
|
613
|
+
assertEqual(255, a);
|
614
|
+
// write and read
|
615
|
+
EEPROM.write(0, 24);
|
616
|
+
a = EEPROM.read(0);
|
617
|
+
assertEqual(24, a);
|
618
|
+
// update
|
619
|
+
EEPROM.write(1, 14);
|
620
|
+
EEPROM.update(1, 22);
|
621
|
+
a = EEPROM.read(1);
|
622
|
+
assertEqual(22, a);
|
623
|
+
// put and get
|
624
|
+
const float f1 = 0.025f;
|
625
|
+
float f2 = 0.0f;
|
626
|
+
EEPROM.put(5, f1);
|
627
|
+
assertEqual(0.0f, f2);
|
628
|
+
EEPROM.get(5, f2);
|
629
|
+
assertEqual(0.025f, f2);
|
630
|
+
// array access
|
631
|
+
int val = 10;
|
632
|
+
EEPROM[2] = val;
|
633
|
+
a = EEPROM[2];
|
634
|
+
assertEqual(10, a);
|
635
|
+
}
|
636
|
+
```
|