tryouts 3.1.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/lib/tryouts/cli/formatters/verbose.rb +1 -1
- data/lib/tryouts/expectation_evaluators/intentional_failure.rb +1 -1
- data/lib/tryouts/prism_parser.rb +109 -5
- data/lib/tryouts/{testbatch.rb → test_batch.rb} +85 -33
- data/lib/tryouts/{testcase.rb → test_case.rb} +1 -1
- data/lib/tryouts/test_executor.rb +1 -1
- data/lib/tryouts/test_runner.rb +1 -1
- data/lib/tryouts/translators/minitest_translator.rb +38 -0
- data/lib/tryouts/translators/rspec_translator.rb +38 -0
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca346c47683b393c8c60c586ab053a73c09b7d5f3e19ce184b35453135ae6594
|
4
|
+
data.tar.gz: 2ba211232994981e53393167cce8e09fc29bbc988d14b9ac3178321cbf158aaf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12f4d97b1f2049f1f6df4d0badca42d2a585360f12678e678cfbbcb85e0768cc2365eb7eb09a744713df15e2d51d3df9690fde78d160ff73947d2d9bdf986aa9
|
7
|
+
data.tar.gz: d919d1923c471aaa240f66dd0f03760bf7f5f9e53f71c35f8ace03d84935648dc21dafd5c8dceb68468a1b7e31ed6a47ad554471758d6e345146e15a661eca4f
|
@@ -134,7 +134,7 @@ class Tryouts
|
|
134
134
|
end
|
135
135
|
|
136
136
|
test_case = result_packet.test_case
|
137
|
-
location = "#{Console.pretty_path(test_case.path)}:#{test_case.
|
137
|
+
location = "#{Console.pretty_path(test_case.path)}:#{test_case.first_expectation_line + 1}"
|
138
138
|
puts
|
139
139
|
puts indent_text("#{status_line} @ #{location}", 2)
|
140
140
|
|
data/lib/tryouts/prism_parser.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Modern Ruby 3.4+ solution for the teardown bug
|
2
2
|
|
3
3
|
require 'prism'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'test_case'
|
5
5
|
|
6
6
|
class Tryouts
|
7
7
|
# Fixed PrismParser with pattern matching for robust token filtering
|
@@ -16,8 +16,10 @@ class Tryouts
|
|
16
16
|
def parse
|
17
17
|
return handle_syntax_errors if @prism_result.failure?
|
18
18
|
|
19
|
-
tokens
|
20
|
-
|
19
|
+
tokens = tokenize_content
|
20
|
+
test_boundaries = find_test_case_boundaries(tokens)
|
21
|
+
tokens = classify_potential_descriptions_with_boundaries(tokens, test_boundaries)
|
22
|
+
test_blocks = group_into_test_blocks(tokens)
|
21
23
|
process_test_blocks(test_blocks)
|
22
24
|
end
|
23
25
|
|
@@ -66,8 +68,106 @@ class Tryouts
|
|
66
68
|
tokens << token
|
67
69
|
end
|
68
70
|
|
69
|
-
#
|
70
|
-
|
71
|
+
# Return tokens with potential_descriptions - they'll be classified later with test boundaries
|
72
|
+
tokens
|
73
|
+
end
|
74
|
+
|
75
|
+
# Find actual test case boundaries by looking for ## descriptions or # TEST: patterns
|
76
|
+
# followed by code and expectations
|
77
|
+
def find_test_case_boundaries(tokens)
|
78
|
+
boundaries = []
|
79
|
+
|
80
|
+
tokens.each_with_index do |token, index|
|
81
|
+
# Look for explicit test descriptions (## or # TEST:)
|
82
|
+
if token[:type] == :description
|
83
|
+
# Find the end of this test case by looking for the last expectation
|
84
|
+
# before the next description or end of file
|
85
|
+
start_line = token[:line]
|
86
|
+
end_line = find_test_case_end(tokens, index)
|
87
|
+
|
88
|
+
boundaries << { start: start_line, end: end_line } if end_line
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
boundaries
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find where a test case ends by looking for the last expectation
|
96
|
+
# before the next test description or end of tokens
|
97
|
+
def find_test_case_end(tokens, start_index)
|
98
|
+
last_expectation_line = nil
|
99
|
+
|
100
|
+
# Look forward from the description for expectations
|
101
|
+
(start_index + 1).upto(tokens.length - 1) do |i|
|
102
|
+
token = tokens[i]
|
103
|
+
|
104
|
+
# Stop if we hit another test description
|
105
|
+
break if token[:type] == :description
|
106
|
+
|
107
|
+
# Track the last expectation we see
|
108
|
+
if is_expectation_type?(token[:type])
|
109
|
+
last_expectation_line = token[:line]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
last_expectation_line
|
114
|
+
end
|
115
|
+
|
116
|
+
# Convert potential_descriptions to descriptions or comments using test case boundaries
|
117
|
+
def classify_potential_descriptions_with_boundaries(tokens, test_boundaries)
|
118
|
+
tokens.map.with_index do |token, index|
|
119
|
+
if token[:type] == :potential_description
|
120
|
+
# Check if this comment falls within any test case boundary
|
121
|
+
line_num = token[:line]
|
122
|
+
within_test_case = test_boundaries.any? { |boundary|
|
123
|
+
line_num >= boundary[:start] && line_num <= boundary[:end]
|
124
|
+
}
|
125
|
+
|
126
|
+
if within_test_case
|
127
|
+
# This comment is within a test case, treat as regular comment
|
128
|
+
token.merge(type: :comment)
|
129
|
+
else
|
130
|
+
# For comments outside test boundaries, be more conservative
|
131
|
+
# Only treat as description if it immediately precedes a test pattern AND
|
132
|
+
# looks like a test description
|
133
|
+
content = token[:content].strip
|
134
|
+
|
135
|
+
# Check if this looks like a test description based on content
|
136
|
+
looks_like_test_description = content.match?(/test|example|demonstrate|show|should|when|given/i) &&
|
137
|
+
content.length > 10
|
138
|
+
|
139
|
+
# Check if there's code immediately before this (suggesting it's mid-test)
|
140
|
+
prev_token = index > 0 ? tokens[index - 1] : nil
|
141
|
+
has_code_before = prev_token && prev_token[:type] == :code
|
142
|
+
|
143
|
+
if has_code_before || !looks_like_test_description
|
144
|
+
# Treat as regular comment
|
145
|
+
token.merge(type: :comment)
|
146
|
+
else
|
147
|
+
# Look ahead for IMMEDIATE test pattern (stricter than before)
|
148
|
+
following_tokens = tokens[(index + 1)..]
|
149
|
+
|
150
|
+
# Skip blanks and comments to find meaningful content
|
151
|
+
meaningful_following = following_tokens.reject { |t| [:blank, :comment].include?(t[:type]) }
|
152
|
+
|
153
|
+
# Look for test pattern within next 5 tokens (more restrictive)
|
154
|
+
test_window = meaningful_following.first(5)
|
155
|
+
has_code = test_window.any? { |t| t[:type] == :code }
|
156
|
+
has_expectation = test_window.any? { |t| is_expectation_type?(t[:type]) }
|
157
|
+
|
158
|
+
# Only promote to description if BOTH code and expectation are found nearby
|
159
|
+
# AND it looks like a test description
|
160
|
+
if has_code && has_expectation && looks_like_test_description
|
161
|
+
token.merge(type: :description)
|
162
|
+
else
|
163
|
+
token.merge(type: :comment)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
else
|
168
|
+
token
|
169
|
+
end
|
170
|
+
end
|
71
171
|
end
|
72
172
|
|
73
173
|
# Convert potential_descriptions to descriptions or comments based on context
|
@@ -360,6 +460,9 @@ class Tryouts
|
|
360
460
|
# Extract source lines from the original source during parsing
|
361
461
|
source_lines = @lines[start_line..end_line]
|
362
462
|
|
463
|
+
# Find the first expectation line for better error reporting
|
464
|
+
first_expectation_line = exp_tokens.empty? ? start_line : exp_tokens.first[:line]
|
465
|
+
|
363
466
|
TestCase.new(
|
364
467
|
description: desc,
|
365
468
|
code: extract_code_content(code_tokens),
|
@@ -387,6 +490,7 @@ class Tryouts
|
|
387
490
|
line_range: start_line..end_line,
|
388
491
|
path: @source_path,
|
389
492
|
source_lines: source_lines,
|
493
|
+
first_expectation_line: first_expectation_line,
|
390
494
|
)
|
391
495
|
else
|
392
496
|
raise "Invalid test block structure: #{block}"
|
@@ -38,10 +38,13 @@ class Tryouts
|
|
38
38
|
@test_case_count = 0
|
39
39
|
@setup_failed = false
|
40
40
|
|
41
|
+
# Setup container for fresh context mode - preserves @instance_variables from setup
|
42
|
+
@setup_container = nil
|
43
|
+
|
41
44
|
# Circuit breaker for batch-level failure protection
|
42
|
-
@consecutive_failures
|
45
|
+
@consecutive_failures = 0
|
43
46
|
@max_consecutive_failures = options[:max_consecutive_failures] || 10
|
44
|
-
@circuit_breaker_active
|
47
|
+
@circuit_breaker_active = false
|
45
48
|
|
46
49
|
# Expose context objects for testing - different strategies for each mode
|
47
50
|
@shared_context = if options[:shared_context]
|
@@ -68,7 +71,19 @@ class Tryouts
|
|
68
71
|
|
69
72
|
# Stop execution if setup failed
|
70
73
|
if @setup_failed
|
71
|
-
@output_manager&.error(
|
74
|
+
@output_manager&.error('Stopping batch execution due to setup failure')
|
75
|
+
@status = :failed
|
76
|
+
finalize_results([])
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
else
|
80
|
+
# Fresh context mode: execute setup once to establish shared @instance_variables
|
81
|
+
@output_manager&.info('Running setup for fresh context...', 2)
|
82
|
+
execute_fresh_context_setup
|
83
|
+
|
84
|
+
# Stop execution if setup failed
|
85
|
+
if @setup_failed
|
86
|
+
@output_manager&.error('Stopping batch execution due to setup failure')
|
72
87
|
@status = :failed
|
73
88
|
finalize_results([])
|
74
89
|
return false
|
@@ -94,10 +109,10 @@ class Tryouts
|
|
94
109
|
update_circuit_breaker(result)
|
95
110
|
|
96
111
|
result
|
97
|
-
rescue StandardError =>
|
98
|
-
@output_manager&.test_end(test_case, idx, @test_case_count, status: :failed, error:
|
112
|
+
rescue StandardError => ex
|
113
|
+
@output_manager&.test_end(test_case, idx, @test_case_count, status: :failed, error: ex)
|
99
114
|
# Create error result packet to maintain consistent data flow
|
100
|
-
error_result = build_error_result(test_case,
|
115
|
+
error_result = build_error_result(test_case, ex)
|
101
116
|
process_test_result(error_result)
|
102
117
|
|
103
118
|
# Update circuit breaker for exception cases
|
@@ -174,7 +189,7 @@ class Tryouts
|
|
174
189
|
error: result.error,
|
175
190
|
captured_output: captured_output,
|
176
191
|
elapsed_time: result.elapsed_time,
|
177
|
-
metadata: result.metadata
|
192
|
+
metadata: result.metadata,
|
178
193
|
)
|
179
194
|
end
|
180
195
|
|
@@ -188,7 +203,7 @@ class Tryouts
|
|
188
203
|
execute_test_case_with_container(test_case, @container)
|
189
204
|
end
|
190
205
|
|
191
|
-
# Fresh context execution -
|
206
|
+
# Fresh context execution - tests run in isolated state but inherit setup @instance_variables
|
192
207
|
def execute_with_fresh_context(test_case)
|
193
208
|
fresh_container = if @shared_context.is_a?(FreshContextFactory)
|
194
209
|
@shared_context.create_container
|
@@ -196,10 +211,12 @@ class Tryouts
|
|
196
211
|
Object.new # Fallback for backwards compatibility
|
197
212
|
end
|
198
213
|
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
214
|
+
# Copy @instance_variables from setup container to fresh container
|
215
|
+
if @setup_container
|
216
|
+
@setup_container.instance_variables.each do |var|
|
217
|
+
value = @setup_container.instance_variable_get(var)
|
218
|
+
fresh_container.instance_variable_set(var, value)
|
219
|
+
end
|
203
220
|
end
|
204
221
|
|
205
222
|
execute_test_case_with_container(test_case, fresh_container)
|
@@ -225,7 +242,7 @@ class Tryouts
|
|
225
242
|
# Check if we need output capture for any expectations
|
226
243
|
needs_output_capture = test_case.expectations.any?(&:output?)
|
227
244
|
|
228
|
-
result_value,
|
245
|
+
result_value, _, _, _, expectations_result =
|
229
246
|
execute_with_timeout(test_timeout, test_case) do
|
230
247
|
if needs_output_capture
|
231
248
|
# Execute with output capture using Fiber-local isolation
|
@@ -239,9 +256,9 @@ class Tryouts
|
|
239
256
|
else
|
240
257
|
# Regular execution with timing capture only
|
241
258
|
execution_start_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
242
|
-
result_value
|
243
|
-
execution_end_ns
|
244
|
-
execution_time_ns
|
259
|
+
result_value = container.instance_eval(code, path, range.first + 1)
|
260
|
+
execution_end_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
261
|
+
execution_time_ns = execution_end_ns - execution_start_ns
|
245
262
|
|
246
263
|
expectations_result = evaluate_expectations(test_case, result_value, container, execution_time_ns)
|
247
264
|
[result_value, execution_time_ns, nil, nil, expectations_result]
|
@@ -276,9 +293,9 @@ class Tryouts
|
|
276
293
|
|
277
294
|
# Execute with timing capture
|
278
295
|
execution_start_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
279
|
-
result_value
|
280
|
-
execution_end_ns
|
281
|
-
execution_time_ns
|
296
|
+
result_value = container.instance_eval(code, path, range.first + 1)
|
297
|
+
execution_end_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
298
|
+
execution_time_ns = execution_end_ns - execution_start_ns
|
282
299
|
|
283
300
|
[result_value, execution_time_ns]
|
284
301
|
end.resume.tap do |result_value, execution_time_ns|
|
@@ -317,7 +334,7 @@ class Tryouts
|
|
317
334
|
{
|
318
335
|
passed: evaluation_results.all? { |r| r[:passed] },
|
319
336
|
actual_results: evaluation_results.map { |r| r[:actual] },
|
320
|
-
expected_results: evaluation_results.map { |r| r[:expected] }
|
337
|
+
expected_results: evaluation_results.map { |r| r[:expected] },
|
321
338
|
}
|
322
339
|
end
|
323
340
|
|
@@ -328,14 +345,14 @@ class Tryouts
|
|
328
345
|
test_case,
|
329
346
|
result_value,
|
330
347
|
expectations_result[:actual_results],
|
331
|
-
expectations_result[:expected_results]
|
348
|
+
expectations_result[:expected_results],
|
332
349
|
)
|
333
350
|
else
|
334
351
|
TestCaseResultPacket.from_failure(
|
335
352
|
test_case,
|
336
353
|
result_value,
|
337
354
|
expectations_result[:actual_results],
|
338
|
-
expectations_result[:expected_results]
|
355
|
+
expectations_result[:expected_results],
|
339
356
|
)
|
340
357
|
end
|
341
358
|
end
|
@@ -375,7 +392,7 @@ class Tryouts
|
|
375
392
|
@output_manager&.setup_output(captured_output) if captured_output && !captured_output.empty?
|
376
393
|
end
|
377
394
|
rescue StandardError => ex
|
378
|
-
@setup_failed
|
395
|
+
@setup_failed = true
|
379
396
|
@global_tally[:total_errors] += 1 if @global_tally
|
380
397
|
|
381
398
|
# Classify error and handle appropriately
|
@@ -394,6 +411,43 @@ class Tryouts
|
|
394
411
|
raise "Global setup failed (#{ex.class}): #{ex.message}"
|
395
412
|
end
|
396
413
|
|
414
|
+
# Setup execution for fresh context mode - creates @setup_container with @instance_variables
|
415
|
+
def execute_fresh_context_setup
|
416
|
+
setup = @testrun.setup
|
417
|
+
|
418
|
+
if setup && !setup.code.empty? && !@options[:shared_context]
|
419
|
+
@output_manager&.setup_start(setup.line_range)
|
420
|
+
|
421
|
+
# Create setup container to hold @instance_variables
|
422
|
+
@setup_container = Object.new
|
423
|
+
|
424
|
+
# Capture setup output instead of letting it print directly
|
425
|
+
captured_output = capture_output do
|
426
|
+
@setup_container.instance_eval(setup.code, setup.path, setup.line_range.first + 1)
|
427
|
+
end
|
428
|
+
|
429
|
+
@output_manager&.setup_output(captured_output) if captured_output && !captured_output.empty?
|
430
|
+
end
|
431
|
+
rescue StandardError => ex
|
432
|
+
@setup_failed = true
|
433
|
+
@global_tally[:total_errors] += 1 if @global_tally
|
434
|
+
|
435
|
+
# Classify error and handle appropriately
|
436
|
+
error_type = Tryouts.classify_error(ex)
|
437
|
+
|
438
|
+
Tryouts.debug "Setup failed with #{error_type} error: (#{ex.class}): #{ex.message}"
|
439
|
+
Tryouts.trace ex.backtrace
|
440
|
+
|
441
|
+
# For non-catastrophic errors, we still stop batch execution
|
442
|
+
unless Tryouts.batch_stopping_error?(ex)
|
443
|
+
@output_manager&.error("Fresh context setup failed: #{ex.message}")
|
444
|
+
return
|
445
|
+
end
|
446
|
+
|
447
|
+
# For catastrophic errors, still raise to stop execution
|
448
|
+
raise "Fresh context setup failed (#{ex.class}): #{ex.message}"
|
449
|
+
end
|
450
|
+
|
397
451
|
# Global teardown execution
|
398
452
|
def execute_global_teardown
|
399
453
|
teardown = @testrun.teardown
|
@@ -420,11 +474,11 @@ class Tryouts
|
|
420
474
|
@output_manager&.error("Teardown failed: #{ex.message}")
|
421
475
|
|
422
476
|
# Teardown failures are generally non-fatal - log and continue
|
423
|
-
|
424
|
-
@output_manager&.error("Continuing despite teardown failure")
|
425
|
-
else
|
477
|
+
if Tryouts.batch_stopping_error?(ex)
|
426
478
|
# Only catastrophic errors should potentially affect batch completion
|
427
|
-
@output_manager&.error(
|
479
|
+
@output_manager&.error('Teardown failure may affect subsequent operations')
|
480
|
+
else
|
481
|
+
@output_manager&.error('Continuing despite teardown failure')
|
428
482
|
end
|
429
483
|
end
|
430
484
|
|
@@ -477,11 +531,9 @@ class Tryouts
|
|
477
531
|
end
|
478
532
|
|
479
533
|
# Timeout protection for individual test execution
|
480
|
-
def execute_with_timeout(timeout_seconds, test_case)
|
481
|
-
Timeout.timeout(timeout_seconds)
|
482
|
-
|
483
|
-
end
|
484
|
-
rescue Timeout::Error => e
|
534
|
+
def execute_with_timeout(timeout_seconds, test_case, &)
|
535
|
+
Timeout.timeout(timeout_seconds, &)
|
536
|
+
rescue Timeout::Error
|
485
537
|
Tryouts.debug "Test timeout after #{timeout_seconds}s: #{test_case.description}"
|
486
538
|
raise StandardError.new("Test execution timeout (#{timeout_seconds}s)")
|
487
539
|
end
|
@@ -496,7 +548,7 @@ class Tryouts
|
|
496
548
|
end
|
497
549
|
else
|
498
550
|
# Reset on success
|
499
|
-
@consecutive_failures
|
551
|
+
@consecutive_failures = 0
|
500
552
|
@circuit_breaker_active = false
|
501
553
|
end
|
502
554
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# Modern data structures using Ruby 3.2+ Data classes
|
4
4
|
class Tryouts
|
5
5
|
# Core data structures
|
6
|
-
TestCase = Data.define(:description, :code, :expectations, :line_range, :path, :source_lines) do
|
6
|
+
TestCase = Data.define(:description, :code, :expectations, :line_range, :path, :source_lines, :first_expectation_line) do
|
7
7
|
def empty?
|
8
8
|
code.empty?
|
9
9
|
end
|
data/lib/tryouts/test_runner.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# lib/tryouts/test_runner.rb
|
2
2
|
|
3
3
|
require_relative 'prism_parser'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'test_batch'
|
5
5
|
require_relative 'translators/rspec_translator'
|
6
6
|
require_relative 'translators/minitest_translator'
|
7
7
|
require_relative 'file_processor'
|
@@ -2,6 +2,44 @@
|
|
2
2
|
|
3
3
|
class Tryouts
|
4
4
|
module Translators
|
5
|
+
# Translates Tryouts test files to Minitest format
|
6
|
+
#
|
7
|
+
# IMPORTANT: Context Mode Differences
|
8
|
+
# ==================================
|
9
|
+
#
|
10
|
+
# Tryouts supports two context modes that behave differently than Minitest:
|
11
|
+
#
|
12
|
+
# 1. Tryouts Shared Context (default):
|
13
|
+
# - Setup runs once, all tests share the same context object
|
14
|
+
# - Tests can modify variables/state and affect subsequent tests
|
15
|
+
# - Behaves like a Ruby script executing top-to-bottom
|
16
|
+
# - Designed for documentation-style tests where examples build on each other
|
17
|
+
#
|
18
|
+
# 2. Tryouts Fresh Context (--no-shared-context):
|
19
|
+
# - Setup @instance_variables are copied to each test's fresh context
|
20
|
+
# - Tests are isolated but inherit setup state
|
21
|
+
# - Similar to Minitest's setup method but with setup state inheritance
|
22
|
+
#
|
23
|
+
# Minitest Translation Behavior:
|
24
|
+
# ==============================
|
25
|
+
# - Uses setup method which runs before each test (Minitest standard)
|
26
|
+
# - Each test method gets fresh context (Minitest standard)
|
27
|
+
# - Tests that rely on shared state between test cases WILL FAIL
|
28
|
+
# - This is intentional and reveals inappropriate test dependencies
|
29
|
+
#
|
30
|
+
# Example that works in Tryouts shared mode but fails in Minitest:
|
31
|
+
# ## TEST 1
|
32
|
+
# @counter = 1
|
33
|
+
# @counter
|
34
|
+
# #=> 1
|
35
|
+
#
|
36
|
+
# ## TEST 2
|
37
|
+
# @counter += 1 # Will be reset to 1 by setup, then fail
|
38
|
+
# @counter
|
39
|
+
# #=> 2
|
40
|
+
#
|
41
|
+
# Recommendation: Write tryouts tests that work in fresh context mode
|
42
|
+
# if you plan to use Minitest translation.
|
5
43
|
class MinitestTranslator
|
6
44
|
def initialize
|
7
45
|
require 'minitest/test'
|
@@ -2,6 +2,44 @@
|
|
2
2
|
|
3
3
|
class Tryouts
|
4
4
|
module Translators
|
5
|
+
# Translates Tryouts test files to RSpec format
|
6
|
+
#
|
7
|
+
# IMPORTANT: Context Mode Differences
|
8
|
+
# ==================================
|
9
|
+
#
|
10
|
+
# Tryouts supports two context modes that behave differently than RSpec:
|
11
|
+
#
|
12
|
+
# 1. Tryouts Shared Context (default):
|
13
|
+
# - Setup runs once, all tests share the same context object
|
14
|
+
# - Tests can modify variables/state and affect subsequent tests
|
15
|
+
# - Behaves like a Ruby script executing top-to-bottom
|
16
|
+
# - Designed for documentation-style tests where examples build on each other
|
17
|
+
#
|
18
|
+
# 2. Tryouts Fresh Context (--no-shared-context):
|
19
|
+
# - Setup @instance_variables are copied to each test's fresh context
|
20
|
+
# - Tests are isolated but inherit setup state
|
21
|
+
# - Similar to RSpec's before(:each) but with setup state inheritance
|
22
|
+
#
|
23
|
+
# RSpec Translation Behavior:
|
24
|
+
# ===========================
|
25
|
+
# - Uses before(:all) for setup code (closest equivalent to shared context)
|
26
|
+
# - Each 'it' block gets fresh context (RSpec standard)
|
27
|
+
# - Tests that rely on shared state between test cases WILL FAIL
|
28
|
+
# - This is intentional and reveals inappropriate test dependencies
|
29
|
+
#
|
30
|
+
# Example that works in Tryouts shared mode but fails in RSpec:
|
31
|
+
# ## TEST 1
|
32
|
+
# @counter = 1
|
33
|
+
# @counter
|
34
|
+
# #=> 1
|
35
|
+
#
|
36
|
+
# ## TEST 2
|
37
|
+
# @counter += 1 # Will be nil in RSpec, causing failure
|
38
|
+
# @counter
|
39
|
+
# #=> 2
|
40
|
+
#
|
41
|
+
# Recommendation: Write tryouts tests that work in fresh context mode
|
42
|
+
# if you plan to use RSpec translation.
|
5
43
|
class RSpecTranslator
|
6
44
|
def initialize
|
7
45
|
require 'rspec/core'
|
data/lib/tryouts/version.rb
CHANGED
data/lib/tryouts.rb
CHANGED
@@ -8,7 +8,7 @@ require 'timeout'
|
|
8
8
|
TRYOUTS_LIB_HOME = __dir__ unless defined?(TRYOUTS_LIB_HOME)
|
9
9
|
|
10
10
|
require_relative 'tryouts/console'
|
11
|
-
require_relative 'tryouts/
|
11
|
+
require_relative 'tryouts/test_batch'
|
12
12
|
require_relative 'tryouts/version'
|
13
13
|
require_relative 'tryouts/prism_parser'
|
14
14
|
require_relative 'tryouts/cli'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tryouts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -98,10 +98,10 @@ files:
|
|
98
98
|
- lib/tryouts/expectation_evaluators/true.rb
|
99
99
|
- lib/tryouts/file_processor.rb
|
100
100
|
- lib/tryouts/prism_parser.rb
|
101
|
+
- lib/tryouts/test_batch.rb
|
102
|
+
- lib/tryouts/test_case.rb
|
101
103
|
- lib/tryouts/test_executor.rb
|
102
104
|
- lib/tryouts/test_runner.rb
|
103
|
-
- lib/tryouts/testbatch.rb
|
104
|
-
- lib/tryouts/testcase.rb
|
105
105
|
- lib/tryouts/translators/minitest_translator.rb
|
106
106
|
- lib/tryouts/translators/rspec_translator.rb
|
107
107
|
- lib/tryouts/version.rb
|