flatware 1.0.0 → 2.0.0.rc3

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 -49
  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: 0ff7cc506cc64f0d64a8892435f946ec19551461
4
- data.tar.gz: e27ffb2ff50e1547a03e2c8f24c8a2c1f6220abe
2
+ SHA256:
3
+ metadata.gz: e2128e35d85e75f68e907edc28bc38982e3833113bcb523df4a3b1db00defd53
4
+ data.tar.gz: 241753f759a9f62dc6b35aa0cc706e0fad16ff1cf17ea0a2755d27f6b6449c46
5
5
  SHA512:
6
- metadata.gz: df21484a242be9f3e979c53bd38830c1e824e045a966d2e66023bd9f56c43ea64ed7a1c6e365206ea4bfb59e2440ce6654520eaf1be55f25f463119e89306b2d
7
- data.tar.gz: c6ce36dc2f5472e7abc59eee306cc1afc82dcae1a6c4fba5844a82a1501f3f3d036d1bb8ba2aba99d0faa7d5eb07f2280deb1bd8097f0953a574c874371581ad
6
+ metadata.gz: f355f409f4d2cdb72c78af7f8e5995d2aac5eb7493032bcdaacfb39581828a7a9c1c6c04f81d3106402475ea0464f7fb1a8dd70f32a4fe99a84361797698af81
7
+ data.tar.gz: 406a469cc1a7780ea9b7c81d93b1967bcd518f6bb1528460b14ee0b7b6d5a338777572461b0497b035ace1c6c321b92b7aa80dabd87359a978be65bd5c3d7163
data/README.md CHANGED
@@ -1,45 +1,10 @@
1
- # Flatware [![Build Status][travis-badge]][travis] [![Code Climate][code-climate-badge]][code-climate]
1
+ # Flatware [![Code Climate][code-climate-badge]][code-climate]
2
2
 
3
- [travis-badge]: https://travis-ci.org/briandunn/flatware.svg?branch=master
4
- [travis]: http://travis-ci.org/briandunn/flatware
5
3
  [code-climate-badge]: https://codeclimate.com/github/briandunn/flatware.png
6
4
  [code-climate]: https://codeclimate.com/github/briandunn/flatware
7
5
 
8
6
  Flatware parallelizes your test suite to significantly reduce test time.
9
7
 
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
8
  ### Flatware
44
9
 
45
10
  Add the runners you need to your Gemfile:
@@ -73,6 +38,18 @@ To run your entire suite with the default rspec options add the `flatware-rspec`
73
38
  $ flatware rspec
74
39
  ```
75
40
 
41
+ The rspec runner can balance worker loads, making your suite even faster.
42
+
43
+ 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).
44
+
45
+ For this to work the configuration option must be loaded before any specs are run. The `.rspec` file is one way to achive this:
46
+
47
+ --require spec_helper
48
+
49
+ 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](
50
+ #faster-startup-with-activerecord
51
+ ).
52
+
76
53
  ### Options
77
54
 
78
55
  If you'd like to limit the number of forked workers, you can pass the 'w' flag:
@@ -123,9 +100,31 @@ Now you are ready to rock:
123
100
  $ flatware rspec && flatware cucumber
124
101
  ```
125
102
 
126
- ## Planned Features
103
+ ### Faster Startup With ActiveRecord
127
104
 
128
- * Use heuristics to run your slowest tests first
105
+ Flatware has a couple lifecycle callbacks that you can use to avoid booting your app
106
+ over again on every core. One way to take advantage of this via a `spec/flatware_helper.rb` file like so:
107
+
108
+ ```ruby
109
+ Flatware.configure do |conf|
110
+ conf.before_fork do
111
+ require 'rails_helper'
112
+
113
+ ActiveRecord::Base.connection.disconnect!
114
+ end
115
+
116
+ conf.after_fork do |test_env_number|
117
+ config = ActiveRecord::Base.connection_config
118
+
119
+ ActiveRecord::Base.establish_connection(
120
+ config.merge(
121
+ database: config.fetch(:database) + test_env_number.to_s
122
+ )
123
+ )
124
+ end
125
+ end
126
+ ```
127
+ Now when I run `bundle exec flatware rspec -r ./spec/flatware_helper` My app only boots once, rather than once per core.
129
128
 
130
129
  ## Design Goals
131
130
 
@@ -156,20 +155,10 @@ directory. CD there and `flatware` will be in your path so you can tinker away.
156
155
 
157
156
  ## How it works
158
157
 
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.
158
+ 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
159
 
167
160
  ## Resources
168
161
 
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
162
  [a]: https://github.com/cucumber/aruba
174
163
 
175
164
  ## 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