diff --git a/.gitignore b/.gitignore index 50cae147..c507a689 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ pkg Gemfile.lock coverage /.idea +/gemfiles/*.gemfile.lock diff --git a/.travis.base.yml b/.travis.base.yml new file mode 100644 index 00000000..e0b96e26 --- /dev/null +++ b/.travis.base.yml @@ -0,0 +1,38 @@ +os: linux +dist: xenial +language: ruby +cache: bundler +script: "bundle exec rake test" +rvm: + - 2.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - 2.7 + - 3.0 + - head + - rbx +gemfile: + - gemfiles/rails_3.0.gemfile + - gemfiles/rails_3.1.gemfile + - gemfiles/rails_3.2.gemfile + - gemfiles/rails_4.0.gemfile + - gemfiles/rails_4.1.gemfile + - gemfiles/rails_4.2.gemfile + - gemfiles/rails_5.0.gemfile + - gemfiles/rails_5.1.gemfile + - gemfiles/rails_5.2.gemfile + - gemfiles/rails_6.0.gemfile + - gemfiles/rails_6.1.gemfile + - gemfiles/rails_7_edge.gemfile + - gemfiles/datamapper.gemfile + - gemfiles/sequel.gemfile +jobs: + allow_failures: + - rvm: head + - gemfile: gemfiles/rails_7_edge.gemfile + - rvm: rbx + fast_finish: true diff --git a/.travis.yml b/.travis.yml index f67ed619..ffcf417e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,109 +1,148 @@ -sudo: false +--- +os: linux +dist: xenial language: ruby cache: bundler +script: bundle exec rake test rvm: - - 2.0 - - 2.1 - - 2.2 - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - 3.0 - - rbx -env: - - ACTIVERECORD=3.0.x - - ACTIVERECORD=3.1.x - - ACTIVERECORD=3.2.x - - ACTIVERECORD=4.0.x - - ACTIVERECORD=4.1.x - - ACTIVERECORD=4.2.x - - ACTIVERECORD=5.0.x - - ACTIVERECORD=5.1.x - - ACTIVERECORD=5.2.x - - ACTIVERECORD=6.0.x - - ACTIVERECORD=6.1.x - - ACTIVERECORD=6.2.x -matrix: - exclude: - - rvm: 2.0 - env: ACTIVERECORD=5.0.x - - rvm: 2.0 - env: ACTIVERECORD=5.1.x - - rvm: 2.0 - env: ACTIVERECORD=5.2.x - - rvm: 2.0 - env: ACTIVERECORD=6.0.x - - rvm: 2.0 - env: ACTIVERECORD=6.1.x - - rvm: 2.0 - env: ACTIVERECORD=6.2.x - - rvm: 2.1 - env: ACTIVERECORD=5.0.x - - rvm: 2.1 - env: ACTIVERECORD=5.1.x - - rvm: 2.0 - env: ACTIVERECORD=5.2.x - - rvm: 2.1 - env: ACTIVERECORD=6.0.x - - rvm: 2.1 - env: ACTIVERECORD=6.1.x - - rvm: 2.1 - env: ACTIVERECORD=6.2.x - - rvm: 2.1 - env: ACTIVERECORD=6.0.x - - rvm: 2.2 - env: ACTIVERECORD=6.1.x - - rvm: 2.2 - env: ACTIVERECORD=6.2.x - - rvm: 2.2 - env: ACTIVERECORD=6.0.x - - rvm: 2.3 - env: ACTIVERECORD=6.1.x - - rvm: 2.3 - env: ACTIVERECORD=6.2.x - - rvm: 2.3 - env: ACTIVERECORD=3.0.x - - rvm: 2.4 - env: ACTIVERECORD=3.1.x - - rvm: 2.4 - env: ACTIVERECORD=3.2.x - - rvm: 2.4 - env: ACTIVERECORD=4.0.x - - rvm: 2.4 - env: ACTIVERECORD=4.1.x - - rvm: 2.4 - env: ACTIVERECORD=6.0.x - - rvm: 2.4 - env: ACTIVERECORD=6.1.x - - rvm: 2.4 - env: ACTIVERECORD=6.2.x - - rvm: 2.5 - env: ACTIVERECORD=3.0.x - - rvm: 2.5 - env: ACTIVERECORD=3.1.x - - rvm: 2.5 - env: ACTIVERECORD=3.2.x - - rvm: 2.5 - env: ACTIVERECORD=4.0.x - - rvm: 2.5 - env: ACTIVERECORD=4.1.x - - rvm: rbx - env: ACTIVERECORD=5.0.x - - rvm: rbx - env: ACTIVERECORD=5.1.x - - rvm: rbx - env: ACTIVERECORD=5.2.x - - rvm: rbx - env: ACTIVERECORD=6.0.x - - rvm: rbx - env: ACTIVERECORD=6.1.x - - rvm: rbx - env: ACTIVERECORD=6.2.x +- 2.0 +- 2.1 +- 2.2 +- 2.3 +- 2.4 +- 2.5 +- 2.6 +- 2.7 +- 3.0 +- head +- rbx +gemfile: +- gemfiles/rails_3.0.gemfile +- gemfiles/rails_3.1.gemfile +- gemfiles/rails_3.2.gemfile +- gemfiles/rails_4.0.gemfile +- gemfiles/rails_4.1.gemfile +- gemfiles/rails_4.2.gemfile +- gemfiles/rails_5.0.gemfile +- gemfiles/rails_5.1.gemfile +- gemfiles/rails_5.2.gemfile +- gemfiles/rails_6.0.gemfile +- gemfiles/rails_6.1.gemfile +- gemfiles/rails_7_edge.gemfile +- gemfiles/datamapper.gemfile +- gemfiles/sequel.gemfile +jobs: allow_failures: - - rvm: rbx + - rvm: head + - gemfile: gemfiles/rails_7_edge.gemfile + - rvm: rbx + - gemfile: gemfiles/rails_4.2.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_4.2.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_4.2.gemfile + rvm: '2.2' + - gemfile: gemfiles/rails_4.2.gemfile + rvm: '2.3' + - gemfile: gemfiles/rails_4.2.gemfile + rvm: '2.4' fast_finish: true -addons: - code_climate: - repo_token: a90435ed4954dd6e9f3697a20c5bc3754f67d94703f870e8fc7b00f69f5b2d06 + exclude: + - gemfile: gemfiles/rails_3.0.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_3.0.gemfile + rvm: '2.5' + - gemfile: gemfiles/rails_3.0.gemfile + rvm: '2.6' + - gemfile: gemfiles/rails_3.0.gemfile + rvm: '2.7' + - gemfile: gemfiles/rails_3.0.gemfile + rvm: '3.0' + - gemfile: gemfiles/rails_3.1.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_3.1.gemfile + rvm: '2.5' + - gemfile: gemfiles/rails_3.1.gemfile + rvm: '2.6' + - gemfile: gemfiles/rails_3.1.gemfile + rvm: '2.7' + - gemfile: gemfiles/rails_3.1.gemfile + rvm: '3.0' + - gemfile: gemfiles/rails_3.2.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_3.2.gemfile + rvm: '2.5' + - gemfile: gemfiles/rails_3.2.gemfile + rvm: '2.6' + - gemfile: gemfiles/rails_3.2.gemfile + rvm: '2.7' + - gemfile: gemfiles/rails_3.2.gemfile + rvm: '3.0' + - gemfile: gemfiles/rails_4.0.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_4.0.gemfile + rvm: '2.5' + - gemfile: gemfiles/rails_4.0.gemfile + rvm: '2.6' + - gemfile: gemfiles/rails_4.0.gemfile + rvm: '2.7' + - gemfile: gemfiles/rails_4.0.gemfile + rvm: '3.0' + - gemfile: gemfiles/rails_4.1.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_4.1.gemfile + rvm: '2.5' + - gemfile: gemfiles/rails_4.1.gemfile + rvm: '2.6' + - gemfile: gemfiles/rails_4.1.gemfile + rvm: '2.7' + - gemfile: gemfiles/rails_4.1.gemfile + rvm: '3.0' + - gemfile: gemfiles/rails_5.0.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_5.0.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_5.1.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_5.1.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_5.2.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_5.2.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_5.2.gemfile + rvm: '2.2' + - gemfile: gemfiles/rails_5.2.gemfile + rvm: '2.3' + - gemfile: gemfiles/rails_5.2.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_6.0.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_6.0.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_6.0.gemfile + rvm: '2.2' + - gemfile: gemfiles/rails_6.0.gemfile + rvm: '2.3' + - gemfile: gemfiles/rails_6.0.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_6.1.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_6.1.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_6.1.gemfile + rvm: '2.2' + - gemfile: gemfiles/rails_6.1.gemfile + rvm: '2.3' + - gemfile: gemfiles/rails_6.1.gemfile + rvm: '2.4' + - gemfile: gemfiles/rails_7_edge.gemfile + rvm: '2.0' + - gemfile: gemfiles/rails_7_edge.gemfile + rvm: '2.1' + - gemfile: gemfiles/rails_7_edge.gemfile + rvm: '2.2' + - gemfile: gemfiles/rails_7_edge.gemfile + rvm: '2.3' + - gemfile: gemfiles/rails_7_edge.gemfile + rvm: '2.4' diff --git a/Appraisals b/Appraisals new file mode 100644 index 00000000..8e988636 --- /dev/null +++ b/Appraisals @@ -0,0 +1,31 @@ + +SUPPORTED_RAILS_VERSIONS = %w[3.0 3.1 3.2 4.0 4.1 4.2 5.0 5.1 5.2 6.0 6.1] + +SUPPORTED_RAILS_VERSIONS.each do |rails_ver| + appraise "rails-#{rails_ver}" do + gem 'activerecord', "~> #{rails_ver}.x" + gem 'actionpack', "~> #{rails_ver}.x" + + install_if -> { %w[3.0 3.1 3.2].include?(rails_ver) } do + gem 'activerecord-sqlite3-adapter' + end + + end +end + +appraise 'rails-7-edge' do + gem 'activerecord', git: 'https://github.com/rails/rails', branch: 'main' + gem 'actionpack', git: 'https://github.com/rails/rails', branch: 'main' +end + +appraise 'datamapper' do + gem 'datamapper' + gem 'dm-sqlite-adapter' +end + +appraise 'sequel' do + gem 'sequel' +end + + + diff --git a/Gemfile b/Gemfile index fa75df15..eafb552d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,29 @@ source 'https://rubygems.org' gemspec + +gem 'appraisal' +gem 'sqlite3' +gem 'encryptor', '~> 3.0.0' +gem 'rake' + +gem 'minitest' +gem 'simplecov' +gem 'simplecov-rcov' +gem 'codeclimate-test-reporter', '<= 0.6.0' + + +if RUBY_VERSION < '2.1.0' + gem 'nokogiri' + gem 'public_suffix', '< 3.0.0' +end + +platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3', '< 3.8.7' # 3.8.7 is nice and broke +end + +group :development do + gem 'wwtd', require: false + gem 'rubocop', require: false +end diff --git a/Rakefile b/Rakefile index 3dbc96ce..7f453bb0 100644 --- a/Rakefile +++ b/Rakefile @@ -3,6 +3,7 @@ require 'rake/testtask' require 'rdoc/task' require "bundler/gem_tasks" +require 'wwtd/tasks' desc 'Test the attr_encrypted gem.' Rake::TestTask.new(:test) do |t| @@ -22,4 +23,7 @@ Rake::RDocTask.new(:rdoc) do |rdoc| end desc 'Default: run unit tests.' -task :default => :test +if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"] + task :default => :appraisal +end +#task :default => :test diff --git a/attr_encrypted.gemspec b/attr_encrypted.gemspec index 8ee98af6..a640be4c 100644 --- a/attr_encrypted.gemspec +++ b/attr_encrypted.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.0.0' s.add_dependency 'encryptor', '~> 3.0.0' + s.add_development_dependency 'appraisal' s.add_development_dependency 'activerecord', '>= 2.0.0' s.add_development_dependency 'actionpack', '>= 2.0.0' s.add_development_dependency 'datamapper' diff --git a/gemfiles/datamapper.gemfile b/gemfiles/datamapper.gemfile new file mode 100644 index 00000000..3d48f150 --- /dev/null +++ b/gemfiles/datamapper.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "datamapper" +gem "dm-sqlite-adapter" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_3.0.gemfile b/gemfiles/rails_3.0.gemfile new file mode 100644 index 00000000..25b339e0 --- /dev/null +++ b/gemfiles/rails_3.0.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 3.0.x" +gem "actionpack", "~> 3.0.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_3.1.gemfile b/gemfiles/rails_3.1.gemfile new file mode 100644 index 00000000..d501743e --- /dev/null +++ b/gemfiles/rails_3.1.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 3.1.x" +gem "actionpack", "~> 3.1.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_3.2.gemfile b/gemfiles/rails_3.2.gemfile new file mode 100644 index 00000000..5e4cd8ad --- /dev/null +++ b/gemfiles/rails_3.2.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 3.2.x" +gem "actionpack", "~> 3.2.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_4.0.gemfile b/gemfiles/rails_4.0.gemfile new file mode 100644 index 00000000..1a3ea043 --- /dev/null +++ b/gemfiles/rails_4.0.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 4.0.x" +gem "actionpack", "~> 4.0.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_4.1.gemfile b/gemfiles/rails_4.1.gemfile new file mode 100644 index 00000000..c76f7e9b --- /dev/null +++ b/gemfiles/rails_4.1.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 4.1.x" +gem "actionpack", "~> 4.1.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile new file mode 100644 index 00000000..94ebdf22 --- /dev/null +++ b/gemfiles/rails_4.2.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 4.2.x" +gem "actionpack", "~> 4.2.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile new file mode 100644 index 00000000..0e6fa56c --- /dev/null +++ b/gemfiles/rails_5.0.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 5.0.x" +gem "actionpack", "~> 5.0.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile new file mode 100644 index 00000000..e51f0290 --- /dev/null +++ b/gemfiles/rails_5.1.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 5.1.x" +gem "actionpack", "~> 5.1.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile new file mode 100644 index 00000000..79dcf6a9 --- /dev/null +++ b/gemfiles/rails_5.2.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 5.2.x" +gem "actionpack", "~> 5.2.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_6.0.gemfile b/gemfiles/rails_6.0.gemfile new file mode 100644 index 00000000..c4357c87 --- /dev/null +++ b/gemfiles/rails_6.0.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 6.0.x" +gem "actionpack", "~> 6.0.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_6.1.gemfile b/gemfiles/rails_6.1.gemfile new file mode 100644 index 00000000..ecbc9384 --- /dev/null +++ b/gemfiles/rails_6.1.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 6.1.x" +gem "actionpack", "~> 6.1.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_6.2.gemfile b/gemfiles/rails_6.2.gemfile new file mode 100644 index 00000000..c5f9fd75 --- /dev/null +++ b/gemfiles/rails_6.2.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 6.2.x" +gem "actionpack", "~> 6.2.x" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/rails_7_edge.gemfile b/gemfiles/rails_7_edge.gemfile new file mode 100644 index 00000000..3ec7b50c --- /dev/null +++ b/gemfiles/rails_7_edge.gemfile @@ -0,0 +1,13 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", git: "https://github.com/rails/rails", branch: "main" +gem "actionpack", git: "https://github.com/rails/rails", branch: "main" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/gemfiles/sequel.gemfile b/gemfiles/sequel.gemfile new file mode 100644 index 00000000..3f4a7396 --- /dev/null +++ b/gemfiles/sequel.gemfile @@ -0,0 +1,12 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "sequel" + +group :development do + gem "wwtd", require: false + gem "rubocop", require: false +end + +gemspec path: "../" diff --git a/scripts/generate_travis_excludes.rb b/scripts/generate_travis_excludes.rb new file mode 100755 index 00000000..7468c349 --- /dev/null +++ b/scripts/generate_travis_excludes.rb @@ -0,0 +1,76 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'yaml' + +class String + def versionish? + ::Gem::Version.correct?(self) + end +end + +class Numeric + def versionish? + true + end +end + +TRAVIS_TEMPLATE_PATH = File.expand_path('../.travis.base.yml', __dir__) +TRAVIS_CONFIG_PATH = File.expand_path('../.travis.yml', __dir__) + +GEMFILE_RUBY_VERSIONS = { + 'gemfiles/rails_3.0.gemfile': '< 2.4', + 'gemfiles/rails_3.1.gemfile': '< 2.4', + 'gemfiles/rails_3.2.gemfile': '< 2.4', + 'gemfiles/rails_4.0.gemfile': '< 2.4', + 'gemfiles/rails_4.1.gemfile': '< 2.4', + # apparently supports up to at least ruby 2.5 + 'gemfiles/rails_4.2.gemfile': '>= 0', + 'gemfiles/rails_5.0.gemfile': '>= 2.2', + 'gemfiles/rails_5.1.gemfile': '>= 2.2', + 'gemfiles/rails_5.2.gemfile': '>= 2.5', + 'gemfiles/rails_6.0.gemfile': '>= 2.5', + 'gemfiles/rails_6.1.gemfile': '>= 2.5', + 'gemfiles/rails_7_edge.gemfile': '>= 2.5', + #'gemfiles/datamapper.gemfile': '>= 0', + #'gemfiles/sequel.gemfile': '>= 0', +}.transform_values { |ver| Gem::Requirement.create(ver.to_s) } + +GEMFILE_FAILABLE_RUBY_VERSIONS = { + 'gemfiles/rails_4.2.gemfile': '>= 2.5', +}.transform_values { |ver| Gem::Requirement.create(ver.to_s) } + +travis_config = YAML.load_file(TRAVIS_TEMPLATE_PATH) + +ruby_versions = travis_config['rvm'].select(&:versionish?).map { |ver| Gem::Version.new(ver) } + +travis_config['jobs'] ||= {} +excludes = (travis_config['jobs']['exclude'] ||= []) +failables = (travis_config['jobs']['allow_failures'] ||= []) + +requirement_combinations = GEMFILE_RUBY_VERSIONS.to_a.product(ruby_versions) +failable_combinations = GEMFILE_FAILABLE_RUBY_VERSIONS.to_a.product(ruby_versions) + +requirement_combinations.each do |((gemfile, constraint), ruby_ver)| + next if constraint.satisfied_by?(ruby_ver) + + excludes << { + 'gemfile' => gemfile.to_s, + 'rvm' => ruby_ver.to_s + } +end + +failable_combinations.each do |((gemfile, constraint), ruby_ver)| + next if constraint.satisfied_by?(ruby_ver) + + failables << { + 'gemfile' => gemfile.to_s, + 'rvm' => ruby_ver.to_s + } +end + +File.open TRAVIS_CONFIG_PATH, 'wt' do |f| + YAML.dump(travis_config, f) +end + +printf "Done. (%d excludes and %d allowed failures)\n", excludes.size, failables.size