Class: MiniAutobot::Parallel

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_autobot/parallel.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(simultaneous_jobs, all_tests) ⇒ Parallel

Returns a new instance of Parallel.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/mini_autobot/parallel.rb', line 6

def initialize(simultaneous_jobs, all_tests)
  @start_time = Time.now

  @result_dir = 'logs/tap_results'

  connector = MiniAutobot.settings.connector
  @on_sauce = true if connector.include? 'saucelabs'
  @platform = connector.split(':')[2] || ''

  @simultaneous_jobs = simultaneous_jobs
  @simultaneous_jobs = 10 if run_on_mac? # saucelabs account limit for parallel is 10 for mac
  @all_tests = all_tests

  @pids = []
  @static_run_command = "mini_autobot -c #{MiniAutobot.settings.connector} -e #{MiniAutobot.settings.env}"
  if MiniAutobot.settings.rerun_failure
    @static_run_command += " -R #{MiniAutobot.settings.rerun_failure}"
  end
  if MiniAutobot.settings.google_sheets?
    @static_run_command += " -g #{MiniAutobot.settings.google_sheet}"
  end
  unless MiniAutobot.settings.feature_flips.empty?
    @static_run_command += " -f #{MiniAutobot.settings.feature_flips}"
  end
  tap_reporter_path = MiniAutobot.gem_root.join('lib/tapout/custom_reporters/fancy_tap_reporter.rb')
  @pipe_tap = "--tapy | tapout --no-color -r #{tap_reporter_path.to_s} fancytap"
end

Instance Attribute Details

#all_testsObject (readonly)

Returns the value of attribute all_tests.



4
5
6
# File 'lib/mini_autobot/parallel.rb', line 4

def all_tests
  @all_tests
end

#simultaneous_jobsObject (readonly)

Returns the value of attribute simultaneous_jobs.



4
5
6
# File 'lib/mini_autobot/parallel.rb', line 4

def simultaneous_jobs
  @simultaneous_jobs
end

Instance Method Details

#aggregate_tap_resultsObject

Aggregate all individual test_*.t files replace them with one file - test_aggregated_result.tap so they will be considered as one test plan by tap result parser



78
79
80
81
82
83
84
85
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
# File 'lib/mini_autobot/parallel.rb', line 78

def aggregate_tap_results
  results_count = Dir.glob("#{@result_dir}/*.t").size
  File.open("#{@result_dir}/test_aggregated_result.tap", 'a+') do |result_file|
    result_stats = {
        'pass' => 0,
        'fail' => 0,
        'errs' => 0,
        'todo' => 0,
        'omit' => 0
    }
    result_stats_line_start = '  # 1 tests:'
    result_file.puts "1..#{results_count}"
    file_count = 0
    Dir.glob("#{@result_dir}/*.t") do |filename|
      file_count += 1
      File.open(filename, 'r') do |file|
        breakpoint_line = 0
        file.each_with_index do |line, index|
          next if index == 0 || (breakpoint_line > 0 && index > breakpoint_line)
          if line.start_with?(result_stats_line_start)
            pass, fail, errs, todo, omit = line.match(/(\d+) pass, (\d+) fail, (\d+) errs, (\d+) todo, (\d+) omit/).captures
            one_test_result = {
                'pass' => pass.to_i,
                'fail' => fail.to_i,
                'errs' => errs.to_i,
                'todo' => todo.to_i,
                'omit' => omit.to_i
            }
            result_stats = result_stats.merge(one_test_result) { |k, total, one| total + one }
            breakpoint_line = index
          elsif line.strip == '#'
            next
          else
            if line.start_with?('ok 1') || line.start_with?('not ok 1')
              line_begin, line_end = line.split('1 -')
              result_file.puts [line_begin, line_end].join("#{file_count} -")
            else
              result_file.puts line
            end
          end
        end
      end
      File.delete(filename)
    end
    result_file.puts '  #'
    result_file.puts "  # #{results_count} tests: #{result_stats['pass']} pass, #{result_stats['fail']} fail, #{result_stats['errs']} errs, #{result_stats['todo']} todo, #{result_stats['omit']} omit"
    result_file.puts "  # [00:00:00.00 0.00t/s 00.0000s/t] Finished at: #{Time.now}"
  end
end

#clean_result!Object

remove all results files under @result_dir if there’s any

Raises:

  • (Exception)


41
42
43
44
45
46
47
# File 'lib/mini_autobot/parallel.rb', line 41

def clean_result!
  raise Exception, '@result_dir is not set' if @result_dir.nil?
  unless Dir.glob("#{@result_dir}/*").empty?
    FileUtils.rm_rf(Dir.glob("#{@result_dir}/*"))
  end
  puts "Cleaning result files.\n"
end

#count_autobot_processObject



