gemika 0.4.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +112 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +31 -4
  5. data/Gemfile.5.2.mysql2 +1 -1
  6. data/Gemfile.5.2.mysql2.lock +4 -4
  7. data/Gemfile.5.2.pg.lock +2 -2
  8. data/Gemfile.5.2.sqlite3.lock +2 -2
  9. data/{Gemfile.6.0.pg → Gemfile.6.1.pg} +2 -2
  10. data/{Gemfile.6.0.pg.lock → Gemfile.6.1.pg.lock} +21 -22
  11. data/{Gemfile.4.2.pg → Gemfile.7.0.pg} +3 -5
  12. data/Gemfile.7.0.pg.lock +64 -0
  13. data/README.md +77 -7
  14. data/lib/gemika/database.rb +2 -2
  15. data/lib/gemika/env.rb +9 -1
  16. data/lib/gemika/errors.rb +2 -0
  17. data/lib/gemika/github_actions_generator.rb +150 -0
  18. data/lib/gemika/matrix/github_actions_config.rb +61 -0
  19. data/lib/gemika/matrix/travis_config.rb +42 -0
  20. data/lib/gemika/matrix.rb +109 -42
  21. data/lib/gemika/tasks/gemika.rb +14 -0
  22. data/lib/gemika/tasks/matrix.rb +4 -4
  23. data/lib/gemika/tasks.rb +1 -0
  24. data/lib/gemika/version.rb +1 -1
  25. data/spec/fixtures/github_actions_yml/Gemfile_without_gemika +1 -0
  26. data/spec/fixtures/github_actions_yml/excludes.yml +13 -0
  27. data/spec/fixtures/github_actions_yml/gemfile_without_gemika.yml +8 -0
  28. data/spec/fixtures/github_actions_yml/includes.yml +20 -0
  29. data/spec/fixtures/github_actions_yml/invalid.yml +8 -0
  30. data/spec/fixtures/github_actions_yml/missing_gemfile.yml +8 -0
  31. data/spec/fixtures/github_actions_yml/multiple_jobs.yml +16 -0
  32. data/spec/fixtures/github_actions_yml/two_by_two.yml +10 -0
  33. data/spec/fixtures/migrate/expected_github_actions.yml +129 -0
  34. data/{.travis.yml → spec/fixtures/migrate/travis.yml} +1 -1
  35. data/spec/gemika/matrix/row_spec.rb +62 -0
  36. data/spec/gemika/matrix_spec.rb +100 -0
  37. data/spec/spec_helper.rb +5 -1
  38. data/spec/support/database.github.yml +13 -0
  39. metadata +24 -15
  40. data/Gemfile.2.3.mysql2 +0 -18
  41. data/Gemfile.2.3.mysql2.lock +0 -42
  42. data/Gemfile.3.2.mysql2 +0 -18
  43. data/Gemfile.3.2.mysql2.lock +0 -67
  44. data/Gemfile.4.2.mysql2 +0 -17
  45. data/Gemfile.4.2.mysql2.lock +0 -69
  46. data/Gemfile.4.2.pg.lock +0 -69
  47. data/spec/support/database.travis.yml +0 -9
