gemika 0.3.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +114 -0
  3. data/.ruby-version +1 -1
  4. data/CHANGELOG.md +85 -0
  5. data/{gemfiles/Gemfile.3.2.mysql2 → Gemfile.3.2.mysql2} +4 -3
  6. data/{gemfiles/Gemfile.3.2.mysql2.lock → Gemfile.3.2.mysql2.lock} +19 -5
  7. data/{gemfiles/Gemfile.4.2.mysql2 → Gemfile.4.2.mysql2} +3 -2
  8. data/{gemfiles/Gemfile.4.2.mysql2.lock → Gemfile.4.2.mysql2.lock} +11 -5
  9. data/{gemfiles/Gemfile.4.2.pg → Gemfile.4.2.pg} +2 -1
  10. data/{gemfiles/Gemfile.4.2.pg.lock → Gemfile.4.2.pg.lock} +9 -3
  11. data/{gemfiles/Gemfile.5.0.mysql2 → Gemfile.5.2.mysql2} +4 -3
  12. data/{gemfiles/Gemfile.5.0.mysql2.lock → Gemfile.5.2.mysql2.lock} +28 -21
  13. data/{gemfiles/Gemfile.5.0.pg → Gemfile.5.2.pg} +3 -2
  14. data/{gemfiles/Gemfile.5.0.pg.lock → Gemfile.5.2.pg.lock} +26 -19
  15. data/{gemfiles/Gemfile.5.0.sqlite3 → Gemfile.5.2.sqlite3} +3 -2
  16. data/{gemfiles/Gemfile.5.0.sqlite3.lock → Gemfile.5.2.sqlite3.lock} +20 -14
  17. data/Gemfile.6.1.pg +17 -0
  18. data/Gemfile.6.1.pg.lock +66 -0
  19. data/README.md +117 -49
  20. data/gemika.gemspec +2 -2
  21. data/lib/gemika/database.rb +10 -4
  22. data/lib/gemika/env.rb +6 -0
  23. data/lib/gemika/errors.rb +2 -0
  24. data/lib/gemika/github_actions_generator.rb +150 -0
  25. data/lib/gemika/matrix.rb +38 -33
  26. data/lib/gemika/matrix/github_actions_config.rb +61 -0
  27. data/lib/gemika/matrix/travis_config.rb +42 -0
  28. data/lib/gemika/tasks.rb +1 -0
  29. data/lib/gemika/tasks/gemika.rb +14 -0
  30. data/lib/gemika/tasks/matrix.rb +4 -4
  31. data/lib/gemika/version.rb +1 -1
  32. data/spec/fixtures/github_actions_yml/Gemfile_without_gemika +1 -0
  33. data/spec/fixtures/github_actions_yml/excludes.yml +13 -0
  34. data/spec/fixtures/github_actions_yml/gemfile_without_gemika.yml +8 -0
  35. data/spec/fixtures/github_actions_yml/includes.yml +20 -0
  36. data/spec/fixtures/github_actions_yml/invalid.yml +8 -0
  37. data/spec/fixtures/github_actions_yml/missing_gemfile.yml +8 -0
  38. data/spec/fixtures/github_actions_yml/multiple_jobs.yml +16 -0
  39. data/spec/fixtures/github_actions_yml/two_by_two.yml +10 -0
  40. data/spec/fixtures/migrate/expected_github_actions.yml +129 -0
  41. data/spec/fixtures/migrate/travis.yml +66 -0
  42. data/spec/fixtures/travis_yml/includes.yml +12 -0
  43. data/spec/gemika/matrix_spec.rb +116 -0
  44. data/spec/spec_helper.rb +1 -0
  45. data/spec/support/database.github.yml +13 -0
  46. metadata +35 -20
  47. data/.travis.yml +0 -62
  48. data/gemfiles/Gemfile.2.3.mysql2 +0 -17
  49. data/gemfiles/Gemfile.2.3.mysql2.lock +0 -34
  50. data/spec/support/database.travis.yml +0 -9
data/gemika.gemspec CHANGED
@@ -11,8 +11,8 @@ Gem::Specification.new do |s|
11
11
  s.description = s.summary
12
12
  s.license = 'MIT'
13
13
 
14
- s.files = `git ls-files`.split("\n").reject { |path| File.lstat(path).symlink? }
15
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").reject { |path| File.lstat(path).symlink? }
14
+ s.files = `git ls-files`.split("\n").reject { |path| !File.exists?(path) || File.lstat(path).symlink? }
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n").reject { |path| !File.exists?(path) || File.lstat(path).symlink? }
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
  end
@@ -14,13 +14,19 @@ module Gemika
14
14
 
15
15
  def initialize(options = {})
16
16
  yaml_config_folder = options.fetch(:config_folder, 'spec/support')
