tryouts 3.5.1 → 3.5.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/README.md +5 -8
- data/lib/tryouts/cli/formatters/agent.rb +33 -15
- data/lib/tryouts/cli/opts.rb +15 -15
- data/lib/tryouts/file_processor.rb +2 -2
- data/lib/tryouts/parser_warning.rb +10 -0
- data/lib/tryouts/parsers/CLAUDE.md +178 -0
- data/lib/tryouts/parsers/base_parser.rb +101 -1
- data/lib/tryouts/parsers/enhanced_parser.rb +177 -25
- data/lib/tryouts/parsers/legacy_parser.rb +254 -0
- data/lib/tryouts/parsers/shared_methods.rb +5 -1
- data/lib/tryouts/test_batch.rb +1 -1
- data/lib/tryouts/test_executor.rb +12 -4
- data/lib/tryouts/test_result_aggregator.rb +15 -11
- data/lib/tryouts/test_runner.rb +18 -15
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +1 -1
- metadata +3 -2
- data/lib/tryouts/parsers/prism_parser.rb +0 -122
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 958254585920c850e17a18e13699040fad97ec8610f3c2141b32899016dc4194
|
4
|
+
data.tar.gz: 3666350beb13d17cf24bf184350fd88746cebbf7b7efc109c3e5af7f8d5c6c3b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a1091a28256b97c210f8a0a970db3a78d715e1b195e95a5a844e8f32b7d65f7e6d5cb49209baaf2c061b02500ad6bf2d71968cbcb76c16206108eabfa8039f3
|
7
|
+
data.tar.gz: 9613b25213146c5da6dbd5153fa101e8139e205f06fb5bcddb8fa172a82fc76dffff56f79d69609c4764623ac9bf0682a0ba8ef4e76fd49517920a9b3e1141af
|
data/README.md
CHANGED
@@ -132,18 +132,15 @@ try --agent --agent-limit 1000 # limit output to 1000 tokens
|
|
132
132
|
|
133
133
|
#### Why Not Pipe Test Output Directly to AI?
|
134
134
|
|
135
|
-
|
135
|
+
I mean, you could. If that already works well, you could probably still benefit from an agent that is able to focus on the critical information for the task. And the extra context window space.
|
136
136
|
|
137
|
-
|
138
|
-
- **Signal vs noise**: Important failures get buried in passing test details and framework boilerplate
|
139
|
-
- **Inconsistent parsing**: AI struggles with varying output formats across different test runs
|
140
|
-
- **Context overflow**: Large test suites exceed AI token limits, truncating critical information
|
137
|
+
Raw test output creates problems when working with AI assistants: high token usage with inconsistent parsing across different runs, where the same logical failure might be interpreted differently, making it difficult to reliably produce and analyze results consistently.
|
141
138
|
|
142
|
-
####
|
139
|
+
#### TOPAZ: A Better Approach
|
143
140
|
|
144
|
-
Tryouts' `--agent` mode inspired the development of **
|
141
|
+
Tryouts' `--agent` mode inspired the development of **TOPAZ (Test Output Protocol for AI Zealots)** - a standardized format optimized for AI analysis. The [tpane](https://github.com/delano/tpane) tool implements this protocol, transforming any test framework's output into structured, token-efficient formats.
|
145
142
|
|
146
|
-
Instead of overwhelming AI with raw output,
|
143
|
+
Instead of overwhelming AI with raw output, TOPAZ provides clean semantic data focusing on what actually needs attention - failures, errors, and actionable context.
|
147
144
|
|
148
145
|
### Exit Codes
|
149
146
|
|
@@ -4,13 +4,13 @@ require_relative 'token_budget'
|
|
4
4
|
|
5
5
|
class Tryouts
|
6
6
|
class CLI
|
7
|
-
#
|
7
|
+
# TOPAZ (Test Output Protocol for AI Zealots) Formatter
|
8
8
|
#
|
9
9
|
# Language-agnostic test output format designed for LLM context management.
|
10
|
-
# This formatter implements the
|
10
|
+
# This formatter implements the TOPAZ v1.0 specification for structured,
|
11
11
|
# token-efficient test result communication.
|
12
12
|
#
|
13
|
-
#
|
13
|
+
# TOPAZ Features:
|
14
14
|
# - Language-agnostic field naming (snake_case, hierarchical)
|
15
15
|
# - Standardized execution context (runtime, environment, VCS)
|
16
16
|
# - Token budget awareness with smart truncation
|
@@ -27,7 +27,7 @@ class Tryouts
|
|
27
27
|
# - environment: Normalized env vars (ci_system, app_env, etc.)
|
28
28
|
# - test_framework: Framework name, isolation mode, parser
|
29
29
|
# - execution_flags: Runtime flags in normalized form
|
30
|
-
# - protocol:
|
30
|
+
# - protocol: TOPAZ version and configuration
|
31
31
|
# - project: Auto-detected project type
|
32
32
|
# - test_discovery: File pattern matching rules
|
33
33
|
#
|
@@ -49,7 +49,7 @@ class Tryouts
|
|
49
49
|
@focus_mode = options[:agent_focus] || :failures
|
50
50
|
@collected_files = []
|
51
51
|
@current_file_data = nil
|
52
|
-
@total_stats = { files: 0, tests: 0, failures: 0, errors: 0,
|
52
|
+
@total_stats = { files: 0, tests: 0, failures: 0, errors: 0, elapsed_time: 0 }
|
53
53
|
@output_rendered = false
|
54
54
|
@options = options # Store all options for execution context display
|
55
55
|
@all_warnings = [] # Store warnings globally for execution details
|
@@ -120,7 +120,7 @@ class Tryouts
|
|
120
120
|
# Always update global totals
|
121
121
|
@total_stats[:failures] += failed_count
|
122
122
|
@total_stats[:errors] += error_count
|
123
|
-
@total_stats[:
|
123
|
+
@total_stats[:elapsed_time] += elapsed_time if elapsed_time
|
124
124
|
|
125
125
|
# Update per-file data - file_result is called AFTER file_end, so data is in @collected_files
|
126
126
|
relative_file_path = relative_path(file_path)
|
@@ -175,7 +175,7 @@ class Tryouts
|
|
175
175
|
error_count: @collected_files.sum { |f| f[:errors].size },
|
176
176
|
successful_files: @collected_files.size - @collected_files.count { |f| f[:failures].any? || f[:errors].any? },
|
177
177
|
total_files: @collected_files.size,
|
178
|
-
elapsed_time: @total_stats[:
|
178
|
+
elapsed_time: @total_stats[:elapsed_time]
|
179
179
|
) unless @output_rendered
|
180
180
|
end
|
181
181
|
|
@@ -188,7 +188,7 @@ class Tryouts
|
|
188
188
|
errors: error_count,
|
189
189
|
successful_files: successful_files,
|
190
190
|
total_files: total_files,
|
191
|
-
|
191
|
+
elapsed_time: elapsed_time,
|
192
192
|
)
|
193
193
|
|
194
194
|
# Now render all collected data
|
@@ -278,6 +278,12 @@ class Tryouts
|
|
278
278
|
def render_summary_only
|
279
279
|
output = []
|
280
280
|
|
281
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
282
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
283
|
+
else
|
284
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
285
|
+
end
|
286
|
+
|
281
287
|
# Add execution context header for agent clarity
|
282
288
|
output << render_execution_context
|
283
289
|
output << ""
|
@@ -293,13 +299,13 @@ class Tryouts
|
|
293
299
|
details = []
|
294
300
|
details << "#{failed_count} failed" if failed_count > 0
|
295
301
|
details << "#{error_count} errors" if error_count > 0
|
296
|
-
status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed)"
|
302
|
+
status_parts << "FAIL: #{issues_count}/#{@total_stats[:tests]} tests (#{details.join(', ')}, #{passed_count} passed#{time_str})"
|
297
303
|
else
|
298
304
|
# Agent doesn't need output in the positive case (i.e. for passing
|
299
305
|
# tests). It just fills out the context window.
|
300
306
|
end
|
301
307
|
|
302
|
-
status_parts << "(#{format_time(@total_stats[:
|
308
|
+
status_parts << "(#{format_time(@total_stats[:elapsed_time])})" if @total_stats[:elapsed_time]
|
303
309
|
|
304
310
|
output << status_parts.join(" ")
|
305
311
|
|
@@ -331,6 +337,12 @@ class Tryouts
|
|
331
337
|
# Only show errors (exceptions), skip assertion failures
|
332
338
|
critical_files = @collected_files.select { |f| f[:errors].any? }
|
333
339
|
|
340
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
341
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
342
|
+
else
|
343
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
344
|
+
end
|
345
|
+
|
334
346
|
output = []
|
335
347
|
|
336
348
|
# Add execution context header for agent clarity
|
@@ -343,7 +355,7 @@ class Tryouts
|
|
343
355
|
return
|
344
356
|
end
|
345
357
|
|
346
|
-
output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors"
|
358
|
+
output << "CRITICAL: #{critical_files.size} file#{'s' if critical_files.size != 1} with errors#{time_str}"
|
347
359
|
output << ""
|
348
360
|
|
349
361
|
critical_files.each do |file_data|
|
@@ -373,6 +385,12 @@ class Tryouts
|
|
373
385
|
def render_full_structured
|
374
386
|
output = []
|
375
387
|
|
388
|
+
time_str = if @total_stats[:elapsed_time] < 2.0
|
389
|
+
" (#{(@total_stats[:elapsed_time] * 1000).round}ms)"
|
390
|
+
else
|
391
|
+
" (#{@total_stats[:elapsed_time].round(2)}s)"
|
392
|
+
end
|
393
|
+
|
376
394
|
# Add execution context header for agent clarity
|
377
395
|
output << render_execution_context
|
378
396
|
output << ""
|
@@ -408,7 +426,7 @@ class Tryouts
|
|
408
426
|
summary = "Summary: \n"
|
409
427
|
summary += "#{passed_count} testcases passed, #{failed_count} failed"
|
410
428
|
summary += ", #{error_count} errors" if error_count > 0
|
411
|
-
summary += " in #{@total_stats[:files]} files"
|
429
|
+
summary += " in #{@total_stats[:files]} files#{time_str}"
|
412
430
|
|
413
431
|
output << summary
|
414
432
|
|
@@ -548,7 +566,7 @@ class Tryouts
|
|
548
566
|
|
549
567
|
# Runtime - compact format
|
550
568
|
platform = RUBY_PLATFORM.gsub(/darwin\d+/, 'darwin') # Simplify darwin25 -> darwin
|
551
|
-
context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform})"
|
569
|
+
context_lines << " runtime: ruby #{RUBY_VERSION} (#{platform}); tryouts #{Tryouts::VERSION}"
|
552
570
|
|
553
571
|
# Package manager - only if present, compact format
|
554
572
|
if defined?(Bundler)
|
@@ -592,8 +610,8 @@ class Tryouts
|
|
592
610
|
context_lines << " flags: #{flags.join(', ')}"
|
593
611
|
end
|
594
612
|
|
595
|
-
#
|
596
|
-
context_lines << " protocol:
|
613
|
+
# TOPAZ protocol - compact
|
614
|
+
context_lines << " protocol: TOPAZ v0.3 | focus: #{@focus_mode} | limit: #{@budget.limit}"
|
597
615
|
|
598
616
|
# File count being tested
|
599
617
|
if @collected_files && @collected_files.any?
|
data/lib/tryouts/cli/opts.rb
CHANGED
@@ -10,20 +10,20 @@ class Tryouts
|
|
10
10
|
Minitest: Fresh context (each test isolated)
|
11
11
|
|
12
12
|
Examples:
|
13
|
-
try test_try.rb
|
14
|
-
try --rspec test_try.rb
|
15
|
-
try --direct --shared-context test_try.rb
|
16
|
-
try --generate-rspec test_try.rb
|
17
|
-
try --inspect test_try.rb
|
18
|
-
try --agent test_try.rb
|
19
|
-
try --agent --agent-limit 10000 tests/
|
13
|
+
try test_try.rb # Tryouts test runner with shared context
|
14
|
+
try --rspec test_try.rb # RSpec with fresh context
|
15
|
+
try --direct --shared-context test_try.rb # Explicit shared context
|
16
|
+
try --generate-rspec test_try.rb # Output RSpec code only
|
17
|
+
try --inspect test_try.rb # Inspect file structure and validation
|
18
|
+
try --agent test_try.rb # Agent-optimized structured output
|
19
|
+
try --agent --agent-limit 10000 tests/ # Agent mode with 10K token limit
|
20
20
|
|
21
21
|
Agent Output Modes:
|
22
|
-
--agent
|
23
|
-
--agent-focus summary
|
24
|
-
--agent-focus first-failure
|
25
|
-
--agent-focus critical
|
26
|
-
--agent-limit 1000
|
22
|
+
--agent # Structured, token-efficient output
|
23
|
+
--agent-focus summary # Show counts and problem files only
|
24
|
+
--agent-focus first-failure # Show first failure per file
|
25
|
+
--agent-focus critical # Show errors/exceptions only
|
26
|
+
--agent-limit 1000 # Limit output to 1000 tokens
|
27
27
|
|
28
28
|
File Naming & Organization:
|
29
29
|
Files must end with '_try.rb' or '.try.rb' (e.g., auth_service_try.rb, user_model.try.rb)
|
@@ -37,7 +37,7 @@ class Tryouts
|
|
37
37
|
#=> true # this is the expected result
|
38
38
|
|
39
39
|
File Structure (3 sections):
|
40
|
-
# Setup section (optional) - runs once before all tests
|
40
|
+
# Setup section (optional) - code before first testcase runs once before all tests
|
41
41
|
@shared_var = "available to all test cases"
|
42
42
|
|
43
43
|
## TEST: Feature description
|
@@ -45,9 +45,9 @@ class Tryouts
|
|
45
45
|
result = some_operation()
|
46
46
|
#=> expected_value
|
47
47
|
|
48
|
-
# Teardown section (optional) - runs once after all tests
|
48
|
+
# Teardown section (optional) - code after last testcase runs once after all tests
|
49
49
|
|
50
|
-
Context
|
50
|
+
Execution Context:
|
51
51
|
Shared Context (default): Instance variables persist across test cases
|
52
52
|
- Use for: Integration testing, stateful scenarios, realistic workflows
|
53
53
|
- Caution: Test order matters, state accumulates
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# lib/tryouts/file_processor.rb
|
2
2
|
|
3
|
-
require_relative 'parsers/
|
3
|
+
require_relative 'parsers/legacy_parser'
|
4
4
|
require_relative 'parsers/enhanced_parser'
|
5
5
|
require_relative 'test_executor'
|
6
6
|
require_relative 'cli/modes/inspect'
|
@@ -84,7 +84,7 @@ class Tryouts
|
|
84
84
|
when :enhanced
|
85
85
|
EnhancedParser.new(file, options)
|
86
86
|
when :prism
|
87
|
-
|
87
|
+
LegacyParser.new(file, options)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
@@ -22,5 +22,15 @@ class Tryouts
|
|
22
22
|
suggestion: "Use explicit '## Description' to clarify test structure"
|
23
23
|
)
|
24
24
|
end
|
25
|
+
|
26
|
+
def self.malformed_expectation(line_number:, syntax:, context:)
|
27
|
+
new(
|
28
|
+
type: :malformed_expectation,
|
29
|
+
message: "Malformed expectation syntax '#=#{syntax}>' at line #{line_number}",
|
30
|
+
line_number: line_number,
|
31
|
+
context: context,
|
32
|
+
suggestion: "Use valid expectation syntax like #=>, #==>, #=:>, #=!>, etc."
|
33
|
+
)
|
34
|
+
end
|
25
35
|
end
|
26
36
|
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
In Ruby 3.4+, `case/when` and `case/in` represent fundamentally different approaches to conditional logic:
|
2
|
+
|
3
|
+
## `case/when` - Traditional Equality Matching
|
4
|
+
|
5
|
+
Uses the `===` operator for comparison. Simple and straightforward:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
def classify_response(status)
|
9
|
+
case status
|
10
|
+
when 200..299
|
11
|
+
"success"
|
12
|
+
when 400..499
|
13
|
+
"client_error"
|
14
|
+
when 500..599
|
15
|
+
"server_error"
|
16
|
+
when String
|
17
|
+
"string_status"
|
18
|
+
else
|
19
|
+
"unknown"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
## `case/in` - Pattern Matching with Destructuring
|
25
|
+
|
26
|
+
Matches structure and binds variables. Much more powerful:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
def process_api_response(response)
|
30
|
+
case response
|
31
|
+
in { status: 200, data: { user: { name: String => name, age: Integer => age } } }
|
32
|
+
"User #{name} is #{age} years old"
|
33
|
+
|
34
|
+
in { status: 200, data: Array => items } if items.length > 10
|
35
|
+
"Got #{items.length} items"
|
36
|
+
|
37
|
+
in { status: 400..499, error: { message: msg } }
|
38
|
+
"Client error: #{msg}"
|
39
|
+
|
40
|
+
in { status: 500.. }
|
41
|
+
"Server error occurred"
|
42
|
+
|
43
|
+
in nil | {}
|
44
|
+
"Empty response"
|
45
|
+
else
|
46
|
+
"Unexpected response format"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
## Key Differences
|
52
|
+
|
53
|
+
### 1. **Variable Binding**
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# case/when - no binding
|
57
|
+
case user
|
58
|
+
when Hash
|
59
|
+
puts user[:name] # Must access manually
|
60
|
+
end
|
61
|
+
|
62
|
+
# case/in - automatic binding
|
63
|
+
case user
|
64
|
+
in { name: String => username, age: } # 'age' variable created automatically
|
65
|
+
puts username # Bound variable available
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
### 2. **Structural Matching**
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
# case/when - only surface comparison
|
73
|
+
case data
|
74
|
+
when Array
|
75
|
+
# Know it's an array, but not its contents
|
76
|
+
end
|
77
|
+
|
78
|
+
# case/in - deep structure matching
|
79
|
+
case data
|
80
|
+
in [first, *middle, last] if middle.length > 2
|
81
|
+
# Automatically destructured with guard condition
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
### 3. **Guard Conditions**
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# case/when - separate if needed
|
89
|
+
case number
|
90
|
+
when Integer
|
91
|
+
if number > 100
|
92
|
+
"big integer"
|
93
|
+
else
|
94
|
+
"small integer"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# case/in - integrated guards
|
99
|
+
case number
|
100
|
+
in Integer => n if n > 100
|
101
|
+
"big integer"
|
102
|
+
in Integer
|
103
|
+
"small integer"
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
## Practical Example for Tryouts
|
108
|
+
|
109
|
+
For parsing tryout lines, here's the difference:
|
110
|
+
|
111
|
+
### Traditional `case/when`
|
112
|
+
```ruby
|
113
|
+
def parse_line(line)
|
114
|
+
case line
|
115
|
+
when /^##\s*(.+)/
|
116
|
+
[:description, $1.strip]
|
117
|
+
when /^#=>\s*(.+)/
|
118
|
+
[:expectation, $1.strip]
|
119
|
+
when /^#=\?>\s*(.+)/
|
120
|
+
[:debug_info, $1.strip]
|
121
|
+
else
|
122
|
+
[:code, line]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
### Pattern Matching `case/in`
|
128
|
+
```ruby
|
129
|
+
def parse_line(line)
|
130
|
+
case line
|
131
|
+
in /^##\s*(.+)/ => description
|
132
|
+
[:description, description.strip]
|
133
|
+
in /^#=>\s*(.+)/ => expectation
|
134
|
+
[:expectation, expectation.strip]
|
135
|
+
in /^#=\?>\s*(.+)/ => debug_expr
|
136
|
+
[:debug_info, debug_expr.strip]
|
137
|
+
in /^\s*$/
|
138
|
+
[:blank]
|
139
|
+
else
|
140
|
+
[:code, line]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
## When to Use Which
|
146
|
+
|
147
|
+
### Use `case/when` for:
|
148
|
+
- Simple value comparisons
|
149
|
+
- Class/type checking
|
150
|
+
- Range matching
|
151
|
+
- Traditional switch-like logic
|
152
|
+
|
153
|
+
### Use `case/in` for:
|
154
|
+
- Complex data structure matching
|
155
|
+
- When you need variable binding
|
156
|
+
- Guard conditions
|
157
|
+
- Destructuring arrays/hashes
|
158
|
+
- Multiple conditions per branch
|
159
|
+
|
160
|
+
## Ruby 3.4+ Enhancements
|
161
|
+
|
162
|
+
Ruby 3.4 added several pattern matching improvements:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# Variable binding in array patterns
|
166
|
+
case data
|
167
|
+
in [String => first, *String => middle, String => last]
|
168
|
+
# All string array with bound variables
|
169
|
+
end
|
170
|
+
|
171
|
+
# Hash patterns with rest
|
172
|
+
case config
|
173
|
+
in { required: true, **rest } if rest.keys.all? { |k| k.is_a?(Symbol) }
|
174
|
+
# Required config with symbol keys only
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
For the Tryouts modernization, `case/in` provides cleaner syntax for parsing complex comment patterns while binding the captured content directly to variables, eliminating the need for global match variables like `$1`.
|
@@ -6,11 +6,89 @@ require_relative 'shared_methods'
|
|
6
6
|
require_relative '../parser_warning'
|
7
7
|
|
8
8
|
class Tryouts
|
9
|
-
# Fixed PrismParser with pattern matching for robust token filtering
|
10
9
|
module Parsers
|
10
|
+
# Base class for all tryout parsers providing common functionality
|
11
|
+
#
|
12
|
+
# BaseParser establishes the foundation for parsing tryout files by handling
|
13
|
+
# file loading, Prism integration, and providing shared parsing infrastructure.
|
14
|
+
# All concrete parser implementations (EnhancedParser, LegacyParser) inherit
|
15
|
+
# from this class.
|
16
|
+
#
|
17
|
+
# @abstract Subclass and implement {#parse} to create a concrete parser
|
18
|
+
# @example Implementing a custom parser
|
19
|
+
# class MyCustomParser < Tryouts::Parsers::BaseParser
|
20
|
+
# def parse
|
21
|
+
# # Your parsing logic here
|
22
|
+
# # Must return a Tryouts::Testrun object
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# private
|
26
|
+
#
|
27
|
+
# def parser_type
|
28
|
+
# :custom
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @!attribute [r] source_path
|
33
|
+
# @return [String] Path to the source file being parsed
|
34
|
+
# @!attribute [r] source
|
35
|
+
# @return [String] Raw source code content
|
36
|
+
# @!attribute [r] lines
|
37
|
+
# @return [Array<String>] Source lines with line endings removed
|
38
|
+
# @!attribute [r] prism_result
|
39
|
+
# @return [Prism::ParseResult] Result of parsing source with Prism
|
40
|
+
# @!attribute [r] parsed_at
|
41
|
+
# @return [Time] Timestamp when parsing was initiated
|
42
|
+
# @!attribute [r] options
|
43
|
+
# @return [Hash] Parser configuration options
|
44
|
+
# @!attribute [r] warnings
|
45
|
+
# @return [Array<Tryouts::ParserWarning>] Collection of parsing warnings
|
46
|
+
#
|
47
|
+
# ## Shared Functionality
|
48
|
+
#
|
49
|
+
# ### 1. File and Source Management
|
50
|
+
# - Automatic file reading and line splitting
|
51
|
+
# - UTF-8 encoding handling
|
52
|
+
# - Path normalization and validation
|
53
|
+
#
|
54
|
+
# ### 2. Prism Integration
|
55
|
+
# - Automatic Prism parsing of source code
|
56
|
+
# - Syntax error detection and handling
|
57
|
+
# - AST access for advanced parsing needs
|
58
|
+
#
|
59
|
+
# ### 3. Warning System
|
60
|
+
# - Centralized warning collection and management
|
61
|
+
# - Type-safe warning objects with context
|
62
|
+
# - Integration with output formatters
|
63
|
+
#
|
64
|
+
# ### 4. Shared Methods
|
65
|
+
# - Token grouping and classification logic
|
66
|
+
# - Test case boundary detection
|
67
|
+
# - Common utility methods for all parsers
|
68
|
+
#
|
69
|
+
# ## Parser Requirements
|
70
|
+
#
|
71
|
+
# Concrete parser implementations must:
|
72
|
+
# 1. Implement the abstract `parse` method
|
73
|
+
# 2. Return a `Tryouts::Testrun` object
|
74
|
+
# 3. Handle syntax errors appropriately
|
75
|
+
# 4. Provide a unique `parser_type` identifier
|
76
|
+
#
|
77
|
+
# @see EnhancedParser For Prism-based comment extraction
|
78
|
+
# @see LegacyParser For line-by-line parsing approach
|
79
|
+
# @see SharedMethods For common parsing utilities
|
80
|
+
# @since 3.0.0
|
11
81
|
class BaseParser
|
12
82
|
include Tryouts::Parsers::SharedMethods
|
13
83
|
|
84
|
+
# Initialize a new parser instance
|
85
|
+
#
|
86
|
+
# @param source_path [String] Absolute path to the tryout source file
|
87
|
+
# @param options [Hash] Configuration options for parsing behavior
|
88
|
+
# @option options [Boolean] :strict Enable strict mode validation
|
89
|
+
# @option options [Boolean] :warnings Enable warning collection (default: true)
|
90
|
+
# @raise [Errno::ENOENT] If source file doesn't exist
|
91
|
+
# @raise [Errno::EACCES] If source file isn't readable
|
14
92
|
def initialize(source_path, options = {})
|
15
93
|
@source_path = source_path
|
16
94
|
@source = File.read(source_path)
|
@@ -21,6 +99,28 @@ class Tryouts
|
|
21
99
|
@warnings = []
|
22
100
|
end
|
23
101
|
|
102
|
+
# Parse the source file into structured test data
|
103
|
+
#
|
104
|
+
# @abstract Subclasses must implement this method
|
105
|
+
# @return [Tryouts::Testrun] Parsed test structure with setup, tests, teardown, and warnings
|
106
|
+
# @raise [NotImplementedError] If called directly on BaseParser
|
107
|
+
def parse
|
108
|
+
raise NotImplementedError, "Subclasses must implement #parse"
|
109
|
+
end
|
110
|
+
|
111
|
+
protected
|
112
|
+
|
113
|
+
# Get the parser type identifier
|
114
|
+
#
|
115
|
+
# @abstract Subclasses should override to provide unique identifier
|
116
|
+
# @return [Symbol] Parser type identifier
|
117
|
+
def parser_type
|
118
|
+
:base
|
119
|
+
end
|
120
|
+
|
121
|
+
# Access to instance variables for subclasses
|
122
|
+
attr_reader :source_path, :source, :lines, :prism_result, :parsed_at, :options
|
123
|
+
|
24
124
|
end
|
25
125
|
end
|
26
126
|
end
|