matrixeval-ruby 0.1.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +101 -1
- data/exe/matrixeval +1 -1
- data/exe/meval +1 -1
- data/lib/matrixeval/ruby/command_line/parse_context_arguments.rb +49 -2
- data/lib/matrixeval/ruby/command_line.rb +21 -11
- data/lib/matrixeval/ruby/config/yaml.rb +5 -0
- data/lib/matrixeval/ruby/config.rb +43 -0
- data/lib/matrixeval/ruby/container.rb +15 -0
- data/lib/matrixeval/ruby/context/build_docker_compose_extend.rb +43 -0
- data/lib/matrixeval/ruby/context.rb +10 -1
- data/lib/matrixeval/ruby/docker_compose/extend.rb +21 -0
- data/lib/matrixeval/ruby/docker_compose/extend_raw.rb +19 -0
- data/lib/matrixeval/ruby/docker_compose/file.rb +135 -0
- data/lib/matrixeval/ruby/docker_compose.rb +35 -14
- data/lib/matrixeval/ruby/extra_mount_files.rb +23 -0
- data/lib/matrixeval/ruby/gemfile_locks.rb +3 -3
- data/lib/matrixeval/ruby/gitignore.rb +1 -1
- data/lib/matrixeval/ruby/runner.rb +60 -22
- data/lib/matrixeval/ruby/templates/matrixeval.yml +46 -4
- data/lib/matrixeval/ruby/variant.rb +6 -3
- data/lib/matrixeval/ruby/vector.rb +2 -3
- data/lib/matrixeval/ruby/version.rb +1 -1
- data/lib/matrixeval/ruby.rb +1 -0
- metadata +8 -4
- data/Gemfile.lock +0 -37
- data/lib/matrixeval/ruby/docker_compose/yaml.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 526b698efa76d623a97a48ec4f9ed2d365ed9c05f2d6ea8230d7778997173dd9
|
4
|
+
data.tar.gz: fcb6eb6971632aaf115e00390c7b0230e903e4ef57421e9e0d3a192535b645b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc21ef4da660bddffb12d523559c7609b6d8b232c2b38409184c9e67260b37d5acf23a0d2456afe977076f09ccf436b0b138502caf3b3db90b4d330a0050d2dd
|
7
|
+
data.tar.gz: 35085fc143ce553458ebbab452aec1a062f1faed2915098c6d9753115ca906cd19d2f335bbf5def2310f18bddd380bf43351ad23f00191d3401035892580ba0f
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,28 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.3.0] - 2022-02-21
|
4
|
+
|
5
|
+
- Support add extra docker compose services and volumes
|
6
|
+
- Isolate each job with docker compose project name and network
|
7
|
+
|
8
|
+
## [0.2.2] - 2022-02-11
|
9
|
+
|
10
|
+
- Auto remove containers
|
11
|
+
|
12
|
+
## [0.2.1] - 2022-02-11
|
13
|
+
|
14
|
+
- Fix a assignment method issue
|
15
|
+
|
16
|
+
## [0.2.0] - 2022-02-10
|
17
|
+
|
18
|
+
- Change config format from 0.1 to 0.2
|
19
|
+
- CLI description
|
20
|
+
- Other errors fix
|
21
|
+
|
3
22
|
## [0.1.1] - 2022-02-09
|
4
23
|
|
5
24
|
- Fix a execution report issue
|
25
|
+
|
6
26
|
## [0.1.0] - 2022-02-09
|
7
27
|
|
8
28
|
- Initial release
|
data/README.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
# matrixeval-ruby
|
2
2
|
|
3
3
|
Test your ruby code against multiple versions of dependencies like Ruby, Rails, Env ...
|
4
|
+
|
5
|
+

|
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:
|
@@ -19,7 +28,98 @@ Or install it yourself as:
|
|
19
28
|
|
20
29
|
## Usage
|
21
30
|
|
22
|
-
|
31
|
+
Initialize
|
32
|
+
|
33
|
+
```bash
|
34
|
+
matrixeval init
|
35
|
+
```
|
36
|
+
|
37
|
+
Customize `matrixeval.yml` file and run commands like:
|
38
|
+
|
39
|
+
```bash
|
40
|
+
matrixeval --all bundle install
|
41
|
+
matrixeval --all rspec
|
42
|
+
matrixeval --ruby 3.0 rspec a_spec.rb
|
43
|
+
matrixeval --ruby 3.1 --rails 7.0 rake test
|
44
|
+
matrixeval bash
|
45
|
+
```
|
46
|
+
Run `matrixeval --help` for more details
|
47
|
+
|
48
|
+

