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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -115
  3. data/exe/try +25 -4
  4. data/lib/tryouts/cli/formatters/base.rb +33 -21
  5. data/lib/tryouts/cli/formatters/compact.rb +122 -84
  6. data/lib/tryouts/cli/formatters/factory.rb +1 -1
  7. data/lib/tryouts/cli/formatters/output_manager.rb +13 -2
  8. data/lib/tryouts/cli/formatters/quiet.rb +22 -16
  9. data/lib/tryouts/cli/formatters/verbose.rb +102 -61
  10. data/lib/tryouts/console.rb +53 -17
  11. data/lib/tryouts/expectation_evaluators/base.rb +101 -0
  12. data/lib/tryouts/expectation_evaluators/boolean.rb +60 -0
  13. data/lib/tryouts/expectation_evaluators/exception.rb +61 -0
  14. data/lib/tryouts/expectation_evaluators/expectation_result.rb +67 -0
  15. data/lib/tryouts/expectation_evaluators/false.rb +60 -0
  16. data/lib/tryouts/expectation_evaluators/intentional_failure.rb +74 -0
  17. data/lib/tryouts/expectation_evaluators/output.rb +101 -0
  18. data/lib/tryouts/expectation_evaluators/performance_time.rb +81 -0
  19. data/lib/tryouts/expectation_evaluators/regex_match.rb +57 -0
  20. data/lib/tryouts/expectation_evaluators/registry.rb +66 -0
  21. data/lib/tryouts/expectation_evaluators/regular.rb +67 -0
  22. data/lib/tryouts/expectation_evaluators/result_type.rb +51 -0
  23. data/lib/tryouts/expectation_evaluators/true.rb +58 -0
  24. data/lib/tryouts/prism_parser.rb +221 -20
  25. data/lib/tryouts/test_batch.rb +556 -0
  26. data/lib/tryouts/test_case.rb +192 -0
  27. data/lib/tryouts/test_executor.rb +7 -5
  28. data/lib/tryouts/test_runner.rb +2 -2
  29. data/lib/tryouts/translators/minitest_translator.rb +78 -11
  30. data/lib/tryouts/translators/rspec_translator.rb +85 -12
  31. data/lib/tryouts/version.rb +1 -1
  32. data/lib/tryouts.rb +43 -1
  33. metadata +18 -5
  34. data/lib/tryouts/testbatch.rb +0 -314
  35. data/lib/tryouts/testcase.rb +0 -51
@@ -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
@@ -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