@@ -0,0 +1,150 @@
1
+ module Gemika
2
+ class GithubActionsGenerator
3
+ TYPES = {
4
+ test_sqlite: {
5
+ gemfile_filter: /\.sqlite/,
6
+ },
7
+ test_pg: {
8
+ gemfile_filter: /\.pg/,
9
+ database_setup: [
10
+ 'sudo apt-get install -y postgresql-client',
11
+ "PGPASSWORD=postgres psql -c 'create database test;' -U postgres -p 5432 -h localhost",
12
+ ],
13
+ services: {
14
+ 'postgres' => {
15
+ 'image' => 'postgres',
16
+ 'env' => {
17
+ 'POSTGRES_PASSWORD' => 'postgres'
18
+ },
19
+ 'options' => '--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5',
20
+ 'ports' => ['5432:5432'],
21
+ },
22
+ },
23
+ },
24
+ test_mysql: {
25
+ gemfile_filter: /\.mysql/,
26
+ database_setup: [
27
+ 'sudo apt-get install -y mysql-client libmariadbclient-dev',
28
+ "mysql -e 'create database IF NOT EXISTS test;' -u root --password=password -P 3306 -h 127.0.0.1",
29
+ ],
30
+ services: {
31
+ 'mysql' => {
32
+ 'image' => 'mysql:5.6',
33
+ 'env' => {
34
+ 'MYSQL_ROOT_PASSWORD' => 'password',
35
+ },
36
+ 'options' => '--health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5',
37
+ 'ports' => ['3306:3306'],
38
+ },
39
+ },
40
+ },
41
+ test: {
42
+ gemfile_filter: //,
43
+ }
44
+ }
45
+
46
+ def initialize(bundler_version:)
47
+ @bundler_version = bundler_version
48
+ end
49
+
50
+ def generate(rows)
51
+ rows_by_type = split_rows_by_gemfile(rows)
52
+ jobs = {}
53
+ rows_by_type.each do |type, type_rows|
54
+ jobs[type.to_s] = job_by_type(type, type_rows)
55
+ end
56
+ full_config(jobs)
57
+ end
58
+
59
+ private
60
+
61
+ def split_rows_by_gemfile(rows)
62
+ rows.group_by do |row|
63
+ TYPES.detect do |type, type_definition|
64
+ row.gemfile =~ type_definition[:gemfile_filter]
65
+ end.first
66
+ end
67
+ end
68
+
69
+ def job_by_type(type, rows)
70
+ matrix = full_matrix(rows) || include_matrix(rows)
71
+ type_definition = TYPES[type]
72
+
73
+ steps = [{
74
+ 'uses' => 'actions/checkout@v2',
75
+ }, {
76
+ 'name' => 'Install ruby',
77
+ 'uses' => 'ruby/setup-ruby@v1',
78
+ 'with' => {'ruby-version' => '${{ matrix.ruby }}'},
79
+ }]
80
+
81
+ if (database_setup = type_definition[:database_setup])
82
+ steps << {
83
+ 'name' => 'Setup database',
84
+ 'run' => database_setup.join("\n") + "\n",
85
+ }
86
+ end
87
+
88
+ steps += [{
89
+ 'name' => 'Bundle',
90
+ 'run' => "gem install bundler:#{@bundler_version}\nbundle install --no-deployment\n",
91
+ }, {
92
+ 'name' => 'Run tests',
93
+ 'run' => 'bundle exec rspec',
94
+ }]
95
+
96
+ job = {}
97
+ job['runs-on'] = 'ubuntu-20.04'
98
+ if (services = type_definition[:services])
99
+ job['services'] = services
100
+ end
101
+ job['strategy'] = {
102
+ 'fail-fast' => false,
103
+ 'matrix' => matrix,
104
+ }
105
+ job['env'] = {
106
+ 'BUNDLE_GEMFILE' => '${{ matrix.gemfile }}',
107
+ }
108
+ job['steps'] = steps
109
+
110
+ job
111
+ end
112
+
113
+ def full_matrix(rows)
114
+ rubies = rows.map(&:ruby)
115
+ gemfiles = rows.map(&:gemfile)
116
+ if rubies.size * gemfiles.size == rows.size
117
+ {
118
+ 'ruby' => rubies,
119
+ 'gemfile' => gemfiles,
120
+ }
121
+ end
122
+ end
123
+
124
+ def include_matrix(rows)
125
+ {
126
+ 'include' => rows.map do |row|
127
+ {
128
+ 'ruby' => row.ruby,
129
+ 'gemfile' => row.gemfile,
130
+ }
131
+ end,
132
+ }
133
+ end
134
+
135
+ def full_config(jobs)
136
+ {
137
+ 'name' => 'Tests',
138
+ 'on' => {
139
+ 'push' => {
140
+ 'branches' => ['master'],
141
+ },
142
+ 'pull_request' => {
143
+ 'branches' => ['master'],
144
+ },
145
+ },
146
+ 'jobs' => jobs,
147
+ }
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,61 @@
1
+ module Gemika
2
+ class Matrix
3
+
4
+ ##
5
+ # Load Github Action `.yml` files.
6
+ #
7
+ # @!visibility private
8
+ #
9
+ class GithubActionsConfig
10
+ class << self
11
+
12
+ def load_rows(options)
13
+ path = options.fetch(:path, '.github/workflows/test.yml')
14
+ workflow_yml = YAML.load_file(path)
15
+
16
+ matrices = workflow_yml.fetch('jobs', {}).values.map do |job|
17
+ job.fetch('strategy', {})['matrix']
18
+ end.reject(&:nil?)
19
+
20
+ matrices.map do |matrix|
21
+ matrix_to_rows(matrix)
22
+ end.flatten(1)
23
+ end
24
+
25
+ private
26
+
27
+ def matrix_to_rows(matrix)
28
+ if (!matrix['ruby'] || !matrix['gemfile']) && (!matrix['include'])
29
+ raise InvalidMatrixDefinition, 'matrix must use the keys "ruby" and "gemfile"'
30
+ end
31
+
32
+ rubies = matrix.fetch('ruby', [])
33
+ gemfiles = matrix.fetch('gemfile', [])
34
+
35
+ includes = matrix.fetch('include', [])
36
+ excludes = matrix.fetch('exclude', [])
37
+
38
+ rows = []
39
+ rubies.each do |ruby|
40
+ gemfiles.each do |gemfile|
41
+ row = { 'ruby' => ruby, 'gemfile' => gemfile }
42
+ rows << row unless excludes.include?(row)
43
+ end
44
+ end
45
+
46
+ rows = rows + includes
47
+ rows.map { |row| convert_row(row) }
48
+ end
49
+
50
+ def convert_row(row_hash)
51
+ if !row_hash['ruby'] || !row_hash['gemfile']
52
+ raise InvalidMatrixDefinition, 'matrix must use the keys "ruby" and "gemfile"'
53
+ end
54
+ Row.new(:ruby => row_hash['ruby'], :gemfile => row_hash['gemfile'])
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,42 @@
1
+ module Gemika
2
+ class Matrix
3
+
4
+ ##
5
+ # Load `.travis.yml` files.
6
+ #
7
+ # @!visibility private
8
+ #
9
+ class TravisConfig
10
+ class << self
11
+
12
+ def load_rows(options)
13
+ path = options.fetch(:path, '.travis.yml')
14
+ travis_yml = YAML.load_file(path)
15
+ rubies = travis_yml.fetch('rvm', [])
16
+ gemfiles = travis_yml.fetch('gemfile', [])
17
+ matrix_options = travis_yml.fetch('matrix', {})
18
+ includes = matrix_options.fetch('include', [])
19
+ excludes = matrix_options.fetch('exclude', [])
20
+
21
+ rows = []
22
+ rubies.each do |ruby|
23
+ gemfiles.each do |gemfile|
24
+ row = { 'rvm' => ruby, 'gemfile' => gemfile }
25
+ rows << row unless excludes.include?(row)
26
+ end
27
+ end
28
+
29
+ rows = rows + includes
30
+ rows = rows.map { |row| convert_row(row) }
31
+ rows
32
+ end
33
+
34
+ def convert_row(travis_row)
35
+ Row.new(:ruby => travis_row['rvm'], :gemfile => travis_row['gemfile'])
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ end
42
+ end
data/lib/gemika/matrix.rb CHANGED
@@ -1,47 +1,12 @@
1
1
  require 'yaml'
