Class: Paraspec::Master

Inherits:
Object
  • Object
show all
Defined in:
lib/paraspec/master.rb

Overview

The master process has three responsibilities:

  1. Load all tests and abort the run if there are errors outside of examples.

  2. Maintain the queue of tests to feed the workers. The master process also synchronizes access to this queue.

  3. Aggregate test reports from the workers and present them to the outside world in a coherent fashion. The latter means that numbers presented are for the entire suite, not for parts of it as executed by any single worker, and that output from a single test execution is not broken up by output from other test executions.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Master

Returns a new instance of Master.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/paraspec/master.rb', line 14

def initialize(options={})
  @supervisor_pipe = options[:supervisor_pipe]
  #RSpec.configuration.formatter = 'progress'
  if RSpec.world.example_groups.count > 0
    raise 'Example groups loaded too early/spilled across processes'
  end

  rspec_options = RSpec::Core::ConfigurationOptions.new(ARGV)
  @non_example_exception_count = 0
  begin
    # This can fail if for example a nonexistent formatter is referenced
    rspec_options.configure(RSpec.configuration)
  rescue Exception => e
    puts "#{e.class}: #{e}"
    puts e.backtrace.join("\n")
    # TODO and report this situation as a configuration problem
    # and not a test suite problem
    @non_example_exception_count = 1
  end

=begin
  if RSpec.configuration.files_to_run.empty?
    RSpec.configuration.send(:remove_instance_variable, '@files_to_run')
    RSpec.configuration.files_or_directories_to_run = RSpec.configuration.default_path
    RSpec.configuration.files_to_run
    p ['aa1',RSpec.configuration.files_to_run]
    rspec_options.configure(RSpec.configuration)
    RSpec.configuration.load_spec_files
  end
=end

  # It seems that load_spec_files sometimes rescues exceptions outside of
  # examples and sometimes does not, handle it both ways
  if @non_example_exception_count == 0
    begin
      RSpec.configuration.load_spec_files
    rescue Exception => e
      puts "#{e.class}: #{e}"
      puts e.backtrace.join("\n")
      @non_example_exception_count = 1
    end
  end
  if @non_example_exception_count == 0
    @non_example_exception_count = RSpec.world.reporter.non_example_exception_count
  end
  @queue = []
  if @non_example_exception_count == 0
    @queue += RSpecFacade.all_example_groups
    puts "#{@queue.length} example groups queued"
  else
    puts "#{@non_example_exception_count} errors outside of examples, aborting"
  end
end

Instance Attribute Details

#non_example_exception_countObject (readonly)

Returns the value of attribute non_example_exception_count.



68
69
70
# File 'lib/paraspec/master.rb', line 68

def non_example_exception_count
  @non_example_exception_count
end

Instance Method Details

#do_example_passed(spec, execution_result) ⇒ Object



133
134
135
136
137
138
139
140
141
142
# File 'lib/paraspec/master.rb', line 133

def do_example_passed(spec, execution_result)
#return
  example = find_example(spec)
  # Can write to example here
  example.[:execution_result] = execution_result
  status = execution_result.status
  m = "example_#{status}"
  reporter.send(m, example)
  nil
end

#dump_summaryObject



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/paraspec/master.rb', line 180

def dump_summary
  reporter.stop

  all_examples = RSpecFacade.all_examples
  notification = RSpec::Core::Notifications::SummaryNotification.new(
    @start_time ? Time.now-@start_time : 0,
    all_examples,
    all_examples.select { |e| e.execution_result.status == :failed },
    all_examples.select { |e| e.execution_result.status == :pending },
    0,
    non_example_exception_count,
  )
  examples_notification = RSpec::Core::Notifications::ExamplesNotification.new(reporter)
  RSpec.configuration.formatters.each do |f|
    if f.respond_to?(:dump_summary)
      f.dump_summary(notification)
    end
    if f.respond_to?(:dump_failures)
      f.dump_failures(examples_notification)
    end
    if f.respond_to?(:dump_pending)
      f.dump_pending(examples_notification)
    end
  end
  nil
end

#example_countObject



98
99
100
# File 'lib/paraspec/master.rb', line 98

def example_count
  RSpecFacade.all_examples.count
end

#example_passed(payload) ⇒ Object



121
122
123
124
125
126
# File 'lib/paraspec/master.rb', line 121

def example_passed(payload)
  spec = payload[:spec]
  # ExecutionResult
  result = payload['result']
  do_example_passed(spec, result)
end

#find_example(spec) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/paraspec/master.rb', line 144

def find_example(spec)
  if spec.nil?
    #byebug
    raise ArgumentError, 'Nil spec'
  end
  example = (RSpecFacade.all_example_groups + RSpecFacade.all_examples).detect do |example|
    example.[:file_path] == spec[:file_path] &&
    example.[:scoped_id] == spec[:scoped_id]
  end
  unless example
    puts "Not found: #{spec[:file_path]}[#{spec[:scoped_id]}]"
  #byebug
    raise "Not found: #{spec[:file_path]}[#{spec[:scoped_id]}]"
  end
  example
end

#get_specObject



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/paraspec/master.rb', line 102

def get_spec
  while true
    example_group = @queue.shift
    return nil if example_group.nil?

    # TODO I am still not 100% on what should be filtered and pruned where,
    # but we shouldn't be returning a specification here unless
    # there are tests in it that a worker will run
    pruned_examples = RSpec.configuration.filter_manager.prune(example_group.examples)
    next if pruned_examples.empty?

    m = example_group.
    return {
      file_path: m[:file_path],
      scoped_id: m[:scoped_id],
    }
  end
end

#identObject



215
216
217
# File 'lib/paraspec/master.rb', line 215

def ident
  "[m]"
end

#notify_example_started(payload) ⇒ Object



128
129
130
131
# File 'lib/paraspec/master.rb', line 128

def notify_example_started(payload)
  example = find_example(payload[:spec])
  reporter.example_started(example)
end

#pingObject



81
82
83
# File 'lib/paraspec/master.rb', line 81

def ping
  true
end

#reporterObject



161
162
163
# File 'lib/paraspec/master.rb', line 161

def reporter
  @reporter ||= RSpec.configuration.reporter
end

#runObject



70
71
72
73
74
75
76
77
78
79
# File 'lib/paraspec/master.rb', line 70

def run
  Thread.new do
    #HttpServer.set(:master, self).run!(port: 6031)
    MsgpackServer.new(self).run
  end
  until @stop
    sleep 1
  end
  Paraspec.logger.debug_state("Exiting")
end

#statusObject



207
208
209
210
211
212
213
# File 'lib/paraspec/master.rb', line 207

def status
  if RSpecFacade.all_examples.any? { |example| example.execution_result.status == :failed }
    1
  else
    0
  end
end

#stopObject



85
86
87
88
# File 'lib/paraspec/master.rb', line 85

def stop
  Paraspec.logger.debug_state("Stopping")
  @stop = true
end

#stop?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/paraspec/master.rb', line 90

def stop?
  @stop
end

#suite_ok?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/paraspec/master.rb', line 94

def suite_ok?
  RSpec.configuration.reporter.send(:instance_variable_get,'@non_example_exception_count') == 0
end

#suite_startedObject



165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/paraspec/master.rb', line 165

def suite_started
  @start_time = Time.now

  notification = RSpec::Core::Notifications::StartNotification.new(
    RSpecFacade.all_examples.count, 0
  )
  RSpec.configuration.formatters.each do |f|
    if f.respond_to?(:start)
      f.start(notification)
    end
  end

  true
end