flatware 0.2.0 → 0.3.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,67 @@
1
+ require 'cucumber/formatter/console'
2
+ require 'flatware/formatters/console'
3
+ require 'flatware/checkpoint'
4
+ module Flatware
5
+ module Formatters
6
+ class Console
7
+ class Summary
8
+ include ::Cucumber::Formatter::Console
9
+ attr_reader :io, :steps, :scenarios
10
+
11
+ def initialize(steps, scenarios=[], io=StringIO.new)
12
+ @io = io
13
+ @steps = steps
14
+ @scenarios = scenarios
15
+ end
16
+
17
+ def summarize
18
+ 2.times { io.puts }
19
+ print_failures(steps, 'step')
20
+ print_failures(scenarios.select(&:failed_outside_step?), 'scenario')
21
+ print_failed_scenarios scenarios
22
+ print_counts 'scenario', scenarios
23
+ print_counts 'step', steps
24
+ end
25
+
26
+ private
27
+
28
+ def print_failed_scenarios(scenarios)
29
+ return unless scenarios.any? &with_status(:failed)
30
+
31
+ io.puts format_string "Failing Scenarios:", :failed
32
+ scenarios.select(&with_status(:failed)).sort_by(&:file_colon_line).each do |scenario|
33
+ io.puts format_string(scenario.file_colon_line, :failed) + format_string(" # Scenario: " + scenario.name, :comment)
34
+ end
35
+ io.puts
36
+ end
37
+
38
+ def print_failures(collection, label)
39
+ failures = collection.select(&with_status(:failed))
40
+ print_elements failures, :failed, pluralize(label, failures.size)
41
+ end
42
+
43
+ def print_counts(label, collection)
44
+ io.puts pluralize(label, collection.size) + count_summary(collection)
45
+ end
46
+
47
+ def pluralize(word, number)
48
+ "#{number} #{number == 1 ? word : word + 's'}"
49
+ end
50
+
51
+ def with_status(status)
52
+ proc {|r| r.status == status}
53
+ end
54
+
55
+ def count_summary(results)
56
+ return "" unless results.any?
57
+ status_counts = STATUSES.map do |status|
58
+ count = results.select(&with_status(status)).size
59
+ format_string "#{count} #{status}", status if count > 0
60
+ end.compact.join ", "
61
+
62
+ " (#{status_counts})"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,83 @@
1
+ require 'net/http'
2
+ require 'pathname'
3
+
4
+ module Flatware
5
+ module Formatters
6
+ class Http
7
+ attr_reader :out, :client, :server_pid
8
+ def initialize(out, err)
9
+ @out = out
10
+ @client = Client.new
11
+ end
12
+
13
+ def jobs(jobs)
14
+ client.send_message [:jobs, jobs.map {|job| {id: job.id}}]
15
+ end
16
+
17
+ def started(job)
18
+ client.send_message [:started, job: job.id, worker: job.worker]
19
+ end
20
+
21
+ def finished(job)
22
+ client.send_message [:finished, job: job.id, worker: job.worker]
23
+ end
24
+
25
+ def progress(result)
26
+ client.send_message [:progress, status: result.progress, worker: result.worker]
27
+ end
28
+
29
+ def summarize(steps, scenarios)
30
+ client.send_message [
31
+ :summarize, {
32
+ steps: steps.map(&method(:step_as_json)),
33
+ scenarios: scenarios.map(&method(:scenario_as_json))
34
+ }
35
+ ]
36
+ end
37
+
38
+ private
39
+
40
+ def step_as_json(step)
41
+ { status: step.status }.tap do |h|
42
+ h.merge(exception: {
43
+ class: step.exception.class,
44
+ message: step.exception.message,
45
+ backtrace: step.exception.backtrace
46
+ }) if step.exception
47
+ end
48
+ end
49
+
50
+ def scenario_as_json(scenario)
51
+ {
52
+ status: scenario.status,
53
+ file_colon_line: scenario.file_colon_line,
54
+ name: scenario.name
55
+ }
56
+ end
57
+
58
+ class Client
59
+ attr_reader :uri
60
+ include Net
61
+
62
+ def initialize
63
+ @uri = URI ENV['FLATWARE_URL']
64
+ end
65
+
66
+ def send_message(message)
67
+ send_request uri.path, JSON.dump(message)
68
+ end
69
+
70
+ private
71
+
72
+ def send_request(path, body=nil)
73
+ req = HTTP::Post.new path
74
+ req.body = body
75
+ res = HTTP.start uri.hostname, uri.port do |http|
76
+ http.request req
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,25 @@
1
+ require 'flatware/processor_info'
2
+ module Flatware
3
+ extend self
4
+ # All the pids of all the processes called flatware on this machine
5
+ def pids
6
+ pids_command.map do |row|
7
+ row =~ /(\d+).*flatware/ and $1.to_i
8
+ end.compact
9
+ end
10
+
11
+ def pids_command
12
+ case ProcessorInfo.operating_system
13
+ when 'Darwin'
14
+ `ps -c -opid,pgid,command`
15
+ when 'Linux'
16
+ `ps -opid,pgid,command`
17
+ end.split("\n")[1..-1]
18
+ end
19
+
20
+ def pids_of_group(group_pid)
21
+ pids_command.map(&:split).map do |pid, pgid, _|
22
+ pid.to_i if pgid.to_i == group_pid
23
+ end.compact
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module Flatware
2
+ class Poller
3
+ attr_reader :sockets, :zmq_poller
4
+ def initialize(*sockets)
5
+ @sockets = sockets
6
+ @zmq_poller = ZMQ::Poller.new
7
+ register_sockets
8
+ end
9
+
10
+ def each(&block)
11
+ while (result = zmq_poller.poll) != 0
12
+ raise Error, ZMQ::Util.error_string, caller if result == -1
13
+ for socket in zmq_poller.readables.map &find_wrapped_socket
14
+ yield socket
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def find_wrapped_socket
22
+ ->(s) do
23
+ sockets.find do |socket|
24
+ socket.s == s
25
+ end
26
+ end
27
+ end
28
+
29
+ def register_sockets
30
+ sockets.each do |socket|
31
+ zmq_poller.register_readable socket.s
32
+ end
33
+ end
34
+ end
35
+ end
@@ -16,5 +16,9 @@ module Flatware
16
16
  def self.count