2
2
  require 'gemika/errors'
3
3
  require 'gemika/env'
4
+ require 'gemika/matrix/travis_config'
5
+ require 'gemika/matrix/github_actions_config'
4
6
 
5
7
  module Gemika
6
8
  class Matrix
7
9
 
8
- ##
9
- # Load `.travis.yml` files.
10
- #
11
- # @!visibility private
12
- #
13
- class TravisConfig
14
- class << self
15
-
16
- def load_rows(options)
17
- path = options.fetch(:path, '.travis.yml')
18
- travis_yml = YAML.load_file(path)
19
- rubies = travis_yml.fetch('rvm', [])
20
- gemfiles = travis_yml.fetch('gemfile', [])
21
- matrix_options = travis_yml.fetch('matrix', {})
22
- includes = matrix_options.fetch('include', [])
23
- excludes = matrix_options.fetch('exclude', [])
24
-
25
- rows = []
26
- rubies.each do |ruby|
27
- gemfiles.each do |gemfile|
28
- row = { 'rvm' => ruby, 'gemfile' => gemfile }
29
- rows << row unless excludes.include?(row)
30
- end
31
- end
32
-
33
- rows = rows + includes
34
- rows = rows.map { |row| convert_row(row) }
35
- rows
36
- end
37
-
38
- def convert_row(travis_row)
39
- Row.new(:ruby => travis_row['rvm'], :gemfile => travis_row['gemfile'])
40
- end
41
-
42
- end
43
- end
44
-
45
10
  ##
