Skip to content

Commit ee61841

Browse files
authored
Merge pull request #20 from ifreecarve/2018-02-19_mocks
Interactive serial mocks
2 parents 92930c5 + 06cb9d0 commit ee61841

21 files changed

+822
-30
lines changed

.github/issue_template.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## System
2+
3+
- OS: _(Travis/OSX/Linux/Windows)_
4+
- `ruby -v`:
5+
- `bundle -v`:
6+
- `bundle info arduino_ci`:
7+
- `gcc -v`:
8+
- Arduino IDE version:
9+
- URL of failing Travis CI job:
10+
- URL of your Arduino project:
11+
12+
13+
## Issue / Feature Request Summary
14+
15+
16+
## Arduino or Unit Test Code, Illustrating the Problem
17+
18+
19+
## Arduino Architecture(s) Affected

.github/pull_request_template.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Highlights from `CHANGELOG.md`
2+
3+
* See CHANGELOG.md for more
4+
5+
6+
## Issues Fixed
7+
8+
* Fixes #3000

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
- Yaml files support select/reject critera for paths of unit tests for targeted testing
1111
- Pins now track history and can report it in Ascii (big- or little-endian) for digital sequences
1212
- Pins now accept an array (or string) of input bits for providing pin values across multiple reads
13+
- FlashStringHelper (and related macros) compilation mocks
1314
- SoftwareSerial. That took a while.
15+
- Queue template implementation
16+
- Table template implementation
17+
- ObservableDataStream and DataStreamObserver pattern implementation
18+
- DeviceUsingBytes and implementation of mocked serial device
1419

1520
### Changed
1621
- Unit test executables print to STDERR just in case there are segfaults. Uh, just in case I ever write any.
@@ -24,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2429
- `arduino_ci_remote.rb` no longer makes unnecessary changes to the board being tested
2530
- Scripts no longer crash if there is no `test/` directory
2631
- Scripts no longer crash if there is no `examples/` directory
32+
- `assureTrue` and `assureFalse` now `assure` instead of just `assert`ing.
2733

2834
### Security
2935

