Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Big fix branch for 2021 #296

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf
* text=auto eol=lf
2 changes: 1 addition & 1 deletion .github/workflows/linux.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This is the name of the workflow, visible on GitHub UI
name: linux

on: [pull_request]
on: [push, pull_request]

jobs:
"unittest_lint_sampleproject":
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/windows.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on: [pull_request]

jobs:
"unittest_lint_sampleproject":
runs-on: ubuntu-latest
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
Expand All @@ -23,7 +23,7 @@ jobs:
bundle exec arduino_ci.rb

NetworkLib:
runs-on: ubuntu-latest
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Working directory is now printed in test runner output
- Explicitly include `irb` via rubygems
- `arduino:megaavr` architecture is now included by default, using the `nano_every` platform, via midasgossye

### Changed
- Update .gitattributes so we have consistent line endings
- Test runner detects console width if possible, allowing variable width from 80-132 chars
- Unit test executables are now built as tempfiles

### Deprecated

### Removed

### Fixed
- A missing `examples` directory no longer causes a crash in `cpp_library.rb`
- Referring to an undefined platform no longer causes a crash; it's now a helpful error message
- A copy/paste error that prevented compiler warning flags from being supplied has been fixed, via jgfoster
- RSpec was not communicating compile errors from unit test executables that failed to build. Now it does, via jgfoster
- Windows paths now avoid picking up backslashes, for proper equality comparisons

### Security

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ArduinoCI uses a very standard GitHub workflow.
* If you are submitting code, use `master` as the base branch
* If you are submitting broken unit tests (illustrating a bug that should be fixed), use `tdd` as the base branch.

Pull requests will trigger a Travis CI job. The following two commands will be expected to pass (so you may want to run them locally before opening the pull request):
Pull requests will trigger a CI job. The following two commands will be expected to pass (so you may want to run them locally before opening the pull request):

* `bundle exec rubocop -D .` - code style tests
* `bundle exec rspec` - functional tests
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
gemspec

