Class: ParallelTests::Test::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/parallel_tests/test/runner.rb

Direct Known Subclasses

Gherkin::Runner, RSpec::Runner

Constant Summary collapse

RuntimeLogTooSmallError =
Class.new(StandardError)

Class Method Summary collapse

Class Method Details

.command_with_seed(cmd, seed) ⇒ Object

remove old seed and add new seed



152
153
154
155
# File 'lib/parallel_tests/test/runner.rb', line 152

def command_with_seed(cmd, seed)
  clean = remove_command_arguments(cmd, '--seed')
  [*clean, '--seed', seed]
end

.default_test_folderObject



21
22
23
# File 'lib/parallel_tests/test/runner.rb', line 21

def default_test_folder
  "test"
end

.execute_command(cmd, process_number, num_processes, options) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/parallel_tests/test/runner.rb', line 88

def execute_command(cmd, process_number, num_processes, options)
  number = test_env_number(process_number, options).to_s
  env = (options[:env] || {}).merge(
    "TEST_ENV_NUMBER" => number,
    "PARALLEL_TEST_GROUPS" => num_processes.to_s,
    "PARALLEL_PID_FILE" => ParallelTests.pid_file_path
  )
  cmd = ["nice", *cmd] if options[:nice]

  # being able to run with for example `-output foo-$TEST_ENV_NUMBER` worked originally and is convenient
  cmd = cmd.map { |c| c.gsub("$TEST_ENV_NUMBER", number).gsub("${TEST_ENV_NUMBER}", number) }

  print_command(cmd, env) if report_process_command?(options) && !options[:serialize_stdout]

  execute_command_and_capture_output(env, cmd, options)
end

.execute_command_and_capture_output(env, cmd, options) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/parallel_tests/test/runner.rb', line 110

def execute_command_and_capture_output(env, cmd, options)
  popen_options = {} # do not add `pgroup: true`, it will break `binding.irb` inside the test
  popen_options[:err] = [:child, :out] if options[:combine_stderr]

  pid = nil
  output = IO.popen(env, cmd, popen_options) do |io|
    pid = io.pid
    ParallelTests.pids.add(pid)
    capture_output(io, env, options)
  end
  ParallelTests.pids.delete(pid) if pid
  exitstatus = $?.exitstatus
  seed = output[/seed (\d+)/, 1]

  output = "#{Shellwords.shelljoin(cmd)}\n#{output}" if report_process_command?(options) && options[:serialize_stdout]

  { env: env, stdout: output, exit_status: exitstatus, command: cmd, seed: seed }
end

.find_results(test_output) ⇒ Object



129
130
131
132
133
134
135
136
# File 'lib/parallel_tests/test/runner.rb', line 129

def find_results(test_output)
  test_output.lines.map do |line|
    line.chomp!
    line.gsub!(/\e\[\d+m/, '') # remove color coding
    next unless line_is_result?(line)
    line
  end.compact
end

.line_is_result?(line) ⇒ Boolean

ignores other commands runner noise

Returns:

  • (Boolean)


43
44
45
# File 'lib/parallel_tests/test/runner.rb', line 43

def line_is_result?(line)
  line =~ /\d+ failure(?!:)/
end


105
106
107
108
# File 'lib/parallel_tests/test/runner.rb', line 105

def print_command(command, env)
  env_str = ['TEST_ENV_NUMBER', 'PARALLEL_TEST_GROUPS'].map { |e| "#{e}=#{env[e]}" }.join(' ')
  puts [env_str, Shellwords.shelljoin(command)].compact.join(' ')
end

.run_tests(test_files, process_number, num_processes, options) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/parallel_tests/test/runner.rb', line 29

def run_tests(test_files, process_number, num_processes, options)
  require_list = test_files.map { |file| file.gsub(" ", "\\ ") }.join(" ")
  cmd = [
    *executable,
    '-Itest',
    '-e',
    "%w[#{require_list}].each { |f| require %{./\#{f}} }",
    '--',
    *options[:test_options]
  ]
  execute_command(cmd, process_number, num_processes, options)
end

.runtime_logObject

— usually overwritten by other runners



13
14
15
# File 'lib/parallel_tests/test/runner.rb', line 13

def runtime_log
  'tmp/parallel_runtime_test.log'
end

.summarize_results(results) ⇒ Object



146
147
148
149
# File 'lib/parallel_tests/test/runner.rb', line 146

def summarize_results(results)
  sums = sum_up_results(results)
  sums.sort.map { |word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
end

.test_env_number(process_number, options = {}) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/parallel_tests/test/runner.rb', line 138

def test_env_number(process_number, options = {})
  if process_number == 0 && !options[:first_is_1]
    ''
  else
    process_number + 1
  end
end

.test_file_nameObject



25
26
27
# File 'lib/parallel_tests/test/runner.rb', line 25

def test_file_name
  "test"
end

.test_suffixObject



17
18
19
# File 'lib/parallel_tests/test/runner.rb', line 17

def test_suffix
  /_(test|spec).rb$/
end

.tests_in_groups(tests, num_groups, options = {}) ⇒ Object

finds all tests and partitions them into groups



50
51
52
53
# File 'lib/parallel_tests/test/runner.rb', line 50

def tests_in_groups(tests, num_groups, options = {})
  tests = tests_with_size(tests, options)
  Grouper.in_even_groups_by_size(tests, num_groups, options)
end

.tests_with_size(tests, options) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/parallel_tests/test/runner.rb', line 55

def tests_with_size(tests, options)
  tests = find_tests(tests, options)

  case options[:group_by]
  when :found
    tests.map! { |t| [t, 1] }
  when :filesize
    sort_by_filesize(tests)
  when :runtime
    sort_by_runtime(
      tests, runtimes(tests, options),
      options.merge(allowed_missing: (options[:allowed_missing_percent] || 50) / 100.0)
    )
  when nil
    # use recorded test runtime if we got enough data
    runtimes = begin
      runtimes(tests, options)
    rescue StandardError
      []
    end
    if runtimes.size * 1.5 > tests.size
      puts "Using recorded test runtime" unless options[:quiet]
      sort_by_runtime(tests, runtimes)
    else
      sort_by_filesize(tests)
    end
  else
    raise ArgumentError, "Unsupported option #{options[:group_by]}"
  end

  tests
end