Class: ParallelizedSpecs

Inherits:
Object
  • Object
show all
Defined in:
lib/parallelized_specs.rb,
lib/parallelized_specs/grouper.rb,
lib/parallelized_specs/railtie.rb

Defined Under Namespace

Classes: ExampleRerunFailuresLogger, FailuresFormatter, Grouper, OutcomeBuilder, Railtie, RuntimeLogger, SlowestSpecLogger, SpecErrorCountLogger, SpecErrorLogger, SpecFailuresLogger, SpecLoggerBase, SpecRuntimeLogger, SpecStartFinishLogger, SpecSummaryLogger, TrendingExampleFailures

Constant Summary collapse

VERSION =
File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
SpecLoggerBaseBase =
base

Class Method Summary collapse

Class Method Details

.abort_reruns(code, result = "", l = "") ⇒ Object



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/parallelized_specs.rb', line 391

def self.abort_reruns(code, result = "", l = "")
  case
    when code == 1
      print_failures("#{Rails.root}/tmp/parallel_log/error.log")
      abort "SEVERE: shared specs currently are not eligiable for reruns, marking build as a failure"
    when code == 2 # <--- won't ever happen, 2 and 3 are duplicate clean up on refactor
      update_rerun_summary(l, "FAILED", result)
      print_failures(@failure_summary, "RERUN")
      abort "SEVERE: spec didn't actually run, ending rerun process early"
    when code == 3
      update_rerun_summary(l, "FAILED", result)
      print_failures(@failure_summary, "RERUN")
      abort "SEVERE: the spec failed to run on the rerun try, marking build as failed"
    when code == 4
      update_rerun_summary(l, "FAILED", result)
      print_failures(@failure_summary, "RERUN")
      abort "SEVERE: unexpected outcome on the rerun, marking build as a failure"
    when code == 5
      abort "SEVERE: #{@error_count} errors, but the build failed, errors were not written to the file or there is something else wrong, marking build as a failure"
    when code == 6
      print_failures("#{Rails.root}/tmp/parallel_log/error.log")
      abort "SEVERE: #{@error_count} errors are to many to rerun, marking the build as a failure. Max errors defined for this build is #{@max_reruns}"
    when code == 7
      puts "#Total errors #{@error_count}"
      abort "SEVERE: unexpected error information, please check errors are being written to file correctly"
    when code == 8
      print_failures(@failure_summary, "RERUN")
      File.open("#{Rails.root}/tmp/failure_cause.log", 'a+') { |f| f.write "#{@rerun_failures.count} failures" }
      abort "SEVERE: some specs failed on rerun, the build will be marked as failed"
    when code == 9
      abort "SEVERE: unexpected situation on rerun, marking build as failure"
    else
      abort "SEVERE: unhandled abort_reruns code"
  end
end

.bundler_enabled?Boolean

copied from github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile



310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/parallelized_specs.rb', line 310

def self.bundler_enabled?
  return true if Object.const_defined?(:Bundler)

  previous = nil
  current = File.expand_path(Dir.pwd)

  until !File.directory?(current) || current == previous
    filename = File.join(current, "Gemfile")
    return true if File.exists?(filename)
    current, previous = File.expand_path("..", current), current
  end

  false
end

.calculate_error_countObject



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/parallelized_specs.rb', line 444

def self.calculate_error_count()
  @error_count = %x{wc -l "#{@filename}"}.match(/\d*[^\D]/).to_s #counts the number of lines in the file
  @error_count = @error_count.to_i
  puts "INFO: error count = #@error_count"
  ENV["RERUNS"] != nil ? @max_reruns = ENV["RERUNS"].to_i : @max_reruns = 9

  if !@error_count.between?(1, @max_reruns)
    puts "INFO: total errors are not in rerun eligibility range"
    case
      when @error_count == 0
        puts "INFO: 0 errors build being aborted"
        abort_reruns(5)
      when @error_count > @max_reruns
        puts "INFO: error count has exceeded maximum errors of #{@max_reruns}"
        abort_reruns(6)
      else
        abort_reruns(7)
    end
  else
    @error_count
  end
end

.calculate_total_spec_detailsObject



139
140
141
142
143
144
145
146
147
148
# File 'lib/parallelized_specs.rb', line 139

