flatware 0.4.1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +38 -47
  3. data/bin/flatware +2 -2
  4. data/lib/flatware.rb +32 -3
  5. data/lib/flatware/broadcaster.rb +20 -3
  6. data/lib/flatware/cli.rb +45 -21
  7. data/lib/flatware/configuration.rb +40 -0
  8. data/lib/flatware/job.rb +13 -1
  9. data/lib/flatware/pid.rb +41 -0
  10. data/lib/flatware/serialized_exception.rb +16 -4
  11. data/lib/flatware/sink.rb +97 -65
  12. data/lib/flatware/sink/client.rb +7 -5
  13. data/lib/flatware/version.rb +3 -1
  14. data/lib/flatware/worker.rb +50 -31
  15. metadata +20 -81
  16. data/lib/flatware-cucumber.rb +0 -1
  17. data/lib/flatware-rspec.rb +0 -1
  18. data/lib/flatware/cucumber.rb +0 -50
  19. data/lib/flatware/cucumber/cli.rb +0 -22
  20. data/lib/flatware/cucumber/formatter.rb +0 -66
  21. data/lib/flatware/cucumber/formatters/console.rb +0 -48
  22. data/lib/flatware/cucumber/formatters/console/summary.rb +0 -65
  23. data/lib/flatware/cucumber/result.rb +0 -27
  24. data/lib/flatware/cucumber/runtime.rb +0 -36
  25. data/lib/flatware/cucumber/scenario_result.rb +0 -38
  26. data/lib/flatware/cucumber/step_result.rb +0 -30
  27. data/lib/flatware/pids.rb +0 -25
  28. data/lib/flatware/poller.rb +0 -35
  29. data/lib/flatware/processor_info.rb +0 -24
  30. data/lib/flatware/rspec.rb +0 -28
  31. data/lib/flatware/rspec/checkpoint.rb +0 -29
  32. data/lib/flatware/rspec/cli.rb +0 -16
  33. data/lib/flatware/rspec/example_notification.rb +0 -21
  34. data/lib/flatware/rspec/examples_notification.rb +0 -24
  35. data/lib/flatware/rspec/formatter.rb +0 -50
  36. data/lib/flatware/rspec/formatters/console.rb +0 -33
  37. data/lib/flatware/rspec/summary.rb +0 -26
  38. data/lib/flatware/socket.rb +0 -176
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 05e90320723eb9d0bcfb63d679ab20fefcf959d6
4
- data.tar.gz: 246b6941233445b4049b195841a09a312ab688a5
2
+ SHA256:
3
+ metadata.gz: d26c7dda07103572c9663bcd35269aae5067ae06920d895ac38d45d5c7b53c6b
4
+ data.tar.gz: 4276be012abcbcf23b00d3fbdaf64a428addb3418f0aaea4701c2df4cdc5252b
5
5
  SHA512:
6
- metadata.gz: 85b3f22abdd35c210aee7063073ac27b6fe77d8db1ac7488dea8ee62ea11fab16eccbbef10b57d049833ec3b49ba388a9719c003d9241287d0a03b7ebcf2992c
7
- data.tar.gz: 62e93337c7304ec941196dfc5d959e0bbc20acdda6dc45ea136b6748bc6cb61809b8e5943f842dfed521dd27b6fd9d6db4a7fc4e40cf2d8881ffb2f6911792a8
6
+ metadata.gz: 01c758894c82439dc3b11ddf3d312671658f49d9138ef59a5db0763859044c4813f6ee3d4af8fdb50d427f788ed716ff45caf333a7fef33dad71bd30572ed2bc
7
+ data.tar.gz: e9a7050fd1824937cc0b3bfbf3b27015957627b45d228847c237653527d768bc6e1a34bcae4c11a7c8d30c4a2f1413e50007928a6fd1fdd7eac2caeb566fb118
data/README.md CHANGED
@@ -7,39 +7,6 @@
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
-
29
- If you're on macOS 10.12, use the custom hashrocket ZMQ homebrew formula.
30
-
31
- ```sh
32
- brew tap hashrocket/formulas
33
- brew install hashrocket/formulas/zeromq
34
- ```
35
-
36
- The stock homebrew version will likely work on older versions of macOS.
37
-
38
-
39
- ```sh
40
- brew install zeromq
41
- ```
42
-
43
10
  ### Flatware
44
11
 
45
12
  Add the runners you need to your Gemfile:
@@ -73,6 +40,18 @@ To run your entire suite with the default rspec options add the `flatware-rspec`
73
40
  $ flatware rspec