|
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.3
|
56
|
+
project_name: REPLACE_ME
|
57
|
+
target: ruby
|
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
|
+
|
112
|
+
### Gemfile configuration example
|
113
|
+
|
114
|
+
Here is an example from [ruby-trello](https://github.com/jeremytregunna/ruby-trello)
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
if active_model_version = ENV['ACTIVE_MODEL_VERSION']
|
118
|
+
gem 'activemodel', active_model_version
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
You can also check its corresponding [`matrixeval.yml`](https://github.com/jeremytregunna/ruby-trello/blob/master/matrixeval.yml)
|
23
123
|
|
24
124
|
## Development
|
25
125
|
|
data/exe/matrixeval
CHANGED
data/exe/meval
CHANGED
@@ -24,11 +24,58 @@ module Matrixeval
|
|
24
24
|
|
25
25
|
def parse!
|
26
26
|
OptionParser.new do |opts|
|
27
|
-
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"
|
28
38
|
|
29
39
|
Config.vectors.each do |vector|
|
30
|
-
|
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
|
31
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
|
+
|
32
79
|
end.parse!(context_arguments, into: options)
|
33
80
|
end
|
34
81
|
|
@@ -2,6 +2,12 @@ require_relative "./command_line/parse_context_arguments"
|
|
2
2
|
|
3
3
|
module Matrixeval
|
4
4
|
module Ruby
|
5
|
+
COMMANDS = [
|
6
|
+
'ruby', 'rake', 'rails', 'rspec', 'bundle',
|
7
|
+
'bin/rake', 'bin/rails', 'bin/rspec', 'bin/test',
|
8
|
+
'bash', 'dash', 'sh', 'zsh'
|
9
|
+
]
|
10
|
+
|
5
11
|
class CommandLine
|
6
12
|
|
7
13
|
attr_reader :argv
|
@@ -10,38 +16,42 @@ module Matrixeval
|
|
10
16
|
@argv = argv
|
11
17
|
end
|
12
18
|
|
19
|
+
def valid?
|
20
|
+
init? ||
|
21
|
+
!context_options.empty? ||
|
22
|
+
!seperator_index.nil?
|
23
|
+
end
|
24
|
+
|
13
25
|
def init?
|
14
|
-
argv[0] == 'init'
|
26
|
+
@argv[0] == 'init'
|
15
27
|
end
|
16
28
|
|
17
29
|
def all?
|
18
|
-
|
30
|
+
context_options[:all]
|
19
31
|
end
|
20
32
|
|
21
33
|
def context_options
|
22
|
-
ParseContextArguments.call(context_arguments)
|
34
|
+
@context_options ||= ParseContextArguments.call(context_arguments)
|
23
35
|
end
|
24
36
|
|
25
37
|
def context_arguments
|
26
|
-
argv[0...seperator_index]
|
38
|
+
arguments = @argv[0...seperator_index]
|
39
|
+
arguments << "-h" if @argv.empty?
|
40
|
+
arguments
|
27
41
|
end
|
28
42
|
|
29
43
|
def rest_arguments
|
30
|
-
argv[seperator_index..-1]
|
44
|
+
@argv[seperator_index..-1]
|
31
45
|
end
|
32
46
|
|
33
47
|
private
|
34
48
|
|
35
49
|
def seperator_index
|
36
|
-
argv.index do |argument|
|
37
|
-
|
50
|
+
@argv.index do |argument|
|
51
|
+
Config.commands.include?(argument)
|
38
52
|
end
|
39
53
|
end
|
40
54
|
|
41
|
-
def seperator_commands
|
42
|
-
['rake', 'rspec', 'bundle', 'bash']
|
43
|
-
end
|
44
|
-
|
45
55
|
end
|
46
56
|
end
|
47
57
|
end
|
@@ -2,6 +2,9 @@ module Matrixeval
|
|
2
2
|
module Ruby
|
3
3
|
class Config
|
4
4
|
class YAML
|
5
|
+
|
6
|
+
class MissingError < StandardError; end
|
7
|
+
|
5
8
|
class << self
|
6
9
|
|
7
10
|
def create
|
@@ -25,6 +28,8 @@ module Matrixeval
|
|
25
28
|
end
|
26
29
|
|
27
30
|
def yaml
|
31
|
+
raise MissingError unless File.exist?(path)
|
32
|
+
|
28
33
|
::YAML.load File.read(path)
|
29
34
|
end
|
30
35
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require_relative "./vector"
|
3
3
|
require_relative "./config/yaml"
|
4
|
+
require_relative "./docker_compose/extend_raw"
|
5
|
+
require_relative "./docker_compose/extend"
|
4
6
|
|
5
7
|
module Matrixeval
|
6
8
|
module Ruby
|
@@ -15,6 +17,16 @@ module Matrixeval
|
|
15
17
|
YAML["target"]
|
16
18
|
end
|
17
19
|
|
20
|
+
def project_name
|
21
|
+
name = YAML["project_name"]
|
22
|
+
|
23
|
+
if name.nil? || name.strip.empty?
|
24
|
+
raise Error.new('missing project_name')
|
25
|
+
end
|
26
|
+
|
27
|
+
name
|
28
|
+
end
|
29
|
+
|
18
30
|
def vectors
|
19
31
|
@vectors = YAML["matrix"].map do |key, vector_config|
|
20
32
|
Vector.new(key, vector_config)
|
@@ -49,6 +61,37 @@ module Matrixeval
|
|
49
61
|
YAML["parallel_workers"] || "number_of_processors"
|
50
62
|
end
|
51
63
|
|
64
|
+
def commands
|
65
|
+
cmds = YAML["commands"] || []
|
66
|
+
COMMANDS + cmds
|
67
|
+
end
|
68
|
+
|
69
|
+
def docker_compose_extend_raw
|
70
|
+
DockerCompose::ExtendRaw.new(
|
71
|
+
YAML["docker-compose-extend"] || {}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def env
|
76
|
+
YAML["env"] || {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def mounts
|
80
|
+
YAML["mounts"] || []
|
81
|
+
end
|
82
|
+
|
83
|
+
def all_mounts
|
84
|
+
mounts + all_variant_mounts
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def all_variant_mounts
|
90
|
+
Config.vectors
|
91
|
+
.map(&:variants).flatten
|
92
|
+
.map(&:mounts).flatten
|
93
|
+
end
|
94
|
+
|
52
95
|
end
|
53
96
|
end
|
54
97
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Matrixeval
|
5
|
+
module Ruby
|
6
|
+
class Context
|
7
|
+
class BuildDockerComposeExtend
|
8
|
+
class << self
|
9
|
+
def call(context)
|
10
|
+
new(context).call
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :context
|
15
|
+
|
16
|
+
def initialize(context)
|
17
|
+
@context = context
|
18
|
+
end
|
19
|
+
|
20
|
+
def matrix_combination_id
|
21
|
+
context.id
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
DockerCompose::Extend.new(docker_compose_extend)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def docker_compose_extend
|
31
|
+
JSON.parse(render_erb)
|
32
|
+
end
|
33
|
+
|
34
|
+
def render_erb
|
35
|
+
ERB.new(
|
36
|
+
Config.docker_compose_extend_raw.content
|
37
|
+
).result(binding)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative "./context/find_by_command_options"
|
2
|
+
require_relative "./context/build_docker_compose_extend"
|
2
3
|
|
3
4
|
module Matrixeval
|
4
5
|
module Ruby
|
@@ -52,7 +53,11 @@ module Matrixeval
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def gemfile_lock_path
|
55
|
-
Matrixeval.working_dir.join(".matrixeval/
|
56
|
+
Matrixeval.working_dir.join(".matrixeval/gemfile_locks/#{id}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def docker_compose_file_path
|
60
|
+
Matrixeval.working_dir.join(".matrixeval/docker-compose/#{id}.yml")
|
56
61
|
end
|
57
62
|
|
58
63
|
def variants
|
@@ -72,6 +77,10 @@ module Matrixeval
|
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
80
|
+
def docker_compose_extend
|
81
|
+
BuildDockerComposeExtend.call(self)
|
82
|
+
end
|
83
|
+
|
75
84
|
end
|
76
85
|
end
|
77
86
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Matrixeval
|
2
|
+
module Ruby
|
3
|
+
class DockerCompose
|
4
|
+
class Extend
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@config = config || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def volumes
|
11
|
+
@config["volumes"] || {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def services
|
15
|
+
@config["services"] || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "erb"
|
2
|
+
|
3
|
+
module Matrixeval
|
4
|
+
module Ruby
|
5
|
+
class DockerCompose
|
6
|
+
class File
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def create_all
|
10
|
+
FileUtils.mkdir_p folder
|
11
|
+
|
12
|
+
Context.all.each do |context|
|
13
|
+
new(context).create
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def folder
|
20
|
+
Matrixeval.working_dir.join(".matrixeval/docker-compose")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :context
|
25
|
+
|
26
|
+
def initialize(context)
|
27
|
+
@context = context
|
28
|
+
end
|
29
|
+
|
30
|
+
def create
|
31
|
+
::File.open(docker_compose_file_path, 'w+') do |file|
|
32
|
+
file.puts build_content
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def docker_compose_file_path
|
39
|
+
context.docker_compose_file_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def build_content
|
43
|
+
{
|
44
|
+
"version" => "3",
|
45
|
+
"services" => services_json,
|
46
|
+
"volumes" => volumes_json
|
47
|
+
}.to_yaml.sub(/---\n/, "")
|
48
|
+
end
|
49
|
+
|
50
|
+
def services_json
|
51
|
+
services = {}
|
52
|
+
|
53
|
+
services[main_variant.docker_compose_service_name] = {
|
54
|
+
"image" => main_variant.container.image,
|
55
|
+
"volumes" => mounts(main_variant),
|
56
|
+
"environment" => {
|
57
|
+
"BUNDLE_PATH" => "/bundle",
|
58
|
+
"GEM_HOME" => "/bundle",
|
59
|
+
"BUNDLE_APP_CONFIG" => "/bundle",
|
60
|
+
"BUNDLE_BIN" => "/bundle/bin",
|
61
|
+
"PATH" => "/app/bin:/bundle/bin:$PATH"
|
62
|
+
}.merge(extra_env),
|
63
|
+
"working_dir" => "/app"
|
64
|
+
}.merge(depends_on)
|
65
|
+
|
66
|
+
services.merge(docker_compose_extend.services)
|
67
|
+
end
|
68
|
+
|
69
|
+
def volumes_json
|
70
|
+
{
|
71
|
+
bundle_volume => {
|
72
|
+
"name" => bundle_volume
|
73
|
+
}
|
74
|
+
}.merge(docker_compose_extend.volumes)
|
75
|
+
end
|
76
|
+
|
77
|
+
def depends_on
|
78
|
+
if docker_compose_extend.services.keys.empty?
|
79
|
+
{}
|
80
|
+
else
|
81
|
+
{ "depends_on" => docker_compose_extend.services.keys }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def extra_env
|
86
|
+
Config.env.merge(context.env)
|
87
|
+
.merge(main_variant.container.env)
|
88
|
+
end
|
89
|
+
|
90
|
+
def main_variant
|
91
|
+
context.main_variant
|
92
|
+
end
|
93
|
+
|
94
|
+
def bundle_volume
|
95
|
+
main_variant.bundle_volume_name
|
96
|
+
end
|
97
|
+
|
98
|
+
def mounts(variant)
|
99
|
+
[
|
100
|
+
"../..:/app:cached",
|
101
|
+
"#{variant.bundle_volume_name}:/bundle",
|
102
|
+
"../gemfile_locks/#{context.id}:/app/Gemfile.lock"
|
103
|
+
] + extra_mounts
|
104
|
+
end
|
105
|
+
|
106
|
+
def extra_mounts
|
107
|
+
mounts = Config.mounts + context.variants.map(&:mounts).flatten
|
108
|
+
mounts.map do |mount|
|
109
|
+
local_path, in_docker_path = mount.split(':')
|
110
|
+
next mount if Pathname.new(local_path).absolute?
|
111
|
+
|
112
|
+
local_path = Matrixeval.working_dir.join(local_path)
|
113
|
+
docker_compose_folder_path = Matrixeval.working_dir.join(".matrixeval/docker-compose")
|
114
|
+
local_path = local_path.relative_path_from docker_compose_folder_path
|
115
|
+
|
116
|
+
"#{local_path}:#{in_docker_path}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def docker_compose_extend
|
121
|
+
@docker_compose_extend ||= context.docker_compose_extend
|
122
|
+
end
|
123
|
+
|
124
|
+
def working_dir_name
|
125
|
+
Matrixeval.working_dir.basename
|
126
|
+
end
|
127
|
+
|
128
|
+
def project_name
|
129
|
+
Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
require_relative "./docker_compose/
|
2
|
+
require_relative "./docker_compose/file"
|
3
3
|
|
4
4
|
module Matrixeval
|
5
5
|
module Ruby
|
@@ -12,34 +12,55 @@ module Matrixeval
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def run(arguments)
|
15
|
-
forward_arguments = arguments.
|
15
|
+
forward_arguments = arguments.map do |arg|
|
16
|
+
arg.match(/\s/) ? "\"#{arg}\"" : arg
|
17
|
+
end.join(" ")
|
18
|
+
|
19
|
+
no_tty = %w[bash sh zsh dash].include?(arguments[0]) ? '' : '--no-TTY'
|
16
20
|
|
17
21
|
system(
|
18
22
|
<<~DOCKER_COMPOSE_COMMAND
|
19
|
-
|
23
|
+
#{docker_compose} \
|
20
24
|
run --rm \
|
21
|
-
#{
|
22
|
-
#{
|
23
|
-
#{docker_compose_service_name} \
|
25
|
+
#{no_tty} \
|
26
|
+
#{context.docker_compose_service_name} \
|
24
27
|
#{forward_arguments}
|
25
28
|
DOCKER_COMPOSE_COMMAND
|
26
29
|
)
|
30
|
+
ensure
|
31
|
+
stop_containers
|
32
|
+
clean_containers_and_anonymous_volumes
|
33
|
+
turn_on_stty_opost
|
27
34
|
end
|
28
35
|
|
29
36
|
private
|
30
37
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
|
38
|
+
def stop_containers
|
39
|
+
system("#{docker_compose} stop >> /dev/null 2>&1")
|
40
|
+
end
|
41
|
+
|
42
|
+
def clean_containers_and_anonymous_volumes
|
43
|
+
system("#{docker_compose} rm -v -f >> /dev/null 2>&1")
|
44
|
+
end
|
45
|
+
|
46
|
+
def docker_compose
|
47
|
+
<<~DOCKER_COMPOSE_COMMAND.strip
|
48
|
+
docker --log-level error compose \
|
49
|
+
-f #{yaml_file} \
|
50
|
+
-p matrixeval-#{project_name}-#{context.id}
|
51
|
+
DOCKER_COMPOSE_COMMAND
|
52
|
+
end
|
53
|
+
|
54
|
+
def yaml_file
|
55
|
+
".matrixeval/docker-compose/#{context.id}.yml"
|
35
56
|
end
|
36
57
|
|
37
|
-
def
|
38
|
-
"
|
58
|
+
def turn_on_stty_opost
|
59
|
+
system("stty opost")
|
39
60
|
end
|
40
61
|
|
41
|
-
def
|
42
|
-
|
62
|
+
def project_name
|
63
|
+
Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
|
43
64
|
end
|
44
65
|
|
45
66
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Matrixeval
|
2
|
+
module Ruby
|
3
|
+
class ExtraMountFiles
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def create
|
7
|
+
Config.all_mounts.each do |mount|
|
8
|
+
local_path, _ = mount.split(':')
|
9
|
+
next mount if Pathname.new(local_path).absolute?
|
10
|
+
|
11
|
+
local_path = Matrixeval.working_dir.join(local_path)
|
12
|
+
next if local_path.extname.empty?
|
13
|
+
next if local_path.ascend.none? { |path| path == Matrixeval.working_dir }
|
14
|
+
|
15
|
+
FileUtils.mkdir_p local_path.dirname
|
16
|
+
FileUtils.touch local_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -4,7 +4,7 @@ module Matrixeval
|
|
4
4
|
class << self
|
5
5
|
|
6
6
|
def create
|
7
|
-
FileUtils.mkdir_p
|
7
|
+
FileUtils.mkdir_p gemfile_lock_folder
|
8
8
|
|
9
9
|
Context.all.each do |context|
|
10
10
|
FileUtils.touch context.gemfile_lock_path
|
@@ -13,8 +13,8 @@ module Matrixeval
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
-
def
|
17
|
-
Matrixeval.working_dir.join(".matrixeval")
|
16
|
+
def gemfile_lock_folder
|
17
|
+
Matrixeval.working_dir.join(".matrixeval/gemfile_locks")
|
18
18
|
end
|
19
19
|
|
20
20
|
end
|
@@ -20,9 +20,13 @@ module Matrixeval
|
|
20
20
|
def initialize(argv)
|
21
21
|
@argv = argv
|
22
22
|
@command = CommandLine.new(argv)
|
23
|
+
@threads ||= []
|
24
|
+
@matrixeval_results ||= []
|
23
25
|
end
|
24
26
|
|
25
27
|
def start
|
28
|
+
validates
|
29
|
+
|
26
30
|
if command.init?
|
27
31
|
init
|
28
32
|
elsif command.all?
|
@@ -30,12 +34,31 @@ module Matrixeval
|
|
30
34
|
else
|
31
35
|
run_a_specific_context
|
32
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
|
33
46
|
ensure
|
34
47
|
turn_on_stty_opost
|
35
48
|
end
|
36
49
|
|
37
50
|
private
|
38
51
|
|
52
|
+
def validates
|
53
|
+
return if command.valid?
|
54
|
+
|
55
|
+
puts <<~ERROR
|
56
|
+
matrixeval: '#{argv.join(' ')}' is not a MatrixEval command.
|
57
|
+
See 'matrixeval --help'
|
58
|
+
ERROR
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
39
62
|
def init
|
40
63
|
Config::YAML.create
|
41
64
|
Gitignore.update
|
@@ -43,9 +66,12 @@ module Matrixeval
|
|
43
66
|
|
44
67
|
def run_all_contexts
|
45
68
|
Config::YAML.create
|
46
|
-
DockerCompose::
|
69
|
+
DockerCompose::File.create_all
|
47
70
|
GemfileLocks.create
|
48
71
|
Gitignore.update
|
72
|
+
ExtraMountFiles.create
|
73
|
+
|
74
|
+
pull_all_images
|
49
75
|
|
50
76
|
if workers_count == 1
|
51
77
|
run_all_contexts_sequentially
|
@@ -62,17 +88,17 @@ module Matrixeval
|
|
62
88
|
docker_compose = DockerCompose.new(context)
|
63
89
|
success = docker_compose.run(command.rest_arguments)
|
64
90
|
|
65
|
-
|
91
|
+
@matrixeval_results << [context, !!success]
|
66
92
|
end
|
67
93
|
|
68
94
|
report
|
69
95
|
end
|
70
96
|
|
71
97
|
def run_all_contexts_in_parallel
|
72
|
-
parallel do |
|
98
|
+
parallel(contexts) do |sub_contexts|
|
73
99
|
Thread.current[:matrixeval_results] = []
|
74
100
|
|
75
|
-
|
101
|
+
sub_contexts.each do |context|
|
76
102
|
docker_compose = DockerCompose.new(context)
|
77
103
|
success = docker_compose.run(command.rest_arguments)
|
78
104
|
|
@@ -85,9 +111,10 @@ module Matrixeval
|
|
85
111
|
|
86
112
|
def run_a_specific_context
|
87
113
|
Config::YAML.create
|
88
|
-
DockerCompose::
|
114
|
+
DockerCompose::File.create_all
|
89
115
|
GemfileLocks.create
|
90
116
|
Gitignore.update
|
117
|
+
ExtraMountFiles.create
|
91
118
|
|
92
119
|
context = Context.find_by_command_options!(command.context_options)
|
93
120
|
|
@@ -98,15 +125,28 @@ module Matrixeval
|
|
98
125
|
docker_compose.run(command.rest_arguments)
|
99
126
|
end
|
100
127
|
|
128
|
+
def pull_all_images
|
129
|
+
parallel(Config.main_vector_variants) do |sub_variants|
|
130
|
+
sub_variants.each do |variant|
|
131
|
+
puts "Docker image check/pull #{variant.container.image}"
|
132
|
+
image_exists = system %Q{[ -n "$(docker images -q #{variant.container.image})" ]}
|
133
|
+
next if image_exists
|
134
|
+
|
135
|
+
system "docker pull #{variant.container.image}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
101
140
|
def report
|
102
141
|
turn_on_stty_opost
|
103
142
|
|
104
143
|
table = Terminal::Table.new(title: Rainbow("MatrixEval").blue.bright + " Summary", alignment: :center) do |table|
|
105
144
|
|
106
|
-
|
145
|
+
headers = Config.vectors.map(&:key) + ['result']
|
146
|
+
table.add_row headers.map { |value| { value: value, alignment: :center } }
|
107
147
|
table.add_separator
|
108
148
|
|
109
|
-
matrixeval_results.each do |context, success|
|
149
|
+
@matrixeval_results.each do |context, success|
|
110
150
|
success_cell = [success ? Rainbow('Success').green : Rainbow('Failed').red]
|
111
151
|
row = (context.variants.map(&:key) + success_cell).map do |value|
|
112
152
|
{ value: value, alignment: :center }
|
@@ -120,32 +160,30 @@ module Matrixeval
|
|
120
160
|
puts table
|
121
161
|
end
|
122
162
|
|
123
|
-
def parallel
|
124
|
-
|
163
|
+
def parallel(collection)
|
164
|
+
@threads = [] unless @threads.empty?
|
165
|
+
@matrixeval_results = [] unless @matrixeval_results.empty?
|
125
166
|
|
126
|
-
|
127
|
-
threads << Thread.new do
|
128
|
-
yield
|
167
|
+
collection.each_slice(per_worker_contexts_count) do |sub_collection|
|
168
|
+
@threads << Thread.new do
|
169
|
+
yield sub_collection
|
129
170
|
end
|
130
171
|
end
|
131
172
|
|
132
|
-
threads.each(&:join)
|
173
|
+
@threads.each(&:join)
|
133
174
|
|
134
|
-
threads.each do |thread|
|
135
|
-
|
175
|
+
@threads.each do |thread|
|
176
|
+
@matrixeval_results += (thread[:matrixeval_results] || [])
|
136
177
|
end
|
137
178
|
end
|
138
179
|
|
139
|
-
def threads
|
140
|
-
@threads ||= []
|
141
|
-
end
|
142
180
|
|
143
|
-
def
|
144
|
-
|
181
|
+
def per_worker_contexts_count
|
182
|
+
[(contexts.count / workers_count), 1].max
|
145
183
|
end
|
146
184
|
|
147
|
-
def
|
148
|
-
@
|
185
|
+
def contexts
|
186
|
+
@contexts ||= Context.all
|
149
187
|
end
|
150
188
|
|
151
189
|
def workers_count
|
@@ -1,13 +1,55 @@
|
|
1
|
-
version: 0.
|
1
|
+
version: 0.3
|
2
|
+
project_name: REPLACE_ME
|
2
3
|
target: ruby
|
3
4
|
parallel_workers: number_of_processors
|
5
|
+
# commands:
|
6
|
+
# - ps
|
7
|
+
# - top
|
8
|
+
# - an_additional_command
|
9
|
+
# mounts:
|
10
|
+
# - /a/path/need/to/mount:/a/path/mount/to
|
4
11
|
matrix:
|
5
12
|
ruby:
|
6
13
|
variants:
|
7
14
|
- key: 2.7
|
8
|
-
|
15
|
+
container:
|
16
|
+
image: ruby:2.7.1
|
9
17
|
- key: 3.0
|
10
|
-
image: ruby:3.0.0
|
11
18
|
default: true
|
19
|
+
container:
|
20
|
+
image: ruby:3.0.0
|
12
21
|
- key: 3.1
|
13
|
-
|
22
|
+
container:
|
23
|
+
image: ruby:3.1.0
|
24
|
+
# - key: jruby-9.3
|
25
|
+
# container:
|
26
|
+
# image: jruby:9.3
|
27
|
+
# env:
|
28
|
+
# PATH: "/opt/jruby/bin:/app/bin:/bundle/bin:$PATH"
|
29
|
+
# mounts:
|
30
|
+
# - /a/path/need/to/mount:/a/path/mount/to
|
31
|
+
|
32
|
+
# rails:
|
33
|
+
# variants:
|
34
|
+
# - key: 6.1
|
35
|
+
# default: true
|
36
|
+
# env:
|
37
|
+
# RAILS_VERSION: "~> 6.1.0"
|
38
|
+
# - key: 7.0
|
39
|
+
# env:
|
40
|
+
# RAILS_VERSION: "~> 7.0.0"
|
41
|
+
# another:
|
42
|
+
# variants:
|
43
|
+
# - key: key1
|
44
|
+
# default: true
|
45
|
+
# env:
|
46
|
+
# ENV_KEY: 1
|
47
|
+
# - key: key2
|
48
|
+
# env:
|
49
|
+
# ENV_KEY: 2
|
50
|
+
|
51
|
+
exclude:
|
52
|
+
# - ruby: 3.0
|
53
|
+
# rails: 4.2
|
54
|
+
# - ruby: jruby-9.3
|
55
|
+
# rails: 7.0
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative "./container"
|
2
|
+
|
1
3
|
module Matrixeval
|
2
4
|
module Ruby
|
3
5
|
class Variant
|
@@ -7,16 +9,17 @@ module Matrixeval
|
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
|
-
attr_reader :key, :
|
12
|
+
attr_reader :key, :env, :vector, :default, :container, :mounts
|
11
13
|
|
12
14
|
def initialize(config = {}, vector)
|
13
15
|
raise Error.new("Variant#key is missing") if config["key"].nil?
|
14
16
|
|
15
17
|
@vector = vector
|
16
18
|
@key = config["key"].to_s
|
17
|
-
@
|
19
|
+
@container = Container.new(config["container"])
|
18
20
|
@env = config["env"] || {}
|
19
21
|
@default = config["default"] || false
|
22
|
+
@mounts = config["mounts"] || []
|
20
23
|
end
|
21
24
|
|
22
25
|
def name
|
@@ -24,7 +27,7 @@ module Matrixeval
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def bundle_volume_name
|
27
|
-
"bundle_#{image.gsub(/[^A-Za-z0-9]/,'_')}"
|
30
|
+
"bundle_#{container.image.gsub(/[^A-Za-z0-9]/,'_')}"
|
28
31
|
end
|
29
32
|
|
30
33
|
def id
|
@@ -3,11 +3,10 @@ require_relative "./variant"
|
|
3
3
|
module Matrixeval
|
4
4
|
module Ruby
|
5
5
|
class Vector
|
6
|
-
attr_reader :key, :variants
|
6
|
+
attr_reader :key, :variants
|
7
7
|
|
8
8
|
def initialize(key, config)
|
9
9
|
@key = key.to_s
|
10
|
-
@mounts = config["mounts"] || []
|
11
10
|
@variants = (config["variants"] || []).map do |variant_config|
|
12
11
|
config = if variant_config.is_a?(Hash)
|
13
12
|
variant_config
|
@@ -30,7 +29,7 @@ module Matrixeval
|
|
30
29
|
def default_variant
|
31
30
|
variant = variants.find(&:default?)
|
32
31
|
if variant.nil?
|
33
|
-
raise Error.new("Please set a default variant for matrix #{
|
32
|
+
raise Error.new("Please set a default variant for matrix #{key}")
|
34
33
|
end
|
35
34
|
|
36
35
|
variant
|
data/lib/matrixeval/ruby.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: matrixeval-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hopper Gee
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rainbow
|
@@ -64,7 +64,6 @@ files:
|
|
64
64
|
- CHANGELOG.md
|
65
65
|
- CODE_OF_CONDUCT.md
|
66
66
|
- Gemfile
|
67
|
-
- Gemfile.lock
|
68
67
|
- LICENSE.txt
|
69
68
|
- README.md
|
70
69
|
- Rakefile
|
@@ -78,10 +77,15 @@ files:
|
|
78
77
|
- lib/matrixeval/ruby/command_line/parse_context_arguments.rb
|
79
78
|
- lib/matrixeval/ruby/config.rb
|
80
79
|
- lib/matrixeval/ruby/config/yaml.rb
|
80
|
+
- lib/matrixeval/ruby/container.rb
|
81
81
|
- lib/matrixeval/ruby/context.rb
|
82
|
+
- lib/matrixeval/ruby/context/build_docker_compose_extend.rb
|
82
83
|
- lib/matrixeval/ruby/context/find_by_command_options.rb
|
83
84
|
- lib/matrixeval/ruby/docker_compose.rb
|
84
|
-
- lib/matrixeval/ruby/docker_compose/
|
85
|
+
- lib/matrixeval/ruby/docker_compose/extend.rb
|
86
|
+
- lib/matrixeval/ruby/docker_compose/extend_raw.rb
|
87
|
+
- lib/matrixeval/ruby/docker_compose/file.rb
|
88
|
+
- lib/matrixeval/ruby/extra_mount_files.rb
|
85
89
|
- lib/matrixeval/ruby/gemfile_locks.rb
|
86
90
|
- lib/matrixeval/ruby/gitignore.rb
|
87
91
|
- lib/matrixeval/ruby/runner.rb
|
data/Gemfile.lock
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
matrixeval-ruby (0.1.0)
|
5
|
-
concurrent-ruby
|
6
|
-
rainbow (~> 3.1)
|
7
|
-
terminal-table
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
byebug (11.1.3)
|
13
|
-
concurrent-ruby (1.1.9)
|
14
|
-
minitest (5.15.0)
|
15
|
-
minitest-focus (1.3.1)
|
16
|
-
minitest (>= 4, < 6)
|
17
|
-
mocha (1.13.0)
|
18
|
-
rainbow (3.1.1)
|
19
|
-
rake (13.0.6)
|
20
|
-
terminal-table (3.0.2)
|
21
|
-
unicode-display_width (>= 1.1.1, < 3)
|
22
|
-
unicode-display_width (2.1.0)
|
23
|
-
|
24
|
-
PLATFORMS
|
25
|
-
x86_64-darwin-19
|
26
|
-
x86_64-linux
|
27
|
-
|
28
|
-
DEPENDENCIES
|
29
|
-
byebug
|
30
|
-
matrixeval-ruby!
|
31
|
-
minitest (~> 5.0)
|
32
|
-
minitest-focus
|
33
|
-
mocha
|
34
|
-
rake (~> 13.0)
|
35
|
-
|
36
|
-
BUNDLED WITH
|
37
|
-
2.2.32
|
@@ -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.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
|
-
},
|
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
|