def self.calculate_total_spec_details
  spec_total_details = [0, 0, 0]
  File.open("#{Rails.root}/tmp/parallel_log/total_specs.txt").each_line do |count|
    thread_spec_details = count.split("*")
    spec_total_details[0] += thread_spec_details[0].to_i
    spec_total_details[1] += thread_spec_details[1].to_i
    spec_total_details[2] += thread_spec_details[2].to_i
  end
  [spec_total_details[0], spec_total_details[1], spec_total_details[2]]
end

.determine_rerun_eligibilityObject



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/parallelized_specs.rb', line 467

def self.determine_rerun_eligibility
  @error_count = calculate_error_count
  puts "INFO: total errors #{@error_count}"

  File.open(@filename).each_line do |line|
    if line =~ /spec\/selenium\/helpers/
      abort_reruns(1)
    else
      puts "INFO: the following spec is eligible for reruns #{line}"
      @rerun_specs.push line
    end
  end
  puts "INFO: failures meet rerun criteria \n INFO: rerunning #{@error_count} examples"
  File.open("#{Rails.root}/tmp/parallel_log/error.log", 'a+') { |f| f.puts("\n\n\n\n\n *****RERUN FAILURES*****\n") }
end

.determine_rerun_outcomeObject



516
517
518
519
520
521
522
523
524
# File 'lib/parallelized_specs.rb', line 516

def self.determine_rerun_outcome
  if @rerun_failures.count > 0
    abort_reruns(8)
  elsif @rerun_passes.count >= @error_count
    pass_reruns
  else
    abort_reruns(9)
  end
end

.directory_cleanup_and_create(dir) ⇒ Object



171
172
173
174
175
176
177
# File 'lib/parallelized_specs.rb', line 171

def self.directory_cleanup_and_create(dir)
  if File.directory?(dir) || File.exists?(dir)
    `rm -rf #{dir} && mkdir #{dir}`
  else
    `mkdir #{dir}`
  end
end

.executableObject



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/parallelized_specs.rb', line 25

def self.executable
  cmd = if File.file?("script/spec")
          "script/spec"
        elsif bundler_enabled?
          cmd = (run("bundle show rspec") =~ %r{/rspec-1[^/]+$} ? "spec" : "rspec")
          "bundle exec #{cmd}"
        else
          %w[spec rspec].detect { |cmd| system "#{cmd} --version > /dev/null 2>&1" }
        end
  cmd or raise("Can't find executables rspec or spec")
end

.execute_command(cmd, process_number, options) ⇒ Object



243
244
245
246
247
248
249
250
# File 'lib/parallelized_specs.rb', line 243

def self.execute_command(cmd, process_number, options)
  cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
  f = open("|#{cmd}", 'r')
  output = fetch_output(f, options)
  f.close
  puts "Exit status for process #{process_number} #{$?.exitstatus}"
  {:stdout => output, :exit_status => $?.exitstatus}
end

.execute_parallel_db(cmd, options = {}) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/parallelized_specs.rb', line 61

def self.execute_parallel_db(cmd, options={})
  count = options[:count].to_i || Parallel.processor_count
  count = Parallel.processor_count if count == 0
  runs = (0...count).to_a
  results = if options[:non_parallel]
              runs.map do |i|
                execute_command(cmd, i, options)
              end
            else
              Parallel.map(runs, :in_processes => count) do |i|
                execute_command(cmd, i, options)
              end
            end.flatten
  abort if results.any? { |r| r[:exit_status] != 0 }
end

.execute_parallel_specs(options) ⇒ Object



77
78
79
80
81
82
83
84
# File 'lib/parallelized_specs.rb', line 77

def self.execute_parallel_specs(options)
  if options[:files].to_s.empty?
    tests = find_tests(Rails.root, options)
    run_specs(tests, options)
  else
    run_specs(options[:files], options)
  end
end

.false_positive_sniffer(num_processes) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/parallelized_specs.rb', line 191