17
17
  new.count
18
18
  end
19
+
20
+ def self.operating_system
21
+ new.operating_system
22
+ end
19
23
  end
20
24
  end
@@ -1,9 +1,10 @@
1
1
  module Flatware
2
2
  class Result
3
- attr_reader :progress, :steps
3
+ attr_reader :progress, :worker
4
4
 
5
- def initialize(progress, steps=nil)
6
- @progress, @steps = progress, steps || []
5
+ def initialize(progress)
6
+ @progress = progress
7
+ @worker = ENV['TEST_ENV_NUMBER'].to_i
7
8
  end
8
9
 
9
10
  class << self
@@ -13,7 +14,7 @@ module Flatware
13
14
  end
14
15
 
15
16
  def status(status)
16
- new Cucumber::ProgressString.format status
17
+ new status
17
18
  end
18
19
 
19
20
  def background(status, exception)
@@ -1,24 +1,22 @@
1
+ require 'forwardable'
1
2
  module Flatware
2
3
  class ScenarioDecorator
3
- attr_reader :status
4
+ extend Forwardable
5
+ def_delegators :scenario, :name, :file_colon_line
4
6
 
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
7
+ attr_reader :status, :exception
13
8
 
14
- def file_colon_line
15
- @scenario.file_colon_line
9
+ def initialize(scenario)
10
+ @scenario, @status, @exception = scenario, scenario.status, scenario.exception
11
+ @scenario, @exception = scenario.scenario_outline, scenario.exception if example_row?
16
12
  end
17
13
 
18
14
  private
19
15
 
16
+ attr_reader :scenario
17
+
20
18
  def example_row?
21
- @scenario.respond_to? :scenario_outline
19
+ scenario.respond_to? :scenario_outline
22
20
  end
23
21
  end
24
22
  end
@@ -1,10 +1,13 @@
1
+ require 'flatware/serialized_exception'
1
2
  module Flatware
2
3
  class ScenarioResult
3
4
  attr_reader :status, :file_colon_line, :name
4
- def initialize(status, file_colon_line, name)
5
+ def initialize(status, file_colon_line, name, e)
5
6
  @status = status
6
7
  @file_colon_line = file_colon_line
7
8
  @name = name
9
+ @exception = SerializedException.new(e.class, e.message, e.backtrace) if e
10
+ @failed_outside_step = false
8
11
  end
9
12
 
10
13
  def passed?
@@ -14,5 +17,20 @@ module Flatware
14
17
  def failed?
15
18
  status == :failed
16
19
  end
20
+
21
+ def failed_outside_step!(file_colon_line)
22
+ @failed_outside_step = file_colon_line
23
+ end
24
+
25
+ def failed_outside_step?
26
+ !!@failed_outside_step
27
+ end
28
+
29
+ def exception
30
+ @exception.tap do |e|
31
+ e.backtrace = e.backtrace.grep(Regexp.new(Dir.pwd)).map { |line| line[Dir.pwd.size..-1] }
32
+ e.backtrace = e.backtrace + [@failed_outside_step] if failed_outside_step?
33
+ end
34
+ end
17
35
  end
