Class: TestRunner::Executor

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tests, **opts) ⇒ Executor

Returns a new instance of Executor.

Parameters:

  • tests (Array<Test>)
  • opts (Hash)

Options Hash (**opts):

  • :state_dir (String)
  • :jobs (Integer)
  • :default_timeout (Integer)
  • :stop_on_failure (Boolean)
  • :destructive (Boolean)


14
15
16
17
18
19
20
21
22
23
# File 'lib/test-runner/executor.rb', line 14

def initialize(tests, **opts)
  @tests = tests
  @opts = opts
  @workers = []
  @queue = Queue.new
  tests.each_with_index { |t, i| @queue << [i, t] }
  @results = []
  @stop_work = false
  @mutex = Mutex.new
end

Instance Attribute Details

#mutexObject (readonly, protected)

Returns the value of attribute mutex.



86
87
88
# File 'lib/test-runner/executor.rb', line 86

def mutex
  @mutex
end

#optsObject (readonly)

Returns the value of attribute opts.



5
6
7
# File 'lib/test-runner/executor.rb', line 5

def opts
  @opts
end

#queueObject (readonly, protected)

Returns the value of attribute queue.



86
87
88
# File 'lib/test-runner/executor.rb', line 86

def queue
  @queue
end

#resultsObject (readonly)

Returns the value of attribute results.



5
6
7
# File 'lib/test-runner/executor.rb', line 5

def results
  @results
end

#testsObject (readonly)

Returns the value of attribute tests.



5
6
7
# File 'lib/test-runner/executor.rb', line 5

def tests
  @tests
end

#workersObject (readonly, protected)

Returns the value of attribute workers.



86
87
88
# File 'lib/test-runner/executor.rb', line 86

def workers
  @workers
end

Instance Method Details

#log(msg) ⇒ Object (protected)



184
185
186
# File 'lib/test-runner/executor.rb', line 184

def log(msg)
  mutex.synchronize { puts "[#{Time.now}] #{msg}" }
end

#runArray<TestResult>

Returns:



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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/test-runner/executor.rb', line 26

def run
  log("Running #{tests.length} tests, #{opts[:jobs]} at a time")
  log("State directory is #{state_dir}")
  t1 = Time.now

  opts[:jobs].times do |i|
    start_worker(i)
  end

  wait_for_workers

  log("Run #{results.length} tests in #{(Time.now - t1).round(2)} seconds")

  expected_successful = results.select do |r|
    r.expected_to_succeed? && r.successful?
  end

  expected_failed = results.select do |r|
    r.expected_to_fail? && r.failed?
  end

  unexpected_failed = results.select do |r|
    r.expected_to_succeed? && r.failed?
  end

  unexpected_successful = results.select do |r|
    r.expected_to_fail? && r.successful?
  end

  if expected_successful.any?
    log("#{expected_successful.length} tests successful")
  end

  if expected_failed.any?
    log("#{expected_failed.length} tests failed as expected")
  end

  if unexpected_failed.any?
    log("#{unexpected_failed.length} tests should have succeeded, but failed")
  end

  if unexpected_successful.any?
    log("#{unexpected_successful.length} tests should have failed, but succeeded")
  end

  if unexpected_failed.any?
    log("Unexpectedly failed tests:\n#{unexpected_failed.map { |r| "  #{r.test.path}" }.join("\n")}")
    puts
  end

  if unexpected_successful.any?
    log("Unexpectedly successful tests:\n#{unexpected_successful.map { |r| "  #{r.test.path}" }.join("\n")}")
    puts
  end

  results
end

#run_test(test) ⇒ Object (protected)



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/test-runner/executor.rb', line 132

def run_test(test)
  t1 = Time.now
  dir = test_state_dir(test)

  pid = Process.fork do
    FileUtils.mkdir_p(dir)

    out = File.open(File.join(dir, 'test-runner.log'), 'w')
    $stdout.reopen(out)
    $stderr.reopen(out)
    $stdin.close

    ev = TestRunner::TestEvaluator.new(
      test,
      state_dir: dir,
      sock_dir: test_sock_dir,
      default_timeout: opts[:default_timeout],
      destructive: opts[:destructive]
    )
    ev.run
  end

  Process.wait(pid)

  TestResult.new(
    test,
    $?.exitstatus == 0,
    Time.now - t1,
    dir
  )
end

#run_worker(_w_i) ⇒ Object (protected)



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
# File 'lib/test-runner/executor.rb', line 96

def run_worker(_w_i)
  loop do
    return if stop_work?

    begin
      i, t = queue.pop(true)
    rescue ThreadError
      return
    end

    prefix = "[#{i + 1}/#{tests.length}]"
    log("#{prefix} Running test '#{t.path}'")
    result = run_test(t)

    secs = result.elapsed_time.round(2)

    if result.expected_result?
      if result.successful?
        log("#{prefix} Test '#{t.path}' successful in #{secs} seconds")
      else
        log("#{prefix} Test '#{t.path}' failed as expected in #{secs} seconds")
      end
    else # unexpected result
      if result.successful?
        log("#{prefix} Test '#{t.path}' unexpectedly succeeded in #{secs} seconds, see #{result.state_dir}")
      else
        log("#{prefix} Test '#{t.path}' failed after #{secs} seconds, see #{result.state_dir}")
      end

      stop_work! if opts[:stop_on_failure]
    end

    mutex.synchronize { results << result }
  end
end

#start_worker(i) ⇒ Object (protected)



88
89
90
# File 'lib/test-runner/executor.rb', line 88

def start_worker(i)
  workers << Thread.new { run_worker(i) }
end

#state_dirObject (protected)



180
181
182
# File 'lib/test-runner/executor.rb', line 180

def state_dir
  opts[:state_dir]
end

#stop_work!Object (protected)



164
165
166
# File 'lib/test-runner/executor.rb', line 164

def stop_work!
  @stop_work = true
end

#stop_work?Boolean (protected)

Returns:

  • (Boolean)


168
169
170
# File 'lib/test-runner/executor.rb', line 168

def stop_work?
  @stop_work
end

#test_sock_dirObject (protected)



176
177
178
# File 'lib/test-runner/executor.rb', line 176

def test_sock_dir
  File.join(state_dir, 'socks')
end

#test_state_dir(test) ⇒ Object (protected)



172
173
174
# File 'lib/test-runner/executor.rb', line 172

def test_state_dir(test)
  File.join(state_dir, "os-test-#{test.name}")
end

#wait_for_workersObject (protected)



92
93
94
# File 'lib/test-runner/executor.rb', line 92

def wait_for_workers
  workers.each(&:join)
end