turbo_test 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7cfabcac4630bd19d2529fa63dc7022e4c7dddf518afe98284a838c10ec0e0cf
4
+ data.tar.gz: 23941350f74476d097e5e4af2c65d877265c6a6b5b32911f68b6b8aae76230a8
5
+ SHA512:
6
+ metadata.gz: 5430e363ce617c8d0581a6c2cceed6477c63eed7234636ed031a97447ff9f512d53d269be17df4ff9e9a139493cd67d41da2f731a659f59838ddba98bd8396dd
7
+ data.tar.gz: 316141c95de82103149a2d57e532e08f252797e8f4953c2493020906b77cf8c252d20252dcfaccea1df1c9145407848611db0c358ab5baa430d867cf42d115d7
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative '../lib/turbo_test/command'
24
+
25
+ begin
26
+ TurboTest::Command.call
27
+ rescue Interrupt
28
+ # Ignore.
29
+ rescue => error
30
+ Async.logger.error(TurboTest::Command) {error}
31
+ exit! 1
32
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative 'turbo_test/version'
24
+
25
+ require_relative 'turbo_test/server'
26
+ require_relative 'turbo_test/rspec/job'
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative 'command/top'
24
+
25
+ module TurboTest
26
+ module Command
27
+ # The main entry point for the `falcon` executable.
28
+ # @parameter arguments [Array(String)] The command line arguments.
29
+ def self.call(*arguments)
30
+ Top.call(*arguments)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'samovar'
24
+
25
+ require_relative '../server'
26
+ require_relative '../configuration'
27
+ require_relative '../rspec/job'
28
+
29
+ require 'bundler'
30
+
31
+ module TurboTest
32
+ module Command
33
+ class Run < Samovar::Command
34
+ self.description = "Runs tests using a distributed fan-out queue."
35
+
36
+ # The command line options.
37
+ # @attribute [Samovar::Options]
38
+ options do
39
+ option '-n/--count <count>', "Number of instances to start.", default: Async::Container.processor_count, type: Integer
40
+
41
+ option '-c/--configuration', "The configuration path to use.", default: "turbo_test.rb"
42
+ end
43
+
44
+ many :paths, "The test paths to execute."
45
+
46
+ # Prepare the environment and run the controller.
47
+ def call
48
+ Async.logger.info(self) do |buffer|
49
+ buffer.puts "TurboTest v#{VERSION} preparing for maximum thrust!"
50
+ end
51
+
52
+ path = @options[:configuration]
53
+ full_path = File.expand_path(path)
54
+
55
+ if File.exist?(full_path)
56
+ configuration = Configuration.load(full_path)
57
+ end
58
+
59
+ Bundler.require(:preload)
60
+
61
+ if GC.respond_to?(:compact)
62
+ GC.compact
63
+ end
64
+
65
+ server = Server.new(configuration)
66
+
67
+ queue = paths.map do |path|
68
+ [RSpec::Job, path]
69
+ end
70
+
71
+ results = server.host(queue)
72
+
73
+ server.workers
74
+ server.wait
75
+
76
+ return results.read
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require_relative 'run'
24
+ require_relative '../version'
25
+
26
+ require 'samovar'
27
+
28
+ module TurboTest
29
+ module Command
30
+ # The top level command for the `falcon` executable.
31
+ class Top < Samovar::Command
32
+ self.description = "A parallel test runner."
33
+
34
+ # The command line options.
35
+ # @attribute [Samovar::Options]
36
+ options do
37
+ option '-h/--help', "Print out help information."
38
+ option '-v/--version', "Print out the application version."
39
+ end
40
+
41
+ # The nested command to execute.
42
+ # @name nested
43
+ # @attribute [Command]
44
+ nested :command, {
45
+ 'run' => Run,
46
+ }, default: 'run'
47
+
48
+ # Prepare the environment and invoke the sub-command.
49
+ def call
50
+ if @options[:version]
51
+ puts "#{self.name} v#{VERSION}"
52
+ elsif @options[:help]
53
+ self.print_usage
54
+ else
55
+ @command.call
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module TurboTest
24
+ class Configuration
25
+ def initialize
26
+ @worker = nil
27
+ end
28
+
29
+ attr_accessor :worker
30
+
31
+ def self.load(path)
32
+ configuration = self.new
33
+
34
+ loader = Loader.new(configuration)
35
+ loader.instance_eval(File.read(path), path.to_s)
36
+
37
+ return configuration
38
+ end
39
+
40
+ class Loader
41
+ def initialize(configuration)
42
+ @configuration = configuration
43
+ end
44
+
45
+ def worker(&block)
46
+ @configuration.worker = block
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'rspec/support'
24
+ require 'rspec/core/formatters'
25
+
26
+ module TurboTest
27
+ module RSpec
28
+ class ExampleFormatter
29
+ ::RSpec::Core::Formatters.register self, :example_finished, :example_failed, :dump_summary
30
+
31
+ def initialize(packer)
32
+ @packer = packer
33
+
34
+ @colorizer = ::RSpec::Core::Formatters::ConsoleCodes
35
+ end
36
+
37
+ def output
38
+ @packer
39
+ end
40
+
41
+ def example_finished(notification)
42
+ @packer.write([:finished, notification.example.id])
43
+ @packer.flush
44
+ end
45
+
46
+ def example_failed(notification)
47
+ example = notification.example
48
+
49
+ presenter = ::RSpec::Core::Formatters::ExceptionPresenter.new(example.exception, example)
50
+
51
+ message = {
52
+ description: example.full_description,
53
+ location: example.location_rerun_argument,
54
+ report: presenter.fully_formatted(nil, @colorizer),
55
+ }
56
+
57
+ @packer.write([:failed, message])
58
+ @packer.flush
59
+ end
60
+
61
+ def dump_summary(summary)
62
+ count = summary.examples.count
63
+
64
+ @packer.write([:count, count])
65
+ @packer.flush
66
+ end
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'rspec/core'
24
+
25
+ require_relative 'example_formatter'
26
+
27
+ module TurboTest
28
+ module RSpec
29
+ class ConfigurationOptions < ::RSpec::Core::ConfigurationOptions
30
+ def initialize(arguments, packer:)
31
+ super(arguments)
32
+
33
+ @packer = packer
34
+ end
35
+
36
+ def configure(config)
37
+ super(config)
38
+
39
+ config.add_formatter(ExampleFormatter.new(@packer))
40
+ end
41
+
42
+ def load_formatters_into(config)
43
+ # Don't load any formatters, default or otherwise.
44
+ end
45
+ end
46
+
47
+ class Job
48
+ def initialize(path)
49
+ @path = path
50
+ end
51
+
52
+ def call(packer:, stdout: $stdout, stderr: $stderr)
53
+ reset_rspec_state!
54
+
55
+ options = ConfigurationOptions.new([@path],
56
+ packer: packer
57
+ )
58
+
59
+ runner = ::RSpec::Core::Runner.new(options)
60
+
61
+ runner.run(stdout, stderr)
62
+ end
63
+
64
+ private
65
+
66
+ def reset_rspec_state!
67
+ ::RSpec.clear_examples
68
+
69
+ # see https://github.com/rspec/rspec-core/pull/2723
70
+ if Gem::Version.new(::RSpec::Core::Version::STRING) <= Gem::Version.new("3.9.1")
71
+ ::RSpec.world.instance_variable_set(
72
+ :@example_group_counts_by_spec_file, Hash.new(0)
73
+ )
74
+ end
75
+
76
+ # RSpec.clear_examples does not reset those, which causes issues when
77
+ # a non-example error occurs (subsequent jobs are not executed)
78
+ # TODO: upstream
79
+ ::RSpec.world.non_example_failure = false
80
+
81
+ # we don't want an error that occured outside of the examples (which
82
+ # would set this to `true`) to stop the worker
83
+ ::RSpec.world.wants_to_quit = false
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'async'
24
+ require 'async/container'
25
+ require 'async/io/unix_endpoint'
26
+ require 'async/io/shared_endpoint'
27
+ require 'msgpack'
28
+
29
+ module TurboTest
30
+ class Wrapper < MessagePack::Factory
31
+ def initialize
32
+ super()
33
+
34
+ # self.register_type(0x00, Object, packer: @bus.method(:temporary), unpacker: @bus.method(:[]))
35
+
36
+ self.register_type(0x01, Symbol)
37
+ self.register_type(0x02, Exception,
38
+ packer: ->(exception){Marshal.dump(exception)},
39
+ unpacker: ->(data){Marshal.load(data)},
40
+ )
41
+
42
+ self.register_type(0x03, Class,
43
+ packer: ->(klass){Marshal.dump(klass)},
44
+ unpacker: ->(data){Marshal.load(data)},
45
+ )
46
+ end
47
+ end
48
+
49
+ class Server
50
+ def initialize(configuration, endpoint = nil)
51
+ @configuration = configuration
52
+ @endpoint = endpoint || Async::IO::Endpoint.unix('turbo_test.ipc')
53
+ @wrapper = Wrapper.new
54
+
55
+ @container = Async::Container.new
56
+
57
+ @bound_endpoint = Sync do
58
+ Async::IO::SharedEndpoint.bound(@endpoint)
59
+ end
60
+ end
61
+
62
+ def host(queue)
63
+ input, output = IO.pipe
64
+
65
+ @container.spawn(name: "#{self.class} Host") do |instance|
66
+ connected = 0
67
+ progress = Console.logger.progress("Queue", queue.size)
68
+ failures = []
69
+
70
+ statistics = {
71
+ succeeded: 0,
72
+ failed: 0,
73
+ }
74
+
75
+ Async do |task|
76
+ instance.ready!
77
+
78
+ @bound_endpoint.accept do |peer|
79
+ # Console.logger.info(self) {"Incoming connection from #{peer}..."}
80
+
81
+ packer = @wrapper.packer(peer)
82
+ unpacker = @wrapper.unpacker(peer)
83
+
84
+ packer.write([:connected, connected])
85
+ connected += 1
86
+
87
+ unpacker.each do |message|
88
+ command, *arguments = message
89
+
90
+ case command
91
+ when :ready
92
+ Console.logger.debug("Child Ready") {arguments}
93
+
94
+ if job = queue.pop
95
+ packer.write([:job, job])
96
+ packer.flush
97
+ else
98
+ Console.logger.debug("Child Closed")
99
+ peer.close_write
100
+ connected -= 1
101
+
102
+ if connected.zero?
103
+ print_summary(failures)
104
+ task.stop
105
+ end
106
+ end
107
+ when :finished
108
+ Console.logger.debug("Job Finished") {arguments}
109
+ when :failed
110
+ Console.logger.debug("Job Failed") {arguments}
111
+ failures << arguments
112
+ statistics[:failed] += 1
113
+ when :count
114
+ Console.logger.debug("Job Count") {arguments}
115
+ statistics[:succeeded] += arguments.first
116
+ when :result
117
+ Console.logger.debug("Job Result") {arguments}
118
+ progress.increment
119
+ when :error
120
+ Console.logger.error("Job Error") {arguments}
121
+ else
122
+ Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"}
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ ensure
129
+ Console.logger.info("Writing results")
130
+ @wrapper.packer(output).write(statistics).flush
131
+ end
132
+
133
+ output.close
134
+
135
+ return @wrapper.unpacker(input)
136
+ end
137
+
138
+ def print_summary(failures, command = $0)
139
+ return unless failures.any?
140
+
141
+ failures.sort_by!{|(failure)| failure[:location]}
142
+
143
+ $stderr.puts nil, "Failures:", nil
144
+
145
+ failures.each do |(failure)|
146
+ $stderr.puts failure[:report]
147
+ $stderr.puts
148
+ end
149
+
150
+ $stderr.puts nil, "Summary:", nil
151
+
152
+ failures.each do |(failure)|
153
+ $stderr.puts "#{command} #{failure[:location]} \# #{failure[:description]}"
154
+ end
155
+
156
+ $stderr.puts
157
+ end
158
+
159
+ def workers
160
+ @container.run(name: "#{self.class} Worker") do |instance|
161
+ Async do |task|
162
+ @endpoint.connect do |peer|
163
+ instance.ready!
164
+
165
+ packer = @wrapper.packer(peer)
166
+ unpacker = @wrapper.unpacker(peer)
167
+
168
+ packer.write([:ready])
169
+ packer.flush
170
+
171
+ unpacker.each do |message|
172
+ command, tail = message
173
+
174
+ case command
175
+ when :connected
176
+ @configuration&.worker&.call(*tail)
177
+ when :job
178
+ klass, *arguments = *tail
179
+
180
+ begin
181
+ result = klass.new(*arguments).call(packer: packer)
182
+ packer.write([:result, result])
183
+ rescue Exception => exception
184
+ packer.write([:error, exception, exception.backtrace])
185
+ end
186
+
187
+ packer.write([:ready])
188
+ packer.flush
189
+ else
190
+ Console.logger.warn(self) {"Unhandled command: #{command}#{arguments.inspect}"}
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ def wait
199
+ @container.wait
200
+ @bound_endpoint.close
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ module TurboTest
24
+ VERSION = "0.1.0"
25
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turbo_test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async-container
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async-io
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: msgpack
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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: samovar
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ executables:
86
+ - turbo_test
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - bin/turbo_test
91
+ - lib/turbo_test.rb
92
+ - lib/turbo_test/command.rb
93
+ - lib/turbo_test/command/run.rb
94
+ - lib/turbo_test/command/top.rb
95
+ - lib/turbo_test/configuration.rb
96
+ - lib/turbo_test/rspec/example_formatter.rb
97
+ - lib/turbo_test/rspec/job.rb
98
+ - lib/turbo_test/server.rb
99
+ - lib/turbo_test/version.rb
100
+ homepage: https://github.com/ioquatix/turbo_test
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ funding_uri: https://github.com/sponsors/ioquatix/
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.3.0
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.0.3
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Press the turbo button... for your tests.
124
+ test_files: []