46
11
  # A row in the test matrix
47
12
  #
@@ -57,6 +22,11 @@ module Gemika
57
22
  #
58
23
  attr_reader :ruby
59
24
 
25
+ ##
26
+ # The actually used Ruby version for the row.
27
+ #
28
+ attr_reader :used_ruby
29
+
60
30
  ##
61
31
  # The path to the gemfile for the row.
62
32
  #
@@ -66,7 +36,9 @@ module Gemika
66
36
  # Returns whether this row can be run with the given Ruby version.
67
37
  #
68
38
  def compatible_with_ruby?(current_ruby = Env.ruby)
69
- ruby == current_ruby
39
+ @used_ruby = aliased_ruby(ruby)
40
+
41
+ @used_ruby == current_ruby
70
42
  end
71
43
 
72
44
  ##
@@ -80,6 +52,53 @@ module Gemika
80
52
  contents.include?('gemika') or raise UnusableGemfile, "Gemfile is missing gemika dependency: #{gemfile}"
81
53
  end
82
54
 
55
+ private
56
+
57
+ ##
58
+ # Checks if the requested ruby version is aliased by rbenv to use another ruby version.
59
+ # Returns the runnable ruby version.
60
+ #
61
+ def aliased_ruby(requested_version)
62
+ ruby_aliases = rbenv_aliases
63
+
64
+ aliased_versions = {}
65
+
66
+ ruby_aliases.split("\n").each do |ruby_alias|
67
+ split_pattern = /\A(.+) => (.+)\z/
68
+ alias_name, aliased_version = ruby_alias.match(split_pattern)&.captures
69
+ aliased_versions[alias_name] = aliased_version
70
+ end
71
+
72
+ find_aliased_ruby(requested_version, aliased_versions)
73
+ end
74
+
75
+ ##
76
+ # Recursively traverses aliases until the requested Ruby version is found.
77
+ # Returns the requested version if no alias can be found for that version.
78
+ #
79
+ def find_aliased_ruby(requested_version, aliased_versions)
80
+ found_version = aliased_versions[requested_version]
81
+
82
+ if found_version == requested_version
83
+ found_version
84
+ elsif found_version
85
+ find_aliased_ruby(found_version, aliased_versions)
86
+ else
87
+ requested_version
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Returns the list of rbenv aliases, if rbenv is installed.
93
+ #
94
+ def rbenv_aliases
95
+ if `which rbenv` != ''
96
+ `rbenv alias --list`
97
+ else
98
+ ''
99
+ end
100
+ end
101
+
83
102
  end
84
103
 
85
104
  COLOR_HEAD = "\e[44;97m"
@@ -99,6 +118,7 @@ module Gemika
99
118
  @compatible_count = 0
100
119
  @all_passed = nil
101
120
  @current_ruby = options.fetch(:current_ruby, RUBY_VERSION)
121
+ @aliased_rubys = {}
102
122
  end
103
123
 
104
124
  ##
@@ -114,6 +134,9 @@ module Gemika
114
134
  gemfile = row.gemfile
115
135
  if row.compatible_with_ruby?(current_ruby)
116
136
  @compatible_count += 1
137
+
138
+ @aliased_rubys[current_ruby] = row.ruby
139
+
117
140
  print_title gemfile