def self.false_positive_sniffer(num_processes)
  if Dir.glob("#{Rails.root}/tmp/parallel_log/spec_count/{*,.*}").count == 2 && Dir.glob("#{Rails.root}/tmp/parallel_log/thread_started/{*,.*}").count == num_processes + 2
    (puts "INFO: All threads completed")
  elsif Dir.glob("#{Rails.root}/tmp/parallel_log/thread_started/{*,.*}").count != num_processes + 2
    File.open("#{Rails.root}/tmp/parallel_log/error.log", 'a+') { |f| f.write "\n\n\n syntax errors" }
    File.open("#{Rails.root}/tmp/failure_cause.log", 'a+') { |f| f.write "syntax errors" }
    abort "SEVERE: one or more threads didn't get started by rspec, this may be caused by a syntax issue in specs, check logs right before specs start running"
  else
    threads = Dir["#{Rails.root}/tmp/parallel_log/spec_count/*"]
    threads.each do |t|
      failed_thread = t.match(/\d/).to_s
      if failed_thread == "1"
        last_spec = IO.readlines("#{Rails.root}/tmp/parallel_log/thread_.log")[-1]
        puts "INFO: Thread 1 last spec to start running \n #{last_spec}"
        File.open("#{Rails.root}/tmp/parallel_log/error.log", 'a+') { |f| f.write "\n\n\n\nrspec thread #{failed_thread} failed to complete\n the last spec to try to run was #{last_spec}" }
      else
        last_spec = IO.readlines("#{Rails.root}/tmp/parallel_log/thread_#{failed_thread}.log")[-1]
        puts "INFO: Thread #{failed_thread} last spec to start running \n #{last_spec}"
        File.open("#{Rails.root}/tmp/parallel_log/error.log", 'a+') { |f| f.write "\n\n\n\nrspec thread #{failed_thread} failed to complete\n the last spec to try to run was #{last_spec}" }
      end
    end
    File.open("#{Rails.root}/tmp/failure_cause.log", 'a+') { |f| f.write "rspec thread failed to complete" }
    abort "SEVERE: One or more threads have failed to complete, this may be caused by a rspec runtime crashing prematurely" #works on both 1.8.7\1.9.3
  end
end

.fetch_output(process, options) ⇒ Object

read output of the process and print in in chucks



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/parallelized_specs.rb', line 281

def self.fetch_output(process, options)
  all = ''
  buffer = ''
  timeout = options[:chunk_timeout] || 0.2
  flushed = Time.now.to_f

  while (char = process.getc)
    char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
    all << char

    # print in chunks so large blocks stay together
    now = Time.now.to_f
    buffer << char
    if flushed + timeout < now
      print buffer
      STDOUT.flush
      buffer = ''
      flushed = now
    end
  end

  # print the remainder
  print buffer
  STDOUT.flush

  all
end

.find_results(test_output) ⇒ Object



252
253
254
255
256
257
258
# File 'lib/parallelized_specs.rb', line 252

def self.find_results(test_output)
  test_output.split("\n").map { |line|
    line = line.gsub(/\.|F|\*/, '')
    next unless line_is_result?(line)
    line
  }.compact
end

.find_tests(root, options = {}) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/parallelized_specs.rb', line 347

