From a50d8a1eef9dc43a70d1e8c075d228f6e19c934d Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 16 Oct 2020 16:33:13 -0400 Subject: [PATCH 01/33] Increase clarity of command line tool The command line option --skip-compilation can be confusing, because unit tests must be compiled before they can run. This adds some text to the flag such that the fact that examples are compiled is acknowledged. --- CHANGELOG.md | 2 ++ REFERENCE.md | 13 +++++++++---- exe/arduino_ci.rb | 7 ++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db353f1c..2bfe7763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - Add `__AVR__` to defines when compiling +- `arduino_ci_remote.rb` CLI switch `--skip-examples-compilation` - Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` ### Changed @@ -15,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Revise math macros to avoid name clashes ### Deprecated +- `arduino_ci_remote.rb` CLI switch `--skip-compilation` - Deprecated `arduino_ci_remote.rb` in favor of `arduino_ci.rb` ### Removed diff --git a/REFERENCE.md b/REFERENCE.md index 41925bdc..4089cbbb 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -19,9 +19,14 @@ When testing locally, it's often advantageous to limit the number of tests that This completely skips the unit testing portion of the CI script. -### `--skip-compilation` option +### `--skip-compilation` option (deprecated) -This completely skips the compilation tests (of library examples) portion of the CI script. +This completely skips the compilation tests (of library examples) portion of the CI script. It does not skip the compilation of unit tests. + + +### `--skip-examples-compilation` option + +This completely skips the compilation tests (of library examples) portion of the CI script. It does not skip the compilation of unit tests. ### `--testfile-select` option @@ -90,8 +95,8 @@ platforms: ### Control How Examples Are Compiled -Put a file `.arduino-ci.yml` in each example directory where you require a different configuration than default. -The `compile:` section controls the platforms on which the compilation will be attempted, as well as any external libraries that must be installed and included. +Put a file `.arduino-ci.yml` in each example directory where you require a different configuration than default. +The `compile:` section controls the platforms on which the compilation will be attempted, as well as any external libraries that must be installed and included. ```yaml compile: diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 12ab224e..ded5415a 100644 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -29,7 +29,12 @@ def self.parse(options) output_options[:skip_unittests] = p end - opts.on("--skip-compilation", "Don't compile example sketches") do |p| + opts.on("--skip-compilation", "Don't compile example sketches (deprecated)") do |p| + puts "The option --skip-compilation has been deprecated in favor of --skip-examples-compilation" + output_options[:skip_compilation] = p + end + + opts.on("--skip-examples-compilation", "Don't compile example sketches") do |p| output_options[:skip_compilation] = p end From 4f6eb2ae3fe0d4c584d67d17a7678d5694c84bd9 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 18 Oct 2020 23:27:05 -0400 Subject: [PATCH 02/33] Use proper 1.5 library format --- SampleProjects/TestSomething/.arduino-ci.yml | 2 +- .../TestSomething/{ => src}/excludeThis/exclude-this.cpp | 0 .../TestSomething/{ => src}/excludeThis/exclude-this.h | 0 SampleProjects/TestSomething/{ => src}/test-something.cpp | 0 SampleProjects/TestSomething/{ => src}/test-something.h | 0 SampleProjects/TestSomething/test/library.cpp | 2 +- spec/testsomething_unittests_spec.rb | 8 ++++---- 7 files changed, 6 insertions(+), 6 deletions(-) rename SampleProjects/TestSomething/{ => src}/excludeThis/exclude-this.cpp (100%) rename SampleProjects/TestSomething/{ => src}/excludeThis/exclude-this.h (100%) rename SampleProjects/TestSomething/{ => src}/test-something.cpp (100%) rename SampleProjects/TestSomething/{ => src}/test-something.h (100%) diff --git a/SampleProjects/TestSomething/.arduino-ci.yml b/SampleProjects/TestSomething/.arduino-ci.yml index c418bdbe..2aa851f1 100644 --- a/SampleProjects/TestSomething/.arduino-ci.yml +++ b/SampleProjects/TestSomething/.arduino-ci.yml @@ -1,6 +1,6 @@ unittest: exclude_dirs: - - excludeThis + - src/excludeThis platforms: - uno - due diff --git a/SampleProjects/TestSomething/excludeThis/exclude-this.cpp b/SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp similarity index 100% rename from SampleProjects/TestSomething/excludeThis/exclude-this.cpp rename to SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp diff --git a/SampleProjects/TestSomething/excludeThis/exclude-this.h b/SampleProjects/TestSomething/src/excludeThis/exclude-this.h similarity index 100% rename from SampleProjects/TestSomething/excludeThis/exclude-this.h rename to SampleProjects/TestSomething/src/excludeThis/exclude-this.h diff --git a/SampleProjects/TestSomething/test-something.cpp b/SampleProjects/TestSomething/src/test-something.cpp similarity index 100% rename from SampleProjects/TestSomething/test-something.cpp rename to SampleProjects/TestSomething/src/test-something.cpp diff --git a/SampleProjects/TestSomething/test-something.h b/SampleProjects/TestSomething/src/test-something.h similarity index 100% rename from SampleProjects/TestSomething/test-something.h rename to SampleProjects/TestSomething/src/test-something.h diff --git a/SampleProjects/TestSomething/test/library.cpp b/SampleProjects/TestSomething/test/library.cpp index d80ad2c4..675d83e9 100644 --- a/SampleProjects/TestSomething/test/library.cpp +++ b/SampleProjects/TestSomething/test/library.cpp @@ -1,5 +1,5 @@ #include -#include "../test-something.h" +#include "../src/test-something.h" unittest(library_tests_something) { diff --git a/spec/testsomething_unittests_spec.rb b/spec/testsomething_unittests_spec.rb index 5e0f9152..08300fc5 100644 --- a/spec/testsomething_unittests_spec.rb +++ b/spec/testsomething_unittests_spec.rb @@ -19,8 +19,8 @@ def get_relative_dir(sampleprojects_tests_dir) context "cpp_files" do it "finds cpp files in directory" do testsomething_cpp_files = [ - Pathname.new("TestSomething/test-something.cpp"), - Pathname.new("TestSomething/excludeThis/exclude-this.cpp") + Pathname.new("TestSomething/src/test-something.cpp"), + Pathname.new("TestSomething/src/excludeThis/exclude-this.cpp") ] relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } expect(relative_paths).to match_array(testsomething_cpp_files) @@ -47,10 +47,10 @@ def get_relative_dir(sampleprojects_tests_dir) cpp_lib_path = sampleproj_path + "TestSomething" cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), - ["excludeThis"].map(&Pathname.method(:new))) + ["src/excludeThis"].map(&Pathname.method(:new))) context "cpp_files" do it "finds cpp files in directory" do - testsomething_cpp_files = [Pathname.new("TestSomething/test-something.cpp")] + testsomething_cpp_files = [Pathname.new("TestSomething/src/test-something.cpp")] relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } expect(relative_paths).to match_array(testsomething_cpp_files) end From 9b4f31f0f2106121642a47d56406c7a5970e7e2f Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 18 Oct 2020 22:36:59 -0400 Subject: [PATCH 03/33] Obey 1.0 / 1.5 library specification when finding C++ library source --- CHANGELOG.md | 2 + SampleProjects/OnePointFiveDummy/NoBase.cpp | 0 SampleProjects/OnePointFiveDummy/NoBase.h | 0 SampleProjects/OnePointFiveDummy/README.md | 1 + .../OnePointFiveDummy/library.properties | 0 .../OnePointFiveDummy/src/YesSrc.cpp | 0 SampleProjects/OnePointFiveDummy/src/YesSrc.h | 0 .../src/subdir/YesSubdir.cpp | 0 .../OnePointFiveDummy/src/subdir/YesSubdir.h | 0 .../OnePointFiveDummy/utility/ImNotHere.cpp | 0 .../OnePointFiveDummy/utility/ImNotHere.h | 0 .../OnePointFiveMalformed/README.md | 1 + .../OnePointFiveMalformed/YesBase.cpp | 0 .../OnePointFiveMalformed/YesBase.h | 0 .../OnePointFiveMalformed/src/ImNotHere.cpp | 0 .../OnePointFiveMalformed/src/ImNotHere.h | 0 .../OnePointFiveMalformed/utility/YesUtil.cpp | 0 .../OnePointFiveMalformed/utility/YesUtil.h | 0 SampleProjects/OnePointOhDummy/README.md | 1 + SampleProjects/OnePointOhDummy/YesBase.cpp | 0 SampleProjects/OnePointOhDummy/YesBase.h | 0 .../OnePointOhDummy/src/ImNotHere.cpp | 0 .../OnePointOhDummy/src/ImNotHere.h | 0 .../OnePointOhDummy/utility/YesUtil.cpp | 0 .../OnePointOhDummy/utility/YesUtil.h | 0 SampleProjects/README.md | 12 +- lib/arduino_ci/cpp_library.rb | 78 +++++++++--- spec/cpp_library_spec.rb | 118 +++++++++++++----- 28 files changed, 158 insertions(+), 55 deletions(-) create mode 100644 SampleProjects/OnePointFiveDummy/NoBase.cpp create mode 100644 SampleProjects/OnePointFiveDummy/NoBase.h create mode 100644 SampleProjects/OnePointFiveDummy/README.md create mode 100644 SampleProjects/OnePointFiveDummy/library.properties create mode 100644 SampleProjects/OnePointFiveDummy/src/YesSrc.cpp create mode 100644 SampleProjects/OnePointFiveDummy/src/YesSrc.h create mode 100644 SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.cpp create mode 100644 SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.h create mode 100644 SampleProjects/OnePointFiveDummy/utility/ImNotHere.cpp create mode 100644 SampleProjects/OnePointFiveDummy/utility/ImNotHere.h create mode 100644 SampleProjects/OnePointFiveMalformed/README.md create mode 100644 SampleProjects/OnePointFiveMalformed/YesBase.cpp create mode 100644 SampleProjects/OnePointFiveMalformed/YesBase.h create mode 100644 SampleProjects/OnePointFiveMalformed/src/ImNotHere.cpp create mode 100644 SampleProjects/OnePointFiveMalformed/src/ImNotHere.h create mode 100644 SampleProjects/OnePointFiveMalformed/utility/YesUtil.cpp create mode 100644 SampleProjects/OnePointFiveMalformed/utility/YesUtil.h create mode 100644 SampleProjects/OnePointOhDummy/README.md create mode 100644 SampleProjects/OnePointOhDummy/YesBase.cpp create mode 100644 SampleProjects/OnePointOhDummy/YesBase.h create mode 100644 SampleProjects/OnePointOhDummy/src/ImNotHere.cpp create mode 100644 SampleProjects/OnePointOhDummy/src/ImNotHere.h create mode 100644 SampleProjects/OnePointOhDummy/utility/YesUtil.cpp create mode 100644 SampleProjects/OnePointOhDummy/utility/YesUtil.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfe7763..3358d162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `__AVR__` to defines when compiling - `arduino_ci_remote.rb` CLI switch `--skip-examples-compilation` - Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` +- `CppLibrary.header_files` to find header files ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci - Revise math macros to avoid name clashes +- `CppLibrary` functions returning C++ header or code files now respect the 1.0/1.5 library specification ### Deprecated - `arduino_ci_remote.rb` CLI switch `--skip-compilation` diff --git a/SampleProjects/OnePointFiveDummy/NoBase.cpp b/SampleProjects/OnePointFiveDummy/NoBase.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/NoBase.h b/SampleProjects/OnePointFiveDummy/NoBase.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/README.md b/SampleProjects/OnePointFiveDummy/README.md new file mode 100644 index 00000000..8ee1e7c5 --- /dev/null +++ b/SampleProjects/OnePointFiveDummy/README.md @@ -0,0 +1 @@ +This project resembles a "1.5 spec" library: it has `library.properties` and a `src/` directory that will be scanned recursively. `utility/`, if present, will be ignored. diff --git a/SampleProjects/OnePointFiveDummy/library.properties b/SampleProjects/OnePointFiveDummy/library.properties new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/src/YesSrc.cpp b/SampleProjects/OnePointFiveDummy/src/YesSrc.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/src/YesSrc.h b/SampleProjects/OnePointFiveDummy/src/YesSrc.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.cpp b/SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.h b/SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/utility/ImNotHere.cpp b/SampleProjects/OnePointFiveDummy/utility/ImNotHere.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveDummy/utility/ImNotHere.h b/SampleProjects/OnePointFiveDummy/utility/ImNotHere.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/README.md b/SampleProjects/OnePointFiveMalformed/README.md new file mode 100644 index 00000000..905d8336 --- /dev/null +++ b/SampleProjects/OnePointFiveMalformed/README.md @@ -0,0 +1 @@ +This project lacks a `library.properties` and so should be treated as a "1.0 spec" library -- the base and `utility` directories will be scanned for code, non-recursively. `src/`, if present, will be ignored. diff --git a/SampleProjects/OnePointFiveMalformed/YesBase.cpp b/SampleProjects/OnePointFiveMalformed/YesBase.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/YesBase.h b/SampleProjects/OnePointFiveMalformed/YesBase.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/src/ImNotHere.cpp b/SampleProjects/OnePointFiveMalformed/src/ImNotHere.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/src/ImNotHere.h b/SampleProjects/OnePointFiveMalformed/src/ImNotHere.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/utility/YesUtil.cpp b/SampleProjects/OnePointFiveMalformed/utility/YesUtil.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointFiveMalformed/utility/YesUtil.h b/SampleProjects/OnePointFiveMalformed/utility/YesUtil.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/README.md b/SampleProjects/OnePointOhDummy/README.md new file mode 100644 index 00000000..8afffdd9 --- /dev/null +++ b/SampleProjects/OnePointOhDummy/README.md @@ -0,0 +1 @@ +This project should resemble "1.0 spec" library -- the base and `utility` directories will be scanned for code, non-recursively. `src/`, if present, will be ignored. diff --git a/SampleProjects/OnePointOhDummy/YesBase.cpp b/SampleProjects/OnePointOhDummy/YesBase.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/YesBase.h b/SampleProjects/OnePointOhDummy/YesBase.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/src/ImNotHere.cpp b/SampleProjects/OnePointOhDummy/src/ImNotHere.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/src/ImNotHere.h b/SampleProjects/OnePointOhDummy/src/ImNotHere.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/utility/YesUtil.cpp b/SampleProjects/OnePointOhDummy/utility/YesUtil.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/OnePointOhDummy/utility/YesUtil.h b/SampleProjects/OnePointOhDummy/utility/YesUtil.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/README.md b/SampleProjects/README.md index 6b7ed569..834838c7 100644 --- a/SampleProjects/README.md +++ b/SampleProjects/README.md @@ -1,7 +1,13 @@ Arduino Sample Projects ======================= -This directory contains projects that are meant to be built with and tested by this gem. Although this directory is named `SampleProjects`, it is by no means optional. These project test the testing framework itself, but also provide examples of how you might write your own tests (which should be placed in your system's Arduino `libraries` directory). +This directory contains projects that are intended solely for testing the various features of this gem -- to test the testing framework itself. The RSpec tests refer specifically to these projects. -* "DoSomething" is a simple test of the testing framework (arduino_ci) itself to verfy that passes and failures are properly identified and reported. -* "TestSomething" contains tests for all the mock features of arduino_ci. +Because of this, these projects include some intentional quirks that differ from what a well-formed an Arduino project for testing with `arduino_ci` might contain. See other projects in the "Arduino-CI" GitHub organization for practical examples. + + +* "TestSomething" contains a minimial library, but tests for all the C++ compilation feature-mocks of arduino_ci. +* "DoSomething" is a simple test of the testing framework (arduino_ci) itself to verfy that passes and failures are properly identified and reported. Because of this, it includes test files that are expected to fail -- they are prefixed with "bad-". +* "OnePointOhDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the "1.0" specification +* "OnePointFiveMalformed" is a non-functional library meant to test file inclusion logic on libraries that attempt to conform to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) but fail to include a `src` directory +* "OnePointFiveDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 42323be2..f5d92b69 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -55,6 +55,18 @@ def initialize(base_dir, arduino_lib_dir, exclude_dirs) @vendor_bundle_cache = nil end + # Decide whether this is a 1.5-compatible library + # + # according to https://arduino.github.io/arduino-cli/latest/library-specification + # + # Should match logic from https://github.com/arduino/arduino-cli/blob/master/arduino/libraries/loader.go + # @return [bool] + def one_point_five? + lib_props = (@base_dir + "library.properties") + src_dir = (@base_dir + "src") + [lib_props, src_dir].all?(&:exist?) && lib_props.file? && src_dir.directory? + end + # Guess whether a file is part of the vendor bundle (indicating we should ignore it). # # A safe way to do this seems to be to check whether any of the installed gems @@ -152,41 +164,76 @@ def libasan?(gcc_binary) # Get a list of all CPP source files in a directory and its subdirectories # @param some_dir [Pathname] The directory in which to begin the search + # @param extensions [Array] The set of allowable file extensions # @return [Array] The paths of the found files - def cpp_files_in(some_dir) + def code_files_in(some_dir, extensions) raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname return [] unless some_dir.exist? && some_dir.directory? real = some_dir.realpath - files = Find.find(real).map { |p| Pathname.new(p) }.reject(&:directory?) - cpp = files.select { |path| CPP_EXTENSIONS.include?(path.extname.downcase) } + files = some_dir.realpath.children.reject(&:directory?) + cpp = files.select { |path| extensions.include?(path.extname.downcase) } not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") } not_hidden.sort_by(&:to_s) end + # Get a list of all CPP source files in a directory and its subdirectories + # @param some_dir [Pathname] The directory in which to begin the search + # @param extensions [Array] The set of allowable file extensions + # @return [Array] The paths of the found files + def code_files_in_recursive(some_dir, extensions) + raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname + return [] unless some_dir.exist? && some_dir.directory? + + real = some_dir.realpath + Find.find(real).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten + end + + # Header files that are part of the project library under test + # @return [Array] + def header_files + ret = if one_point_five? + code_files_in_recursive(@base_dir + "src", HPP_EXTENSIONS) + else + [@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, HPP_EXTENSIONS) }.flatten + end + + # note to future troubleshooter: some of these tests may not be relevant, but at the moment at + # least some of them are tied to existing features + ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) } + end + # CPP files that are part of the project library under test # @return [Array] def cpp_files - cpp_files_in(@base_dir).reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) } + ret = if one_point_five? + code_files_in_recursive(@base_dir + "src", CPP_EXTENSIONS) + else + [@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten + end + + # note to future troubleshooter: some of these tests may not be relevant, but at the moment at + # least some of them are tied to existing features + ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) } end # CPP files that are part of the arduino mock library we're providing # @return [Array] def cpp_files_arduino - cpp_files_in(ARDUINO_HEADER_DIR) + code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS) end # CPP files that are part of the unit test library we're providing # @return [Array] def cpp_files_unittest - cpp_files_in(UNITTEST_HEADER_DIR) + code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS) end # CPP files that are part of the 3rd-party libraries we're including # @param [Array] aux_libraries # @return [Array] def cpp_files_libraries(aux_libraries) - arduino_library_src_dirs(aux_libraries).map { |d| cpp_files_in(d) }.flatten.uniq + arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq end # Returns the Pathnames for all paths to exclude from testing and compilation @@ -204,15 +251,13 @@ def tests_dir # The files provided by the user that contain unit tests # @return [Array] def test_files - cpp_files_in(tests_dir) + code_files_in(tests_dir, CPP_EXTENSIONS) end # Find all directories in the project library that include C++ header files # @return [Array] def header_dirs - real = @base_dir.realpath - all_files = Find.find(real).map { |f| Pathname.new(f) }.reject(&:directory?) - unbundled = all_files.reject { |path| vendor_bundle?(path) } + unbundled = header_files.reject { |path| vendor_bundle?(path) } unexcluded = unbundled.reject { |path| in_exclude_dir?(path) } files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) } files.map(&:dirname).uniq @@ -241,15 +286,8 @@ def gcc_version(gcc_binary) def arduino_library_src_dirs(aux_libraries) # Pull in all possible places that headers could live, according to the spec: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification - # TODO: be smart and implement library spec (library.properties, etc)? - subdirs = ["", "src", "utility"] - all_aux_include_dirs_nested = aux_libraries.map do |libdir| - # library manager coerces spaces in package names to underscores - # see https://github.com/Arduino-CI/arduino_ci/issues/132#issuecomment-518857059 - legal_libdir = libdir.tr(" ", "_") - subdirs.map { |subdir| Pathname.new(@arduino_lib_dir) + legal_libdir + subdir } - end - all_aux_include_dirs_nested.flatten.select(&:exist?).select(&:directory?) + + aux_libraries.map { |d| self.new(d, @arduino_lib_dir, @exclude_dirs).header_dirs }.flatten end # GCC command line arguments for including aux libraries diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index 40b2407a..3e24a433 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -12,47 +12,101 @@ def get_relative_dir(sampleprojects_tests_dir) RSpec.describe ArduinoCI::CppLibrary do next if skip_ruby_tests - cpp_lib_path = sampleproj_path + "DoSomething" - cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), []) - context "cpp_files" do - it "finds cpp files in directory" do - dosomething_cpp_files = [Pathname.new("DoSomething") + "do-something.cpp"] - relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } - expect(relative_paths).to match_array(dosomething_cpp_files) - end - end - - context "header_dirs" do - it "finds directories containing h files" do - dosomething_header_dirs = [Pathname.new("DoSomething")] - relative_paths = cpp_library.header_dirs.map { |f| get_relative_dir(f) } - expect(relative_paths).to match_array(dosomething_header_dirs) - end - end - - context "tests_dir" do - it "locates the tests directory" do - # since we don't know where the CI system will install this stuff, - # we need to go looking for a relative path to the SampleProjects directory - # just to get our "expected" value - relative_path = get_relative_dir(cpp_library.tests_dir) - expect(relative_path.to_s).to eq("DoSomething/test") - end - end - context "test_files" do - it "finds cpp files in directory" do - dosomething_test_files = [ + answers = { + "DoSomething": { + one_five: false, + cpp_files: [Pathname.new("DoSomething") + "do-something.cpp"], + header_dirs: [Pathname.new("DoSomething")], + test_files: [ "DoSomething/test/good-null.cpp", "DoSomething/test/good-library.cpp", "DoSomething/test/bad-null.cpp", ].map { |f| Pathname.new(f) } - relative_paths = cpp_library.test_files.map { |f| get_relative_dir(f) } - expect(relative_paths).to match_array(dosomething_test_files) + }, + "OnePointOhDummy": { + one_five: false, + cpp_files: [ + "OnePointOhDummy/YesBase.cpp", + "OnePointOhDummy/utility/YesUtil.cpp", + ].map { |f| Pathname.new(f) }, + header_dirs: [ + "OnePointOhDummy", + "OnePointOhDummy/utility" + ].map { |f| Pathname.new(f) }, + test_files: [] + }, + "OnePointFiveMalformed": { + one_five: false, + cpp_files: [ + "OnePointFiveMalformed/YesBase.cpp", + "OnePointFiveMalformed/utility/YesUtil.cpp", + ].map { |f| Pathname.new(f) }, + header_dirs: [ + "OnePointFiveMalformed", + "OnePointFiveMalformed/utility" + ].map { |f| Pathname.new(f) }, + test_files: [] + }, + "OnePointFiveDummy": { + one_five: true, + cpp_files: [ + "OnePointFiveDummy/src/YesSrc.cpp", + "OnePointFiveDummy/src/subdir/YesSubdir.cpp", + ].map { |f| Pathname.new(f) }, + header_dirs: [ + "OnePointFiveDummy/src", + "OnePointFiveDummy/src/subdir", + ].map { |f| Pathname.new(f) }, + test_files: [] + } + }.freeze + + answers.each do |sampleproject, expected| + context "#{sampleproject}" do + cpp_lib_path = sampleproj_path + sampleproject.to_s + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), []) + + it "detects 1.5 format" do + expect(cpp_library.one_point_five?).to eq(expected[:one_five]) + end + + context "cpp_files" do + it "finds cpp files in directory" do + relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } + expect(relative_paths.map(&:to_s)).to match_array(expected[:cpp_files].map(&:to_s)) + end + end + + context "header_dirs" do + it "finds directories containing h files" do + relative_paths = cpp_library.header_dirs.map { |f| get_relative_dir(f) } + expect(relative_paths.map(&:to_s)).to match_array(expected[:header_dirs].map(&:to_s)) + end + end + + context "tests_dir" do + it "locates the tests directory" do + # since we don't know where the CI system will install this stuff, + # we need to go looking for a relative path to the SampleProjects directory + # just to get our "expected" value + relative_path = get_relative_dir(cpp_library.tests_dir) + expect(relative_path.to_s).to eq("#{sampleproject}/test") + end + end + + context "test_files" do + it "finds cpp files in directory" do + relative_paths = cpp_library.test_files.map { |f| get_relative_dir(f) } + expect(relative_paths.map(&:to_s)).to match_array(expected[:test_files].map(&:to_s)) + end + end end end context "test" do + cpp_lib_path = sampleproj_path + "DoSomething" + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), []) config = ArduinoCI::CIConfig.default after(:each) do |example| From 09b8cc27a5013b2e33bf2bd322b16f2519eed68f Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 18 Oct 2020 23:09:06 -0400 Subject: [PATCH 04/33] Appease rubocop --- .rubocop.yml | 6 ++++++ lib/arduino_ci/arduino_installation.rb | 10 +++++----- lib/arduino_ci/cpp_library.rb | 1 - 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ff2099a1..07841202 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,6 +31,12 @@ Layout/ExtraSpacing: Layout/EndOfLine: EnforcedStyle: lf +Layout/EndAlignment: + EnforcedStyleAlignWith: start_of_line + +Layout/CaseIndentation: + EnforcedStyle: end + Metrics/LineLength: Description: Limit lines to 80 characters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 5dfc5c8b..9ca32ada 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -110,11 +110,11 @@ def autolocate!(output = $stdout) # Forcibly install Arduino from the web # @return [bool] Whether the command succeeded def force_install(output = $stdout, version = DESIRED_ARDUINO_IDE_VERSION) - worker_class = case Host.os - when :osx then ArduinoDownloaderOSX - when :windows then ArduinoDownloaderWindows - when :linux then ArduinoDownloaderLinux - end + worker_class = case Host.os + when :osx then ArduinoDownloaderOSX + when :windows then ArduinoDownloaderWindows + when :linux then ArduinoDownloaderLinux + end worker = worker_class.new(version, output) worker.execute end diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index f5d92b69..7e108f05 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -170,7 +170,6 @@ def code_files_in(some_dir, extensions) raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname return [] unless some_dir.exist? && some_dir.directory? - real = some_dir.realpath files = some_dir.realpath.children.reject(&:directory?) cpp = files.select { |path| extensions.include?(path.extname.downcase) } not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") } From 8a14ad1ff357bb3e11851d54c4158c812c8dc948 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Sun, 18 Oct 2020 23:18:27 -0400 Subject: [PATCH 05/33] avoid error when testing for membership in a nonexistent directory --- CHANGELOG.md | 1 + lib/arduino_ci/cpp_library.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3358d162..f9f4fdce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Don't define `ostream& operator<<(nullptr_t)` if already defined by Apple +- `CppLibrary.in_tests_dir?` no longer produces an error if there is no tests directory ### Security diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 7e108f05..d2d14ee7 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -122,6 +122,8 @@ def vendor_bundle?(path) # @param path [Pathname] The path to check # @return [bool] def in_tests_dir?(path) + return false unless tests_dir.exist? + tests_dir_aliases = [tests_dir, tests_dir.realpath] # we could do this but some rubies don't return an enumerator for ascend # path.ascend.any? { |part| tests_dir_aliases.include?(part) } From 082ac955f4ada48d90e76a98109478dd44360ad3 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 26 Oct 2020 22:11:01 -0400 Subject: [PATCH 06/33] Add library.properties parsing --- CHANGELOG.md | 1 + lib/arduino_ci.rb | 1 + lib/arduino_ci/library_properties.rb | 86 ++++++++++++++++++++++ spec/library_properties_spec.rb | 45 +++++++++++ spec/properties/example.library.properties | 12 +++ 5 files changed, 145 insertions(+) create mode 100644 lib/arduino_ci/library_properties.rb create mode 100644 spec/library_properties_spec.rb create mode 100644 spec/properties/example.library.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f4fdce..6c9e0403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `arduino_ci_remote.rb` CLI switch `--skip-examples-compilation` - Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` - `CppLibrary.header_files` to find header files +- `LibraryProperties` to read metadata from Arduino libraries ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/lib/arduino_ci.rb b/lib/arduino_ci.rb index 14280084..344a1463 100644 --- a/lib/arduino_ci.rb +++ b/lib/arduino_ci.rb @@ -2,6 +2,7 @@ require "arduino_ci/arduino_installation" require "arduino_ci/cpp_library" require "arduino_ci/ci_config" +require "arduino_ci/library_properties" # ArduinoCI contains classes for automated testing of Arduino code on the command line # @author Ian Katz diff --git a/lib/arduino_ci/library_properties.rb b/lib/arduino_ci/library_properties.rb new file mode 100644 index 00000000..1a080713 --- /dev/null +++ b/lib/arduino_ci/library_properties.rb @@ -0,0 +1,86 @@ +module ArduinoCI + + # Information about an Arduino library package, as specified by the library.properties file + # + # See https://arduino.github.io/arduino-cli/library-specification/#libraryproperties-file-format + class LibraryProperties + + # @return [Hash] The properties file parsed as a hash + attr_reader :fields + + # @param path [Pathname] The path to the library.properties file + def initialize(path) + @fields = {} + File.foreach(path) do |line| + parts = line.split("=", 2) + @fields[parts[0]] = parts[1].chomp unless parts.empty? + end + end + + # Enable a shortcut syntax for library property accessors, in the style of `attr_accessor` metaprogramming. + # This is used to create a named field pointing to a specific property in the file, optionally applying + # a specific formatting function. + # + # The formatting function MUST be a static method on this class. This is a limitation caused by the desire + # to both (1) expose the formatters outside this class, and (2) use them for metaprogramming without the + # having to name the entire function. field_reader is a static method, so if not for the fact that + # `self.class.methods.include? formatter` fails to work for class methods in this context (unlike + # `self.methods.include?`, which properly finds instance methods), I would allow either one and just + # conditionally `define_method` the proper definition + # + # @param name [String] What the accessor will be called + # @param field_num [Integer] The name of the key of the property + # @param formatter [Symbol] The symbol for the formatting function to apply to the field (optional) + # @return [void] + # @macro [attach] field_reader + # @!attribute [r] $1 + # @return property $2 of the library.properties file, formatted with the function {$3} + def self.field_reader(name, formatter = nil) + key = name.to_s + if formatter.nil? + define_method(name) { @fields[key] } + else + define_method(name) { @fields.key?(key) ? self.class.send(formatter.to_sym, @fields[key]) : nil } + end + end + + # Parse a value as a comma-separated array + # @param input [String] + # @return [Array] The individual values + def self._csv(input) + input.split(",").map(&:strip) + end + + # Parse a value as a boolean + # @param input [String] + # @return [Array] The individual values + def self._bool(input) + input == "true" # no indication given in the docs that anything but lowercase "true" indicates boolean true. + end + + field_reader :name + field_reader :version + field_reader :author, :_csv + field_reader :maintainer + field_reader :sentence + field_reader :paragraph + field_reader :category + field_reader :url + field_reader :architectures, :_csv + field_reader :depends, :_csv + field_reader :dot_a_linkage, :_bool + field_reader :includes, :_csv + field_reader :precompiled, :_bool + field_reader :ldflags, :_csv + + # The value of sentence always will be prepended, so you should start by writing the second sentence here + # + # (according to the docs) + # @return [String] the sentence and paragraph together + def full_paragraph + [sentence, paragraph].join(" ") + end + + end + +end diff --git a/spec/library_properties_spec.rb b/spec/library_properties_spec.rb new file mode 100644 index 00000000..3c6de1ee --- /dev/null +++ b/spec/library_properties_spec.rb @@ -0,0 +1,45 @@ +require "spec_helper" + +RSpec.describe ArduinoCI::LibraryProperties do + + context "property extraction" do + library_properties = ArduinoCI::LibraryProperties.new(Pathname.new(__dir__) + "properties/example.library.properties") + + expected = { + string: { + name: "WebServer", + version: "1.0.0", + maintainer: "Cristian Maglie ", + sentence: "A library that makes coding a Webserver a breeze.", + paragraph: "Supports HTTP1.1 and you can do GET and POST.", + category: "Communication", + url: "http://example.com/", + }, + + bool: { + precompiled: true + }, + + csv: { + author: ["Cristian Maglie ", "Pippo Pluto "], + architectures: ["avr"], + includes: ["WebServer.h"], + depends: ["ArduinoHttpClient"], + }, + }.freeze + + expected.each do |atype, values| + values.each do |meth, val| + it "reads #{atype} field #{meth}" do + expect(library_properties.send(meth)).to eq(val) + end + end + end + + it "doesn't crash on nonexistent fields" do + expect(library_properties.dot_a_linkage).to be(nil) + end + end + + +end diff --git a/spec/properties/example.library.properties b/spec/properties/example.library.properties new file mode 100644 index 00000000..f0cd9bb3 --- /dev/null +++ b/spec/properties/example.library.properties @@ -0,0 +1,12 @@ +name=WebServer +version=1.0.0 +author=Cristian Maglie , Pippo Pluto +maintainer=Cristian Maglie +sentence=A library that makes coding a Webserver a breeze. +paragraph=Supports HTTP1.1 and you can do GET and POST. +category=Communication +url=http://example.com/ +architectures=avr +includes=WebServer.h +depends=ArduinoHttpClient +precompiled=true From da1cef610f25594ce21fa473251d5a767a299a51 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 26 Oct 2020 22:14:57 -0400 Subject: [PATCH 07/33] Add recursive library dependency compilation --- CHANGELOG.md | 2 + .../DependOnSomething/library.properties | 1 + .../DependOnSomething/src/YesDeps.cpp | 0 .../DependOnSomething/src/YesDeps.h | 0 SampleProjects/README.md | 1 + .../src/excludeThis/exclude-this.cpp | 6 --- .../src/excludeThis/exclude-this.h | 6 --- exe/arduino_ci.rb | 2 + lib/arduino_ci/cpp_library.rb | 51 ++++++++++++++++--- spec/cpp_library_spec.rb | 48 ++++++++++++++--- 10 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 SampleProjects/DependOnSomething/library.properties create mode 100644 SampleProjects/DependOnSomething/src/YesDeps.cpp create mode 100644 SampleProjects/DependOnSomething/src/YesDeps.h delete mode 100644 SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp delete mode 100644 SampleProjects/TestSomething/src/excludeThis/exclude-this.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c9e0403..4d587c9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` - `CppLibrary.header_files` to find header files - `LibraryProperties` to read metadata from Arduino libraries +- `CppLibrary.library_properties_path`, `CppLibrary.library_properties?`, `CppLibrary.library_properties` to expose library properties of a Cpp library +- `CppLibrary.arduino_library_dependencies` to list the dependent libraries specified by the library.properties file ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/SampleProjects/DependOnSomething/library.properties b/SampleProjects/DependOnSomething/library.properties new file mode 100644 index 00000000..ea93aeb0 --- /dev/null +++ b/SampleProjects/DependOnSomething/library.properties @@ -0,0 +1 @@ +depends=OnePointOhDummy,OnePointFiveDummy diff --git a/SampleProjects/DependOnSomething/src/YesDeps.cpp b/SampleProjects/DependOnSomething/src/YesDeps.cpp new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/DependOnSomething/src/YesDeps.h b/SampleProjects/DependOnSomething/src/YesDeps.h new file mode 100644 index 00000000..e69de29b diff --git a/SampleProjects/README.md b/SampleProjects/README.md index 834838c7..64bd3c8a 100644 --- a/SampleProjects/README.md +++ b/SampleProjects/README.md @@ -11,3 +11,4 @@ Because of this, these projects include some intentional quirks that differ from * "OnePointOhDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the "1.0" specification * "OnePointFiveMalformed" is a non-functional library meant to test file inclusion logic on libraries that attempt to conform to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) but fail to include a `src` directory * "OnePointFiveDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) +* "DependOnSomething" is a non-functional library meant to test file inclusion logic with dependencies diff --git a/SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp b/SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp deleted file mode 100644 index 11b7551e..00000000 --- a/SampleProjects/TestSomething/src/excludeThis/exclude-this.cpp +++ /dev/null @@ -1,6 +0,0 @@ -This file intentionally contains syntactically incorrect code -to break unit test compilation. If arduino_ci is working -properly, it should exclude this file (as per .arduino-ci.yml -configuration) and unit test compilation should succeed. - -~!@#$%^&*() diff --git a/SampleProjects/TestSomething/src/excludeThis/exclude-this.h b/SampleProjects/TestSomething/src/excludeThis/exclude-this.h deleted file mode 100644 index 11b7551e..00000000 --- a/SampleProjects/TestSomething/src/excludeThis/exclude-this.h +++ /dev/null @@ -1,6 +0,0 @@ -This file intentionally contains syntactically incorrect code -to break unit test compilation. If arduino_ci is working -properly, it should exclude this file (as per .arduino-ci.yml -configuration) and unit test compilation should succeed. - -~!@#$%^&*() diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index ded5415a..87b48a6d 100644 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -210,6 +210,8 @@ def perform_unit_tests(file_config) all_platform_info = {} config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) } + inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" } + # iterate boards / tests if !cpp_library.tests_dir.exist? inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index d2d14ee7..d28d393e 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -55,6 +55,19 @@ def initialize(base_dir, arduino_lib_dir, exclude_dirs) @vendor_bundle_cache = nil end + # The expected path to the library.properties file (i.e. even if it does not exist) + # @return [Pathname] + def library_properties_path + @base_dir + "library.properties" + end + + # Whether library.properties definitions for this library exist + # @return [bool] + def library_properties? + lib_props = library_properties_path + lib_props.exist? && lib_props.file? + end + # Decide whether this is a 1.5-compatible library # # according to https://arduino.github.io/arduino-cli/latest/library-specification @@ -62,9 +75,10 @@ def initialize(base_dir, arduino_lib_dir, exclude_dirs) # Should match logic from https://github.com/arduino/arduino-cli/blob/master/arduino/libraries/loader.go # @return [bool] def one_point_five? - lib_props = (@base_dir + "library.properties") + return false unless library_properties? + src_dir = (@base_dir + "src") - [lib_props, src_dir].all?(&:exist?) && lib_props.file? && src_dir.directory? + src_dir.exist? && src_dir.directory? end # Guess whether a file is part of the vendor bundle (indicating we should ignore it). @@ -164,6 +178,21 @@ def libasan?(gcc_binary) @has_libasan_cache[gcc_binary] end + # Library properties + def library_properties + return nil unless library_properties? + + LibraryProperties.new(library_properties_path) + end + + # Get a list of all dependencies as defined in library.properties + # @return [Array] The library names of the dependencies (not the paths) + def arduino_library_dependencies + return nil unless library_properties? + + library_properties.depends + end + # Get a list of all CPP source files in a directory and its subdirectories # @param some_dir [Pathname] The directory in which to begin the search # @param extensions [Array] The set of allowable file extensions @@ -282,16 +311,19 @@ def gcc_version(gcc_binary) @last_err end - # Arduino library directories containing sources + # Arduino library directories containing sources -- only those of the dependencies # @return [Array] def arduino_library_src_dirs(aux_libraries) # Pull in all possible places that headers could live, according to the spec: # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification - aux_libraries.map { |d| self.new(d, @arduino_lib_dir, @exclude_dirs).header_dirs }.flatten + aux_libraries.map { |d| self.class.new(@arduino_lib_dir + d, @arduino_lib_dir, @exclude_dirs).header_dirs }.flatten.uniq end # GCC command line arguments for including aux libraries + # + # This function recursively collects the library directores of the dependencies + # # @param aux_libraries [Array] The external Arduino libraries required by this project # @return [Array] The GCC command-line flags necessary to include those libraries def include_args(aux_libraries) @@ -354,6 +386,9 @@ def test_args(aux_libraries, ci_gcc_config) end # build a file for running a test of the given unit test file + # + # The dependent libraries configuration is appended with data from library.properties internal to the library under test + # # @param test_file [Pathname] The path to the file containing the unit tests # @param aux_libraries [Array] The external Arduino libraries required by this project # @param ci_gcc_config [Hash] The GCC config object @@ -372,8 +407,12 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g "-fsanitize=address" ] end - arg_sets << test_args(aux_libraries, ci_gcc_config) - arg_sets << cpp_files_libraries(aux_libraries).map(&:to_s) + + # combine library.properties defs (if existing) with config file. + # TODO: as much as I'd like to rely only on the properties file(s), I think that would prevent testing 1.0-spec libs + full_aux_libraries = arduino_library_dependencies.nil? ? aux_libraries : aux_libaries + arduino_library_dependencies + arg_sets << test_args(full_aux_libraries, ci_gcc_config) + arg_sets << cpp_files_libraries(full_aux_libraries).map(&:to_s) arg_sets << [test_file.to_s] args = arg_sets.flatten(1) return nil unless run_gcc(gcc_binary, *args) diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index 3e24a433..af934e60 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -14,58 +14,79 @@ def get_relative_dir(sampleprojects_tests_dir) next if skip_ruby_tests answers = { - "DoSomething": { + DoSomething: { one_five: false, cpp_files: [Pathname.new("DoSomething") + "do-something.cpp"], + cpp_files_libraries: [], header_dirs: [Pathname.new("DoSomething")], + arduino_library_src_dirs: [], test_files: [ "DoSomething/test/good-null.cpp", "DoSomething/test/good-library.cpp", "DoSomething/test/bad-null.cpp", ].map { |f| Pathname.new(f) } }, - "OnePointOhDummy": { + OnePointOhDummy: { one_five: false, cpp_files: [ "OnePointOhDummy/YesBase.cpp", "OnePointOhDummy/utility/YesUtil.cpp", ].map { |f| Pathname.new(f) }, + cpp_files_libraries: [], header_dirs: [ "OnePointOhDummy", "OnePointOhDummy/utility" ].map { |f| Pathname.new(f) }, + arduino_library_src_dirs: [], test_files: [] }, - "OnePointFiveMalformed": { + OnePointFiveMalformed: { one_five: false, cpp_files: [ "OnePointFiveMalformed/YesBase.cpp", "OnePointFiveMalformed/utility/YesUtil.cpp", ].map { |f| Pathname.new(f) }, + cpp_files_libraries: [], header_dirs: [ "OnePointFiveMalformed", "OnePointFiveMalformed/utility" ].map { |f| Pathname.new(f) }, + arduino_library_src_dirs: [], test_files: [] }, - "OnePointFiveDummy": { + OnePointFiveDummy: { one_five: true, cpp_files: [ "OnePointFiveDummy/src/YesSrc.cpp", "OnePointFiveDummy/src/subdir/YesSubdir.cpp", ].map { |f| Pathname.new(f) }, + cpp_files_libraries: [], header_dirs: [ "OnePointFiveDummy/src", "OnePointFiveDummy/src/subdir", ].map { |f| Pathname.new(f) }, + arduino_library_src_dirs: [], test_files: [] } - }.freeze + } + + # easier to construct this one from the other test cases + answers[:DependOnSomething] = { + one_five: true, + cpp_files: ["DependOnSomething/src/YesDeps.cpp"].map { |f| Pathname.new(f) }, + cpp_files_libraries: answers[:OnePointOhDummy][:cpp_files] + answers[:OnePointFiveDummy][:cpp_files], + header_dirs: ["DependOnSomething/src"].map { |f| Pathname.new(f) }, # this is not recursive! + arduino_library_src_dirs: answers[:OnePointOhDummy][:header_dirs] + answers[:OnePointFiveDummy][:header_dirs], + test_files: [] + } + + answers.freeze answers.each do |sampleproject, expected| context "#{sampleproject}" do cpp_lib_path = sampleproj_path + sampleproject.to_s - cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), []) + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, sampleproj_path, []) + dependencies = cpp_library.arduino_library_dependencies.nil? ? [] : cpp_library.arduino_library_dependencies it "detects 1.5 format" do expect(cpp_library.one_point_five?).to eq(expected[:one_five]) @@ -78,6 +99,13 @@ def get_relative_dir(sampleprojects_tests_dir) end end + context "cpp_files_libraries" do + it "finds cpp files in directories of dependencies" do + relative_paths = cpp_library.cpp_files_libraries(dependencies).map { |f| get_relative_dir(f) } + expect(relative_paths.map(&:to_s)).to match_array(expected[:cpp_files_libraries].map(&:to_s)) + end + end + context "header_dirs" do it "finds directories containing h files" do relative_paths = cpp_library.header_dirs.map { |f| get_relative_dir(f) } @@ -101,6 +129,14 @@ def get_relative_dir(sampleprojects_tests_dir) expect(relative_paths.map(&:to_s)).to match_array(expected[:test_files].map(&:to_s)) end end + + context "arduino_library_src_dirs" do + it "finds src dirs from dependent libraries" do + # we explicitly feed in the internal dependencies + relative_paths = cpp_library.arduino_library_src_dirs(dependencies).map { |f| get_relative_dir(f) } + expect(relative_paths.map(&:to_s)).to match_array(expected[:arduino_library_src_dirs].map(&:to_s)) + end + end end end From c2302ec42a3b5d5b505e67a0a2fb888c4afacd3c Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 26 Oct 2020 23:01:27 -0400 Subject: [PATCH 08/33] Fix unit tests for source directory exclusion --- .../ExcludeSomething/.arduino-ci.yml | 3 ++ SampleProjects/ExcludeSomething/README.md | 3 ++ .../ExcludeSomething/library.properties | 10 ++++ .../src/exclude-something.cpp | 4 ++ .../ExcludeSomething/src/exclude-something.h | 3 ++ .../src/excludeThis/exclude-this.cpp | 6 +++ .../src/excludeThis/exclude-this.h | 6 +++ SampleProjects/ExcludeSomething/test/null.cpp | 7 +++ SampleProjects/README.md | 1 + SampleProjects/TestSomething/.arduino-ci.yml | 2 - spec/cpp_library_spec.rb | 52 +++++++++++++++++++ spec/testsomething_unittests_spec.rb | 31 ----------- 12 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 SampleProjects/ExcludeSomething/.arduino-ci.yml create mode 100644 SampleProjects/ExcludeSomething/README.md create mode 100644 SampleProjects/ExcludeSomething/library.properties create mode 100644 SampleProjects/ExcludeSomething/src/exclude-something.cpp create mode 100644 SampleProjects/ExcludeSomething/src/exclude-something.h create mode 100644 SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.cpp create mode 100644 SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.h create mode 100644 SampleProjects/ExcludeSomething/test/null.cpp diff --git a/SampleProjects/ExcludeSomething/.arduino-ci.yml b/SampleProjects/ExcludeSomething/.arduino-ci.yml new file mode 100644 index 00000000..f35995f0 --- /dev/null +++ b/SampleProjects/ExcludeSomething/.arduino-ci.yml @@ -0,0 +1,3 @@ +unittest: + exclude_dirs: + - src/excludeThis diff --git a/SampleProjects/ExcludeSomething/README.md b/SampleProjects/ExcludeSomething/README.md new file mode 100644 index 00000000..5bcc6553 --- /dev/null +++ b/SampleProjects/ExcludeSomething/README.md @@ -0,0 +1,3 @@ +# ExcludeSomething + +This example exists to test directory-exclusion code of ArduinoCI diff --git a/SampleProjects/ExcludeSomething/library.properties b/SampleProjects/ExcludeSomething/library.properties new file mode 100644 index 00000000..1745537f --- /dev/null +++ b/SampleProjects/ExcludeSomething/library.properties @@ -0,0 +1,10 @@ +name=TestSomething +version=0.1.0 +author=Ian Katz +maintainer=Ian Katz +sentence=Arduino CI unit test example +paragraph=A skeleton library demonstrating file exclusion +category=Other +url=https://github.com/Arduino-CI/arduino_ci/SampleProjects/ExcludeSomething +architectures=avr,esp8266 +includes=do-something.h diff --git a/SampleProjects/ExcludeSomething/src/exclude-something.cpp b/SampleProjects/ExcludeSomething/src/exclude-something.cpp new file mode 100644 index 00000000..951953f7 --- /dev/null +++ b/SampleProjects/ExcludeSomething/src/exclude-something.cpp @@ -0,0 +1,4 @@ +#include "exclude-something.h" +int excludeSomething(void) { + return -1; +}; diff --git a/SampleProjects/ExcludeSomething/src/exclude-something.h b/SampleProjects/ExcludeSomething/src/exclude-something.h new file mode 100644 index 00000000..abacb177 --- /dev/null +++ b/SampleProjects/ExcludeSomething/src/exclude-something.h @@ -0,0 +1,3 @@ +#pragma once +#include +int excludeSomething(void); diff --git a/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.cpp b/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.cpp new file mode 100644 index 00000000..11b7551e --- /dev/null +++ b/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.cpp @@ -0,0 +1,6 @@ +This file intentionally contains syntactically incorrect code +to break unit test compilation. If arduino_ci is working +properly, it should exclude this file (as per .arduino-ci.yml +configuration) and unit test compilation should succeed. + +~!@#$%^&*() diff --git a/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.h b/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.h new file mode 100644 index 00000000..11b7551e --- /dev/null +++ b/SampleProjects/ExcludeSomething/src/excludeThis/exclude-this.h @@ -0,0 +1,6 @@ +This file intentionally contains syntactically incorrect code +to break unit test compilation. If arduino_ci is working +properly, it should exclude this file (as per .arduino-ci.yml +configuration) and unit test compilation should succeed. + +~!@#$%^&*() diff --git a/SampleProjects/ExcludeSomething/test/null.cpp b/SampleProjects/ExcludeSomething/test/null.cpp new file mode 100644 index 00000000..d58eca29 --- /dev/null +++ b/SampleProjects/ExcludeSomething/test/null.cpp @@ -0,0 +1,7 @@ +#include + +unittest(nothing) +{ +} + +unittest_main() diff --git a/SampleProjects/README.md b/SampleProjects/README.md index 64bd3c8a..8e5f5b11 100644 --- a/SampleProjects/README.md +++ b/SampleProjects/README.md @@ -12,3 +12,4 @@ Because of this, these projects include some intentional quirks that differ from * "OnePointFiveMalformed" is a non-functional library meant to test file inclusion logic on libraries that attempt to conform to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) but fail to include a `src` directory * "OnePointFiveDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) * "DependOnSomething" is a non-functional library meant to test file inclusion logic with dependencies +* "ExcludeSomething" is a non-functional library meant to test directory exclusion logic diff --git a/SampleProjects/TestSomething/.arduino-ci.yml b/SampleProjects/TestSomething/.arduino-ci.yml index 2aa851f1..f9890177 100644 --- a/SampleProjects/TestSomething/.arduino-ci.yml +++ b/SampleProjects/TestSomething/.arduino-ci.yml @@ -1,6 +1,4 @@ unittest: - exclude_dirs: - - src/excludeThis platforms: - uno - due diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index af934e60..ebffa2bb 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -10,6 +10,58 @@ def get_relative_dir(sampleprojects_tests_dir) sampleprojects_tests_dir.relative_path_from(base_dir) end + +RSpec.describe "ExcludeSomething C++" do + next if skip_cpp_tests + + cpp_lib_path = sampleproj_path + "ExcludeSomething" + context "without excludes" do + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, + Pathname.new("my_fake_arduino_lib_dir"), + []) + context "cpp_files" do + it "finds cpp files in directory" do + excludesomething_cpp_files = [ + Pathname.new("ExcludeSomething/src/exclude-something.cpp"), + Pathname.new("ExcludeSomething/src/excludeThis/exclude-this.cpp") + ] + relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } + expect(relative_paths).to match_array(excludesomething_cpp_files) + end + end + + context "unit tests" do + it "can't build due to files that should have been excluded" do + config = ArduinoCI::CIConfig.default.from_example(cpp_lib_path) + path = config.allowable_unittest_files(cpp_library.test_files).first + compiler = config.compilers_to_use.first + result = cpp_library.build_for_test_with_configuration(path, + [], + compiler, + config.gcc_config("uno")) + expect(result).to be nil + end + end + end + + context "with excludes" do + cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, + Pathname.new("my_fake_arduino_lib_dir"), + ["src/excludeThis"].map(&Pathname.method(:new))) + context "cpp_files" do + it "finds cpp files in directory" do + excludesomething_cpp_files = [ + Pathname.new("ExcludeSomething/src/exclude-something.cpp") + ] + relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } + expect(relative_paths).to match_array(excludesomething_cpp_files) + end + end + + end + +end + RSpec.describe ArduinoCI::CppLibrary do next if skip_ruby_tests diff --git a/spec/testsomething_unittests_spec.rb b/spec/testsomething_unittests_spec.rb index 08300fc5..4cb49541 100644 --- a/spec/testsomething_unittests_spec.rb +++ b/spec/testsomething_unittests_spec.rb @@ -10,37 +10,6 @@ def get_relative_dir(sampleprojects_tests_dir) sampleprojects_tests_dir.relative_path_from(base_dir) end -RSpec.describe "TestSomething C++ without excludes" do - next if skip_cpp_tests - cpp_lib_path = sampleproj_path + "TestSomething" - cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, - Pathname.new("my_fake_arduino_lib_dir"), - []) - context "cpp_files" do - it "finds cpp files in directory" do - testsomething_cpp_files = [ - Pathname.new("TestSomething/src/test-something.cpp"), - Pathname.new("TestSomething/src/excludeThis/exclude-this.cpp") - ] - relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) } - expect(relative_paths).to match_array(testsomething_cpp_files) - end - end - - context "unit tests" do - it "can't build due to files that should have been excluded" do - config = ArduinoCI::CIConfig.default.from_example(cpp_lib_path) - path = config.allowable_unittest_files(cpp_library.test_files).first - compiler = config.compilers_to_use.first - result = cpp_library.build_for_test_with_configuration(path, - [], - compiler, - config.gcc_config("uno")) - expect(result).to be nil - end - end - -end RSpec.describe "TestSomething C++" do next if skip_cpp_tests From 34aa9f9e95f213c1ed2341736238cc7cf9bb66c1 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Mon, 26 Oct 2020 23:13:47 -0400 Subject: [PATCH 09/33] reorder changelog sections --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d587c9f..ec56d07a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,16 +20,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Revise math macros to avoid name clashes - `CppLibrary` functions returning C++ header or code files now respect the 1.0/1.5 library specification +### Fixed +- Don't define `ostream& operator<<(nullptr_t)` if already defined by Apple +- `CppLibrary.in_tests_dir?` no longer produces an error if there is no tests directory + ### Deprecated - `arduino_ci_remote.rb` CLI switch `--skip-compilation` - Deprecated `arduino_ci_remote.rb` in favor of `arduino_ci.rb` ### Removed -### Fixed -- Don't define `ostream& operator<<(nullptr_t)` if already defined by Apple -- `CppLibrary.in_tests_dir?` no longer produces an error if there is no tests directory - ### Security From 14a551bca5a9da8a8a2f02f58c7d6a2c8d335f77 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 27 Oct 2020 00:09:52 -0400 Subject: [PATCH 10/33] Fix _SFR_IO8 macro definition -- use volatile keyword to prevent optimization --- CHANGELOG.md | 1 + SampleProjects/TestSomething/test/defines.cpp | 11 +++++++++++ cpp/arduino/avr/io.h | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec56d07a..a6ce4aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Don't define `ostream& operator<<(nullptr_t)` if already defined by Apple - `CppLibrary.in_tests_dir?` no longer produces an error if there is no tests directory +- The definition of the `_SFR_IO8` macro no longer produces errors about rvalues ### Deprecated - `arduino_ci_remote.rb` CLI switch `--skip-compilation` diff --git a/SampleProjects/TestSomething/test/defines.cpp b/SampleProjects/TestSomething/test/defines.cpp index 6b09d851..91810a03 100644 --- a/SampleProjects/TestSomething/test/defines.cpp +++ b/SampleProjects/TestSomething/test/defines.cpp @@ -8,4 +8,15 @@ unittest(binary) assertEqual(100, B1100100); } +#define DDRE _SFR_IO8(0x02) + +unittest(SFR_IO8) +{ + // in normal arduino code, you can do this. in arduino_ci, you might get an + // error like: cannot take the address of an rvalue of type 'int' + // + // this tests that directly + &DDRE; +} + unittest_main() diff --git a/cpp/arduino/avr/io.h b/cpp/arduino/avr/io.h index f07699a0..b39b894f 100644 --- a/cpp/arduino/avr/io.h +++ b/cpp/arduino/avr/io.h @@ -96,7 +96,7 @@ #ifndef _AVR_IO_H_ #define _AVR_IO_H_ -#define _SFR_IO8(io_addr) (io_addr) // this macro is all we need from the sfr file +#define _SFR_IO8(io_addr) (*(volatile uint8_t *)(io_addr)) // this macro is all we need from the sfr file #if defined (__AVR_AT94K__) # include "ioat94k.h" From 2af1a4cb377b71c6f6c6f1483f6c18c3c213924a Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 27 Oct 2020 09:00:56 -0400 Subject: [PATCH 11/33] Add dummy test files to sample projects to ensure they aren't included in C++ file listings --- SampleProjects/DependOnSomething/test/null.cpp | 7 +++++++ SampleProjects/OnePointFiveDummy/test/null.cpp | 7 +++++++ SampleProjects/OnePointOhDummy/test/null.cpp | 7 +++++++ spec/cpp_library_spec.rb | 12 +++++++++--- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 SampleProjects/DependOnSomething/test/null.cpp create mode 100644 SampleProjects/OnePointFiveDummy/test/null.cpp create mode 100644 SampleProjects/OnePointOhDummy/test/null.cpp diff --git a/SampleProjects/DependOnSomething/test/null.cpp b/SampleProjects/DependOnSomething/test/null.cpp new file mode 100644 index 00000000..d58eca29 --- /dev/null +++ b/SampleProjects/DependOnSomething/test/null.cpp @@ -0,0 +1,7 @@ +#include + +unittest(nothing) +{ +} + +unittest_main() diff --git a/SampleProjects/OnePointFiveDummy/test/null.cpp b/SampleProjects/OnePointFiveDummy/test/null.cpp new file mode 100644 index 00000000..d58eca29 --- /dev/null +++ b/SampleProjects/OnePointFiveDummy/test/null.cpp @@ -0,0 +1,7 @@ +#include + +unittest(nothing) +{ +} + +unittest_main() diff --git a/SampleProjects/OnePointOhDummy/test/null.cpp b/SampleProjects/OnePointOhDummy/test/null.cpp new file mode 100644 index 00000000..d58eca29 --- /dev/null +++ b/SampleProjects/OnePointOhDummy/test/null.cpp @@ -0,0 +1,7 @@ +#include + +unittest(nothing) +{ +} + +unittest_main() diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index ebffa2bb..6a25468f 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -90,7 +90,9 @@ def get_relative_dir(sampleprojects_tests_dir) "OnePointOhDummy/utility" ].map { |f| Pathname.new(f) }, arduino_library_src_dirs: [], - test_files: [] + test_files: [ + "OnePointOhDummy/test/null.cpp", + ].map { |f| Pathname.new(f) } }, OnePointFiveMalformed: { one_five: false, @@ -118,7 +120,9 @@ def get_relative_dir(sampleprojects_tests_dir) "OnePointFiveDummy/src/subdir", ].map { |f| Pathname.new(f) }, arduino_library_src_dirs: [], - test_files: [] + test_files: [ + "OnePointFiveDummy/test/null.cpp", + ].map { |f| Pathname.new(f) } } } @@ -129,7 +133,9 @@ def get_relative_dir(sampleprojects_tests_dir) cpp_files_libraries: answers[:OnePointOhDummy][:cpp_files] + answers[:OnePointFiveDummy][:cpp_files], header_dirs: ["DependOnSomething/src"].map { |f| Pathname.new(f) }, # this is not recursive! arduino_library_src_dirs: answers[:OnePointOhDummy][:header_dirs] + answers[:OnePointFiveDummy][:header_dirs], - test_files: [] + test_files: [ + "DependOnSomething/test/null.cpp", + ].map { |f| Pathname.new(f) } } answers.freeze From be95d0ca37abb540b2434be0c4fb8e44965627fc Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 27 Oct 2020 09:43:44 -0400 Subject: [PATCH 12/33] Print unit test stack traces if encountered --- CHANGELOG.md | 1 + lib/arduino_ci/cpp_library.rb | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6ce4aa8..aed6a198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `LibraryProperties` to read metadata from Arduino libraries - `CppLibrary.library_properties_path`, `CppLibrary.library_properties?`, `CppLibrary.library_properties` to expose library properties of a Cpp library - `CppLibrary.arduino_library_dependencies` to list the dependent libraries specified by the library.properties file +- `CppLibrary.print_stack_dump` prints stack trace dumps (on Windows specifically) to the console if encountered ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index d28d393e..e61201c2 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -421,14 +421,31 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g executable end + # print any found stack dumps + # @param executable [Pathname] the path to the test file + def print_stack_dump(executable) + possible_dumpfiles = [ + executable.sub_ext(executable.extname + ".stackdump") + ] + possible_dumpfiles.select(&:exist?).each do |dump| + puts "========== Stack dump from #{dump}:" + File.foreach(dump) { |line| print " #{line}" } + end + end + # run a test file - # @param [Pathname] the path to the test file + # @param executable [Pathname] the path to the test file # @return [bool] whether all tests were successful def run_test_file(executable) @last_cmd = executable @last_out = "" @last_err = "" - Host.run_and_output(executable.to_s.shellescape) + ret = Host.run_and_output(executable.to_s.shellescape) + + # print any stack traces found during a failure + print_stack_dump(executable) unless ret + + ret end end From 42bc4fe3af2b4d3d08eeaa347e0bf279c63ac22d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 25 Aug 2020 17:48:51 +0200 Subject: [PATCH 13/33] Show current platform while running unittests --- exe/arduino_ci.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 87b48a6d..4ea2d614 100644 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -234,7 +234,7 @@ def perform_unit_tests(file_config) config.allowable_unittest_files(cpp_library.test_files).each do |unittest_path| unittest_name = unittest_path.basename.to_s compilers.each do |gcc_binary| - attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary}") do + attempt_multiline("Unit testing #{unittest_name} with #{gcc_binary} for #{p}") do exe = cpp_library.build_for_test_with_configuration( unittest_path, config.aux_libraries_for_unittest, From 7d7d8103b877f6d9ddda84552a95ce3097ade3c3 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 25 Aug 2020 17:57:23 +0200 Subject: [PATCH 14/33] Make SPI.h work on non-AVR unittests The SPI library contains some code to read the SPCR register to determine the byte order of 16-bit transfers, presumably because there is no official Arduino API to set it, so sketches had to rely on setting this register directly. By reading it, the SPI unittest implementation can detect how to emulate multibyte transfers. However, this register is only defined by avr/io.h when the unittest emulates an AVR platform, so this code would fail to compile on other platforms. This adds a preprocessor guard around this code, defaulting to the lsb-first, which is also the hardware and Arduino default. This fixes #140. --- cpp/arduino/SPI.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cpp/arduino/SPI.h b/cpp/arduino/SPI.h index f10cd201..e22be357 100644 --- a/cpp/arduino/SPI.h +++ b/cpp/arduino/SPI.h @@ -94,10 +94,14 @@ class SPIClass: public ObservableDataStream { uint16_t transfer16(uint16_t data) { union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; in.val = data; + #if defined(SPCR) && defined(DORD) if (!(SPCR & (1 << DORD))) { out.msb = transfer(in.msb); out.lsb = transfer(in.lsb); - } else { + } + else + #endif + { out.lsb = transfer(in.lsb); out.msb = transfer(in.msb); } From 22976613fbc0f337d9584907753c5ae9c9dd8c73 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 10:49:43 +0200 Subject: [PATCH 15/33] Do not include SPI/Wire from Arduino.h This is not done on a regular Arduino either, so this needlessly pollutes the namespace and might cause issues in some rare cases. This also changes one testcase that uses SPI to include SPI.h, since it previously relied on Arduino.h to do so. --- SampleProjects/TestSomething/test/godmode.cpp | 1 + cpp/arduino/Arduino.h | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/SampleProjects/TestSomething/test/godmode.cpp b/SampleProjects/TestSomething/test/godmode.cpp index e6c69502..15c13f3c 100644 --- a/SampleProjects/TestSomething/test/godmode.cpp +++ b/SampleProjects/TestSomething/test/godmode.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "fibonacciClock.h" GodmodeState* state = GODMODE(); diff --git a/cpp/arduino/Arduino.h b/cpp/arduino/Arduino.h index e107126e..4cec73ad 100644 --- a/cpp/arduino/Arduino.h +++ b/cpp/arduino/Arduino.h @@ -14,8 +14,6 @@ Where possible, variable names from the Arduino library are used to avoid confli #include "Print.h" #include "Stream.h" #include "HardwareSerial.h" -#include "SPI.h" -#include "Wire.h" typedef bool boolean; typedef uint8_t byte; From 97a499cd4f8ddcd52860e1cb0022c9ca1219078b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 25 Aug 2020 20:38:51 +0200 Subject: [PATCH 16/33] Complete and fix defines for various boards This adds relevant defines that identify the architecture and board currently compiled for. Most of these are usually set by the platform's platform.txt and boards.txt, except for the __AVR* defines that are set by avr-gcc internally. This only adds extra defines, except for the Arduino Due, which previously incorrectly identified as an ATmega328p. This seems to fix part of #89. --- misc/default.yml | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/misc/default.yml b/misc/default.yml index 67d7f87f..9fa60041 100644 --- a/misc/default.yml +++ b/misc/default.yml @@ -22,7 +22,10 @@ platforms: gcc: features: defines: + - __AVR__ - __AVR_ATmega328P__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_UNO warnings: flags: due: @@ -31,7 +34,9 @@ platforms: gcc: features: defines: - - __AVR_ATmega328__ + - __SAM3X8E__ + - ARDUINO_ARCH_SAM + - ARDUINO_SAM_DUE warnings: flags: zero: @@ -40,8 +45,9 @@ platforms: gcc: features: defines: - - __SAMD21G18A__ - - ARDUINO_SAMD_ZERO + - __SAMD21G18A__ + - ARDUINO_ARCH_SAMD + - ARDUINO_SAMD_ZERO warnings: flags: esp32: @@ -50,6 +56,9 @@ platforms: gcc: features: defines: + - ESP32 + - ARDUINO_ARCH_ESP32 + - ARDUINO_FEATHER_ESP32 warnings: flags: esp8266: @@ -58,6 +67,9 @@ platforms: gcc: features: defines: + - ESP8266 + - ARDUINO_ARCH_ESP8266 + - ARDUINO_ESP8266_ESP12 warnings: flags: leonardo: @@ -66,7 +78,10 @@ platforms: gcc: features: defines: + - __AVR__ - __AVR_ATmega32U4__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_LEONARDO warnings: flags: trinket: @@ -75,6 +90,10 @@ platforms: gcc: features: defines: + - __AVR__ + - __AVR_ATtiny85__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_TRINKET5 warnings: flags: gemma: @@ -83,6 +102,10 @@ platforms: gcc: features: defines: + - __AVR__ + - __AVR_ATtiny85__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_GEMMA warnings: flags: m4: @@ -91,6 +114,10 @@ platforms: gcc: features: defines: + - __SAMD51__ + - __SAMD51J19A__ + - ARDUINO_ARCH_SAMD + - ARDUINO_METRO_M4 warnings: flags: mega2560: @@ -99,7 +126,10 @@ platforms: gcc: features: defines: + - __AVR__ - __AVR_ATmega2560__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_MEGA2560 warnings: flags: cplayClassic: @@ -108,6 +138,10 @@ platforms: gcc: features: defines: + - __AVR__ + - __AVR_ATmega32U4__ + - ARDUINO_ARCH_AVR + - ARDUINO_AVR_CIRCUITPLAY warnings: flags: cplayExpress: @@ -116,6 +150,9 @@ platforms: gcc: features: defines: + - __SAMD21G18A__ + - ARDUINO_ARCH_SAMD + - ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS warnings: flags: From 6bfa32cc7e432a578956ce04bc0f952117b4706b Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 11:18:41 +0200 Subject: [PATCH 17/33] Only include avr/io.h if __AVR__ is defined When this file is included, but no MCU type (e.g. `__AVR_ATmega328p__`) is defined, a warning is generated and only a partial set of AVR-specific macros is defined. When using a non-AVR target, no warning should be generated and none of these AVR-specific macros shoudl be made available, so this only includes avr/io.h from `Godmode.h` when `__AVR__` is defined, and do not include it from `avr/pgmspace.h` at all, since it is not actually needed there. This introduces a small compatibility issue: Until recently, `__AVR__` was never defined when running unittests. All included boards now do so, but if anyone has defined custom AVR boards without defining `__AVR__` in their `.arduino-ci.yaml` file, those would no longer work. This is easy enough to fix, though, just add the `__AVR__` define. --- cpp/arduino/Godmode.h | 2 ++ cpp/arduino/avr/pgmspace.h | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index de7a299e..72cd48e1 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -1,6 +1,8 @@ #pragma once #include "ArduinoDefines.h" +#if defined(__AVR__) #include +#endif #include "WString.h" #include "PinHistory.h" diff --git a/cpp/arduino/avr/pgmspace.h b/cpp/arduino/avr/pgmspace.h index 6b21287b..725eef73 100644 --- a/cpp/arduino/avr/pgmspace.h +++ b/cpp/arduino/avr/pgmspace.h @@ -14,7 +14,6 @@ out = externs.map {|l| l.split("(")[0].split(" ")[-1].gsub("*", "") }.uniq out.each { |l| puts d(l) } */ -#include #include #define PROGMEM From c4c753c529e5ede478c104ebcfe72c66626e3865 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 11:00:58 +0200 Subject: [PATCH 18/33] Allow specifying NUM_SERIAL_PORTS explicitly For AVR-based unittests, this was autodetected based on the registers defined in `avr/io.h`, just like on an actual build. For non-AVR targets, `NUM_SERIAL_PORTS` would always be 0. Now, any existing define on the compiler commandline (i.e. from `.arduino-ci.yaml`) takes precedence over any autodetected value. --- cpp/arduino/Godmode.h | 22 ++++++++++++---------- cpp/arduino/HardwareSerial.h | 8 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index 72cd48e1..30596167 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -20,16 +20,18 @@ unsigned long micros(); #define MOCK_PINS_COUNT 256 -#if defined(UBRR3H) - #define NUM_SERIAL_PORTS 4 -#elif defined(UBRR2H) - #define NUM_SERIAL_PORTS 3 -#elif defined(UBRR1H) - #define NUM_SERIAL_PORTS 2 -#elif defined(UBRRH) || defined(UBRR0H) - #define NUM_SERIAL_PORTS 1 -#else - #define NUM_SERIAL_PORTS 0 +#if (!defined NUM_SERIAL_PORTS) + #if defined(UBRR3H) + #define NUM_SERIAL_PORTS 4 + #elif defined(UBRR2H) + #define NUM_SERIAL_PORTS 3 + #elif defined(UBRR1H) + #define NUM_SERIAL_PORTS 2 + #elif defined(UBRRH) || defined(UBRR0H) + #define NUM_SERIAL_PORTS 1 + #else + #define NUM_SERIAL_PORTS 0 + #endif #endif class GodmodeState { diff --git a/cpp/arduino/HardwareSerial.h b/cpp/arduino/HardwareSerial.h index d4ea97f9..68c2010c 100644 --- a/cpp/arduino/HardwareSerial.h +++ b/cpp/arduino/HardwareSerial.h @@ -44,19 +44,19 @@ class HardwareSerial : public StreamTape operator bool() { return true; } }; -#if defined(UBRRH) || defined(UBRR0H) +#if NUM_SERIAL_PORTS >= 1 extern HardwareSerial Serial; #define HAVE_HWSERIAL0 #endif -#if defined(UBRR1H) +#if NUM_SERIAL_PORTS >= 2 extern HardwareSerial Serial1; #define HAVE_HWSERIAL1 #endif -#if defined(UBRR2H) +#if NUM_SERIAL_PORTS >= 3 extern HardwareSerial Serial2; #define HAVE_HWSERIAL2 #endif -#if defined(UBRR3H) +#if NUM_SERIAL_PORTS >= 4 extern HardwareSerial Serial3; #define HAVE_HWSERIAL3 #endif From 9eab3cd742597cd3eeeab16ff7351287bb7c0bc4 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 11:16:12 +0200 Subject: [PATCH 19/33] Specify NUM_SERIAL_PORTS for non-AVR targets For AVR-targets, this value is autodetected based on the MCU type, but for other targets it must be explicitly specified. --- misc/default.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/misc/default.yml b/misc/default.yml index 9fa60041..1bef2e27 100644 --- a/misc/default.yml +++ b/misc/default.yml @@ -37,6 +37,7 @@ platforms: - __SAM3X8E__ - ARDUINO_ARCH_SAM - ARDUINO_SAM_DUE + - NUM_SERIAL_PORTS=4 warnings: flags: zero: @@ -48,6 +49,8 @@ platforms: - __SAMD21G18A__ - ARDUINO_ARCH_SAMD - ARDUINO_SAMD_ZERO + # This also has SerialUSB, which is not included here. + - NUM_SERIAL_PORTS=2 warnings: flags: esp32: @@ -59,6 +62,7 @@ platforms: - ESP32 - ARDUINO_ARCH_ESP32 - ARDUINO_FEATHER_ESP32 + - NUM_SERIAL_PORTS=3 warnings: flags: esp8266: @@ -70,6 +74,7 @@ platforms: - ESP8266 - ARDUINO_ARCH_ESP8266 - ARDUINO_ESP8266_ESP12 + - NUM_SERIAL_PORTS=2 warnings: flags: leonardo: @@ -118,6 +123,8 @@ platforms: - __SAMD51J19A__ - ARDUINO_ARCH_SAMD - ARDUINO_METRO_M4 + # Serial is actually USB virtual serial, not HardwareSerial + - NUM_SERIAL_PORTS=2 warnings: flags: mega2560: @@ -153,6 +160,8 @@ platforms: - __SAMD21G18A__ - ARDUINO_ARCH_SAMD - ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS + # Serial is actually an alias of SerialUSB, not a HardwareSerial + - NUM_SERIAL_PORTS=2 warnings: flags: From 77d8737358416a9c528d26750a0899d3bad3843d Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 10:30:44 +0200 Subject: [PATCH 20/33] Fix pgm_read_ptr_near/far This was missing one level of pointer indirection, making any use of these functions fail at compiletime. --- cpp/arduino/avr/pgmspace.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/arduino/avr/pgmspace.h b/cpp/arduino/avr/pgmspace.h index 725eef73..b3b5a319 100644 --- a/cpp/arduino/avr/pgmspace.h +++ b/cpp/arduino/avr/pgmspace.h @@ -33,13 +33,13 @@ out.each { |l| puts d(l) } #define pgm_read_word_near(address_short) (* (const uint16_t *) (address_short) ) #define pgm_read_dword_near(address_short) (* (const uint32_t *) (address_short) ) #define pgm_read_float_near(address_short) (* (const float *) (address_short) ) -#define pgm_read_ptr_near(address_short) (* (const void *) (address_short) ) +#define pgm_read_ptr_near(address_short) (* (const void **) (address_short) ) #define pgm_read_byte_far(address_long) (* (const uint8_t *) (address_long) ) #define pgm_read_word_far(address_long) (* (const uint16_t *) (address_long) ) #define pgm_read_dword_far(address_long) (* (const uint32_t *) (address_long) ) #define pgm_read_float_far(address_long) (* (const float *) (address_long) ) -#define pgm_read_ptr_far(address_long) (* (const void *) (address_long) ) +#define pgm_read_ptr_far(address_long) (* (const void **) (address_long) ) #define pgm_read_byte(address_short) pgm_read_byte_near(address_short) #define pgm_read_word(address_short) pgm_read_word_near(address_short) From 614ffecf80eeeaa241d465de0c9af89a1de59192 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 10:23:36 +0200 Subject: [PATCH 21/33] Replace _P macros with inline functions Previously, functions like memcpy_P were replaced by their non-progmem version using macros. However, these macros added a :: prefix, presumably to allow e.g. memcpy_P to be used in a context where a (namespace or class) local version of memcpy is defined. Adding the :: makes sure the global version is used. However, if the actual invocation of e.g. memcpy_P also uses this prefix (e.g. `return ::memcpy_P(...)`, to disambiguate when a local version of `memcpy_P` is also defined), this results in a double prefix and compilation failure. To fix this, this commit replaces the wrapper macros with inline functions that call the non-progmem version normally. These inline functions were mostly mechanically generated from the original avr/pgmspace.h documentation, removing some AVR-specific functions that do not have a standard equivalent and adding som casts here and there (where the progmem version returns `char*` and the regular version `const char*`). --- cpp/arduino/avr/pgmspace.h | 93 ++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/cpp/arduino/avr/pgmspace.h b/cpp/arduino/avr/pgmspace.h index b3b5a319..5225363c 100644 --- a/cpp/arduino/avr/pgmspace.h +++ b/cpp/arduino/avr/pgmspace.h @@ -15,6 +15,7 @@ out.each { |l| puts d(l) } */ #include +#include #define PROGMEM @@ -26,6 +27,11 @@ out.each { |l| puts d(l) } #define PGM_VOID_P const void * #endif +// These are normally 32-bit, but here use (u)intptr_t to ensure a pointer can +// always be safely cast to these types. +typedef intptr_t int_farptr_t; +typedef uintptr_t uint_farptr_t; + // everything's a no-op #define PSTR(s) ((const char *)(s)) @@ -49,46 +55,47 @@ out.each { |l| puts d(l) } #define pgm_get_far_address(var) ( (uint_farptr_t) (&(var)) ) -#define memchr_P(...) ::memchr(__VA_ARGS__) -#define memcmp_P(...) ::memcmp(__VA_ARGS__) -#define memccpy_P(...) ::memccpy(__VA_ARGS__) -#define memcpy_P(...) ::memcpy(__VA_ARGS__) -#define memmem_P(...) ::memmem(__VA_ARGS__) -#define memrchr_P(...) ::memrchr(__VA_ARGS__) -#define strcat_P(...) ::strcat(__VA_ARGS__) -#define strchr_P(...) ::strchr(__VA_ARGS__) -#define strchrnul_P(...) ::strchrnul(__VA_ARGS__) -#define strcmp_P(...) ::strcmp(__VA_ARGS__) -#define strcpy_P(...) ::strcpy(__VA_ARGS__) -#define strcasecmp_P(...) ::strcasecmp(__VA_ARGS__) -#define strcasestr_P(...) ::strcasestr(__VA_ARGS__) -#define strcspn_P(...) ::strcspn(__VA_ARGS__) -#define strlcat_P(...) ::strlcat(__VA_ARGS__) -#define strlcpy_P(...) ::strlcpy(__VA_ARGS__) -#define strnlen_P(...) ::strnlen(__VA_ARGS__) -#define strncmp_P(...) ::strncmp(__VA_ARGS__) -#define strncasecmp_P(...) ::strncasecmp(__VA_ARGS__) -#define strncat_P(...) ::strncat(__VA_ARGS__) -#define strncpy_P(...) ::strncpy(__VA_ARGS__) -#define strpbrk_P(...) ::strpbrk(__VA_ARGS__) -#define strrchr_P(...) ::strrchr(__VA_ARGS__) -#define strsep_P(...) ::strsep(__VA_ARGS__) -#define strspn_P(...) ::strspn(__VA_ARGS__) -#define strstr_P(...) ::strstr(__VA_ARGS__) -#define strtok_P(...) ::strtok(__VA_ARGS__) -#define strtok_P(...) ::strtok(__VA_ARGS__) -#define strlen_P(...) ::strlen(__VA_ARGS__) -#define strnlen_P(...) ::strnlen(__VA_ARGS__) -#define memcpy_P(...) ::memcpy(__VA_ARGS__) -#define strcpy_P(...) ::strcpy(__VA_ARGS__) -#define strncpy_P(...) ::strncpy(__VA_ARGS__) -#define strcat_P(...) ::strcat(__VA_ARGS__) -#define strlcat_P(...) ::strlcat(__VA_ARGS__) -#define strncat_P(...) ::strncat(__VA_ARGS__) -#define strcmp_P(...) ::strcmp(__VA_ARGS__) -#define strncmp_P(...) ::strncmp(__VA_ARGS__) -#define strcasecmp_P(...) ::strcasecmp(__VA_ARGS__) -#define strncasecmp_P(...) ::strncasecmp(__VA_ARGS__) -#define strstr_P(...) ::strstr(__VA_ARGS__) -#define strlcpy_P(...) ::strlcpy(__VA_ARGS__) -#define memcmp_P(...) ::memcmp(__VA_ARGS__) +inline const void * memchr_P(const void *s, int val, size_t len) { return memchr(s, val, len); } +inline int memcmp_P(const void *s1, const void *s2, size_t len) { return memcmp(s1, s2, len); } +inline void *memccpy_P(void *dest, const void *src, int val, size_t len) { return memccpy(dest, src, val, len); } +inline void *memcpy_P(void *dest, const void *src, size_t n) { return memcpy(dest, src, n); } +inline void *memmem_P(const void *s1, size_t len1, const void *s2, size_t len2) { return memmem(s1, len1, s2, len2); } +inline const void *memrchr_P(const void *src, int val, size_t len) { return memrchr(src, val, len); } +inline char *strcat_P(char *dest, const char *src) { return strcat(dest, src); } +inline const char *strchr_P(const char *s, int val) { return strchr(s, val); } +inline const char *strchrnul_P(const char *s, int c) { return strchrnul(s, c); } +inline int strcmp_P(const char *s1, const char *s2) { return strcmp(s1, s2); } +inline char *strcpy_P(char *dest, const char *src) { return strcpy(dest, src); } +inline int strcasecmp_P(const char *s1, const char *s2) { return strcasecmp(s1, s2); } +inline char *strcasestr_P(const char *s1, const char *s2) { return (char*)strcasestr(s1, s2); } +inline size_t strcspn_P(const char *s, const char *reject) { return strcspn(s, reject); } +// strlcat and strlcpy are AVR-specific and not entirely trivial to reimplement using strncat it seems +//inline size_t strlcat_P(char *dst, const char *src, size_t siz) { return strlcat(dst, src, siz); } +//inline size_t strlcpy_P(char *dst, const char *src, size_t siz) { return strlcpy(dst, src, siz); } +//inline size_t strlcat_PF(char *dst, uint_farptr_t src, size_t n) { return strlcat(dst, (const char*)src, n); } +//inline size_t strlcpy_PF(char *dst, uint_farptr_t src, size_t siz) { return strlcpy(dst, (const char*)src, siz); } +inline int strncmp_P(const char *s1, const char *s2, size_t n) { return strncmp(s1, s2, n); } +inline int strncasecmp_P(const char *s1, const char *s2, size_t n) { return strncasecmp(s1, s2, n); } +inline char *strncat_P(char *dest, const char *src, size_t len) { return strncat(dest, src, len); } +inline char *strncpy_P(char *dest, const char *src, size_t n) { return strncpy(dest, src, n); } +inline char *strpbrk_P(const char *s, const char *accept) { return (char*)strpbrk(s, accept); } +inline const char *strrchr_P(const char *s, int val) { return strrchr(s, val); } +inline char *strsep_P(char **sp, const char *delim) { return strsep(sp, delim); } +inline size_t strspn_P(const char *s, const char *accept) { return strspn(s, accept); } +inline char *strstr_P(const char *s1, const char *s2) { return (char*)strstr(s1, s2); } +inline char *strtok_P(char *s, const char * delim) { return strtok(s, delim); } +inline char *strtok_r_P(char *string, const char *delim, char **last) { return strtok_r(string, delim, last); } +inline size_t strlen_PF(uint_farptr_t s) { return strlen((char*)s); } +inline size_t strnlen_P(uint_farptr_t s, size_t len) { return strnlen((char*)s, len); } +inline void *memcpy_PF(void *dest, uint_farptr_t src, size_t n) { return memcpy(dest, (const char*)src, n); } +inline char *strcpy_PF(char *dst, uint_farptr_t src) { return strcpy(dst, (const char*)src); } +inline char *strncpy_PF(char *dst, uint_farptr_t src, size_t n) { return strncpy(dst, (const char*)src, n); } +inline char *strcat_PF(char *dst, uint_farptr_t src) { return strcat(dst, (const char*)src); } +inline char *strncat_PF(char *dst, uint_farptr_t src, size_t n) { return strncat(dst, (const char*)src, n); } +inline int strcmp_PF(const char *s1, uint_farptr_t s2) { return strcmp(s1, (const char*)s2); } +inline int strncmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return strncmp(s1, (const char*)s2, n); } +inline int strcasecmp_PF(const char *s1, uint_farptr_t s2) { return strcasecmp(s1, (const char*)s2); } +inline int strncasecmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return strncasecmp(s1, (const char*)s2, n); } +inline char *strstr_PF(const char *s1, uint_farptr_t s2) { return (char*)strstr(s1, (const char*)s2); } +inline int memcmp_PF(const void *s1, uint_farptr_t s2, size_t len) { return memcmp(s1, (const char*)s2, len); } +inline size_t strlen_P(const char *src) { return strlen(src); } From 1df399534ea8e7e62231e66b7a50ce2b00991107 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 26 Aug 2020 10:30:11 +0200 Subject: [PATCH 22/33] Mock stdio.h progmem functions --- cpp/arduino/avr/pgmspace.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cpp/arduino/avr/pgmspace.h b/cpp/arduino/avr/pgmspace.h index 5225363c..eb7050ad 100644 --- a/cpp/arduino/avr/pgmspace.h +++ b/cpp/arduino/avr/pgmspace.h @@ -15,7 +15,9 @@ out.each { |l| puts d(l) } */ #include +#include #include +#include #define PROGMEM @@ -99,3 +101,21 @@ inline int strncasecmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return s inline char *strstr_PF(const char *s1, uint_farptr_t s2) { return (char*)strstr(s1, (const char*)s2); } inline int memcmp_PF(const void *s1, uint_farptr_t s2, size_t len) { return memcmp(s1, (const char*)s2, len); } inline size_t strlen_P(const char *src) { return strlen(src); } + +// These are normally defined by stdio.h on AVR, but we cannot override that +// include file (at least not without no longer being able to include the +// original as well), so just define these here. It seems likely that any +// sketch that uses these progmem-stdio functions will also include pgmspace.h +inline int vfprintf_P(FILE *stream, const char *__fmt, va_list __ap) { return vfprintf(stream, __fmt, __ap); } +inline int printf_P(const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vprintf(__fmt, args); va_end(args); } +inline int sprintf_P(char *s, const char *__fmt, ...) { va_list args; va_start(args, __fmt); return sprintf(s, __fmt, args); va_end(args); } +inline int snprintf_P(char *s, size_t __n, const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vsnprintf(s, __n, __fmt, args); va_end(args); } +inline int vsprintf_P(char *s, const char *__fmt, va_list ap) { return vsprintf(s, __fmt, ap); } +inline int vsnprintf_P(char *s, size_t __n, const char *__fmt, va_list ap) { return vsnprintf(s, __n, __fmt, ap); } +inline int fprintf_P(FILE *stream, const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vfprintf(stream, __fmt, args); va_end(args); } +inline int fputs_P(const char *str, FILE *__stream) { return fputs(str, __stream); } +inline int puts_P(const char *str) { return puts(str); } +inline int vfscanf_P(FILE *stream, const char *__fmt, va_list __ap) { return vfscanf(stream, __fmt, __ap); } +inline int fscanf_P(FILE *stream, const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vfscanf(stream, __fmt, args); va_end(args); } +inline int scanf_P(const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vscanf(__fmt, args); va_end(args); } +inline int sscanf_P(const char *buf, const char *__fmt, ...) { va_list args; va_start(args, __fmt); return vsscanf(buf, __fmt, args); va_end(args); } From a83cb632421c8aeea8172bbc1542ca51c784bc05 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Fri, 6 Nov 2020 12:09:21 +0100 Subject: [PATCH 23/33] Remove some functions from pgmspace.h that break on cygwin Somehow, these functions cannot be found when compiling the test build under cygwin, even though it seems they should be available. To at least allow the rest of the fixes in this series to be merged, disable these functions for now. Since these are mostly uncommon functions that are unlikely to be used in actual Arduino code, this should probably not cause much problems for now. --- cpp/arduino/avr/pgmspace.h | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/cpp/arduino/avr/pgmspace.h b/cpp/arduino/avr/pgmspace.h index eb7050ad..cab19057 100644 --- a/cpp/arduino/avr/pgmspace.h +++ b/cpp/arduino/avr/pgmspace.h @@ -59,17 +59,11 @@ typedef uintptr_t uint_farptr_t; inline const void * memchr_P(const void *s, int val, size_t len) { return memchr(s, val, len); } inline int memcmp_P(const void *s1, const void *s2, size_t len) { return memcmp(s1, s2, len); } -inline void *memccpy_P(void *dest, const void *src, int val, size_t len) { return memccpy(dest, src, val, len); } inline void *memcpy_P(void *dest, const void *src, size_t n) { return memcpy(dest, src, n); } -inline void *memmem_P(const void *s1, size_t len1, const void *s2, size_t len2) { return memmem(s1, len1, s2, len2); } -inline const void *memrchr_P(const void *src, int val, size_t len) { return memrchr(src, val, len); } inline char *strcat_P(char *dest, const char *src) { return strcat(dest, src); } inline const char *strchr_P(const char *s, int val) { return strchr(s, val); } -inline const char *strchrnul_P(const char *s, int c) { return strchrnul(s, c); } inline int strcmp_P(const char *s1, const char *s2) { return strcmp(s1, s2); } inline char *strcpy_P(char *dest, const char *src) { return strcpy(dest, src); } -inline int strcasecmp_P(const char *s1, const char *s2) { return strcasecmp(s1, s2); } -inline char *strcasestr_P(const char *s1, const char *s2) { return (char*)strcasestr(s1, s2); } inline size_t strcspn_P(const char *s, const char *reject) { return strcspn(s, reject); } // strlcat and strlcpy are AVR-specific and not entirely trivial to reimplement using strncat it seems //inline size_t strlcat_P(char *dst, const char *src, size_t siz) { return strlcat(dst, src, siz); } @@ -77,18 +71,14 @@ inline size_t strcspn_P(const char *s, const char *reject) { return strcspn(s, r //inline size_t strlcat_PF(char *dst, uint_farptr_t src, size_t n) { return strlcat(dst, (const char*)src, n); } //inline size_t strlcpy_PF(char *dst, uint_farptr_t src, size_t siz) { return strlcpy(dst, (const char*)src, siz); } inline int strncmp_P(const char *s1, const char *s2, size_t n) { return strncmp(s1, s2, n); } -inline int strncasecmp_P(const char *s1, const char *s2, size_t n) { return strncasecmp(s1, s2, n); } inline char *strncat_P(char *dest, const char *src, size_t len) { return strncat(dest, src, len); } inline char *strncpy_P(char *dest, const char *src, size_t n) { return strncpy(dest, src, n); } inline char *strpbrk_P(const char *s, const char *accept) { return (char*)strpbrk(s, accept); } inline const char *strrchr_P(const char *s, int val) { return strrchr(s, val); } -inline char *strsep_P(char **sp, const char *delim) { return strsep(sp, delim); } inline size_t strspn_P(const char *s, const char *accept) { return strspn(s, accept); } inline char *strstr_P(const char *s1, const char *s2) { return (char*)strstr(s1, s2); } inline char *strtok_P(char *s, const char * delim) { return strtok(s, delim); } -inline char *strtok_r_P(char *string, const char *delim, char **last) { return strtok_r(string, delim, last); } inline size_t strlen_PF(uint_farptr_t s) { return strlen((char*)s); } -inline size_t strnlen_P(uint_farptr_t s, size_t len) { return strnlen((char*)s, len); } inline void *memcpy_PF(void *dest, uint_farptr_t src, size_t n) { return memcpy(dest, (const char*)src, n); } inline char *strcpy_PF(char *dst, uint_farptr_t src) { return strcpy(dst, (const char*)src); } inline char *strncpy_PF(char *dst, uint_farptr_t src, size_t n) { return strncpy(dst, (const char*)src, n); } @@ -96,12 +86,26 @@ inline char *strcat_PF(char *dst, uint_farptr_t src) { return strcat(dst, (const inline char *strncat_PF(char *dst, uint_farptr_t src, size_t n) { return strncat(dst, (const char*)src, n); } inline int strcmp_PF(const char *s1, uint_farptr_t s2) { return strcmp(s1, (const char*)s2); } inline int strncmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return strncmp(s1, (const char*)s2, n); } -inline int strcasecmp_PF(const char *s1, uint_farptr_t s2) { return strcasecmp(s1, (const char*)s2); } -inline int strncasecmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return strncasecmp(s1, (const char*)s2, n); } inline char *strstr_PF(const char *s1, uint_farptr_t s2) { return (char*)strstr(s1, (const char*)s2); } inline int memcmp_PF(const void *s1, uint_farptr_t s2, size_t len) { return memcmp(s1, (const char*)s2, len); } inline size_t strlen_P(const char *src) { return strlen(src); } +// TODO: These functions cannot be found on the CYGWIN test build for +// some reason, so disable them for now. Most of these are less common +// and/or GNU-specific addons anyway +//inline void *memccpy_P(void *dest, const void *src, int val, size_t len) { return memccpy(dest, src, val, len); } +//inline void *memmem_P(const void *s1, size_t len1, const void *s2, size_t len2) { return memmem(s1, len1, s2, len2); } +//inline const void *memrchr_P(const void *src, int val, size_t len) { return memrchr(src, val, len); } +//inline const char *strchrnul_P(const char *s, int c) { return strchrnul(s, c); } +//inline int strcasecmp_P(const char *s1, const char *s2) { return strcasecmp(s1, s2); } +//inline char *strcasestr_P(const char *s1, const char *s2) { return (char*)strcasestr(s1, s2); } +//inline int strncasecmp_P(const char *s1, const char *s2, size_t n) { return strncasecmp(s1, s2, n); } +//inline char *strsep_P(char **sp, const char *delim) { return strsep(sp, delim); } +//inline char *strtok_r_P(char *string, const char *delim, char **last) { return strtok_r(string, delim, last); } +//inline int strcasecmp_PF(const char *s1, uint_farptr_t s2) { return strcasecmp(s1, (const char*)s2); } +//inline int strncasecmp_PF(const char *s1, uint_farptr_t s2, size_t n) { return strncasecmp(s1, (const char*)s2, n); } +//inline size_t strnlen_P(uint_farptr_t s, size_t len) { return strnlen((char*)s, len); } + // These are normally defined by stdio.h on AVR, but we cannot override that // include file (at least not without no longer being able to include the // original as well), so just define these here. It seems likely that any From cb67e90aa333625ccff9f95b505ca2c35611e9e9 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 10 Nov 2020 22:22:15 -0500 Subject: [PATCH 24/33] Annotate matthijskooijman changes --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aed6a198..1e5a13e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `CppLibrary.library_properties_path`, `CppLibrary.library_properties?`, `CppLibrary.library_properties` to expose library properties of a Cpp library - `CppLibrary.arduino_library_dependencies` to list the dependent libraries specified by the library.properties file - `CppLibrary.print_stack_dump` prints stack trace dumps (on Windows specifically) to the console if encountered +- Definitions for Arduino zero ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci - Revise math macros to avoid name clashes - `CppLibrary` functions returning C++ header or code files now respect the 1.0/1.5 library specification +- Mocks of built-in macros made more accurate +- NUM_SERIAL_PORTS can now be set explicitly +- Improve SPI header strategy ### Fixed - Don't define `ostream& operator<<(nullptr_t)` if already defined by Apple From 583f4a1bd463eebc77da30659433390b814269ec Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 10 Nov 2020 23:15:48 -0500 Subject: [PATCH 25/33] Remove hard-coded __AVR__ in favor of platform configured data --- CHANGELOG.md | 1 - lib/arduino_ci/cpp_library.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e5a13e1..52995b49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added -- Add `__AVR__` to defines when compiling - `arduino_ci_remote.rb` CLI switch `--skip-examples-compilation` - Add support for `diditalPinToPort()`, `digitalPinToBitMask()`, and `portOutputRegister()` - `CppLibrary.header_files` to find header files diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index e61201c2..ea3d62eb 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -398,7 +398,7 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g executable = Pathname.new("unittest_#{base}.bin").expand_path File.delete(executable) if File.exist?(executable) arg_sets = [] - arg_sets << ["-std=c++0x", "-o", executable.to_s, "-DARDUINO=100", "-D__AVR__"] + arg_sets << ["-std=c++0x", "-o", executable.to_s, "-DARDUINO=100"] if libasan?(gcc_binary) arg_sets << [ # Stuff to help with dynamic memory mishandling "-g", "-O1", From 7978af6a2d954e7fb0d0adc7cad215126cec09fa Mon Sep 17 00:00:00 2001 From: James Foster Date: Tue, 10 Nov 2020 23:17:17 -0500 Subject: [PATCH 26/33] Support for EEPROM (squashed) --- CHANGELOG.md | 1 + REFERENCE.md | 37 +++++++++ SampleProjects/TestSomething/test/eeprom.cpp | 80 ++++++++++++++++++++ cpp/arduino/EEPROM.h | 64 ++++++++++++++++ cpp/arduino/Godmode.cpp | 5 ++ cpp/arduino/Godmode.h | 19 +++++ 6 files changed, 206 insertions(+) create mode 100644 SampleProjects/TestSomething/test/eeprom.cpp create mode 100644 cpp/arduino/EEPROM.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 52995b49..38f19d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `CppLibrary.arduino_library_dependencies` to list the dependent libraries specified by the library.properties file - `CppLibrary.print_stack_dump` prints stack trace dumps (on Windows specifically) to the console if encountered - Definitions for Arduino zero +- Support for mock EEPROM (but only if board supports it) ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/REFERENCE.md b/REFERENCE.md index 4089cbbb..d54b5828 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -586,3 +586,40 @@ unittest(spi) { assertEqual("LMNOe", String(inBuf)); } ``` + +### EEPROM + +`EEPROM` is a global with a simple API to read and write bytes to persistent memory (like a tiny hard disk) given an `int` location. Since the Arduino core already provides this as a global, and the core API is sufficient for basic testing (read/write), there is no direct tie to the `GODMODE` API. (If you need more, such as a log of intermediate values, enter a feature request.) + +```C++ +unittest(eeprom) +{ + uint8_t a; + // size + assertEqual(EEPROM_SIZE, EEPROM.length()); + // initial values + a = EEPROM.read(0); + assertEqual(255, a); + // write and read + EEPROM.write(0, 24); + a = EEPROM.read(0); + assertEqual(24, a); + // update + EEPROM.write(1, 14); + EEPROM.update(1, 22); + a = EEPROM.read(1); + assertEqual(22, a); + // put and get + const float f1 = 0.025f; + float f2 = 0.0f; + EEPROM.put(5, f1); + assertEqual(0.0f, f2); + EEPROM.get(5, f2); + assertEqual(0.025f, f2); + // array access + int val = 10; + EEPROM[2] = val; + a = EEPROM[2]; + assertEqual(10, a); +} +``` diff --git a/SampleProjects/TestSomething/test/eeprom.cpp b/SampleProjects/TestSomething/test/eeprom.cpp new file mode 100644 index 00000000..8a844249 --- /dev/null +++ b/SampleProjects/TestSomething/test/eeprom.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +// Only run EEPROM tests if there is hardware support! +#if defined(EEPROM_SIZE) +#include + +GodmodeState* state = GODMODE(); + +unittest_setup() +{ + state->reset(); +} + +unittest(length) +{ + assertEqual(EEPROM_SIZE, EEPROM.length()); +} + +unittest(firstRead) +{ + uint8_t a = EEPROM.read(0); + assertEqual(255, a); +} + +unittest(writeRead) +{ + EEPROM.write(0, 24); + uint8_t a = EEPROM.read(0); + assertEqual(24, a); + + EEPROM.write(0, 128); + a = EEPROM.read(0); + assertEqual(128, a); + + EEPROM.write(0, 255); + a = EEPROM.read(0); + assertEqual(255, a); + + int addr = EEPROM_SIZE / 2; + EEPROM.write(addr, 63); + a = EEPROM.read(addr); + assertEqual(63, a); + + addr = EEPROM_SIZE - 1; + EEPROM.write(addr, 188); + a = EEPROM.read(addr); + assertEqual(188, a); +} + +unittest(updateWrite) +{ + EEPROM.write(1, 14); + EEPROM.update(1, 22); + uint8_t a = EEPROM.read(1); + assertEqual(22, a); +} + +unittest(putGet) +{ + const float f1 = 0.025f; + float f2 = 0.0f; + EEPROM.put(5, f1); + assertEqual(0.0f, f2); + EEPROM.get(5, f2); + assertEqual(0.025f, f2); +} + +unittest(array) +{ + int val = 10; + EEPROM[2] = val; + uint8_t a = EEPROM[2]; + assertEqual(10, a); +} + +#endif + +unittest_main() diff --git a/cpp/arduino/EEPROM.h b/cpp/arduino/EEPROM.h new file mode 100644 index 00000000..05b3daa5 --- /dev/null +++ b/cpp/arduino/EEPROM.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + +// Does the current board have EEPROM? +#ifndef EEPROM_SIZE + // In lieu of an "EEPROM.h not found" error for unsupported boards + #error "EEPROM library not available for your board" +#endif + +class EEPROMClass { +private: + GodmodeState* state; +public: + // constructor + EEPROMClass() { + state = GODMODE(); + } + // array subscript operator + uint8_t &operator[](const int index) { + assert(index < EEPROM_SIZE); + return state->eeprom[index]; + } + + uint8_t read(const int index) { + assert(index < EEPROM_SIZE); + return state->eeprom[index]; + } + + void write(const int index, const uint8_t value) { + assert(index < EEPROM_SIZE); + state->eeprom[index] = value; + } + + void update(const int index, const uint8_t value) { + assert(index < EEPROM_SIZE); + state->eeprom[index] = value; + } + + uint16_t length() { return EEPROM_SIZE; } + + // read any object + template T &get(const int index, T &object) { + uint8_t *ptr = (uint8_t *)&object; + for (int i = 0; i < sizeof(T); ++i) { + *ptr++ = read(index + i); + } + return object; + } + + // write any object + template const T &put(const int index, T &object) { + const uint8_t *ptr = (const uint8_t *)&object; + for (int i = 0; i < sizeof(T); ++i) { + write(index + i, *ptr++); + } + return object; + } +}; + +// global available in Godmode.cpp +extern EEPROMClass EEPROM; diff --git a/cpp/arduino/Godmode.cpp b/cpp/arduino/Godmode.cpp index 102afca6..f68867e4 100644 --- a/cpp/arduino/Godmode.cpp +++ b/cpp/arduino/Godmode.cpp @@ -113,3 +113,8 @@ SPIClass SPI = SPIClass(&GODMODE()->spi.dataIn, &GODMODE()->spi.dataOut); // defined in Wire.h TwoWire Wire = TwoWire(); + +#if defined(EEPROM_SIZE) + #include + EEPROMClass EEPROM; +#endif diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index 30596167..0ed72ee6 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -34,6 +34,17 @@ unsigned long micros(); #endif #endif +// different EEPROM implementations have different macros that leak out +#if !defined(EEPROM_SIZE) && defined(E2END) && (E2END) + // public value indicates that feature is available + #define EEPROM_SIZE (E2END + 1) + // local array size + #define _EEPROM_SIZE EEPROM_SIZE +#else + // feature is not available but we want to have the array so other code compiles + #define _EEPROM_SIZE (0) +#endif + class GodmodeState { private: struct PortDef { @@ -60,6 +71,7 @@ class GodmodeState { struct PortDef serialPort[NUM_SERIAL_PORTS]; struct InterruptDef interrupt[MOCK_PINS_COUNT]; // not sure how to get actual number struct PortDef spi; + uint8_t eeprom[_EEPROM_SIZE]; void resetPins() { for (int i = 0; i < MOCK_PINS_COUNT; ++i) { @@ -99,6 +111,12 @@ class GodmodeState { } } + void resetEEPROM() { + for(int i = 0; i < EEPROM_SIZE; ++i) { + eeprom[i] = 255; + } + } + void reset() { resetClock(); resetPins(); @@ -106,6 +124,7 @@ class GodmodeState { resetPorts(); resetSPI(); resetMmapPorts(); + resetEEPROM(); seed = 1; } From 41990929d36869bababac4e4f48a4c2bb39a1a89 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 11 Nov 2020 00:15:16 -0500 Subject: [PATCH 27/33] Fix EEPROM compilation --- cpp/arduino/Godmode.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/arduino/Godmode.h b/cpp/arduino/Godmode.h index 0ed72ee6..b748a148 100644 --- a/cpp/arduino/Godmode.h +++ b/cpp/arduino/Godmode.h @@ -112,9 +112,11 @@ class GodmodeState { } void resetEEPROM() { +#if defined(EEPROM_SIZE) for(int i = 0; i < EEPROM_SIZE; ++i) { eeprom[i] = 255; } +#endif } void reset() { From cdecc4091cef95d7b636fe0e9af847089189b81b Mon Sep 17 00:00:00 2001 From: James Foster Date: Tue, 10 Nov 2020 23:27:47 -0500 Subject: [PATCH 28/33] Add files needed to compile Ethernet library (squashed) --- .travis.yml | 6 ++ CHANGELOG.md | 1 + SampleProjects/NetworkLib/.arduino-ci.yml | 11 +++ SampleProjects/NetworkLib/.gitignore | 1 + SampleProjects/NetworkLib/Gemfile | 2 + SampleProjects/NetworkLib/README.md | 3 + .../EthernetExample/EthernetExample.ino | 6 ++ SampleProjects/NetworkLib/library.properties | 10 +++ SampleProjects/NetworkLib/scripts/install.sh | 8 ++ SampleProjects/NetworkLib/src/NetworkLib.cpp | 1 + SampleProjects/NetworkLib/src/NetworkLib.h | 3 + SampleProjects/NetworkLib/test/test.cpp | 15 ++++ .../TestSomething/test/clientServer.cpp | 87 +++++++++++++++++++ appveyor.yml | 6 ++ cpp/arduino/Arduino.h | 1 + cpp/arduino/Client.h | 26 ++++++ cpp/arduino/IPAddress.h | 59 +++++++++++++ cpp/arduino/Print.h | 21 ++--- cpp/arduino/Printable.h | 8 ++ cpp/arduino/Server.h | 5 ++ cpp/arduino/Udp.h | 27 ++++++ 21 files changed, 295 insertions(+), 12 deletions(-) create mode 100644 SampleProjects/NetworkLib/.arduino-ci.yml create mode 100644 SampleProjects/NetworkLib/.gitignore create mode 100644 SampleProjects/NetworkLib/Gemfile create mode 100644 SampleProjects/NetworkLib/README.md create mode 100644 SampleProjects/NetworkLib/examples/EthernetExample/EthernetExample.ino create mode 100644 SampleProjects/NetworkLib/library.properties create mode 100644 SampleProjects/NetworkLib/scripts/install.sh create mode 100644 SampleProjects/NetworkLib/src/NetworkLib.cpp create mode 100644 SampleProjects/NetworkLib/src/NetworkLib.h create mode 100644 SampleProjects/NetworkLib/test/test.cpp create mode 100644 SampleProjects/TestSomething/test/clientServer.cpp create mode 100644 cpp/arduino/Client.h create mode 100644 cpp/arduino/IPAddress.h create mode 100644 cpp/arduino/Printable.h create mode 100644 cpp/arduino/Server.h create mode 100644 cpp/arduino/Udp.h diff --git a/.travis.yml b/.travis.yml index 2603ca2c..36067457 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,3 +27,9 @@ script: - cd SampleProjects/TestSomething - bundle install - bundle exec arduino_ci.rb + - cd ../NetworkLib + - cd scripts + - bash -x ./install.sh + - cd .. + - bundle install + - bundle exec arduino_ci.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 38f19d0c..a1e3a9ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `CppLibrary.print_stack_dump` prints stack trace dumps (on Windows specifically) to the console if encountered - Definitions for Arduino zero - Support for mock EEPROM (but only if board supports it) +- Add stubs for `Client.h`, `IPAddress.h`, `Printable.h`, `Server.h`, and `Udp.h` ### Changed - Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci diff --git a/SampleProjects/NetworkLib/.arduino-ci.yml b/SampleProjects/NetworkLib/.arduino-ci.yml new file mode 100644 index 00000000..a242a79b --- /dev/null +++ b/SampleProjects/NetworkLib/.arduino-ci.yml @@ -0,0 +1,11 @@ +unittest: + platforms: + - mega2560 + libraries: + - "Ethernet" + +compile: + platforms: + - mega2560 + libraries: + - "Ethernet" diff --git a/SampleProjects/NetworkLib/.gitignore b/SampleProjects/NetworkLib/.gitignore new file mode 100644 index 00000000..06de90aa --- /dev/null +++ b/SampleProjects/NetworkLib/.gitignore @@ -0,0 +1 @@ +.bundle \ No newline at end of file diff --git a/SampleProjects/NetworkLib/Gemfile b/SampleProjects/NetworkLib/Gemfile new file mode 100644 index 00000000..b2b3b1fd --- /dev/null +++ b/SampleProjects/NetworkLib/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'arduino_ci', path: '../../' diff --git a/SampleProjects/NetworkLib/README.md b/SampleProjects/NetworkLib/README.md new file mode 100644 index 00000000..b25d2e14 --- /dev/null +++ b/SampleProjects/NetworkLib/README.md @@ -0,0 +1,3 @@ +# NetworkLib + +This is an example of a library that depends on Ethernet. diff --git a/SampleProjects/NetworkLib/examples/EthernetExample/EthernetExample.ino b/SampleProjects/NetworkLib/examples/EthernetExample/EthernetExample.ino new file mode 100644 index 00000000..127afc76 --- /dev/null +++ b/SampleProjects/NetworkLib/examples/EthernetExample/EthernetExample.ino @@ -0,0 +1,6 @@ +#include +// if it seems bare, that's because it's only meant to +// demonstrate compilation -- that references work +void setup() {} + +void loop() {} diff --git a/SampleProjects/NetworkLib/library.properties b/SampleProjects/NetworkLib/library.properties new file mode 100644 index 00000000..2efc89bd --- /dev/null +++ b/SampleProjects/NetworkLib/library.properties @@ -0,0 +1,10 @@ +name=Ethernet +version=0.1.0 +author=James Foster +maintainer=James Foster +sentence=Sample Ethernet library to validate Client/Server mocks +paragraph=Sample Ethernet library to validate Client/Server mocks +category=Other +url=https://github.com/Arduino-CI/arduino_ci/SampleProjects/Ethernet +architectures=avr,esp8266 +includes=NetworkLib.h diff --git a/SampleProjects/NetworkLib/scripts/install.sh b/SampleProjects/NetworkLib/scripts/install.sh new file mode 100644 index 00000000..97039a1e --- /dev/null +++ b/SampleProjects/NetworkLib/scripts/install.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# if we don't have an Ethernet library already (say, in new install or for an automated test), +# then get the custom one we want to use for testing +cd $(bundle exec arduino_library_location.rb) +if [ ! -d ./Ethernet ] ; then + git clone https://github.com/arduino-libraries/Ethernet.git +fi diff --git a/SampleProjects/NetworkLib/src/NetworkLib.cpp b/SampleProjects/NetworkLib/src/NetworkLib.cpp new file mode 100644 index 00000000..01e5d5b0 --- /dev/null +++ b/SampleProjects/NetworkLib/src/NetworkLib.cpp @@ -0,0 +1 @@ +#include "Ethernet.h" diff --git a/SampleProjects/NetworkLib/src/NetworkLib.h b/SampleProjects/NetworkLib/src/NetworkLib.h new file mode 100644 index 00000000..9ee81b24 --- /dev/null +++ b/SampleProjects/NetworkLib/src/NetworkLib.h @@ -0,0 +1,3 @@ +#pragma once + +#include diff --git a/SampleProjects/NetworkLib/test/test.cpp b/SampleProjects/NetworkLib/test/test.cpp new file mode 100644 index 00000000..4c2d4eca --- /dev/null +++ b/SampleProjects/NetworkLib/test/test.cpp @@ -0,0 +1,15 @@ +/* +cd SampleProjects/NetworkLib +bundle config --local path vendor/bundle +bundle install +bundle exec arduino_ci_remote.rb --skip-compilation +# bundle exec arduino_ci_remote.rb --skip-examples-compilation +*/ + +#include +#include +#include + +unittest(test) { assertEqual(EthernetNoHardware, Ethernet.hardwareStatus()); } + +unittest_main() diff --git a/SampleProjects/TestSomething/test/clientServer.cpp b/SampleProjects/TestSomething/test/clientServer.cpp new file mode 100644 index 00000000..f088c821 --- /dev/null +++ b/SampleProjects/TestSomething/test/clientServer.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +// Provide some rudamentary tests for these classes +// They get more thoroughly tested in SampleProjects/NetworkLib + +unittest(Client) { + Client client; + assertEqual(0, client.available()); // subclass of Stream + assertEqual(0, client.availableForWrite()); // subclass of Print + String outData = "Hello, world!"; + client.println(outData); + String inData = client.readString(); + assertEqual(outData + "\r\n", inData); +} + +unittest(IPAddress) { + IPAddress ipAddress0; + assertEqual(0, ipAddress0.asWord()); + uint32_t one = 0x01020304; + IPAddress ipAddress1(one); + assertEqual(one, ipAddress1.asWord()); + IPAddress ipAddress2(2, 3, 4, 5); + assertEqual(0x05040302, ipAddress2.asWord()); + uint8_t bytes[] = {3, 4, 5, 6}; + IPAddress ipAddress3(bytes); + assertEqual(0x06050403, ipAddress3.asWord()); + uint8_t *pBytes = ipAddress1.raw_address(); + assertEqual(*(pBytes + 0), 4); + assertEqual(*(pBytes + 1), 3); + assertEqual(*(pBytes + 2), 2); + assertEqual(*(pBytes + 3), 1); + IPAddress ipAddress1a(one); + assertTrue(ipAddress1 == ipAddress1a); + assertTrue(ipAddress1 != ipAddress2); + assertEqual(1, ipAddress1[3]); + ipAddress1[1] = 11; + assertEqual(11, ipAddress1[1]); + assertEqual(1, ipAddress0 + 1); +} + +class TestPrintable : public Printable { +public: + virtual size_t printTo(Print &p) const { + p.print("TestPrintable"); + return 13; + } +}; + +unittest(Printable) { + TestPrintable printable; + Client client; + client.print(printable); + assertEqual("TestPrintable", client.readString()); +} + +class TestServer : public Server { +public: + uint8_t data; + virtual size_t write(uint8_t value) { + data = value; + return 1; + }; +}; + +unittest(Server) { + TestServer server; + server.write(67); + assertEqual(67, server.data); +} + +unittest(Udp) { + UDP udp; + assertEqual(0, udp.available()); // subclass of Stream + assertEqual(0, udp.availableForWrite()); // subclass of Print + String outData = "Hello, world!"; + udp.println(outData); + String inData = udp.readString(); + assertEqual(outData + "\r\n", inData); +} + +unittest_main() diff --git a/appveyor.yml b/appveyor.yml index af4bd854..d8576b06 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,3 +25,9 @@ test_script: - cd SampleProjects\TestSomething - bundle install - bundle exec arduino_ci.rb + - cd ../NetworkLib + - cd scripts + - install.sh + - cd .. + - bundle install + - bundle exec arduino_ci.rb diff --git a/cpp/arduino/Arduino.h b/cpp/arduino/Arduino.h index 4cec73ad..4d00095b 100644 --- a/cpp/arduino/Arduino.h +++ b/cpp/arduino/Arduino.h @@ -9,6 +9,7 @@ Where possible, variable names from the Arduino library are used to avoid confli #include "ArduinoDefines.h" +#include "IPAddress.h" #include "WCharacter.h" #include "WString.h" #include "Print.h" diff --git a/cpp/arduino/Client.h b/cpp/arduino/Client.h new file mode 100644 index 00000000..b08e183e --- /dev/null +++ b/cpp/arduino/Client.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +class Client : public Stream { +public: + Client() { + // The Stream mock defines a String buffer but never puts anyting in it! + if (!mGodmodeDataIn) { + mGodmodeDataIn = new String; + } + } + ~Client() { + if (mGodmodeDataIn) { + delete mGodmodeDataIn; + mGodmodeDataIn = nullptr; + } + } + virtual size_t write(uint8_t value) { + mGodmodeDataIn->concat(value); + return 1; + } + +protected: + uint8_t *rawIPAddress(IPAddress &addr) { return addr.raw_address(); } +}; diff --git a/cpp/arduino/IPAddress.h b/cpp/arduino/IPAddress.h new file mode 100644 index 00000000..89a343e1 --- /dev/null +++ b/cpp/arduino/IPAddress.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +class IPAddress { +private: + union { + uint8_t bytes[4]; + uint32_t dword; + operator uint8_t *() const { return (uint8_t *)bytes; } + } _address; + +public: + // Constructors + IPAddress() : IPAddress(0, 0, 0, 0) {} + IPAddress(uint8_t octet1, uint8_t octet2, uint8_t octet3, uint8_t octet4) { + _address.bytes[0] = octet1; + _address.bytes[1] = octet2; + _address.bytes[2] = octet3; + _address.bytes[3] = octet4; + } + IPAddress(uint32_t dword) { _address.dword = dword; } + IPAddress(const uint8_t bytes[]) { + _address.bytes[0] = bytes[0]; + _address.bytes[1] = bytes[1]; + _address.bytes[2] = bytes[2]; + _address.bytes[3] = bytes[3]; + } + IPAddress(unsigned long dword) { _address.dword = (uint32_t)dword; } + + // Accessors + uint32_t asWord() const { return _address.dword; } + uint8_t *raw_address() { return _address.bytes; } + + // Comparisons + bool operator==(const IPAddress &rhs) const { + return _address.dword == rhs.asWord(); + } + + bool operator!=(const IPAddress &rhs) const { + return _address.dword != rhs.asWord(); + } + + // Indexing + uint8_t operator[](int index) const { return _address.bytes[index]; } + uint8_t &operator[](int index) { return _address.bytes[index]; } + + // Conversions + operator uint32_t() const { return _address.dword; }; + + friend class EthernetClass; + friend class UDP; + friend class Client; + friend class Server; + friend class DhcpClass; + friend class DNSClient; +}; + +const IPAddress INADDR_NONE(0, 0, 0, 0); diff --git a/cpp/arduino/Print.h b/cpp/arduino/Print.h index b7d8a522..261b116d 100644 --- a/cpp/arduino/Print.h +++ b/cpp/arduino/Print.h @@ -2,6 +2,8 @@ #include #include + +#include "Printable.h" #include "WString.h" #define DEC 10 @@ -12,22 +14,17 @@ #endif #define BIN 2 -class Print; - -class Printable -{ - public: - virtual size_t printTo(Print& p) const = 0; -}; - class Print { + private: + int write_error; + protected: + void setWriteError(int err = 1) { write_error = err; } public: - Print() {} + Print() : write_error(0) {} - // Arduino's version of this is richer but until I see an actual error case I'm not sure how to mock - int getWriteError() { return 0; } - void clearWriteError() { } + int getWriteError() { return write_error; } + void clearWriteError() { setWriteError(0); } virtual int availableForWrite() { return 0; } virtual size_t write(uint8_t) = 0; diff --git a/cpp/arduino/Printable.h b/cpp/arduino/Printable.h new file mode 100644 index 00000000..cdd361d3 --- /dev/null +++ b/cpp/arduino/Printable.h @@ -0,0 +1,8 @@ +#pragma once + +class Print; + +class Printable { +public: + virtual size_t printTo(Print &p) const = 0; +}; diff --git a/cpp/arduino/Server.h b/cpp/arduino/Server.h new file mode 100644 index 00000000..dd1993ff --- /dev/null +++ b/cpp/arduino/Server.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +class Server : public Print {}; diff --git a/cpp/arduino/Udp.h b/cpp/arduino/Udp.h new file mode 100644 index 00000000..8352f7f6 --- /dev/null +++ b/cpp/arduino/Udp.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +class UDP : public Stream { +protected: + uint8_t *rawIPAddress(IPAddress &addr) { return addr.raw_address(); }; + +public: + UDP() { + // The Stream mock defines a String buffer but never puts anyting in it! + if (!mGodmodeDataIn) { + mGodmodeDataIn = new String; + } + } + ~UDP() { + if (mGodmodeDataIn) { + delete mGodmodeDataIn; + mGodmodeDataIn = nullptr; + } + } + virtual size_t write(uint8_t value) { + mGodmodeDataIn->concat(value); + return 1; + } +}; From a8eff6aed22993d4252345897de08c2089f80e02 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 13 Nov 2020 00:15:01 -0500 Subject: [PATCH 29/33] Fix custom ethernet library location --- SampleProjects/NetworkLib/scripts/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SampleProjects/NetworkLib/scripts/install.sh b/SampleProjects/NetworkLib/scripts/install.sh index 97039a1e..b4e2dd40 100644 --- a/SampleProjects/NetworkLib/scripts/install.sh +++ b/SampleProjects/NetworkLib/scripts/install.sh @@ -4,5 +4,5 @@ # then get the custom one we want to use for testing cd $(bundle exec arduino_library_location.rb) if [ ! -d ./Ethernet ] ; then - git clone https://github.com/arduino-libraries/Ethernet.git + git clone https://github.com/Arduino-CI/Ethernet.git fi From 78047305b06a6efc1917a3ecac83ec59b6b99bff Mon Sep 17 00:00:00 2001 From: James Foster Date: Wed, 11 Nov 2020 00:06:10 -0500 Subject: [PATCH 30/33] Implement __ARDUNO_CI_SFR_MOCK (squashed) --- SampleProjects/TestSomething/test/defines.cpp | 2 +- cpp/arduino/Godmode.cpp | 2 ++ cpp/arduino/SPI.h | 10 ++++++++-- cpp/arduino/avr/io.h | 10 +++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/SampleProjects/TestSomething/test/defines.cpp b/SampleProjects/TestSomething/test/defines.cpp index 91810a03..7e93d62b 100644 --- a/SampleProjects/TestSomething/test/defines.cpp +++ b/SampleProjects/TestSomething/test/defines.cpp @@ -16,7 +16,7 @@ unittest(SFR_IO8) // error like: cannot take the address of an rvalue of type 'int' // // this tests that directly - &DDRE; + auto foo = &DDRE; // avoid compiler warning by using the result of an expression } unittest_main() diff --git a/cpp/arduino/Godmode.cpp b/cpp/arduino/Godmode.cpp index f68867e4..7cc0b155 100644 --- a/cpp/arduino/Godmode.cpp +++ b/cpp/arduino/Godmode.cpp @@ -118,3 +118,5 @@ TwoWire Wire = TwoWire(); #include EEPROMClass EEPROM; #endif + +volatile long long __ARDUINO_CI_SFR_MOCK[1024]; diff --git a/cpp/arduino/SPI.h b/cpp/arduino/SPI.h index e22be357..f8f874ed 100644 --- a/cpp/arduino/SPI.h +++ b/cpp/arduino/SPI.h @@ -40,7 +40,11 @@ class SPISettings { public: - SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode){}; + uint8_t bitOrder; + + SPISettings(uint32_t clock, uint8_t bitOrder = MSBFIRST, uint8_t dataMode = SPI_MODE0) { + this->bitOrder = bitOrder; + }; SPISettings(){}; }; @@ -68,6 +72,7 @@ class SPIClass: public ObservableDataStream { // and configure the correct settings. void beginTransaction(SPISettings settings) { + this->bitOrder = settings.bitOrder; #ifdef SPI_TRANSACTION_MISMATCH_LED if (inTransactionFlag) { pinMode(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); @@ -95,7 +100,7 @@ class SPIClass: public ObservableDataStream { union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; in.val = data; #if defined(SPCR) && defined(DORD) - if (!(SPCR & (1 << DORD))) { + if (bitOrder == MSBFIRST) { out.msb = transfer(in.msb); out.lsb = transfer(in.lsb); } @@ -147,6 +152,7 @@ class SPIClass: public ObservableDataStream { #endif bool isStarted = false; + uint8_t bitOrder; String* dataIn; String* dataOut; }; diff --git a/cpp/arduino/avr/io.h b/cpp/arduino/avr/io.h index b39b894f..547c0f0c 100644 --- a/cpp/arduino/avr/io.h +++ b/cpp/arduino/avr/io.h @@ -96,7 +96,15 @@ #ifndef _AVR_IO_H_ #define _AVR_IO_H_ -#define _SFR_IO8(io_addr) (*(volatile uint8_t *)(io_addr)) // this macro is all we need from the sfr file +// hardware mocks + +// this set of macros is all we need from the sfr file +extern volatile long long __ARDUINO_CI_SFR_MOCK[1024]; +#define _SFR_IO8(io_addr) (*(volatile uint8_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) +#define _SFR_IO16(io_addr) (*(volatile uint16_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) +#define _SFR_MEM8(io_addr) (*(volatile uint8_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) +#define _SFR_MEM16(io_addr) (*(volatile uint16_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) +#define _SFR_MEM32(io_addr) (*(volatile uint32_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) #if defined (__AVR_AT94K__) # include "ioat94k.h" From cbccf6a714a07ee072a9204d6ab7550e546a5b71 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 11 Nov 2020 11:28:12 -0500 Subject: [PATCH 31/33] Don't test SFR on non-AVR --- SampleProjects/TestSomething/test/defines.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SampleProjects/TestSomething/test/defines.cpp b/SampleProjects/TestSomething/test/defines.cpp index 7e93d62b..89311ea2 100644 --- a/SampleProjects/TestSomething/test/defines.cpp +++ b/SampleProjects/TestSomething/test/defines.cpp @@ -8,6 +8,7 @@ unittest(binary) assertEqual(100, B1100100); } +#ifdef __AVR__ #define DDRE _SFR_IO8(0x02) unittest(SFR_IO8) @@ -18,5 +19,6 @@ unittest(SFR_IO8) // this tests that directly auto foo = &DDRE; // avoid compiler warning by using the result of an expression } +#endif unittest_main() From ebfa6a79e7c698708fb661006e8446e30f56f871 Mon Sep 17 00:00:00 2001 From: James Foster Date: Fri, 13 Nov 2020 00:31:01 -0500 Subject: [PATCH 32/33] Additional __ARDUINO_CI_SFR_MOCK commits (squashed) --- SampleProjects/TestSomething/test/defines.cpp | 9 ++++++++ SampleProjects/TestSomething/test/godmode.cpp | 22 +++++++++++++++++-- cpp/arduino/Godmode.cpp | 2 +- cpp/arduino/avr/io.h | 5 +++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/SampleProjects/TestSomething/test/defines.cpp b/SampleProjects/TestSomething/test/defines.cpp index 89311ea2..bbfe134d 100644 --- a/SampleProjects/TestSomething/test/defines.cpp +++ b/SampleProjects/TestSomething/test/defines.cpp @@ -19,6 +19,15 @@ unittest(SFR_IO8) // this tests that directly auto foo = &DDRE; // avoid compiler warning by using the result of an expression } + +unittest(read_write) +{ + _SFR_IO8(1) = 0x11; + _SFR_IO8(2) = 0x22; + assertEqual((int) 0x11, (int) _SFR_IO8(1)); + assertEqual((int) 0x22, (int) _SFR_IO8(2)); + assertEqual((int) 0x2211, (int) _SFR_IO16(1)); +} #endif unittest_main() diff --git a/SampleProjects/TestSomething/test/godmode.cpp b/SampleProjects/TestSomething/test/godmode.cpp index 15c13f3c..6e57d9d6 100644 --- a/SampleProjects/TestSomething/test/godmode.cpp +++ b/SampleProjects/TestSomething/test/godmode.cpp @@ -176,18 +176,35 @@ unittest(spi) { // 8-bit state->reset(); state->spi.dataIn = "LMNO"; + SPI.beginTransaction(SPISettings(14000000, LSBFIRST, SPI_MODE0)); uint8_t out8 = SPI.transfer('a'); + SPI.endTransaction(); assertEqual("a", state->spi.dataOut); assertEqual('L', out8); assertEqual("MNO", state->spi.dataIn); - // 16-bit + // 16-bit MSBFIRST union { uint16_t val; struct { char lsb; char msb; }; } in16, out16; state->reset(); state->spi.dataIn = "LMNO"; in16.lsb = 'a'; in16.msb = 'b'; + SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0)); out16.val = SPI.transfer16(in16.val); + SPI.endTransaction(); + assertEqual("NO", state->spi.dataIn); + assertEqual('M', out16.lsb); + assertEqual('L', out16.msb); + assertEqual("ba", state->spi.dataOut); + + // 16-bit LSBFIRST + state->reset(); + state->spi.dataIn = "LMNO"; + in16.lsb = 'a'; + in16.msb = 'b'; + SPI.beginTransaction(SPISettings(14000000, LSBFIRST, SPI_MODE0)); + out16.val = SPI.transfer16(in16.val); + SPI.endTransaction(); assertEqual("NO", state->spi.dataIn); assertEqual('L', out16.lsb); assertEqual('M', out16.msb); @@ -197,14 +214,15 @@ unittest(spi) { state->reset(); state->spi.dataIn = "LMNOP"; char inBuf[6] = "abcde"; + SPI.beginTransaction(SPISettings(14000000, LSBFIRST, SPI_MODE0)); SPI.transfer(inBuf, 4); + SPI.endTransaction(); assertEqual("abcd", state->spi.dataOut); assertEqual("LMNOe", String(inBuf)); } - #ifdef HAVE_HWSERIAL0 void smartLightswitchSerialHandler(int pin) { diff --git a/cpp/arduino/Godmode.cpp b/cpp/arduino/Godmode.cpp index 7cc0b155..96bdfe6f 100644 --- a/cpp/arduino/Godmode.cpp +++ b/cpp/arduino/Godmode.cpp @@ -119,4 +119,4 @@ TwoWire Wire = TwoWire(); EEPROMClass EEPROM; #endif -volatile long long __ARDUINO_CI_SFR_MOCK[1024]; +volatile uint8_t __ARDUINO_CI_SFR_MOCK[1024]; diff --git a/cpp/arduino/avr/io.h b/cpp/arduino/avr/io.h index 547c0f0c..337b979e 100644 --- a/cpp/arduino/avr/io.h +++ b/cpp/arduino/avr/io.h @@ -96,10 +96,11 @@ #ifndef _AVR_IO_H_ #define _AVR_IO_H_ -// hardware mocks +#include +// hardware mocks // this set of macros is all we need from the sfr file -extern volatile long long __ARDUINO_CI_SFR_MOCK[1024]; +extern volatile uint8_t __ARDUINO_CI_SFR_MOCK[1024]; #define _SFR_IO8(io_addr) (*(volatile uint8_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) #define _SFR_IO16(io_addr) (*(volatile uint16_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) #define _SFR_MEM8(io_addr) (*(volatile uint8_t *)(__ARDUINO_CI_SFR_MOCK + io_addr)) From b4ee115fae1fda38cf19236bbbdcf7648e2a705a Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Fri, 13 Nov 2020 01:02:31 -0500 Subject: [PATCH 33/33] Remove possible unnecessary preprocessor guard --- cpp/arduino/SPI.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp/arduino/SPI.h b/cpp/arduino/SPI.h index f8f874ed..2096ccb0 100644 --- a/cpp/arduino/SPI.h +++ b/cpp/arduino/SPI.h @@ -99,13 +99,11 @@ class SPIClass: public ObservableDataStream { uint16_t transfer16(uint16_t data) { union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; in.val = data; - #if defined(SPCR) && defined(DORD) if (bitOrder == MSBFIRST) { out.msb = transfer(in.msb); out.lsb = transfer(in.lsb); } else - #endif { out.lsb = transfer(in.lsb); out.msb = transfer(in.msb);