README.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ unittest(example_godmode_stuff)
7070
}
7171
```
7272

73+
#### Pin Histories
74+
7375
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:
7476

7577
```C++
@@ -102,6 +104,9 @@ unittest(pin_history)
102104
}
103105
```
104106

107+
108+
#### Pin Futures
109+
105110
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.
106111

107112
```C++
@@ -134,6 +139,9 @@ unittest(pin_read_history)
134139
}
135140
```
136141

142+
#### Serial Data
143+
144+
137145
A more complicated example: working with serial port IO. Let's say I have the following function:
138146

139147
```C++
@@ -191,10 +199,10 @@ unittest(two_flips)
191199
}
192200
```
193201

202+
#### Pin History as ASCII
194203

195204

196-
197-
Finally, there are some cases where you want to use a pin as a serial port. There are history functions for that too.
205+
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.
198206

199207
```C++
200208
int myPin = 3;
@@ -217,6 +225,45 @@ Finally, there are some cases where you want to use a pin as a serial port. The
217225
assertEqual("Yes", state->digitalPin[myPin].toAscii(offset, bigEndian));
218226
```
219227

228+
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.
229+
230+
231+
#### Interactivity of "Devices" with Observers
232+
233+
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.
234+
235+
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.
236+
237+
```c++
238+
class FakeHayesModem : public DeviceUsingBytes {
239+
public:
240+
String mLast;
241+
242+
FakeHayesModem() : DeviceUsingBytes() {
243+
mLast = "";
244+
addResponseLine("AT", "OK");
245+
addResponseLine("ATV1", "NO CARRIER");
246+
}
247+
virtual ~FakeHayesModem() {}
248+
virtual void onMatchInput(String output) { mLast = output; }
249+
};
250+
251+
unittest(modem_hardware)
252+
{
253+
GodmodeState* state = GODMODE();
254+
state->reset();
255+
FakeHayesModem m;
256+
m.attach(&Serial);
257+
258+
Serial.write("AT\n");
259+
assertEqual("AT\n", state->serialPort[0].dataOut);
260+
assertEqual("OK\n", m.mLast);
261+
}
262+
```
263+
264+
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.
265+
266+
220267
## Overriding default build behavior
221268

222269
You can add `.arduino-ci.yml` files to the project directory (which will then apply to both `test/` and `examples/`), as well as to the `test/` directory and each example directory in `examples/`. All defined fields can be overridden.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include <Arduino.h>
2+
#include <ArduinoUnitTests.h>
3+
#include <SoftwareSerial.h>
4+
#include <ci/DeviceUsingBytes.h>
5+
6+
// DeviceUsingBytes extends DataStreamObserver,
7+
// so we will be able to attach this class to an
8+
// ObservableDataStream object, of which the pin
9+
// history (soft-serial) and HardwareSerial
10+
// objects are.
11+
class FakeHayesModem : public DeviceUsingBytes {
12+
public:
13+
String mLast;
14+
bool mMatchedInput;
15+
16+
FakeHayesModem() : DeviceUsingBytes() {
17+
mLast = "";
18+
mMatchedInput = false;
19+
addResponseLine("AT", "OK");
20+
addResponseLine("ATV1", "NO CARRIER");
21+
}
22+
23+
virtual ~FakeHayesModem() {}
24+
25+
virtual void onMatchInput(String output) {
26+
mLast = output;
27+
mMatchedInput = true;
28+
}
29+
};
30+
31+
unittest(modem_hardware)
32+
{
33+
GodmodeState* state = GODMODE();
34+
state->reset();
35+
36+
String cmd = "AT\n";
37+
38+
FakeHayesModem m;
39+
m.attach(&Serial);
40+
assertEqual(0, Serial.available());
41+
assertFalse(m.mMatchedInput);
42+
assertEqual("", m.mMessage);
43+
44+
for (int i = 0; i < cmd.length(); ++i) {
45+
assertEqual(i, m.mMessage.length()); // before we write, length should equal i
46+
Serial.write(cmd[i]);
47+
}
48+
assertEqual(0, m.mMessage.length()); // should have matched and reset
49+
50+
assertEqual("", state->serialPort[0].dataIn);
51+
assertEqual("AT\n", state->serialPort[0].dataOut);
52+
53+
assureTrue(m.mMatchedInput);
54+
//assertEqual(3, Serial.available());
55+
assertEqual("OK\n", m.mLast);
56+
}
57+
58+
unittest(modem_software)
59+
{
60+
GodmodeState* state = GODMODE();
61+
state->reset();
62+
63+
bool bigEndian = false;
64+
bool flipLogic = false;
65+
SoftwareSerial ss(1, 2, flipLogic);
66+
ss.listen();
67+
68+
String cmd = "AT\n";
69+
70+
FakeHayesModem m;
71+
m.attach(&state->digitalPin[2]);
72+
assertEqual(0, ss.available());
73+
assertFalse(m.mMatchedInput);
74+
assertEqual("", m.mMessage);
75+
76+
for (int i = 0; i < cmd.length(); ++i) {
77+
assertEqual(i, m.mMessage.length()); // before we write, length should equal i
78+
assertEqual(cmd.substr(0, i), state->digitalPin[2].toAscii(1, bigEndian));
79+
assertEqual(cmd.substr(0, i), m.mMessage);
80+
ss.write(cmd[i]);
81+
}
82+
assertEqual(0, m.mMessage.length()); // should have matched and reset
83+
84+
assertEqual("", state->digitalPin[1].incomingToAscii(1, bigEndian));
85+
assertEqual("AT\n", state->digitalPin[2].toAscii(1, bigEndian));
86+
87+
88+
assureTrue(m.mMatchedInput);
89+
//assertEqual(3, Serial.available());
90+
assertEqual("OK\n", m.mLast);
91+
}
92+
93+
unittest_main()
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <Arduino.h>
2+
#include <ArduinoUnitTests.h>
3+
#include <ci/ObservableDataStream.h>
4+
5+
class Source : public ObservableDataStream {
6+
public:
7+
Source() : ObservableDataStream() {}
8+
9+
// expose protected functions
10+
void doBit(bool val) { advertiseBit(val); }
11+
void doByte(unsigned char val) { advertiseByte(val); }
12+
};
13+
14+
class Sink : public DataStreamObserver {
15+
public:
16+
bool lastBit;
17+
unsigned char lastByte;
18+
19+
Sink() : DataStreamObserver(false, false) {}
20+
21+
virtual String observerName() const { return "Sink"; }
22+
virtual void onBit(bool val) { lastBit = val; }
23+
virtual void onByte(unsigned char val) { lastByte = val; }
24+
};
25+
26+
class BitpackSink : public DataStreamObserver {
27+
public:
28+
bool lastBit;
29+
unsigned char lastByte;
30+
31+
BitpackSink() : DataStreamObserver(true, true) {}
32+
33+
virtual String observerName() const { return "BitpackSink"; }
34+
virtual void onBit(bool val) { lastBit = val; }
35+
virtual void onByte(unsigned char val) { lastByte = val; }
36+
};
37+
38+
unittest(attach_sink_to_src)
39+
{
40+
Source src = Source();
41+
Sink dst = Sink();
42+
43+
dst.lastByte = 'z';
44+
src.addObserver("foo", &dst);
45+
src.doByte('a');
46+
assertEqual('a', dst.lastByte);
47+
src.removeObserver("foo");
48+
src.doByte('b');
49+
assertEqual('a', dst.lastByte);
50+
}
51+
52+
unittest(attach_src_to_sink)
53+
{
54+
Source src = Source();
55+
Sink dst = Sink();
56+
57+
dst.attach(&src);
58+
src.doByte('f');
59+
assertEqual('f', dst.lastByte);
60+
}
61+
62+
// 01010100 T if bigendian
63+
unittest(bitpack)
64+
{
65+
Source src = Source();
66+
Sink dst = Sink();
67+
BitpackSink bst = BitpackSink();
68+
69+
bool message[8] = {0, 1, 0, 1, 0, 1, 0, 0};
70+
71+
bst.lastByte = 'f';
72+
dst.lastByte = 'f';
73+
bst.attach(&src);
74+
dst.attach(&src);
75+
76+
for (int i = 0; i < 8; ++i) {
77+
src.doBit(message[i]);
78+
assertEqual(message[i], bst.lastBit);
79+
assertEqual(message[i], dst.lastBit);
80+
}
81+
82+
assertEqual('f', dst.lastByte); // not doing bitpacking
83+
assertEqual('T', bst.lastByte); // should have formed a binary T char by now
84+
assertNotEqual('*', bst.lastByte); // backwards endianness
85+
}
86+
87+
// 01010100 T if bigendian
88+
unittest(from_pinhistory)
89+
{
90+
GodmodeState* state = GODMODE();
91+
state->reset();
92+
93+
BitpackSink bst = BitpackSink();
94+
bst.attach(&state->digitalPin[2]);
95+
bst.lastByte = 'f';
96+
97+
bool message[8] = {0, 1, 0, 1, 0, 1, 0, 0};
98+
for (int i = 0; i < 8; ++i) {
99+
digitalWrite(2, message[i]);
100+
assertEqual(message[i], bst.lastBit);
101+
}
102+
103+
assertEqual('T', bst.lastByte); // should have formed a binary T char by now
104+
assertNotEqual('*', bst.lastByte); // backwards endianness
105+
}
106+
107+
unittest_main()

SampleProjects/TestSomething/test/queue.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#include <ArduinoUnitTests.h>
2-
#include <Queue.h>
2+
#include <ci/Queue.h>
33

44
unittest(basic_queue_dequeue_and_size)
55
{

SampleProjects/TestSomething/test/softwareserial.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
#include <ArduinoUnitTests.h>
22
#include <SoftwareSerial.h>
33

4+
bool bigEndian = false;
5+
bool flipLogic = false;
6+
47
unittest(software_input_output)
58
{
69
GodmodeState* state = GODMODE();
710
state->reset();
811

9-
SoftwareSerial ss(1, 2, false);
12+
SoftwareSerial ss(1, 2, flipLogic);
1013

1114
assertEqual(-1, ss.peek());
1215

13-
state->digitalPin[1].fromAscii("Holy crap ", true);
14-
state->digitalPin[1].fromAscii("this took a lot of prep work", true);
16+
state->digitalPin[1].fromAscii("Holy crap ", bigEndian);
17+
state->digitalPin[1].fromAscii("this took a lot of prep work", bigEndian);
1518

1619
assertFalse(ss.isListening());
1720
assertEqual(-1, ss.peek());
@@ -28,17 +31,17 @@ unittest(software_input_output)
2831
ss.write('b');
2932
ss.write('A');
3033
ss.write('r');
31-
assertEqual("bAr", state->digitalPin[2].toAscii(1, true));
34+
assertEqual("bAr", state->digitalPin[2].toAscii(1, bigEndian));
3235
}
3336

3437
unittest(print) {
3538
GodmodeState* state = GODMODE();
3639
state->reset();
3740

38-
SoftwareSerial ss(1, 2, false);
41+
SoftwareSerial ss(1, 2, flipLogic);
3942
ss.listen();
4043
ss.print(1.3, 2);
41-
assertEqual("1.30", state->digitalPin[2].toAscii(1, true));
44+
assertEqual("1.30", state->digitalPin[2].toAscii(1, bigEndian));
4245
}
4346

4447
unittest_main()

0 commit comments

Comments
 (0)