matrixeval-ruby 0.3.1 → 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.
@@ -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,86 +0,0 @@
1
- require_relative "./context/find_by_command_options"
2
- require_relative "./context/build_docker_compose_extend"
3
-
4
- module Matrixeval
5
- module Ruby
6
- class Context
7
-
8
- class << self
9
-
10
- def find_by_command_options!(options)
11
- FindByCommandOptions.call(options)
12
- end
13
-
14
- def all
15
- Config.variant_combinations.map do |variants|
16
- Context.new(
17
- main_variant: variants.find { |v| v.vector.main? },
18
- rest_variants: variants.reject { |v| v.vector.main? }
19
- )
20
- end.select do |context|
21
- Config.exclusions.none? do |exclusion|
22
- context.match_exclusion?(exclusion)
23
- end
24
- end
25
- end
26
-
27
- end
28
-
29
- attr_reader :main_variant, :rest_variants
30
-
31
- def initialize(main_variant:, rest_variants:)
32
- @main_variant = main_variant
33
- @rest_variants = (rest_variants || []).sort do |v1, v2|
34
- v1.id <=> v2.id
35
- end
36
- end
37
-
38
- def name
39
- variants.map(&:name).join(", ")
40
- end
41
-
42
- def id
43
- [[main_variant.id] + rest_variants.map(&:id)].join("_")
44
- end
45
-
46
- def env
47
- rest_variants.map(&:env).reduce({}, &:merge)
48
- .merge(main_variant.env)
49
- end
50
-
51
- def docker_compose_service_name
52
- main_variant.id
53
- end
54
-
55
- def gemfile_lock_path
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")
61
- end
62
-
63
- def variants
64
- [main_variant] + rest_variants
65
- end
66
-
67
- def match_exclusion?(exclusion)
68
- return false if exclusion.empty?
69
-
70
- variants.all? do |variant|
71
- vector_key = variant.vector.key
72
- if exclusion.key?(vector_key)
73
- exclusion[vector_key].to_s == variant.key
74
- else
75
- true
76
- end
77
- end
78
- end
79
-
80
- def docker_compose_extend
81
- BuildDockerComposeExtend.call(self)
82
- end
83
-
84
- end
85
- end
86
- end
@@ -1,21 +0,0 @@
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
@@ -1,19 +0,0 @@
1
- require 'json'
2
-
3
- module Matrixeval
4
- module Ruby
5
- class DockerCompose
6
- class ExtendRaw
7
-
8
- def initialize(config)
9
- @config = config || {}
10
- end
11
-
12
- def content
13
- @config.to_json
14
- end
15
-
16
- end
17
- end
18
- end
19
- end
@@ -1,135 +0,0 @@
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,68 +0,0 @@
1
-
2
- require_relative "./docker_compose/file"
3
-
4
- module Matrixeval
5
- module Ruby
6
- class DockerCompose
7
-
8
- attr_reader :context
9
-
10
- def initialize(context)
11
- @context = context
12
- end
13
-
14
- def run(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'
20
-
21
- system(
22
- <<~DOCKER_COMPOSE_COMMAND
23
- #{docker_compose} \
24
- run --rm \
25
- #{no_tty} \
26
- #{context.docker_compose_service_name} \
27
- #{forward_arguments}
28
- DOCKER_COMPOSE_COMMAND
29
- )
30
- ensure
31
- stop_containers
32
- clean_containers_and_anonymous_volumes
33
- turn_on_stty_opost
34
- end
35
-
36
- private
37
-
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"
56
- end
57
-
58
- def turn_on_stty_opost
59
- system("stty opost")
60
- end
61
-
62
- def project_name
63
- Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
64
- end
65
-
66
- end
67
- end
68
- end
@@ -1,23 +0,0 @@
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
@@ -1,23 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class GemfileLocks
4
- class << self
5
-
6
- def create
7
- FileUtils.mkdir_p gemfile_lock_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 gemfile_lock_folder
17
- Matrixeval.working_dir.join(".matrixeval/gemfile_locks")
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
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)
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
40
- ".matrixeval/docker-compose"
41
- end
42
-
43
- def gemfile_locks
44
- ".matrixeval/gemfile_locks"
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,205 +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
- end
49
-
50
- private
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
-
62
- def init
63
- Config::YAML.create
64
- Gitignore.update
65
- end
66
-
67
- def run_all_contexts
68
- Config::YAML.create
69
- DockerCompose::File.create_all
70
- GemfileLocks.create
71
- Gitignore.update
72
- ExtraMountFiles.create
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::File.create_all
115
- GemfileLocks.create
116
- Gitignore.update
117
- ExtraMountFiles.create
118
-
119
- context = Context.find_by_command_options!(command.context_options)
120
-
121
- puts Rainbow("[ MatrixEval ] ").blue.bright + Rainbow(" #{context.name} ").white.bright.bg(:blue)
122
- puts Rainbow("[ MatrixEval ] Run \"#{command.rest_arguments.join(" ")}\"").blue.bright
123
-
124
- docker_compose = DockerCompose.new(context)
125
- docker_compose.run(command.rest_arguments)
126
- end
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
-
140
- def report
141
- turn_on_stty_opost
142
-
143
- table = Terminal::Table.new(title: Rainbow("MatrixEval").blue.bright + " Summary", alignment: :center) do |table|
144
-
145
- headers = Config.vectors.map(&:key) + ['result']
146
- table.add_row headers.map { |value| { value: value, alignment: :center } }
147
- table.add_separator
148
-
149
- @matrixeval_results.each do |context, success|
150
- success_cell = [success ? Rainbow('Success').green : Rainbow('Failed').red]
151
- row = (context.variants.map(&:key) + success_cell).map do |value|
152
- { value: value, alignment: :center }
153
- end
154
-
155
- table.add_row row
156
- end
157
-
158
- end
159
-
160
- puts table
161
- end
162
-
163
- def parallel(collection)
164
- @threads = [] unless @threads.empty?
165
- @matrixeval_results = [] unless @matrixeval_results.empty?
166
-
167
- collection.each_slice(per_worker_contexts_count) do |sub_collection|
168
- @threads << Thread.new do
169
- yield sub_collection
170
- end
171
- end
172
-
173
- @threads.each(&:join)
174
-
175
- @threads.each do |thread|
176
- @matrixeval_results += (thread[:matrixeval_results] || [])
177
- end
178
- end
179
-
180
-
181
- def per_worker_contexts_count
182
- [(contexts.count / workers_count), 1].max
183
- end
184
-
185
- def contexts
186
- @contexts ||= Context.all
187
- end
188
-
189
- def workers_count
190
- count = if Config.parallel_workers == "number_of_processors"
191
- Concurrent.physical_processor_count
192
- else
193
- Integer(Config.parallel_workers)
194
- end
195
-
196
- [count, 1].max
197
- end
198
-
199
- def turn_on_stty_opost
200
- system("stty opost")
201
- end
202
-
203
- end
204
- end
205
- end