tryouts 3.0.0 → 3.1.1
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 +4 -4
- data/README.md +51 -115
- data/exe/try +25 -4
- data/lib/tryouts/cli/formatters/base.rb +33 -21
- data/lib/tryouts/cli/formatters/compact.rb +122 -84
- data/lib/tryouts/cli/formatters/factory.rb +1 -1
- data/lib/tryouts/cli/formatters/output_manager.rb +13 -2
- data/lib/tryouts/cli/formatters/quiet.rb +22 -16
- data/lib/tryouts/cli/formatters/verbose.rb +102 -61
- data/lib/tryouts/console.rb +53 -17
- data/lib/tryouts/expectation_evaluators/base.rb +101 -0
- data/lib/tryouts/expectation_evaluators/boolean.rb +60 -0
- data/lib/tryouts/expectation_evaluators/exception.rb +61 -0
- data/lib/tryouts/expectation_evaluators/expectation_result.rb +67 -0
- data/lib/tryouts/expectation_evaluators/false.rb +60 -0
- data/lib/tryouts/expectation_evaluators/intentional_failure.rb +74 -0
- data/lib/tryouts/expectation_evaluators/output.rb +101 -0
- data/lib/tryouts/expectation_evaluators/performance_time.rb +81 -0
- data/lib/tryouts/expectation_evaluators/regex_match.rb +57 -0
- data/lib/tryouts/expectation_evaluators/registry.rb +66 -0
- data/lib/tryouts/expectation_evaluators/regular.rb +67 -0
- data/lib/tryouts/expectation_evaluators/result_type.rb +51 -0
- data/lib/tryouts/expectation_evaluators/true.rb +58 -0
- data/lib/tryouts/prism_parser.rb +221 -20
- data/lib/tryouts/test_batch.rb +556 -0
- data/lib/tryouts/test_case.rb +192 -0
- data/lib/tryouts/test_executor.rb +7 -5
- data/lib/tryouts/test_runner.rb +2 -2
- data/lib/tryouts/translators/minitest_translator.rb +78 -11
- data/lib/tryouts/translators/rspec_translator.rb +85 -12
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +43 -1
- metadata +18 -5
- data/lib/tryouts/testbatch.rb +0 -314
- data/lib/tryouts/testcase.rb +0 -51
data/lib/tryouts/testbatch.rb
DELETED
@@ -1,314 +0,0 @@
|
|
1
|
-
# lib/tryouts/testbatch.rb
|
2
|
-
|
3
|
-
require 'stringio'
|
4
|
-
|
5
|
-
class Tryouts
|
6
|
-
# Modern TestBatch using Ruby 3.4+ patterns and formatter system
|
7
|
-
class TestBatch
|
8
|
-
attr_reader :testrun, :failed_count, :container, :status, :results, :formatter, :output_manager
|
9
|
-
|
10
|
-
def initialize(testrun, **options)
|
11
|
-
@testrun = testrun
|
12
|
-
@container = Object.new
|
13
|
-
@options = options
|
14
|
-
@formatter = Tryouts::CLI::FormatterFactory.create_formatter(options)
|
15
|
-
@output_manager = options[:output_manager]
|
16
|
-
@global_tally = options[:global_tally]
|
17
|
-
@failed_count = 0
|
18
|
-
@status = :pending
|
19
|
-
@results = []
|
20
|
-
@start_time = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
# Main execution pipeline using functional composition
|
24
|
-
def run(before_test_hook = nil, &)
|
25
|
-
return false if empty?
|
26
|
-
|
27
|
-
@start_time = Time.now
|
28
|
-
@output_manager&.execution_phase(test_cases.size)
|
29
|
-
@output_manager&.info("Context: #{@options[:shared_context] ? 'shared' : 'fresh'}", 1)
|
30
|
-
@output_manager&.file_start(path, context: @options[:shared_context] ? :shared : :fresh)
|
31
|
-
|
32
|
-
if shared_context?
|
33
|
-
@output_manager&.info('Running global setup...', 2)
|
34
|
-
execute_global_setup
|
35
|
-
end
|
36
|
-
|
37
|
-
idx = 0
|
38
|
-
execution_results = test_cases.map do |test_case|
|
39
|
-
@output_manager&.trace("Test #{idx + 1}/#{test_cases.size}: #{test_case.description}", 2)
|
40
|
-
idx += 1
|
41
|
-
result = execute_single_test(test_case, before_test_hook, &) # runs the test code
|
42
|
-
result
|
43
|
-
end
|
44
|
-
|
45
|
-
execute_global_teardown
|
46
|
-
finalize_results(execution_results)
|
47
|
-
|
48
|
-
@status = :completed
|
49
|
-
!failed?
|
50
|
-
end
|
51
|
-
|
52
|
-
def empty?
|
53
|
-
@testrun.empty?
|
54
|
-
end
|
55
|
-
|
56
|
-
def size
|
57
|
-
@testrun.total_tests
|
58
|
-
end
|
59
|
-
|
60
|
-
def test_cases
|
61
|
-
@testrun.test_cases
|
62
|
-
end
|
63
|
-
|
64
|
-
def path
|
65
|
-
@testrun.source_file
|
66
|
-
end
|
67
|
-
|
68
|
-
def failed?
|
69
|
-
@failed_count > 0
|
70
|
-
end
|
71
|
-
|
72
|
-
def completed?
|
73
|
-
@status == :completed
|
74
|
-
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
# Pattern matching for execution strategy selection
|
79
|
-
def execute_single_test(test_case, before_test_hook = nil)
|
80
|
-
before_test_hook&.call(test_case)
|
81
|
-
|
82
|
-
# Capture output during test execution
|
83
|
-
result = nil
|
84
|
-
captured_output = capture_output do
|
85
|
-
result = case @options[:shared_context]
|
86
|
-
when true
|
87
|
-
execute_with_shared_context(test_case)
|
88
|
-
when false, nil
|
89
|
-
execute_with_fresh_context(test_case)
|
90
|
-
else
|
91
|
-
raise 'Invalid execution context configuration'
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# Add captured output to the result
|
96
|
-
result[:captured_output] = captured_output if captured_output && !captured_output.empty?
|
97
|
-
|
98
|
-
process_test_result(result)
|
99
|
-
yield(test_case) if block_given?
|
100
|
-
result
|
101
|
-
end
|
102
|
-
|
103
|
-
# Shared context execution - setup runs once, all tests share state
|
104
|
-
def execute_with_shared_context(test_case)
|
105
|
-
code = test_case.code
|
106
|
-
path = test_case.path
|
107
|
-
range = test_case.line_range
|
108
|
-
|
109
|
-
result_value = @container.instance_eval(code, path, range.first + 1)
|
110
|
-
expectations_result = evaluate_expectations(test_case, result_value, @container)
|
111
|
-
|
112
|
-
build_test_result(test_case, result_value, expectations_result)
|
113
|
-
rescue StandardError => ex
|
114
|
-
build_error_result(test_case, ex.message, ex)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Fresh context execution - setup runs per test, isolated state
|
118
|
-
def execute_with_fresh_context(test_case)
|
119
|
-
fresh_container = Object.new
|
120
|
-
|
121
|
-
# Execute setup in fresh context if present
|
122
|
-
setup = @testrun.setup
|
123
|
-
if setup && !setup.code.empty?
|
124
|
-
fresh_container.instance_eval(setup.code, setup.path, 1)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Execute test in same fresh context
|
128
|
-
code = test_case.code
|
129
|
-
path = test_case.path
|
130
|
-
range = test_case.line_range
|
131
|
-
|
132
|
-
result_value = fresh_container.instance_eval(code, path, range.first + 1)
|
133
|
-
expectations_result = evaluate_expectations(test_case, result_value, fresh_container)
|
134
|
-
|
135
|
-
build_test_result(test_case, result_value, expectations_result)
|
136
|
-
rescue StandardError => ex
|
137
|
-
build_error_result(test_case, ex.message, ex)
|
138
|
-
end
|
139
|
-
|
140
|
-
# Evaluate expectations using pattern matching for clean result handling
|
141
|
-
def evaluate_expectations(test_case, actual_result, context)
|
142
|
-
if test_case.expectations.empty?
|
143
|
-
{ passed: true, actual_results: [], expected_results: [] }
|
144
|
-
else
|
145
|
-
evaluation_results = test_case.expectations.map do |expectation|
|
146
|
-
evaluate_single_expectation(expectation, actual_result, context, test_case)
|
147
|
-
end
|
148
|
-
|
149
|
-
{
|
150
|
-
passed: evaluation_results.all? { |r| r[:passed] },
|
151
|
-
actual_results: evaluation_results.map { |r| r[:actual] },
|
152
|
-
expected_results: evaluation_results.map { |r| r[:expected] },
|
153
|
-
}
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def evaluate_single_expectation(expectation, actual_result, context, test_case)
|
158
|
-
path = test_case.path
|
159
|
-
range = test_case.line_range
|
160
|
-
|
161
|
-
expected_value = context.instance_eval(expectation, path, range.first + 1)
|
162
|
-
|
163
|
-
{
|
164
|
-
passed: actual_result == expected_value,
|
165
|
-
actual: actual_result,
|
166
|
-
expected: expected_value,
|
167
|
-
expectation: expectation,
|
168
|
-
}
|
169
|
-
rescue StandardError => ex
|
170
|
-
{
|
171
|
-
passed: false,
|
172
|
-
actual: actual_result,
|
173
|
-
expected: "EXPECTED: #{ex.message}",
|
174
|
-
expectation: expectation,
|
175
|
-
}
|
176
|
-
end
|
177
|
-
|
178
|
-
# Build structured test results using pattern matching
|
179
|
-
def build_test_result(test_case, result_value, expectations_result)
|
180
|
-
if expectations_result[:passed]
|
181
|
-
{
|
182
|
-
test_case: test_case,
|
183
|
-
status: :passed,
|
184
|
-
result_value: result_value,
|
185
|
-
actual_results: expectations_result[:actual_results],
|
186
|
-
error: nil,
|
187
|
-
}
|
188
|
-
else
|
189
|
-
{
|
190
|
-
test_case: test_case,
|
191
|
-
status: :failed,
|
192
|
-
result_value: result_value,
|
193
|
-
actual_results: expectations_result[:actual_results],
|
194
|
-
error: nil,
|
195
|
-
}
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
def build_error_result(test_case, message, exception = nil)
|
200
|
-
{
|
201
|
-
test_case: test_case,
|
202
|
-
status: :error,
|
203
|
-
result_value: nil,
|
204
|
-
actual_results: ["ACTUAL: #{message}"],
|
205
|
-
error: exception,
|
206
|
-
}
|
207
|
-
end
|
208
|
-
|
209
|
-
# Process and display test results using formatter
|
210
|
-
def process_test_result(result)
|
211
|
-
@results << result
|
212
|
-
|
213
|
-
if [:failed, :error].include?(result[:status])
|
214
|
-
@failed_count += 1
|
215
|
-
end
|
216
|
-
|
217
|
-
show_test_result(result)
|
218
|
-
|
219
|
-
# Show captured output if any exists
|
220
|
-
if result[:captured_output] && !result[:captured_output].empty?
|
221
|
-
@output_manager&.test_output(result[:test_case], result[:captured_output])
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
# Global setup execution for shared context mode
|
226
|
-
def execute_global_setup
|
227
|
-
setup = @testrun.setup
|
228
|
-
|
229
|
-
if setup && !setup.code.empty? && @options[:shared_context]
|
230
|
-
@output_manager&.setup_start(setup.line_range)
|
231
|
-
|
232
|
-
# Capture setup output instead of letting it print directly
|
233
|
-
captured_output = capture_output do
|
234
|
-
@container.instance_eval(setup.code, setup.path, setup.line_range.first + 1)
|
235
|
-
end
|
236
|
-
|
237
|
-
@output_manager&.setup_output(captured_output) if captured_output && !captured_output.empty?
|
238
|
-
end
|
239
|
-
rescue StandardError => ex
|
240
|
-
@global_tally[:total_errors] += 1 if @global_tally
|
241
|
-
raise "Global setup failed: #{ex.message}"
|
242
|
-
end
|
243
|
-
|
244
|
-
# Global teardown execution
|
245
|
-
def execute_global_teardown
|
246
|
-
teardown = @testrun.teardown
|
247
|
-
|
248
|
-
if teardown && !teardown.code.empty?
|
249
|
-
@output_manager&.teardown_start(teardown.line_range)
|
250
|
-
|
251
|
-
# Capture teardown output instead of letting it print directly
|
252
|
-
captured_output = capture_output do
|
253
|
-
@container.instance_eval(teardown.code, teardown.path, teardown.line_range.first + 1)
|
254
|
-
end
|
255
|
-
|
256
|
-
@output_manager&.teardown_output(captured_output) if captured_output && !captured_output.empty?
|
257
|
-
end
|
258
|
-
rescue StandardError => ex
|
259
|
-
@global_tally[:total_errors] += 1 if @global_tally
|
260
|
-
@output_manager&.error("Teardown failed: #{ex.message}")
|
261
|
-
end
|
262
|
-
|
263
|
-
# Result finalization and summary display
|
264
|
-
def finalize_results(_execution_results)
|
265
|
-
@status = :completed
|
266
|
-
elapsed_time = Time.now - @start_time
|
267
|
-
show_summary(elapsed_time)
|
268
|
-
end
|
269
|
-
|
270
|
-
|
271
|
-
def show_test_result(result)
|
272
|
-
test_case = result[:test_case]
|
273
|
-
status = result[:status]
|
274
|
-
actuals = result[:actual_results]
|
275
|
-
|
276
|
-
@output_manager&.test_result(test_case, status, actuals)
|
277
|
-
end
|
278
|
-
|
279
|
-
def show_summary(elapsed_time)
|
280
|
-
@output_manager&.batch_summary(size, @failed_count, elapsed_time)
|
281
|
-
end
|
282
|
-
|
283
|
-
# Helper methods using pattern matching
|
284
|
-
|
285
|
-
def shared_context?
|
286
|
-
@options[:shared_context] == true
|
287
|
-
end
|
288
|
-
|
289
|
-
def capture_output
|
290
|
-
old_stdout = $stdout
|
291
|
-
old_stderr = $stderr
|
292
|
-
$stdout = StringIO.new
|
293
|
-
$stderr = StringIO.new
|
294
|
-
|
295
|
-
yield
|
296
|
-
|
297
|
-
captured = $stdout.string + $stderr.string
|
298
|
-
captured.empty? ? nil : captured
|
299
|
-
ensure
|
300
|
-
$stdout = old_stdout
|
301
|
-
$stderr = old_stderr
|
302
|
-
end
|
303
|
-
|
304
|
-
def handle_batch_error(exception)
|
305
|
-
@status = :error
|
306
|
-
@failed_count = 1
|
307
|
-
|
308
|
-
error_message = "Batch execution failed: #{exception.message}"
|
309
|
-
backtrace = exception.respond_to?(:backtrace) ? exception.backtrace : nil
|
310
|
-
|
311
|
-
@output_manager&.error(error_message, backtrace)
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
data/lib/tryouts/testcase.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
# lib/tryouts/testcase.rb
|
2
|
-
|
3
|
-
# Modern data structures using Ruby 3.2+ Data classes
|
4
|
-
class Tryouts
|
5
|
-
# Core data structures
|
6
|
-
TestCase = Data.define(:description, :code, :expectations, :line_range, :path, :source_lines) do
|
7
|
-
def empty?
|
8
|
-
code.empty?
|
9
|
-
end
|
10
|
-
|
11
|
-
def expectations?
|
12
|
-
!expectations.empty?
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
Setup = Data.define(:code, :line_range, :path) do
|
17
|
-
def empty?
|
18
|
-
code.empty?
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
Teardown = Data.define(:code, :line_range, :path) do
|
23
|
-
def empty?
|
24
|
-
code.empty?
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
Testrun = Data.define(:setup, :test_cases, :teardown, :source_file, :metadata) do
|
29
|
-
def total_tests
|
30
|
-
test_cases.size
|
31
|
-
end
|
32
|
-
|
33
|
-
def empty?
|
34
|
-
test_cases.empty?
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Enhanced error with context
|
39
|
-
class TryoutSyntaxError < StandardError
|
40
|
-
attr_reader :line_number, :context, :source_file
|
41
|
-
|
42
|
-
def initialize(message, line_number:, context:, source_file: nil)
|
43
|
-
@line_number = line_number
|
44
|
-
@context = context
|
45
|
-
@source_file = source_file
|
46
|
-
|
47
|
-
location = source_file ? "#{source_file}:#{line_number}" : "line #{line_number}"
|
48
|
-
super("#{message} at #{location}: #{context}")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|