flatware 0.4.0 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +40 -42
- data/bin/flatware +2 -2
- data/lib/flatware.rb +33 -3
- data/lib/flatware/broadcaster.rb +20 -3
- data/lib/flatware/cli.rb +45 -21
- data/lib/flatware/configuration.rb +40 -0
- data/lib/flatware/job.rb +22 -0
- data/lib/flatware/pid.rb +41 -0
- data/lib/flatware/serialized_exception.rb +16 -4
- data/lib/flatware/sink.rb +97 -65
- data/lib/flatware/sink/client.rb +7 -5
- data/lib/flatware/version.rb +3 -1
- data/lib/flatware/worker.rb +50 -31
- metadata +21 -82
- data/lib/flatware-cucumber.rb +0 -1
- data/lib/flatware-rspec.rb +0 -1
- data/lib/flatware/cucumber.rb +0 -51
- data/lib/flatware/cucumber/checkpoint.rb +0 -28
- data/lib/flatware/cucumber/cli.rb +0 -22
- data/lib/flatware/cucumber/formatter.rb +0 -66
- data/lib/flatware/cucumber/formatters/console.rb +0 -48
- data/lib/flatware/cucumber/formatters/console/summary.rb +0 -66
- data/lib/flatware/cucumber/result.rb +0 -27
- data/lib/flatware/cucumber/runtime.rb +0 -36
- data/lib/flatware/cucumber/scenario_result.rb +0 -38
- data/lib/flatware/cucumber/step_result.rb +0 -30
- data/lib/flatware/pids.rb +0 -25
- data/lib/flatware/poller.rb +0 -35
- data/lib/flatware/processor_info.rb +0 -24
- data/lib/flatware/rspec.rb +0 -28
- data/lib/flatware/rspec/checkpoint.rb +0 -29
- data/lib/flatware/rspec/cli.rb +0 -16
- data/lib/flatware/rspec/example_notification.rb +0 -21
- data/lib/flatware/rspec/examples_notification.rb +0 -24
- data/lib/flatware/rspec/formatter.rb +0 -50
- data/lib/flatware/rspec/formatters/console.rb +0 -33
- data/lib/flatware/rspec/summary.rb +0 -40
- data/lib/flatware/socket.rb +0 -181
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 944469a1460443f84117dcdacbe79ec133c7605e83cc2b2882faaf573661a458
|
4
|
+
data.tar.gz: cc120e2ad04cf8baabf001921f16a03c67f15dd81884a6df83d097c06af420ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80d9ae698fd2f8c9c01c78e09d65fa36a1989b6bbdbee62cac5b74c5d69003734025cec6862f89415ca1e79ced1fbce97a17d37b31238f617b5d4fc5e160e74f
|
7
|
+
data.tar.gz: 35a22fe1de1df1a761ef16114729d10e51ddd797c3a7590293d49465f9ab9e86a77e22572d89abb4b603e56093da0d2ec8b1dccccd4c7cbc666339aa693d987d
|
data/README.md
CHANGED
@@ -7,39 +7,13 @@
|
|
7
7
|
|
8
8
|
Flatware parallelizes your test suite to significantly reduce test time.
|
9
9
|
|
10
|
-
## Requirements
|
11
|
-
|
12
|
-
* ZeroMQ > 4.0
|
13
|
-
|
14
|
-
## Installation
|
15
|
-
|
16
|
-
### ZeroMQ
|
17
|
-
|
18
|
-
#### Linux Ubuntu
|
19
|
-
|
20
|
-
```sh
|
21
|
-
sudo apt-get install -qq libzmq3-dev
|
22
|
-
```
|
23
|
-
|
24
|
-
(Never you mind the 3. This package contains ZMQ version 4.)
|
25
|
-
|
26
|
-
#### Mac OSX
|
27
|
-
|
28
|
-
Ruby FFI isn't getting along with the latest ZMQ formula. A tweaked verson is available in the Hashrocket tap.
|
29
|
-
|
30
|
-
```sh
|
31
|
-
brew tap hashrocket/formulas
|
32
|
-
brew install hashrocket/formulas/zeromq
|
33
|
-
brew install zeromq
|
34
|
-
```
|
35
|
-
|
36
10
|
### Flatware
|
37
11
|
|
38
12
|
Add the runners you need to your Gemfile:
|
39
13
|
|
40
14
|
```ruby
|
41
|
-
gem 'flatware-rspec' # one
|
42
|
-
gem 'flatware-cucumber' # or both
|
15
|
+
gem 'flatware-rspec', require: false # one
|
16
|
+
gem 'flatware-cucumber', require: false # or both
|
43
17
|
```
|
44
18
|
|
45
19
|
then run
|
@@ -66,6 +40,18 @@ To run your entire suite with the default rspec options add the `flatware-rspec`
|
|
66
40
|
$ flatware rspec
|
67
41
|
```
|
68
42
|
|
43
|
+
The rspec runner can balance worker loads, making your suite even faster.
|
44
|
+
|
45
|
+
It forms balaced groups of spec files according to their last run times, if you've set `example_status_persistence_file_path` [in your RSpec config](https://relishapp.com/rspec/rspec-core/v/3-8/docs/command-line/only-failures).
|
46
|
+
|
47
|
+
For this to work the configuration option must be loaded before any specs are run. The `.rspec` file is one way to achive this:
|
48
|
+
|
49
|
+
--require spec_helper
|
50
|
+
|
51
|
+
But beware, if you're using ActiveRecord in your suite you'll need to avoid doing things that cause it to establish a database connection in `spec_helper.rb`. If ActiveRecord connects before flatware forks off workers, each will die messily. All of this will just work if you're following [the recomended pattern of splitting your helpers into `spec_helper` and `rails_helper`](https://github.com/rspec/rspec-rails/blob/v3.8.2/lib/generators/rspec/install/templates/spec/rails_helper.rb). Another option is to use [the configurable hooks](
|
52
|
+
#faster-startup-with-activerecord
|
53
|
+
).
|
54
|
+
|
69
55
|
### Options
|
70
56
|
|
71
57
|
If you'd like to limit the number of forked workers, you can pass the 'w' flag:
|
@@ -78,7 +64,7 @@ You can also pass most cucumber/rspec options to Flatware. For example, to run o
|
|
78
64
|
features that are not tagged 'javascript', you can:
|
79
65
|
|
80
66
|
```sh
|
81
|
-
$ flatware cucumber -t
|
67
|
+
$ flatware cucumber -t 'not @javascript'
|
82
68
|
```
|
83
69
|
|
84
70
|
Additionally, for either cucumber or rspec you can specify a directory:
|
@@ -116,9 +102,31 @@ Now you are ready to rock:
|
|
116
102
|
$ flatware rspec && flatware cucumber
|
117
103
|
```
|
118
104
|
|
119
|
-
|
105
|
+
### Faster Startup With ActiveRecord
|
120
106
|
|
121
|
-
|
107
|
+
Flatware has a couple lifecycle callbacks that you can use to avoid booting your app
|
108
|
+
over again on every core. One way to take advantage of this via a `spec/flatware_helper.rb` file like so:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
Flatware.configure do |conf|
|
112
|
+
conf.before_fork do
|
113
|
+
require 'rails_helper'
|
114
|
+
|
115
|
+
ActiveRecord::Base.connection.disconnect!
|
116
|
+
end
|
117
|
+
|
118
|
+
conf.after_fork do |test_env_number|
|
119
|
+
config = ActiveRecord::Base.connection_config
|
120
|
+
|
121
|
+
ActiveRecord::Base.establish_connection(
|
122
|
+
config.merge(
|
123
|
+
database: config.fetch(:database) + test_env_number.to_s
|
124
|
+
)
|
125
|
+
)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
```
|
129
|
+
Now when I run `bundle exec flatware rspec -r ./spec/flatware_helper` My app only boots once, rather than once per core.
|
122
130
|
|
123
131
|
## Design Goals
|
124
132
|
|
@@ -149,20 +157,10 @@ directory. CD there and `flatware` will be in your path so you can tinker away.
|
|
149
157
|
|
150
158
|
## How it works
|
151
159
|
|
152
|
-
Flatware relies on a message passing system to enable concurrency.
|
153
|
-
The main process declares a worker for each cpu in the computer. Each
|
154
|
-
worker forks from the main process and is then assigned a portion of the
|
155
|
-
test suite. As the worker runs the test suite it sends progress
|
156
|
-
messages to the main process. These messages are collected and when
|
157
|
-
the last worker is finished the main process provides a report on the
|
158
|
-
collected progress messages.
|
160
|
+
Flatware relies on a message passing system to enable concurrency. The main process forks a worker for each cpu in the computer. These workers are each given a chunk of the tests to run. The workers report back to the main process about their progress. The main process prints those progress messages. When the last worker is finished the main process prints the results.
|
159
161
|
|
160
162
|
## Resources
|
161
163
|
|
162
|
-
To learn more about the messaging system that Flatware uses, take a look at the
|
163
|
-
[excellent ZeroMQ guide][z].
|
164
|
-
|
165
|
-
[z]: http://zguide.zeromq.org/page:all
|
166
164
|
[a]: https://github.com/cucumber/aruba
|
167
165
|
|
168
166
|
## Contributing to Flatware
|
data/bin/flatware
CHANGED
data/lib/flatware.rb
CHANGED
@@ -1,9 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
1
5
|
module Flatware
|
2
|
-
require 'flatware/
|
6
|
+
require 'flatware/job'
|
3
7
|
require 'flatware/cli'
|
4
|
-
require 'flatware/poller'
|
5
8
|
require 'flatware/sink'
|
6
|
-
require 'flatware/socket'
|
7
9
|
require 'flatware/worker'
|
8
10
|
require 'flatware/broadcaster'
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
def logger
|
15
|
+
@logger ||= Logger.new($stderr, level: :fatal)
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger=(logger)
|
19
|
+
@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def log(*message)
|
23
|
+
case message.first
|
24
|
+
when Exception
|
25
|
+
logger.error message.first
|
26
|
+
else
|
27
|
+
logger.info(([$PROGRAM_NAME] + message).join(' '))
|
28
|
+
end
|
29
|
+
message
|
30
|
+
end
|
31
|
+
|
32
|
+
def verbose=(bool)
|
33
|
+
logger.level = bool ? :debug : :fatal
|
34
|
+
end
|
35
|
+
|
36
|
+
def verbose?
|
37
|
+
logger.level < Logger::SEV_LABEL.index('FATAL')
|
38
|
+
end
|
9
39
|
end
|
data/lib/flatware/broadcaster.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Flatware
|
4
|
+
# sends messages to all formatters
|
2
5
|
class Broadcaster
|
6
|
+
FORMATTER_MESSAGES = %i[
|
7
|
+
jobs
|
8
|
+
started
|
9
|
+
progress
|
10
|
+
finished
|
11
|
+
summarize
|
12
|
+
summarize_remaining
|
13
|
+
].freeze
|
14
|
+
|
3
15
|
attr_reader :formatters
|
4
16
|
|
5
17
|
def initialize(formatters)
|
@@ -7,9 +19,14 @@ module Flatware
|
|
7
19
|
end
|
8
20
|
|
9
21
|
def method_missing(name, *args)
|
10
|
-
|
11
|
-
|
12
|
-
|
22
|
+
return super unless FORMATTER_MESSAGES.include? name
|
23
|
+
|
24
|
+
formatters.select { |formatter| formatter.respond_to? name }
|
25
|
+
.each { |formatter| formatter.send name, *args }
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(name, _include_all)
|
29
|
+
FORMATTER_MESSAGES.include? name
|
13
30
|
end
|
14
31
|
end
|
15
32
|
end
|
data/lib/flatware/cli.rb
CHANGED
@@ -1,37 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thor'
|
2
|
-
require 'flatware/
|
4
|
+
require 'flatware/pid'
|
5
|
+
require 'etc'
|
6
|
+
|
3
7
|
module Flatware
|
8
|
+
# shared flatware cli
|
4
9
|
class CLI < Thor
|
5
|
-
|
6
10
|
def self.processors
|
7
|
-
@processors ||=
|
11
|
+
@processors ||= Etc.nprocessors
|
8
12
|
end
|
9
13
|
|
10
14
|
def self.worker_option
|
11
|
-
method_option
|
15
|
+
method_option(
|
16
|
+
:workers,
|
17
|
+
aliases: '-w',
|
18
|
+
type: :numeric,
|
19
|
+
default: processors,
|
20
|
+
desc: 'Number of concurent processes to run'
|
21
|
+
)
|
12
22
|
end
|
13
23
|
|
14
|
-
class_option
|
24
|
+
class_option(
|
25
|
+
:log,
|
26
|
+
aliases: '-l',
|
27
|
+
type: :boolean,
|
28
|
+
desc: 'Print debug messages to $stderr'
|
29
|
+
)
|
15
30
|
|
16
31
|
worker_option
|
17
|
-
desc
|
32
|
+
desc 'fan [COMMAND]', 'executes the given job on all of the workers'
|
18
33
|
def fan(*command)
|
19
34
|
Flatware.verbose = options[:log]
|
20
35
|
|
21
|
-
command = command.join(
|
36
|
+
command = command.join(' ')
|
22
37
|
puts "Running '#{command}' on #{workers} workers"
|
23
38
|
|
24
39
|
workers.times do |i|
|
25
40
|
fork do
|
26
|
-
exec({
|
41
|
+
exec({ 'TEST_ENV_NUMBER' => i.to_s }, command)
|
27
42
|
end
|
28
43
|
end
|
29
44
|
Process.waitall
|
30
45
|
end
|
31
46
|
|
32
|
-
desc
|
47
|
+
desc 'clear', 'kills all flatware processes'
|
33
48
|
def clear
|
34
|
-
(Flatware.pids - [
|
49
|
+
(Flatware.pids - [Process.pid]).each do |pid|
|
35
50
|
Process.kill 6, pid
|
36
51
|
end
|
37
52
|
end
|
@@ -39,26 +54,35 @@ module Flatware
|
|
39
54
|
private
|
40
55
|
|
41
56
|
def start_sink(jobs:, workers:, formatter:)
|
42
|
-
|
57
|
+
$0 = 'flatware sink'
|
43
58
|
Process.setpgrp
|
44
|
-
passed = Sink.start_server
|
59
|
+
passed = Sink.start_server(
|
60
|
+
jobs: jobs,
|
61
|
+
formatter: Flatware::Broadcaster.new([formatter]),
|
62
|
+
sink: options['sink-endpoint'],
|
63
|
+
worker_count: workers
|
64
|
+
)
|
45
65
|
exit passed ? 0 : 1
|
46
66
|
end
|
47
67
|
|
48
|
-
def log(*args)
|
49
|
-
Flatware.log(*args)
|
50
|
-
end
|
51
|
-
|
52
68
|
def workers
|
53
69
|
options[:workers]
|
54
70
|
end
|
55
71
|
end
|
56
72
|
end
|
57
73
|
|
58
|
-
flatware_gems =
|
74
|
+
flatware_gems = %w[flatware-rspec flatware-cucumber]
|
75
|
+
|
76
|
+
loaded_flatware_gem_count = flatware_gems.map do |flatware_gem|
|
77
|
+
require flatware_gem
|
78
|
+
rescue LoadError
|
79
|
+
nil
|
80
|
+
end.compact.size
|
59
81
|
|
60
|
-
if
|
61
|
-
|
62
|
-
|
63
|
-
|
82
|
+
if loaded_flatware_gem_count.zero?
|
83
|
+
warn(
|
84
|
+
format(<<~MESSAGE, gem_list: flatware_gems.join(' or ')))
|
85
|
+
The flatware gem is a dependency of flatware runners for rspec and cucumber.
|
86
|
+
Install %<gem_list>s for more usefull commands.
|
87
|
+
MESSAGE
|
64
88
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flatware
|
4
|
+
class Configuration
|
5
|
+
def initialize
|
6
|
+
reset!
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_fork(&block)
|
10
|
+
if block_given?
|
11
|
+
@before_fork = block
|
12
|
+
else
|
13
|
+
@before_fork
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_fork(&block)
|
18
|
+
if block_given?
|
19
|
+
@after_fork = block
|
20
|
+
else
|
21
|
+
@after_fork
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset!
|
26
|
+
@before_fork = -> {}
|
27
|
+
@after_fork = ->(_) {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module_function
|
32
|
+
|
33
|
+
def configuration
|
34
|
+
@configuration ||= Configuration.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def configure(&_block)
|
38
|
+
yield configuration
|
39
|
+
end
|
40
|
+
end
|
data/lib/flatware/job.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Flatware
|
2
|
+
Job = Struct.new :id, :args do
|
3
|
+
attr_accessor :worker
|
4
|
+
attr_writer :failed
|
5
|
+
|
6
|
+
def failed?
|
7
|
+
@failed == true
|
8
|
+
end
|
9
|
+
|
10
|
+
def failed!
|
11
|
+
@failed = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def sentinel?
|
15
|
+
id == 'seppuku'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.sentinel
|
19
|
+
new 'seppuku'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/flatware/pid.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'etc'
|
4
|
+
|
5
|
+
module Flatware
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# All the pids of all the processes called flatware on this machine
|
9
|
+
def pids
|
10
|
+
Pid.pids { |pid| pid.command =~ /flatware/ }
|
11
|
+
end
|
12
|
+
|
13
|
+
def pids_of_group(group_pid)
|
14
|
+
Pid.pids { |pid| pid.pgid == group_pid }
|
15
|
+
end
|
16
|
+
|
17
|
+
Pid = Struct.new(:pid, :pgid, :ppid, :command) do
|
18
|
+
def self.pids(&block)
|
19
|
+
ps.select(&block).map(&:pid)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.ps
|
23
|
+
args = ['-o', members.join(',')]
|
24
|
+
args += { 'Darwin' => %w[-c] }.fetch(Etc.uname.fetch(:sysname), [])
|
25
|
+
|
26
|
+
IO
|
27
|
+
.popen(['ps', *args])
|
28
|
+
.readlines
|
29
|
+
.map do |row|
|
30
|
+
fields = row.strip.split(/\s+/, 4)
|
31
|
+
new(*fields.take(3).map(&:to_i), fields.last)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def pids_of_group(group_pid)
|
36
|
+
ps
|
37
|
+
.select { |pid| pid.pgid == group_pid }
|
38
|
+
.map(&:pid)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -2,15 +2,25 @@ module Flatware
|
|
2
2
|
class SerializedException
|
3
3
|
attr_reader :class, :message, :cause
|
4
4
|
attr_accessor :backtrace
|
5
|
-
|
6
|
-
|
5
|
+
|
6
|
+
def initialize(klass, message, backtrace, cause = '')
|
7
|
+
@class = serialized(klass)
|
8
|
+
@message = message
|
9
|
+
@backtrace = backtrace
|
10
|
+
@cause = cause
|
7
11
|
end
|
8
12
|
|
9
13
|
def self.from(exception)
|
10
|
-
new
|
14
|
+
new(
|
15
|
+
exception.class,
|
16
|
+
exception.message,
|
17
|
+
exception.backtrace,
|
18
|
+
exception.cause
|
19
|
+
)
|
11
20
|
end
|
12
21
|
|
13
22
|
private
|
23
|
+
|
14
24
|
def serialized(klass)
|
15
25
|
SerializedClass.new(klass.to_s)
|
16
26
|
end
|
@@ -19,6 +29,8 @@ module Flatware
|
|
19
29
|
class SerializedClass
|
20
30
|
attr_reader :name
|
21
31
|
alias to_s name
|
22
|
-
def initialize(name)
|
32
|
+
def initialize(name)
|
33
|
+
@name = name
|
34
|
+
end
|
23
35
|
end
|
24
36
|
end
|