Skip to content

Allow non-GUI binaries #5

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

Merged
merged 11 commits into from
Jan 23, 2018
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ vendor

# rspec failure tracking
.rspec_status

# C++ stuff
arduino_ci_built.bin
1 change: 1 addition & 0 deletions SampleProjects/DoSomething/do-something.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <Arduino.h>
#include <do-something.h>
int doSomething(void) {
millis(); // this line is only here to test that we're able to refer to the builtins
Expand Down
2 changes: 2 additions & 0 deletions arduino_ci.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "os", "~> 1.0"

spec.add_development_dependency "bundler", "~> 1.15"
spec.add_development_dependency "rspec", "~> 3.0"
spec.add_development_dependency 'rubocop', '~>0.49.0'
Expand Down
30 changes: 30 additions & 0 deletions cpp/Arduino.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Mock Arduino.h library.
Where possible, variable names from the Arduino library are used to avoid conflicts
*/


#ifndef ARDUINO_CI_ARDUINO

#include "math.h"
#define ARDUINO_CI_ARDUINO

struct unit_test_state {
unsigned long micros;
};

struct unit_test_state godmode {
0, // micros
};

unsigned long millis() {
return godmode.micros / 1000;
}

unsigned long micros() {
return godmode.micros;
}

#endif
41 changes: 41 additions & 0 deletions cpp/math.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//abs
long abs(long x) { return x > 0 ? x : -x; }
double fabs(double x) { return x > 0 ? x : -x; }

//max
long max(long a, long b) { return a > b ? a : b; }
double fmax(double a, double b) { return a > b ? a : b; }

//min
long min(long a, long b) { return a < b ? a : b; }
double fmin(double a, double b) { return a < b ? a : b; }

//constrain
long constrain(long x, long a, long b) { return max(a, min(b, x)); }
double constrain(double x, double a, double b) { return max(a, min(b, x)); }

//map
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

double map(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

//sq
long sq(long x) { return x * x; }
double sq(double x) { return x * x; }

// ??? too lazy to sort these now
//pow
//sqrt

// http://www.ganssle.com/approx.htm
// http://www.ganssle.com/approx/sincos.cpp
//cos
//sin
//tan

10 changes: 6 additions & 4 deletions exe/ci_system_check.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
require 'arduino_ci'

puts "Enabling display with display manager"
ArduinoCI::DisplayManager::instance.enable

puts "Autlocating Arduino command"
arduino_cmd = ArduinoCI::ArduinoCmd.autolocate!
arduino_cmd = ArduinoCI::ArduinoInstallation.autolocate!

board_tests = {
"arduino:avr:uno" => true,
Expand Down Expand Up @@ -50,6 +47,11 @@
got_problem = true unless arduino_cmd.verify_sketch(simple_sketch)

library_path = File.join(File.dirname(File.dirname(__FILE__)), "SampleProjects", "DoSomething")

puts "verify a library with arduino mocks"
cpp_library = ArduinoCI::CppLibrary.new(library_path)
got_problem = true unless cpp_library.build(arduino_cmd)

puts "verify the examples of a library (#{library_path})..."
puts " - Install the library"
installed_library_path = arduino_cmd.install_local_library(library_path)
Expand Down
3 changes: 2 additions & 1 deletion lib/arduino_ci.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "arduino_ci/version"
require "arduino_ci/arduino_cmd"
require "arduino_ci/arduino_installation"
require "arduino_ci/cpp_library"

# ArduinoCI contains classes for automated testing of Arduino code on the command line
# @author Ian Katz <[email protected]>
Expand Down
144 changes: 68 additions & 76 deletions lib/arduino_ci/arduino_cmd.rb
Original file line number Diff line number Diff line change
@@ -1,41 +1,43 @@
require 'fileutils'
require 'arduino_ci/display_manager'
require 'arduino_ci/arduino_installation'

module ArduinoCI

# Wrap the Arduino executable. This requires, in some cases, a faked display.
class ArduinoCmd

class << self
protected :new

# @return [ArduinoCmd] A command object with a best guess (or nil) for the installation
def autolocate
new(ArduinoInstallation.autolocate)
end

# @return [ArduinoCmd] A command object, installing Arduino if necessary
def autolocate!
new(ArduinoInstallation.autolocate!)
end

# Enable a shortcut syntax for command line flags
# @param name [String] What the flag will be called (prefixed with 'flag_')
# @return [void]
# @macro [attach] flag
# @!attribute [r] flag_$1
# @return String $2 the text of the command line flag
def self.flag(name, text = nil)
text = "(flag #{name} not defined)" if text.nil?
self.class_eval("def flag_#{name};\"#{text}\";end")
end

attr_accessor :installation
attr_reader :prefs_response_time
attr_accessor :base_cmd
attr_accessor :gcc_cmd

attr_reader :library_is_indexed

# @param installation [ArduinoInstallation] the location of the Arduino program installation
def initialize(installation)
@display_mgr = DisplayManager::instance
@installation = installation
@prefs_response_time = nil
# set the command line flags (undefined for now).
# These vary between gui/cli
flag :get_pref
flag :set_pref
flag :save_prefs
flag :use_board
flag :install_boards
flag :install_library
flag :verify

def initialize
@prefs_cache = nil
@library_is_indexed = false
end

def _parse_pref_string(arduino_output)
def parse_pref_string(arduino_output)
lines = arduino_output.split("\n").select { |l| l.include? "=" }
ret = lines.each_with_object({}) do |e, acc|
parts = e.split("=", 2)
Expand All @@ -45,26 +47,21 @@ def _parse_pref_string(arduino_output)
ret
end

# fetch preferences to a hash
def _prefs
resp = nil
if @installation.requires_x
@display_mgr.with_display do
start = Time.now
resp = run_and_capture("--get-pref")
@prefs_response_time = Time.now - start
end
else
start = Time.now
resp = run_and_capture("--get-pref")
@prefs_response_time = Time.now - start
end
def _lib_dir
"<lib dir not defined>"
end

# fetch preferences to a string
def _prefs_raw
resp = run_and_capture(flag_get_pref)
return nil unless resp[:success]
_parse_pref_string(resp[:out])
resp[:out]
end

def prefs
@prefs_cache = _prefs if @prefs_cache.nil?
prefs_raw = _prefs_raw if @prefs_cache.nil?
return nil if prefs_raw.nil?
@prefs_cache = parse_pref_string(prefs_raw)
@prefs_cache.clone
end

Expand All @@ -74,42 +71,38 @@ def get_pref(key)
data[key]
end

# set a preference key/value pair
# underlying preference-setter.
# @return [bool] whether the command succeeded
def _set_pref(key, value)
run_and_capture(flag_set_pref, "#{key}=#{value}", flag_save_prefs)[:success]
end

# set a preference key/value pair, and update the cache.
# @param key [String] the preference key
# @param value [String] the preference value
# @return [bool] whether the command succeeded
def set_pref(key, value)
success = run_with_gui_guess(" about preferences", "--pref", "#{key}=#{value}", "--save-prefs")
success = _set_pref(key, value)
@prefs_cache[key] = value if success
success
end

# run the arduino command
def run(*args, **kwargs)
full_args = @installation.base_cmd + args
if @installation.requires_x
@display_mgr.run(*full_args, **kwargs)
else
Host.run(*full_args, **kwargs)
end
def _run(*args, **kwargs)
raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
end

def run_with_gui_guess(message, *args, **kwargs)
# On Travis CI, we get an error message in the GUI instead of on STDERR
# so, assume that if we don't get a rapid reply that things are not installed

# if we don't need X, we can skip this whole thing
return run_and_capture(*args, **kwargs)[:success] unless @installation.requires_x
# build and run the arduino command
def run(*args, **kwargs)
# TODO: detect env!!
full_args = @base_cmd + args
_run(*full_args, **kwargs)
end

prefs if @prefs_response_time.nil?
x3 = @prefs_response_time * 3
Timeout.timeout(x3) do
result = run_and_capture(*args, **kwargs)
result[:success]
end
rescue Timeout::Error
puts "No response in #{x3} seconds. Assuming graphical modal error message#{message}."
false
def run_gcc(*args, **kwargs)
# TODO: detect env!!
full_args = @gcc_cmd + args
_run(*full_args, **kwargs)
end

# run a command and capture its output
Expand Down Expand Up @@ -140,30 +133,29 @@ def run_wrap(*args, **kwargs)
# we do this by just selecting a board.
# the arduino binary will error if unrecognized and do a successful no-op if it's installed
def board_installed?(boardname)
run_with_gui_guess(" about board not installed", "--board", boardname)
run_and_capture(flag_use_board, boardname)[:success]
end

# install a board by name
# @param name [String] the board name
# @return [bool] whether the command succeeded
def install_board(boardname)
# TODO: find out why IO.pipe fails but File::NULL succeeds :(
run_and_capture("--install-boards", boardname, out: File::NULL)[:success]
run_and_capture(flag_install_boards, boardname, out: File::NULL)[:success]
end

# install a library by name
# @param name [String] the library name
# @return [bool] whether the command succeeded
def install_library(library_name)
result = run_and_capture("--install-library", library_name)
result = run_and_capture(flag_install_library, library_name)
@library_is_indexed = true if result[:success]
result[:success]
end

# generate the (very likely) path of a library given its name
def library_path(library_name)
sketchbook = get_pref("sketchbook.path")
File.join(sketchbook, library_name)
File.join(_lib_dir, library_name)
end

# update the library index
Expand All @@ -175,7 +167,7 @@ def update_library_index

# use a particular board for compilation
def use_board(boardname)
run_with_gui_guess(" about board not installed", "--board", boardname, "--save-prefs")
run_and_capture(flag_use_board, boardname, flag_save_prefs)[:success]
end

# use a particular board for compilation, installing it if necessary
Expand All @@ -197,25 +189,25 @@ def verify_sketch(path)
puts "Can't verify nonexistent Sketch at '#{path}'!"
return false
end
run("--verify", path, err: :out)
run(flag_verify, path, err: :out)
end

# ensure that the given library is installed, or symlinked as appropriate
# return the path of the prepared library, or nil
def install_local_library(library_path)
library_name = File.basename(library_path)
destination_path = File.join(@installation.lib_dir, library_name)
def install_local_library(path)
library_name = File.basename(path)
destination_path = library_path(library_name)

# things get weird if the sketchbook contains the library.
# check that first
if File.exist? destination_path
uhoh = "There is already a library '#{library_name}' in the library directory"
return destination_path if destination_path == library_path
return destination_path if destination_path == path

# maybe it's a symlink? that would be OK
if File.symlink?(destination_path)
return destination_path if File.readlink(destination_path) == library_path
puts "#{uhoh} and it's not symlinked to #{library_path}"
return destination_path if File.readlink(destination_path) == path
puts "#{uhoh} and it's not symlinked to #{path}"
return nil
end

Expand All @@ -224,7 +216,7 @@ def install_local_library(library_path)
end

# install the library
FileUtils.ln_s(library_path, destination_path)
FileUtils.ln_s(path, destination_path)
destination_path
end

Expand Down
Loading