tryouts 3.3.0 → 3.3.2
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/exe/try +1 -1
- data/lib/tryouts/cli/formatters/compact.rb +8 -4
- data/lib/tryouts/cli/formatters/quiet.rb +4 -3
- data/lib/tryouts/cli/formatters/verbose.rb +8 -4
- data/lib/tryouts/cli/opts.rb +14 -4
- data/lib/tryouts/console.rb +32 -4
- data/lib/tryouts/expectation_evaluators/exception.rb +8 -2
- data/lib/tryouts/expectation_evaluators/non_nil.rb +77 -0
- data/lib/tryouts/expectation_evaluators/regex_match.rb +11 -3
- data/lib/tryouts/expectation_evaluators/registry.rb +2 -0
- data/lib/tryouts/expectation_evaluators/result_type.rb +9 -1
- data/lib/tryouts/file_processor.rb +9 -5
- data/lib/tryouts/parsers/base_parser.rb +23 -0
- data/lib/tryouts/parsers/enhanced_parser.rb +115 -0
- data/lib/tryouts/parsers/prism_parser.rb +122 -0
- data/lib/tryouts/parsers/shared_methods.rb +416 -0
- data/lib/tryouts/test_batch.rb +54 -13
- data/lib/tryouts/test_case.rb +3 -3
- data/lib/tryouts/test_executor.rb +6 -4
- data/lib/tryouts/test_result_aggregator.rb +138 -0
- data/lib/tryouts/test_runner.rb +76 -20
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +7 -2
- metadata +21 -3
- data/lib/tryouts/enhanced_parser.rb +0 -461
- data/lib/tryouts/prism_parser.rb +0 -516
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8d7c33ad6377a7fb1c64c83e24e643c434643aa09e5451ad42bbe80c65b9c9d
|
4
|
+
data.tar.gz: 89cfe371c0fd575614a56702c904d0c5140db2a3a2af41d22777e459de1f4d8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 174645930ab03bc8332e415e9dde67e6c261a66ec2aa4f5572a31841a339cb244d15da75f6d314e3e2ec2d987fb5439e03c34d35d244b37d8b81a7e5b4dbc55d
|
7
|
+
data.tar.gz: 524ded2d4d670ed5fce810ef363faaba2459e56979fc13e61cdd7fbad6a3cffe2cadef7e7c8f1be0901ed1d26b44185f05f8be524c0ebe60a76e3bb841be5b37
|
data/exe/try
CHANGED
@@ -17,7 +17,7 @@ if ENV['COVERAGE'] || ENV['SIMPLECOV']
|
|
17
17
|
add_group 'Core', 'lib/tryouts.rb'
|
18
18
|
add_group 'CLI', 'lib/tryouts/cli'
|
19
19
|
add_group 'Formatters', 'lib/tryouts/cli/formatters'
|
20
|
-
add_group 'Parsers', 'lib/tryouts/
|
20
|
+
add_group 'Parsers', 'lib/tryouts/parsers'
|
21
21
|
add_group 'Data Structures', ['lib/tryouts/testcase.rb', 'lib/tryouts/testbatch.rb']
|
22
22
|
add_group 'Translators', 'lib/tryouts/translators'
|
23
23
|
add_group 'Execution', ['lib/tryouts/test_executor.rb', 'lib/tryouts/test_runner.rb', 'lib/tryouts/file_processor.rb']
|
@@ -11,6 +11,7 @@ class Tryouts
|
|
11
11
|
@show_debug = options.fetch(:debug, false)
|
12
12
|
@show_trace = options.fetch(:trace, false)
|
13
13
|
@show_passed = options.fetch(:show_passed, true)
|
14
|
+
@show_stack_traces = options.fetch(:stack_traces, false) || options.fetch(:debug, false)
|
14
15
|
end
|
15
16
|
|
16
17
|
# Phase-level output - minimal for compact mode
|
@@ -57,6 +58,8 @@ class Tryouts
|
|
57
58
|
puts Console.color(:red, 'Failed Tests:')
|
58
59
|
puts
|
59
60
|
|
61
|
+
# Number failures sequentially across all files instead of per-file
|
62
|
+
failure_number = 1
|
60
63
|
failure_collector.failures_by_file.each do |file_path, failures|
|
61
64
|
failures.each do |failure|
|
62
65
|
pretty_path = Console.pretty_path(file_path)
|
@@ -68,10 +71,11 @@ class Tryouts
|
|
68
71
|
pretty_path
|
69
72
|
end
|
70
73
|
|
71
|
-
puts " #{location}"
|
74
|
+
puts " #{failure_number}) #{location}"
|
72
75
|
puts " #{Console.color(:red, '✗')} #{failure.description}"
|
73
76
|
puts " #{failure.failure_reason}"
|
74
77
|
puts
|
78
|
+
failure_number += 1
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
@@ -235,10 +239,10 @@ class Tryouts
|
|
235
239
|
def error_message(message, backtrace: nil)
|
236
240
|
@stderr.puts Console.color(:red, "ERROR: #{message}")
|
237
241
|
|
238
|
-
return unless backtrace && @
|
242
|
+
return unless backtrace && @show_stack_traces
|
239
243
|
|
240
|
-
|
241
|
-
@stderr.puts indent_text(line
|
244
|
+
Console.pretty_backtrace(backtrace, limit: 3).each do |line|
|
245
|
+
@stderr.puts indent_text(line, 1)
|
242
246
|
end
|
243
247
|
end
|
244
248
|
|
@@ -10,6 +10,7 @@ class Tryouts
|
|
10
10
|
super
|
11
11
|
@show_errors = options.fetch(:show_errors, true)
|
12
12
|
@show_final_summary = options.fetch(:show_final_summary, true)
|
13
|
+
@show_stack_traces = options.fetch(:stack_traces, false) || options.fetch(:debug, false)
|
13
14
|
@current_file = nil
|
14
15
|
end
|
15
16
|
|
@@ -80,10 +81,10 @@ class Tryouts
|
|
80
81
|
@stderr.puts
|
81
82
|
@stderr.puts Console.color(:red, "ERROR: #{message}")
|
82
83
|
|
83
|
-
return unless backtrace && @
|
84
|
+
return unless backtrace && @show_stack_traces
|
84
85
|
|
85
|
-
|
86
|
-
@stderr.puts " #{line
|
86
|
+
Console.pretty_backtrace(backtrace, limit: 3).each do |line|
|
87
|
+
@stderr.puts " #{line}"
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
@@ -12,6 +12,7 @@ class Tryouts
|
|
12
12
|
@show_passed = options.fetch(:show_passed, true)
|
13
13
|
@show_debug = options.fetch(:debug, false)
|
14
14
|
@show_trace = options.fetch(:trace, false)
|
15
|
+
@show_stack_traces = options.fetch(:stack_traces, false) || options.fetch(:debug, false)
|
15
16
|
end
|
16
17
|
|
17
18
|
# Phase-level output
|
@@ -56,8 +57,10 @@ class Tryouts
|
|
56
57
|
puts
|
57
58
|
puts Console.color(:red, 'Failed Tests:')
|
58
59
|
|
60
|
+
# Number failures sequentially across all files instead of per-file
|
61
|
+
failure_number = 1
|
59
62
|
failure_collector.failures_by_file.each do |file_path, failures|
|
60
|
-
failures.
|
63
|
+
failures.each do |failure|
|
61
64
|
pretty_path = Console.pretty_path(file_path)
|
62
65
|
|
63
66
|
# Include line number with file path for easy copying/clicking
|
@@ -69,7 +72,7 @@ class Tryouts
|
|
69
72
|
|
70
73
|
puts
|
71
74
|
puts Console.color(:yellow, location)
|
72
|
-
puts " #{
|
75
|
+
puts " #{failure_number}) #{failure.description}"
|
73
76
|
puts " #{Console.color(:red, 'Failure:')} #{failure.failure_reason}"
|
74
77
|
|
75
78
|
# Show source context in verbose mode
|
@@ -80,6 +83,7 @@ class Tryouts
|
|
80
83
|
end
|
81
84
|
end
|
82
85
|
puts
|
86
|
+
failure_number += 1
|
83
87
|
end
|
84
88
|
end
|
85
89
|
end
|
@@ -241,11 +245,11 @@ class Tryouts
|
|
241
245
|
error_msg = Console.color(:red, "ERROR: #{message}")
|
242
246
|
puts indent_text(error_msg, 1)
|
243
247
|
|
244
|
-
return unless backtrace && @
|
248
|
+
return unless backtrace && @show_stack_traces
|
245
249
|
|
246
250
|
puts indent_text('Details:', 2)
|
247
251
|
# Show first 10 lines of backtrace to avoid overwhelming output
|
248
|
-
|
252
|
+
Console.pretty_backtrace(backtrace, limit: 10).each do |line|
|
249
253
|
puts indent_text(line, 3)
|
250
254
|
end
|
251
255
|
puts indent_text("... (#{backtrace.length - 10} more lines)", 3) if backtrace.length > 10
|
data/lib/tryouts/cli/opts.rb
CHANGED
@@ -26,8 +26,8 @@ class Tryouts
|
|
26
26
|
|
27
27
|
#=> Value equality #==> Must be true #=/=> Must be false
|
28
28
|
#=|> True OR false #=!> Must raise error #=:> Type matching
|
29
|
-
#=~> Regex matching #=%> Time constraints
|
30
|
-
#=2> STDERR content
|
29
|
+
#=~> Regex matching #=%> Time constraints #=*> Non-nil result
|
30
|
+
#=1> STDOUT content #=2> STDERR content #=<> Intentional failure
|
31
31
|
HELP
|
32
32
|
|
33
33
|
class << self
|
@@ -65,19 +65,29 @@ class Tryouts
|
|
65
65
|
opts.on('-q', '--quiet', 'Minimal output (dots and summary only)') { options[:quiet] = true }
|
66
66
|
opts.on('-c', '--compact', 'Compact single-line output') { options[:compact] = true }
|
67
67
|
opts.on('-l', '--live', 'Live status display') { options[:live_status] = true }
|
68
|
+
opts.on('-j', '--parallel [THREADS]', 'Run test files in parallel (optional thread count)') do |threads|
|
69
|
+
options[:parallel] = true
|
70
|
+
options[:parallel_threads] = threads.to_i if threads && threads.to_i > 0
|
71
|
+
end
|
68
72
|
|
69
73
|
opts.separator "\nParser Options:"
|
70
|
-
opts.on('--enhanced-parser', 'Use enhanced parser with inhouse comment extraction') { options[:parser] = :enhanced }
|
71
|
-
opts.on('--legacy-parser', 'Use legacy parser
|
74
|
+
opts.on('--enhanced-parser', 'Use enhanced parser with inhouse comment extraction (default)') { options[:parser] = :enhanced }
|
75
|
+
opts.on('--legacy-parser', 'Use legacy prism parser') { options[:parser] = :prism }
|
72
76
|
|
73
77
|
opts.separator "\nInspection Options:"
|
74
78
|
opts.on('-i', '--inspect', 'Inspect file structure without running tests') { options[:inspect] = true }
|
75
79
|
|
76
80
|
opts.separator "\nGeneral Options:"
|
81
|
+
opts.on('-s', '--stack-traces', 'Show stack traces for exceptions') do
|
82
|
+
options[:stack_traces] = true
|
83
|
+
Tryouts.stack_traces = true
|
84
|
+
end
|
77
85
|
opts.on('-V', '--version', 'Show version') { options[:version] = true }
|
78
86
|
opts.on('-D', '--debug', 'Enable debug mode') do
|
79
87
|
options[:debug] = true
|
88
|
+
options[:stack_traces] = true # Debug mode auto-enables stack traces
|
80
89
|
Tryouts.debug = true
|
90
|
+
Tryouts.stack_traces = true
|
81
91
|
end
|
82
92
|
opts.on('-h', '--help', 'Show this help') do
|
83
93
|
puts opts
|
data/lib/tryouts/console.rb
CHANGED
@@ -161,12 +161,40 @@ class Tryouts
|
|
161
161
|
# directory. This simplifies logging and error reporting by showing
|
162
162
|
# only the relevant parts of file paths instead of lengthy absolute paths.
|
163
163
|
#
|
164
|
-
def pretty_path(
|
165
|
-
return nil if
|
164
|
+
def pretty_path(filepath)
|
165
|
+
return nil if filepath.nil? || filepath.empty?
|
166
166
|
|
167
|
-
file = File.expand_path(file) # be absolutely sure
|
168
167
|
basepath = Dir.pwd
|
169
|
-
|
168
|
+
begin
|
169
|
+
relative_path = Pathname.new(filepath).relative_path_from(basepath)
|
170
|
+
if relative_path.to_s.start_with?('..')
|
171
|
+
File.basename(filepath)
|
172
|
+
else
|
173
|
+
relative_path.to_s
|
174
|
+
end
|
175
|
+
rescue ArgumentError
|
176
|
+
# Handle cases where filepath cannot be relativized (e.g., empty paths, different roots)
|
177
|
+
File.basename(filepath)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Format backtrace entries with pretty file paths
|
182
|
+
def pretty_backtrace(backtrace, limit: 10)
|
183
|
+
return [] unless backtrace&.any?
|
184
|
+
|
185
|
+
backtrace.first(limit).map do |frame|
|
186
|
+
# Split the frame to get file path and line info
|
187
|
+
# Use non-greedy match and more specific pattern to prevent ReDoS
|
188
|
+
if frame.match(/^([^:]+(?::[^:0-9][^:]*)*):(\d+):(.*)$/)
|
189
|
+
file_part = $1
|
190
|
+
line_part = $2
|
191
|
+
method_part = $3
|
192
|
+
pretty_file = pretty_path(file_part) || File.basename(file_part)
|
193
|
+
"#{pretty_file}:#{line_part}#{method_part}"
|
194
|
+
else
|
195
|
+
frame
|
196
|
+
end
|
197
|
+
end
|
170
198
|
end
|
171
199
|
end
|
172
200
|
end
|
@@ -9,8 +9,14 @@ class Tryouts
|
|
9
9
|
expectation_type == :exception
|
10
10
|
end
|
11
11
|
|
12
|
-
def evaluate(_actual_result = nil)
|
13
|
-
|
12
|
+
def evaluate(_actual_result = nil, caught_exception: nil)
|
13
|
+
if caught_exception
|
14
|
+
# Use the pre-caught exception to avoid double execution
|
15
|
+
evaluate_exception_condition(caught_exception)
|
16
|
+
else
|
17
|
+
# Fallback for direct calls - shouldn't happen in normal flow
|
18
|
+
execute_test_code_and_evaluate_exception
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
22
|
private
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# lib/tryouts/expectation_evaluators/non_nil.rb
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
class Tryouts
|
6
|
+
module ExpectationEvaluators
|
7
|
+
# Evaluator for non-nil expectations using syntax: #=*>
|
8
|
+
#
|
9
|
+
# PURPOSE:
|
10
|
+
# - Validates that the test result is not nil and no exception occurred
|
11
|
+
# - Provides a simple "anything goes" expectation for existence checks
|
12
|
+
# - Useful for API responses, object creation, method return values
|
13
|
+
#
|
14
|
+
# SYNTAX: #=*>
|
15
|
+
# Examples:
|
16
|
+
# user = User.create(name: "test")
|
17
|
+
# #=*> # Pass: user object exists (not nil)
|
18
|
+
#
|
19
|
+
# response = api_call()
|
20
|
+
# #=*> # Pass: got some response (not nil)
|
21
|
+
#
|
22
|
+
# nil
|
23
|
+
# #=*> # Fail: result is nil
|
24
|
+
#
|
25
|
+
# raise StandardError.new("error")
|
26
|
+
# #=*> # Fail: exception occurred
|
27
|
+
#
|
28
|
+
# VALIDATION LOGIC:
|
29
|
+
# - Passes when result is not nil AND no exception was raised during execution
|
30
|
+
# - Fails when result is nil OR an exception occurred
|
31
|
+
# - Does not evaluate any additional expression (unlike other expectation types)
|
32
|
+
#
|
33
|
+
# IMPLEMENTATION DETAILS:
|
34
|
+
# - Simple existence check without complex evaluation
|
35
|
+
# - No expression parsing needed - syntax is just #=*>
|
36
|
+
# - Expected display shows "non-nil result with no exception"
|
37
|
+
# - Actual display shows the actual result value or exception
|
38
|
+
#
|
39
|
+
# DESIGN DECISIONS:
|
40
|
+
# - Uses #=*> syntax where * represents "anything"
|
41
|
+
# - Part of unified #= prefix convention for all expectation types
|
42
|
+
# - Complements existing boolean and equality expectations
|
43
|
+
# - Provides simple alternative to complex conditional expressions
|
44
|
+
# - Useful for integration tests where exact values are unpredictable
|
45
|
+
#
|
46
|
+
# VARIABLE ACCESS:
|
47
|
+
# - No special variables needed since no expression is evaluated
|
48
|
+
# - Works purely on the actual test result value
|
49
|
+
class NonNil < Base
|
50
|
+
def self.handles?(expectation_type)
|
51
|
+
expectation_type == :non_nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def evaluate(actual_result = nil, caught_exception: nil)
|
55
|
+
# Check if an exception occurred during test execution
|
56
|
+
if caught_exception
|
57
|
+
return build_result(
|
58
|
+
passed: false,
|
59
|
+
actual: "(#{caught_exception.class}) #{caught_exception.message}",
|
60
|
+
expected: 'non-nil result with no exception',
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if result is nil
|
65
|
+
passed = !actual_result.nil?
|
66
|
+
|
67
|
+
build_result(
|
68
|
+
passed: passed,
|
69
|
+
actual: actual_result,
|
70
|
+
expected: 'non-nil result',
|
71
|
+
)
|
72
|
+
rescue StandardError => ex
|
73
|
+
handle_evaluation_error(ex, actual_result)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -40,9 +40,17 @@ class Tryouts
|
|
40
40
|
expectation_result = ExpectationResult.from_result(actual_result)
|
41
41
|
pattern = eval_expectation_content(@expectation.content, expectation_result)
|
42
42
|
|
43
|
-
#
|
44
|
-
|
45
|
-
|
43
|
+
# Auto-detect exceptions and use message for regex matching
|
44
|
+
# This allows #=~> /pattern/ to work naturally with exception messages
|
45
|
+
string_result = if actual_result.is_a?(Exception)
|
46
|
+
# Make error available in context for manual access if needed
|
47
|
+
@context.define_singleton_method(:error) { actual_result }
|
48
|
+
actual_result.message # Match against error message
|
49
|
+
else
|
50
|
+
actual_result.to_s # Normal case: convert to string
|
51
|
+
end
|
52
|
+
|
53
|
+
match_result = string_result =~ pattern
|
46
54
|
|
47
55
|
build_result(
|
48
56
|
passed: !match_result.nil?,
|
@@ -12,6 +12,7 @@ require_relative 'regex_match'
|
|
12
12
|
require_relative 'performance_time'
|
13
13
|
require_relative 'intentional_failure'
|
14
14
|
require_relative 'output'
|
15
|
+
require_relative 'non_nil'
|
15
16
|
|
16
17
|
class Tryouts
|
17
18
|
module ExpectationEvaluators
|
@@ -61,6 +62,7 @@ class Tryouts
|
|
61
62
|
register(PerformanceTime)
|
62
63
|
register(IntentionalFailure)
|
63
64
|
register(Output)
|
65
|
+
register(NonNil)
|
64
66
|
end
|
65
67
|
end
|
66
68
|
end
|
@@ -36,7 +36,15 @@ class Tryouts
|
|
36
36
|
|
37
37
|
def evaluate(actual_result = nil)
|
38
38
|
expectation_result = ExpectationResult.from_result(actual_result)
|
39
|
-
|
39
|
+
|
40
|
+
# Try to evaluate in test context first, then fallback to global context for constants
|
41
|
+
begin
|
42
|
+
expected_class = eval_expectation_content(@expectation.content, expectation_result)
|
43
|
+
rescue NameError => e
|
44
|
+
# If we can't find the constant in test context, try global context
|
45
|
+
# This is common for exception classes like ArgumentError, StandardError, etc.
|
46
|
+
expected_class = Object.const_get(@expectation.content.strip)
|
47
|
+
end
|
40
48
|
|
41
49
|
build_result(
|
42
50
|
passed: actual_result.is_a?(expected_class),
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# lib/tryouts/file_processor.rb
|
2
2
|
|
3
|
-
require_relative 'prism_parser'
|
4
|
-
require_relative 'enhanced_parser'
|
3
|
+
require_relative 'parsers/prism_parser'
|
4
|
+
require_relative 'parsers/enhanced_parser'
|
5
5
|
require_relative 'test_executor'
|
6
6
|
require_relative 'cli/modes/inspect'
|
7
7
|
require_relative 'cli/modes/generate'
|
@@ -20,7 +20,7 @@ class Tryouts
|
|
20
20
|
|
21
21
|
def process
|
22
22
|
testrun = create_parser(@file, @options).parse
|
23
|
-
@global_tally[:
|
23
|
+
@global_tally[:aggregator].increment_total_files
|
24
24
|
@output_manager.file_parsed(@file, testrun.total_tests)
|
25
25
|
|
26
26
|
if @options[:inspect]
|
@@ -39,7 +39,7 @@ class Tryouts
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def create_parser(file, options)
|
42
|
-
parser_type = options[:parser] || :
|
42
|
+
parser_type = options[:parser] || :enhanced # enhanced parser is now the default
|
43
43
|
|
44
44
|
unless PARSER_TYPES.include?(parser_type)
|
45
45
|
raise ArgumentError, "Unknown parser: #{parser_type}. Allowed types: #{PARSER_TYPES.join(', ')}"
|
@@ -76,7 +76,11 @@ class Tryouts
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def handle_general_error(ex)
|
79
|
-
|
79
|
+
if @global_tally
|
80
|
+
@global_tally[:aggregator].add_infrastructure_failure(
|
81
|
+
:file_processing, @file, ex.message, ex
|
82
|
+
)
|
83
|
+
end
|
80
84
|
@output_manager.file_failure(@file, ex.message, ex.backtrace)
|
81
85
|
1
|
82
86
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# lib/tryouts/parsers/base_parser.rb
|
2
|
+
|
3
|
+
require 'prism'
|
4
|
+
|
5
|
+
require_relative 'shared_methods'
|
6
|
+
|
7
|
+
class Tryouts
|
8
|
+
# Fixed PrismParser with pattern matching for robust token filtering
|
9
|
+
module Parsers
|
10
|
+
class BaseParser
|
11
|
+
include Tryouts::Parsers::SharedMethods
|
12
|
+
|
13
|
+
def initialize(source_path)
|
14
|
+
@source_path = source_path
|
15
|
+
@source = File.read(source_path)
|
16
|
+
@lines = @source.lines.map(&:chomp)
|
17
|
+
@prism_result = Prism.parse(@source)
|
18
|
+
@parsed_at = Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# lib/tryouts/parsers/enhanced_parser.rb
|
2
|
+
|
3
|
+
# Enhanced parser using Prism's inhouse comment extraction capabilities
|
4
|
+
# Drop-in replacement for PrismParser that eliminates HEREDOC parsing issues
|
5
|
+
|
6
|
+
require_relative '../test_case'
|
7
|
+
require_relative 'base_parser'
|
8
|
+
|
9
|
+
class Tryouts
|
10
|
+
# Enhanced parser that replaces manual line-by-line parsing with inhouse Prism APIs
|
11
|
+
# while maintaining full compatibility with the original parser's logic structure
|
12
|
+
class EnhancedParser < Tryouts::Parsers::BaseParser
|
13
|
+
|
14
|
+
def parse
|
15
|
+
return handle_syntax_errors if @prism_result.failure?
|
16
|
+
|
17
|
+
# Use inhouse comment extraction instead of line-by-line regex parsing
|
18
|
+
# This automatically excludes HEREDOC content!
|
19
|
+
tokens = tokenize_content_with_inhouse_extraction
|
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)
|
23
|
+
process_test_blocks(test_blocks)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Inhouse comment extraction - replaces the manual regex parsing
|
29
|
+
def tokenize_content_with_inhouse_extraction
|
30
|
+
tokens = []
|
31
|
+
|
32
|
+
# Get all comments using inhouse Prism extraction
|
33
|
+
comments = Prism.parse_comments(@source)
|
34
|
+
comment_by_line = comments.group_by { |comment| comment.location.start_line }
|
35
|
+
|
36
|
+
# Process each line, handling multiple comments per line
|
37
|
+
@lines.each_with_index do |line, index|
|
38
|
+
line_number = index + 1
|
39
|
+
|
40
|
+
if (comments_for_line = comment_by_line[line_number]) && !comments_for_line.empty?
|
41
|
+
emitted_code = false
|
42
|
+
comments_for_line.sort_by! { |c| c.location.start_column }
|
43
|
+
comments_for_line.each do |comment|
|
44
|
+
comment_content = comment.slice.strip
|
45
|
+
if comment.location.start_column > 0
|
46
|
+
unless emitted_code
|
47
|
+
tokens << { type: :code, content: line, line: index, ast: parse_ruby_line(line) }
|
48
|
+
emitted_code = true
|
49
|
+
end
|
50
|
+
# Inline comment may carry expectations; classify it too
|
51
|
+
tokens << classify_comment_inhousely(comment_content, line_number)
|
52
|
+
else
|
53
|
+
tokens << classify_comment_inhousely(comment_content, line_number)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
next
|
57
|
+
end
|
58
|
+
|
59
|
+
# Handle non-comment lines (blank lines and code)
|
60
|
+
token = case line
|
61
|
+
when /^\s*$/
|
62
|
+
{ type: :blank, line: index }
|
63
|
+
else
|
64
|
+
{ type: :code, content: line, line: index, ast: parse_ruby_line(line) }
|
65
|
+
end
|
66
|
+
tokens << token
|
67
|
+
end
|
68
|
+
|
69
|
+
tokens
|
70
|
+
end
|
71
|
+
|
72
|
+
# Inhouse comment classification - replaces complex regex patterns
|
73
|
+
def classify_comment_inhousely(content, line_number)
|
74
|
+
case content
|
75
|
+
when /^##\s*(.*)$/
|
76
|
+
{ type: :description, content: $1.strip, line: line_number - 1 }
|
77
|
+
when /^#\s*TEST\s*\d*:\s*(.*)$/
|
78
|
+
{ type: :description, content: $1.strip, line: line_number - 1 }
|
79
|
+
when /^#\s*=!>\s*(.*)$/
|
80
|
+
{ type: :exception_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
81
|
+
when /^#\s*=<>\s*(.*)$/
|
82
|
+
{ type: :intentional_failure_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
83
|
+
when /^#\s*==>\s*(.*)$/
|
84
|
+
{ type: :true_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
85
|
+
when %r{^#\s*=/=>\s*(.*)$}
|
86
|
+
{ type: :false_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
87
|
+
when /^#\s*=\|>\s*(.*)$/
|
88
|
+
{ type: :boolean_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
89
|
+
when /^#\s*=\*>\s*(.*)$/
|
90
|
+
{ type: :non_nil_expectation, content: $1.strip, line: line_number - 1 }
|
91
|
+
when /^#\s*=:>\s*(.*)$/
|
92
|
+
{ type: :result_type_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
93
|
+
when /^#\s*=~>\s*(.*)$/
|
94
|
+
{ type: :regex_match_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
95
|
+
when /^#\s*=%>\s*(.*)$/
|
96
|
+
{ type: :performance_time_expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
97
|
+
when /^#\s*=(\d+)>\s*(.*)$/
|
98
|
+
{ type: :output_expectation, content: $2.strip, pipe: $1.to_i, line: line_number - 1, ast: parse_expectation($2.strip) }
|
99
|
+
when /^#\s*=>\s*(.*)$/
|
100
|
+
{ type: :expectation, content: $1.strip, line: line_number - 1, ast: parse_expectation($1.strip) }
|
101
|
+
when /^##\s*=>\s*(.*)$/
|
102
|
+
{ type: :comment, content: '=>' + $1.strip, line: line_number - 1 }
|
103
|
+
when /^#\s*(.*)$/
|
104
|
+
{ type: :potential_description, content: $1.strip, line: line_number - 1 }
|
105
|
+
else
|
106
|
+
{ type: :comment, content: content.sub(/^#\s*/, ''), line: line_number - 1 }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parser type identification for metadata
|
111
|
+
def parser_type
|
112
|
+
:enhanced
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|