Skip to content
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