118
141
  gemfile_passed = Env.with_gemfile(gemfile, row, &block)
119
142
  @all_passed &= gemfile_passed
@@ -129,6 +152,25 @@ module Gemika
129
152
  print_summary
130
153
  end
131
154
 
155
+
156
+ ##
157
+ # Builds a {Matrix} from a `.travis.yml` file, or falls back to a Github Action .yml file
158
+ #
159
+ # @param [Hash] options
160
+ # @option options [String] Path to the `.travis.yml` file.
161
+ #
162
+ def self.from_ci_config
163
+ travis_location = '.travis.yml'
164
+ workflow_location = '.github/workflows/test.yml'
165
+ if File.exists?(travis_location)
166
+ from_travis_yml(:path => travis_location)
167
+ elsif File.exists?(workflow_location)
168
+ from_github_actions_yml(:path => workflow_location)
169
+ else
170
+ raise MissingMatrixDefinition, "expected either a #{travis_location} or a #{workflow_location}"
171
+ end
172
+ end
173
+
132
174
  ##
133
175
  # Builds a {Matrix} from the given `.travis.yml` file.
134
176
  #
@@ -140,8 +182,25 @@ module Gemika
140
182
  new(options.merge(:rows => rows))
141
183
  end
142
184
 
185
+ ##
186
+ # Builds a {Matrix} from the given Github Action workflow definition
187
+ #
188
+ # @param [Hash] options
189
+ # @option options [String] Path to the `.yml` file.
190
+ #
191
+ def self.from_github_actions_yml(options = {})
192
+ rows = GithubActionsConfig.load_rows(options)
193
+ new(options.merge(:rows => rows))
194
+ end
195
+
143
196
  attr_reader :rows, :current_ruby
144
197
 
198
+ def self.generate_github_actions_workflow(options= {})
199
+ require 'gemika/github_actions_generator'
200
+ rows = TravisConfig.load_rows(options)
201
+ GithubActionsGenerator.new(bundler_version: Bundler::VERSION).generate(rows)
202
+ end
203
+
145
204
  private
146
205
 
147
206
  def puts(*args)
@@ -177,19 +236,27 @@ module Gemika
177
236
  puts
178
237
 
179
238
  if @compatible_count == 0
180
- message = "No gemfiles were compatible with Ruby #{RUBY_VERSION}"
239
+ message = "No gemfiles were compatible with Ruby #{@aliased_rubys[RUBY_VERSION]}"
181
240
  puts tint(message, COLOR_FAILURE)
182
- puts
183
241
  raise UnsupportedRuby, message
184
242
  elsif @all_passed
185
- puts tint("All gemfiles succeeded for Ruby #{RUBY_VERSION}", COLOR_SUCCESS)
186
- puts
243
+ puts tint("All gemfiles succeeded for Ruby #{@aliased_rubys[RUBY_VERSION]}", COLOR_SUCCESS)
187
244
  else
188
245
  message = 'Some gemfiles failed'
189
246
  puts tint(message, COLOR_FAILURE)
190
247
  puts
191
248
  raise MatrixFailed, message
192
249
  end
250
+
251
+ print_aliases
252
+
253
+ puts
254
+ end
255
+
256
+ def print_aliases
257
+ @aliased_rubys.select { |used_version, alias_name| used_version != alias_name }.each do |used_version, alias_name|
258
+ puts tint("Ruby #{alias_name} is an alias for Ruby #{used_version} in this environment.", COLOR_WARNING)
259
+ end
193
260
  end
194
261
 
195
262
  end
@@ -0,0 +1,14 @@
1
+ require 'gemika/env'
2
+ require 'gemika/matrix'
3
+
4
+ ##
5
+ # Rake tasks to run commands for each compatible row in the test matrix.
6
+ #
7
+ namespace :gemika do
8
+
9
+ desc "Generate a github action workflow from a .travis.yml"
10
+ task :generate_github_actions_workflow do
11
+ puts Gemika::Matrix.generate_github_actions_workflow.to_yaml
12
+ end
13
+
14
+ end
@@ -9,7 +9,7 @@ namespace :matrix do
9
9
 
