matrixeval-ruby 0.2.2 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c1fa003fd849f1a5baf6c9393c5210e030e52a14c044308f6732b602c196027
4
- data.tar.gz: 652ab5ecd63138e426a002aae768c7bd61214785ef8a8eaa698cffa2eddda1ae
3
+ metadata.gz: b38c89479923e7c9b607eff588ef02e9b4af821899b45b3a55a995e5673c9a9f
4
+ data.tar.gz: d2984e75182192bf0c80fced8062a019754c6d1ef042b757074e014e2851c6bd
5
5
  SHA512:
6
- metadata.gz: fc85db98392512e7683fbfa96c54e4f34ff2f5c248ab07ceac57bd18a6781cdfac45325b874c1580c068b5a1f56a8653bc0d943baea6fcd391e3baf521038dd4
7
- data.tar.gz: af503dd911a0522975061dfb37696470adedafc4a6868019480e89cfd51c686bb5aa4f5d848683dfe9b52ba94be0d34cb7fbb7478a262c6fe62f8964f0c5c6a1
6
+ metadata.gz: ec71cc460fe724eb580b95da0af5e42e9324a7f789ab951af5082871cf45a8e5906545a3ce60ec31993da95872d76ae8cdae5372ed58b5ecae603e6c4d42ced3
7
+ data.tar.gz: bf8be870710b0f433319e9be0360e69bdd23625d078661d288ef23b3b5c8cef61535e5baca49eff4013abf88a8769ab6030d7840a77f8f74fda92b452c480d7e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2022-02-27
4
+
5
+ - Refactor this gem as a plugin of matrixeval
6
+
7
+ ## [0.3.1] - 2022-02-21
8
+
9
+ - Add `.matrixeval/docker-compose` to `.gitignore`
10
+
11
+ ## [0.3.0] - 2022-02-21
12
+
13
+ - Support add extra docker compose services and volumes
14
+ - Isolate each job with docker compose project name and network
15
+
3
16
  ## [0.2.2] - 2022-02-11
4
17
 
5
18
  - Auto remove containers
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # matrixeval-ruby
2
2
 
