verity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +227 -0
- data/bin/benchmark +99 -0
- data/bin/test_all +43 -0
- data/bin/verity +82 -0
- data/lib/verity/assertions.rb +316 -0
- data/lib/verity/configuration.rb +129 -0
- data/lib/verity/fingerprint.rb +155 -0
- data/lib/verity/manifest.rb +462 -0
- data/lib/verity/reporter.rb +54 -0
- data/lib/verity/reporters/colored_dots.rb +59 -0
- data/lib/verity/reporters/composite_reporter.rb +44 -0
- data/lib/verity/reporters/documentation_reporter.rb +121 -0
- data/lib/verity/reporters/dots_reporter.rb +48 -0
- data/lib/verity/reporters/null_reporter.rb +11 -0
- data/lib/verity/reporters/parallel_summary_reporter.rb +51 -0
- data/lib/verity/reporters/test_reporter.rb +48 -0
- data/lib/verity/runner.rb +210 -0
- data/lib/verity/version.rb +5 -0
- data/lib/verity.rb +614 -0
- metadata +139 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verity
|
|
4
|
+
module Reporters
|
|
5
|
+
# Public: Verbose reporter that prints one indented line per test, nesting
|
|
6
|
+
# output under group headers. Uses ANSI colors (green/red/yellow/magenta)
|
|
7
|
+
# when outputting to a TTY, unless suppressed by NO_COLOR or forced via
|
|
8
|
+
# FORCE_COLOR / VERITY_FORCE_COLOR.
|
|
9
|
+
class DocumentationReporter
|
|
10
|
+
include Verity::Reporter
|
|
11
|
+
|
|
12
|
+
ESC = "\e["
|
|
13
|
+
RESET = "#{ESC}0m"
|
|
14
|
+
PASS_STYLE = "#{ESC}32m"
|
|
15
|
+
FAIL_STYLE = "#{ESC}31m"
|
|
16
|
+
SKIP_STYLE = "#{ESC}33m"
|
|
17
|
+
ERROR_STYLE = "#{ESC}35m"
|
|
18
|
+
|
|
19
|
+
# Public: Create a new DocumentationReporter.
|
|
20
|
+
#
|
|
21
|
+
# io - IO object for output (default $stdout).
|
|
22
|
+
# color - Boolean to force color on/off, or nil for auto-detect.
|
|
23
|
+
def initialize(io = $stdout, color: nil)
|
|
24
|
+
@io = io
|
|
25
|
+
@color_override = color
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Public: Print a "Running N tests..." header.
|
|
29
|
+
def on_run_start(total:, worker_id:)
|
|
30
|
+
@last_group_path = nil
|
|
31
|
+
return if total.nil?
|
|
32
|
+
|
|
33
|
+
@io.puts "Running #{total} tests..."
|
|
34
|
+
@io.puts
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Public: Print a status-labeled line for the completed test, indented
|
|
38
|
+
# under its group headers.
|
|
39
|
+
def on_test_complete(result:, worker_id:)
|
|
40
|
+
path = Array(result.test.group_path)
|
|
41
|
+
emit_group_headers(path)
|
|
42
|
+
indent = " " * (path.size + 1)
|
|
43
|
+
case result.status
|
|
44
|
+
when :pass
|
|
45
|
+
@io.puts "#{indent}#{paint("pass", PASS_STYLE)} #{result.test.description}"
|
|
46
|
+
when :fail
|
|
47
|
+
@io.puts "#{indent}#{paint("FAIL", FAIL_STYLE)} #{result.test.description}\n #{result.error.message}"
|
|
48
|
+
when :error
|
|
49
|
+
msg = "#{result.error.class}: #{result.error.message}"
|
|
50
|
+
@io.puts "#{indent}#{paint("ERROR", ERROR_STYLE)} #{result.test.description}\n #{msg}"
|
|
51
|
+
when :skip
|
|
52
|
+
@io.puts "#{indent}#{paint("skip", SKIP_STYLE)} #{result.test.description}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Public: Print the final summary line with counts and optional color.
|
|
57
|
+
def on_run_finish(summary:, worker_id:)
|
|
58
|
+
t = summary[:total]
|
|
59
|
+
p = summary[:passed]
|
|
60
|
+
f = summary[:failed]
|
|
61
|
+
e = summary[:errored]
|
|
62
|
+
sk = summary[:skipped].to_i
|
|
63
|
+
if color?
|
|
64
|
+
parts = [
|
|
65
|
+
"\n#{t} tests:",
|
|
66
|
+
"#{paint("#{p} passed", PASS_STYLE)},",
|
|
67
|
+
"#{paint("#{f} failed", FAIL_STYLE)},",
|
|
68
|
+
"#{paint("#{e} errored", ERROR_STYLE)}"
|
|
69
|
+
]
|
|
70
|
+
parts << ", #{paint("#{sk} skipped", SKIP_STYLE)}" if sk.positive?
|
|
71
|
+
line = "#{parts.join(" ")}#{RESET}"
|
|
72
|
+
else
|
|
73
|
+
line = "\n#{t} tests: #{p} passed, #{f} failed, #{e} errored"
|
|
74
|
+
line += ", #{sk} skipped" if sk.positive?
|
|
75
|
+
end
|
|
76
|
+
line += " (focus)" if summary[:focus]
|
|
77
|
+
@io.puts line
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Public: Delegate to ParallelSummaryReporter for the multi-worker summary.
|
|
81
|
+
def on_parallel_complete(counts:, problem_rows:)
|
|
82
|
+
ParallelSummaryReporter.new(@io).emit(counts:, problem_rows:)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def paint(text, sequence)
|
|
88
|
+
return text unless color?
|
|
89
|
+
|
|
90
|
+
"#{sequence}#{text}#{RESET}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def color?
|
|
94
|
+
return @color_override unless @color_override.nil?
|
|
95
|
+
|
|
96
|
+
return false if ENV.key?("NO_COLOR")
|
|
97
|
+
return true if truthy_env?(ENV["FORCE_COLOR"]) || truthy_env?(ENV["VERITY_FORCE_COLOR"])
|
|
98
|
+
|
|
99
|
+
@io.respond_to?(:tty?) && @io.tty?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def truthy_env?(value)
|
|
103
|
+
%w[1 true yes].include?(value&.downcase)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def emit_group_headers(path)
|
|
107
|
+
last = @last_group_path || []
|
|
108
|
+
common = 0
|
|
109
|
+
n = [last.size, path.size].min
|
|
110
|
+
while common < n && last[common] == path[common]
|
|
111
|
+
common += 1
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
(common...path.size).each do |i|
|
|
115
|
+
@io.puts "#{" " * i}#{path[i]}"
|
|
116
|
+
end
|
|
117
|
+
@last_group_path = path.dup
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verity
|
|
4
|
+
module Reporters
|
|
5
|
+
# Public: Minimal reporter that prints a single character per test:
|
|
6
|
+
# "." for pass, "F" for failure, "E" for error. No color.
|
|
7
|
+
class DotsReporter
|
|
8
|
+
include Verity::Reporter
|
|
9
|
+
|
|
10
|
+
# Public: Create a new DotsReporter.
|
|
11
|
+
#
|
|
12
|
+
# io - IO object for output (default $stdout).
|
|
13
|
+
def initialize(io = $stdout)
|
|
14
|
+
@io = io
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Public: Print a dot, F, E, or S (skip) for the completed test.
|
|
18
|
+
def on_test_complete(result:, worker_id:)
|
|
19
|
+
char =
|
|
20
|
+
case result.status
|
|
21
|
+
when :pass then "."
|
|
22
|
+
when :fail then "F"
|
|
23
|
+
when :error then "E"
|
|
24
|
+
when :skip then "S"
|
|
25
|
+
end
|
|
26
|
+
@io.print char
|
|
27
|
+
@io.flush
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Public: Print the final summary line with counts.
|
|
31
|
+
def on_run_finish(summary:, worker_id:)
|
|
32
|
+
t = summary[:total]
|
|
33
|
+
p = summary[:passed]
|
|
34
|
+
f = summary[:failed]
|
|
35
|
+
e = summary[:errored]
|
|
36
|
+
line = "\n\n#{t} tests: #{p} passed, #{f} failed, #{e} errored"
|
|
37
|
+
line += ", #{summary[:skipped]} skipped" if summary[:skipped].to_i.positive?
|
|
38
|
+
line += " (focus)" if summary[:focus]
|
|
39
|
+
@io.puts line
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Public: Delegate to ParallelSummaryReporter for the multi-worker summary.
|
|
43
|
+
def on_parallel_complete(counts:, problem_rows:)
|
|
44
|
+
ParallelSummaryReporter.new(@io).emit(counts:, problem_rows:)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verity
|
|
4
|
+
module Reporters
|
|
5
|
+
# Public: Silent reporter that discards all output. Used in forked worker
|
|
6
|
+
# processes and in tests where reporter output is unwanted.
|
|
7
|
+
class NullReporter
|
|
8
|
+
include Verity::Reporter
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verity
|
|
4
|
+
module Reporters
|
|
5
|
+
# Internal: Shared helper that emits the multi-worker summary block.
|
|
6
|
+
# Typically called from a reporter's on_parallel_complete to print
|
|
7
|
+
# aggregate counts and list any failures or errors from the manifest.
|
|
8
|
+
class ParallelSummaryReporter
|
|
9
|
+
def initialize(io = $stdout)
|
|
10
|
+
@io = io
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Public: Write the parallel-run summary to the IO stream.
|
|
14
|
+
#
|
|
15
|
+
# counts - Hash with String status keys ("passed", "failed", etc.)
|
|
16
|
+
# and Integer counts.
|
|
17
|
+
# problem_rows - Array of Hashes with :fingerprint, :description, :status,
|
|
18
|
+
# and :failure from Manifest#failures_for_report.
|
|
19
|
+
#
|
|
20
|
+
# Returns nothing.
|
|
21
|
+
def emit(counts:, problem_rows:)
|
|
22
|
+
passed = counts.fetch("passed", 0)
|
|
23
|
+
failed = counts.fetch("failed", 0)
|
|
24
|
+
errored = counts.fetch("errored", 0)
|
|
25
|
+
pending = counts.fetch("pending", 0)
|
|
26
|
+
running = counts.fetch("running", 0)
|
|
27
|
+
skipped = counts.fetch("skipped", 0)
|
|
28
|
+
total = passed + failed + errored + pending + running
|
|
29
|
+
|
|
30
|
+
line = "Parallel run finished (#{total} tests in manifest: #{passed} passed, #{failed} failed, #{errored} errored, #{pending} pending, #{running} running"
|
|
31
|
+
line += ", #{skipped} skipped" if skipped > 0
|
|
32
|
+
line += ")"
|
|
33
|
+
@io.puts "\n#{line}"
|
|
34
|
+
|
|
35
|
+
return if problem_rows.empty?
|
|
36
|
+
|
|
37
|
+
@io.puts "\nFailures and errors:"
|
|
38
|
+
problem_rows.each do |row|
|
|
39
|
+
fp = row[:fingerprint]
|
|
40
|
+
desc = row[:description]
|
|
41
|
+
st = row[:status]
|
|
42
|
+
@io.puts " #{st} #{desc} (#{fp})"
|
|
43
|
+
next if row[:failure].nil? || row[:failure].empty?
|
|
44
|
+
|
|
45
|
+
msg = row[:failure]["message"] || row[:failure][:message]
|
|
46
|
+
@io.puts " #{msg}" if msg
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Verity
|
|
4
|
+
module Reporters
|
|
5
|
+
# Public: In-memory reporter that records every callback for later
|
|
6
|
+
# inspection. Produces no I/O — designed for use in Verity's own tests
|
|
7
|
+
# and tooling.
|
|
8
|
+
class TestReporter
|
|
9
|
+
include Verity::Reporter
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@run_starts = []
|
|
13
|
+
@test_completes = []
|
|
14
|
+
@run_finishes = []
|
|
15
|
+
@parallel_finishes = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Public: Array of Hashes recorded from on_run_start calls.
|
|
19
|
+
# Each Hash contains :total and :worker_id.
|
|
20
|
+
#
|
|
21
|
+
# Public: Array of Hashes recorded from on_test_complete calls.
|
|
22
|
+
# Each Hash contains :status, :error (exception or nil), and :worker_id.
|
|
23
|
+
#
|
|
24
|
+
# Public: Array of Hashes recorded from on_run_finish calls.
|
|
25
|
+
# Each Hash contains :summary and :worker_id.
|
|
26
|
+
#
|
|
27
|
+
# Public: Array of Hashes recorded from on_parallel_complete calls.
|
|
28
|
+
# Each Hash contains :counts and :problem_rows.
|
|
29
|
+
attr_reader :run_starts, :test_completes, :run_finishes, :parallel_finishes
|
|
30
|
+
|
|
31
|
+
def on_run_start(total:, worker_id:)
|
|
32
|
+
@run_starts << { total: total, worker_id: worker_id }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def on_test_complete(result:, worker_id:)
|
|
36
|
+
@test_completes << { status: result.status, error: result.error, worker_id: worker_id }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def on_run_finish(summary:, worker_id:)
|
|
40
|
+
@run_finishes << { summary: summary, worker_id: worker_id }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def on_parallel_complete(counts:, problem_rows:)
|
|
44
|
+
@parallel_finishes << { counts: counts, problem_rows: problem_rows }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
module Verity
|
|
6
|
+
# Public: Executes tests, fires reporter hooks, and records results back to
|
|
7
|
+
# the manifest. A single Runner instance services one worker process.
|
|
8
|
+
class Runner
|
|
9
|
+
# Public: Immutable outcome of running a single test.
|
|
10
|
+
#
|
|
11
|
+
# test - The Verity::Test that was executed.
|
|
12
|
+
# status - Symbol :pass, :fail, :error, or :skip.
|
|
13
|
+
# error - Exception instance or nil.
|
|
14
|
+
Result = Data.define(:test, :status, :error)
|
|
15
|
+
|
|
16
|
+
# Public: Create a new Runner.
|
|
17
|
+
#
|
|
18
|
+
# reporter - Object implementing Verity::Reporter (default: from configuration).
|
|
19
|
+
def initialize(reporter: nil)
|
|
20
|
+
@reporter = reporter || Verity.configuration.reporter
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Public: Run a list of tests in-process without a manifest. Primarily
|
|
24
|
+
# used for simple single-worker execution.
|
|
25
|
+
#
|
|
26
|
+
# tests - Array of Verity::Test (default: all registered tests).
|
|
27
|
+
#
|
|
28
|
+
# Returns true if every test passed.
|
|
29
|
+
def run(tests = Registry.all)
|
|
30
|
+
run_worker(tests, worker_id: 0)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
CONFLICT_RETRY_INTERVAL = 0.05
|
|
34
|
+
|
|
35
|
+
# Public: Claim and execute tests from a shared manifest until none remain.
|
|
36
|
+
# Fires before_worker_start hooks, then loops claim_next until exhausted.
|
|
37
|
+
# When resource resolvers are registered, builds a conflict exclusion list
|
|
38
|
+
# before each claim and sleeps briefly when blocked by running tests.
|
|
39
|
+
#
|
|
40
|
+
# manifest - A Verity::Manifest instance.
|
|
41
|
+
# worker_id - Integer identifying this worker.
|
|
42
|
+
#
|
|
43
|
+
# Returns true if every executed test passed.
|
|
44
|
+
def run_manifest(manifest, worker_id:)
|
|
45
|
+
Verity.hooks[:before_worker_start].each(&:call)
|
|
46
|
+
|
|
47
|
+
@reporter.on_run_start(total: manifest.example_count, worker_id: worker_id)
|
|
48
|
+
|
|
49
|
+
results = []
|
|
50
|
+
loop do
|
|
51
|
+
claimed = next_claim(manifest, worker_id)
|
|
52
|
+
if claimed == :blocked
|
|
53
|
+
sleep CONFLICT_RETRY_INTERVAL
|
|
54
|
+
next
|
|
55
|
+
end
|
|
56
|
+
break unless claimed
|
|
57
|
+
|
|
58
|
+
test = Registry.find(claimed.fingerprint)
|
|
59
|
+
unless test
|
|
60
|
+
err = RuntimeError.new(
|
|
61
|
+
"Test fingerprint not in Registry (load files before replace_tests): #{claimed.fingerprint}"
|
|
62
|
+
)
|
|
63
|
+
manifest.record_error(claimed.fingerprint, err)
|
|
64
|
+
result = Result.new(test: synthetic_test_from_claim(claimed), status: :error, error: err)
|
|
65
|
+
results << result
|
|
66
|
+
@reporter.on_test_complete(result: result, worker_id: worker_id)
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
result = run_with_hooks(test)
|
|
71
|
+
results << result
|
|
72
|
+
@reporter.on_test_complete(result: result, worker_id: worker_id)
|
|
73
|
+
|
|
74
|
+
case result.status
|
|
75
|
+
when :pass then manifest.record_pass(test.fingerprint)
|
|
76
|
+
when :fail then manifest.record_failure(test.fingerprint, result.error)
|
|
77
|
+
when :error then manifest.record_error(test.fingerprint, result.error)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
Registry.all.select { Verity.skipped?(_1) }.each do |t|
|
|
82
|
+
@reporter.on_test_complete(
|
|
83
|
+
result: Result.new(test: t, status: :skip, error: nil),
|
|
84
|
+
worker_id: worker_id
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
without_skip = Registry.all.reject { Verity.skipped?(_1) }
|
|
89
|
+
skipped = Registry.all.count { Verity.skipped?(_1) }
|
|
90
|
+
focus = Verity.focus_filter_active?(without_skip)
|
|
91
|
+
|
|
92
|
+
@reporter.on_run_finish(
|
|
93
|
+
summary: build_summary(results, skipped: skipped, focus: focus),
|
|
94
|
+
worker_id: worker_id
|
|
95
|
+
)
|
|
96
|
+
results.all? { |r| r.status == :pass }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# Returns the next ClaimedRow, nil when the queue is empty, or the symbol
|
|
102
|
+
# :blocked when pending tests exist but all conflict with running tests.
|
|
103
|
+
def next_claim(manifest, worker_id)
|
|
104
|
+
return manifest.claim_next(worker_id) if Verity.resource_resolvers.empty?
|
|
105
|
+
|
|
106
|
+
running_res = manifest.running_resources
|
|
107
|
+
exclude = Verity.conflict_exclusion_list(running_res)
|
|
108
|
+
claimed = manifest.claim_next(worker_id, exclude: exclude)
|
|
109
|
+
return claimed if claimed
|
|
110
|
+
exclude.empty? ? nil : :blocked
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def run_worker(tests, worker_id:)
|
|
114
|
+
without_skip = tests.reject { Verity.skipped?(_1) }
|
|
115
|
+
list =
|
|
116
|
+
if without_skip.any? { Verity.focus_tag?(_1) }
|
|
117
|
+
without_skip.select { Verity.focus_tag?(_1) }
|
|
118
|
+
else
|
|
119
|
+
without_skip
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
skipped = tests.count { Verity.skipped?(_1) }
|
|
123
|
+
focus = Verity.focus_filter_active?(without_skip)
|
|
124
|
+
|
|
125
|
+
@reporter.on_run_start(total: list.size, worker_id: worker_id)
|
|
126
|
+
|
|
127
|
+
results = []
|
|
128
|
+
list.each do |t|
|
|
129
|
+
r = run_with_hooks(t)
|
|
130
|
+
results << r
|
|
131
|
+
@reporter.on_test_complete(result: r, worker_id: worker_id)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
tests.select { Verity.skipped?(_1) }.each do |t|
|
|
135
|
+
r = Result.new(test: t, status: :skip, error: nil)
|
|
136
|
+
@reporter.on_test_complete(result: r, worker_id: worker_id)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
@reporter.on_run_finish(
|
|
140
|
+
summary: build_summary(results, skipped: skipped, focus: focus),
|
|
141
|
+
worker_id: worker_id
|
|
142
|
+
)
|
|
143
|
+
results.all? { |r| r.status == :pass }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def build_summary(results, skipped:, focus:)
|
|
147
|
+
{
|
|
148
|
+
total: results.size,
|
|
149
|
+
passed: results.count { |r| r.status == :pass },
|
|
150
|
+
failed: results.count { |r| r.status == :fail },
|
|
151
|
+
errored: results.count { |r| r.status == :error },
|
|
152
|
+
skipped: skipped,
|
|
153
|
+
focus: focus
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def synthetic_test_from_claim(claimed)
|
|
158
|
+
Test.new(
|
|
159
|
+
fingerprint: claimed.fingerprint,
|
|
160
|
+
description: claimed.description,
|
|
161
|
+
tags: claimed.tags.map(&:to_sym),
|
|
162
|
+
timeout: claimed.timeout,
|
|
163
|
+
requires: claimed.requires.map(&:to_sym),
|
|
164
|
+
resources: claimed.resources.transform_keys(&:to_sym),
|
|
165
|
+
file: claimed.file,
|
|
166
|
+
line: claimed.line,
|
|
167
|
+
fn: -> {},
|
|
168
|
+
group_path: [],
|
|
169
|
+
inherited_group_tags: [],
|
|
170
|
+
group_scopes: []
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def run_with_hooks(test)
|
|
175
|
+
result = nil
|
|
176
|
+
begin
|
|
177
|
+
Verity.hooks[:before_test].each(&:call)
|
|
178
|
+
result = execute(test)
|
|
179
|
+
rescue => e
|
|
180
|
+
result = Result.new(test:, status: :error, error: e)
|
|
181
|
+
ensure
|
|
182
|
+
Verity.hooks[:after_test].each(&:call)
|
|
183
|
+
end
|
|
184
|
+
result
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def execute(test)
|
|
188
|
+
body = proc { test.fn.call }
|
|
189
|
+
if (sec = timeout_seconds_for(test))
|
|
190
|
+
Timeout.timeout(sec, TestTimeoutError, &body)
|
|
191
|
+
else
|
|
192
|
+
body.call
|
|
193
|
+
end
|
|
194
|
+
Result.new(test:, status: :pass, error: nil)
|
|
195
|
+
rescue AssertionError => e
|
|
196
|
+
Result.new(test:, status: :fail, error: e)
|
|
197
|
+
rescue TestTimeoutError => e
|
|
198
|
+
Result.new(test:, status: :error, error: e)
|
|
199
|
+
rescue => e
|
|
200
|
+
Result.new(test:, status: :error, error: e)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def timeout_seconds_for(test)
|
|
204
|
+
Verity.validate_test_timeout!(test.timeout)
|
|
205
|
+
return nil if test.timeout.nil?
|
|
206
|
+
|
|
207
|
+
test.timeout.to_f
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|