10
10
  desc "Run specs for all Ruby #{RUBY_VERSION} gemfiles"
11
11
  task :spec, :files do |t, options|
12
- Gemika::Matrix.from_travis_yml.each do |row|
12
+ Gemika::Matrix.from_ci_config.each do |row|
13
13
  options = options.to_hash.merge(
14
14
  :gemfile => row.gemfile,
15
15
  :fatal => false,
@@ -21,21 +21,21 @@ namespace :matrix do
21
21
 
22
22
  desc "Install all Ruby #{RUBY_VERSION} gemfiles"
23
23
  task :install do
24
- Gemika::Matrix.from_travis_yml.each do |row|
24
+ Gemika::Matrix.from_ci_config.each do |row|
25
25
  system('bundle install')
26
26
  end
27
27
  end
28
28
 
29
29
  desc "List dependencies for all Ruby #{RUBY_VERSION} gemfiles"
30
30
  task :list do
31
- Gemika::Matrix.from_travis_yml.each do |row|
31
+ Gemika::Matrix.from_ci_config.each do |row|
32
32
  system('bundle list')
33
33
  end
34
34
  end
35
35
 
36
36
  desc "Update all Ruby #{RUBY_VERSION} gemfiles"
37
37
  task :update, :gems do |t, options|
38
- Gemika::Matrix.from_travis_yml.each do |row|
38
+ Gemika::Matrix.from_ci_config.each do |row|
39
39
  system("bundle update #{options[:gems]}")
40
40
  end
41
41
  end
data/lib/gemika/tasks.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  require 'gemika/tasks/matrix'
2
2
  require 'gemika/tasks/rspec'
3
+ require 'gemika/tasks/gemika'
@@ -1,3 +1,3 @@
1
1
  module Gemika
2
- VERSION = '0.4.1'
2
+ VERSION = '0.7.0'
3
3
  end
@@ -0,0 +1 @@
1
+ gem 'some-gem'
@@ -0,0 +1,13 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby:
6
+ - "2.1.8"
7
+ - "2.3.1"
8
+ gemfile:
9
+ - gemfiles/Gemfile1
10
+ - gemfiles/Gemfile2
11
+ exclude:
12
+ - ruby: 2.1.8
13
+ gemfile: gemfiles/Gemfile1
@@ -0,0 +1,8 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby:
6
+ - 2.1.8
7
+ gemfile:
8
+ - spec/fixtures/travis_yml/Gemfile_without_gemika
@@ -0,0 +1,20 @@
1
+ matrix:
2
+ jobs:
3
+ first_job:
4
+ strategy:
5
+ matrix:
6
+ ruby:
7
+ - "2.1.8"
8
+ - "2.3.1"
9
+ gemfile:
10
+ - gemfiles/Gemfile1
11
+ - gemfiles/Gemfile2
12
+ include:
13
+ - ruby: 2.6.3
14
+ gemfile: gemfiles/Gemfile3
15
+ second_job:
16
+ strategy:
17
+ matrix:
18
+ include:
19
+ - ruby: 2.7.1
20
+ gemfile: gemfiles/Gemfile3
@@ -0,0 +1,8 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby_version:
6
+ - "2.1.8"
7
+ gemfile:
8
+ - gemfiles/Gemfile1
@@ -0,0 +1,8 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby:
6
+ - 2.1.8
7
+ gemfile:
8
+ - gemfiles/nonexisting_gemfile
@@ -0,0 +1,16 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby:
6
+ - "2.1.8"
7
+ gemfile:
8
+ - gemfiles/Gemfile1
9
+
10
+ second_job:
11
+ strategy:
12
+ matrix:
13
+ ruby:
14
+ - "2.3.1"
15
+ gemfile:
16
+ - gemfiles/Gemfile2
@@ -0,0 +1,10 @@
1
+ jobs:
2
+ first_job:
3
+ strategy:
4
+ matrix:
5
+ ruby:
6
+ - "2.1.8"
7
+ - "2.3.1"
8
+ gemfile:
9
+ - gemfiles/Gemfile1
10
+ - gemfiles/Gemfile2