128
129
130
131
# File 'lib/mini_autobot/parallel.rb', line 128

def count_autobot_process
  counting_process_output = IO.popen "ps -ef | grep 'bin/#{@static_run_command}' -c"
  counting_process_output.readlines[0].to_i - 1 # minus grep process
end

#keep_running_full(all_to_run) ⇒ Object

recursively keep running ##simultaneous_jobs number of tests in parallel exit when no test left to run



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/mini_autobot/parallel.rb', line 165

def keep_running_full(all_to_run)
  running_subprocess_count = count_autobot_process - 1 # minus parent process
  puts "WARNING: running_subprocess_count = #{running_subprocess_count}
        is more than what it is supposed to run(#{simultaneous_jobs}),
        notify mini_autobot maintainers" if running_subprocess_count > simultaneous_jobs + 1
  while running_subprocess_count >= simultaneous_jobs
    sleep 5
    running_subprocess_count = count_autobot_process - 1
  end
  to_run_count = simultaneous_jobs - running_subprocess_count
  tests_to_run = all_to_run.slice!(0, to_run_count)

  run_test_set(tests_to_run)

  keep_running_full(all_to_run) if all_to_run.size > 0
end

#remove_redundant_tapObject



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/mini_autobot/parallel.rb', line 49

def remove_redundant_tap
  ever_failed_tests_file = "#{@result_dir}/ever_failed_tests.json"
  if File.file? ever_failed_tests_file
    data_hash = JSON.parse(File.read(ever_failed_tests_file))
    data_hash.keys.each do |test|
      if test.start_with? 'test_'
        tap_result_file = "#{@result_dir}/#{test}.t"
        result_lines = IO.readlines(tap_result_file)
        last_tap_start_index = 0
        last_tap_end_index = result_lines.size - 1
        result_lines.each_with_index do |l, index|
          last_tap_start_index = index if l.delete!("\n") == '1..1'
        end
        File.open(tap_result_file, 'w') do |f|
          f.puts result_lines[last_tap_start_index..last_tap_end_index]
        end
        puts "Processed #{tap_result_file}"
      else
        next
      end
    end
  else
    puts "==> File #{ever_failed_tests_file} doesn't exist - all tests passed!"
  end
end

#run_in_parallel!Object

run multiple commands with logging to start multiple tests in parallel n = number of tests will be running in parallel

Parameters:

  • (Integer, Array)


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/mini_autobot/parallel.rb', line 136

def run_in_parallel!
  size = all_tests.size
  if size <= simultaneous_jobs
    run_test_set(all_tests)
    puts "CAUTION! All #{size} tests are starting at the same time!"
    puts "will not really run it since computer will die" if size > 30
    sleep 20
  else
    first_test_set = all_tests[0, simultaneous_jobs]
    all_to_run = all_tests[simultaneous_jobs..(all_tests.size - 1)]
    run_test_set(first_test_set)
    keep_running_full(all_to_run)
  end

  Process.waitall
  puts "\nAll Complete! Started at #{@start_time} and finished at #{Time.now}\n"
end

#run_on_mac?boolean

return true only if specified to run on mac in connector

Returns:

  • (boolean)


36
37
38
# File 'lib/mini_autobot/parallel.rb', line 36

def run_on_mac?
  @platform.include?('osx')
end

#run_test_set(test_set) ⇒ Object

runs each test from a test set in a separate child process



155
156
157
158
159
160
161
# File 'lib/mini_autobot/parallel.rb', line 155

def run_test_set(test_set)
  test_set.each do |test|
    run_command = "#{@static_run_command} -n #{test} #{@pipe_tap} > #{@result_dir}/#{test}.t"
    pipe = IO.popen(run_command)
    puts "Running #{test}  #{pipe.pid}"
  end
end

#wait_all_done_saucelabsObject

Deprecated.

Too time consuming and fragile, should use more native wait/check of Process



198
199
200
201
202
203
204
205
206
# File 'lib/mini_autobot/parallel.rb', line 198

def wait_all_done_saucelabs
  size = all_tests.size
  job_statuses = saucelabs_last_n_statuses(size)
  while job_statuses.include?('in progress')
    puts "There are tests still running, waiting..."
    sleep 20
    job_statuses = saucelabs_last_n_statuses(size)
  end
end

#wait_for_pids(pids) ⇒ Object

Deprecated.

Use more native wait/check of Process



183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/mini_autobot/parallel.rb', line 183

def wait_for_pids(pids)
  running_pids = pids # assume all pids are running at this moment
  while running_pids.size > 1
    sleep 5
    puts "running_pids = #{running_pids}"
    running_pids.each do |pid|
      unless process_running?(pid)
        puts "#{pid} is not running, removing it from pool"
        running_pids.delete(pid)
      end
    end
  end
end