diff --git a/CHANGELOG.md b/CHANGELOG.md index 435e26b4..2bcfd963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +* `release-new-version.sh` script +* outputs for `PinHistory` can now report timestamps +* Fibonacci Clock for clock testing purposes (internal to this library) ### Changed +* Shortened `ArduinoQueue` push and pop operations +* `ci/Queue.h` is now `MockEventQueue.h`, with timing data +* `MockEventQueue::Node` now contains struct `MockEventQueue::Event`, which contains both the templated type `T` and a field for a timestamp. +* Construction of `MockEventQueue` now includes a constructor argument for the time-fetching function +* Construction of `PinHistory` now includes a constructor argument for the time-fetching function +* `PinHistory` can now return an array of timestamps for its events +* `GodmodeState` is now a singleton pattern, which is necessary to support the globality of Arduino functions +* `GodmodeState` now uses timestamped PinHistory for Analog and Digital ### Deprecated ### Removed ### Fixed +* `ArduinoQueue` no longer leaks memory ### Security diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d37e004..7d15efa4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,16 +46,17 @@ ARDUINO_CI_SKIP_RUBY_RSPEC_TESTS=1 bundle exec rspec ## Packaging the Gem * Merge pull request with new features -* `git stash save` (at least before the gem build step, but easiest here). -* `git pull --rebase` -* Update the sections of `CHANGELOG.md` by running `bundle exec keepachangelog_manager.rb --increment-patch` -* Bump the version in lib/arduino_ci/version.rb and change it in README.md (since rubydoc.info doesn't always redirect to the latest version) -* `git add README.md CHANGELOG.md lib/arduino_ci/version.rb` -* `git commit -m "vVERSION bump"` -* `git tag -a vVERSION -m "Released version VERSION"` -* `gem build arduino_ci.gemspec` -* `git stash pop` -* `gem push arduino_ci-VERSION.gem` -* `git push upstream` -* `git push upstream --tags` +* Execute `release-new-version.sh` with the appropriate argument (e.g. `--increment-patch`), which does the following: + * `git stash save` (at least before the gem build step, but easiest here). + * `git pull --rebase` + * Update the sections of `CHANGELOG.md` by running `bundle exec keepachangelog_manager.rb --increment-patch` + * Bump the version in lib/arduino_ci/version.rb and change it in README.md (since rubydoc.info doesn't always redirect to the latest version) + * `git add README.md CHANGELOG.md lib/arduino_ci/version.rb` + * `git commit -m "vVERSION bump"` + * `git tag -a vVERSION -m "Released version VERSION"` + * `gem build arduino_ci.gemspec` + * `git stash pop` + * `gem push arduino_ci-VERSION.gem` + * `git push upstream` + * `git push upstream --tags` * Visit http://www.rubydoc.info/gems/arduino_ci/VERSION to initiate the doc generation process diff --git a/SampleProjects/DoSomething/test/README.md b/SampleProjects/DoSomething/test/README.md new file mode 100644 index 00000000..6e099ec6 --- /dev/null +++ b/SampleProjects/DoSomething/test/README.md @@ -0,0 +1,7 @@ +# Purpose + +These files are designed to test the Ruby gem itself, such that its basic tasks of library installation and compilation can be verified. (i.e., use minimal C++ files -- feature tests for C++ unittest/arduino code belong in `../TestSomething/test/`). + +## Naming convention + +Files in this directory are expected to have names that either contains "bad" if it is expected to fail or "good" if it is expected to pass. This provides a signal to `rspec` for how the code is expected to perform. diff --git a/SampleProjects/TestSomething/test/fibonacciClock.h b/SampleProjects/TestSomething/test/fibonacciClock.h new file mode 100644 index 00000000..fb4d0ddf --- /dev/null +++ b/SampleProjects/TestSomething/test/fibonacciClock.h @@ -0,0 +1,17 @@ +#pragma once + +// fibbonacci clock +unsigned long lastFakeMicros = 1; +unsigned long fakeMicros = 0; + +void resetFibClock() { + lastFakeMicros = 1; + fakeMicros = 0; +} + +unsigned long fibMicros() { + unsigned long ret = lastFakeMicros + fakeMicros; + lastFakeMicros = fakeMicros; + fakeMicros = ret; + return ret; +} diff --git a/SampleProjects/TestSomething/test/fibonacciclock.cpp b/SampleProjects/TestSomething/test/fibonacciclock.cpp new file mode 100644 index 00000000..d805ec50 --- /dev/null +++ b/SampleProjects/TestSomething/test/fibonacciclock.cpp @@ -0,0 +1,25 @@ +#include +#include +#include "fibonacciClock.h" + +unittest(my_fib_clock) +{ + resetFibClock(); + assertEqual(1, fibMicros()); + assertEqual(1, fibMicros()); + assertEqual(2, fibMicros()); + assertEqual(3, fibMicros()); + assertEqual(5, fibMicros()); + assertEqual(8, fibMicros()); + assertEqual(13, fibMicros()); + assertEqual(21, fibMicros()); + + // and again + resetFibClock(); + assertEqual(1, fibMicros()); + assertEqual(1, fibMicros()); + assertEqual(2, fibMicros()); +} + + +unittest_main() diff --git a/SampleProjects/TestSomething/test/godmode.cpp b/SampleProjects/TestSomething/test/godmode.cpp index e07bc13b..e6c69502 100644 --- a/SampleProjects/TestSomething/test/godmode.cpp +++ b/SampleProjects/TestSomething/test/godmode.cpp @@ -1,15 +1,15 @@ #include #include +#include "fibonacciClock.h" GodmodeState* state = GODMODE(); -unittest_setup() -{ +unittest_setup() { + resetFibClock(); state->reset(); } -unittest(millis_micros_and_delay) -{ +unittest(millis_micros_and_delay) { assertEqual(0, millis()); assertEqual(0, micros()); delay(3); @@ -20,8 +20,7 @@ unittest(millis_micros_and_delay) assertEqual(14000, micros()); } -unittest(random) -{ +unittest(random) { randomSeed(1); assertEqual(state->seed, 1); @@ -37,8 +36,7 @@ unittest(random) assertEqual(state->seed, 4294967282); } -unittest(pins) -{ +unittest(pins) { pinMode(1, OUTPUT); // this is a no-op in unit tests. it's just here to prove compilation digitalWrite(1, HIGH); assertEqual(HIGH, state->digitalPin[1]); @@ -64,8 +62,7 @@ unittest(pins) assertEqual(56, analogRead(1)); } -unittest(pin_read_history) -{ +unittest(pin_read_history) { int future[6] = {33, 22, 55, 11, 44, 66}; state->analogPin[1].fromArray(future, 6); for (int i = 0; i < 6; ++i) @@ -88,20 +85,20 @@ unittest(pin_read_history) } } -unittest(pin_write_history) -{ +unittest(digital_pin_write_history_with_timing) { int numMoved; + bool expectedD[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH}; + bool actualD[6]; + unsigned long expectedT[6] = {0, 1, 1, 2, 3, 5}; + unsigned long actualT[6]; - // history for digital pin - digitalWrite(1, HIGH); - digitalWrite(1, LOW); - digitalWrite(1, LOW); - digitalWrite(1, HIGH); - digitalWrite(1, HIGH); + // history for digital pin. start from 1 since LOW is the initial value + for (int i = 1; i < 6; ++i) { + state->micros = fibMicros(); + digitalWrite(1, expectedD[i]); + } assertEqual(6, state->digitalPin[1].historySize()); - bool expectedD[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH}; - bool actualD[6]; numMoved = state->digitalPin[1].toArray(actualD, 6); assertEqual(6, numMoved); // assert non-destructive @@ -113,6 +110,19 @@ unittest(pin_write_history) assertEqual(expectedD[i], actualD[i]); } + numMoved = state->digitalPin[1].toTimestampArray(actualT, 6); + assertEqual(6, numMoved); + for (int i = 0; i < numMoved; ++i) + { + assertEqual(expectedT[i], actualT[i]); + } +} + +unittest(analog_pin_write_history) { + int numMoved; + int expectedA[6] = {0, 11, 22, 33, 44, 55}; + int actualA[6]; + // history for analog pin analogWrite(1, 11); analogWrite(1, 22); @@ -121,8 +131,7 @@ unittest(pin_write_history) analogWrite(1, 55); assertEqual(6, state->analogPin[1].historySize()); - int expectedA[6] = {0, 11, 22, 33, 44, 55}; - int actualA[6]; + numMoved = state->analogPin[1].toArray(actualA, 6); assertEqual(6, numMoved); // assert non-destructive @@ -133,7 +142,9 @@ unittest(pin_write_history) { assertEqual(expectedA[i], actualA[i]); } +} +unittest(ascii_pin_write_history) { // digitial history as serial data, big-endian bool binaryAscii[24] = { 0, 1, 0, 1, 1, 0, 0, 1, @@ -207,8 +218,7 @@ unittest(spi) { } } - unittest(does_nothing_if_no_data) - { + unittest(does_nothing_if_no_data) { int myPin = 3; state->serialPort[0].dataIn = ""; state->serialPort[0].dataOut = ""; @@ -218,8 +228,7 @@ unittest(spi) { assertEqual("", state->serialPort[0].dataOut); } - unittest(keeps_pin_low_and_acks) - { + unittest(keeps_pin_low_and_acks) { int myPin = 3; state->serialPort[0].dataIn = "0"; state->serialPort[0].dataOut = ""; @@ -230,8 +239,7 @@ unittest(spi) { assertEqual("Ack 3 0", state->serialPort[0].dataOut); } - unittest(flips_pin_high_and_acks) - { + unittest(flips_pin_high_and_acks) { int myPin = 3; state->serialPort[0].dataIn = "1"; state->serialPort[0].dataOut = ""; @@ -242,8 +250,7 @@ unittest(spi) { assertEqual("Ack 3 1", state->serialPort[0].dataOut); } - unittest(two_flips) - { + unittest(two_flips) { int myPin = 3; state->serialPort[0].dataIn = "10junk"; state->serialPort[0].dataOut = ""; diff --git a/SampleProjects/TestSomething/test/pinhistory.cpp b/SampleProjects/TestSomething/test/pinhistory.cpp index 183fe3e3..8f1687e2 100644 --- a/SampleProjects/TestSomething/test/pinhistory.cpp +++ b/SampleProjects/TestSomething/test/pinhistory.cpp @@ -1,9 +1,9 @@ #include #include +#include "fibonacciClock.h" - -unittest(pin_read_history) { - PinHistory phi; +unittest(pin_read_history_int) { + PinHistory phi; // pin history int const int future[6] = {33, 22, 55, 11, 44, 66}; assertEqual(0, phi.queueSize()); @@ -16,8 +16,10 @@ unittest(pin_read_history) { // assert end of history works assertEqual(future[5], phi.retrieve()); +} - PinHistory phb; +unittest(pin_read_history_bool_to_ascii) { + PinHistory phb; // pin history bool phb.fromAscii("Yo", true); // digitial history as serial data, big-endian @@ -32,7 +34,19 @@ unittest(pin_read_history) { assertEqual("Yo", phb.toAscii(0, true)); } -unittest(ascii_stuff) { +unittest(assignment_dumps_queue) { + PinHistory phb; // pin history bool + assertEqual(0, phb.queueSize()); + assertEqual(0, phb.historySize()); + phb.fromAscii("Yo", true); + assertEqual(16, phb.queueSize()); + assertEqual(0, phb.historySize()); + phb = false; + assertEqual(0, phb.queueSize()); + assertEqual(1, phb.historySize()); +} + +unittest(ascii_to_bool_and_back) { PinHistory phb; assertEqual(0, phb.historySize()); phb.reset(false); @@ -50,4 +64,106 @@ unittest(ascii_stuff) { assertEqual("hi", phb.toAscii(1, true)); } +unittest(write_history) { + PinHistory phi; // pin history int + int expectedA[6] = {0, 11, 22, 33, 44, 55}; + for (int i = 0; i < 6; ++i) + { + assertEqual(i, phi.historySize()); + phi = expectedA[i]; + assertEqual(i + 1, phi.historySize()); + assertEqual(0, phi.queueSize()); + assertEqual(phi, expectedA[i]); + } + + int actualA[6]; + int numMoved = phi.toArray(actualA, 6); + assertEqual(6, numMoved); + // assert non-destructive by repeating the operation + numMoved = phi.toArray(actualA, 6); + assertEqual(6, numMoved); + for (int i = 0; i < 6; ++i) + { + assertEqual(expectedA[i], actualA[i]); + } +} + +unittest(null_timing) { + PinHistory phi; // pin history int + int expectedA[6] = {0, 11, 22, 33, 44, 55}; + for (int i = 0; i < 6; ++i) + { + phi = expectedA[i]; + } + + unsigned long tStamps[6]; + int numMoved = phi.toTimestampArray(tStamps, 6); + assertEqual(6, numMoved); + // assert non-destructive by repeating the operation + numMoved = phi.toTimestampArray(tStamps, 6); + assertEqual(6, numMoved); + for (int i = 0; i < 6; ++i) + { + assertEqual(0, tStamps[i]); + } +} + +unittest(actual_timing_set_in_constructor) { + resetFibClock(); + PinHistory phi(fibMicros); // pin history int + for (int i = 0; i < 6; ++i) + { + phi = 0; + } + + int expectedT[6] = {1, 1, 2, 3, 5, 8}; + unsigned long tStamps[6]; + int numMoved = phi.toTimestampArray(tStamps, 6); + assertEqual(6, numMoved); + for (int i = 0; i < 6; ++i) + { + assertEqual(expectedT[i], tStamps[i]); + } +} + +unittest(actual_timing_set_after_constructor) { + resetFibClock(); + PinHistory phi; // pin history int + phi.setMicrosRetriever(fibMicros); + for (int i = 0; i < 6; ++i) + { + phi = 0; + } + + int expectedT[6] = {1, 1, 2, 3, 5, 8}; + unsigned long tStamps[6]; + int numMoved = phi.toTimestampArray(tStamps, 6); + assertEqual(6, numMoved); + for (int i = 0; i < 6; ++i) + { + assertEqual(expectedT[i], tStamps[i]); + } +} + +unittest(event_history) { + resetFibClock(); + PinHistory phi(fibMicros); // pin history int + for (int i = 0; i < 6; ++i) + { + phi = i; + } + + int expectedT[6] = {1, 1, 2, 3, 5, 8}; + MockEventQueue::Event event[6]; + int numMoved = phi.toEventArray(event, 6); + assertEqual(6, numMoved); + for (int i = 0; i < 6; ++i) + { + assertEqual(i, event[i].data); + assertEqual(expectedT[i], event[i].micros); + } +} + + + unittest_main() diff --git a/SampleProjects/TestSomething/test/queue.cpp b/SampleProjects/TestSomething/test/queue.cpp index 275bbb9b..f1828907 100644 --- a/SampleProjects/TestSomething/test/queue.cpp +++ b/SampleProjects/TestSomething/test/queue.cpp @@ -1,9 +1,10 @@ #include -#include +#include +#include "fibonacciClock.h" unittest(basic_queue_dequeue_and_size) { - ArduinoCIQueue q; + MockEventQueue q; int data[5] = {11, 22, 33, 44, 55}; assertTrue(q.empty()); @@ -11,13 +12,14 @@ unittest(basic_queue_dequeue_and_size) for (int i = 0; i < 5; ++i) { assertEqual(i, q.size()); q.push(data[i]); - assertEqual(data[i], q.back()); + assertEqual(data[i], q.backData()); + assertEqual(0, q.backTime()); // we didn't provide a function, so it should default to 0 assertEqual(i + 1, q.size()); } for (int i = 0; i < 5; ++i) { assertEqual(5 - i, q.size()); - assertEqual(data[i], q.front()); + assertEqual(data[i], q.frontData()); q.pop(); assertEqual(5 - i - 1, q.size()); } @@ -27,22 +29,22 @@ unittest(basic_queue_dequeue_and_size) unittest(copy_constructor) { - ArduinoCIQueue q; + MockEventQueue q; int data[5] = {11, 22, 33, 44, 55}; for (int i = 0; i < 5; ++i) q.push(data[i]); - ArduinoCIQueue q2(q); + MockEventQueue q2(q); for (int i = 0; i < 5; ++i) { assertEqual(5 - i, q2.size()); - assertEqual(data[i], q2.front()); + assertEqual(data[i], q2.frontData()); q2.pop(); assertEqual(5 - i - 1, q2.size()); } for (int i = 0; i < 5; ++i) { assertEqual(5 - i, q.size()); - assertEqual(data[i], q.front()); + assertEqual(data[i], q.frontData()); q.pop(); assertEqual(5 - i - 1, q.size()); } @@ -50,7 +52,7 @@ unittest(copy_constructor) unittest(boundaries) { - ArduinoCIQueue q; + MockEventQueue q; int data[2] = {11, 22}; for (int i = 0; i < 2; ++i) q.push(data[i]); @@ -64,4 +66,41 @@ unittest(boundaries) } +unittest(timed_events) +{ + MockEventQueue q; + int data[7] = {4, 50, 600, 8555, 9000, 9001, 1000000000}; + for (int i = 0; i < 7; ++i) { + q.push(data[i], data[i]); + assertEqual(data[i], q.backData()); + assertEqual(data[i], q.backTime()); + } + + for (int i = 0; i < 7; ++i) { + assertEqual(data[i], q.frontData()); + assertEqual(data[i], q.frontTime()); + q.pop(); + } + +} + +unittest(clocked_events) +{ + resetFibClock(); + MockEventQueue q(fibMicros); + int data[7] = {1, 1, 2, 3, 5, 8, 13}; //eureka + for (int i = 0; i < 7; ++i) { + q.push(data[i]); + assertEqual(data[i], q.backData()); + assertEqual(data[i], q.backTime()); + } + + for (int i = 0; i < 7; ++i) { + assertEqual(data[i], q.frontData()); + assertEqual(data[i], q.frontTime()); + q.pop(); + } + +} + unittest_main() diff --git a/arduino_ci.gemspec b/arduino_ci.gemspec index 769b77f4..9b13c280 100644 --- a/arduino_ci.gemspec +++ b/arduino_ci.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |spec| spec.add_dependency "rubyzip", "~> 1.2" spec.add_development_dependency "bundler", "~> 1.15" - spec.add_development_dependency "keepachangelog_manager", "~> 0.0.1" + spec.add_development_dependency "keepachangelog_manager", "~> 0.0.2" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency 'rubocop', '~>0.59.0' spec.add_development_dependency 'yard', '~>0.9.11' diff --git a/cpp/arduino/Godmode.cpp b/cpp/arduino/Godmode.cpp index 7d84e2b8..b964b7e4 100644 --- a/cpp/arduino/Godmode.cpp +++ b/cpp/arduino/Godmode.cpp @@ -2,37 +2,45 @@ #include "HardwareSerial.h" #include "SPI.h" -GodmodeState godmode = GodmodeState(); - GodmodeState* GODMODE() { - return &godmode; + return GodmodeState::getInstance(); +} + +GodmodeState* GodmodeState::instance = nullptr; + +GodmodeState* GodmodeState::getInstance() +{ + if (instance == nullptr) + { + instance = new GodmodeState(); + for (int i = 0; i < MOCK_PINS_COUNT; ++i) { + instance->digitalPin[i].setMicrosRetriever(&GodmodeState::getMicros); + instance->analogPin[i].setMicrosRetriever(&GodmodeState::getMicros); + } + } + + return instance; } unsigned long millis() { - GodmodeState* godmode = GODMODE(); - return godmode->micros / 1000; + return GODMODE()->micros / 1000; } unsigned long micros() { - GodmodeState* godmode = GODMODE(); - return godmode->micros; + return GODMODE()->micros; } void delay(unsigned long millis) { - GodmodeState* godmode = GODMODE(); - godmode->micros += millis * 1000; + GODMODE()->micros += millis * 1000; } void delayMicroseconds(unsigned long micros) { - GodmodeState* godmode = GODMODE(); - godmode->micros += micros; + GODMODE()->micros += micros; } - void randomSeed(unsigned long seed) { - GodmodeState* godmode = GODMODE(); - godmode->seed = seed; + GODMODE()->seed = seed; } long random(long vmax) @@ -81,16 +89,16 @@ void detachInterrupt(uint8_t interrupt) { // Serial ports #if defined(HAVE_HWSERIAL0) - HardwareSerial Serial(&godmode.serialPort[0].dataIn, &godmode.serialPort[0].dataOut, &godmode.serialPort[0].readDelayMicros); + HardwareSerial Serial(&GODMODE()->serialPort[0].dataIn, &GODMODE()->serialPort[0].dataOut, &GODMODE()->serialPort[0].readDelayMicros); #endif #if defined(HAVE_HWSERIAL1) - HardwareSerial Serial1(&godmode.serialPort[1].dataIn, &godmode.serialPort[1].dataOut, &godmode.serialPort[1].readDelayMicros); + HardwareSerial Serial1(&GODMODE()->serialPort[1].dataIn, &GODMODE()->serialPort[1].dataOut, &GODMODE()->serialPort[1].readDelayMicros); #endif #if defined(HAVE_HWSERIAL2) - HardwareSerial Serial2(&godmode.serialPort[2].dataIn, &godmode.serialPort[2].dataOut, &godmode.serialPort[2].readDelayMicros); + HardwareSerial Serial2(&GODMODE()->serialPort[2].dataIn, &GODMODE()->serialPort[2].dataOut, &GODMODE()->serialPort[2].readDelayMicros); #endif #if defined(HAVE_HWSERIAL3) - HardwareSerial Serial3(&godmode.serialPort[3].dataIn, &godmode.serialPort[3].dataOut, &godmode.serialPort[3].readDelayMicros); + HardwareSerial Serial3(&GODMODE()->serialPort[3].dataIn, &GODMODE()->serialPort[3].dataOut, &GODMODE()->serialPort[3].readDelayMicros); #endif template @@ -100,4 +108,4 @@ inline std::ostream& operator << ( std::ostream& out, const PinHistory& ph ) } // defined in SPI.h -SPIClass SPI = SPIClass(&godmode.spi.dataIn, &godmode.spi.dataOut); +SPIClass SPI = SPIClass(&GODMODE()->spi.dataIn, &GODMODE()->spi.dataOut); diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index b50f99d2..12fa1b51 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -16,7 +16,6 @@ void delayMicroseconds(unsigned long micros); unsigned long millis(); unsigned long micros(); - #define MOCK_PINS_COUNT 256 #if defined(UBRR3H) @@ -32,16 +31,19 @@ unsigned long micros(); #endif class GodmodeState { - struct PortDef { - String dataIn; - String dataOut; - unsigned long readDelayMicros; - }; + private: + struct PortDef { + String dataIn; + String dataOut; + unsigned long readDelayMicros; + }; + + struct InterruptDef { + bool attached; + uint8_t mode; + }; - struct InterruptDef { - bool attached; - uint8_t mode; - }; + static GodmodeState* instance; public: unsigned long micros; @@ -98,8 +100,24 @@ class GodmodeState { return NUM_SERIAL_PORTS; } - GodmodeState() - { + // Using this for anything other than unit testing arduino_ci itself + // is unsupported at the moment + void overrideClockTruth(unsigned long (*getMicros)(void)) { + } + + // singleton pattern + static GodmodeState* getInstance(); + + static unsigned long getMicros() { + return instance->micros; + } + + // C++ 11, declare as public for better compiler error messages + GodmodeState(GodmodeState const&) = delete; + void operator=(GodmodeState const&) = delete; + + private: + GodmodeState() { reset(); } }; @@ -123,4 +141,3 @@ inline void noTone(uint8_t _pin) {} GodmodeState* GODMODE(); - diff --git a/cpp/arduino/MockEventQueue.h b/cpp/arduino/MockEventQueue.h new file mode 100644 index 00000000..31119f32 --- /dev/null +++ b/cpp/arduino/MockEventQueue.h @@ -0,0 +1,86 @@ +#pragma once + +template +class MockEventQueue { + public: + struct Event { + T data; + unsigned long micros; + + Event() : data(T()), micros(0) {} + Event(const T &d, unsigned long const t) : data(d), micros(t) { } + }; + + private: + struct Node { + Event event; + Node* next; + + Node(const Event &e, Node* n) : event(e), next(n) { } + }; + + Node* mFront; + Node* mBack; + unsigned long mSize; + T mNil; + unsigned long (*mGetMicros)(void); + + void init(unsigned long (*getMicros)(void)) { + mFront = mBack = nullptr; + mSize = 0; + mGetMicros = getMicros; + } + + public: + MockEventQueue(unsigned long (*getMicros)(void)): mNil() { init(getMicros); } + MockEventQueue(): mNil() { init(nullptr); } + + MockEventQueue(const MockEventQueue& q) { + init(q.mGetMicros); + for (Node* n = q.mFront; n; n = n->next) push(n->event); + } + + void setMicrosRetriever(unsigned long (*getMicros)(void)) { mGetMicros = getMicros; } + + inline unsigned long size() const { return mSize; } + inline bool empty() const { return 0 == mSize; } + inline Event front() const { return empty() ? Event(mNil, 0) : mFront->event; } + inline Event back() const { return empty() ? Event(mNil, 0) : mBack->event; } + inline T frontData() const { return front().data; } + inline T backData() const { return back().data; } + inline unsigned long frontTime() const { return front().micros; } + inline unsigned long backTime() const { return back().micros; } + + + // fully formed event + bool push(const Event& e) { + Node *n = new Node(e, nullptr); + if (n == nullptr) return false; + mBack = (mFront == nullptr ? mFront : mBack->next) = n; + return ++mSize; + } + + // fully specfied event + bool push(const T& v, unsigned long const time) { + Event e = {v, time}; + return push(e); + } + + // event needing timestamp + bool push(const T& v) { + unsigned long micros = mGetMicros == nullptr ? 0 : mGetMicros(); + return push(v, micros); + } + + void pop() { + if (empty()) return; + Node* n = mFront; + mFront = mFront->next; + delete n; + if (--mSize == 0) mBack = nullptr; + } + + void clear() { while (!empty()) pop(); } + + ~MockEventQueue() { clear(); } +}; diff --git a/cpp/arduino/PinHistory.h b/cpp/arduino/PinHistory.h index 6fadebce..39cdfcd1 100644 --- a/cpp/arduino/PinHistory.h +++ b/cpp/arduino/PinHistory.h @@ -1,5 +1,5 @@ #pragma once -#include "ci/Queue.h" +#include "MockEventQueue.h" #include "ci/ObservableDataStream.h" #include "WString.h" @@ -7,8 +7,8 @@ template class PinHistory : public ObservableDataStream { private: - ArduinoCIQueue qIn; - ArduinoCIQueue qOut; + MockEventQueue qIn; + MockEventQueue qOut; void clear() { qOut.clear(); @@ -16,14 +16,14 @@ class PinHistory : public ObservableDataStream { } // enqueue ascii bits - void a2q(ArduinoCIQueue &q, String input, bool bigEndian, bool advertise) { + void a2q(MockEventQueue &q, String input, bool bigEndian, bool advertise) { // 8 chars at a time, form up for (int j = 0; j < input.length(); ++j) { for (int i = 0; i < 8; ++i) { int shift = bigEndian ? 7 - i : i; unsigned char mask = (0x01 << shift); q.push(mask & input[j]); - if (advertise) advertiseBit(q.back()); // not valid for all possible types but whatever + if (advertise) advertiseBit(q.backData()); // not valid for all possible types but whatever } } } @@ -31,10 +31,10 @@ class PinHistory : public ObservableDataStream { // convert a queue to a string as if it was serial bits // start from offset, consider endianness - String q2a(const ArduinoCIQueue &q, unsigned int offset, bool bigEndian) const { + String q2a(const MockEventQueue &q, unsigned int offset, bool bigEndian) const { String ret = ""; - ArduinoCIQueue q2(q); + MockEventQueue q2(q); while (offset) { q2.pop(); @@ -48,7 +48,7 @@ class PinHistory : public ObservableDataStream { unsigned char acc = 0x00; for (int i = 0; i < 8; ++i) { int shift = bigEndian ? 7 - i : i; - T val = q2.front(); + T val = q2.frontData(); unsigned char bit = val ? 0x1 : 0x0; acc |= (bit << shift); q2.pop(); @@ -59,15 +59,25 @@ class PinHistory : public ObservableDataStream { return ret; } + void init() { + asciiEncodingOffsetIn = 0; // default is sensible + asciiEncodingOffsetOut = 1; // default is sensible + } + public: unsigned int asciiEncodingOffsetIn; unsigned int asciiEncodingOffsetOut; + PinHistory(unsigned long (*getMicros)(void)) : ObservableDataStream(), qOut(getMicros) { + init(); + } + PinHistory() : ObservableDataStream() { - asciiEncodingOffsetIn = 0; // default is sensible - asciiEncodingOffsetOut = 1; // default is sensible + init(); } + void setMicrosRetriever(unsigned long (*getMicros)(void)) { qOut.setMicrosRetriever(getMicros); } + void reset(T val) { clear(); qOut.push(val); @@ -79,8 +89,8 @@ class PinHistory : public ObservableDataStream { // This returns the "value" of the pin in a raw sense operator T() const { - if (!qIn.empty()) return qIn.front(); - return qOut.back(); + if (!qIn.empty()) return qIn.frontData(); + return qOut.backData(); } // this sets the value of the pin authoritatively @@ -89,8 +99,8 @@ class PinHistory : public ObservableDataStream { T operator=(const T& i) { qIn.clear(); qOut.push(i); - advertiseBit(qOut.back()); // not valid for all possible types but whatever - return qOut.back(); + advertiseBit(qOut.backData()); // not valid for all possible types but whatever + return qOut.backData(); } // This returns the "value" of the pin according to the queued values @@ -98,14 +108,14 @@ class PinHistory : public ObservableDataStream { // then take the latest output. T retrieve() { if (!qIn.empty()) { - T hack_required_by_travis_ci = qIn.front(); + T hack_required_by_travis_ci = qIn.frontData(); qIn.pop(); qOut.push(hack_required_by_travis_ci); } - return qOut.back(); + return qOut.backData(); } - // enqueue a set of elements + // enqueue a set of data elements void fromArray(T const * const arr, unsigned int length) { for (int i = 0; i < length; ++i) qIn.push(arr[i]); } @@ -124,18 +134,48 @@ class PinHistory : public ObservableDataStream { // start from offset, consider endianness String incomingToAscii(bool bigEndian) const { return incomingToAscii(asciiEncodingOffsetIn, bigEndian); } - // convert the pin history to a string as if it was Serial comms + // convert the pin history data to a string as if it was Serial comms // start from offset, consider endianness String toAscii(unsigned int offset, bool bigEndian) const { return q2a(qOut, offset, bigEndian); } - // convert the pin history to a string as if it was Serial comms + // convert the pin history data to a string as if it was Serial comms // start from offset, consider endianness String toAscii(bool bigEndian) const { return toAscii(asciiEncodingOffsetOut, bigEndian); } - // copy elements to an array, up to a given length + // copy data elements to an array, up to a given length // return the number of elements moved int toArray (T* arr, unsigned int length) const { - ArduinoCIQueue q2(qOut); + MockEventQueue q2(qOut); // preserve const by copying + + int ret = 0; + for (int i = 0; i < length && q2.size(); ++i) { + arr[i] = q2.frontData(); + q2.pop(); + ++ret; + } + return ret; + } + + // copy pin history timing to an array, up to a given length. + // note that this records times between calls to the pin, not between transitions + // return the number of elements moved + int toTimestampArray(unsigned long* arr, unsigned int length) const { + MockEventQueue q2(qOut); // preserve const by copying + + int ret = 0; + for (int i = 0; i < length && q2.size(); ++i) { + arr[i] = q2.frontTime(); + q2.pop(); + ++ret; + } + return ret; + } + + // copy pin history timing to an array, up to a given length. + // note that this records times between calls to the pin, not between transitions + // return the number of elements moved + int toEventArray(typename MockEventQueue::Event* arr, unsigned int length) const { + MockEventQueue q2(qOut); // preserve const by copying int ret = 0; for (int i = 0; i < length && q2.size(); ++i) { @@ -146,12 +186,12 @@ class PinHistory : public ObservableDataStream { return ret; } - // see if the array matches the elements in the queue + // see if the array matches the data of the elements in the queue bool hasElements (T const * const arr, unsigned int length) const { int i; - ArduinoCIQueue q2(qOut); + MockEventQueue q2(qOut); // preserve const by copying for (i = 0; i < length && q2.size(); ++i) { - if (q2.front() != arr[i]) return false; + if (q2.frontData() != arr[i]) return false; q2.pop(); } return i == length; diff --git a/cpp/arduino/ci/Queue.h b/cpp/arduino/ci/Queue.h deleted file mode 100644 index bbd9f7f4..00000000 --- a/cpp/arduino/ci/Queue.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -template -class ArduinoCIQueue { - private: - struct Node { - T data; - Node* next; - }; - - Node* mFront; - Node* mBack; - unsigned long mSize; - T mNil; - - void init() { - mFront = mBack = NULL; - mSize = 0; - } - - public: - ArduinoCIQueue(): mNil() { init(); } - - ArduinoCIQueue(const ArduinoCIQueue& q) { - init(); - for (Node* n = q.mFront; n; n = n->next) push(n->data); - } - - inline unsigned long size() const { return mSize; } - - inline bool empty() const { return 0 == mSize; } - - T front() const { return empty() ? mNil : mFront->data; } - - T back() const { return empty() ? mNil : mBack->data; } - - bool push(const T& v) - { - Node *n = new Node; - if (n == NULL) return false; - - n->data = v; - n->next = NULL; - - if (mFront == NULL) - { - mFront = mBack = n; - } else { - mBack->next = n; - mBack = n; - } - - ++mSize; - return true; - } - - void pop() { - if (empty()) return; - if (mFront == mBack) { - mFront = mBack = NULL; - } else { - Node* n = mFront; - mFront = mFront->next; - delete n; - } - - --mSize; - } - - void clear() { while (!empty()) pop(); } - - ~ArduinoCIQueue() { clear(); } -}; diff --git a/release-new-version.sh b/release-new-version.sh new file mode 100644 index 00000000..a51ce976 --- /dev/null +++ b/release-new-version.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# This script automates the gem release project for this repo. + +GEM_NAME=arduino_ci +GEM_MODULE=ArduinoCI +PUSH_REMOTE=upstream + +# test if we have an arguments on the command line +if [ $# -lt 1 ] +then + echo "You must pass an argument for KeepAChangelogManager:" + bundle exec keepachangelog_manager.rb + exit 1 +fi + +# set up a cleanup function for any errors, so that we git stash pop +cleanup () { + set +x +e + echo -e "\n### Reverting uncommitted changes" + git checkout README.md CHANGELOG.md lib/$GEM_NAME/version.rb + if [ $DID_STASH -eq 0 ]; then + echo -e "\n### Unstashing changes" + git stash pop + fi + exit $1 +} + +DIDNT_STASH="No local changes to save" +DID_STASH=1 +echo -ne "\n### Stashing changes..." +STASH_OUTPUT=$(git stash save) +[ "$DIDNT_STASH" != "$STASH_OUTPUT" ] +DID_STASH=$? +echo DID_STASH=$DID_STASH + +trap "cleanup 1" INT TERM ERR +set -xe + +echo "### Checking existence of specified git push destination '$PUSH_REMOTE'" +git remote get-url $PUSH_REMOTE + +# ensure latest master +git pull --rebase + +# update version in changelog and save it +NEW_VERSION=$(bundle exec keepachangelog_manager.rb $@) + +echo "Checking whether new version string is a semver" +echo $NEW_VERSION | grep -Eq ^[0-9]*\.[0-9]*\.[0-9]*$ + +# write version.rb with new version +cat << EOF > lib/$GEM_NAME/version.rb +module $GEM_MODULE + VERSION = "$NEW_VERSION".freeze +end +EOF + +# update README with new version +sed -e "s/\/gems\/$GEM_NAME\/[0-9]*\.[0-9]*\.[0-9]*)/\/gems\/$GEM_NAME\/$NEW_VERSION)/" -i "" README.md + +# mutation! +git add README.md CHANGELOG.md lib/$GEM_NAME/version.rb +git commit -m "v$NEW_VERSION bump" +git tag -a v$NEW_VERSION -m "Released version $NEW_VERSION" +gem build $GEM_NAME.gemspec +gem push $GEM_NAME-$NEW_VERSION.gem +git push $PUSH_REMOTE +git push $PUSH_REMOTE --tags +git fetch + +# do normal cleanup +cleanup 0