diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c519c8d..9dc4a3ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- C++ functions for `assure`; `assert`s will run tests and continue, `assure`s will abort on failures +- Missing dotfiles in the `DoSomething` project have been committed ### Changed +- `arduino_ci_remote.rb` doesn't attempt to set URLs if nothing needs to be downloaded +- `arduino_ci_remote.rb` does unit tests first +- `unittest_main()` is now the macro for the `int main()` of test files ### Deprecated ### Removed ### Fixed +- All test files were reporting "not ok" in TAP output. Now they are OK iff all asserts pass. +- Directories with a C++ extension in their name could cause problems. Now they are ignored. +- `CppLibrary` had trouble with symlinks. It shoudn't anymore. +- `CppLibrary` had trouble with vendor bundles. It might in the future, but I have a better fix ready to go if it's an issue. ### Security diff --git a/README.md b/README.md index ac306883..b88fccdf 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,10 @@ unittest(your_test_name) assertEqual(4, doSomething()); } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() ``` -This test defines one `unittest` (a macro provided by `ArduionUnitTests.h`), called `your_test_name`, which makes some assertions on the target library. The `int main` section is boilerplate. +This test defines one `unittest` (a macro provided by `ArduionUnitTests.h`), called `your_test_name`, which makes some assertions on the target library. The `unittest_main()` is a macro for the `int main()` boilerplate required for unit testing. ## More Documentation diff --git a/SampleProjects/DoSomething/.arduino-ci.yaml b/SampleProjects/DoSomething/.arduino-ci.yaml new file mode 100644 index 00000000..f75da570 --- /dev/null +++ b/SampleProjects/DoSomething/.arduino-ci.yaml @@ -0,0 +1,13 @@ +compile: + libraries: ~ + platforms: + - uno + - due + - leonardo + +unittest: + libraries: ~ + platforms: + - uno + - due + - leonardo diff --git a/SampleProjects/DoSomething/.travis.yml b/SampleProjects/DoSomething/.travis.yml new file mode 100644 index 00000000..63c08085 --- /dev/null +++ b/SampleProjects/DoSomething/.travis.yml @@ -0,0 +1,5 @@ +sudo: false +language: ruby +script: + - bundle install + - bundle exec arduino_ci_remote.rb diff --git a/SampleProjects/DoSomething/README.md b/SampleProjects/DoSomething/README.md index 9b0cd5d2..dba67bf1 100644 --- a/SampleProjects/DoSomething/README.md +++ b/SampleProjects/DoSomething/README.md @@ -98,9 +98,7 @@ unittest(your_test_name) assertEqual(4, doSomething()); } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() ``` This test defines one `unittest` (a macro provided by `ArduionUnitTests.h`), called `your_test_name`, which makes some assertions on the target library. The `int main` section is boilerplate. diff --git a/SampleProjects/DoSomething/test/good-library.cpp b/SampleProjects/DoSomething/test/good-library.cpp index dbfaa6e9..cd2f4838 100644 --- a/SampleProjects/DoSomething/test/good-library.cpp +++ b/SampleProjects/DoSomething/test/good-library.cpp @@ -6,6 +6,4 @@ unittest(library_does_something) assertEqual(4, doSomething()); } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() diff --git a/SampleProjects/DoSomething/test/good-null.cpp b/SampleProjects/DoSomething/test/good-null.cpp index 3df30eab..f9c5e59a 100644 --- a/SampleProjects/DoSomething/test/good-null.cpp +++ b/SampleProjects/DoSomething/test/good-null.cpp @@ -19,6 +19,4 @@ unittest(nothing) { } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() diff --git a/SampleProjects/TestSomething/test/bad-null.cpp b/SampleProjects/TestSomething/test/bad-null.cpp index d2d1cd1e..c7768879 100644 --- a/SampleProjects/TestSomething/test/bad-null.cpp +++ b/SampleProjects/TestSomething/test/bad-null.cpp @@ -7,6 +7,4 @@ unittest(pretend_equal_things_arent) assertNotEqual(x, y); } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() diff --git a/SampleProjects/TestSomething/test/good-library.cpp b/SampleProjects/TestSomething/test/good-library.cpp index 342b0680..0ffbd676 100644 --- a/SampleProjects/TestSomething/test/good-library.cpp +++ b/SampleProjects/TestSomething/test/good-library.cpp @@ -6,6 +6,4 @@ unittest(library_tests_something) assertEqual(4, testSomething()); } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() diff --git a/SampleProjects/TestSomething/test/good-null.cpp b/SampleProjects/TestSomething/test/good-null.cpp index 3df30eab..f9c5e59a 100644 --- a/SampleProjects/TestSomething/test/good-null.cpp +++ b/SampleProjects/TestSomething/test/good-null.cpp @@ -19,6 +19,4 @@ unittest(nothing) { } -int main(int argc, char *argv[]) { - return Test::run_and_report(argc, argv); -} +unittest_main() diff --git a/cpp/unittest/ArduinoUnitTests.h b/cpp/unittest/ArduinoUnitTests.h index 3f92d29d..8ce52f69 100644 --- a/cpp/unittest/ArduinoUnitTests.h +++ b/cpp/unittest/ArduinoUnitTests.h @@ -130,14 +130,16 @@ class Test Results results = {0, 0, 0}; for (Test *p = sRoot; p; p = p->mNext) { - TestData td = {p->name(), p->result()}; + p->prepare(); p->mReporter = reporter; - if (reporter) reporter->onTestStart(td); + TestData t1 = {p->name(), p->result()}; + if (reporter) reporter->onTestStart(t1); p->test(); if (p->mResult == RESULT_PASS) ++results.passed; if (p->mResult == RESULT_FAIL) ++results.failed; if (p->mResult == RESULT_SKIP) ++results.skipped; - if (reporter) reporter->onTestEnd(td); + TestData t2 = {p->name(), p->result()}; + if (reporter) reporter->onTestEnd(t2); } return results; @@ -154,8 +156,12 @@ class Test return results.failed + results.skipped; } + void prepare() { + mResult = RESULT_PASS; // not None, and not fail unless we hear otherwise + } + void test() { - mResult = RESULT_PASS; // not None, and not fail unless we hear otherwise + // thin wrapper. nothing to do here for now task(); } @@ -210,3 +216,9 @@ class Test void task(); \ } test_##name##_instance; \ void test_##name ::task() + + +#define unittest_main() \ + int main(int argc, char *argv[]) { \ + return Test::run_and_report(argc, argv); \ + } diff --git a/cpp/unittest/Assertion.h b/cpp/unittest/Assertion.h index 1a53bff3..43c5f44d 100644 --- a/cpp/unittest/Assertion.h +++ b/cpp/unittest/Assertion.h @@ -1,21 +1,30 @@ #pragma once #include "Compare.h" -// helper define for the operators below -#define assertOp(desc, relevance1, arg1, op, op_name, relevance2, arg2) \ - do \ - { \ - if (!assertion(__FILE__, __LINE__, \ - desc, \ - relevance1, #arg1, (arg1), \ - op_name, op, \ - relevance2, #arg2, (arg2))) \ - { \ - return; \ - } \ +#define testBehaviorOp(die, desc, rel1, arg1, op, op_name, rel2, arg2) \ + do \ + { \ + if (!assertion(__FILE__, __LINE__, \ + desc, \ + rel1, #arg1, (arg1), \ + op_name, op, \ + rel2, #arg2, (arg2))) \ + { \ + if (die) return; \ + } \ } while (0) -/** macro generates optional output and calls fail() followed by a return if false. */ + + +// helper define for the operators below +#define assertOp(desc, rel1, arg1, op, op_name, rel2, arg2) \ + testBehaviorOp(false, desc, rel1, arg1, op, op_name, rel2, arg2) + +#define assureOp(desc, rel1, arg1, op, op_name, rel2, arg2) \ + testBehaviorOp(true, desc, rel1, arg1, op, op_name, rel2, arg2) + + +/** macro generates optional output and calls fail() but does not return if false. */ #define assertEqual(arg1,arg2) assertOp("assertEqual","expected",arg1,compareEqual,"==","actual",arg2) #define assertNotEqual(arg1,arg2) assertOp("assertNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) #define assertLess(arg1,arg2) assertOp("assertLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) @@ -25,3 +34,13 @@ #define assertTrue(arg) assertEqual(arg,true) #define assertFalse(arg) assertEqual(arg,false) +/** macro generates optional output and calls fail() followed by a return if false. */ +#define assureEqual(arg1,arg2) assureOp("assureEqual","expected",arg1,compareEqual,"==","actual",arg2) +#define assureNotEqual(arg1,arg2) assureOp("assureNotEqual","unwanted",arg1,compareNotEqual,"!=","actual",arg2) +#define assureLess(arg1,arg2) assureOp("assureLess","lowerBound",arg1,compareLess,"<","upperBound",arg2) +#define assureMore(arg1,arg2) assureOp("assureMore","upperBound",arg1,compareMore,">","lowerBound",arg2) +#define assureLessOrEqual(arg1,arg2) assureOp("assureLessOrEqual","lowerBound",arg1,compareLessOrEqual,"<=","upperBound",arg2) +#define assureMoreOrEqual(arg1,arg2) assureOp("assureMoreOrEqual","upperBound",arg1,compareMoreOrEqual,">=","lowerBound",arg2) +#define assureTrue(arg) assertEqual(arg,true) +#define assureFalse(arg) assertEqual(arg,false) + diff --git a/exe/arduino_ci_remote.rb b/exe/arduino_ci_remote.rb index cdeef346..8fe947e0 100755 --- a/exe/arduino_ci_remote.rb +++ b/exe/arduino_ci_remote.rb @@ -72,8 +72,10 @@ def assure(message, &block) # do that, set the URLs, and download the packages all_packages = all_platforms.values.map { |v| v[:package] }.uniq.reject(&:nil?) all_urls = all_packages.map { |p| config.package_url(p) }.uniq.reject(&:nil?) -assure("Setting board manager URLs") do - @arduino_cmd.set_pref("boardsmanager.additional.urls", all_urls.join(",")) +unless all_urls.empty? + assure("Setting board manager URLs") do + @arduino_cmd.set_pref("boardsmanager.additional.urls", all_urls.join(",")) + end end all_packages.each do |p| @@ -86,26 +88,6 @@ def assure(message, &block) assure("Installing aux library '#{l}'") { @arduino_cmd.install_library(l) } end -attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") } - -library_examples.each do |example_path| - ovr_config = config.from_example(example_path) - ovr_config.platforms_to_build.each do |p| - board = all_platforms[p][:board] - assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) } - example_name = File.basename(example_path) - attempt("Verifying #{example_name}") do - ret = @arduino_cmd.verify_sketch(example_path) - unless ret - puts - puts "Last command: #{@arduino_cmd.last_msg}" - puts @arduino_cmd.last_err - end - ret - end - end -end - config.platforms_to_unittest.each do |p| board = all_platforms[p][:board] assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) } @@ -129,4 +111,24 @@ def assure(message, &block) end end +attempt("Setting compiler warning level") { @arduino_cmd.set_pref("compiler.warning_level", "all") } + +library_examples.each do |example_path| + ovr_config = config.from_example(example_path) + ovr_config.platforms_to_build.each do |p| + board = all_platforms[p][:board] + assure("Switching to board for #{p} (#{board})") { @arduino_cmd.use_board(board) } + example_name = File.basename(example_path) + attempt("Verifying #{example_name}") do + ret = @arduino_cmd.verify_sketch(example_path) + unless ret + puts + puts "Last command: #{@arduino_cmd.last_msg}" + puts @arduino_cmd.last_err + end + ret + end + end +end + terminate(true) diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 8f634583..678b5e1d 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -35,12 +35,28 @@ def initialize(base_dir) @last_msg = "" end + # Guess whether a file is part of the vendor bundle (indicating we should ignore it). + # + # This assumes the vendor bundle will be at `vendor/bundle` and not some other location + # @param path [String] The path to check + # @return [Array] The paths of the found files + def vendor_bundle?(path) + # TODO: look for Gemfile, look for .bundle/config and get BUNDLE_PATH from there? + base = File.join(@base_dir, "vendor") + real = File.join(File.realpath(@base_dir), "vendor") + return true if path.start_with?(base) + return true if path.start_with?(real) + false + end + # Get a list of all CPP source files in a directory and its subdirectories # @param some_dir [String] The directory in which to begin the search # @return [Array] The paths of the found files def cpp_files_in(some_dir) real = File.realpath(some_dir) - Find.find(real).select { |path| CPP_EXTENSIONS.include?(File.extname(path)) } + files = Find.find(real).reject { |path| File.directory?(path) } + ret = files.select { |path| CPP_EXTENSIONS.include?(File.extname(path)) } + ret end # CPP files that are part of the project library under test @@ -50,6 +66,7 @@ def cpp_files cpp_files_in(@base_dir).reject do |p| next true if File.dirname(p).include?(tests_dir) next true if File.dirname(p).include?(real_tests_dir) + next true if vendor_bundle?(p) end end @@ -80,8 +97,12 @@ def test_files # Find all directories in the project library that include C++ header files # @return [Array] def header_dirs - files = Find.find(@base_dir).select { |path| HPP_EXTENSIONS.include?(File.extname(path)) } - files.map { |path| File.dirname(path) }.uniq + real = File.realpath(@base_dir) + all_files = Find.find(real).reject { |path| File.directory?(path) } + unbundled = all_files.reject { |path| vendor_bundle?(path) } + files = unbundled.select { |path| HPP_EXTENSIONS.include?(File.extname(path)) } + ret = files.map { |path| File.dirname(path) }.uniq + ret end # wrapper for the GCC command diff --git a/misc/default.yaml b/misc/default.yaml index 4eb439aa..1fdb3a85 100644 --- a/misc/default.yaml +++ b/misc/default.yaml @@ -1,3 +1,5 @@ +# Note that ci_config_spec.rb has tests for this file's contents + packages: esp8266:esp8266: url: http://arduino.esp8266.com/stable/package_esp8266com_index.json @@ -22,7 +24,7 @@ platforms: warnings: flags: zero: - board: arduino:samd:zero + board: arduino:samd:arduino_zero_native package: arduino:samd gcc: features: @@ -30,7 +32,7 @@ platforms: warnings: flags: esp8266: - board: esp8266:esp8266:huzzah + board: esp8266:esp8266:huzzah:FlashSize=4M3M,CpuFrequency=80 package: esp8266:esp8266 gcc: features: @@ -67,6 +69,7 @@ compile: platforms: - uno - due + - zero - leonardo unittest: @@ -74,4 +77,5 @@ unittest: platforms: - uno - due + - zero - leonardo diff --git a/spec/ci_config_spec.rb b/spec/ci_config_spec.rb index 929fa166..6f72a84c 100644 --- a/spec/ci_config_spec.rb +++ b/spec/ci_config_spec.rb @@ -19,13 +19,13 @@ zero = default_config.platform_definition("zero") expect(zero.class).to eq(Hash) - expect(zero[:board]).to eq("arduino:samd:zero") + expect(zero[:board]).to eq("arduino:samd:arduino_zero_native") expect(zero[:package]).to eq("arduino:samd") expect(zero[:gcc].class).to eq(Hash) expect(default_config.package_url("adafruit:avr")).to eq("https://adafruit.github.io/arduino-board-index/package_adafruit_index.json") - expect(default_config.platforms_to_build).to match(["uno", "due", "leonardo"]) - expect(default_config.platforms_to_unittest).to match(["uno", "due", "leonardo"]) + expect(default_config.platforms_to_build).to match(["uno", "due", "zero", "leonardo"]) + expect(default_config.platforms_to_unittest).to match(["uno", "due", "zero", "leonardo"]) expect(default_config.aux_libraries_for_build).to match([]) expect(default_config.aux_libraries_for_unittest).to match([]) end