74
41
  ```
75
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
+
76
55
  ### Options
77
56
 
78
57
  If you'd like to limit the number of forked workers, you can pass the 'w' flag:
@@ -85,7 +64,7 @@ You can also pass most cucumber/rspec options to Flatware. For example, to run o
85
64
  features that are not tagged 'javascript', you can:
86
65
 
87
66
  ```sh
88
- $ flatware cucumber -t ~@javascript
67
+ $ flatware cucumber -t 'not @javascript'
89
68
  ```
90
69
 
91
70
  Additionally, for either cucumber or rspec you can specify a directory:
@@ -123,9 +102,31 @@ Now you are ready to rock:
123
102
  $ flatware rspec && flatware cucumber
124
103
  ```
125
104
 
126
- ## Planned Features
105
+ ### Faster Startup With ActiveRecord
127
106
 
128
- * Use heuristics to run your slowest tests first
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.
129
130
 
130
131
  ## Design Goals
131
132
 
@@ -156,20 +157,10 @@ directory. CD there and `flatware` will be in your path so you can tinker away.
156
157
 
157
158
  ## How it works
158
159
 
159
- Flatware relies on a message passing system to enable concurrency.
160
- The main process declares a worker for each cpu in the computer. Each
161
- worker forks from the main process and is then assigned a portion of the
162
- test suite. As the worker runs the test suite it sends progress
163
- messages to the main process. These messages are collected and when
164
- the last worker is finished the main process provides a report on the
165
- 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.
166
161
 
167
162
  ## Resources
168
163
 
169
- To learn more about the messaging system that Flatware uses, take a look at the
170
- [excellent ZeroMQ guide][z].
171
-
172
- [z]: http://zguide.zeromq.org/page:all
173
164
  [a]: https://github.com/cucumber/aruba
174
165
 
175
166
  ## Contributing to Flatware
data/bin/flatware CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
- lib = File.expand_path('../../lib', __FILE__)
3
- $:.unshift lib unless $:.include? lib
2
+ lib = File.expand_path('../lib', __dir__)
3
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
4
4
 
5
5
  require 'flatware'
6
6
  Flatware::CLI.start
data/lib/flatware.rb CHANGED
@@ -1,10 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
1
5
  module Flatware
2
- require 'flatware/processor_info'
3
6
  require 'flatware/job'
4
7
  require 'flatware/cli'
5
- require 'flatware/poller'
6
8
  require 'flatware/sink'
7
- require 'flatware/socket'
8
9
  require 'flatware/worker'
9
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
10
39
  end
@@ -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
- formatters.each do |formatter|
11
- formatter.send name, *args if formatter.respond_to? name
12
- end
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/pids'
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 ||= ProcessorInfo.count
11
+ @processors ||= Etc.nprocessors
8
12
  end
9
13
 
10
14
  def self.worker_option
11
- method_option :workers, aliases: "-w", type: :numeric, default: processors, desc: "Number of concurent processes to run"
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 :log, aliases: "-l", type: :boolean, desc: "Print debug messages to $stderr"
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 "fan [COMMAND]", "executes the given job on all of the workers"
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({"TEST_ENV_NUMBER" => i.to_s}, command)
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 "clear", "kills all flatware processes"
47
+ desc 'clear', 'kills all flatware processes'
33
48
  def clear
34
- (Flatware.pids - [$$]).each do |pid|
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
- $0 = 'flatware sink'
57
+ $0 = 'flatware sink'
43
58
  Process.setpgrp
44
- passed = Sink.start_server jobs: jobs, formatter: Flatware::Broadcaster.new([formatter]), sink: options['sink-endpoint'], dispatch: options['dispatch-endpoint'], worker_count: workers
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 = Gem.loaded_specs.keys.grep(/^flatware-/)
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 flatware_gems.count > 0
61
- flatware_gems.each(&method(:require))
62
- else
63
- puts "The flatware gem is a dependency of flatware runners for rspec and cucumber. Try adding flatware-rspec or flatware-cucumber to your Gemfile."
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 CHANGED
@@ -4,7 +4,19 @@ module Flatware
4
4
  attr_writer :failed
5
5
 
6
6
  def failed?
7
- !!@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'
8
20
  end
9
21
  end
10
22
  end
@@ -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
- def initialize(klass, message, backtrace, cause='')
6
- @class, @message, @backtrace, @cause = serialized(klass), message, backtrace, cause
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 exception.class, exception.message, exception.backtrace, exception.cause
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); @name = name end
32
+ def initialize(name)
33
+ @name = name
34
+ end
23
35
  end
24
36
  end