gemika 0.3.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) 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 +91 -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 +9 -3
  22. data/lib/gemika/env.rb +15 -1
  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 -21
  47. data/.travis.yml +0 -62
  48. data/Gemfile.lock +0 -63
  49. data/gemfiles/Gemfile.2.3.mysql2 +0 -17
  50. data/gemfiles/Gemfile.2.3.mysql2.lock +0 -34
  51. 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,7 +14,13 @@ 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)
@@ -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
@@ -35,7 +35,15 @@ module Gemika
35
35
  # process, regardless of what's in ENV temporarily
36
36
  @gemfile_changed = true
37
37
  @process_gemfile = ENV['BUNDLE_GEMFILE']
38
- Bundler.with_clean_env do
38
+
39
+ # .with_clean_env is deprecated since Bundler ~> 2.
40
+ bundler_method = if Gemika::Env.gem?('bundler', '< 2')
41
+ :with_clean_env
42
+ else
43
+ :with_unbundled_env
44
+ end
45
+
46
+ Bundler.send(bundler_method) do
39
47
  ENV['BUNDLE_GEMFILE'] = path
40
48
  block.call(*args)
41
49
  end
@@ -87,6 +95,12 @@ module Gemika
87
95
  !!ENV['TRAVIS']
88
96
  end
89
97
 
98
+ ##
99
+ # Return whether this process is running within a Github Actions build.
100
+ def github?
101
+ ENV.key?('GITHUB_WORKFLOW')
102
+ end
103
+
90
104
  ##
91
105
  # Creates an hash that enumerates entries in order of insertion.
92
106
  #
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