3
- Test your ruby code against multiple versions of dependencies like Ruby, Rails, Env ...
3
+ It's a plugin of [matrixeval](https://github.com/MatrixEval/matrixeval-ruby) for Ruby. Test your ruby code against multiple versions of dependencies like Ruby, Rails, Env ...
4
+
5
+ ![](https://raw.githubusercontent.com/MatrixEval/assets/main/screenshots/summary.png)
6
+
7
+ ## Features
8
+
9
+ - Parallel test your ruby code against multiple versions of dependencies combinations.
10
+ - Test your ruby code against a specific dependencies combination.
11
+ - Choose any docker image you like for each job.
12
+ - Easy to use CLI to speed up your development efficiency
4
13
  ## Installation
5
14
 
6
15
  Add this line to your application's Gemfile:
@@ -22,7 +31,7 @@ Or install it yourself as:
22
31
  Initialize
23
32
 
24
33
  ```bash
25
- matrixeval init
34
+ matrixeval init -t ruby
26
35
  ```
27
36
 
28
37
  Customize `matrixeval.yml` file and run commands like:
@@ -36,6 +45,97 @@ matrixeval bash
36
45
  ```
37
46
  Run `matrixeval --help` for more details
38
47
 
48
+ ![](https://raw.githubusercontent.com/MatrixEval/assets/main/screenshots/help.png)
49
+
50
+ ### Configuration Example
51
+
52
+ Here is the configuration file `matrixeval.yml` which will auto created by `matrixeval init`
53
+
54
+ ```yaml
55
+ version: 0.4
56
+ target: ruby
57
+ project_name: REPLACE_ME
58
+ parallel_workers: number_of_processors
59
+ # commands:
60
+ # - ps
61
+ # - top
62
+ # - an_additional_command
63
+ # mounts:
64
+ # - /a/path/need/to/mount:/a/path/mount/to
65
+ matrix:
66
+ ruby:
67
+ variants:
68
+ - key: 2.7
69
+ container:
70
+ image: ruby:2.7.1
71
+ - key: 3.0
72
+ default: true
73
+ container:
74
+ image: ruby:3.0.0
75
+ - key: 3.1
76
+ container:
77
+ image: ruby:3.1.0
78
+ # - key: jruby-9.3
79
+ # container:
80
+ # image: jruby:9.3
81
+ # env:
82
+ # PATH: "/opt/jruby/bin:/app/bin:/bundle/bin:$PATH"
83
+ # mounts:
84
+ # - /a/path/need/to/mount:/a/path/mount/to
85
+
86
+ # rails:
87
+ # variants:
88
+ # - key: 6.1
89
+ # default: true
90
+ # env:
91
+ # RAILS_VERSION: "~> 6.1.0"
92
+ # - key: 7.0
93
+ # env:
94
+ # RAILS_VERSION: "~> 7.0.0"
95
+ # another:
96
+ # variants:
97
+ # - key: key1
98
+ # default: true
99
+ # env:
100
+ # ENV_KEY: 1
101
+ # - key: key2
102
+ # env:
103
+ # ENV_KEY: 2
104
+
105
+ exclude:
106
+ # - ruby: 3.0
107
+ # rails: 4.2
108
+ # - ruby: jruby-9.3
109
+ # rails: 7.0
110
+
111
+ docker-compose-extend:
112
+ # services:
113
+ # postgres:
114
+ # image: postgres:12.8
115
+ # volumes:
116
+ # - postgres12:/var/lib/postgresql/data
117
+ # environment:
118
+ # POSTGRES_HOST_AUTH_METHOD: trust
119
+
120
+ # redis:
121
+ # image: redis:6.2-alpine
122
+
123
+ # volumes:
124
+ # postgres12:
125
+ ```
126
+
127
+ ### Gemfile configuration example
128
+
129
+ Here is an example from [ruby-trello](https://github.com/jeremytregunna/ruby-trello)
130
+
131
+ ```ruby
132
+ if active_model_version = ENV['ACTIVE_MODEL_VERSION']
133
+ gem 'activemodel', active_model_version
134
+ end
135
+ ```
136
+
137
+ You can also check its corresponding [`matrixeval.yml`](https://github.com/jeremytregunna/ruby-trello/blob/master/matrixeval.yml)
138
+
39
139
  ## Development
40
140
 
41
141
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,87 @@
1
+ module Matrixeval
2
+ module Ruby
3
+ class Target < Matrixeval::Target
4
+
5
+ def version
6
+ Matrixeval::Ruby::VERSION
7
+ end
8
+
9
+ def matrixeval_yml_template_path
10
+ Matrixeval::Ruby.root.join("lib/matrixeval/ruby/templates/matrixeval.yml")
11
+ end
12
+
13
+ def vector_key
14
+ "ruby"
15
+ end
16
+
17
+ def env(context)
18
+ {
19
+ "BUNDLE_PATH" => "/bundle",
20
+ "GEM_HOME" => "/bundle",
21
+ "BUNDLE_APP_CONFIG" => "/bundle",
22
+ "BUNDLE_BIN" => "/bundle/bin",
23
+ "PATH" => "/app/bin:/bundle/bin:$PATH"
24
+ }
25
+ end
26
+
27
+ def mounts(context)
28
+ bundle_volume = bundle_volume(context)
29
+
30
+ [
31
+ "#{bundle_volume}:/bundle",
32
+ "../gemfile_locks/#{context.id}:/app/Gemfile.lock"
33
+ ]
34
+ end
35
+
36
+ def volumes(context)
37
+ bundle_volume = bundle_volume(context)
38
+
39
+ {
40
+ bundle_volume => {
41
+ "name" => bundle_volume
42
+ }
43
+ }
44
+ end
45
+
46
+ def gitignore_paths
47
+ [
48
+ ".matrixeval/gemfile_locks"
49
+ ]
50
+ end
51
+
52
+ def support_commands
53
+ [
54
+ 'ruby', 'rake', 'rails', 'rspec', 'bundle',
55
+ 'bin/rake', 'bin/rails', 'bin/rspec', 'bin/test'
56
+ ]
57
+ end
58
+
59
+ def cli_example_lines
60
+ [
61
+ "",
62
+ "Example:",
63
+ " matrixeval --all bundle install",
64
+ " matrixeval --ruby 3.0 rspec a_spec.rb",
65
+ " matrixeval --ruby 3.1 --active_model 7.0 rake test",
66
+ " matrixeval bash"
67
+ ]
68
+ end
69
+
70
+ def create_files
71
+ gemfile_lock_folder = Matrixeval.working_dir.join(".matrixeval/gemfile_locks")
72
+ FileUtils.mkdir_p gemfile_lock_folder
73
+
74
+ Context.all.each do |context|
75
+ FileUtils.touch gemfile_lock_folder.join(context.id)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def bundle_volume(context)
82
+ docker_image = context.main_variant.container.image
83
+ "bundle_#{docker_image.gsub(/[^A-Za-z0-9]/,'_')}"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,14 +1,15 @@
1
- version: 0.2
1
+ version: 0.4
2
2
  target: ruby
3
+ project_name: REPLACE_ME
3
4
  parallel_workers: number_of_processors
4
5
  # commands:
5
6
  # - ps
6
7
  # - top
7
8
  # - an_additional_command
9
+ # mounts:
10
+ # - /a/path/need/to/mount:/a/path/mount/to
8
11
  matrix:
9
12
  ruby:
10
- # mounts:
11
- # - /a/path/need/to/mount:/a/path/mount/to
12
13
  variants:
13
14
  - key: 2.7
14
15
  container:
@@ -23,8 +24,10 @@ matrix:
23
24
  # - key: jruby-9.3
24
25
  # container:
25
26
  # image: jruby:9.3
26
- # env:
27
+ # env:
27
28
  # PATH: "/opt/jruby/bin:/app/bin:/bundle/bin:$PATH"
29
+ # mounts:
30
+ # - /a/path/need/to/mount:/a/path/mount/to
28
31
 
29
32
  # rails:
30
33
  # variants:
@@ -50,3 +53,18 @@ exclude:
50
53
  # rails: 4.2
51
54
  # - ruby: jruby-9.3
52
55
  # rails: 7.0
56
+
57
+ docker-compose-extend:
58
+ # services:
59
+ # postgres:
60
+ # image: postgres:12.8
61
+ # volumes:
62
+ # - postgres12:/var/lib/postgresql/data
63
+ # environment:
64
+ # POSTGRES_HOST_AUTH_METHOD: trust
65
+
66
+ # redis:
67
+ # image: redis:6.2-alpine
68
+
69
+ # volumes:
70
+ # postgres12:
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Matrixeval
4
2
  module Ruby
5
- VERSION = "0.2.2"
3
+ VERSION = '0.4.0'
6
4
  end
7
5
  end
@@ -1,29 +1,14 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "ruby/version"
4
- require 'rainbow'
5
- require 'matrixeval/ruby/docker_compose'
6
- require 'matrixeval/ruby/context'
7
- require 'matrixeval/ruby/gemfile_locks'
8
- require 'matrixeval/ruby/runner'
9
- require 'matrixeval/ruby/gitignore'
1
+ require 'matrixeval'
2
+ require_relative 'ruby/version'
3
+ require_relative 'ruby/target'
10
4
 
11
5
  module Matrixeval
12
6
  module Ruby
13
- class Error < StandardError; end
14
-
15
7
  module_function
16
8
  def root
17
9
  Pathname.new("#{__dir__}/../..")
18
10
  end
19
11
  end
20
-
21
- module_function
22
- def start(argv)
23
- Ruby::Runner.start(argv)
24
- end
25
-
26
- def working_dir
27
- Pathname.new(Dir.getwd)
28
- end
29
12
  end
13
+
14
+ Matrixeval.register_target(:ruby, Matrixeval::Ruby::Target)
metadata CHANGED
@@ -1,63 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matrixeval-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hopper Gee
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-11 00:00:00.000000000 Z
11
+ date: 2022-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rainbow
14
+ name: matrixeval
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '3.1'
19
+ version: 0.4.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '3.1'
27
- - !ruby/object:Gem::Dependency
28
- name: concurrent-ruby
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: terminal-table
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
26
+ version: 0.4.2
55
27
  description: MatrixEval-Ruby
56
28
  email:
57
29
  - hopper.gee@hey.com
58
- executables:
59
- - matrixeval
60
- - meval
30
+ executables: []
61
31
  extensions: []
62
32
  extra_rdoc_files: []
63
33
  files:
@@ -70,24 +40,9 @@ files:
70
40
  - bin/console
71
41
  - bin/setup
72
42
  - bin/test
73
- - exe/matrixeval
74
- - exe/meval
75
43
  - lib/matrixeval/ruby.rb
76
- - lib/matrixeval/ruby/command_line.rb
77
- - lib/matrixeval/ruby/command_line/parse_context_arguments.rb
78
- - lib/matrixeval/ruby/config.rb
79
- - lib/matrixeval/ruby/config/yaml.rb
80
- - lib/matrixeval/ruby/container.rb
81
- - lib/matrixeval/ruby/context.rb
82
- - lib/matrixeval/ruby/context/find_by_command_options.rb
83
- - lib/matrixeval/ruby/docker_compose.rb
84
- - lib/matrixeval/ruby/docker_compose/yaml.rb
85
- - lib/matrixeval/ruby/gemfile_locks.rb
86
- - lib/matrixeval/ruby/gitignore.rb
87
- - lib/matrixeval/ruby/runner.rb
44
+ - lib/matrixeval/ruby/target.rb
88
45
  - lib/matrixeval/ruby/templates/matrixeval.yml
89
- - lib/matrixeval/ruby/variant.rb
90
- - lib/matrixeval/ruby/vector.rb
91
46
  - lib/matrixeval/ruby/version.rb
92
47
  homepage: https://github.com/MatrixEval/matrixeval-ruby
93
48
  licenses:
data/exe/matrixeval DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'matrixeval/ruby'
4
-
5
- Matrixeval.start(ARGV)
data/exe/meval DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'matrixeval/ruby'
4
-
5
- Matrixeval.start(ARGV)
@@ -1,85 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class CommandLine
4
- class ParseContextArguments
5
- class << self
6
- def call(context_arguments)
7
- new(context_arguments).call
8
- end
9
- end
10
-
11
- attr_reader :context_arguments, :options
12
-
13
- def initialize(context_arguments)
14
- @context_arguments = context_arguments
15
- @options = {}
16
- end
17
-
18
- def call
19
- parse!
20
- options
21
- end
22
-
23
- private
24
-
25
- def parse!
26
- OptionParser.new do |opts|
27
- opts.version = Matrixeval::Ruby::VERSION
28
- opts.program_name = ""
29
- opts.banner = <<~USAGE
30
- Usage:
31
- matrixeval(meval) [OPTIONS] COMMAND
32
- USAGE
33
-
34
- opts.separator ""
35
- opts.separator "Options:"
36
-
37
- opts.on "-a", "--all", "# Run the COMMAND against all matrix combinations"
38
-
39
- Config.vectors.each do |vector|
40
- # short = "-#{vector.short_key}"
41
- long = "--#{vector.key} [VERSION]"
42
- desc = [
43
- "# Run the COMMAND against a specific #{vector.key} version",
44
- "# Options: #{vector.variants.map(&:key).join("/")}",
45
- "# Default: #{vector.default_variant.key}",
46
- "# Customizable"
47
- ]
48
- opts.separator ""
49
- opts.on(long, *desc)
50
- end
51
-
52
- opts.separator ""
53
- opts.separator "Commands: #{Config.commands.join("/")} (Customizable)"
54
-
55
- opts.separator ""
56
- opts.separator "MatrixEval Options:"
57
-
58
- opts.on("-h", "--help", "# Show help") do
59
- puts opts.help
60
- exit
61
- end
62
-
63
- opts.on("-v", "--version", "# Show version") do
64
- puts opts.version
65
- exit
66
- end
67
-
68
- opts.separator ""
69
- opts.separator "Customizations:"
70
- opts.separator " You can customize all options in matrixeval.yml"
71
-
72
- opts.separator ""
73
- opts.separator "Example:"
74
- opts.separator " matrixeval --all bundle install"
75
- opts.separator " matrixeval --ruby 3.0 rspec a_spec.rb"
76
- opts.separator " matrixeval --ruby 3.1 --active_model 7.0 rake test"
77
- opts.separator " matrixeval bash"
78
-
79
- end.parse!(context_arguments, into: options)
80
- end
81
-
82
- end
83
- end
84
- end
85
- end
@@ -1,53 +0,0 @@
1
- require_relative "./command_line/parse_context_arguments"
2
-
3
- module Matrixeval
4
- module Ruby
5
- COMMANDS = ['rake', 'rspec', 'bundle', 'bash']
6
-
7
- class CommandLine
8
-
9
- attr_reader :argv
10
-
11
- def initialize(argv)
12
- @argv = argv
13
- end
14
-
15
- def valid?
16
- init? ||
17
- !context_options.empty? ||
18
- !seperator_index.nil?
19
- end
20
-
21
- def init?
22
- @argv[0] == 'init'
23
- end
24
-
25
- def all?
26
- context_options[:all]
27
- end
28
-
29
- def context_options
30
- @context_options ||= ParseContextArguments.call(context_arguments)
31
- end
32
-
33
- def context_arguments
34
- arguments = @argv[0...seperator_index]
35
- arguments << "-h" if @argv.empty?
36
- arguments
37
- end
38
-
39
- def rest_arguments
40
- @argv[seperator_index..-1]
41
- end
42
-
43
- private
44
-
45
- def seperator_index
46
- @argv.index do |argument|
47
- Config.commands.include?(argument)
48
- end
49
- end
50
-
51
- end
52
- end
53
- end
@@ -1,40 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Config
4
- class YAML
5
-
6
- class MissingError < StandardError; end
7
-
8
- class << self
9
-
10
- def create
11
- return if File.exist?(path)
12
-
13
- FileUtils.cp(template_path, path)
14
- end
15
-
16
- def template_path
17
- Matrixeval::Ruby.root.join(
18
- "lib/matrixeval/ruby/templates/matrixeval.yml"
19
- )
20
- end
21
-
22
- def path
23
- Matrixeval.working_dir.join("matrixeval.yml")
24
- end
25
-
26
- def [](key)
27
- yaml[key]
28
- end
29
-
30
- def yaml
31
- raise MissingError unless File.exist?(path)
32
-
33
- ::YAML.load File.read(path)
34
- end
35
-
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,60 +0,0 @@
1
- require 'yaml'
2
- require_relative "./vector"
3
- require_relative "./config/yaml"
4
-
5
- module Matrixeval
6
- module Ruby
7
- class Config
8
- class << self
9
-
10
- def version
11
- YAML["version"]
12
- end
13
-
14
- def target
15
- YAML["target"]
16
- end
17
-
18
- def vectors
19
- @vectors = YAML["matrix"].map do |key, vector_config|
20
- Vector.new(key, vector_config)
21
- end
22
- end
23
-
24
- def main_vector
25
- vectors.find(&:main?)
26
- end
27
-
28
- def rest_vectors
29
- vectors.reject(&:main?)
30
- end
31
-
32
- def variant_combinations
33
- main_vector_variants.product(*rest_vector_variants_matrix)
34
- end
35
-
36
- def main_vector_variants
37
- main_vector.variants
38
- end
39
-
40
- def rest_vector_variants_matrix
41
- rest_vectors.map(&:variants)
42
- end
43
-
44
- def exclusions
45
- YAML["exclude"] || []
46
- end
47
-
48
- def parallel_workers
49
- YAML["parallel_workers"] || "number_of_processors"
50
- end
51
-
52
- def commands
53
- cmds = YAML["commands"] || []
54
- COMMANDS + cmds
55
- end
56
-
57
- end
58
- end
59
- end
60
- end
@@ -1,15 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Container
4
-
5
- attr_reader :image, :env
6
-
7
- def initialize(options)
8
- options ||= {}
9
- @image = options["image"]
10
- @env = options["env"] || {}
11
- end
12
-
13
- end
14
- end
15
- end
@@ -1,67 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Context
4
- class FindByCommandOptions
5
- class << self
6
- def call(options)
7
- new(options).call
8
- end
9
- end
10
-
11
- attr_reader :options
12
-
13
- def initialize(options)
14
- @options = options
15
- end
16
-
17
- def call
18
- context = Context.all.find do |context|
19
- context.main_variant == main_variant &&
20
- context.rest_variants == rest_variants
21
- end
22
-
23
- raise Error.new("Can't find a corresponding matrix") if context.nil?
24
-
25
- context
26
- end
27
-
28
- private
29
-
30
- def main_variant
31
- dig_variant Config.main_vector
32
- end
33
-
34
- def rest_variants
35
- Config.rest_vectors.map do |vector|
36
- dig_variant vector
37
- end.sort do |v1, v2|
38
- v1.id <=> v2.id
39
- end
40
- end
41
-
42
- def dig_variant(vector)
43
- if option_key?(vector.key)
44
- find_variant(vector)
45
- else
46
- vector.default_variant
47
- end
48
- end
49
-
50
- def find_variant(vector)
51
- vector.variants.find do |variant|
52
- option(vector.key) == variant.key
53
- end
54
- end
55
-
56
- def option(key)
57
- options[key.to_sym] || options[key.to_s]
58
- end
59
-
60
- def option_key?(key)
61
- options.key?(key.to_sym) || options.key?(key.to_s)
62
- end
63
-
64
- end
65
- end
66
- end
67
- end
@@ -1,77 +0,0 @@
1
- require_relative "./context/find_by_command_options"
2
-
3
- module Matrixeval
4
- module Ruby
5
- class Context
6
-
7
- class << self
8
-
9
- def find_by_command_options!(options)
10
- FindByCommandOptions.call(options)
11
- end
12
-
13
- def all
14
- Config.variant_combinations.map do |variants|
15
- Context.new(
16
- main_variant: variants.find { |v| v.vector.main? },
17
- rest_variants: variants.reject { |v| v.vector.main? }
18
- )
19
- end.select do |context|
20
- Config.exclusions.none? do |exclusion|
21
- context.match_exclusion?(exclusion)
22
- end
23
- end
24
- end
25
-
26
- end
27
-
28
- attr_reader :main_variant, :rest_variants
29
-
30
- def initialize(main_variant:, rest_variants:)
31
- @main_variant = main_variant
32
- @rest_variants = (rest_variants || []).sort do |v1, v2|
33
- v1.id <=> v2.id
34
- end
35
- end
36
-
37
- def name
38
- variants.map(&:name).join(", ")
39
- end
40
-
41
- def id
42
- [[main_variant.id] + rest_variants.map(&:id)].join("_")
43
- end
44
-
45
- def env
46
- rest_variants.map(&:env).reduce({}, &:merge)
47
- .merge(main_variant.env)
48
- end
49
-
50
- def docker_compose_service_name
51
- main_variant.id
52
- end
53
-
54
- def gemfile_lock_path
55
- Matrixeval.working_dir.join(".matrixeval/Gemfile.lock.#{id}")
56
- end
57
-
58
- def variants
59
- [main_variant] + rest_variants
60
- end
61
-
62
- def match_exclusion?(exclusion)
63
- return false if exclusion.empty?
64
-
65
- variants.all? do |variant|
66
- vector_key = variant.vector.key
67
- if exclusion.key?(vector_key)
68
- exclusion[vector_key].to_s == variant.key
69
- else
70
- true
71
- end
72
- end
73
- end
74
-
75
- end
76
- end
77
- end
@@ -1,77 +0,0 @@
1
- require "erb"
2
-
3
- module Matrixeval
4
- module Ruby
5
- class DockerCompose
6
- class YAML
7
- class << self
8
-
9
- def create
10
- FileUtils.mkdir_p dot_matrixeval_folder
11
-
12
- File.open(path, 'w+') do |file|
13
- file.puts build_content
14
- end
15
- end
16
-
17
- private
18
-
19
- def build_content
20
- {
21
- "version" => "3",
22
- "services" => services_json,
23
- "volumes" => volumes_json
24
- }.to_yaml.sub(/---\n/, "")
25
- end
26
-
27
- def services_json
28
- services = {}
29
-
30
- Config.main_vector_variants.map do |variant|
31
- services[variant.docker_compose_service_name] = {
32
- "image" => variant.container.image,
33
- "volumes" => mounts(variant),
34
- "environment" => {
35
- "BUNDLE_PATH" => "/bundle",
36
- "GEM_HOME" => "/bundle",
37
- "BUNDLE_APP_CONFIG" => "/bundle",
38
- "BUNDLE_BIN" => "/bundle/bin",
39
- "PATH" => "/app/bin:/bundle/bin:$PATH"
40
- }.merge(variant.container.env),
41
- "working_dir" => "/app"
42
- }
43
- end
44
-
45
- services
46
- end
47
-
48
- def volumes_json
49
- bundle_volumes.map do |volume|
50
- [volume, {"name" => volume}]
51
- end.to_h
52
- end
53
-
54
- def bundle_volumes
55
- Config.main_vector_variants.map(&:bundle_volume_name)
56
- end
57
-
58
- def mounts(variant)
59
- [
60
- "..:/app:cached",
61
- "#{variant.bundle_volume_name}:/bundle",
62
- ] + Config.main_vector.mounts
63
- end
64
-
65
- def path
66
- dot_matrixeval_folder.join("docker-compose.yml")
67
- end
68
-
69
- def dot_matrixeval_folder
70
- Matrixeval.working_dir.join(".matrixeval")
71
- end
72
-
73
- end
74
- end
75
- end
76
- end
77
- end
@@ -1,53 +0,0 @@
1
-
2
- require_relative "./docker_compose/yaml"
3
-
4
- module Matrixeval
5
- module Ruby
6
- class DockerCompose
7
-
8
- class << self
9
- def clean_containers
10
- system("docker compose -f .matrixeval/docker-compose.yml rm --all -f >> /dev/null 2>&1")
11
- end
12
- end
13
-
14
- attr_reader :context
15
-
16
- def initialize(context)
17
- @context = context
18
- end
19
-
20
- def run(arguments)
21
- forward_arguments = arguments.join(" ")
22
-
23
- system(
24
- <<~DOCKER_COMPOSE_COMMAND
25
- docker compose -f .matrixeval/docker-compose.yml \
26
- run --rm \
27
- #{env} \
28
- #{gemfile_mount} \
29
- #{docker_compose_service_name} \
30
- #{forward_arguments}
31
- DOCKER_COMPOSE_COMMAND
32
- )
33
- end
34
-
35
- private
36
-
37
- def env
38
- context.env.map do |k, v|
39
- "-e #{k}='#{v}'"
40
- end.join(" ")
41
- end
42
-
43
- def gemfile_mount
44
- "-v ./.matrixeval/Gemfile.lock.#{context.id}:/app/Gemfile.lock"
45
- end
46
-
47
- def docker_compose_service_name
48
- context.docker_compose_service_name
49
- end
50
-
51
- end
52
- end
53
- end
@@ -1,23 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class GemfileLocks
4
- class << self
5
-
6
- def create
7
- FileUtils.mkdir_p dot_matrixeval_folder
8
-
9
- Context.all.each do |context|
10
- FileUtils.touch context.gemfile_lock_path
11
- end
12
- end
13
-
14
- private
15
-
16
- def dot_matrixeval_folder
17
- Matrixeval.working_dir.join(".matrixeval")
18
- end
19
-
20
- end
21
- end
22
- end
23
- end
@@ -1,54 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Gitignore
4
- class << self
5
-
6
- def update
7
- add_docker_compose
8
- add_gemfile_locks
9
- end
10
-
11
- private
12
-
13
- def add_docker_compose
14
- return if docker_compose_included?
15
-
16
- File.open(gitignore_path, 'a+') do |file|
17
- file.puts docker_compose_yaml
18
- end
19
- end
20
-
21
- def add_gemfile_locks
22
- return if gemfile_locks_included?
23
-
24
- File.open(gitignore_path, 'a+') do |file|
25
- file.puts gemfile_locks
26
- end
27
- end
28
-
29
- def docker_compose_included?
30
- File.exist?(gitignore_path) &&
31
- File.read(gitignore_path).include?(docker_compose_yaml)
32
- end
33
-
34
- def gemfile_locks_included?
35
- File.exist?(gitignore_path) &&
36
- File.read(gitignore_path).include?(gemfile_locks)
37
- end
38
-
39
- def docker_compose_yaml
40
- ".matrixeval/docker-compose.yml"
41
- end
42
-
43
- def gemfile_locks
44
- ".matrixeval/Gemfile.lock.*"
45
- end
46
-
47
- def gitignore_path
48
- Matrixeval.working_dir.join(".gitignore")
49
- end
50
-
51
- end
52
- end
53
- end
54
- end
@@ -1,204 +0,0 @@
1
- require 'optparse'
2
- require 'pathname'
3
- require 'fileutils'
4
- require 'matrixeval/ruby/config'
5
- require 'matrixeval/ruby/command_line'
6
- require "concurrent/utility/processor_counter"
7
- require 'terminal-table'
8
-
9
- module Matrixeval
10
- module Ruby
11
- class Runner
12
- class << self
13
- def start(argv)
14
- new(argv).start
15
- end
16
- end
17
-
18
- attr_reader :argv, :command
19
-
20
- def initialize(argv)
21
- @argv = argv
22
- @command = CommandLine.new(argv)
23
- @threads ||= []
24
- @matrixeval_results ||= []
25
- end
26
-
27
- def start
28
- validates
29
-
30
- if command.init?
31
- init
32
- elsif command.all?
33
- run_all_contexts
34
- else
35
- run_a_specific_context
36
- end
37
- rescue OptionParser::InvalidOption => e
38
- puts <<~ERROR
39
- #{e.message}
40
- See 'matrixeval --help'
41
- ERROR
42
- exit
43
- rescue Config::YAML::MissingError
44
- puts "Please run 'matrixeval init' first to generate matrixeval.yml"
45
- exit
46
- ensure
47
- turn_on_stty_opost
48
- DockerCompose.clean_containers
49
- end
50
-
51
- private
52
-
53
- def validates
54
- return if command.valid?
55
-
56
- puts <<~ERROR
57
- matrixeval: '#{argv.join(' ')}' is not a MatrixEval command.
58
- See 'matrixeval --help'
59
- ERROR
60
- exit
61
- end
62
-
63
- def init
64
- Config::YAML.create
65
- Gitignore.update
66
- end
67
-
68
- def run_all_contexts
69
- Config::YAML.create
70
- DockerCompose::YAML.create
71
- GemfileLocks.create
72
- Gitignore.update
73
-
74
- pull_all_images
75
-
76
- if workers_count == 1
77
- run_all_contexts_sequentially
78
- else
79
- run_all_contexts_in_parallel
80
- end
81
- end
82
-
83
- def run_all_contexts_sequentially
84
- Context.all.each do |context|
85
- puts Rainbow("[ MatrixEval ] ").blue.bright + Rainbow(" #{context.name} ").white.bright.bg(:blue)
86
- puts Rainbow("[ MatrixEval ] Run \"#{command.rest_arguments.join(" ")}\"").blue.bright
87
-
88
- docker_compose = DockerCompose.new(context)
89
- success = docker_compose.run(command.rest_arguments)
90
-
91
- @matrixeval_results << [context, !!success]
92
- end
93
-
94
- report
95
- end
96
-
97
- def run_all_contexts_in_parallel
98
- parallel(contexts) do |sub_contexts|
99
- Thread.current[:matrixeval_results] = []
100
-
101
- sub_contexts.each do |context|
102
- docker_compose = DockerCompose.new(context)
103
- success = docker_compose.run(command.rest_arguments)
104
-
105
- Thread.current[:matrixeval_results] << [context, !!success]
106
- end
107
- end
108
-
109
- report
110
- end
111
-
112
- def run_a_specific_context
113
- Config::YAML.create
114
- DockerCompose::YAML.create
115
- GemfileLocks.create
116
- Gitignore.update
117
-
118
- context = Context.find_by_command_options!(command.context_options)
119
-
120
- puts Rainbow("[ MatrixEval ] ").blue.bright + Rainbow(" #{context.name} ").white.bright.bg(:blue)
121
- puts Rainbow("[ MatrixEval ] Run \"#{command.rest_arguments.join(" ")}\"").blue.bright
122
-
123
- docker_compose = DockerCompose.new(context)
124
- docker_compose.run(command.rest_arguments)
125
- end
126
-
127
- def pull_all_images
128
- parallel(Config.main_vector_variants) do |sub_variants|
129
- sub_variants.each do |variant|
130
- puts "Docker image check/pull #{variant.container.image}"
131
- image_exists = system %Q{[ -n "$(docker images -q #{variant.container.image})" ]}
132
- next if image_exists
133
-
134
- system "docker pull #{variant.container.image}"
135
- end
136
- end
137
- end
138
-
139
- def report
140
- turn_on_stty_opost
141
-
142
- table = Terminal::Table.new(title: Rainbow("MatrixEval").blue.bright + " Summary", alignment: :center) do |table|
143
-
144
- headers = Config.vectors.map(&:key) + ['result']
145
- table.add_row headers.map { |value| { value: value, alignment: :center } }
146
- table.add_separator
147
-
148
- @matrixeval_results.each do |context, success|
149
- success_cell = [success ? Rainbow('Success').green : Rainbow('Failed').red]
150
- row = (context.variants.map(&:key) + success_cell).map do |value|
151
- { value: value, alignment: :center }
152
- end
153
-
154
- table.add_row row
155
- end
156
-
157
- end
158
-
159
- puts table
160
- end
161
-
162
- def parallel(collection)
163
- @threads = [] unless @threads.empty?
164
- @matrixeval_results = [] unless @matrixeval_results.empty?
165
-
166
- collection.each_slice(per_worker_contexts_count) do |sub_collection|
167
- @threads << Thread.new do
168
- yield sub_collection
169
- end
170
- end
171
-
172
- @threads.each(&:join)
173
-
174
- @threads.each do |thread|
175
- @matrixeval_results += (thread[:matrixeval_results] || [])
176
- end
177
- end
178
-
179
-
180
- def per_worker_contexts_count
181
- [(contexts.count / workers_count), 1].max
182
- end
183
-
184
- def contexts
185
- @contexts ||= Context.all
186
- end
187
-
188
- def workers_count
189
- count = if Config.parallel_workers == "number_of_processors"
190
- Concurrent.physical_processor_count
191
- else
192
- Integer(Config.parallel_workers)
193
- end
194
-
195
- [count, 1].max
196
- end
197
-
198
- def turn_on_stty_opost
199
- system("stty opost")
200
- end
201
-
202
- end
203
- end
204
- end
@@ -1,58 +0,0 @@
1
- require_relative "./container"
2
-
3
- module Matrixeval
4
- module Ruby
5
- class Variant
6
- class << self
7
- def default(key, vector)
8
- self.new({"key" => key}, vector)
9
- end
10
- end
11
-
12
- attr_reader :key, :env, :vector, :default, :container
13
-
14
- def initialize(config = {}, vector)
15
- raise Error.new("Variant#key is missing") if config["key"].nil?
16
-
17
- @vector = vector
18
- @key = config["key"].to_s
19
- @container = Container.new(config["container"])
20
- @env = config["env"] || {}
21
- @default = config["default"] || false
22
- end
23
-
24
- def name
25
- "#{vector.key}: #{key}"
26
- end
27
-
28
- def bundle_volume_name
29
- "bundle_#{container.image.gsub(/[^A-Za-z0-9]/,'_')}"
30
- end
31
-
32
- def id
33
- "#{vector.id}_#{key.to_s.gsub(/[^A-Za-z0-9]/,'_')}"
34
- end
35
-
36
- def docker_compose_service_name
37
- id
38
- end
39
-
40
- def pathname
41
- id
42
- end
43
-
44
- def default?
45
- default
46
- end
47
-
48
- def match_command_options?(options)
49
- options[vector.key] == key.to_s
50
- end
51
-
52
- def ==(variant)
53
- vector.key == variant.vector.key &&
54
- key == variant.key
55
- end
56
- end
57
- end
58
- end
@@ -1,40 +0,0 @@
1
- require_relative "./variant"
2
-
3
- module Matrixeval
4
- module Ruby
5
- class Vector
6
- attr_reader :key, :variants, :mounts
7
-
8
- def initialize(key, config)
9
- @key = key.to_s
10
- @mounts = config["mounts"] || []
11
- @variants = (config["variants"] || []).map do |variant_config|
12
- config = if variant_config.is_a?(Hash)
13
- variant_config
14
- else
15
- { "key" => variant_config.to_s }
16
- end
17
-
18
- Variant.new(config, self)
19
- end
20
- end
21
-
22
- def main?
23
- key == "ruby"
24
- end
25
-
26
- def id
27
- "#{key.to_s.gsub(/[^A-Za-z0-9]/,'_')}"
28
- end
29
-
30
- def default_variant
31
- variant = variants.find(&:default?)
32
- if variant.nil?
33
- raise Error.new("Please set a default variant for matrix #{key}")
34
- end
35
-
36
- variant
37
- end
38
- end
39
- end
40
- end