diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..be26b81f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +vendor + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..8c18f1ab --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..f89ea5b5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,80 @@ +AllCops: + Exclude: + - '*.gemspec' + - 'spec/*.rb' + - '**/generated_parser/*' + - './vendor/**/*' + +# inherit_from: .rubocop_todo.yml + +# Extra lines for readability +Layout/EmptyLinesAroundClassBody: + Enabled: false + +Layout/EmptyLinesAroundMethodBody: + Enabled: false + +Layout/EmptyLinesAroundModuleBody: + Enabled: false + +# Configuration parameters: AllowForAlignment. +Layout/ExtraSpacing: + Enabled: false + +Metrics/LineLength: + Description: Limit lines to 80 characters. + StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits + Enabled: true + Max: 130 + +# Configuration parameters: CountComments. +Metrics/ClassLength: + Enabled: false + Max: 400 + +Metrics/AbcSize: + Enabled: false + Max: 50 + +Metrics/MethodLength: + Enabled: false + Max: 50 + +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 7 + +Style/BlockComments: + Enabled: false + +Style/ColonMethodCall: + Enabled: false + +# if you find "a == 3" readable and "3 == a" 'unreadable', do not contribute to this project. +Style/YodaCondition: + Enabled: false + +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/FormatString: + Enabled: false + +# Offense count: 1 +Metrics/CyclomaticComplexity: + Enabled: false + Max: 11 + +# Offense count: 1 +Metrics/PerceivedComplexity: + Enabled: false + Max: 14 + +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# because apostrophes +Style/StringLiterals: + Enabled: false + +Style/TrailingCommaInLiteral: + Enabled: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..08dae787 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +sudo: false +language: ruby + +rvm: +# - "2.0.0" +# - "2.1.0" +# - "2.2.0" +# - rbx + - "2.5.0" + +before_install: gem install bundler -v 1.15.4 +script: + - bundle exec rubocop --version + - bundle exec rubocop -D . + - bundle exec rspec diff --git a/.yardopts b/.yardopts new file mode 100644 index 00000000..7b700680 --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +lib/arduino_ci.rb lib/**/*.rb diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..d6bbd532 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + + +## [0.0.1] - 2018-01-10 +### Added +- Skeleton for gem with working unit tests + + +[Unreleased]: https://github.com/ifreecarve/arduino_ci/compare/v0.0.1...HEAD +[0.0.1]: https://github.com/ifreecarve/arduino_ci/compare/v0.0.0...v0.0.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..dde6db80 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# Contributing to the ArduinoCI gem + +ArduinoCI uses a very standard GitHub workflow. + +1. Fork the repository on github +2. Make your desired changes +3. Push to your personal fork +4. Open a pull request + +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): + + * `rubocop -D` - code style tests + * `rspec` - functional tests + +Be prepared to write tests to accompany any code you would like to see merged. + + +## Packaging the Gem + +* Merge pull request with new features +* Bump the version in lib/arduino_ci/version.rb and change it in README.md (since rubydoc.info doesn't always redirect to the latest version) +* Update the sections of `CHANGELOG.md` +* `git add README.md CHANGELOG.md lib/arduino_ci/version.rb` +* `git commit -m "vVERSION bump"` +* `git tag -a vVERSION -m "Released version VERSION"` +* `gem build arduino_ci.gemspec` +* `gem push arduino_ci-VERSION.gem` +* `git push upstream` +* `git push upstream --tags` diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..c88b26e7 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in arduino_ci.gemspec +gemspec diff --git a/README.md b/README.md index e925430f..238a9c8b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ -# arduino_ci -A Ruby gem for executing Continuous Integration (CI) tests on an Arduino library +[![Gem Version](https://badge.fury.io/rb/arduino_ci.svg)](https://rubygems.org/gems/arduino_ci) +[![Build Status](https://travis-ci.org/ifreecarve/arduino_ci.svg)](https://travis-ci.org/ifreecarve/arduino_ci) +[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/arduino_ci/) + +# ArduinoCI Ruby gem (`arduino_ci`) + +[Arduino CI](https://github.com/ifreecarve/arduino_ci) is a Ruby gem for executing Continuous Integration (CI) tests on an Arduino library -- both locally and as part of a service like Travis CI. + + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'arduino_ci' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install arduino_ci + + +## Usage + +TODO: Write usage instructions here, based on other TODO of writing the actual gem. + + +## Author + +This gem was written by Ian Katz (ifreecarve@gmail.com) in 2018. It's released under the Apache 2.0 license. + + +## See Also + +* [Contributing](CONTRIBUTING.md) diff --git a/arduino_ci.gemspec b/arduino_ci.gemspec new file mode 100644 index 00000000..fdb41385 --- /dev/null +++ b/arduino_ci.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path("../lib", __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "arduino_ci/version" + +Gem::Specification.new do |spec| + spec.name = "arduino_ci" + spec.version = ArduinoCI::VERSION + spec.licenses = ['Apache 2.0'] + spec.authors = ["Ian Katz"] + spec.email = ["ifreecarve@gmail.com"] + + spec.summary = "Tools for building and unit testing Arduino libraries" + spec.description = spec.description + spec.homepage = "http://github.com/ifreecarve/arduino_ci" + + spec.files = ['README.md', '.yardopts'] + Dir['lib/**/*.*'].reject { |f| f.match(%r{^(test|spec|features)/}) } + + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.15" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency 'rubocop', '~> 0', '>= 0.46.0' + spec.add_development_dependency 'yard', '~>0.8', '>= 0.8' +end diff --git a/bin/console b/bin/console new file mode 100755 index 00000000..d6e8b5fa --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "arduino_ci" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/arduino_ci.rb b/lib/arduino_ci.rb new file mode 100644 index 00000000..7edf9596 --- /dev/null +++ b/lib/arduino_ci.rb @@ -0,0 +1,124 @@ +require "arduino_ci/version" + +require 'singleton' + +# Cross-platform way of finding an executable in the $PATH. +# via https://stackoverflow.com/a/5471032/2063546 +# which('ruby') #=> /usr/bin/ruby +def which(cmd) + exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] + ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| + exts.each do |ext| + exe = File.join(path, "#{cmd}#{ext}") + return exe if File.executable?(exe) && !File.directory?(exe) + end + end + nil +end + +# ArduinoCI contains classes for automated testing of Arduino code on the command line +# @author Ian Katz +module ArduinoCI + + # Wrap the Arduino executable. This requires, in some cases, a faked display. + class ArduinoCmd + + # create as many ArduinoCmds as you like, but we need one and only one display manager + class DisplayMgr + include Singleton + attr_reader :enabled + + def initialize + @existing = existing_display? + @enabled = false + @pid = nil + end + + # attempt to determine if the machine is running a graphical display (i.e. not Travis) + def existing_display? + return true if RUBY_PLATFORM.include? "darwin" + return true if ENV["DISPLAY"].nil? + return true if ENV["DISPLAY"].include? ":" + false + end + + # enable a virtual display + def enable + return @enabled = true if @existing # silent no-op if built in display + return unless @pid.nil? + + @enabled = true + @pid = fork do + puts "Forking Xvfb" + system("Xvfb", ":1", "-ac", "-screen", "0", "1280x1024x16") + puts "Xvfb unexpectedly quit!" + end + sleep(3) # TODO: test a connection to the X server? + end + + # disable the virtual display + def disable + return @enabled = false if @existing # silent no-op if built in display + return if @pid.nil? + + begin + Process.kill 9, @pid + ensure + Process.wait @pid + @pid = nil + end + puts "Xvfb killed" + end + + # Enable a virtual display for the duration of the given block + def with_display + enable + begin + yield environment + ensure + disable + end + end + + def environment + return nil unless @existing || @enabled + return {} if @existing + { DISPLAY => ":1.0" } + end + + # On finalize, ensure child process is ended + def self.finalize + disable + end + end + + class << self + protected :new + + # attempt to find a workable Arduino executable across platforms + def guess_executable_location + osx_place = "/Applications/Arduino.app/Contents/MacOS/Arduino" + places = { + "arduino" => !which("arduino").nil?, + osx_place => (File.exist? osx_place), + } + places.each { |k, v| return k if v } + nil + end + + def autolocate + ret = new + ret.path = guess_executable_location + ret + end + end + + attr_accessor :path + + def initialize + @display_mgr = DisplayMgr::instance + end + + end + +end diff --git a/lib/arduino_ci/version.rb b/lib/arduino_ci/version.rb new file mode 100644 index 00000000..e16b7477 --- /dev/null +++ b/lib/arduino_ci/version.rb @@ -0,0 +1,3 @@ +module ArduinoCI + VERSION = "0.0.1".freeze +end diff --git a/spec/arduino_ci_spec.rb b/spec/arduino_ci_spec.rb new file mode 100644 index 00000000..0f3d9c57 --- /dev/null +++ b/spec/arduino_ci_spec.rb @@ -0,0 +1,7 @@ +require "spec_helper" + +RSpec.describe ArduinoCI do + it "has a version number" do + expect(ArduinoCI::VERSION).not_to be nil + end +end diff --git a/spec/arduino_exec_spec.rb b/spec/arduino_exec_spec.rb new file mode 100644 index 00000000..19d2147a --- /dev/null +++ b/spec/arduino_exec_spec.rb @@ -0,0 +1,30 @@ +require "spec_helper" + +RSpec.describe ArduinoCI::ArduinoCmd do + it "Finds the Arduino executable" do + arduino_cmd = ArduinoCI::ArduinoCmd.autolocate + # expect(arduino_cmd.path).not_to be nil + end +end + +RSpec.describe ArduinoCI::ArduinoCmd::DisplayMgr do + context "singleton ::instance" do + it "produces an instance" do + expect(ArduinoCI::ArduinoCmd::DisplayMgr::instance).not_to be_nil + end + end + + context "with_display" do + it "Properly enables and disables" do + manager = ArduinoCI::ArduinoCmd::DisplayMgr::instance + expect(manager.enabled).to be false + manager.with_display do |environment| + expect(manager.enabled).to be true + expect(environment.class).to eq(Hash) + also_manager = ArduinoCI::ArduinoCmd::DisplayMgr::instance + expect(also_manager.enabled).to be true + end + expect(manager.enabled).to be false + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..77b061d4 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,14 @@ +require "bundler/setup" +require "arduino_ci" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end