def self.find_tests(root, options={})
  if root.is_a?(Array)
    root
  else
    # follow one symlink and direct children
    # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
    files = Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniq
    files = files.map { |f| f.sub(root+'/', '') }
    files = files.grep(/#{options[:pattern]}/)
    files.map { |f| "/#{f}" }
  end
end

.formatter_directory_management(formatters) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
# File 'lib/parallelized_specs.rb', line 159

def self.formatter_directory_management(formatters)
  FileUtils.mkdir_p('parallel_log') if !File.directory?('tmp/parallel_log')
  begin
    ['tmp/parallel_log/spec_count', 'tmp/parallel_log/failed_specs', 'tmp/parallel_log/thread_started', 'tmp/parallel_log/total_specs.txt'].each do |dir|
      directory_cleanup_and_create(dir)
    end
  rescue SystemCallError
    $stderr.print "directory management error " + $!
    raise
  end
end

.formatters_setupObject



150
151
152
153
154
155
156
157
# File 'lib/parallelized_specs.rb', line 150

def self.formatters_setup
  formatters = []
  File.open("#{Rails.root}/spec/spec.opts").each_line do |line|
    formatters << line
  end
  formatter_directory_management(formatters)
  formatters
end

.line_is_result?(line) ⇒ Boolean



325
326
327
# File 'lib/parallelized_specs.rb', line 325

def self.line_is_result?(line)
  line =~ /\d+ failure/
end

.parse_rake_args(args) ⇒ Object

parallel:spec[:count, :pattern, :options]



218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/parallelized_specs.rb', line 218

def self.parse_rake_args(args)
  # order as given by user
  args = [args[:count], args[:pattern]]

  # count given or empty ?
  count = args.shift if args.first.to_s =~ /^\d*$/
  num_processes = count.to_i unless count.to_s.empty?
  num_processes ||= ENV['PARALLEL_TEST_PROCESSORS'].to_i if ENV['PARALLEL_TEST_PROCESSORS']
  num_processes ||= Parallel.processor_count

  pattern = args.shift

  [num_processes.to_i, pattern.to_s]
end

.parse_result(result) ⇒ Object



365
366
367
368
369
370
371
372
# File 'lib/parallelized_specs.rb', line 365

def self.parse_result(result)
  puts "INFO: this is the result\n#{result}"
  #can't just use exit code, if specs fail to start it will pass or if a spec isn't found, and sometimes rspec 1 exit codes aren't right
  examples = result.match(/(\d) example/).to_a
  failures = result.match(/(\d) failure/).to_a
  @examples = examples.last.to_i
  @failures = failures.last.to_i
end

.pass_rerunsObject



439
440
441
442
# File 'lib/parallelized_specs.rb', line 439

def self.pass_reruns()
  print_failures(@failure_summary, "RERUN")
  puts "INFO: rerun summary all rerun examples passed, rspec will mark this build as passed"
end

.populate_slowest_specs(spec_durations, file) ⇒ Object



552
553
554
555
556
557
558
559
560
561
562
563
564
# File 'lib/parallelized_specs.rb', line 552

def self.populate_slowest_specs(spec_durations, file)
  slowest_specs = []
  ENV["MAX_TIME"] != nil ? max_time = ENV["MAX_TIME"].to_i : max_time = 30
  spec_durations.each do |spec|
    time = spec.match(/.*\d/).to_s
    if time.to_f >= max_time
      slowest_specs << spec
    end
  end
  slowest_specs.each do |slow_spec|
    File.open(file, 'a+') { |f| f.puts slow_spec }
  end
end


383
384
385
386
387
388
389
# File 'lib/parallelized_specs.rb', line 383

def self.print_failures(failure_summary, state = "")
  puts "*****************INFO: #{state} SUMMARY*****************\n"
  state == "RERUN" ? puts("INFO: outcomes of the specs that were rerun") : puts("INFO: summary of build failures")
  file = File.open(failure_summary, "r")
  content = file.read
  puts content
end

.rerunObject



532
533
534
535
536
# File 'lib/parallelized_specs.rb', line 532

def self.rerun()
  puts "INFO: beginning the failed specs rerun process"
  runtime_setup
  start_reruns
end

.rerun_initializerObject



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/parallelized_specs.rb', line 179

def self.rerun_initializer()

  if @total_failures != 0 && !File.zero?("#{Rails.root}/tmp/parallel_log/rspec.failures") # works on both 1.8.7\1.9.3
    puts "INFO: some specs failed, about to start the rerun process\n...\n..\n."
    ParallelizedSpecs.rerun()
  else
    #works on both 1.8.7\1.9.3
    puts "ERROR: the build had failures but the rspec.failures file is null"
    abort "SEVERE: SPECS Failed"
  end
end

.rerun_spec(spec) ⇒ Object



374
375
376
377
378
379
380
381
# File 'lib/parallelized_specs.rb', line 374

def self.rerun_spec(spec)
  puts "INFO: #{spec} will be ran and marked as a success if it passes"
  @examples = 0
  @failures = 0
  result = %x[DISPLAY=:99 bundle exec rake spec #{spec}]
  parse_result(result)
  result
end

.rspec_1_colorObject



43
44
45
# File 'lib/parallelized_specs.rb', line 43

def self.rspec_1_color
  'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' if $stdout.tty?
end

.rspec_2_colorObject



47
48
49
# File 'lib/parallelized_specs.rb', line 47

def self.rspec_2_color
  '--color --tty ' if $stdout.tty?
end

.run(cmd) ⇒ Object

so it can be stubbed.…



39
40
41
# File 'lib/parallelized_specs.rb', line 39

def self.run(cmd)
  `#{cmd}`
end

.run_specs(tests, options) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/parallelized_specs.rb', line 86

def self.run_specs(tests, options)
  formatters = formatters_setup
  @outcome_builder_enabled = formatters.any? { |formatter| formatter.match(/OutcomeBuilder/) }
  @reruns_enabled = formatters.any? { |formatter| formatter.match(/FailuresFormatter/) }
  @slow_specs_enabled = formatters.any? { |formatter| formatter.match(/SlowestSpecLogger/) }
  num_processes = options[:count] || Parallel.processor_count
  name = 'spec'

  start = Time.now

  tests_folder = 'spec'
  tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
  if !tests.is_a?(Array)
    files_array = tests.split(/ /)
  end
  groups = tests_in_groups(files_array || tests || tests_folder, num_processes, options)

  num_processes = groups.size

  #adjust processes to groups
  abort "SEVERE: no #{name}s found!" if groups.size == 0

  num_tests = groups.inject(0) { |sum, item| sum + item.size }
  puts "INFO: #{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"

  test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
    run_tests(group, groups.index(group), options)
  end
  failed = test_results.any? { |result| result[:exit_status] != 0 } #ruby 1.8.7 works breaks on 1.9.3
  slowest_spec_determination("#{Rails.root}/tmp/parallel_log/slowest_specs.log") if @slow_specs_enabled

  results = find_results(test_results.map { |result| result[:stdout] }*"")

  if @outcome_builder_enabled
    puts "INFO: OutcomeBuilder is enabled now checking for false positives"
    @total_specs, @total_failures, @total_pending = calculate_total_spec_details
    puts "INFO: Total specs run #{@total_specs} failed specs  #{@total_failures} pending specs #{@total_pending}\n INFO: Took #{Time.now - start} seconds"
    #determines if any tricky conditions happened that can cause false positives and offers logging into what was the last spec to start or finishing running
    false_positive_sniffer(num_processes)
    if @reruns_enabled && @total_failures != 0
      puts "INFO: RERUNS are enabled"
      rerun_initializer
    else
      abort("SEVERE: #{name.capitalize}s Failed") if @total_failures != 0
    end
  else
    puts "WARNING: OutcomeBuilder is disabled not checking for false positives its likely things like thread failures and rspec non 0 exit codes will cause false positives"
    puts "INFO: #{summarize_results(results)} Took #{Time.now - start} seconds"
    abort("SEVERE: #{name.capitalize}s Failed") if failed
  end
  puts "INFO: marking build as PASSED"
end

.run_tests(test_files, process_number, options) ⇒ Object



18
19
20
21
22
23
# File 'lib/parallelized_specs.rb', line 18

def self.run_tests(test_files, process_number, options)
  exe = executable # expensive, so we cache
  version = (exe =~ /\brspec\b/ ? 2 : 1)
  cmd = "#{rspec_1_color if version == 1}#{exe} #{options[:test_options]} #{rspec_2_color if version == 2}#{spec_opts(version)} #{test_files*' '}"
  execute_command(cmd, process_number, options)
end

.runtime_logObject



264
265
266
# File 'lib/parallelized_specs.rb', line 264

def self.runtime_log
  'tmp/parallelized_runtime_test.log'
end

.runtime_setupObject



526
527
528
529
530
# File 'lib/parallelized_specs.rb', line 526

def self.runtime_setup
  @rerun_specs = []
  @filename = "#{Rails.root}/tmp/parallel_log/rspec.failures"
  @failure_summary = "#{Rails.root}/tmp/parallel_log/rerun_failure_summary.log"
end

.slowest_spec_determination(file) ⇒ Object



538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/parallelized_specs.rb', line 538

def self.slowest_spec_determination(file)
  if File.exists?(file)
    spec_durations = []
    File.open(file).each_line do |line|
      spec_durations << line
    end

    File.open(file, 'w') { |f| f.truncate(0) }
    populate_slowest_specs(spec_durations, file)
  else
    puts "slow spec profiling was not enabled"
  end
end

.spec_opts(rspec_version) ⇒ Object



51
52
53
54
55
# File 'lib/parallelized_specs.rb', line 51

def self.spec_opts(rspec_version)
  options_file = %w(spec/parallelized_spec.opts spec/spec.opts).detect { |f| File.file?(f) }
  return unless options_file
  "-O #{options_file}"
end

.start_rerunsObject



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/parallelized_specs.rb', line 483

def self.start_reruns
  determine_rerun_eligibility

  @rerun_failures ||= []
  @rerun_passes ||= []

  @rerun_specs.each do |l|
    puts "INFO: starting next spec"
    result = rerun_spec(l)

    puts "INFO: determining if the spec passed or failed"
    puts "INFO: examples run = #@examples examples failed = #@failures"


    if @examples == 0 && @failures == 0 #when specs fail to run it exits with 0 examples, 0 failures and won't be matched by the previous regex
      abort_reruns(3, result, l)
    elsif @failures > 0
      update_failed(l, result)
    elsif @examples > 0 && @failures == 0
      update_passed(l)
    else
      abort_reruns(4, result, l)
    end

    puts "INFO: spec finished and has updated rerun state"
  end
  puts "INFO: reruns have completed calculating if build has passed or failed"
  puts "INFO: total failed specs #{@rerun_failures.count}"
  puts "INFO: total passed specs #{@rerun_passes.count}"
  determine_rerun_outcome
end

.summarize_results(results) ⇒ Object



268
269
270
271
272
273
274
275
276
# File 'lib/parallelized_specs.rb', line 268

def self.summarize_results(results)
  results = results.join(' ').gsub(/s\b/, '') # combine and singularize results
  counts = results.scan(/(\d+) (\w+)/)
  sums = counts.inject(Hash.new(0)) do |sum, (number, word)|
    sum[word] += number.to_i
    sum
  end
  sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
end

.test_env_number(process_number) ⇒ Object



260
261
262
# File 'lib/parallelized_specs.rb', line 260

def self.test_env_number(process_number)
  process_number == 0 ? '' : process_number + 1
end

.test_suffixObject



57
58
59
# File 'lib/parallelized_specs.rb', line 57

def self.test_suffix
  "_spec.rb"
end

.tests_in_groups(tests, num_groups, options) ⇒ Object

finds all tests and partitions them into groups



234
235
236
237
238
239
240
241
# File 'lib/parallelized_specs.rb', line 234

def self.tests_in_groups(tests, num_groups, options)
  if options[:no_sort]
    Grouper.in_groups(tests, num_groups)
  else
    tests = with_runtime_info(tests)
    Grouper.in_even_groups_by_size(tests, num_groups, options)
  end
end

.update_failed(l, result) ⇒ Object



427
428
429
430
431
# File 'lib/parallelized_specs.rb', line 427

def self.update_failed(l, result)
  puts "WARNING: the example failed again"
  update_rerun_summary(l, "FAILED", result)
  @rerun_failures << l
end

.update_passed(l) ⇒ Object



433
434
435
436
437
# File 'lib/parallelized_specs.rb', line 433

def self.update_passed(l)
  update_rerun_summary(l, "PASSED")
  puts "INFO: the example passed and is being marked as a success"
  @rerun_passes << l
end

.update_rerun_summary(l, outcome, stack = "") ⇒ Object



360
361
362
363
# File 'lib/parallelized_specs.rb', line 360

def self.update_rerun_summary(l, outcome, stack = "")
  File.open(@failure_summary, 'a+') { |f| f.puts("Outcome #{outcome} for #{l}\n #{stack}") }
  File.open("#{Rails.root}/tmp/parallel_log/error.log", 'a+') { |f| f.puts("Outcome #{outcome} for #{l}\n #{stack}") } if outcome == "FAILED"
end

.with_runtime_info(tests) ⇒ Object



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/parallelized_specs.rb', line 329

def self.with_runtime_info(tests)
  lines = File.read(runtime_log).split("\n") rescue []

  # use recorded test runtime if we got enough data
  if lines.size * 1.5 > tests.size
    puts "Using recorded test runtime"
    times = Hash.new(1)
    lines.each do |line|
      test, time = line.split(":")
      next unless test and time
      times[File.expand_path(test)] = time.to_f
    end
    tests.sort.map { |test| [test, times[test]] }
  else # use file sizes
    tests.sort.map { |test| [test, File.stat(test).size] }
  end
end