18
36
  end
@@ -0,0 +1,20 @@
1
+ module Flatware
2
+ class SerializedException
3
+ attr_reader :class, :message
4
+ attr_accessor :backtrace
5
+ def initialize(klass, message, backtrace)
6
+ @class, @message, @backtrace = serialized(klass), message, backtrace
7
+ end
8
+
9
+ private
10
+ def serialized(klass)
11
+ SerializedClass.new(klass.to_s)
12
+ end
13
+
14
+ class SerializedClass
15
+ attr_reader :name
16
+ alias to_s name
17
+ def initialize(name); @name = name end
18
+ end
19
+ end
20
+ end
data/lib/flatware/sink.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'flatware'
2
- require 'flatware/cucumber/formatter'
3
2
  module Flatware
4
3
  class Sink
5
4
  PORT = 'ipc://sink'
@@ -8,22 +7,28 @@ module Flatware
8
7
  client.push message
9
8
  end
10
9
 
11
- def finished(job)
12
- push job
13
- end
14
-
15
10
  def start_server(*args)
16
11
  Server.new(*args).start
17
12
  end
18
13
 
14
+ private
15
+
19
16
  def client
20
17
  @client ||= Client.new
21
18
  end
22
19
  end
23
20
 
21
+ %w[finished started progress checkpoint].each do |message|
22
+ define_singleton_method message do |content|
23
+ push [message.to_sym, content]
24
+ end
25
+ end
26
+
24
27
  class Server
25
- def initialize(jobs, out=$stdout, error=$stderr, fail_fast=false)
26
- @jobs, @out, @error, @fail_fast = jobs, out, error, fail_fast
28
+ def initialize(jobs, formatter, options={})
29
+ @jobs, @formatter = jobs, formatter
30
+ options = {fail_fast: false}.merge options
31
+ @fail_fast = options[:fail_fast]
27
32
  end
28
33
 
29
34
  def start
@@ -33,30 +38,33 @@ module Flatware
33
38
  exit 1
34
39
  end
35
40
 
36
- before_firing { listen }
41
+ Flatware::Fireable::bind
42
+ formatter.jobs jobs
43
+ listen
44
+ ensure
45
+ Flatware::Fireable::kill
37
46
  Flatware.close
38
47
  end
39
48
 
40
49
  def checkpoint_handler
41
- @checkpoint_handler ||= CheckpointHandler.new(out, fail_fast?)
50
+ @checkpoint_handler ||= CheckpointHandler.new(formatter, fail_fast?)
42
51
  end
43
52
 
44
53
  def listen
45
54
  until done?
46
- result = socket.recv
47
- case result
48
- when Result
49
- print result.progress
50
- when Checkpoint
51
- checkpoint_handler.handle! result
52
- when Job
53
- completed_jobs << result
54
- log "COMPLETED SCENARIO"
55
+ message, content = socket.recv
56
+ case message
57
+ when :checkpoint
58
+ checkpoint_handler.handle! content
59
+ when :finished
60
+ completed_jobs << content
61
+ formatter.finished content
55
62
  else
56
- log "i don't know that message, bro.", result
63
+ formatter.send message, content
57
64
  end
58
65
  end
59
66
  checkpoint_handler.summarize
67
+ !checkpoint_handler.had_failures?
60
68
  rescue Error => e
61
69
  raise unless e.message == "Interrupted system call"
62
70
  end
@@ -67,41 +75,22 @@ module Flatware
67
75
  @fail_fast
68
76
  end
69
77
 
70
- attr_reader :out, :jobs
71
-
72
- def print(*args)
73
- out.print *args
74
- end
75
-
76
- def puts(*args)
77
- out.puts *args
78
- end
78
+ attr_reader :jobs, :formatter
79
79
 
80
80
  def summarize_remaining
81
81
  return if remaining_work.empty?
82
- puts
83
- puts "The following features have not been run:"
84
- for job in remaining_work
85
- puts job.id
86
- end
82
+ formatter.summarize_remaining remaining_work
87
83
  end
88
84
 
89
85
  def log(*args)
90
86
  Flatware.log *args
91
87
  end
92
88
 
93
- def before_firing(&block)
94
- Flatware::Fireable::bind
95
- block.call
96
- Flatware::Fireable::kill
97
- end
98
-
99
89
  def completed_jobs
100
90
  @completed_jobs ||= []
101
91
  end
102
92
 
103
93
  def done?
104
- log remaining_work
105
94
  remaining_work.empty? || checkpoint_handler.done?
106
95
  end
107
96