17
- yaml_config_filename = Env.travis? ? 'database.travis.yml' : 'database.yml'
17
+ yaml_config_filename = if Env.travis?
18
+ 'database.travis.yml'
19
+ elsif Env.github?
20
+ 'database.github.yml'
21
+ else
22
+ 'database.yml'
23
+ end
18
24
  yaml_config_path = File.join(yaml_config_folder, yaml_config_filename)
19
25
  if File.exists?(yaml_config_path)
20
26
  @yaml_config = YAML.load_file(yaml_config_path)
21
27
  else
22
- warn "No database configuration in #{yaml_config_path}, using defaults: #{adapter_config.inspect}"
23
28
  @yaml_config = {}
29
+ warn "No database configuration in #{yaml_config_path}, using defaults: #{adapter_config.inspect}"
24
30
  end
25
31
  @connected = false
26
32
  end
@@ -30,7 +36,7 @@ module Gemika
30
36
  #
31
37
  def connect
32
38
  unless @connected
33
- ActiveRecord::Base.establish_connection(adapter_config)
39
+ ActiveRecord::Base.establish_connection(**adapter_config)
34
40
  @connected = true
35
41
  end
36
42
  end
@@ -102,7 +108,7 @@ module Gemika
102
108
  else
103
109
  raise UnknownAdapter, "Unknown database type. Either 'pg', 'mysql2', or 'sqlite3' gem should be in your current bundle."
104
110
  end
105
- default_config.merge(user_config)
111
+ default_config.merge(user_config).symbolize_keys
106
112
  end
107
113
 
108
114
  private
data/lib/gemika/env.rb CHANGED
@@ -87,6 +87,12 @@ module Gemika
87
87
  !!ENV['TRAVIS']
88
88
  end
89
89
 
90
+ ##
91
+ # Return whether this process is running within a Github Actions build.
92
+ def github?
93
+ ENV.key?('GITHUB_WORKFLOW')
94
+ end
95
+
90
96
  ##
91
97
  # Creates an hash that enumerates entries in order of insertion.
92
98
  #
data/lib/gemika/errors.rb CHANGED
@@ -6,4 +6,6 @@ module Gemika
6
6
  class UnsupportedRuby < Error; end
7
7
  class MatrixFailed < Error; end
8
8
  class RSpecFailed < Error; end
9
+ class MissingMatrixDefinition < Error; end
10
+ class InvalidMatrixDefinition < Error; end
9
11
  end
@@ -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
data/lib/gemika/matrix.rb CHANGED
@@ -1,43 +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
- excludes = matrix_options.fetch('exclude', [])
23
- rows = []
24
- rubies.each do |ruby|
25
- gemfiles.each do |gemfile|
26
- row = { 'rvm' => ruby, 'gemfile' => gemfile }
27
- rows << row unless excludes.include?(row)
28
- end
29
- end
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
10
  ##
42
11
  # A row in the test matrix
43
12
  #
@@ -125,6 +94,25 @@ module Gemika
125
94
  print_summary
126
95
  end
127
96
 
97
+
98
+ ##
99
+ # Builds a {Matrix} from a `.travis.yml` file, or falls back to a Github Action .yml file
100
+ #
101
+ # @param [Hash] options
102
+ # @option options [String] Path to the `.travis.yml` file.
103
+ #
104
+ def self.from_ci_config
105
+ travis_location = '.travis.yml'
106
+ workflow_location = '.github/workflows/test.yml'
107
+ if File.exists?(travis_location)
108
+ from_travis_yml(:path => travis_location)
109
+ elsif File.exists?(workflow_location)
110
+ from_github_actions_yml(:path => workflow_location)
111
+ else
112
+ raise MissingMatrixDefinition, "expected either a #{travis_location} or a #{workflow_location}"
113
+ end
114
+ end
115
+
128
116
  ##
129
117
  # Builds a {Matrix} from the given `.travis.yml` file.
130
118
  #
@@ -136,8 +124,25 @@ module Gemika
136
124
  new(options.merge(:rows => rows))
137
125
  end
138
126
 
127
+ ##
128
+ # Builds a {Matrix} from the given Github Action workflow definition
129
+ #
130
+ # @param [Hash] options
131
+ # @option options [String] Path to the `.yml` file.
132
+ #
133
+ def self.from_github_actions_yml(options = {})
134
+ rows = GithubActionsConfig.load_rows(options)
135
+ new(options.merge(:rows => rows))
136
+ end
137
+
139
138
  attr_reader :rows, :current_ruby
140
139
 
140
+ def self.generate_github_actions_workflow(options= {})
141
+ require 'gemika/github_actions_generator'
142
+ rows = TravisConfig.load_rows(options)
143
+ GithubActionsGenerator.new(bundler_version: Bundler::VERSION).generate(rows)
144
+ end
145
+
141
146
  private
142
147
 
143
148
  def puts(*args)
@@ -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/tasks.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  require 'gemika/tasks/matrix'
2
2
  require 'gemika/tasks/rspec'
3
+ require 'gemika/tasks/gemika'
@@ -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