arduino_ci 0.3.0 → 1.3.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 +125 -69
- data/REFERENCE.md +711 -0
- data/cpp/arduino/Arduino.h +1 -7
- data/cpp/arduino/ArduinoDefines.h +3 -0
- 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 +7 -0
- data/cpp/arduino/Godmode.h +121 -15
- data/cpp/arduino/HardwareSerial.h +4 -4
- 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 +197 -77
- data/cpp/arduino/avr/io.h +10 -1
- data/cpp/arduino/avr/pgmspace.h +76 -46
- data/cpp/unittest/ArduinoUnitTests.h +32 -0
- data/cpp/unittest/Assertion.h +54 -26
- data/cpp/unittest/Compare.h +58 -51
- data/cpp/unittest/OstreamHelpers.h +4 -0
- data/exe/arduino_ci.rb +538 -0
- data/exe/arduino_ci_remote.rb +2 -393
- 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 +238 -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 +8 -11
- data/lib/arduino_ci/cpp_library.rb +250 -59
- data/lib/arduino_ci/host.rb +59 -4
- data/lib/arduino_ci/library_properties.rb +101 -0
- data/lib/arduino_ci/version.rb +1 -1
- data/misc/default.yml +57 -6
- metadata +19 -87
- data/cpp/arduino/Arduino.h.orig +0 -143
- data/exe/libasan.rb +0 -29
- data/lib/arduino_ci/arduino_cmd.rb +0 -332
- 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: c4765de27f7f477f5a64189afba2d70a280d1ae65e64916af6b98aa7d1a0266f
|
4
|
+
data.tar.gz: dd3748bdf67dfc526183d5a1372ce06d11b293cbaf984af19b90b999fdf1623e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae3f368ed4f543f22a0702df9945abac4df15683f85e7d54c5c958cc60b951453bd21aad19beac73a54257bee558ea172d5c8cbcea69deed3a7b15da0cff0495
|
7
|
+
data.tar.gz: b2481e5d6096e6dd6df1aa86efb6465e6f13dc853840fca4ba6044c8dc51b1ac1c8c0196f3c9d49c1412c4e880177a40880346e1bba1d4fb7cc4967468f192f7
|
data/README.md
CHANGED
@@ -1,108 +1,146 @@
|
|
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.3.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)
|
6
|
+
[![GitHub Marketplace](https://img.shields.io/badge/Get_it-on_Marketplace-informational.svg)](https://github.com/marketplace/actions/arduino_ci)
|
3
7
|
|
4
|
-
|
8
|
+
Arduino CI was created to enable better collaboration among Arduino library maintainers and contributors, by enabling automated code checks to be performed as part of a pull request process.
|
5
9
|
|
6
|
-
|
10
|
+
* enables running unit tests against the library **without hardware present**
|
11
|
+
* provides a system of mocks that allow fine-grained control over the hardware inputs, including the system's clock
|
12
|
+
* verifies compilation of any example sketches included in the library
|
13
|
+
* can test a wide range of arduino boards with different hardware options available
|
14
|
+
* compares entries in `library.properties` to the contents of the library and reports mismatches
|
15
|
+
* can be run both locally and as part of CI (GitHub Actions, TravisCI, Appveyor, etc.)
|
16
|
+
* runs on multiple platforms -- any platform that supports the Arduino IDE
|
17
|
+
* provides detailed analysis of segfaults in compilers that support such debugging features
|
7
18
|
|
8
|
-
|
19
|
+
> Note: for running tests in response to [GitHub events](https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/github-event-types), an [Arduino CI GitHub Action](https://github.com/marketplace/actions/arduino_ci) is available for your convenience. This method of running `arduino_ci` is driven by Docker, which may also serve your local testing needs (as it does not require a ruby environment to be installed).
|
9
20
|
|
10
|
-
|
21
|
+
Arduino CI works on multiple platforms, which should enable your CI system of choice to leverage it for testing.
|
11
22
|
|
12
23
|
Platform | CI Status
|
13
24
|
---------|:---------
|
14
|
-
OSX | [![OSX Build Status](
|
15
|
-
Linux | [![Linux Build Status](
|
16
|
-
Windows | [![Windows Build status](https://
|
25
|
+
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)
|
26
|
+
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)
|
27
|
+
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
28
|
|
18
29
|
|
19
|
-
##
|
30
|
+
## Quick Start
|
20
31
|
|
21
|
-
|
22
|
-
|-----------------------------------------------------------------------------|:--:|:---------------:|:--------:|:-------------:|:-------:|:---:|:-----:|:--------|
|
23
|
-
|[ArduinoCI](https://github.com/ianfixes/arduino_ci) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |Free (Apache-2.0)|
|
24
|
-
|[ArduinoUnit](https://github.com/mmurdoch/arduinounit) | ❌ | ❌ | ⚠️ Hardware-based|❌ | ✅ | ✅ | ✅ |Free (MIT)|
|
25
|
-
|[Adafruit `travis-ci-arduino`](https://github.com/adafruit/travis-ci-arduino)| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |Free (MIT)|
|
26
|
-
|[PlatformIO](https://platformio.org) | ✅ | ✅ | ⚠️ Paid only | ❌ | ✅ | ✅ | ✅ |⚠️ EULA|
|
27
|
-
|Official [Arduino IDE](https://www.arduino.cc/en/main/software) | ❌ | ⚠️ Manually | ❌ |N/A 😉| ✅ | ✅ | ✅ |Free (GPLv2)|
|
32
|
+
This project has the following dependencies:
|
28
33
|
|
34
|
+
* `ruby` 2.5 or higher
|
35
|
+
* A compiler like `g++` (on OSX, `clang` works; on Cygwin, use the `mingw-gcc-c++` package)
|
36
|
+
* `python` (if using a board architecutre that requires it, e.g. ESP32, ESP8266; see [this issue](https://github.com/Arduino-CI/arduino_ci/issues/235#issuecomment-739629243)). Consider `pyserial` as well.
|
29
37
|
|
30
|
-
|
38
|
+
In that environment, you can install by running `gem install arduino_ci`. To update to a latest version, use `gem update arduino_ci`.
|
39
|
+
|
40
|
+
You can now test your library by simply running the command `arduino_ci.rb` from your library directory. This will perform the following:
|
41
|
+
|
42
|
+
* validation of some fields in `library.properties`, if it exists
|
43
|
+
* running unit tests from files found in `test/`, if they exist
|
44
|
+
* testing compilation of example sketches found in `examples/`, if they exist
|
45
|
+
|
46
|
+
### Assumptions About Your Repository
|
47
|
+
|
48
|
+
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**.
|
31
49
|
|
32
|
-
|
50
|
+
> 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** 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).
|
33
51
|
|
34
|
-
|
52
|
+
### Changes to Your Repository
|
53
|
+
|
54
|
+
Unit testing binaries created by `arduino_ci` should not be commited to the codebase. To avoid that, add the following to your `.gitignore`:
|
55
|
+
|
56
|
+
```ignore-list
|
57
|
+
# arduino_ci unit test binaries and artifacts
|
58
|
+
*.bin
|
59
|
+
*.bin.dSYM
|
60
|
+
```
|
35
61
|
|
62
|
+
### A Quick Example
|
36
63
|
|
37
|
-
|
64
|
+
For a fairly minimal practical example of a unit-testable library repo that you can copy from, see [the `Arduino-CI/Blink` repository](https://github.com/Arduino-CI/Blink).
|
38
65
|
|
39
|
-
You'll need Ruby version 2.2 or higher, and to `gem install bundler` if it's not already there.
|
40
66
|
|
67
|
+
## Advanced Start
|
41
68
|
|
42
|
-
|
69
|
+
New features and bugfixes reach GitHub before they reach a released ruby gem. Alternately, it may be that (for your own reasons) you do not wish to install `arduino_ci` globally on your system. A few additional setup steps are required if you wish to do this.
|
43
70
|
|
44
|
-
|
71
|
+
### You Need Ruby _and_ Bundler
|
45
72
|
|
46
|
-
|
47
|
-
* **OSX**: `g++` is an alias for `clang`, which is provided by Xcode and the developer tools. You are free to `brew install gcc` as well; this is also tested and working.
|
48
|
-
* **Windows**: you will need Cygwin, and the `mingw-gcc-g++` package. A full set of (working) install instructions can be found in `appveyor.yml`, as this is how CI runs for this project.
|
73
|
+
In addition to version 2.5 or higher, you'll also need to `gem install bundler` to a minimum of version 2.0 if it's not already there. You may find it easiest to do this by using [`rbenv`](https://github.com/rbenv/rbenv).
|
49
74
|
|
75
|
+
You will need to add a file called `Gemfile` (no extension) to your Arduino project.
|
50
76
|
|
51
|
-
|
77
|
+
#### Non-root installation
|
52
78
|
|
53
|
-
|
79
|
+
If you are simply trying to avoid the need to install `arduino_ci` system-wide (which may require administrator permissions), your `Gemfile` would look like this:
|
54
80
|
|
55
81
|
```ruby
|
56
82
|
source 'https://rubygems.org'
|
57
|
-
gem 'arduino_ci'
|
58
|
-
```
|
59
|
-
|
60
|
-
It would also make sense to add the following to your `.gitignore`, or copy [the `.gitignore` used by this project](.gitignore):
|
61
83
|
|
84
|
+
# Replace 1.2 with the desired version of arduino_ci. See https://guides.rubygems.org/patterns/#pessimistic-version-constraint
|
85
|
+
gem 'arduino_ci', '~> 1.2'
|
62
86
|
```
|
87
|
+
|
88
|
+
It would also make sense to add the following to your `.gitignore`:
|
89
|
+
```ignore-list
|
63
90
|
/.bundle/
|
64
|
-
/.yardoc
|
65
|
-
Gemfile.lock
|
66
|
-
/_yardoc/
|
67
|
-
/coverage/
|
68
|
-
/doc/
|
69
|
-
/pkg/
|
70
|
-
/spec/reports/
|
71
91
|
vendor
|
72
|
-
|
92
|
+
```
|
73
93
|
|
74
|
-
|
75
|
-
.rspec_status
|
94
|
+
> Note: this used to be the recommended installation method, but with the library's maturation it's better to avoid the use of `Gemfile` and `bundle install` by just installing as per the "Quick Start" instructions above.
|
76
95
|
|
77
|
-
|
78
|
-
|
79
|
-
|
96
|
+
|
97
|
+
#### Using the latest-available code
|
98
|
+
|
99
|
+
If you want to use the latest code on GitHub, your `Gemfile` would look like this:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
source 'https://rubygems.org'
|
103
|
+
|
104
|
+
# to use the latest github code in a given repo and branch, replace the below values for git: and ref: as needed
|
105
|
+
gem 'arduino_ci', git: 'https://github.com/ArduinoCI/arduino_ci.git', ref: '<your desired ref, branch, or tag>'
|
80
106
|
```
|
81
107
|
|
82
108
|
|
83
|
-
|
109
|
+
#### Using a version of `arduino_ci` source code on your local machine
|
84
110
|
|
85
|
-
|
111
|
+
First, Thanks! See [CONTRIBUTING.md](CONTRIBUTING.md). Your `Gemfile` would look like this:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
source 'https://rubygems.org'
|
115
|
+
|
116
|
+
gem 'arduino_ci', path: '/path/to/development/dir/for/arduino_ci'
|
117
|
+
```
|
118
|
+
|
119
|
+
|
120
|
+
### Installing the Dependencies
|
86
121
|
|
122
|
+
Fulfilling the `arduino_ci` library dependency is as easy as running either of these two commands:
|
87
123
|
```
|
88
|
-
$ bundle install
|
124
|
+
$ bundle install # adds packages to global library (may require admin rights)
|
125
|
+
$ bundle install --path vendor/bundle # adds packages to local library
|
89
126
|
```
|
90
127
|
|
128
|
+
This will create a `Gemfile.lock` in your project directory, which you may optionally check into source control. A broader introduction to ruby dependencies is outside the scope of this document.
|
91
129
|
|
92
|
-
|
130
|
+
|
131
|
+
|
132
|
+
### Running `arduino_ci.rb` To Test Your Library
|
93
133
|
|
94
134
|
With that installed, just the following shell command each time you want the tests to execute:
|
95
135
|
|
96
|
-
```
|
97
|
-
$ bundle exec
|
136
|
+
```console
|
137
|
+
$ bundle exec arduino_ci.rb
|
98
138
|
```
|
99
139
|
|
100
|
-
`arduino_ci_remote.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
|
-
|
102
140
|
|
103
141
|
### Reference
|
104
142
|
|
105
|
-
For more information on the usage of `
|
143
|
+
For more information on the usage of `arduino_ci.rb`, see [REFERENCE.md](REFERENCE.md). It contains information such as:
|
106
144
|
|
107
145
|
* How to configure build options (platforms to test, Arduino library dependencies to install) with an `.arduino-ci.yml` file
|
108
146
|
* Where to put unit test files
|
@@ -113,18 +151,36 @@ For more information on the usage of `arduino_ci_remote.rb`, see [REFERENCE.md](
|
|
113
151
|
|
114
152
|
## Setting up Pull Request Testing and/or External CI
|
115
153
|
|
116
|
-
|
154
|
+
> **Note:** `arduino_ci.rb` expects to be run from the root directory of your Arduino project library.
|
155
|
+
|
156
|
+
### Arduino CI's Own GitHub action
|
117
157
|
|
118
|
-
|
119
|
-
* A CI system like [Travis CI](https://travis-ci.org/) or [Appveyor](https://www.appveyor.com/) that is linked to your project
|
158
|
+
[![GitHub Marketplace](https://img.shields.io/badge/Get_it-on_Marketplace-informational.svg)](https://github.com/marketplace/actions/arduino_ci)
|
120
159
|
|
121
160
|
|
122
|
-
###
|
161
|
+
### Your Own Scripted GitHub Action
|
123
162
|
|
124
|
-
|
163
|
+
GitHub Actions allows you to automate your workflows directly in GitHub.
|
164
|
+
No additional steps are needed.
|
165
|
+
Just create a YAML file with the information below in your repo under the `.github/workflows/` directory.
|
166
|
+
|
167
|
+
```yaml
|
168
|
+
on: [push, pull_request]
|
169
|
+
jobs:
|
170
|
+
runTest:
|
171
|
+
runs-on: ubuntu-latest
|
172
|
+
steps:
|
173
|
+
- uses: actions/checkout@v2
|
174
|
+
- uses: ruby/setup-ruby@v1
|
175
|
+
with:
|
176
|
+
ruby-version: 2.6
|
177
|
+
- run: |
|
178
|
+
gem install arduino_ci
|
179
|
+
arduino_ci.rb
|
180
|
+
```
|
125
181
|
|
126
182
|
|
127
|
-
|
183
|
+
### Travis CI
|
128
184
|
|
129
185
|
You'll need to go to https://travis-ci.org/profile/ and enable testing for your Arduino project. Once that happens, you should be all set. The script will test all example projects of the library and all unit tests.
|
130
186
|
|
@@ -134,12 +190,12 @@ Next, you need this in `.travis.yml` in your repo
|
|
134
190
|
sudo: false
|
135
191
|
language: ruby
|
136
192
|
script:
|
137
|
-
-
|
138
|
-
-
|
193
|
+
- gem install arduino_ci
|
194
|
+
- arduino_ci.rb
|
139
195
|
```
|
140
196
|
|
141
197
|
|
142
|
-
|
198
|
+
### Appveyor CI
|
143
199
|
|
144
200
|
You'll need to go to https://ci.appveyor.com/projects and add your project.
|
145
201
|
|
@@ -148,15 +204,16 @@ Next, you'll need this in `appveyor.yml` in your repo.
|
|
148
204
|
```yaml
|
149
205
|
build: off
|
150
206
|
test_script:
|
151
|
-
-
|
152
|
-
-
|
207
|
+
- gem install arduino_ci
|
208
|
+
- arduino_ci.rb
|
153
209
|
```
|
154
210
|
|
211
|
+
|
155
212
|
## Known Problems
|
156
213
|
|
157
|
-
* The Arduino library is not fully mocked
|
214
|
+
* The Arduino library is not fully mocked, nor is `avr-libc`.
|
158
215
|
* I don't have preprocessor defines for all the Arduino board flavors
|
159
|
-
* https://github.com/
|
216
|
+
* https://github.com/Arduino-CI/arduino_ci/issues
|
160
217
|
|
161
218
|
|
162
219
|
## Author
|
@@ -167,6 +224,5 @@ This gem was written by Ian Katz (ianfixes@gmail.com) in 2018. It's released un
|
|
167
224
|
## See Also
|
168
225
|
|
169
226
|
* [Contributing](CONTRIBUTING.md)
|
170
|
-
* [Adafruit/
|
227
|
+
* [Adafruit/ci-arduino](https://github.com/adafruit/ci-arduino) which inspired this project
|
171
228
|
* [mmurdoch/arduinounit](https://github.com/mmurdoch/arduinounit) from which the unit test macros were adopted
|
172
|
-
|
data/REFERENCE.md
ADDED
@@ -0,0 +1,711 @@
|
|
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
|
+
### `--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
|
+
### `CUSTOM_INIT_SCRIPT` environment variable
|
48
|
+
|
49
|
+
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.
|
50
|
+
|
51
|
+
|
52
|
+
### `USE_SUBDIR` environment variable
|
53
|
+
|
54
|
+
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.
|
55
|
+
|
56
|
+
|
57
|
+
### `EXPECT_UNITTESTS` environment variable
|
58
|
+
|
59
|
+
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.
|
60
|
+
|
61
|
+
|
62
|
+
### `EXPECT_EXAMPLES` environment variable
|
63
|
+
|
64
|
+
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.
|
65
|
+
|
66
|
+
|
67
|
+
## Indirectly Overriding Build Behavior (medium term use), and Advanced Options
|
68
|
+
|
69
|
+
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:
|
70
|
+
|
71
|
+
1. the root of your library
|
72
|
+
2. the `test/` directory
|
73
|
+
3. a subdirectory of `examples/`
|
74
|
+
|
75
|
+
`.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.
|
76
|
+
|
77
|
+
|
78
|
+
### Defining New Arduino Platforms
|
79
|
+
|
80
|
+
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
|
81
|
+
|
82
|
+
Here is how you would declare a package that includes the `potato:salad` set of platforms (aka "board family") in your `.arduino-ci.yml`:
|
83
|
+
|
84
|
+
```yaml
|
85
|
+
packages:
|
86
|
+
potato:salad:
|
87
|
+
url: https://potato.github.io/arduino-board-index/package_salad_index.json
|
88
|
+
```
|
89
|
+
|
90
|
+
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.
|
91
|
+
|
92
|
+
> 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`.
|
93
|
+
|
94
|
+
```yaml
|
95
|
+
platforms:
|
96
|
+
# our custom definition of the "bogo" platform
|
97
|
+
bogo:
|
98
|
+
board: potato:salad:bogo
|
99
|
+
package: potato:salad
|
100
|
+
gcc:
|
101
|
+
features:
|
102
|
+
- omit-frame-pointer # becomes -fomit-frame-pointer flag
|
103
|
+
defines:
|
104
|
+
- HAVE_THING # becomes -DHAVE_THING flag
|
105
|
+
warnings:
|
106
|
+
- no-implicit # becomes -Wno-implicit flag
|
107
|
+
flags:
|
108
|
+
- -foobar # becomes -foobar flag
|
109
|
+
|
110
|
+
# overriding the `zero` platform, to remove it completely
|
111
|
+
zero: ~
|
112
|
+
|
113
|
+
# redefine the existing esp8266
|
114
|
+
esp8266:
|
115
|
+
board: esp8266:esp8266:booo
|
116
|
+
package: esp8266:esp8266
|
117
|
+
gcc:
|
118
|
+
features:
|
119
|
+
defines:
|
120
|
+
warnings:
|
121
|
+
flags:
|
122
|
+
```
|
123
|
+
|
124
|
+
### Control How Examples Are Compiled
|
125
|
+
|
126
|
+
Put a file `.arduino-ci.yml` in each example directory where you require a different configuration than default.
|
127
|
+
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.
|
128
|
+
|
129
|
+
> 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.
|
130
|
+
|
131
|
+
```yaml
|
132
|
+
compile:
|
133
|
+
# Choosing to run compilation tests on 2 different Arduino platforms
|
134
|
+
platforms:
|
135
|
+
- esp8266
|
136
|
+
- bogo
|
137
|
+
|
138
|
+
# Declaring Dependent Arduino Libraries (to be installed via the Arduino Library Manager)
|
139
|
+
libraries:
|
140
|
+
- "Adafruit FONA Library"
|
141
|
+
```
|
142
|
+
|
143
|
+
|
144
|
+
### Control How Unit Tests Are Compiled and Run
|
145
|
+
|
146
|
+
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.
|
147
|
+
|
148
|
+
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.
|
149
|
+
|
150
|
+
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.
|
151
|
+
|
152
|
+
```yaml
|
153
|
+
unittest:
|
154
|
+
|
155
|
+
# Exclude these directories from compilation
|
156
|
+
exclude_dirs:
|
157
|
+
- someDirectory
|
158
|
+
- someOtherDirectory
|
159
|
+
|
160
|
+
# Perform unit tests with these compilers (these are the binaries that will be called via the shell)
|
161
|
+
compilers:
|
162
|
+
- g++ # default
|
163
|
+
- g++-4.9
|
164
|
+
- g++-7
|
165
|
+
|
166
|
+
# Filter the list of test files in some way
|
167
|
+
testfiles:
|
168
|
+
# files matching this glob (executed inside the `test/` directory) will be whitelisted for testing
|
169
|
+
select:
|
170
|
+
- "*-*.*"
|
171
|
+
|
172
|
+
# files matching this glob will be blacklisted from testing
|
173
|
+
reject:
|
174
|
+
- "sam-squamsh.*"
|
175
|
+
|
176
|
+
# These dependent libraries will be installed
|
177
|
+
libraries:
|
178
|
+
- "abc123"
|
179
|
+
- "def456"
|
180
|
+
|
181
|
+
# each of these platforms will be used when compiling the unit tests
|
182
|
+
platforms:
|
183
|
+
- bogo
|
184
|
+
```
|
185
|
+
|
186
|
+
The expected number of tests will be the product of:
|
187
|
+
|
188
|
+
* Number of compilers defined
|
189
|
+
* Number of platforms defined
|
190
|
+
* Number of matching test files
|
191
|
+
|
192
|
+
|
193
|
+
## Writing Unit tests in `test/`
|
194
|
+
|
195
|
+
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.
|
196
|
+
|
197
|
+
|
198
|
+
### Most Basic Unit Test
|
199
|
+
|
200
|
+
The most basic unit test file is as follows:
|
201
|
+
|
202
|
+
```C++
|
203
|
+
#include <ArduinoUnitTests.h>
|
204
|
+
#include "../do-something.h"
|
205
|
+
|
206
|
+
unittest(your_test_name)
|
207
|
+
{
|
208
|
+
assertEqual(4, doSomething());
|
209
|
+
}
|
210
|
+
|
211
|
+
unittest_main()
|
212
|
+
```
|
213
|
+
|
214
|
+
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.
|
215
|
+
|
216
|
+
### Assertions
|
217
|
+
|
218
|
+
The following assertion functions are available in unit tests.
|
219
|
+
|
220
|
+
```c++
|
221
|
+
assertEqual(expected, actual); // a == b
|
222
|
+
assertNotEqual(unwanted, actual); // a != b
|
223
|
+
assertComparativeEquivalent(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b))
|
224
|
+
assertComparativeNotEquivalent(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b))
|
225
|
+
assertLess(upperBound, actual); // a < b
|
226
|
+
assertMore(lowerBound, actual); // a > b
|
227
|
+
assertLessOrEqual(upperBound, actual); // a <= b
|
228
|
+
assertMoreOrEqual(lowerBound, actual); // a >= b
|
229
|
+
assertTrue(actual);
|
230
|
+
assertFalse(actual);
|
231
|
+
assertNull(actual);
|
232
|
+
|
233
|
+
// special cases for floats
|
234
|
+
assertEqualFloat(expected, actual, epsilon); // fabs(a - b) <= epsilon
|
235
|
+
assertNotEqualFloat(unwanted, actual, epsilon); // fabs(a - b) >= epsilon
|
236
|
+
assertInfinity(actual); // isinf(a)
|
237
|
+
assertNotInfinity(actual); // !isinf(a)
|
238
|
+
assertNAN(arg); // isnan(a)
|
239
|
+
assertNotNAN(arg); // !isnan(a)
|
240
|
+
```
|
241
|
+
|
242
|
+
These functions will report the result of the test to the console, and the testing will continue if they fail.
|
243
|
+
|
244
|
+
**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.
|
245
|
+
|
246
|
+
|
247
|
+
### Test Setup and Teardown
|
248
|
+
|
249
|
+
For steps that are common to all tests, setup and teardown functions may optionally be supplied.
|
250
|
+
|
251
|
+
```C++
|
252
|
+
#include <ArduinoUnitTests.h>
|
253
|
+
|
254
|
+
int* myNumber;
|
255
|
+
|
256
|
+
unittest_setup()
|
257
|
+
{
|
258
|
+
myNumber = new int(4);
|
259
|
+
}
|
260
|
+
|
261
|
+
unittest_teardown()
|
262
|
+
{
|
263
|
+
delete myNumber;
|
264
|
+
myNumber = NULL;
|
265
|
+
}
|
266
|
+
|
267
|
+
unittest(your_test_name)
|
268
|
+
{
|
269
|
+
assertEqual(4, *myNumber);
|
270
|
+
}
|
271
|
+
|
272
|
+
unittest_main()
|
273
|
+
```
|
274
|
+
|
275
|
+
|
276
|
+
# Build Scripts
|
277
|
+
|
278
|
+
For most build environments, the only script that need be executed by the CI system is
|
279
|
+
|
280
|
+
```shell
|
281
|
+
# simplest build script
|
282
|
+
bundle install
|
283
|
+
bundle exec arduino_ci.rb
|
284
|
+
```
|
285
|
+
|
286
|
+
However, more flexible usage is available:
|
287
|
+
|
288
|
+
### Custom Versions of external Arduino Libraries
|
289
|
+
|
290
|
+
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.
|
291
|
+
|
292
|
+
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.
|
293
|
+
|
294
|
+
```shell
|
295
|
+
# Example build script
|
296
|
+
bundle install
|
297
|
+
|
298
|
+
# ensure the Arduino installation -- creates the Library directory
|
299
|
+
bundle exec ensure_arduino_installation.rb
|
300
|
+
|
301
|
+
# manually install a custom library from a zip file
|
302
|
+
wget https://hosting.com/custom_library.zip
|
303
|
+
unzip -o custom_library.zip
|
304
|
+
mv custom_library $(bundle exec arduino_library_location.rb)
|
305
|
+
|
306
|
+
# manually install a custom library from a git repository
|
307
|
+
git clone https://repository.com/custom_library_repo.git
|
308
|
+
mv custom_library_repo $(bundle exec arduino_library_location.rb)
|
309
|
+
|
310
|
+
# now run CI
|
311
|
+
bundle exec arduino_ci.rb
|
312
|
+
```
|
313
|
+
|
314
|
+
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.
|
315
|
+
|
316
|
+
|
317
|
+
|
318
|
+
# Mocks of Arduino Hardware Functions
|
319
|
+
|
320
|
+
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.
|
321
|
+
|
322
|
+
## Using `GODMODE`
|
323
|
+
|
324
|
+
Complete control of the Arduino environment is available in your unit tests through a construct called `GODMODE()`.
|
325
|
+
|
326
|
+
```C++
|
327
|
+
unittest(example_godmode_stuff)
|
328
|
+
{
|
329
|
+
GodmodeState* state = GODMODE(); // get access to the state
|
330
|
+
state->reset(); // does a full reset of the state.
|
331
|
+
state->resetClock(); // - you can reset just the clock (to zero)
|
332
|
+
state->resetPins(); // - or just the pins
|
333
|
+
state->micros = 1; // manually set the clock such that micros() returns 1
|
334
|
+
state->digitalPin[4]; // tells you the commanded state of digital pin 4
|
335
|
+
state->digitalPin[4] = HIGH; // digitalRead(4) will now return HIGH
|
336
|
+
state->analogPin[3]; // tells you the commanded state of analog pin 3
|
337
|
+
state->analogPin[3] = 99; // analogRead(3) will now return 99
|
338
|
+
}
|
339
|
+
```
|
340
|
+
|
341
|
+
### Pin Histories
|
342
|
+
|
343
|
+
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:
|
344
|
+
|
345
|
+
```C++
|
346
|
+
unittest(pin_history)
|
347
|
+
{
|
348
|
+
GodmodeState* state = GODMODE();
|
349
|
+
int myPin = 3;
|
350
|
+
state->reset(); // pin will start LOW
|
351
|
+
digitalWrite(myPin, HIGH);
|
352
|
+
digitalWrite(myPin, LOW);
|
353
|
+
digitalWrite(myPin, LOW);
|
354
|
+
digitalWrite(myPin, HIGH);
|
355
|
+
digitalWrite(myPin, HIGH);
|
356
|
+
|
357
|
+
// pin history is queued in case we want to analyze it later.
|
358
|
+
// we expect 6 values in that queue (5 that we set plus one
|
359
|
+
// initial value), which we'll hard-code here for convenience.
|
360
|
+
// (we'll actually assert those 6 values in the next block)
|
361
|
+
assertEqual(6, state->digitalPin[1].queueSize());
|
362
|
+
bool expected[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH};
|
363
|
+
bool actual[6];
|
364
|
+
|
365
|
+
// convert history queue into an array so we can verify it.
|
366
|
+
// while we're at it, check that we received the amount of
|
367
|
+
// elements that we expected.
|
368
|
+
int numMoved = state->digitalPin[myPin].toArray(actual, 6);
|
369
|
+
assertEqual(6, numMoved);
|
370
|
+
|
371
|
+
// verify each element
|
372
|
+
for (int i = 0; i < 6; ++i) {
|
373
|
+
assertEqual(expected[i], actual[i]);
|
374
|
+
}
|
375
|
+
}
|
376
|
+
```
|
377
|
+
|
378
|
+
|
379
|
+
### Pin Futures
|
380
|
+
|
381
|
+
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.
|
382
|
+
|
383
|
+
```C++
|
384
|
+
unittest(pin_read_history)
|
385
|
+
{
|
386
|
+
GodmodeState* state = GODMODE();
|
387
|
+
state->reset();
|
388
|
+
|
389
|
+
int future[6] = {33, 22, 55, 11, 44, 66};
|
390
|
+
state->analogPin[1].fromArray(future, 6);
|
391
|
+
for (int i = 0; i < 6; ++i)
|
392
|
+
{
|
393
|
+
assertEqual(future[i], analogRead(1));
|
394
|
+
}
|
395
|
+
|
396
|
+
// for digital pins, we have the added possibility of specifying
|
397
|
+
// a stream of input bytes encoded as ASCII
|
398
|
+
bool bigEndian = true;
|
399
|
+
state->digitalPin[1].fromAscii("Yo", bigEndian);
|
400
|
+
|
401
|
+
// digitial history as serial data, big-endian
|
402
|
+
bool expectedBits[16] = {
|
403
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
404
|
+
0, 1, 1, 0, 1, 1, 1, 1 // o
|
405
|
+
};
|
406
|
+
|
407
|
+
for (int i = 0; i < 16; ++i) {
|
408
|
+
assertEqual(expectedBits[i], digitalRead(1));
|
409
|
+
}
|
410
|
+
}
|
411
|
+
```
|
412
|
+
|
413
|
+
### Serial Data
|
414
|
+
|
415
|
+
Basic input and output verification of serial port data can be done as follows:
|
416
|
+
|
417
|
+
```c++
|
418
|
+
unittest(reading_writing_serial)
|
419
|
+
{
|
420
|
+
GodmodeState* state = GODMODE();
|
421
|
+
state->serialPort[0].dataIn = ""; // the queue of data waiting to be read
|
422
|
+
state->serialPort[0].dataOut = ""; // the history of data written
|
423
|
+
|
424
|
+
// When there is no data, nothing happens
|
425
|
+
assertEqual(-1, Serial.peek());
|
426
|
+
assertEqual("", state->serialPort[0].dataIn);
|
427
|
+
assertEqual("", state->serialPort[0].dataOut);
|
428
|
+
|
429
|
+
// if we put data on the input and peek at it, we see the value and it's not consumed
|
430
|
+
state->serialPort[0].dataIn = "a";
|
431
|
+
assertEqual('a', Serial.peek());
|
432
|
+
assertEqual("a", state->serialPort[0].dataIn);
|
433
|
+
assertEqual("", state->serialPort[0].dataOut);
|
434
|
+
|
435
|
+
// if we read the input, we see the value and it's consumed
|
436
|
+
assertEqual('a', Serial.read());
|
437
|
+
assertEqual("", state->serialPort[0].dataIn);
|
438
|
+
assertEqual("", state->serialPort[0].dataOut);
|
439
|
+
|
440
|
+
// when we write data, it shows up in the history -- the output buffer
|
441
|
+
Serial.write('b');
|
442
|
+
assertEqual("", state->serialPort[0].dataIn);
|
443
|
+
assertEqual("b", state->serialPort[0].dataOut);
|
444
|
+
|
445
|
+
// when we print more data, note that the history
|
446
|
+
// still contains the first thing we wrote
|
447
|
+
Serial.print("cdefg");
|
448
|
+
assertEqual("", state->serialPort[0].dataIn);
|
449
|
+
assertEqual("bcdefg", state->serialPort[0].dataOut);
|
450
|
+
}
|
451
|
+
```
|
452
|
+
|
453
|
+
A more complicated example: working with serial port IO. Let's say I have the following function:
|
454
|
+
|
455
|
+
```C++
|
456
|
+
void smartLightswitchSerialHandler(int pin) {
|
457
|
+
if (Serial.available() > 0) {
|
458
|
+
int incomingByte = Serial.read();
|
459
|
+
int val = incomingByte == '0' ? LOW : HIGH;
|
460
|
+
Serial.print("Ack ");
|
461
|
+
digitalWrite(pin, val);
|
462
|
+
Serial.print(String(pin));
|
463
|
+
Serial.print(" ");
|
464
|
+
Serial.print((char)incomingByte);
|
465
|
+
}
|
466
|
+
}
|
467
|
+
```
|
468
|
+
|
469
|
+
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.
|
470
|
+
|
471
|
+
```C++
|
472
|
+
unittest(does_nothing_if_no_data)
|
473
|
+
{
|
474
|
+
// configure initial state
|
475
|
+
GodmodeState* state = GODMODE();
|
476
|
+
int myPin = 3;
|
477
|
+
state->serialPort[0].dataIn = "";
|
478
|
+
state->serialPort[0].dataOut = "";
|
479
|
+
state->digitalPin[myPin] = LOW;
|
480
|
+
|
481
|
+
// execute action
|
482
|
+
smartLightswitchSerialHandler(myPin);
|
483
|
+
|
484
|
+
// assess final state
|
485
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
486
|
+
assertEqual("", state->serialPort[0].dataIn);
|
487
|
+
assertEqual("", state->serialPort[0].dataOut);
|
488
|
+
}
|
489
|
+
|
490
|
+
unittest(two_flips)
|
491
|
+
{
|
492
|
+
GodmodeState* state = GODMODE();
|
493
|
+
int myPin = 3;
|
494
|
+
state->serialPort[0].dataIn = "10junk";
|
495
|
+
state->serialPort[0].dataOut = "";
|
496
|
+
state->digitalPin[myPin] = LOW;
|
497
|
+
smartLightswitchSerialHandler(myPin);
|
498
|
+
assertEqual(HIGH, state->digitalPin[myPin]);
|
499
|
+
assertEqual("0junk", state->serialPort[0].dataIn);
|
500
|
+
assertEqual("Ack 3 1", state->serialPort[0].dataOut);
|
501
|
+
|
502
|
+
state->serialPort[0].dataOut = "";
|
503
|
+
smartLightswitchSerialHandler(myPin);
|
504
|
+
assertEqual(LOW, state->digitalPin[myPin]);
|
505
|
+
assertEqual("junk", state->serialPort[0].dataIn);
|
506
|
+
assertEqual("Ack 3 0", state->serialPort[0].dataOut);
|
507
|
+
}
|
508
|
+
```
|
509
|
+
|
510
|
+
### Pin History as ASCII
|
511
|
+
|
512
|
+
|
513
|
+
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.
|
514
|
+
|
515
|
+
```C++
|
516
|
+
int myPin = 3;
|
517
|
+
|
518
|
+
// digitial history as serial data, big-endian
|
519
|
+
bool bigEndian = true;
|
520
|
+
bool binaryAscii[24] = {
|
521
|
+
0, 1, 0, 1, 1, 0, 0, 1, // Y
|
522
|
+
0, 1, 1, 0, 0, 1, 0, 1, // e
|
523
|
+
0, 1, 1, 1, 0, 0, 1, 1 // s
|
524
|
+
};
|
525
|
+
|
526
|
+
// "send" these bits
|
527
|
+
for (int i = 0; i < 24; digitalWrite(myPin, binaryAscii[i++]));
|
528
|
+
|
529
|
+
// The first bit in the history is the initial value, which we will ignore
|
530
|
+
int offset = 1;
|
531
|
+
|
532
|
+
// We should be able to parse the bits as ascii
|
533
|
+
assertEqual("Yes", state->digitalPin[myPin].toAscii(offset, bigEndian));
|
534
|
+
```
|
535
|
+
|
536
|
+
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.
|
537
|
+
|
538
|
+
|
539
|
+
### Interactivity of "Devices" with Observers
|
540
|
+
|
541
|
+
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.
|
542
|
+
|
543
|
+
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.
|
544
|
+
|
545
|
+
```c++
|
546
|
+
class FakeHayesModem : public DeviceUsingBytes {
|
547
|
+
public:
|
548
|
+
String mLast;
|
549
|
+
|
550
|
+
FakeHayesModem() : DeviceUsingBytes() {
|
551
|
+
mLast = "";
|
552
|
+
addResponseLine("AT", "OK");
|
553
|
+
addResponseLine("ATV1", "NO CARRIER");
|
554
|
+
}
|
555
|
+
virtual ~FakeHayesModem() {}
|
556
|
+
virtual void onMatchInput(String output) { mLast = output; }
|
557
|
+
};
|
558
|
+
|
559
|
+
unittest(modem_hardware)
|
560
|
+
{
|
561
|
+
GodmodeState* state = GODMODE();
|
562
|
+
state->reset();
|
563
|
+
FakeHayesModem m;
|
564
|
+
m.attach(&Serial);
|
565
|
+
|
566
|
+
Serial.write("AT\n");
|
567
|
+
assertEqual("AT\n", state->serialPort[0].dataOut);
|
568
|
+
assertEqual("OK\n", m.mLast);
|
569
|
+
}
|
570
|
+
```
|
571
|
+
|
572
|
+
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.
|
573
|
+
|
574
|
+
|
575
|
+
### Interrupts
|
576
|
+
|
577
|
+
Although ISRs should be tested directly (as their asynchronous nature is not mocked), the act of attaching or detaching an interrupt can be measured.
|
578
|
+
|
579
|
+
```C++
|
580
|
+
unittest(interrupt_attachment) {
|
581
|
+
GodmodeState *state = GODMODE();
|
582
|
+
state->reset();
|
583
|
+
assertFalse(state->interrupt[7].attached);
|
584
|
+
attachInterrupt(7, (void (*)(void))0, 3);
|
585
|
+
assertTrue(state->interrupt[7].attached);
|
586
|
+
assertEqual(state->interrupt[7].mode, 3);
|
587
|
+
detachInterrupt(7);
|
588
|
+
assertFalse(state->interrupt[7].attached);
|
589
|
+
}
|
590
|
+
```
|
591
|
+
|
592
|
+
|
593
|
+
### SPI
|
594
|
+
|
595
|
+
These basic mocks of SPI store the values in Strings.
|
596
|
+
|
597
|
+
```C++
|
598
|
+
unittest(spi) {
|
599
|
+
GodmodeState *state = GODMODE();
|
600
|
+
|
601
|
+
// 8-bit
|
602
|
+
state->reset();
|
603
|
+
state->spi.dataIn = "LMNO";
|
604
|
+
uint8_t out8 = SPI.transfer('a');
|
605
|
+
assertEqual("a", state->spi.dataOut);
|
606
|
+
assertEqual('L', out8);
|
607
|
+
assertEqual("MNO", state->spi.dataIn);
|
608
|
+
|
609
|
+
// 16-bit
|
610
|
+
union { uint16_t val; struct { char lsb; char msb; }; } in16, out16;
|
611
|
+
state->reset();
|
612
|
+
state->spi.dataIn = "LMNO";
|
613
|
+
in16.lsb = 'a';
|
614
|
+
in16.msb = 'b';
|
615
|
+
out16.val = SPI.transfer16(in16.val);
|
616
|
+
assertEqual("NO", state->spi.dataIn);
|
617
|
+
assertEqual('L', out16.lsb);
|
618
|
+
assertEqual('M', out16.msb);
|
619
|
+
assertEqual("ab", state->spi.dataOut);
|
620
|
+
|
621
|
+
// buffer
|
622
|
+
state->reset();
|
623
|
+
state->spi.dataIn = "LMNOP";
|
624
|
+
char inBuf[6] = "abcde";
|
625
|
+
SPI.transfer(inBuf, 4);
|
626
|
+
|
627
|
+
assertEqual("abcd", state->spi.dataOut);
|
628
|
+
assertEqual("LMNOe", String(inBuf));
|
629
|
+
}
|
630
|
+
```
|
631
|
+
|
632
|
+
### EEPROM
|
633
|
+
|
634
|
+
`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.)
|
635
|
+
|
636
|
+
```C++
|
637
|
+
unittest(eeprom)
|
638
|
+
{
|
639
|
+
uint8_t a;
|
640
|
+
// size
|
641
|
+
assertEqual(EEPROM_SIZE, EEPROM.length());
|
642
|
+
// initial values
|
643
|
+
a = EEPROM.read(0);
|
644
|
+
assertEqual(255, a);
|
645
|
+
// write and read
|
646
|
+
EEPROM.write(0, 24);
|
647
|
+
a = EEPROM.read(0);
|
648
|
+
assertEqual(24, a);
|
649
|
+
// update
|
650
|
+
EEPROM.write(1, 14);
|
651
|
+
EEPROM.update(1, 22);
|
652
|
+
a = EEPROM.read(1);
|
653
|
+
assertEqual(22, a);
|
654
|
+
// put and get
|
655
|
+
const float f1 = 0.025f;
|
656
|
+
float f2 = 0.0f;
|
657
|
+
EEPROM.put(5, f1);
|
658
|
+
assertEqual(0.0f, f2);
|
659
|
+
EEPROM.get(5, f2);
|
660
|
+
assertEqual(0.025f, f2);
|
661
|
+
// array access
|
662
|
+
int val = 10;
|
663
|
+
EEPROM[2] = val;
|
664
|
+
a = EEPROM[2];
|
665
|
+
assertEqual(10, a);
|
666
|
+
}
|
667
|
+
```
|
668
|
+
|
669
|
+
|
670
|
+
### Wire
|
671
|
+
|
672
|
+
This library allows communication with I2C / TWI devices.
|
673
|
+
|
674
|
+
The interface the library has been fully mocked, with the addition of several functions for debugging
|
675
|
+
|
676
|
+
* `Wire.resetMocks()`: Initializes all mocks, and for test repeatability should be called at the top of any unit tests that use Wire.
|
677
|
+
* `Wire.didBegin()`: returns whether `Wire.begin()` was called at any point
|
678
|
+
* `Wire.getMosi(address)`: returns a pointer to a `deque` that represents the history of data sent to `address`
|
679
|
+
* `Wire.getMiso(address)`: returns a pointer to a `deque` that defines what the master will read from `address` (i.e. for you to supply)
|
680
|
+
|
681
|
+
```c++
|
682
|
+
unittest(wire_basics) {
|
683
|
+
// ensure known starting state
|
684
|
+
Wire.resetMocks();
|
685
|
+
|
686
|
+
// in case you need to check that your library is properly calling .begin()
|
687
|
+
assertFalse(Wire.didBegin());
|
688
|
+
Wire.begin();
|
689
|
+
assertTrue(Wire.didBegin());
|
690
|
+
|
691
|
+
// pick a random device. master write buffer should be empty
|
692
|
+
const uint8_t randomSlaveAddr = 14;
|
693
|
+
deque<uint8_t>* mosi = Wire.getMosi(randomSlaveAddr);
|
694
|
+
assertEqual(0, mosi->size());
|
695
|
+
|
696
|
+
// write some random data to random device
|
697
|
+
const uint8_t randomData[] = { 0x07, 0x0E };
|
698
|
+
Wire.beginTransmission(randomSlaveAddr);
|
699
|
+
Wire.write(randomData[0]);
|
700
|
+
Wire.write(randomData[1]);
|
701
|
+
Wire.endTransmission();
|
702
|
+
|
703
|
+
// check master write buffer values
|
704
|
+
assertEqual(2, mosi->size());
|
705
|
+
assertEqual(randomData[0], mosi->front());
|
706
|
+
mosi->pop_front();
|
707
|
+
assertEqual(randomData[1], mosi->front());
|
708
|
+
mosi->pop_front();
|
709
|
+
assertEqual(0, mosi->size());
|
710
|
+
}
|
711
|
+
```
|