From d39e24ed03a5631e2cb59322673a1ae0dd0846e0 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 1 Dec 2020 16:26:20 -0500 Subject: [PATCH 1/9] Use CI resources more efficiently --- .github/workflows/linux.yaml | 41 ++++--------------------------- .github/workflows/macos.yaml | 41 ++++--------------------------- .github/workflows/windows.yaml | 45 ++++++---------------------------- CHANGELOG.md | 1 + 4 files changed, 18 insertions(+), 110 deletions(-) diff --git a/.github/workflows/linux.yaml b/.github/workflows/linux.yaml index 59185570..f8e6a5d1 100644 --- a/.github/workflows/linux.yaml +++ b/.github/workflows/linux.yaml @@ -1,52 +1,23 @@ # This is the name of the workflow, visible on GitHub UI name: linux -# Run on a Push or a Pull Request -on: [push, pull_request] +on: [pull_request] jobs: - rspec: + "unittest_lint_sampleproject": runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for rspec - - name: Build and Execute + - name: Check style, funcionality, and usage run: | g++ -v - bundle install - bundle exec rspec --backtrace - - rubocop: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for rubocop - - name: Build and Execute - run: | bundle install bundle exec rubocop --version bundle exec rubocop -D . - - TestSomething: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for TestSomething - - name: Build and Execute - run: | - g++ -v + bundle exec rspec --backtrace cd SampleProjects/TestSomething bundle install bundle exec arduino_ci.rb @@ -58,9 +29,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for NetworkLib - - name: Build and Execute + - name: Test NetworkLib from scratch run: | g++ -v cd SampleProjects/NetworkLib diff --git a/.github/workflows/macos.yaml b/.github/workflows/macos.yaml index 68d7ed3c..4e426e88 100644 --- a/.github/workflows/macos.yaml +++ b/.github/workflows/macos.yaml @@ -1,52 +1,23 @@ # This is the name of the workflow, visible on GitHub UI name: macos -# Run on a Push or a Pull Request -on: [push, pull_request] +on: [pull_request] jobs: - rspec: + "unittest_lint_sampleproject": runs-on: macos-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for rspec - - name: Build and Execute + - name: Check style, funcionality, and usage run: | g++ -v - bundle install - bundle exec rspec --backtrace - - rubocop: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for rubocop - - name: Build and Execute - run: | bundle install bundle exec rubocop --version bundle exec rubocop -D . - - TestSomething: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for TestSomething - - name: Build and Execute - run: | - g++ -v + bundle exec rspec --backtrace cd SampleProjects/TestSomething bundle install bundle exec arduino_ci.rb @@ -58,9 +29,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for NetworkLib - - name: Build and Execute + - name: Test NetworkLib from scratch run: | g++ -v cd SampleProjects/NetworkLib diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 4b65ff81..2bb9d3d7 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -1,66 +1,35 @@ # This is the name of the workflow, visible on GitHub UI name: windows -# Run on a Push or a Pull Request -on: [push, pull_request] +on: [pull_request] jobs: - rspec: - runs-on: windows-latest + "unittest_lint_sampleproject": + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for rspec - - name: Build and Execute + - name: Check style, funcionality, and usage run: | g++ -v - bundle install - bundle exec rspec --backtrace - - rubocop: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for rubocop - - name: Build and Execute - run: | bundle install bundle exec rubocop --version bundle exec rubocop -D . - - TestSomething: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - - # Install and run Arduino CI tests for TestSomething - - name: Build and Execute - run: | - g++ -v + bundle exec rspec --backtrace cd SampleProjects/TestSomething bundle install bundle exec arduino_ci.rb NetworkLib: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - - # Install and run Arduino CI tests for Network - - name: Build and Execute + - name: Test NetworkLib from scratch run: | g++ -v cd SampleProjects/NetworkLib diff --git a/CHANGELOG.md b/CHANGELOG.md index 075db281..3f30f652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added ### Changed +- Conserve CI testing minutes by grouping CI into fewer runs ### Deprecated From 0cfe00e08de322b0cb263c342f7338c64a9ba802 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 1 Dec 2020 09:50:45 -0500 Subject: [PATCH 2/9] Fix Host reference in test runner script --- CHANGELOG.md | 1 + exe/arduino_ci.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f30f652..ed350157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed ### Fixed +- Improper reference to `Host` in `arduino_ci.rb` test runner is now properly qualified ### Security diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index c07faf8c..879ef99b 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -157,7 +157,7 @@ def file_is_hidden_somewhere?(path) # print out some files def display_files(pathname) # `find` doesn't follow symlinks, so we should instead - realpath = Host.symlink?(pathname) ? Host.readlink(pathname) : pathname + realpath = ArduinoCI::Host.symlink?(pathname) ? ArduinoCI::Host.readlink(pathname) : pathname # suppress directories and dotfile-based things all_files = realpath.find.select(&:file?) From ea815b2e2ffb23bcf2b0a1e829890e587941a8bc Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 2 Dec 2020 12:06:46 -0500 Subject: [PATCH 3/9] move host tests into host spec --- spec/arduino_ci_spec.rb | 12 ------------ spec/host_spec.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/spec/arduino_ci_spec.rb b/spec/arduino_ci_spec.rb index 05b97fd2..1ed2e58c 100644 --- a/spec/arduino_ci_spec.rb +++ b/spec/arduino_ci_spec.rb @@ -8,15 +8,3 @@ end end end - -RSpec.describe ArduinoCI::Host do - next if skip_ruby_tests - context "which" do - it "can find things with which" do - ruby_path = ArduinoCI::Host.which("ruby") - expect(ruby_path).not_to be nil - expect(ruby_path.include? "ruby").to be true - end - end - -end diff --git a/spec/host_spec.rb b/spec/host_spec.rb index f3523b1e..ac40f4e0 100644 --- a/spec/host_spec.rb +++ b/spec/host_spec.rb @@ -50,4 +50,12 @@ def with_tmpdir(path) end end + context "which" do + it "can find things with which" do + ruby_path = ArduinoCI::Host.which("ruby") + expect(ruby_path).not_to be nil + expect(ruby_path.include? "ruby").to be true + end + end + end From 715cc645ae1cba43e835589ee0fac43e81a8c081 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 1 Dec 2020 10:03:46 -0500 Subject: [PATCH 4/9] Auto-create libraries directory when ensuring installation of arduino backend --- CHANGELOG.md | 1 + exe/ensure_arduino_installation.rb | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed350157..cab2b574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +- `ensure_arduino_installation.rb` now ensures the existence of the library directory as well ### Changed - Conserve CI testing minutes by grouping CI into fewer runs diff --git a/exe/ensure_arduino_installation.rb b/exe/ensure_arduino_installation.rb index c0e9799d..7dc58c6e 100755 --- a/exe/ensure_arduino_installation.rb +++ b/exe/ensure_arduino_installation.rb @@ -2,4 +2,10 @@ require 'arduino_ci' # this will exit after Arduino is located and/or forcibly installed -ArduinoCI::ArduinoInstallation.autolocate! +backend = ArduinoCI::ArduinoInstallation.autolocate! +lib_dir = backend.lib_dir + +unless lib_dir.exist? + puts "Creating libraries directory #{lib_dir}" + lib_dir.mkpath +end From 4f565ea83e697e8cbad08d534e1d33202344f0f9 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 1 Dec 2020 18:08:05 -0500 Subject: [PATCH 5/9] Env vars to escalate certain files not being discovered during CI --- CHANGELOG.md | 1 + REFERENCE.md | 11 +++++++++++ exe/arduino_ci.rb | 33 +++++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab2b574..1b8cf44e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added - `ensure_arduino_installation.rb` now ensures the existence of the library directory as well +- Environment variables to escalate unit tests or examples not being found during CI testing ### Changed - Conserve CI testing minutes by grouping CI into fewer runs diff --git a/REFERENCE.md b/REFERENCE.md index d54b5828..ab47316d 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -33,11 +33,22 @@ This completely skips the compilation tests (of library examples) portion of the This allows a file (or glob) pattern to be executed in your tests directory, creating a whitelist of files to test. E.g. `--testfile-select=test_animal_*.cpp` would match `test_animal_cat.cpp` and `test_animal_dog.cpp` (testing only those) and not `test_plant_rose.cpp`. + ### `--testfile-reject` option This allows a file (or glob) pattern to be executed in your tests directory, creating a blacklist of files to skip. E.g. `--testfile-reject=test_animal_*.cpp` would match `test_animal_cat.cpp` and `test_animal_dog.cpp` (skipping those) and test only `test_plant_rose.cpp`, `test_plant_daisy.cpp`, etc. +### `EXPECT_UNITTESTS` environment variable + +If set, testing will fail if no unit test files are detected (or if the directory does not exist). This is to avoid communicating a passing status in cases where a commit may have accidentally moved or deleted the test files. + + +### `EXPECT_EXAMPLES` environment variable + +If set, testing will fail if no example sketches are detected. This is to avoid communicating a passing status in cases where a commit may have accidentally moved or deleted the examples. + + ## Indirectly Overriding Build Behavior (medium term use), and Advanced Options For build behavior that you'd like to persist across commits (e.g. defining the set of platforms to test against, disabling a test that you expect to re-enable at some future point), a special configuration file called `.arduino-ci.yml` can be used. There are 3 places you can put them: diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index 879ef99b..fa3feb76 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -6,6 +6,8 @@ WIDTH = 80 FIND_FILES_INDENT = 4 +VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze +VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze @failure_count = 0 @passfail = proc { |result| result ? "✓" : "✗" } @@ -48,6 +50,10 @@ def self.parse(options) opts.on("-h", "--help", "Prints this help") do puts opts + puts + puts "Additionally, the following environment variables control the script:" + puts " - #{VAR_EXPECT_EXAMPLES} - if set, testing will fail if no example sketches are present" + puts " - #{VAR_EXPECT_UNITTESTS} - if set, testing will fail if no unit tests are present" exit end end @@ -189,6 +195,23 @@ def install_arduino_library_dependencies(library_names, on_behalf_of, already_in installed end +def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir) + if ENV[expectation_envvar].nil? + inform_multiline("Skipping #{operation}; no #{filegroup_name} were found in #{dir}") do + puts " In case that's an error, this is what was found in the #{dir_description}:" + display_files(dir) + puts "To force an error in this case, set the environment variable #{expectation_envvar}" + true + end + else + assure_multiline("No #{filegroup_name} were found in #{dir} and #{expectation_envvar} was set") do + puts " This is what was found in the #{dir_description}:" + display_files(dir) + false + end + end +end + def perform_unit_tests(cpp_library, file_config) if @cli_options[:skip_unittests] inform("Skipping unit tests") { "as requested via command line" } @@ -239,11 +262,7 @@ def perform_unit_tests(cpp_library, file_config) end end elsif cpp_library.test_files.empty? - inform_multiline("Skipping unit tests; no test files were found in #{cpp_library.tests_dir}") do - puts " In case that's an error, this is what was found in the tests directory:" - display_files(cpp_library.tests_dir) - true - end + handle_expectation_of_files(VAR_EXPECT_UNITTESTS, "unit tests", "test files", "tests directory", cpp_library.tests_dir) elsif config.platforms_to_unittest.empty? inform("Skipping unit tests") { "no platforms were requested" } else @@ -337,9 +356,7 @@ def perform_example_compilation_tests(cpp_library, config) inform("Skipping builds") { "no platforms were requested" } return elsif library_examples.empty? - inform_multiline("Skipping builds; no examples found in #{installed_library_path}") do - display_files(installed_library_path) - end + handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the library directory", installed_library_path) return end From 6144f38546963119baa272bf35b9fd497edfbfd0 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 2 Dec 2020 00:02:16 -0500 Subject: [PATCH 6/9] CppLibrary.examples_dir --- lib/arduino_ci/cpp_library.rb | 5 +++++ spec/cpp_library_spec.rb | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 2d481f1b..461b063a 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -82,6 +82,11 @@ def path @backend.lib_dir + name_on_disk end + # @return [String] The parent directory of all examples + def examples_dir + path + "examples" + end + # Determine whether a library is present in the lib dir # # Note that `true` doesn't guarantee that the library is valid/installed diff --git a/spec/cpp_library_spec.rb b/spec/cpp_library_spec.rb index 48bae5bf..3e4a4a95 100644 --- a/spec/cpp_library_spec.rb +++ b/spec/cpp_library_spec.rb @@ -217,6 +217,13 @@ def verified_install(backend, path) end end + context "examples_dir" do + it "locates the examples directory" do + relative_path = @cpp_library.examples_dir.relative_path_from(@base_dir) + expect(relative_path.to_s).to eq("#{sampleproject}/examples") + end + end + context "test_files" do it "finds cpp files in directory" do relative_paths = @cpp_library.test_files.map { |f| f.relative_path_from(@base_dir) } From 9369dac10df466df1f97ce57e0db3e099b287992 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 2 Dec 2020 01:17:12 -0500 Subject: [PATCH 7/9] Upgrade rubocop --- .rubocop.yml | 25 ++++++++++++++++++++++--- Gemfile | 2 +- exe/arduino_ci_remote.rb | 2 +- lib/arduino_ci/arduino_backend.rb | 4 ++-- lib/arduino_ci/arduino_downloader.rb | 2 +- lib/arduino_ci/ci_config.rb | 5 +++-- lib/arduino_ci/cpp_library.rb | 5 ++--- lib/arduino_ci/host.rb | 4 ++-- 8 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 07841202..3d3c64f9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,7 @@ AllCops: + TargetRubyVersion: 2.6 + NewCops: enable + SuggestExtensions: false Exclude: - '*.gemspec' - 'spec/*.rb' @@ -13,6 +16,24 @@ Style/RescueStandardError: Security/Open: Enabled: false +Style/FrozenStringLiteralComment: + Enabled: false + +# broken :( https://github.com/rubocop-hq/rubocop/issues/9144 +Style/StringConcatenation: + Enabled: false + +# Ruins git diffs +Style/AccessorGrouping: + Enabled: false + +# Ruins keeping the upper half of the conditional smaller +Style/NegatedIfElseCondition: + Enabled: false + +# affects calling style? +Style/OptionalBooleanParameter: + Enabled: false # Extra lines for readability Layout/EmptyLinesAroundClassBody: @@ -37,9 +58,7 @@ Layout/EndAlignment: Layout/CaseIndentation: EnforcedStyle: end -Metrics/LineLength: - Description: Limit lines to 80 characters. - StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits +Layout/LineLength: Enabled: true Max: 130 diff --git a/Gemfile b/Gemfile index 995ed3f5..d3ff73d9 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,6 @@ gemspec gem "bundler", "> 1.15", require: false, group: :test gem "keepachangelog_manager", "~> 0.0.2", require: false, group: :test gem "rspec", "~> 3.0", require: false, group: :test -gem 'rubocop', '~>0.59.0', require: false, group: :test +gem 'rubocop', '~>1.5.0', require: false, group: :test gem 'simplecov', require: false, group: :test gem 'yard', '~>0.9.11', require: false, group: :test diff --git a/exe/arduino_ci_remote.rb b/exe/arduino_ci_remote.rb index e76226b6..4833f271 100755 --- a/exe/arduino_ci_remote.rb +++ b/exe/arduino_ci_remote.rb @@ -1,3 +1,3 @@ #!/usr/bin/env ruby puts "arduino_ci_remote.rb is deprecated in favor of arduino_ci.rb." -require_relative "arduino_ci.rb" +require_relative "arduino_ci" diff --git a/lib/arduino_ci/arduino_backend.rb b/lib/arduino_ci/arduino_backend.rb index 32d03fd8..2b95fbb0 100644 --- a/lib/arduino_ci/arduino_backend.rb +++ b/lib/arduino_ci/arduino_backend.rb @@ -50,9 +50,9 @@ def initialize(binary_path) def _wrap_run(work_fn, *args, **kwargs) # do some work to extract & merge environment variables if they exist - has_env = !args.empty? && args[0].class == Hash + has_env = !args.empty? && args[0].instance_of?(Hash) env_vars = has_env ? args[0] : {} - actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args + actual_args = has_env ? args[1..] : args # need to shift over if we extracted args custom_config = @config_dir.nil? ? [] : ["--config-file", @config_dir.to_s] full_args = [binary_path.to_s, "--format", "json"] + custom_config + actual_args full_cmd = env_vars.empty? ? full_args : [env_vars] + full_args diff --git a/lib/arduino_ci/arduino_downloader.rb b/lib/arduino_ci/arduino_downloader.rb index 12a2d791..2acdf30c 100644 --- a/lib/arduino_ci/arduino_downloader.rb +++ b/lib/arduino_ci/arduino_downloader.rb @@ -104,7 +104,7 @@ def download total_size += size needed_dots = (total_size / chunk_size).to_i unprinted_dots = needed_dots - dots - @output.print("." * unprinted_dots) if unprinted_dots > 0 + @output.print("." * unprinted_dots) if unprinted_dots.positive? dots = needed_dots end diff --git a/lib/arduino_ci/ci_config.rb b/lib/arduino_ci/ci_config.rb index 315f770b..3c5d2a9c 100644 --- a/lib/arduino_ci/ci_config.rb +++ b/lib/arduino_ci/ci_config.rb @@ -66,6 +66,7 @@ def default attr_accessor :platform_info attr_accessor :compile_info attr_accessor :unittest_info + def initialize @package_info = {} @platform_info = {} @@ -107,7 +108,7 @@ def validate_data(rootname, source, schema) good_data = {} source.each do |key, value| ksym = key.to_sym - expected_type = schema[ksym].class == Class ? schema[ksym] : Hash + expected_type = schema[ksym].instance_of?(Class) ? schema[ksym] : Hash if !schema.include?(ksym) puts "Warning: unknown field '#{ksym}' under definition for #{rootname}" elsif value.nil? @@ -115,7 +116,7 @@ def validate_data(rootname, source, schema) elsif value.class != expected_type puts "Warning: expected field '#{ksym}' of #{rootname} to be '#{expected_type}', got '#{value.class}'" else - good_data[ksym] = value.class == Hash ? validate_data(key, value, schema[ksym]) : value + good_data[ksym] = value.instance_of?(Hash) ? validate_data(key, value, schema[ksym]) : value end end good_data diff --git a/lib/arduino_ci/cpp_library.rb b/lib/arduino_ci/cpp_library.rb index 461b063a..4ad7307f 100644 --- a/lib/arduino_ci/cpp_library.rb +++ b/lib/arduino_ci/cpp_library.rb @@ -407,8 +407,7 @@ def all_arduino_library_dependencies!(additional_libraries = []) other_lib.install unless other_lib.installed? other_lib.all_arduino_library_dependencies! end.flatten - ret = (additional_libraries + recursive).uniq - ret + (additional_libraries + recursive).uniq end # Arduino library directories containing sources -- only those of the dependencies @@ -522,7 +521,7 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g # @param executable [Pathname] the path to the test file def print_stack_dump(executable) possible_dumpfiles = [ - executable.sub_ext(executable.extname + ".stackdump") + executable.sub_ext("#{executable.extname}.stackdump") ] possible_dumpfiles.select(&:exist?).each do |dump| puts "========== Stack dump from #{dump}:" diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index 6d175bc4..2cf82c59 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -8,10 +8,10 @@ module ArduinoCI class Host # TODO: this came from https://stackoverflow.com/a/22716582/2063546 # and I'm not sure if it can be replaced by self.os == :windows - WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/ + WINDOWS_VARIANT_REGEX = /mswin32|cygwin|mingw|bccwin/.freeze # e.g. 11/27/2020 01:02 AM ExcludeSomething [C:\projects\arduino-ci\SampleProjects\ExcludeSomething] - DIR_SYMLINK_REGEX = %r{\d+/\d+/\d+\s+[^<]+\s+(.*) \[([^\]]+)\]} + DIR_SYMLINK_REGEX = %r{\d+/\d+/\d+\s+[^<]+\s+(.*) \[([^\]]+)\]}.freeze # Cross-platform way of finding an executable in the $PATH. # via https://stackoverflow.com/a/5471032/2063546 From 6a6b9e40fd586bb4042db1e1dfdc1176198c21d9 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Wed, 2 Dec 2020 10:53:57 -0500 Subject: [PATCH 8/9] Fix board family installation by providing additional_urls --- CHANGELOG.md | 1 + REFERENCE.md | 2 +- lib/arduino_ci/arduino_backend.rb | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b8cf44e..42d6c97b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Improper reference to `Host` in `arduino_ci.rb` test runner is now properly qualified +- Failure to set board manager URLs (for 3rd party board providers) has been fixed ### Security diff --git a/REFERENCE.md b/REFERENCE.md index ab47316d..6ae67bdc 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -64,7 +64,7 @@ For build behavior that you'd like to persist across commits (e.g. defining the Arduino boards are typically named in the form `manufacturer:family:model`. These definitions are not arbitrary -- they are defined in an Arduino _package_. For all but the built-in packages, you will need a package URL. Here is Adafruit's: https://adafruit.github.io/arduino-board-index/package_adafruit_index.json -Here is how you would declare a package that includes the `potato:salad` family of boards in your `.arduino-ci.yml`: +Here is how you would declare a package that includes the `potato:salad` set of platforms (aka "board family") in your `.arduino-ci.yml`: ```yaml packages: diff --git a/lib/arduino_ci/arduino_backend.rb b/lib/arduino_ci/arduino_backend.rb index 2b95fbb0..cda28752 100644 --- a/lib/arduino_ci/arduino_backend.rb +++ b/lib/arduino_ci/arduino_backend.rb @@ -121,7 +121,11 @@ def board_installed?(boardname) # @param name [String] the board name # @return [bool] whether the command succeeded def install_boards(boardfamily) - result = run_and_capture("core", "install", boardfamily) + result = if @additional_urls.empty? + run_and_capture("core", "install", boardfamily) + else + run_and_capture("core", "install", boardfamily, "--additional-urls", @additional_urls.join(",")) + end result[:success] end From 89d5a81681fd9eb1e204ad14d4bf8a02d8bff5d3 Mon Sep 17 00:00:00 2001 From: Ian Katz Date: Tue, 1 Dec 2020 22:38:07 -0500 Subject: [PATCH 9/9] Refactor test runner for clarity --- exe/arduino_ci.rb | 210 ++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 100 deletions(-) diff --git a/exe/arduino_ci.rb b/exe/arduino_ci.rb index fa3feb76..a1142cd8 100755 --- a/exe/arduino_ci.rb +++ b/exe/arduino_ci.rb @@ -5,7 +5,6 @@ require 'optparse' WIDTH = 80 -FIND_FILES_INDENT = 4 VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze VAR_EXPECT_UNITTESTS = "EXPECT_UNITTESTS".freeze @@ -99,7 +98,7 @@ def perform_action(message, multiline, mark_fn, on_fail_msg, tally_on_fail, abor else print line end - STDOUT.flush + $stdout.flush result = yield mark = mark_fn.nil? ? "" : mark_fn.call(result) # if multline, put checkmark at full width @@ -125,7 +124,7 @@ def attempt_multiline(message, &block) end # Make a nice status for something that kills the script immediately on failure -FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with ArduinoCI, or your configuration".freeze +FAILED_ASSURANCE_MESSAGE = "This may indicate a problem with your configuration; halting here".freeze def assure(message, &block) perform_action(message, false, @passfail, FAILED_ASSURANCE_MESSAGE, true, true, &block) end @@ -145,9 +144,7 @@ def inform_multiline(message, &block) # Assure that a platform exists and return its definition def assured_platform(purpose, name, config) platform_definition = config.platform_definition(name) - assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") do - !platform_definition.nil? - end + assure("Requested #{purpose} platform '#{name}' is defined in 'platforms' YML") { !platform_definition.nil? } platform_definition end @@ -170,17 +167,15 @@ def display_files(pathname) non_hidden = all_files.reject { |path| file_is_hidden_somewhere?(path) } # print files with an indent - margin = " " * FIND_FILES_INDENT - non_hidden.each { |p| puts "#{margin}#{p}" } + puts " Files (excluding hidden files): #{non_hidden.size}" + non_hidden.each { |p| puts " #{p}" } end # @return [Array] The list of installed libraries def install_arduino_library_dependencies(library_names, on_behalf_of, already_installed = []) installed = already_installed.clone - library_names.map { |n| @backend.library_of_name(n) }.each do |l| - if installed.include?(l) - # do nothing - elsif l.installed? + (library_names.map { |n| @backend.library_of_name(n) } - installed).each do |l| + if l.installed? inform("Using pre-existing dependency of #{on_behalf_of}") { l.name } else assure("Installing dependency of #{on_behalf_of}: '#{l.name}'") do @@ -195,30 +190,77 @@ def install_arduino_library_dependencies(library_names, on_behalf_of, already_in installed end -def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir) +# @param example_platform_info [Hash] mapping of platform name to package information +# @param board_package_url [Hash] mapping of package name to URL +def install_all_packages(example_platform_info, board_package_url) + # with all platform info, we can extract unique packages and their urls + # do that, set the URLs, and download the packages + all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?) + + # make sure any non-builtin package has a URL defined + all_packages.each { |p| assure("Board package #{p} has a defined URL") { board_package_url[p] } } + + # set up all the board manager URLs. + # we can safely reject nils now, they would be for the builtins + all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?) + unless all_urls.empty? + assure_multiline("Setting board manager URLs") do + @backend.board_manager_urls = all_urls + result = @backend.board_manager_urls + result.each { |u| puts " #{u}" } + (all_urls - result).empty? # check that all_urls is completely contained in the result + end + end + all_packages.each { |p| assure("Installing board package #{p}") { @backend.install_boards(p) } } +end + +# @param expectation_envvar [String] the name of the env var to check +# @param operation [String] a description of what operation we might be skipping +# @param filegroup_name [String] a description of the set of files without which we effectively skip the operation +# @param dir_description [String] a description of the directory where we looked for the files +# @param dir [Pathname] the directory where we looked for the files +def handle_expectation_of_files(expectation_envvar, operation, filegroup_name, dir_description, dir_path) + # alert future me about running the script from the wrong directory, instead of doing the huge file dump + # otherwise, assume that the user might be running the script on a library with no actual unit tests + if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd) + inform_multiline("arduino_ci seems to be trying to test itself") do + [ + "arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against", + "the core library isn't really a valid thing to do... but it's easy for a developer (including the", + "owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of", + "the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!" + ].each { |l| puts " #{l}" } + false + end + exit(1) + end + + # either the directory is empty, or it doesn't exist at all. message accordingly. + (problem, dir_desc, dir) = if dir_path.exist? + ["No #{filegroup_name} were found in", dir_description, dir_path] + else + ["No #{dir_description} at", "base directory", dir_path.parent] + end + + inform(problem) { dir_path } + inform("Environment variable #{expectation_envvar} is") { "(#{ENV[expectation_envvar].class}) #{ENV[expectation_envvar]}" } if ENV[expectation_envvar].nil? - inform_multiline("Skipping #{operation}; no #{filegroup_name} were found in #{dir}") do - puts " In case that's an error, this is what was found in the #{dir_description}:" + inform_multiline("Skipping #{operation}") do + puts " In case that's an error, this is what was found in the #{dir_desc}:" display_files(dir) - puts "To force an error in this case, set the environment variable #{expectation_envvar}" + puts " To force an error in this case, set the environment variable #{expectation_envvar}" true end else - assure_multiline("No #{filegroup_name} were found in #{dir} and #{expectation_envvar} was set") do - puts " This is what was found in the #{dir_description}:" + assure_multiline("Dumping project's #{dir_desc} before exit") do display_files(dir) false end end end -def perform_unit_tests(cpp_library, file_config) - if @cli_options[:skip_unittests] - inform("Skipping unit tests") { "as requested via command line" } - return - end - config = file_config.with_override_config(@cli_options[:ci_config]) - +# report and return the set of compilers +def get_annotated_compilers(config, cpp_library) # check GCC compilers = config.compilers_to_use assure("The set of compilers (#{compilers.length}) isn't empty") { !compilers.empty? } @@ -232,62 +274,54 @@ def perform_unit_tests(cpp_library, file_config) end inform("libasan availability for #{gcc_binary}") { cpp_library.libasan?(gcc_binary) } end + compilers +end - # Ensure platforms exist for unit test, and save their info in all_platform_info keyed by name - all_platform_info = {} - config.platforms_to_unittest.each { |p| all_platform_info[p] = assured_platform("unittest", p, config) } +def perform_unit_tests(cpp_library, file_config) + if @cli_options[:skip_unittests] + inform("Skipping unit tests") { "as requested via command line" } + return + end + + config = file_config.with_override_config(@cli_options[:ci_config]) + compilers = get_annotated_compilers(config, cpp_library) + config.platforms_to_unittest.each_with_object({}) { |p, acc| acc[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? - # alert future me about running the script from the wrong directory, instead of doing the huge file dump - # otherwise, assume that the user might be running the script on a library with no actual unit tests - if Pathname.new(__dir__).parent == Pathname.new(Dir.pwd) - inform_multiline("arduino_ci seems to be trying to test itself") do - [ - "arduino_ci (the ruby gem) isn't an arduino project itself, so running the CI test script against", - "the core library isn't really a valid thing to do... but it's easy for a developer (including the", - "owner) to mistakenly do just that. Hello future me, you probably meant to run this against one of", - "the sample projects in SampleProjects/ ... if not, please submit a bug report; what a wild case!" - ].each { |l| puts " #{l}" } - false - end - exit(1) - else - inform_multiline("Skipping unit tests; no tests dir at #{cpp_library.tests_dir}") do - puts " In case that's an error, this is what was found in the library:" - display_files(cpp_library.tests_dir.parent) - true - end - end - elsif cpp_library.test_files.empty? + # Handle lack of test files + if cpp_library.test_files.empty? handle_expectation_of_files(VAR_EXPECT_UNITTESTS, "unit tests", "test files", "tests directory", cpp_library.tests_dir) - elsif config.platforms_to_unittest.empty? + return + end + + # Handle lack of platforms + if config.platforms_to_unittest.empty? inform("Skipping unit tests") { "no platforms were requested" } - else - install_arduino_library_dependencies(config.aux_libraries_for_unittest, "") - - config.platforms_to_unittest.each do |p| - 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} for #{p}") do - exe = cpp_library.build_for_test_with_configuration( - unittest_path, - config.aux_libraries_for_unittest, - gcc_binary, - config.gcc_config(p) - ) - puts - unless exe - puts "Last command: #{cpp_library.last_cmd}" - puts cpp_library.last_out - puts cpp_library.last_err - next false - end - cpp_library.run_test_file(exe) + return + end + + install_arduino_library_dependencies(config.aux_libraries_for_unittest, "") + + config.platforms_to_unittest.each do |p| + 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} for #{p}") do + exe = cpp_library.build_for_test_with_configuration( + unittest_path, + config.aux_libraries_for_unittest, + gcc_binary, + config.gcc_config(p) + ) + puts + unless exe + puts "Last command: #{cpp_library.last_cmd}" + puts cpp_library.last_out + puts cpp_library.last_err + next false end + cpp_library.run_test_file(exe) end end end @@ -325,38 +359,14 @@ def perform_example_compilation_tests(cpp_library, config) aux_libraries.merge(ovr_config.aux_libraries_for_build) end - # with all platform info, we can extract unique packages and their urls - # do that, set the URLs, and download the packages - all_packages = example_platform_info.values.map { |v| v[:package] }.uniq.reject(&:nil?) - - # make sure any non-builtin package has a URL defined - all_packages.each do |p| - assure("Board package #{p} has a defined URL") { board_package_url[p] } - end - - # set up all the board manager URLs. - # we can safely reject nils now, they would be for the builtins - all_urls = all_packages.map { |p| board_package_url[p] }.uniq.reject(&:nil?) - - unless all_urls.empty? - assure("Setting board manager URLs") do - @backend.board_manager_urls = all_urls - end - end - - all_packages.each do |p| - assure("Installing board package #{p}") do - @backend.install_boards(p) - end - end - + install_all_packages(example_platform_info, board_package_url) install_arduino_library_dependencies(aux_libraries, "") if config.platforms_to_build.empty? inform("Skipping builds") { "no platforms were requested" } return elsif library_examples.empty? - handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the library directory", installed_library_path) + handle_expectation_of_files(VAR_EXPECT_EXAMPLES, "builds", "examples", "the examples directory", cpp_library.examples_dir) return end