flatware 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,6 +1,9 @@
1
1
  = Flatware
2
2
 
3
- Flatware is a a distributed cucumber runner.
3
+ {<img src="https://travis-ci.org/briandunn/flatware.png" />}[https://travis-ci.org/briandunn/flatware]
4
+ {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/briandunn/flatware]
5
+
6
+ Flatware is a distributed cucumber runner.
4
7
 
5
8
  == Requirements
6
9
 
data/lib/flatware.rb CHANGED
@@ -1,29 +1,42 @@
1
- require 'zmq'
1
+ require 'forwardable'
2
+ require 'ffi-rzmq'
2
3
 
3
4
  module Flatware
4
5
  autoload :CLI, 'flatware/cli'
5
6
  autoload :Cucumber, 'flatware/cucumber'
6
7
  autoload :Dispatcher, 'flatware/dispatcher'
7
8
  autoload :Fireable, 'flatware/fireable'
9
+ autoload :ProcessorInfo, 'flatware/processor_info'
8
10
  autoload :Result, 'flatware/result'
9
11
  autoload :ScenarioResult, 'flatware/scenario_result'
10
12
  autoload :Sink, 'flatware/sink'
11
13
  autoload :StepResult, 'flatware/step_result'
12
14
  autoload :Summary, 'flatware/summary'
13
15
  autoload :Worker, 'flatware/worker'
16
+ autoload :ScenarioDecorator, 'flatware/scenario_decorator'
17
+
18
+ Error = Class.new StandardError
14
19
 
15
20
  Job = Struct.new :id, :args
16
21
 
17
22
  extend self
18
- def socket(*args)
19
- context.socket(*args).tap do |socket|
23
+ def socket(type, options={})
24
+ Socket.new(context.socket(type)).tap do |socket|
20
25
  sockets.push socket
26
+ if port = options[:connect]
27
+ socket.connect port
28
+ end
29
+ if port = options[:bind]
30
+ socket.bind port
31
+ end
32
+ #FIXME: figure out how to do this without waiting
33
+ sleep 0.05
21
34
  end
22
35
  end
23
36
 
24
37
  def close
25
38
  sockets.each &:close
26
- context.close
39
+ context.terminate
27
40
  @context = nil
28
41
  end
29
42
 
@@ -47,4 +60,26 @@ module Flatware
47
60
  def sockets
48
61
  @sockets ||= []
49
62
  end
63
+
64
+ Socket = Struct.new :s do
65
+ extend Forwardable
66
+ def_delegators :s, :bind, :connect, :setsockopt
67
+ def send(message)
68
+ result = s.send_string(Marshal.dump(message))
69
+ raise Error, ZMQ::Util.error_string, caller unless result == 0
70
+ message
71
+ end
72
+
73
+ def close
74
+ s.setsockopt(ZMQ::LINGER, 1)
75
+ s.close
76
+ end
77
+
78
+ def recv
79
+ message = ''
80
+ result = s.recv_string(message)
81
+ raise Error, ZMQ::Util.error_string, caller unless result == 0
82
+ Marshal.load message
83
+ end
84
+ end
50
85
  end
@@ -15,8 +15,9 @@ module Flatware
15
15
 
16
16
  def serialize_scenarios(scenarios)
17
17
  scenarios.map do |scenario|
18
- ScenarioResult.new scenario.status
18
+ ScenarioResult.new scenario.status, scenario.file_colon_line, scenario.name
19
19
  end
20
20
  end
21
+
21
22
  end
22
23
  end
data/lib/flatware/cli.rb CHANGED
@@ -3,7 +3,7 @@ module Flatware
3
3
  class CLI < Thor
4
4
 
5
5
  def self.processors
6
- @processors ||= `hostinfo`.match(/^(?<processors>\d+) processors are logically available\.$/)[:processors].to_i
6
+ @processors ||= ProcessorInfo.count
7
7
  end
8
8
 
9
9
  def self.worker_option
@@ -1,5 +1,6 @@
1
1
  require 'cucumber/formatter/console'
2
2
  require 'flatware/checkpoint'
3
+ require 'flatware/scenario_decorator'
3
4
  module Flatware
4
5
  module Cucumber
5
6
  class Formatter
@@ -34,7 +35,7 @@ module Flatware
34
35
  end
35
36
 
36
37
  def checkpoint
37
- Checkpoint.new(step_mother.steps - @ran_steps, step_mother.scenarios - @ran_scenarios).tap do
38
+ Checkpoint.new(step_mother.steps - @ran_steps, decorate_scenarios(step_mother.scenarios - @ran_scenarios)).tap do
38
39
  snapshot
39
40
  end
40
41
  end
@@ -45,6 +46,10 @@ module Flatware
45
46
  @ran_steps = step_mother.steps.dup
46
47
  @ran_scenarios = step_mother.scenarios.dup
47
48
  end
49
+
50
+ def decorate_scenarios(scenarios)
51
+ scenarios.map { |scenario| ScenarioDecorator.new(scenario) }
52
+ end
48
53
  end
49
54
  end
50
55
 
@@ -1,6 +1,6 @@
1
1
  module Flatware
2
2
  class Dispatcher
3
- DISPATCH_PORT = 'ipc://dispatch'
3
+ PORT = 'ipc://dispatch'
4
4
 
5
5
  def self.start(jobs=Cucumber.jobs)
6
6
  new(jobs).dispatch!
@@ -11,10 +11,10 @@ module Flatware
11
11
  end
12
12
 
13
13
  def dispatch!
14
- return Flatware.close if jobs.empty?
14
+ return if jobs.empty?
15
15
  fireable.until_fired dispatch do |request|
16
16
  if job = jobs.pop
17
- dispatch.send Marshal.dump job
17
+ dispatch.send job
18
18
  else
19
19
  dispatch.send 'seppuku'
20
20
  end
@@ -30,9 +30,7 @@ module Flatware
30
30
  end
31
31
 
32
32
  def dispatch
33
- @dispatch ||= Flatware.socket(ZMQ::REP).tap do |socket|
34
- socket.bind DISPATCH_PORT
35
- end
33
+ @dispatch ||= Flatware.socket ZMQ::REP, bind: PORT
36
34
  end
37
35
  end
38
36
  end
@@ -1,22 +1,50 @@
1
1
  module Flatware
2
2
  class Fireable
3
+ PORT = 'ipc://die'
4
+
5
+ def self.bind
6
+ @kill = Flatware.socket(ZMQ::PUB, bind: PORT)
7
+ end
8
+
9
+ def self.kill
10
+ @kill.send 'seppuku'
11
+ end
12
+
3
13
  def initialize
4
- @die = Flatware.socket(ZMQ::SUB).tap do |die|
5
- die.connect 'ipc://die'
14
+ @die = Flatware.socket(ZMQ::SUB, connect: PORT).tap do |die|
6
15
  die.setsockopt ZMQ::SUBSCRIBE, ''
7
16
  end
8
17
  end
9
18
 
10
19
  attr_reader :die
11
20
 
12
- def until_fired(sockets=[], &block)
13
- while ready = ZMQ.select(Array(sockets) + [die])
14
- messages = ready.flatten.compact.map(&:recv)
15
- break if messages.include? 'seppuku'
16
- messages.each &block
21
+ def until_fired(socket, &block)
22
+ poller = Poller.new socket, die
23
+ poller.each do |message|
24
+ break if message == 'seppuku'
25
+ block.call message
17
26
  end
18
27
  ensure
19
28
  Flatware.close
20
29
  end
21
30
  end
31
+
32
+ class Poller
33
+ attr_reader :sockets
34
+ def initialize(*sockets)
35
+ @sockets = sockets
36
+ end
37
+
38
+ def each(&block)
39
+ poller = ZMQ::Poller.new
40
+ for socket in sockets
41
+ poller.register_readable socket.s
42
+ end
43
+ while poller.poll > 0
44
+ poller.readables.each do |s|
45
+ block.call Socket.new(s).recv
46
+ end
47
+ end
48
+ end
49
+ end
22
50
  end
@@ -0,0 +1,20 @@
1
+ module Flatware
2
+ class ProcessorInfo
3
+ def count
4
+ case operating_system
5
+ when 'Darwin'
6
+ `hostinfo`.match(/^(?<processors>\d+) processors are logically available\.$/)[:processors].to_i
7
+ when 'Linux'
8
+ `grep --count '^processor' /proc/cpuinfo`.to_i
9
+ end
10
+ end
11
+
12
+ def operating_system
13
+ `uname`.chomp
14
+ end
15
+
16
+ def self.count
17
+ new.count
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ module Flatware
2
+ class ScenarioDecorator
3
+ attr_reader :status
4
+
5
+ def initialize(scenario)
6
+ @scenario, @status = scenario, scenario.status
7
+ @scenario = scenario.scenario_outline if example_row?
8
+ end
9
+
10
+ def name
11
+ @scenario.name
12
+ end
13
+
14
+ def file_colon_line
15
+ @scenario.file_colon_line
16
+ end
17
+
18
+ private
19
+
20
+ def example_row?
21
+ @scenario.respond_to? :scenario_outline
22
+ end
23
+ end
24
+ end
@@ -1,8 +1,10 @@
1
1
  module Flatware
2
2
  class ScenarioResult
3
- attr_reader :status
4
- def initialize(status)
3
+ attr_reader :status, :file_colon_line, :name
4
+ def initialize(status, file_colon_line, name)
5
5
  @status = status
6
+ @file_colon_line = file_colon_line
7
+ @name = name
6
8
  end
7
9
 
8
10
  def passed?
data/lib/flatware/sink.rb CHANGED
@@ -2,9 +2,10 @@ require 'flatware'
2
2
  require 'flatware/cucumber/formatter'
3
3
  module Flatware
4
4
  class Sink
5
+ PORT = 'ipc://sink'
5
6
  class << self
6
7
  def push(message)
7
- client.push Marshal.dump message
8
+ client.push message
8
9
  end
9
10
 
10
11
  def finished(job)
@@ -29,6 +30,7 @@ module Flatware
29
30
  trap 'INT' do
30
31
  summarize
31
32
  summarize_remaining
33
+ exit 1
32
34
  end
33
35
 
34
36
  before_firing { listen }
@@ -38,7 +40,7 @@ module Flatware
38
40
  def listen
39
41
  until done?
40
42
  message = socket.recv
41
- case (result = Marshal.load message)
43
+ case (result = message)
42
44
  when Result
43
45
  print result.progress
44
46
  when Checkpoint
@@ -47,11 +49,11 @@ module Flatware
47
49
  completed_jobs << result
48
50
  log "COMPLETED SCENARIO"
49
51
  else
50
- log "i don't know that message, bro."
52
+ log "i don't know that message, bro.", message
51
53
  end
52
54
  end
53
55
  summarize
54
- rescue ZMQ::Error => e
56
+ rescue Error => e
55
57
  raise unless e.message == "Interrupted system call"
56
58
  end
57
59
 
@@ -87,11 +89,9 @@ module Flatware
87
89
  end
88
90
 
89
91
  def before_firing(&block)
90
- die = Flatware.socket(ZMQ::PUB).tap do |socket|
91
- socket.bind 'ipc://die'
92
- end
92
+ Flatware::Fireable::bind
93
93
  block.call
94
- die.send 'seppuku'
94
+ Flatware::Fireable::kill
95
95
  end
96
96
 
97
97
  def checkpoints
@@ -116,9 +116,7 @@ module Flatware
116
116
  end
117
117
 
118
118
  def socket
119
- @socket ||= Flatware.socket(ZMQ::PULL).tap do |socket|
120
- socket.bind 'ipc://sink'
121
- end
119
+ @socket ||= Flatware.socket(ZMQ::PULL, bind: PORT)
122
120
  end
123
121
  end
124
122
 
@@ -130,9 +128,7 @@ module Flatware
130
128
  private
131
129
 
132
130
  def socket
133
- @socket ||= Flatware.socket(ZMQ::PUSH).tap do |socket|
134
- socket.connect 'ipc://sink'
135
- end
131
+ @socket ||= Flatware.socket(ZMQ::PUSH, connect: PORT)
136
132
  end
137
133
  end
138
134
  end
@@ -14,12 +14,23 @@ module Flatware
14
14
  def summarize
15
15
  2.times { io.puts }
16
16
  print_steps :failed
17
+ print_failed_scenarios scenarios
17
18
  print_counts 'scenario', scenarios
18
19
  print_counts 'step', steps
19
20
  end
20
21
 
21
22
  private
22
23
 
24
+ def print_failed_scenarios(scenarios)
25
+ return unless scenarios.any? &with_status(:failed)
26
+
27
+ io.puts format_string "Failing Scenarios:", :failed
28
+ scenarios.select(&with_status(:failed)).sort_by(&:file_colon_line).each do |scenario|
29
+ io.puts format_string(scenario.file_colon_line, :failed) + format_string(" # Scenario: " + scenario.name, :comment)
30
+ end
31
+ io.puts
32
+ end
33
+
23
34
  def print_steps(status)
24
35
  print_elements steps.select(&with_status(status)), status, 'steps'
25
36
  end
@@ -1,3 +1,3 @@
1
1
  module Flatware
2
- VERSION = '0.0.4'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -20,8 +20,7 @@ module Flatware
20
20
  time = Benchmark.realtime do
21
21
  fireable
22
22
  report_for_duty
23
- fireable.until_fired task do |work|
24
- job = Marshal.load work
23
+ fireable.until_fired task do |job|
25
24
  log 'working!'
26
25
  Cucumber.run job.id, job.args
27
26
  Sink.finished job
@@ -43,9 +42,7 @@ module Flatware
43
42
  end
44
43
 
45
44
  def task
46
- @task ||= Flatware.socket(ZMQ::REQ).tap do |task|
47
- task.connect Dispatcher::DISPATCH_PORT
48
- end
45
+ @task ||= Flatware.socket ZMQ::REQ, connect: Dispatcher::PORT
49
46
  end
50
47
 
51
48
  def report_for_duty
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flatware
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-24 00:00:00.000000000 Z
12
+ date: 2013-03-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: zmq
15
+ name: ffi-rzmq
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: '0'
21
+ version: 1.0.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: '0'
29
+ version: 1.0.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: thor
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -64,49 +64,49 @@ dependencies:
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ! '>='
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: '0'
69
+ version: 0.5.1
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ! '>='
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: '0'
77
+ version: 0.5.1
78
78
  - !ruby/object:Gem::Dependency
79
79
  name: rake
80
80
  requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
83
- - - ! '>='
83
+ - - ~>
84
84
  - !ruby/object:Gem::Version
85
- version: '0'
85
+ version: 10.0.3
86
86
  type: :development
87
87
  prerelease: false
88
88
  version_requirements: !ruby/object:Gem::Requirement
89
89
  none: false
90
90
  requirements:
91
- - - ! '>='
91
+ - - ~>
92
92
  - !ruby/object:Gem::Version
93
- version: '0'
93
+ version: 10.0.3
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: rspec
96
96
  requirement: !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements:
99
- - - ! '>='
99
+ - - ~>
100
100
  - !ruby/object:Gem::Version
101
- version: '0'
101
+ version: 2.13.0
102
102
  type: :development
103
103
  prerelease: false
104
104
  version_requirements: !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
- - - ! '>='
107
+ - - ~>
108
108
  - !ruby/object:Gem::Version
109
- version: '0'
109
+ version: 2.13.0
110
110
  description: A distributed cucumber runner
111
111
  email: brian@hashrocket.com
112
112
  executables:
@@ -124,7 +124,9 @@ files:
124
124
  - lib/flatware/cucumber/runtime.rb
125
125
  - lib/flatware/dispatcher.rb
126
126
  - lib/flatware/fireable.rb
127
+ - lib/flatware/processor_info.rb
127
128
  - lib/flatware/result.rb
129
+ - lib/flatware/scenario_decorator.rb
128
130
  - lib/flatware/scenario_result.rb
129
131
  - lib/flatware/sink.rb
130
132
  - lib/flatware/step_result.rb
@@ -155,7 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
157
  version: '0'
156
158
  requirements: []
157
159
  rubyforge_project:
158
- rubygems_version: 1.8.24
160
+ rubygems_version: 1.8.25
159
161
  signing_key:
160
162
  specification_version: 3
161
163
  summary: A distributed cucumber runner