diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f521c27..f58dd6fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - `ArduinoCmd` installs boards - `ArduinoCmd` installs libraries - `ArduinoCmd` selects boards (compiler preference) +- `ArduinoCmd` verifies sketches ### Changed - `DisplayManger.with_display` doesn't `disable` if the display was enabled prior to starting the block diff --git a/SampleProjects/DoSomething/Gemfile b/SampleProjects/DoSomething/Gemfile new file mode 100644 index 00000000..b2b3b1fd --- /dev/null +++ b/SampleProjects/DoSomething/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gem 'arduino_ci', path: '../../' diff --git a/SampleProjects/DoSomething/Gemfile.lock b/SampleProjects/DoSomething/Gemfile.lock new file mode 100644 index 00000000..39057fe5 --- /dev/null +++ b/SampleProjects/DoSomething/Gemfile.lock @@ -0,0 +1,17 @@ +PATH + remote: /Users/ikatz/Development/non-wayfair/arduino_ci + specs: + arduino_ci (0.0.1) + +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + ruby + +DEPENDENCIES + arduino_ci! + +BUNDLED WITH + 1.16.0 diff --git a/SampleProjects/DoSomething/LICENSE b/SampleProjects/DoSomething/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/SampleProjects/DoSomething/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/SampleProjects/DoSomething/README.md b/SampleProjects/DoSomething/README.md new file mode 100644 index 00000000..3012fec2 --- /dev/null +++ b/SampleProjects/DoSomething/README.md @@ -0,0 +1,23 @@ +# Arduino CI and Unit Tests HOWTO [![Build Status](https://travis-ci.org/ifreecarve/arduino-ci-unit-tests.svg?branch=master)](https://travis-ci.org/ifreecarve/arduino-ci-unit-tests) + +This project is a template for a CI-enabled (and unit testable) Arduino project of your own. + + +### Features + +* Travis CI +* Unit tests +* Development workflow matches CI workflow - the `platformio ci` line at the bottom of `.travis.yml` can be run on your local terminal (just append the name of the file you want to compile). + +# Where The Magic Happens + +Here is the minimal set of files that you will need to adapt to your own project: + +* `.travis.yml` - You'll need to fill in the `env` section with files relevant to your project, and list out all the `--board`s under the `script` section. +* `platformio.ini` - You'll need to add information for any architectures you plan to support. +* `library.properties` - You'll need to update the `architectures` and `includes` lines as appropriate + + +# Credits + +This Arduino example was created in January 2018 by Ian Katz . diff --git a/SampleProjects/DoSomething/do-something.cpp b/SampleProjects/DoSomething/do-something.cpp new file mode 100644 index 00000000..60f08106 --- /dev/null +++ b/SampleProjects/DoSomething/do-something.cpp @@ -0,0 +1,5 @@ +#include +int doSomething(void) { + millis(); // this line is only here to test that we're able to refer to the builtins + return 4; +}; diff --git a/SampleProjects/DoSomething/do-something.h b/SampleProjects/DoSomething/do-something.h new file mode 100644 index 00000000..f39ecdff --- /dev/null +++ b/SampleProjects/DoSomething/do-something.h @@ -0,0 +1,2 @@ +#include +int doSomething(void); diff --git a/SampleProjects/DoSomething/examples/DoSomethingExample/DoSomethingExample.ino b/SampleProjects/DoSomething/examples/DoSomethingExample/DoSomethingExample.ino new file mode 100644 index 00000000..b00feb16 --- /dev/null +++ b/SampleProjects/DoSomething/examples/DoSomethingExample/DoSomethingExample.ino @@ -0,0 +1,9 @@ +#include +// if it seems bare, that's because it's only meant to +// demonstrate compilation -- that references work +void setup() { +} + +void loop() { + doSomething(); +} diff --git a/SampleProjects/DoSomething/library.properties b/SampleProjects/DoSomething/library.properties new file mode 100644 index 00000000..cfa484dc --- /dev/null +++ b/SampleProjects/DoSomething/library.properties @@ -0,0 +1,10 @@ +name=arduino-ci-unit-tests +version=0.1.0 +author=Ian Katz +maintainer=Ian Katz +sentence=Arduino CI unit test example +paragraph=A skeleton library demonstrating CI and unit tests +category=Other +url=https://github.com/ifreecarve/arduino-ci-unit-tests +architectures=avr +includes=do-something.h diff --git a/SampleProjects/README.md b/SampleProjects/README.md new file mode 100644 index 00000000..892f9af4 --- /dev/null +++ b/SampleProjects/README.md @@ -0,0 +1,4 @@ +Arduino Sample Projects +======================= + +This directory contains example projects that are meant to be built with this gem. diff --git a/exe/ci_system_check.rb b/exe/ci_system_check.rb index 84b565c9..b738f3c4 100755 --- a/exe/ci_system_check.rb +++ b/exe/ci_system_check.rb @@ -33,12 +33,32 @@ got_problem = true unless arduino_cmd.install_library("USBHost") puts "checking that library is indexed" got_problem = true unless arduino_cmd.library_is_indexed + +my_board = "arduino:sam:arduino_due_x" + +puts "use board! (install board)" +got_problem = true unless arduino_cmd.use_board!(my_board) +puts "assert that board has been installed" +got_problem = true unless arduino_cmd.board_installed?(my_board) + puts "setting compiler warning level" got_problem = true unless arduino_cmd.set_pref("compiler.warning_level", "all") -puts "use board! (install board)" -got_problem = true unless arduino_cmd.use_board!("arduino:samd:zero") -puts "verify that board has been installed" -got_problem = true unless arduino_cmd.board_installed?("arduino:samd:zero") + +simple_sketch = File.join(File.dirname(File.dirname(__FILE__)), "spec", "FakeSketch", "FakeSketch.ino") + +puts "verify a simple sketch" +got_problem = true unless arduino_cmd.verify_sketch(simple_sketch) + +library_path = File.join(File.dirname(File.dirname(__FILE__)), "SampleProjects", "DoSomething") +puts "verify the examples of a library (#{library_path})..." +puts " - Install the library" +installed_library_path = arduino_cmd.install_local_library(library_path) +got_problem = true if installed_library_path.nil? +puts " - Iterate over the examples" +arduino_cmd.each_library_example(installed_library_path) do |example_path| + puts "Iterating #{example_path}" + got_problem = true unless arduino_cmd.verify_sketch(example_path) +end abort if got_problem exit(0) diff --git a/lib/arduino_ci/arduino_cmd.rb b/lib/arduino_ci/arduino_cmd.rb index aee8b6fe..cf88347f 100644 --- a/lib/arduino_ci/arduino_cmd.rb +++ b/lib/arduino_ci/arduino_cmd.rb @@ -1,3 +1,4 @@ +require 'fileutils' require 'arduino_ci/display_manager' require 'arduino_ci/arduino_installation' @@ -22,7 +23,6 @@ def autolocate! end attr_accessor :installation - attr_reader :prefs_cache attr_reader :prefs_response_time attr_reader :library_is_indexed @@ -31,26 +31,47 @@ def initialize(installation) @display_mgr = DisplayManager::instance @installation = installation @prefs_response_time = nil - @prefs_cache = prefs + @prefs_cache = nil @library_is_indexed = false end + 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) + acc[parts[0]] = parts[1] + acc + end + ret + end + # fetch preferences to a hash - def prefs + def _prefs resp = nil - @display_mgr.with_display do + 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 return nil unless resp[:success] - lines = resp[:out].split("\n").select { |l| l.include? "=" } - ret = lines.each_with_object({}) do |e, acc| - parts = e.split("=", 2) - acc[parts[0]] = parts[1] - acc - end - ret + _parse_pref_string(resp[:out]) + end + + def prefs + @prefs_cache = _prefs if @prefs_cache.nil? + @prefs_cache.clone + end + + # get a preference key + def get_pref(key) + data = @prefs_cache.nil? ? prefs : @prefs_cache + data[key] end # set a preference key/value pair @@ -65,13 +86,22 @@ def set_pref(key, value) # run the arduino command def run(*args, **kwargs) - full_args = [@installation.cmd_path] + args - @display_mgr.run(*full_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 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 + + prefs if @prefs_response_time.nil? x3 = @prefs_response_time * 3 Timeout.timeout(x3) do result = run_and_capture(*args, **kwargs) @@ -130,6 +160,12 @@ def install_library(library_name) 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) + end + # update the library index def update_library_index # install random lib so the arduino IDE grabs a new library index @@ -151,5 +187,55 @@ def use_board!(boardname) use_board(boardname) end + def verify_sketch(path) + ext = File.extname path + unless ext.casecmp(".ino").zero? + puts "Refusing to verify sketch with '#{ext}' extension -- rename it to '.ino'!" + return false + end + unless File.exist? path + puts "Can't verify nonexistent Sketch at '#{path}'!" + return false + end + run("--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) + + # 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 + + # 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 nil + end + + puts "#{uhoh}. It may need to be removed manually." + return nil + end + + # install the library + FileUtils.ln_s(library_path, destination_path) + destination_path + end + + def each_library_example(installed_library_path) + example_path = File.join(installed_library_path, "examples") + examples = Pathname.new(example_path).children.select(&:directory?).map(&:to_path).map(&File.method(:basename)) + examples.each do |e| + proj_file = File.join(example_path, e, "#{e}.ino") + puts "Considering #{proj_file}" + yield proj_file if File.exist?(proj_file) + end + end end end diff --git a/lib/arduino_ci/arduino_installation.rb b/lib/arduino_ci/arduino_installation.rb index 10ef76fd..a27b86f0 100644 --- a/lib/arduino_ci/arduino_installation.rb +++ b/lib/arduino_ci/arduino_installation.rb @@ -1,63 +1,110 @@ require "arduino_ci/host" +DESIRED_ARDUINO_IDE_VERSION = "1.8.5".freeze +USE_BUILDER = false + module ArduinoCI # Manage the OS-specific install location of Arduino class ArduinoInstallation - attr_accessor :cmd_path + attr_accessor :base_cmd attr_accessor :lib_dir + attr_accessor :requires_x class << self def force_install_location File.join(ENV['HOME'], 'arduino_ci_ide') end + def from_forced_install + ret = new + builder = File.join(force_install_location, "arduino-builder") + if USE_BUILDER && File.exist?(builder) + ret.base_cmd = [builder] + ret.requires_x = false + else + ret.base_cmd = [File.join(force_install_location, "arduino")] + ret.requires_x = true + end + ret.lib_dir = File.join(force_install_location, "libraries") + # TODO: "libraries" is what's in the adafruit install.sh script + ret + end + # attempt to find a workable Arduino executable across platforms def autolocate - ret = new + osx_root = "/Applications/Arduino.app" + old_way = false + if File.exist? osx_root + ret = new + osx_place = "#{osx_root}/Contents/MacOS" - osx_place = "/Applications/Arduino.app/Contents/MacOS" - if File.exist? osx_place - ret.cmd_path = File.join(osx_place, "Arduino") - ret.lib_dir = File.join(osx_place, "Libraries") + if old_way + ret.base_cmd = [File.join(osx_place, "Arduino")] + else + jvm_runtime = `/usr/libexec/java_home` + ret.base_cmd = [ + "java", + "-cp", "#{osx_root}/Contents/Java/*", + "-DAPP_DIR=#{osx_root}/Contents/Java", + "-Djava.ext.dirs=$JVM_RUNTIME/Contents/Home/lib/ext/:#{jvm_runtime}/Contents/Home/jre/lib/ext/", + "-Dfile.encoding=UTF-8", + "-Dapple.awt.UIElement=true", + "-Xms128M", + "-Xmx512M", + "processing.app.Base", + ] + end + ret.lib_dir = File.join(osx_place, "Libraries") # TODO: probably wrong + ret.requires_x = false return ret end - posix_place = Host.which("arduino") - unless posix_place.nil? - ret.cmd_path = posix_place + # AAARRRRGGGGHHH + # Even though arduino-builder is an awesome CLI for Arduino, + # ALL THE OPTIONS ARE DIFFERENT (single vs double dash for flags) + # USELESS FOR THE TIME BEING + posix_place = Host.which("arduino-builder") + if USE_BUILDER && !posix_place.nil? + ret = new + ret.base_cmd = [posix_place] ret.lib_dir = File.join(ENV['HOME'], "Sketchbook") # assume linux + ret.requires_x = false # https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library return ret end - if File.exist? force_install_location - ret.cmd_path = File.join(force_install_location, "arduino") - ret.lib_dir = File.join(force_install_location, "libraries") - # TODO: "libraries" is what's in the adafruit install.sh script + posix_place = Host.which("arduino") + unless posix_place.nil? + ret = new + ret.base_cmd = [posix_place] + ret.lib_dir = File.join(ENV['HOME'], "Sketchbook") # assume linux + ret.requires_x = true + # https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/how-to-install-a-library return ret end - ret + return from_forced_install if File.exist? force_install_location + + new end # Attempt to find a workable Arduino executable across platforms, and install it if we don't def autolocate! candidate = autolocate - return candidate unless candidate.cmd_path.nil? - # force the install + return candidate unless candidate.base_cmd.nil? - if force_install - candidate.cmd_path = File.join(force_install_location, "arduino") - candidate.lib_dir = File.join(force_install_location, "libraries") - end + # force the install + candidate = from_forced_install if force_install candidate end def force_install - system("wget", "https://downloads.arduino.cc/arduino-1.6.5-linux64.tar.xz") - system("tar", "xf", "arduino-1.6.5-linux64.tar.xz") - system("mv", "arduino-1.6.5", force_install_location) + pkgname = "arduino-#{DESIRED_ARDUINO_IDE_VERSION}" + tarfile = "#{pkgname}-linux64.tar.xz" + system("wget", "https://downloads.arduino.cc/#{tarfile}") + system("tar", "xf", tarfile) + system("mv", pkgname, force_install_location) end end diff --git a/lib/arduino_ci/display_manager.rb b/lib/arduino_ci/display_manager.rb index 34e15e92..372af9ae 100644 --- a/lib/arduino_ci/display_manager.rb +++ b/lib/arduino_ci/display_manager.rb @@ -148,10 +148,7 @@ def run(*args, **kwargs) env_vars.merge!(args[0]) if has_env actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args full_cmd = env_vars.empty? ? actual_args : [env_vars] + actual_args - shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ") - puts " $ #{shell_vars} #{actual_args.join(' ')}" - ret = system(*full_cmd, **kwargs) - puts "#{actual_args[0]} has completed" + ret = Host.run(*full_cmd, **kwargs) end ret end diff --git a/lib/arduino_ci/host.rb b/lib/arduino_ci/host.rb index aeed2c2f..8623bfc1 100644 --- a/lib/arduino_ci/host.rb +++ b/lib/arduino_ci/host.rb @@ -15,5 +15,21 @@ def self.which(cmd) end nil end + + # run a command in a display + def self.run(*args, **kwargs) + # do some work to extract & merge environment variables if they exist + has_env = !args.empty? && args[0].class == Hash + env_vars = has_env ? args[0] : {} + actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args + full_cmd = env_vars.empty? ? actual_args : [env_vars] + actual_args + shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ") + puts " $ #{shell_vars} #{actual_args.join(' ')}" + ret = system(*full_cmd, **kwargs) + status = ret ? "succeeded" : "failed" + puts "#{actual_args[0]} has #{status}" + ret + end + end end diff --git a/spec/BadSketch/BadSketch.ino b/spec/BadSketch/BadSketch.ino new file mode 100644 index 00000000..2f590816 --- /dev/null +++ b/spec/BadSketch/BadSketch.ino @@ -0,0 +1 @@ +Oh god how did this get in here I am not good with computer diff --git a/spec/FakeSketch/FakeSketch.ino b/spec/FakeSketch/FakeSketch.ino new file mode 100644 index 00000000..869b9ab3 --- /dev/null +++ b/spec/FakeSketch/FakeSketch.ino @@ -0,0 +1,2 @@ +void setup(void) { 0; } +void loop(void) { 0; } diff --git a/spec/arduino_cmd_spec.rb b/spec/arduino_cmd_spec.rb index 07670fc0..f35e964b 100644 --- a/spec/arduino_cmd_spec.rb +++ b/spec/arduino_cmd_spec.rb @@ -1,5 +1,10 @@ require "spec_helper" +def get_sketch(dir, file) + File.join(File.dirname(__FILE__), dir, file) +end + + RSpec.describe ArduinoCI::ArduinoCmd do context "autolocate" do it "Finds the Arduino executable" do @@ -10,8 +15,8 @@ context "autolocate!" do it "Finds the Arduino executable" do arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! - expect(arduino_cmd.installation.cmd_path).not_to be nil - expect(arduino_cmd.prefs_cache.class).to be Hash + expect(arduino_cmd.installation.base_cmd).not_to be nil + expect(arduino_cmd.prefs.class).to be Hash expect(arduino_cmd.prefs_response_time).not_to be nil end end @@ -37,9 +42,36 @@ ArduinoCI::DisplayManager::instance.enable it "Sets key to what it was before" do - upload_verify = arduino_cmd.prefs_cache["upload.verify"] + upload_verify = arduino_cmd.get_pref("upload.verify") result = arduino_cmd.set_pref("upload.verify", upload_verify) expect(result).to be true end end + + context "verify_sketch" do + arduino_cmd = ArduinoCI::ArduinoCmd.autolocate! + ArduinoCI::DisplayManager::instance.enable + + sketch_path_ino = get_sketch("FakeSketch", "FakeSketch.ino") + sketch_path_pde = get_sketch("FakeSketch", "FakeSketch.pde") + sketch_path_mia = get_sketch("NO_FILE_HERE", "foo.ino") + sketch_path_bad = get_sketch("BadSketch", "BadSketch.ino") + + it "Rejects a PDE sketch at #{sketch_path_pde}" do + expect(arduino_cmd.verify_sketch(sketch_path_pde)).to be false + end + + it "Fails a missing sketch at #{sketch_path_mia}" do + expect(arduino_cmd.verify_sketch(sketch_path_mia)).to be false + end + + it "Fails a bad sketch at #{sketch_path_bad}" do + expect(arduino_cmd.verify_sketch(sketch_path_bad)).to be false + end + + it "Passes a simple INO sketch at #{sketch_path_ino}" do + expect(arduino_cmd.verify_sketch(sketch_path_ino)).to be true + end + + end end diff --git a/spec/arduino_installation_spec.rb b/spec/arduino_installation_spec.rb index c0a81278..6915a027 100644 --- a/spec/arduino_installation_spec.rb +++ b/spec/arduino_installation_spec.rb @@ -16,7 +16,7 @@ context "autolocate!" do it "doesn't fail" do installation = ArduinoCI::ArduinoInstallation.autolocate! - expect(installation.cmd_path).not_to be nil + expect(installation.base_cmd).not_to be nil expect(installation.lib_dir).not_to be nil end end