gemika 0.3.2 → 0.5.0

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 (52) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +122 -0
  3. data/.gitignore +1 -1
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +75 -0
  6. data/{gemfiles/Gemfile.3.2.mysql2 → Gemfile.3.2.mysql2} +3 -2
  7. data/{gemfiles/Gemfile.3.2.mysql2.lock → Gemfile.3.2.mysql2.lock} +19 -5
  8. data/{gemfiles/Gemfile.4.2.mysql2 → Gemfile.4.2.mysql2} +3 -2
  9. data/{gemfiles/Gemfile.4.2.mysql2.lock → Gemfile.4.2.mysql2.lock} +11 -4
  10. data/{gemfiles/Gemfile.4.2.pg → Gemfile.4.2.pg} +2 -1
  11. data/{gemfiles/Gemfile.4.2.pg.lock → Gemfile.4.2.pg.lock} +9 -3
  12. data/{gemfiles/Gemfile.5.0.mysql2 → Gemfile.5.2.mysql2} +4 -3
  13. data/{gemfiles/Gemfile.5.0.mysql2.lock → Gemfile.5.2.mysql2.lock} +28 -20
  14. data/{gemfiles/Gemfile.5.0.pg → Gemfile.5.2.pg} +3 -2
  15. data/{gemfiles/Gemfile.5.0.pg.lock → Gemfile.5.2.pg.lock} +26 -18
  16. data/Gemfile.5.2.sqlite3 +17 -0
  17. data/Gemfile.5.2.sqlite3.lock +67 -0
  18. data/Gemfile.6.0.pg +17 -0
  19. data/Gemfile.6.0.pg.lock +67 -0
  20. data/README.md +144 -71
  21. data/gemika.gemspec +2 -2
  22. data/lib/gemika/database.rb +13 -3
  23. data/lib/gemika/env.rb +6 -0
  24. data/lib/gemika/errors.rb +2 -0
  25. data/lib/gemika/github_actions_generator.rb +150 -0
  26. data/lib/gemika/matrix.rb +38 -33
  27. data/lib/gemika/matrix/github_actions_config.rb +61 -0
  28. data/lib/gemika/matrix/travis_config.rb +42 -0
  29. data/lib/gemika/tasks.rb +1 -0
  30. data/lib/gemika/tasks/gemika.rb +14 -0
  31. data/lib/gemika/tasks/matrix.rb +4 -4
  32. data/lib/gemika/version.rb +1 -1
  33. data/spec/fixtures/github_actions_yml/Gemfile_without_gemika +1 -0
  34. data/spec/fixtures/github_actions_yml/excludes.yml +13 -0
  35. data/spec/fixtures/github_actions_yml/gemfile_without_gemika.yml +8 -0
  36. data/spec/fixtures/github_actions_yml/includes.yml +20 -0
  37. data/spec/fixtures/github_actions_yml/invalid.yml +8 -0
  38. data/spec/fixtures/github_actions_yml/missing_gemfile.yml +8 -0
  39. data/spec/fixtures/github_actions_yml/multiple_jobs.yml +16 -0
  40. data/spec/fixtures/github_actions_yml/two_by_two.yml +10 -0
  41. data/spec/fixtures/migrate/expected_github_actions.yml +129 -0
  42. data/spec/fixtures/migrate/travis.yml +66 -0
  43. data/spec/fixtures/travis_yml/includes.yml +12 -0
  44. data/spec/gemika/matrix_spec.rb +116 -0
  45. data/spec/spec_helper.rb +1 -0
  46. data/spec/support/database.github.yml +13 -0
  47. metadata +35 -19
  48. data/.travis.yml +0 -58
  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
  52. data/spec/support/database.yml +0 -10
@@ -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
@@ -95,8 +101,12 @@ module Gemika
95
101
  default_config['username'] = 'travis' if Env.travis?
96
102
  default_config['encoding'] = 'utf8'
97
103
  user_config = (@yaml_config['mysql'] || @yaml_config['mysql2']) || {}
104
+ elsif Env.gem?('sqlite3')
105
+ default_config['adapter'] = 'sqlite3'
106
+ default_config['database'] = ':memory:'
107
+ user_config = (@yaml_config['sqlite'] || @yaml_config['sqlite3']) || {}
98
108
  else
99
- raise UnknownAdapter, "Unknown database type. Either 'pg' or 'mysql2' gem should be in your current bundle."
109
+ raise UnknownAdapter, "Unknown database type. Either 'pg', 'mysql2', or 'sqlite3' gem should be in your current bundle."
100
110
  end
101
111
  default_config.merge(user_config)
102
112
  end
@@ -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
  #
@@ -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
@@ -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
@@ -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
@@ -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