From c6f4d733512a42820cb42c7156e102634707612c Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 15:49:29 -0500 Subject: [PATCH 01/16] Enable custom installation scripts during CI --- CHANGELOG.md | 3 ++- REFERENCE.md | 5 +++++ exe/arduino_ci.rb | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 019627b9..6fdbf56f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- Environment variable to run a custom initialization script during CI testing: `CUSTOM_INIT_SCRIPT` ### Changed @@ -22,7 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [1.1.0] - 2020-12-02 ### Added - `ensure_arduino_installation.rb` now ensures the existence of the library directory as well -- Environment variables to escalate unit tests or examples not being found during CI testing +- Environment variables to escalate unit tests or examples not being found during CI testing - `EXPECT_EXAMPLES` and `EXPECT_UNITTESTS` ### Changed - Conserve CI testing minutes by grouping CI into fewer runs diff --git a/REFERENCE.md b/REFERENCE.md index 6ae67bdc..7e1dbe7e 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -39,6 +39,11 @@ This allows a file (or glob) pattern to be executed in your tests directory, cre 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. +### `CUSTOM_INIT_SCRIPT` environment variable + +If set, testing will execute (using `/bin/sh`) the script referred to by this variable -- relative to the current working directory. 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. + + ### `EXPECT_UNITTESTS` environment variable 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. diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index a1142cd8..98dd700a 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -5,8 +5,9 @@ require 'optparse' WIDTH = 80 -VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze -VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze +VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze +VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze +VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze @failure_count = 0 @passfail = proc { |result| result ? "✓" : "✗" } @@ -51,6 +52,8 @@ def self.parse(options) puts opts puts puts "Additionally, the following environment variables control the script:" + puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory" + puts " prior to any automated library installation or testing (e.g. to install unoffical libraries)" puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present" puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present" exit @@ -68,7 +71,7 @@ def self.parse(options) # terminate after printing any debug info. TODO: capture debug info def terminate(final = nil) puts "Failures: #{@failure_count}" - unless @failure_count.zero? || final + unless @failure_count.zero? || final || @backend.nil? puts "Last message: #{@backend.last_msg}" puts "========== Stdout:" puts @backend.last_out @@ -277,6 +280,30 @@ def get_annotated_compilers(config, cpp_library) compilers end +# Handle existence or nonexistence of custom initialization script -- run it if you have it +# +# This feature is to drive GitHub actions / docker image installation where the container is +# in a clean-slate state but needs some way to have custom library versions injected into it. +# In this case, the user provided script would fetch a git repo or some other method +def perform_custom_initialization(_config) + script_path = ENV[VAR_CUSTOM_INIT_SCRIPT] + inform("Environment variable #{VAR_CUSTOM_INIT_SCRIPT}") { "'#{script_path}'" } + return if script_path.nil? + return if script_path.empty? + + script_pathname = Pathname.getwd + script_path + assure("Script at #{VAR_CUSTOM_INIT_SCRIPT} exists") { script_pathname.exist? } + + assure_multiline("Running #{script_pathname} with sh in libraries working dir") do + Dir.chdir(@backend.lib_dir) do + IO.popen(["/bin/sh", script_pathname.to_s], err: [:child, :out]) do |io| + io.each_line { |line| puts " #{line}" } + end + end + end +end + +# Unit test procedure def perform_unit_tests(cpp_library, file_config) if @cli_options[:skip_unittests] inform("Skipping unit tests") { "as requested via command line" } @@ -394,6 +421,9 @@ def perform_example_compilation_tests(cpp_library, config) @backend = ArduinoCI::ArduinoInstallation.autolocate! inform("Located arduino-cli binary") { @backend.binary_path.to_s } +# run any library init scripts from the library itself. +perform_custom_initialization(config) + # initialize library under test cpp_library_path = Pathname.new(".") cpp_library = assure("Installing library under test") do From 4b4e6ddd84e6d99747b33194cd2b4b80aa8f9edc Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sat, 26 Dec 2020 22:29:39 -0500 Subject: [PATCH 02/16] fix typo --- .github/workflows/linux.yaml | 2 +- .github/workflows/macos.yaml | 2 +- .github/workflows/windows.yaml | 2 +- REFERENCE.md | 2 +- SampleProjects/TestSomething/test/godmode.cpp | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index f8e6a5d1..054bdb0a 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -11,7 +11,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - name: Check style, funcionality, and usage + - name: Check style, functionality, and usage run: | g++ -v bundle install diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 4e426e88..e69ded2b 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -11,7 +11,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - name: Check style, funcionality, and usage + - name: Check style, functionality, and usage run: | g++ -v bundle install diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 2bb9d3d7..6d7e03a7 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -11,7 +11,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - name: Check style, funcionality, and usage + - name: Check style, functionality, and usage run: | g++ -v bundle install diff --git a/REFERENCE.md b/REFERENCE.md index 7e1dbe7e..9116b963 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -332,7 +332,7 @@ unittest(pin_history) // we expect 6 values in that queue (5 that we set plus one // initial value), which we'll hard-code here for convenience. // (we'll actually assert those 6 values in the next block) - assertEqual(6, state->digitalPin[1].queueSize)); + assertEqual(6, state->digitalPin[1].queueSize()); bool expected[6] = {LOW, HIGH, LOW, LOW, HIGH, HIGH}; bool actual[6]; diff --git a/SampleProjects/TestSomething/test/godmode.cpp b/SampleProjects/TestSomething/test/godmode.cpp index 6e57d9d6..f7da6a36 100644 --- a/SampleProjects/TestSomething/test/godmode.cpp +++ b/SampleProjects/TestSomething/test/godmode.cpp @@ -146,7 +146,7 @@ unittest(analog_pin_write_history) { } unittest(ascii_pin_write_history) { - // digitial history as serial data, big-endian + // digital history as serial data, big-endian bool binaryAscii[24] = { 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, @@ -157,7 +157,7 @@ unittest(ascii_pin_write_history) { assertEqual("Yes", state->digitalPin[2].toAscii(1, true)); - // digitial history as serial data, little-endian + // digital history as serial data, little-endian bool binaryAscii2[16] = { 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0}; From 041dcb4a24da844a93dd7f6ff110ced1c6aa5689 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 16:05:48 -0500 Subject: [PATCH 03/16] Allow testing from a subdirectory during CI testing --- CHANGELOG.md | 1 + REFERENCE.md | 5 +++++ exe/arduino_ci.rb | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fdbf56f..d17185f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Environment variable to run a custom initialization script during CI testing: `CUSTOM_INIT_SCRIPT` +- Environment variable to run from a subdirectory during CI testing: `USE_SUBDIR` ### Changed diff --git a/REFERENCE.md b/REFERENCE.md index 9116b963..20b2bbba 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -44,6 +44,11 @@ This allows a file (or glob) pattern to be executed in your tests directory, cre If set, testing will execute (using `/bin/sh`) the script referred to by this variable -- relative to the current working directory. 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. +### `USE_SUBDIR` environment variable + +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. + + ### `EXPECT_UNITTESTS` environment variable 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. diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 98dd700a..5a6da633 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -6,6 +6,7 @@ WIDTH = 80 VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze +VAR_USE_SUBDIR = "USE_SUBDIR".freeze VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze @@ -54,6 +55,7 @@ def self.parse(options) puts "Additionally, the following environment variables control the script:" puts " - #{VAR_CUSTOM_INIT_SCRIPT} - if set, this script will be run from the Arduino/libraries directory" puts " prior to any automated library installation or testing (e.g. to install unoffical libraries)" + puts " - #{VAR_USE_SUBDIR} - if set, the script will install the library from this subdirectory of the cwd" puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present" puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present" exit @@ -424,8 +426,10 @@ def perform_example_compilation_tests(cpp_library, config) # run any library init scripts from the library itself. perform_custom_initialization(config) + # initialize library under test -cpp_library_path = Pathname.new(".") +inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" } +cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR]) cpp_library = assure("Installing library under test") do @backend.install_local_library(cpp_library_path) end From 1b4e744ff4d487c96c70448a9beb0cb315924a26 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 16:25:53 -0500 Subject: [PATCH 04/16] Fix directory name and library name comparison --- CHANGELOG.md | 1 + exe/arduino_ci.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d17185f6..ac67d4c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed ### Fixed +- Warnings about directory name mismatches are now based on proper comparison of strings ### Security diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 5a6da633..5cebac5b 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -435,7 +435,7 @@ def perform_example_compilation_tests(cpp_library, config) end assumed_name = @backend.name_of_library(cpp_library_path) -ondisk_name = cpp_library_path.realpath.basename +ondisk_name = cpp_library_path.realpath.basename.to_s if assumed_name != ondisk_name inform("WARNING") { "Installed library named '#{assumed_name}' has directory name '#{ondisk_name}'" } end From 2a180a859a4e88d4fb4f3a60a89399dbfd7242d0 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 16:45:23 -0500 Subject: [PATCH 05/16] Remove comparison table in README --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 64a6bc6d..7943b400 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,6 @@ Linux | [![Linux Build Status](https://github.com/Arduino-CI/arduino_ci/workf 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) -## Comparison to Other Arduino Testing Tools - -| Project | CI | Builds Examples | Unittest | Arduino Mocks | Windows | OSX | Linux | License | -|-----------------------------------------------------------------------------|:--:|:---------------:|:--------:|:-------------:|:-------:|:---:|:-----:|:--------| -|[ArduinoCI](https://github.com/Arduino-CI/arduino_ci) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |Free (Apache-2.0)| -|[ArduinoUnit](https://github.com/mmurdoch/arduinounit) | ❌ | ❌ | ⚠️ Hardware-based|❌ | ✅ | ✅ | ✅ |Free (MIT)| -|[Adafruit `ci-arduino`](https://github.com/adafruit/ci-arduino)| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |Free (MIT)| -|[PlatformIO](https://platformio.org) | ✅ | ✅ | ⚠️ Paid only | ❌ | ✅ | ✅ | ✅ |⚠️ EULA| -|Official [Arduino IDE](https://www.arduino.cc/en/main/software) | ❌ | ⚠️ Manually | ❌ |N/A 😉| ✅ | ✅ | ✅ |Free (GPLv2)| - - ## Quick Start For a bare-bones example that you can copy from, see [SampleProjects/DoSomething](SampleProjects/DoSomething). From 894a78fa47a79d3ec43d660e55e32079390cd4d2 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 22:43:53 -0500 Subject: [PATCH 06/16] Add pointer to github marketplace --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7943b400..4b722f6d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci) [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/1.1.0) [![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) +[![GitHub Marketplace](https://img.shields.io/badge/Get_it-on_Marketplace-informational.svg)](https://github.com/marketplace/actions/arduino_ci) You want to run tests on your Arduino library (bonus: without hardware present), but the IDE doesn't support that. Arduino CI provides that ability. @@ -12,6 +13,8 @@ You want your Arduino library to be automatically built and tested every time so `arduino_ci` is a cross-platform build/test system, consisting of a Ruby gem and a series of C++ mocks. It enables tests to be run both locally and as part of a CI service like GitHub Actions, TravisCI, Appveyor, etc. Any OS that can run the Arduino IDE can run `arduino_ci`. +> 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). + Platform | CI Status ---------|:--------- From ba7e6e83430eaf23109a02b73dbf5d248de53b09 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 27 Dec 2020 22:51:18 -0500 Subject: [PATCH 07/16] update README to point to Blink as an example project --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b722f6d..33254bd2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Windows | [![Windows Build status](https://github.com/Arduino-CI/arduino_ci/wor ## Quick Start -For a bare-bones example that you can copy from, see [SampleProjects/DoSomething](SampleProjects/DoSomething). +For a fairly minimal practical example that you can copy from, see [the `Arduino-CI/Blink` repository](https://github.com/Arduino-CI/Blink). The complete set of C++ unit tests for the `arduino_ci` library itself are in the [SampleProjects/TestSomething](SampleProjects/TestSomething) project. The [test files](SampleProjects/TestSomething/test/) are named after the type of feature being tested. From dfa834f405c35e3a0fff46a7b92b0a7da342e139 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 8 Dec 2020 22:54:04 -0500 Subject: [PATCH 08/16] Be more careful about ruby version and syntax --- .rubocop.yml | 8 ++++---- CHANGELOG.md | 1 + README.md | 2 +- exe/arduino_ci.rb | 3 +-- lib/arduino_ci/arduino_backend.rb | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3d3c64f9..5f8ab4ae 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ AllCops: - TargetRubyVersion: 2.6 + TargetRubyVersion: 2.5 NewCops: enable SuggestExtensions: false Exclude: @@ -65,15 +65,15 @@ Layout/LineLength: # Configuration parameters: CountComments. Metrics/ClassLength: Enabled: false - Max: 400 Metrics/AbcSize: Enabled: false - Max: 50 Metrics/MethodLength: Enabled: false - Max: 50 + +Metrics/BlockLength: + Enabled: false # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: diff --git a/CHANGELOG.md b/CHANGELOG.md index ac67d4c6..0cea46e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Environment variable to run from a subdirectory during CI testing: `USE_SUBDIR` ### Changed +- Rubocop expected syntax downgraded from ruby 2.6 to 2.5 ### Deprecated diff --git a/README.md b/README.md index 33254bd2..715b5375 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Add a file called `Gemfile` (no extension) to your Arduino project: ```ruby source 'https://rubygems.org' -gem 'arduino_ci' +gem 'arduino_ci' '~> 1.1' ``` It would also make sense to add the following to your `.gitignore`, or copy [the `.gitignore` used by this project](.gitignore): diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 5cebac5b..9ecaecdc 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -419,14 +419,12 @@ def perform_example_compilation_tests(cpp_library, config) # initialize command and config config = ArduinoCI::CIConfig.default.from_project_library - @backend = ArduinoCI::ArduinoInstallation.autolocate! inform("Located arduino-cli binary") { @backend.binary_path.to_s } # run any library init scripts from the library itself. perform_custom_initialization(config) - # initialize library under test inform("Environment variable #{VAR_USE_SUBDIR}") { "'#{ENV[VAR_USE_SUBDIR]}'" } cpp_library_path = Pathname.new(ENV[VAR_USE_SUBDIR].nil? ? "." : ENV[VAR_USE_SUBDIR]) @@ -434,6 +432,7 @@ def perform_example_compilation_tests(cpp_library, config) @backend.install_local_library(cpp_library_path) end +# Warn if the library name isn't obvious assumed_name = @backend.name_of_library(cpp_library_path) ondisk_name = cpp_library_path.realpath.basename.to_s if assumed_name != ondisk_name diff --git a/lib/arduino_ci/arduino_backend.rb b/lib/arduino_ci/arduino_backend.rb index cda28752..42090d38 100644 --- a/lib/arduino_ci/arduino_backend.rb +++ b/lib/arduino_ci/arduino_backend.rb @@ -52,7 +52,7 @@ def _wrap_run(work_fn, *args, **kwargs) # do some work to extract & merge environment variables if they exist has_env = !args.empty? && args[0].instance_of?(Hash) env_vars = has_env ? args[0] : {} - actual_args = has_env ? args[1..] : args # need to shift over if we extracted args + actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args custom_config = @config_dir.nil? ? [] : ["--config-file", @config_dir.to_s] full_args = [binary_path.to_s, "--format", "json"] + custom_config + actual_args full_cmd = env_vars.empty? ? full_args : [env_vars] + full_args From 75a391e60687b2ac40099481ff479da05dbf71d4 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 9 Dec 2020 09:33:44 -0500 Subject: [PATCH 09/16] Fix esp32 board manager URL --- CHANGELOG.md | 1 + misc/default.yml | 2 +- spec/ci_config_spec.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cea46e3..cd62604b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Warnings about directory name mismatches are now based on proper comparison of strings +- Now using the recommended "stable" URL for the `esp32` board family ### Security diff --git a/misc/default.yml b/misc/default.yml index 4d621a79..4d08ab9b 100644 --- a/misc/default.yml +++ b/misc/default.yml @@ -16,7 +16,7 @@ packages: adafruit:samd: url: https://adafruit.github.io/arduino-board-index/package_adafruit_index.json esp32:esp32: - url: https://dl.espressif.com/dl/package_esp32_index.json + url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json platforms: diff --git a/spec/ci_config_spec.rb b/spec/ci_config_spec.rb index a0d53bde..bab8293c 100644 --- a/spec/ci_config_spec.rb +++ b/spec/ci_config_spec.rb @@ -29,7 +29,7 @@ expect(default_config.package_url("adafruit:avr")).to eq("https://adafruit.github.io/arduino-board-index/package_adafruit_index.json") expect(default_config.package_url("adafruit:samd")).to eq("https://adafruit.github.io/arduino-board-index/package_adafruit_index.json") - expect(default_config.package_url("esp32:esp32")).to eq("https://dl.espressif.com/dl/package_esp32_index.json") + expect(default_config.package_url("esp32:esp32")).to eq("https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json") expect(default_config.platforms_to_build).to match(["uno", "due", "zero", "leonardo", "m4", "esp32", "esp8266", "mega2560"]) expect(default_config.platforms_to_unittest).to match(["uno", "due", "zero", "leonardo"]) expect(default_config.aux_libraries_for_build).to match([]) From 6ed0e88b0295a9437966750e5698d2d8044dbe3d Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 18 Dec 2020 10:14:34 -0500 Subject: [PATCH 10/16] fix 'invalid byte sequence in UTF-8' on Windows --- lib/arduino_ci/host.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 2cf82c59..dc8e2911 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -103,7 +103,7 @@ def self.readlink(path) the_file = path.basename.to_s stdout, _stderr, _exitstatus = Open3.capture3('cmd.exe', "/c dir /al #{the_dir}") - symlinks = stdout.lines.map { |l| DIR_SYMLINK_REGEX.match(l) }.compact + symlinks = stdout.lines.map { |l| DIR_SYMLINK_REGEX.match(l.scrub) }.compact our_link = symlinks.find { |m| m[1] == the_file } return nil if our_link.nil? From 31ed252ded40c65a793a5fa7af3387c898a18a22 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 25 Dec 2020 22:50:34 -0500 Subject: [PATCH 11/16] Note the necessity of python dependencies for Espressif boards --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 715b5375..ecd7287f 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,13 @@ For unit testing, you will need a compiler; [g++](https://gcc.gnu.org/) is prefe * **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. +### You _May_ Need `python` + +ESP32 and ESP8266 boards have [a dependency on `python` that they don't install themselves](https://github.com/Arduino-CI/arduino_ci/issues/235#issuecomment-739629243). If you intend to test on these platforms (which are included in the default list of platforms to test against), you will need to make `python` (and possibly `pyserial`) available in the test environment. + +Alternately, you might configure `arduino_ci` to simply not test against these. Consult the reference for those details. + + ### Changes to Your Repo Add a file called `Gemfile` (no extension) to your Arduino project: From 4e0b9eb5669140f4164ce1a6eccc5ba572d79c3d Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 25 Dec 2020 23:50:02 -0500 Subject: [PATCH 12/16] remove requirement that types be totally ordered in order to evaluate equality on them --- CHANGELOG.md | 2 ++ REFERENCE.md | 22 +++++++----- .../DoSomething/test/bad-errormessages.cpp | 24 +++++++++++++ .../DoSomething/test/good-assert.cpp | 35 +++++++++++++++++++ cpp/unittest/Assertion.h | 29 ++++++++------- cpp/unittest/Compare.h | 3 ++ spec/cpp_library_spec.rb | 6 ++-- 7 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 SampleProjects/DoSomething/test/bad-errormessages.cpp create mode 100644 SampleProjects/DoSomething/test/good-assert.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index cd62604b..a3928ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Environment variable to run a custom initialization script during CI testing: `CUSTOM_INIT_SCRIPT` - Environment variable to run from a subdirectory during CI testing: `USE_SUBDIR` +- `assertComparativeEqual()` and `assertComparativeNotEqual()` to evaluate equality on an `a - b == 0` basis (and/or `!(a > b) && !(a < b)`) ### Changed - Rubocop expected syntax downgraded from ruby 2.6 to 2.5 +- `assertEqual()` and `assertNotEqual()` use actual `==` and `!=` -- they no longer require a type to be totally ordered just to do equality tests ### Deprecated diff --git a/REFERENCE.md b/REFERENCE.md index 20b2bbba..de56f241 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -208,15 +208,19 @@ This test defines one `unittest` (a macro provided by `ArduinoUnitTests.h`), cal The following assertion functions are available in unit tests. -* `assertEqual(expected, actual)` -* `assertNotEqual(expected, actual)` -* `assertLess(expected, actual)` -* `assertMore(expected, actual)` -* `assertLessOrEqual(expected, actual)` -* `assertMoreOrEqual(expected, actual)` -* `assertTrue(actual)` -* `assertFalse(actual)` -* `assertNull(actual)` +```c++ +assertEqual(expected, actual); // a == b +assertNotEqual(unwanted, actual); // a != b +assertComparativeEqual(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b)) +assertComparativeNotEqual(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b)) +assertLess(upperBound, actual); // a < b +assertMore(lowerBound, actual); // a > b +assertLessOrEqual(upperBound, actual); // a <= b +assertMoreOrEqual(lowerBound, actual); // a >= b +assertTrue(actual); +assertFalse(actual); +assertNull(actual); +``` These functions will report the result of the test to the console, and the testing will continue if they fail. diff --git a/SampleProjects/DoSomething/test/bad-errormessages.cpp b/SampleProjects/DoSomething/test/bad-errormessages.cpp new file mode 100644 index 00000000..dcb9423a --- /dev/null +++ b/SampleProjects/DoSomething/test/bad-errormessages.cpp @@ -0,0 +1,24 @@ +#include + + +#pragma once + + + +unittest(check_that_assertion_error_messages_are_comprehensible) +{ + assertEqual(1 ,2); + assertNotEqual(2, 2); + assertComparativeEqual(1, 2); + assertComparativeNotEqual(2, 2); + assertLess(2, 1); + assertMore(1, 2); + assertLessOrEqual(2, 1); + assertMoreOrEqual(1, 2); + assertTrue(false); + assertFalse(true); + assertNull(3); + assertNotNull(NULL); +} + +unittest_main() diff --git a/SampleProjects/DoSomething/test/good-assert.cpp b/SampleProjects/DoSomething/test/good-assert.cpp new file mode 100644 index 00000000..4d7f09b2 --- /dev/null +++ b/SampleProjects/DoSomething/test/good-assert.cpp @@ -0,0 +1,35 @@ +#include +#include "../do-something.h" + +class NonOrderedType { + public: + int x; // ehh why not + NonOrderedType(int some_x) : x(some_x) {} + + bool operator==(const NonOrderedType &that) const { + return that.x == x; + } + + bool operator!=(const NonOrderedType &that) const { + return that.x != x; + } +}; +inline std::ostream& operator << ( std::ostream& out, const NonOrderedType& n ) { + out << "NonOrderedType(" << n.x << ")"; + return out; +} + + +unittest(assert_equal_without_total_ordering) +{ + NonOrderedType a(3); + NonOrderedType b(3); + NonOrderedType c(4); + + assertEqual(a, b); + assertEqual(a, a); + assertNotEqual(a, c); + +} + +unittest_main() diff --git a/cpp/unittest/Assertion.h b/cpp/unittest/Assertion.h index f5e1b129..179a98fa 100644 --- a/cpp/unittest/Assertion.h +++ b/cpp/unittest/Assertion.h @@ -30,26 +30,29 @@ /** macro generates optional output and calls fail() but does not return if false. */ -#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"==","actual",arg2) -#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) -#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) -#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","lowerBound",arg2) -#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) -#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) +#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) +#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) +#define assertComparativeEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"!<>","actual",arg2) +#define assertComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) +#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","lowerBound",arg2) +#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) +#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) #define assertTrue(arg) assertEqual(true, arg) #define assertFalse(arg) assertEqual(false, arg) #define assertNull(arg) assertEqual((void*)NULL, (void*)arg) #define assertNotNull(arg) assertNotEqual((void*)NULL, (void*)arg) /** macro generates optional output and calls fail() followed by a return if false. */ -#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,compareEqual,"==","actual",arg2) -#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) -#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) -#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","lowerBound",arg2) -#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) -#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) +#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) +#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) +#define assureComparativeEqual(arg1,arg2) assertOp("assureEqual","expected",arg1,compareEqual,"!<>","actual",arg2) +#define assureComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) +#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","lowerBound",arg2) +#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) +#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) #define assureTrue(arg) assureEqual(true, arg) #define assureFalse(arg) assureEqual(false, arg) #define assureNull(arg) assureEqual((void*)NULL, (void*)arg) #define assureNotNull(arg) assureNotEqual((void*)NULL, (void*)arg) - diff --git a/cpp/unittest/Compare.h b/cpp/unittest/Compare.h index 882b9c83..60724845 100644 --- a/cpp/unittest/Compare.h +++ b/cpp/unittest/Compare.h @@ -110,3 +110,6 @@ template bool compareLess( const A &a, const B &b template bool compareMore( const A &a, const B &b) { return Compare::more( a, b); } template bool compareLessOrEqual(const A &a, const B &b) { return Compare::lessOrEqual(a, b); } template bool compareMoreOrEqual(const A &a, const B &b) { return Compare::moreOrEqual(a, b); } + +template bool evaluateDoubleEqual(const A &a, const B &b) { return a == b; } +template bool evaluateNotEqual( const A &a, const B &b) { return a != b; } diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index 3e4a4a95..050e0555 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -85,9 +85,11 @@ def verified_install(backend, path) header_dirs: [Pathname.new("DoSomething")], arduino_library_src_dirs: [], test_files: [ - "DoSomething/test/good-null.cpp", - "DoSomething/test/good-library.cpp", + "DoSomething/test/bad-errormessages.cpp", "DoSomething/test/bad-null.cpp", + "DoSomething/test/good-assert.cpp", + "DoSomething/test/good-library.cpp", + "DoSomething/test/good-null.cpp", ].map { |f| Pathname.new(f) } }, OnePointOhDummy: { From a5e648bdca479534bf59db0c71ef31a9b389c14c Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sat, 26 Dec 2020 00:27:16 -0500 Subject: [PATCH 13/16] Add float comparison operators --- CHANGELOG.md | 5 ++ REFERENCE.md | 8 +++ .../DoSomething/test/bad-errormessages.cpp | 9 ++- .../DoSomething/test/good-assert.cpp | 11 ++++ cpp/unittest/ArduinoUnitTests.h | 32 ++++++++++ cpp/unittest/Assertion.h | 59 +++++++++++++------ 6 files changed, 106 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3928ab2..220f5188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Environment variable to run a custom initialization script during CI testing: `CUSTOM_INIT_SCRIPT` - Environment variable to run from a subdirectory during CI testing: `USE_SUBDIR` - `assertComparativeEqual()` and `assertComparativeNotEqual()` to evaluate equality on an `a - b == 0` basis (and/or `!(a > b) && !(a < b)`) +- `assertEqualFloat()` and `assertNotEqualFloat()` for comparing floats with epsilon +- `assertInfinity()` and `assertNotInfinity()` for comparing floats to infinity +- `assertNAN()` and `assertNotNAN()` for comparing floats to `NaN` +- `assertion()`, `ReporterTAP.onAssert()`, and `testBehaviorExp` macro to handle simple expression evaluation (is true, is false, etc) ### Changed - Rubocop expected syntax downgraded from ruby 2.6 to 2.5 - `assertEqual()` and `assertNotEqual()` use actual `==` and `!=` -- they no longer require a type to be totally ordered just to do equality tests +- Evaluative assertions (is true/false/null/etc) now produce simpler error messages instead of masquerading as an operation (e.g. "== true") ### Deprecated diff --git a/REFERENCE.md b/REFERENCE.md index de56f241..1798aaae 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -220,6 +220,14 @@ assertMoreOrEqual(lowerBound, actual); // a >= b assertTrue(actual); assertFalse(actual); assertNull(actual); + +// special cases for floats +assertEqualFloat(expected, actual, epsilon); // fabs(a - b) <= epsilon +assertNotEqualFloat(unwanted, actual, epsilon); // fabs(a - b) >= epsilon +assertInfinity(actual); // isinf(a) +assertNotInfinity(actual); // !isinf(a) +assertNAN(arg); // isnan(a) +assertNotNAN(arg); // !isnan(a) ``` These functions will report the result of the test to the console, and the testing will continue if they fail. diff --git a/SampleProjects/DoSomething/test/bad-errormessages.cpp b/SampleProjects/DoSomething/test/bad-errormessages.cpp index dcb9423a..37922c90 100644 --- a/SampleProjects/DoSomething/test/bad-errormessages.cpp +++ b/SampleProjects/DoSomething/test/bad-errormessages.cpp @@ -7,7 +7,7 @@ unittest(check_that_assertion_error_messages_are_comprehensible) { - assertEqual(1 ,2); + assertEqual(1, 2); assertNotEqual(2, 2); assertComparativeEqual(1, 2); assertComparativeNotEqual(2, 2); @@ -19,6 +19,13 @@ unittest(check_that_assertion_error_messages_are_comprehensible) assertFalse(true); assertNull(3); assertNotNull(NULL); + + assertEqualFloat(1.2, 1.0, 0.01); + assertNotEqualFloat(1.0, 1.02, 0.1); + assertInfinity(42); + assertNotInfinity(INFINITY); + assertNAN(42); + assertNotNAN(0.0/0.0); } unittest_main() diff --git a/SampleProjects/DoSomething/test/good-assert.cpp b/SampleProjects/DoSomething/test/good-assert.cpp index 4d7f09b2..314011fd 100644 --- a/SampleProjects/DoSomething/test/good-assert.cpp +++ b/SampleProjects/DoSomething/test/good-assert.cpp @@ -32,4 +32,15 @@ unittest(assert_equal_without_total_ordering) } +unittest(float_assertions) +{ + assertInfinity(exp(800)); + assertInfinity(0.0/0.0); + assertNotInfinity(42); + + assertNAN(INFINITY - INFINITY); + assertNAN(0.0/0.0); + assertNotNAN(42); +} + unittest_main() diff --git a/cpp/unittest/ArduinoUnitTests.h b/cpp/unittest/ArduinoUnitTests.h index fbeb4394..3c34a786 100644 --- a/cpp/unittest/ArduinoUnitTests.h +++ b/cpp/unittest/ArduinoUnitTests.h @@ -69,6 +69,23 @@ class Test } } + // non-comparative assert + void onAssert( + const char* file, + int line, + const char* description, + bool pass + ) { + cerr << " " << (pass ? "" : "not ") << "ok " << ++mAssertCounter << " - " << description << endl; + if (!pass) { + cerr << " ---" << endl; + cerr << " at:" << endl; + cerr << " file: " << file << endl; + cerr << " line: " << line << endl; + cerr << " ..." << endl; + } + } + template void onAssert( const char* file, int line, @@ -194,6 +211,21 @@ class Test excise(); } + bool assertion( + const char *file, + int line, + const char *description, + bool ok) + { + if (mReporter) { + mReporter->onAssert(file, line, description, ok); + } + + if (!ok) + fail(); + return ok; + } + template bool assertion( const char *file, diff --git a/cpp/unittest/Assertion.h b/cpp/unittest/Assertion.h index 179a98fa..c21212f0 100644 --- a/cpp/unittest/Assertion.h +++ b/cpp/unittest/Assertion.h @@ -6,9 +6,19 @@ #include "Compare.h" +#define testBehaviorExp(die, desc, pass) \ + do \ + { \ + if (!assertion(__FILE__, __LINE__, \ + desc, pass)) \ + { \ + if (die) return; \ + } \ + } while (0) + #define testBehaviorOp(die, desc, rel1, arg1, op, op_name, rel2, arg2) \ do \ - { \ + { \ if (!assertion(__FILE__, __LINE__, \ desc, \ rel1, #arg1, (arg1), \ @@ -30,29 +40,44 @@ /** macro generates optional output and calls fail() but does not return if false. */ +#define assertTrue(arg) testBehaviorExp(false, "assertTrue " #arg, (arg)) +#define assertFalse(arg) testBehaviorExp(false, "assertFalse " #arg, !(arg)) +#define assertNull(arg) testBehaviorExp(false, "assertNull " #arg, ((void*)NULL == (void*)(arg))) +#define assertNotNull(arg) testBehaviorExp(false, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) #define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) #define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) #define assertComparativeEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"!<>","actual",arg2) #define assertComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) -#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) -#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","lowerBound",arg2) -#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) -#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) -#define assertTrue(arg) assertEqual(true, arg) -#define assertFalse(arg) assertEqual(false, arg) -#define assertNull(arg) assertEqual((void*)NULL, (void*)arg) -#define assertNotNull(arg) assertNotEqual((void*)NULL, (void*)arg) +#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","actual",arg2) +#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","actual",arg2) +#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) +#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) + +#define assertEqualFloat(arg1, arg2, arg3) assertOp("assertEqualFloat", "epsilon", arg3, compareMoreOrEqual, ">=", "actualDifference", fabs(arg1 - arg2)) +#define assertNotEqualFloat(arg1, arg2, arg3) assertOp("assertNotEqualFloat", "epsilon", arg3, compareLessOrEqual, "<=", "insufficientDifference", fabs(arg1 - arg2)) +#define assertInfinity(arg) testBehaviorExp(false, "assertInfinity " #arg, isinf(arg)) +#define assertNotInfinity(arg) testBehaviorExp(false, "assertNotInfinity " #arg, !isinf(arg)) +#define assertNAN(arg) testBehaviorExp(false, "assertNAN " #arg, isnan(arg)) +#define assertNotNAN(arg) testBehaviorExp(false, "assertNotNAN " #arg, !isnan(arg)) + /** macro generates optional output and calls fail() followed by a return if false. */ +#define assureTrue(arg) testBehaviorExp(true, "assertTrue " #arg, (arg)) +#define assureFalse(arg) testBehaviorExp(true, "assertFalse " #arg, !(arg)) +#define assureNull(arg) testBehaviorExp(true, "assertNull " #arg, ((void*)NULL == (void*)(arg))) +#define assureNotNull(arg) testBehaviorExp(true, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) #define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) #define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) #define assureComparativeEqual(arg1,arg2) assertOp("assureEqual","expected",arg1,compareEqual,"!<>","actual",arg2) #define assureComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) -#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) -#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","lowerBound",arg2) -#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) -#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) -#define assureTrue(arg) assureEqual(true, arg) -#define assureFalse(arg) assureEqual(false, arg) -#define assureNull(arg) assureEqual((void*)NULL, (void*)arg) -#define assureNotNull(arg) assureNotEqual((void*)NULL, (void*)arg) +#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","actual",arg2) +#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","actual",arg2) +#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) +#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) + +#define assureEqualFloat(arg1, arg2, arg3) assureOp("assureEqualFloat", "epsilon", arg3, compareMoreOrEqual, ">=", "actualDifference", fabs(arg1 - arg2)) +#define assureNotEqualFloat(arg1, arg2, arg3) assureOp("assureNotEqualFloat", "epsilon", arg3, compareLessOrEqual, "<=", "insufficientDifference", fabs(arg1 - arg2)) +#define assureInfinity(arg) testBehaviorExp(true, "assertInfinity " #arg, isinf(arg)) +#define assureNotInfinity(arg) testBehaviorExp(true, "assertNotInfinity " #arg, !isinf(arg)) +#define assureNAN(arg) testBehaviorExp(true, "assertNAN " #arg, isnan(arg)) +#define assureNotNAN(arg) testBehaviorExp(true, "assertNotNAN " #arg, !isnan(arg)) From efbb8aefb3449a3e887991a0accd598f456fdb06 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 27 Dec 2020 23:11:55 -0500 Subject: [PATCH 14/16] assertComparativeEqual -> assertComparativeEquivalent --- CHANGELOG.md | 2 +- REFERENCE.md | 4 +- .../DoSomething/test/bad-errormessages.cpp | 4 +- cpp/unittest/Assertion.h | 48 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 220f5188..f71a78ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Environment variable to run a custom initialization script during CI testing: `CUSTOM_INIT_SCRIPT` - Environment variable to run from a subdirectory during CI testing: `USE_SUBDIR` -- `assertComparativeEqual()` and `assertComparativeNotEqual()` to evaluate equality on an `a - b == 0` basis (and/or `!(a > b) && !(a < b)`) +- `assertComparativeEquivalent()` and `assertComparativeNotEquivalent()` to evaluate equality on an `a - b == 0` basis (and/or `!(a > b) && !(a < b)`) - `assertEqualFloat()` and `assertNotEqualFloat()` for comparing floats with epsilon - `assertInfinity()` and `assertNotInfinity()` for comparing floats to infinity - `assertNAN()` and `assertNotNAN()` for comparing floats to `NaN` diff --git a/REFERENCE.md b/REFERENCE.md index 1798aaae..69f7bbac 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -211,8 +211,8 @@ The following assertion functions are available in unit tests. ```c++ assertEqual(expected, actual); // a == b assertNotEqual(unwanted, actual); // a != b -assertComparativeEqual(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b)) -assertComparativeNotEqual(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b)) +assertComparativeEquivalent(expected, actual); // abs(a - b) == 0 or (!(a > b) && !(a < b)) +assertComparativeNotEquivalent(unwanted, actual); // abs(a - b) > 0 or ((a > b) || (a < b)) assertLess(upperBound, actual); // a < b assertMore(lowerBound, actual); // a > b assertLessOrEqual(upperBound, actual); // a <= b diff --git a/SampleProjects/DoSomething/test/bad-errormessages.cpp b/SampleProjects/DoSomething/test/bad-errormessages.cpp index 37922c90..a20ebda8 100644 --- a/SampleProjects/DoSomething/test/bad-errormessages.cpp +++ b/SampleProjects/DoSomething/test/bad-errormessages.cpp @@ -9,8 +9,8 @@ unittest(check_that_assertion_error_messages_are_comprehensible) { assertEqual(1, 2); assertNotEqual(2, 2); - assertComparativeEqual(1, 2); - assertComparativeNotEqual(2, 2); + assertComparativeEquivalent(1, 2); + assertComparativeNotEquivalent(2, 2); assertLess(2, 1); assertMore(1, 2); assertLessOrEqual(2, 1); diff --git a/cpp/unittest/Assertion.h b/cpp/unittest/Assertion.h index c21212f0..cb228af5 100644 --- a/cpp/unittest/Assertion.h +++ b/cpp/unittest/Assertion.h @@ -40,18 +40,18 @@ /** macro generates optional output and calls fail() but does not return if false. */ -#define assertTrue(arg) testBehaviorExp(false, "assertTrue " #arg, (arg)) -#define assertFalse(arg) testBehaviorExp(false, "assertFalse " #arg, !(arg)) -#define assertNull(arg) testBehaviorExp(false, "assertNull " #arg, ((void*)NULL == (void*)(arg))) -#define assertNotNull(arg) testBehaviorExp(false, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) -#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) -#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) -#define assertComparativeEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"!<>","actual",arg2) -#define assertComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) -#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","actual",arg2) -#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","actual",arg2) -#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) -#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) +#define assertTrue(arg) testBehaviorExp(false, "assertTrue " #arg, (arg)) +#define assertFalse(arg) testBehaviorExp(false, "assertFalse " #arg, !(arg)) +#define assertNull(arg) testBehaviorExp(false, "assertNull " #arg, ((void*)NULL == (void*)(arg))) +#define assertNotNull(arg) testBehaviorExp(false, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) +#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) +#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) +#define assertComparativeEquivalent(arg1,arg2) assertOp("assertComparativeEquivalent","expected",arg1,compareEqual,"!<>","actual",arg2) +#define assertComparativeNotEquivalent(arg1,arg2) assertOp("assertComparativeNotEquivalent","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","actual",arg2) +#define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","actual",arg2) +#define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) +#define assertMoreOrEqual(arg1,arg2) assertOp("assertMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) #define assertEqualFloat(arg1, arg2, arg3) assertOp("assertEqualFloat", "epsilon", arg3, compareMoreOrEqual, ">=", "actualDifference", fabs(arg1 - arg2)) #define assertNotEqualFloat(arg1, arg2, arg3) assertOp("assertNotEqualFloat", "epsilon", arg3, compareLessOrEqual, "<=", "insufficientDifference", fabs(arg1 - arg2)) @@ -62,18 +62,18 @@ /** macro generates optional output and calls fail() followed by a return if false. */ -#define assureTrue(arg) testBehaviorExp(true, "assertTrue " #arg, (arg)) -#define assureFalse(arg) testBehaviorExp(true, "assertFalse " #arg, !(arg)) -#define assureNull(arg) testBehaviorExp(true, "assertNull " #arg, ((void*)NULL == (void*)(arg))) -#define assureNotNull(arg) testBehaviorExp(true, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) -#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) -#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) -#define assureComparativeEqual(arg1,arg2) assertOp("assureEqual","expected",arg1,compareEqual,"!<>","actual",arg2) -#define assureComparativeNotEqual(arg1,arg2) assertOp("assertEqual","unwanted",arg1,compareNotEqual,"<>","actual",arg2) -#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","actual",arg2) -#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","actual",arg2) -#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) -#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) +#define assureTrue(arg) testBehaviorExp(true, "assertTrue " #arg, (arg)) +#define assureFalse(arg) testBehaviorExp(true, "assertFalse " #arg, !(arg)) +#define assureNull(arg) testBehaviorExp(true, "assertNull " #arg, ((void*)NULL == (void*)(arg))) +#define assureNotNull(arg) testBehaviorExp(true, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) +#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) +#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) +#define assureComparativeEquivalent(arg1,arg2) assertOp("assureComparativeEquivalent","expected",arg1,compareEqual,"!<>","actual",arg2) +#define assureComparativeNotEquivalent(arg1,arg2) assertOp("assureComparativeNotEquivalent","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","actual",arg2) +#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","actual",arg2) +#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) +#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","actual",arg2) #define assureEqualFloat(arg1, arg2, arg3) assureOp("assureEqualFloat", "epsilon", arg3, compareMoreOrEqual, ">=", "actualDifference", fabs(arg1 - arg2)) #define assureNotEqualFloat(arg1, arg2, arg3) assureOp("assureNotEqualFloat", "epsilon", arg3, compareLessOrEqual, "<=", "insufficientDifference", fabs(arg1 - arg2)) From 47e294a50dc5b10c853069df88cbab3e0ef1edf7 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 27 Dec 2020 23:59:32 -0500 Subject: [PATCH 15/16] Reconcile equality for strings and equivalence for float infinity --- .../DoSomething/test/good-assert.cpp | 13 +- cpp/unittest/Assertion.h | 16 +-- cpp/unittest/Compare.h | 112 +++++++++--------- 3 files changed, 78 insertions(+), 63 deletions(-) diff --git a/SampleProjects/DoSomething/test/good-assert.cpp b/SampleProjects/DoSomething/test/good-assert.cpp index 314011fd..2d0010f3 100644 --- a/SampleProjects/DoSomething/test/good-assert.cpp +++ b/SampleProjects/DoSomething/test/good-assert.cpp @@ -34,13 +34,24 @@ unittest(assert_equal_without_total_ordering) unittest(float_assertions) { + assertEqualFloat(1.0, 1.02, 0.1); + assertNotEqualFloat(1.2, 1.0, 0.01); + assertInfinity(exp(800)); - assertInfinity(0.0/0.0); + assertInfinity(1.0/0.0); assertNotInfinity(42); assertNAN(INFINITY - INFINITY); assertNAN(0.0/0.0); assertNotNAN(42); + + assertComparativeEquivalent(exp(800), INFINITY); + assertComparativeEquivalent(0.0/0.0, INFINITY - INFINITY); + assertComparativeNotEquivalent(INFINITY, -INFINITY); + + assertLess(0, INFINITY); + assertLess(-INFINITY, 0); + assertLess(-INFINITY, INFINITY); } unittest_main() diff --git a/cpp/unittest/Assertion.h b/cpp/unittest/Assertion.h index cb228af5..a05f1f48 100644 --- a/cpp/unittest/Assertion.h +++ b/cpp/unittest/Assertion.h @@ -44,10 +44,10 @@ #define assertFalse(arg) testBehaviorExp(false, "assertFalse " #arg, !(arg)) #define assertNull(arg) testBehaviorExp(false, "assertNull " #arg, ((void*)NULL == (void*)(arg))) #define assertNotNull(arg) testBehaviorExp(false, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) -#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) -#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) -#define assertComparativeEquivalent(arg1,arg2) assertOp("assertComparativeEquivalent","expected",arg1,compareEqual,"!<>","actual",arg2) -#define assertComparativeNotEquivalent(arg1,arg2) assertOp("assertComparativeNotEquivalent","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"==","actual",arg2) +#define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) +#define assertComparativeEquivalent(arg1,arg2) assertOp("assertComparativeEquivalent","expected",arg1,compareEquivalent,"!<>","actual",arg2) +#define assertComparativeNotEquivalent(arg1,arg2) assertOp("assertComparativeNotEquivalent","unwanted",arg1,compareNotEquivalent,"<>","actual",arg2) #define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","actual",arg2) #define assertMore(arg1,arg2) assertOp("assertMore","upperBound",arg1,compareMore,">","actual",arg2) #define assertLessOrEqual(arg1,arg2) assertOp("assertLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) @@ -66,10 +66,10 @@ #define assureFalse(arg) testBehaviorExp(true, "assertFalse " #arg, !(arg)) #define assureNull(arg) testBehaviorExp(true, "assertNull " #arg, ((void*)NULL == (void*)(arg))) #define assureNotNull(arg) testBehaviorExp(true, "assertNotNull " #arg, ((void*)NULL != (void*)(arg))) -#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,evaluateDoubleEqual,"==","actual",arg2) -#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,evaluateNotEqual,"!=","actual",arg2) -#define assureComparativeEquivalent(arg1,arg2) assertOp("assureComparativeEquivalent","expected",arg1,compareEqual,"!<>","actual",arg2) -#define assureComparativeNotEquivalent(arg1,arg2) assertOp("assureComparativeNotEquivalent","unwanted",arg1,compareNotEqual,"<>","actual",arg2) +#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,compareEqual,"==","actual",arg2) +#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) +#define assureComparativeEquivalent(arg1,arg2) assertOp("assureComparativeEquivalent","expected",arg1,compareEquivalent,"!<>","actual",arg2) +#define assureComparativeNotEquivalent(arg1,arg2) assertOp("assureComparativeNotEquivalent","unwanted",arg1,compareNotEquivalent,"<>","actual",arg2) #define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","actual",arg2) #define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","actual",arg2) #define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","actual",arg2) diff --git a/cpp/unittest/Compare.h b/cpp/unittest/Compare.h index 60724845..c355b1db 100644 --- a/cpp/unittest/Compare.h +++ b/cpp/unittest/Compare.h @@ -10,12 +10,14 @@ template < typename A, typename B > struct Compare if (b struct Compare; \ - template < __VA_ARGS__ > struct Compare \ - { \ - inline static int between( T1 const (&a)T1m, T2 const (&b)T2m) { return betweenImpl; } \ - inline static bool equal( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) == 0; } \ - inline static bool notEqual( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) != 0; } \ - inline static bool less( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) < 0; } \ - inline static bool more( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) > 0; } \ - inline static bool lessOrEqual(T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) <= 0; } \ - inline static bool moreOrEqual(T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) >= 0; } \ +#define eqComparisonTemplateMacro(T1, T1m, T2, T2m, betweenImpl, ...) \ + template < __VA_ARGS__ > struct Compare; \ + template < __VA_ARGS__ > struct Compare \ + { \ + inline static int between( T1 const (&a)T1m, T2 const (&b)T2m) { return betweenImpl; } \ + inline static bool equal( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) == 0; } \ + inline static bool notEqual( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) != 0; } \ + inline static bool equivalent( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) == 0; } \ + inline static bool notEquivalent(T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) != 0; } \ + inline static bool less( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) < 0; } \ + inline static bool more( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) > 0; } \ + inline static bool lessOrEqual( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) <= 0; } \ + inline static bool moreOrEqual( T1 const (&a)T1m, T2 const (&b)T2m) { return between(a, b) >= 0; } \ }; -comparisonTemplateMacro(String, , String, , a.compareTo(b)) -comparisonTemplateMacro(String, , const char *, , a.compareTo(b)) +eqComparisonTemplateMacro(String, , String, , a.compareTo(b)) +eqComparisonTemplateMacro(String, , const char *, , a.compareTo(b)) #if defined(F) -comparisonTemplateMacro(String, , const __FlashStringHelper *, , arduinoCICompareBetween(a, b)) -comparisonTemplateMacro(const char *,, const __FlashStringHelper *, , strcmp_P(a,(const char *)b)) -comparisonTemplateMacro(const __FlashStringHelper *, , String, , -arduinoCICompareBetween(b, a)) -comparisonTemplateMacro(const __FlashStringHelper *, , const char *, , -strcmp_P(b,(const char *)a)) -comparisonTemplateMacro(const __FlashStringHelper *, , const __FlashStringHelper *, , arduinoCICompareBetween(a, b)) -comparisonTemplateMacro(const __FlashStringHelper *, , char *, , -strcmp_P(b,(const char *)a)) -comparisonTemplateMacro(char *, , const __FlashStringHelper *, , strcmp_P(a,(const char *)b)) -comparisonTemplateMacro(const __FlashStringHelper *, , char, [M], -strcmp_P(b,(const char *)a), size_t M) -comparisonTemplateMacro(char, [N], const __FlashStringHelper *, , strcmp_P(a,(const char *)b), size_t N) +eqComparisonTemplateMacro(String, , const __FlashStringHelper *, , arduinoCICompareBetween(a, b)) +eqComparisonTemplateMacro(const char *,, const __FlashStringHelper *, , strcmp_P(a,(const char *)b)) +eqComparisonTemplateMacro(const __FlashStringHelper *, , String, , -arduinoCICompareBetween(b, a)) +eqComparisonTemplateMacro(const __FlashStringHelper *, , const char *, , -strcmp_P(b,(const char *)a)) +eqComparisonTemplateMacro(const __FlashStringHelper *, , const __FlashStringHelper *, , arduinoCICompareBetween(a, b)) +eqComparisonTemplateMacro(const __FlashStringHelper *, , char *, , -strcmp_P(b,(const char *)a)) +eqComparisonTemplateMacro(char *, , const __FlashStringHelper *, , strcmp_P(a,(const char *)b)) +eqComparisonTemplateMacro(const __FlashStringHelper *, , char, [M], -strcmp_P(b,(const char *)a), size_t M) +eqComparisonTemplateMacro(char, [N], const __FlashStringHelper *, , strcmp_P(a,(const char *)b), size_t N) #endif -comparisonTemplateMacro(String, , char *, , a.compareTo(b)) -comparisonTemplateMacro(const char *, , String, , -b.compareTo(a)) -comparisonTemplateMacro(const char *, , const char *, , strcmp(a,b)) -comparisonTemplateMacro(const char *, , char *, , strcmp(a,b)) -comparisonTemplateMacro(char *, , String, , -b.compareTo(a)) -comparisonTemplateMacro(char *, , const char *, , strcmp(a,b)) -comparisonTemplateMacro(char *, , char *, , strcmp(a,b)) -comparisonTemplateMacro(String, , char, [M], a.compareTo(b), size_t M) -comparisonTemplateMacro(const char *, , char, [M], strcmp(a,b), size_t M) -comparisonTemplateMacro(char *, , char, [M], strcmp(a,b), size_t M) -comparisonTemplateMacro(char, [N], String, , -b.compareTo(a), size_t N) -comparisonTemplateMacro(char, [N], const char *, , strcmp(a,b), size_t N) -comparisonTemplateMacro(char, [N], char *, , strcmp(a,b), size_t N) -comparisonTemplateMacro(char, [N], char, [M], strcmp(a,b), size_t N, size_t M) +eqComparisonTemplateMacro(String, , char *, , a.compareTo(b)) +eqComparisonTemplateMacro(const char *, , String, , -b.compareTo(a)) +eqComparisonTemplateMacro(const char *, , const char *, , strcmp(a,b)) +eqComparisonTemplateMacro(const char *, , char *, , strcmp(a,b)) +eqComparisonTemplateMacro(char *, , String, , -b.compareTo(a)) +eqComparisonTemplateMacro(char *, , const char *, , strcmp(a,b)) +eqComparisonTemplateMacro(char *, , char *, , strcmp(a,b)) +eqComparisonTemplateMacro(String, , char, [M], a.compareTo(b), size_t M) +eqComparisonTemplateMacro(const char *, , char, [M], strcmp(a,b), size_t M) +eqComparisonTemplateMacro(char *, , char, [M], strcmp(a,b), size_t M) +eqComparisonTemplateMacro(char, [N], String, , -b.compareTo(a), size_t N) +eqComparisonTemplateMacro(char, [N], const char *, , strcmp(a,b), size_t N) +eqComparisonTemplateMacro(char, [N], char *, , strcmp(a,b), size_t N) +eqComparisonTemplateMacro(char, [N], char, [M], strcmp(a,b), size_t N, size_t M) -comparisonTemplateMacro(A, , std::nullptr_t, , a ? 1 : 0, typename A) -comparisonTemplateMacro(std::nullptr_t, , B, , b ? -1 : 0, typename B) +eqComparisonTemplateMacro(A, , std::nullptr_t, , a ? 1 : 0, typename A) +eqComparisonTemplateMacro(std::nullptr_t, , B, , b ? -1 : 0, typename B) // super general comparisons -template int compareBetween( const A &a, const B &b) { return Compare::between( a, b); } -template bool compareEqual( const A &a, const B &b) { return Compare::equal( a, b); } -template bool compareNotEqual( const A &a, const B &b) { return Compare::notEqual( a, b); } -template bool compareLess( const A &a, const B &b) { return Compare::less( a, b); } -template bool compareMore( const A &a, const B &b) { return Compare::more( a, b); } -template bool compareLessOrEqual(const A &a, const B &b) { return Compare::lessOrEqual(a, b); } -template bool compareMoreOrEqual(const A &a, const B &b) { return Compare::moreOrEqual(a, b); } - -template bool evaluateDoubleEqual(const A &a, const B &b) { return a == b; } -template bool evaluateNotEqual( const A &a, const B &b) { return a != b; } +template int compareBetween( const A &a, const B &b) { return Compare::between( a, b); } +template bool compareEqual( const A &a, const B &b) { return Compare::equal( a, b); } +template bool compareNotEqual( const A &a, const B &b) { return Compare::notEqual( a, b); } +template bool compareEquivalent( const A &a, const B &b) { return Compare::equivalent( a, b); } +template bool compareNotEquivalent(const A &a, const B &b) { return Compare::notEquivalent(a, b); } +template bool compareLess( const A &a, const B &b) { return Compare::less( a, b); } +template bool compareMore( const A &a, const B &b) { return Compare::more( a, b); } +template bool compareLessOrEqual( const A &a, const B &b) { return Compare::lessOrEqual( a, b); } +template bool compareMoreOrEqual( const A &a, const B &b) { return Compare::moreOrEqual( a, b); } From 6a16d2759e780b3c7d6911b3645002f2c20cdc14 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 28 Dec 2020 10:36:34 -0500 Subject: [PATCH 16/16] Harden and document Wire mocks --- CHANGELOG.md | 1 + REFERENCE.md | 44 +++++++++++++++++++ SampleProjects/TestSomething/test/wire.cpp | 23 ++++++++-- cpp/arduino/Wire.h | 50 ++++++++++++++++------ 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f71a78ba..1d8207c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `assertInfinity()` and `assertNotInfinity()` for comparing floats to infinity - `assertNAN()` and `assertNotNAN()` for comparing floats to `NaN` - `assertion()`, `ReporterTAP.onAssert()`, and `testBehaviorExp` macro to handle simple expression evaluation (is true, is false, etc) +- `Wire.resetMocks()` and documentation ### Changed - Rubocop expected syntax downgraded from ruby 2.6 to 2.5 diff --git a/REFERENCE.md b/REFERENCE.md index 69f7bbac..c4f3527a 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -656,3 +656,47 @@ unittest(eeprom) assertEqual(10, a); } ``` + + +### Wire + +This library allows communication with I2C / TWI devices. + +The interface the library has been fully mocked, with the addition of several functions for debugging + +* `Wire.resetMocks()`: Initializes all mocks, and for test repeatability should be called at the top of any unit tests that use Wire. +* `Wire.didBegin()`: returns whether `Wire.begin()` was called at any point +* `Wire.getMosi(address)`: returns a pointer to a `deque` that represents the history of data sent to `address` +* `Wire.getMiso(address)`: returns a pointer to a `deque` that defines what the master will read from `address` (i.e. for you to supply) + +```c++ +unittest(wire_basics) { + // ensure known starting state + Wire.resetMocks(); + + // in case you need to check that your library is properly calling .begin() + assertFalse(Wire.didBegin()); + Wire.begin(); + assertTrue(Wire.didBegin()); + + // pick a random device. master write buffer should be empty + const uint8_t randomSlaveAddr = 14; + deque* mosi = Wire.getMosi(randomSlaveAddr); + assertEqual(0, mosi->size()); + + // write some random data to random device + const uint8_t randomData[] = { 0x07, 0x0E }; + Wire.beginTransmission(randomSlaveAddr); + Wire.write(randomData[0]); + Wire.write(randomData[1]); + Wire.endTransmission(); + + // check master write buffer values + assertEqual(2, mosi->size()); + assertEqual(randomData[0], mosi->front()); + mosi->pop_front(); + assertEqual(randomData[1], mosi->front()); + mosi->pop_front(); + assertEqual(0, mosi->size()); +} +``` diff --git a/SampleProjects/TestSomething/test/wire.cpp b/SampleProjects/TestSomething/test/wire.cpp index 720afb8c..455cb28d 100644 --- a/SampleProjects/TestSomething/test/wire.cpp +++ b/SampleProjects/TestSomething/test/wire.cpp @@ -3,14 +3,27 @@ #include using std::deque; +unittest(wire_init) { + Wire.resetMocks(); + assertFalse(Wire.didBegin()); + Wire.begin(); + assertTrue(Wire.didBegin()); + Wire.resetMocks(); + assertFalse(Wire.didBegin()); +} + unittest(begin_write_end) { + Wire.resetMocks(); + + const uint8_t randomSlaveAddr = 14; + const uint8_t randomData[] = { 0x07, 0x0E }; + // master write buffer should be empty - deque* mosi = Wire.getMosi(14); + deque* mosi = Wire.getMosi(randomSlaveAddr); assertEqual(0, mosi->size()); - + // write some random data to random slave - const uint8_t randomSlaveAddr = 14; - const uint8_t randomData[] = { 0x07, 0x0E }; + Wire.begin(); Wire.beginTransmission(randomSlaveAddr); Wire.write(randomData[0]); @@ -27,6 +40,8 @@ unittest(begin_write_end) { } unittest(readTwo_writeOne) { + Wire.resetMocks(); + Wire.begin(); deque* miso; // place some values on random slaves' read buffers diff --git a/cpp/arduino/Wire.h b/cpp/arduino/Wire.h index c77667a8..5053b8bc 100644 --- a/cpp/arduino/Wire.h +++ b/cpp/arduino/Wire.h @@ -54,19 +54,52 @@ struct wireData_t { class TwoWire : public ObservableDataStream { private: bool _didBegin = false; - wireData_t *in = nullptr; // pointer to current slave for writing - wireData_t *out = nullptr; // pointer to current slave for reading + wireData_t* in = nullptr; // pointer to current slave for writing + wireData_t* out = nullptr; // pointer to current slave for reading wireData_t slaves[SLAVE_COUNT]; public: - // constructor initializes internal data - TwoWire() { + + ////////////////////////////////////////////////////////////////////////////////////////////// + // testing methods + ////////////////////////////////////////////////////////////////////////////////////////////// + + // initialize all the mocks + void resetMocks() { + _didBegin = false; + in = nullptr; // pointer to current slave for writing + out = nullptr; // pointer to current slave for reading for (int i = 0; i < SLAVE_COUNT; ++i) { slaves[i].misoSize = 0; slaves[i].mosiSize = 0; + slaves[i].misoBuffer.clear(); + slaves[i].mosiBuffer.clear(); } } + // to verify that Wire.begin() was called at some point + bool didBegin() { return _didBegin; } + + // to access the MISO buffer, which allows you to mock what the master will read in a request + deque* getMiso(uint8_t address) { + return &slaves[address].misoBuffer; + } + + // to access the MOSI buffer, which records what the master sends during a write + deque* getMosi(uint8_t address) { + return &slaves[address].mosiBuffer; + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + // mock implementation + ////////////////////////////////////////////////////////////////////////////////////////////// + + // constructor initializes internal data + TwoWire() { + resetMocks(); + } + // https://www.arduino.cc/en/Reference/WireBegin // Initiate the Wire library and join the I2C bus as a master or slave. This // should normally be called only once. @@ -220,15 +253,6 @@ class TwoWire : public ObservableDataStream { // We don't (yet) support the slave role in the mock void onRequest(void (*callback)(void)) { assert(false); } - // testing methods - bool didBegin() { return _didBegin; } - - deque *getMiso(uint8_t address) { - return &slaves[address].misoBuffer; - } - deque *getMosi(uint8_t address) { - return &slaves[address].mosiBuffer; - } }; extern TwoWire Wire;