gem "bundler", "> 1.15", require: false, group: :test
gem "irb", "~> 1.3.5", require: false
gem "keepachangelog_manager", "~> 0.0.2", require: false, group: :test
gem "rspec", "~> 3.0", require: false, group: :test
gem 'rubocop', '~>1.5.0', require: false, group: :test
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
[![Gitter](https://badges.gitter.im/Arduino-CI/arduino_ci.svg)](https://gitter.im/Arduino-CI/arduino_ci?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![GitHub Marketplace](https://img.shields.io/badge/Get_it-on_Marketplace-informational.svg)](https://github.com/marketplace/actions/arduino_ci)

Arduino CI was created to enable better collaboration among Arduino library maintainers and contributors, by enabling automated code checks to be performed as part of a pull request process.
Arduino CI tests [Arduino libraries](https://arduino.github.io/arduino-cli/library-specification/); it was created to enable better collaboration among Arduino library maintainers and contributors, by enabling automated code checks to be performed as part of a pull request process.

* enables running unit tests against the library **without hardware present**
* provides a system of mocks that allow fine-grained control over the hardware inputs, including the system's clock
* verifies compilation of any example sketches included in the library
* can test a wide range of arduino boards with different hardware options available
* compares entries in `library.properties` to the contents of the library and reports mismatches
* can be run both locally and as part of CI (GitHub Actions, TravisCI, Appveyor, etc.)
* runs on multiple platforms -- any platform that supports the Arduino IDE
* provides detailed analysis of segfaults in compilers that support such debugging features
Expand Down
1 change: 1 addition & 0 deletions SampleProjects/TestSomething/Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
source 'https://rubygems.org'
gem 'arduino_ci', path: '../../'
gem "irb", "~> 1.3.5", require: false
10 changes: 5 additions & 5 deletions SampleProjects/TestSomething/test/godmode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ unittest(shift_in) {
originalSize = state->digitalPin[clockPin].historySize();

input = shiftIn(dataPin, clockPin, MSBFIRST);
assertEqual(0x7C, (uint)input); // 0111 1100
assertEqual(0x7C, (uint8_t)input); // 0111 1100
assertEqual('|', input); // 0111 1100
assertEqual((uint)'|', (uint)input); // 0111 1100
assertEqual((uint8_t)'|', (uint8_t)input); // 0111 1100

// now verify clock
assertEqual(16, state->digitalPin[clockPin].historySize() - originalSize);
Expand All @@ -249,15 +249,15 @@ unittest(shift_in) {
state->reset();
state->digitalPin[dataPin].fromAscii("|", true); // 0111 1100
input = shiftIn(dataPin, clockPin, LSBFIRST); // <- note the LSB/MSB flip
assertEqual(0x3E, (uint)input); // 0011 1110
assertEqual(0x3E, (uint8_t)input); // 0011 1110
assertEqual('>', input); // 0011 1110
assertEqual((uint)'>', (uint)input); // 0011 1110
assertEqual((uint8_t)'>', (uint8_t)input); // 0011 1110

// test setting MSB
state->reset();
state->digitalPin[dataPin].fromAscii("U", true); // 0101 0101
input = shiftIn(dataPin, clockPin, LSBFIRST); // <- note the LSB/MSB flip
assertEqual(0xAA, (uint)input); // 1010 1010
assertEqual(0xAA, (uint8_t)input); // 1010 1010
}

unittest(shift_out) {
Expand Down
33 changes: 24 additions & 9 deletions exe/arduino_ci.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@
require 'set'
require 'pathname'
require 'optparse'
require 'io/console'

WIDTH = 80
# be flexible between 80 and 132 cols of output
WIDTH = begin
[132, [80, IO::console.winsize[1] - 2].max].min
rescue NoMethodError
80
end
VAR_CUSTOM_INIT_SCRIPT = "CUSTOM_INIT_SCRIPT".freeze
VAR_USE_SUBDIR = "USE_SUBDIR".freeze
VAR_EXPECT_EXAMPLES = "EXPECT_EXAMPLES".freeze
Expand All @@ -17,13 +23,9 @@
# Use some basic parsing to allow command-line overrides of config
class Parser
def self.parse(options)
unit_config = {}
output_options = {
skip_unittests: false,
skip_compilation: false,
ci_config: {
"unittest" => unit_config
},
}

opt_parser = OptionParser.new do |opts|
Expand Down Expand Up @@ -383,14 +385,13 @@ def choose_platform_set(config, reason, desired_platforms, library_properties)
end

# Unit test procedure
def perform_unit_tests(cpp_library, file_config)
def perform_unit_tests(cpp_library, config)
phase("Unit testing")
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)

inform("Library conforms to Arduino library specification") { cpp_library.one_point_five? ? "1.5" : "1.0" }
Expand All @@ -412,6 +413,11 @@ def perform_unit_tests(cpp_library, file_config)
end
end

# having undefined platforms is a config error
platforms.select { |p| config.platform_info[p].nil? }.each do |p|
assure("Platform '#{p}' is defined in configuration files") { false }
end

install_arduino_library_dependencies(config.aux_libraries_for_unittest, "<unittest/libraries>")

platforms.each do |p|
Expand All @@ -433,7 +439,9 @@ def perform_unit_tests(cpp_library, file_config)
puts cpp_library.last_err
next false
end
cpp_library.run_test_file(exe)
cpp_library.run_test_file(Pathname.new(exe.path))
ensure
exe&.unlink
end
end
end
Expand Down Expand Up @@ -462,6 +470,7 @@ def perform_example_compilation_tests(cpp_library, config)
ovr_config = config.from_example(example_path)
platforms = choose_platform_set(ovr_config, "library example", ovr_config.platforms_to_build, cpp_library.library_properties)

# having no platforms defined is probably an error
if platforms.empty?
explain_and_exercise_envvar(VAR_EXPECT_EXAMPLES, "examples compilation", "platforms and architectures") do
puts " Configured platforms: #{ovr_config.platforms_to_build}"
Expand All @@ -471,11 +480,16 @@ def perform_example_compilation_tests(cpp_library, config)
end
end

# having undefined platforms is a config error
platforms.select { |p| ovr_config.platform_info[p].nil? }.each do |p|
assure("Platform '#{p}' is defined in configuration files") { false }
end

install_all_packages(platforms, ovr_config)
install_arduino_library_dependencies(ovr_config.aux_libraries_for_build, "<compile/libraries>")

platforms.each do |p|
board = ovr_config.platform_info[p][:board]
board = ovr_config.platform_info[p][:board] # assured to exist, above
attempt("Compiling #{example_name} for #{board}") do
ret = @backend.compile_sketch(example_path, board)
unless ret
Expand All @@ -491,6 +505,7 @@ def perform_example_compilation_tests(cpp_library, config)

banner
inform("Host OS") { ArduinoCI::Host.os }
inform("Working directory") { Dir.pwd }

# initialize command and config
config = ArduinoCI::CIConfig.default.from_project_library
Expand Down
4 changes: 3 additions & 1 deletion lib/arduino_ci/arduino_backend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ def config_dump

# @return [String] the path to the Arduino libraries directory
def lib_dir
Pathname.new(config_dump["directories"]["user"]) + "libraries"
user_dir_raw = config_dump["directories"]["user"]
user_dir = OS.windows? ? Host.windows_to_pathname(user_dir_raw) : user_dir_raw
Pathname.new(user_dir) + "libraries"
end

# Board manager URLs
Expand Down
2 changes: 1 addition & 1 deletion lib/arduino_ci/arduino_downloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def self.autolocated_executable
# The executable Arduino file in an existing installation, or nil
# @return [Pathname]
def self.existing_executable
self.must_implement(__method__)
Host.which("arduino-cli")
end

# The local file (dir) name of the desired IDE package (zip/tar/etc)
Expand Down
6 changes: 0 additions & 6 deletions lib/arduino_ci/arduino_downloader_linux.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ def self.extracted_file
"arduino-cli"
end

# The executable Arduino file in an existing installation, or nil
# @return [string]
def self.existing_executable
Host.which("arduino-cli")
end

# Make any preparations or run any checks prior to making changes
# @return [string] Error message, or nil if success
def prepare
Expand Down
6 changes: 0 additions & 6 deletions lib/arduino_ci/arduino_downloader_osx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ def self.extracted_file
"arduino-cli"
end

# The executable Arduino file in an existing installation, or nil
# @return [string]
def self.existing_executable
Host.which("arduino-cli")
end

# Make any preparations or run any checks prior to making changes
# @return [string] Error message, or nil if success
def prepare
Expand Down
12 changes: 6 additions & 6 deletions lib/arduino_ci/arduino_downloader_windows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ def package_file
"arduino-cli_#{@desired_version}_Windows_64bit.zip"
end

# The executable Arduino file in an existing installation, or nil
# @return [string]
def self.existing_executable
Host.which("arduino-cli")
end

# The technology that will be used to extract the download
# (for logging purposes)
# @return [string]
Expand All @@ -57,5 +51,11 @@ def self.extracted_file
"arduino-cli.exe"
end

# The executable Arduino file in a forced installation, or nil
# @return [Pathname]
def self.force_installed_executable
Pathname.new(Host.windows_to_pathname(ENV['HOME'])) + self.extracted_file
end

end
end
30 changes: 16 additions & 14 deletions lib/arduino_ci/cpp_library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ class CppLibrary
# @return [ArduinoBackend] The backend support for this library
attr_reader :backend

# @return [Array<Pathname>] The set of artifacts created by this class (note: incomplete!)
attr_reader :artifacts

# @return [Array<Pathname>] The set of directories that should be excluded from compilation
attr_reader :exclude_dirs

Expand All @@ -50,7 +47,6 @@ def initialize(friendly_name, backend)
@name = friendly_name
@backend = backend
@info_cache = nil
@artifacts = []
@last_err = ""
@last_out = ""
@last_msg = ""
Expand Down Expand Up @@ -132,7 +128,10 @@ def info
# @param installed_library_path [String] The library to query
# @return [Array<String>] Example sketch files
def example_sketches
reported_dirs = info["library"]["examples"].map(&Pathname::method(:new))
examples = info["library"]["examples"]
return [] if examples.nil?

reported_dirs = examples.map(&Pathname::method(:new))
reported_dirs.map { |e| e + e.basename.sub_ext(".ino") }.select(&:exist?).sort_by(&:to_s)
end

Expand Down Expand Up @@ -443,7 +442,7 @@ def feature_args(ci_gcc_config)
def warning_args(ci_gcc_config)
return [] if ci_gcc_config[:warnings].nil?

ci_gcc_config[:features].map { |w| "-W#{w}" }
ci_gcc_config[:warnings].map { |w| "-W#{w}" }
end

# GCC command line arguments for defines (e.g. -Dhave_something)
Expand Down Expand Up @@ -488,13 +487,13 @@ def test_args(aux_libraries, ci_gcc_config)
# @param test_file [Pathname] The path to the file containing the unit tests
# @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
# @param ci_gcc_config [Hash] The GCC config object
# @return [Pathname] path to the compiled test executable
# @return [Tempfile] the compiled test executable
def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_gcc_config)
base = test_file.basename
executable = Pathname.new("unittest_#{base}.bin").expand_path
File.delete(executable) if File.exist?(executable)
executable = Tempfile.new("unittest_#{base}.bin")
executable.close
arg_sets = []
arg_sets << ["-std=c++0x", "-o", executable.to_s, "-DARDUINO=100"]
arg_sets << ["-std=c++0x", "-o", executable.path, "-DARDUINO=100"]
if libasan?(gcc_binary)
arg_sets << [ # Stuff to help with dynamic memory mishandling
"-g", "-O1",
Expand All @@ -511,17 +510,20 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g
arg_sets << cpp_files_libraries(full_dependencies).map(&:to_s)
arg_sets << [test_file.to_s]
args = arg_sets.flatten(1)
return nil unless run_gcc(gcc_binary, *args)
unless run_gcc(gcc_binary, *args)
executable.unlink
return nil
end

artifacts << executable
executable
end

# print any found stack dumps
# @param executable [Pathname] the path to the test file
# @param executable [Tempfile] the path to the test file
def print_stack_dump(executable)
path = Pathname.new(executable.path)
possible_dumpfiles = [
executable.sub_ext("#{executable.extname}.stackdump")
path.sub_ext("#{path.extname}.stackdump")
]
possible_dumpfiles.select(&:exist?).each do |dump|
puts "========== Stack dump from #{dump}:"
Expand Down
9 changes: 5 additions & 4 deletions lib/arduino_ci/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ class Host
# via https://stackoverflow.com/a/5471032/2063546
# which('ruby') #=> /usr/bin/ruby
# @param cmd [String] the command to search for
# @return [String] the full path to the command if it exists
# @return [Pathname] the full path to the command if it exists
def self.which(cmd)
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
ENV['PATH'].split(File::PATH_SEPARATOR).each do |string_path|
path = OS.windows? ? windows_to_pathname(string_path) : Pathname.new(string_path)
exts.each do |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
exe = path.join("#{cmd}#{ext}")
return exe if exe.executable? && !exe.directory?
end
end
nil
Expand Down
Loading