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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7ef307bf73a8bd6c70f52c47337d550ae7715e0b64d15c7f708c9fd407f3df2
4
- data.tar.gz: ccd166f03573bee1d31e3843ff95964cf5065ab4a1b2386e9769ec065a6958f5
3
+ metadata.gz: b8d7c33ad6377a7fb1c64c83e24e643c434643aa09e5451ad42bbe80c65b9c9d
4
+ data.tar.gz: 89cfe371c0fd575614a56702c904d0c5140db2a3a2af41d22777e459de1f4d8a
5
5
  SHA512:
6
- metadata.gz: 22b0d458cfadd632495c4df95f41cca3c06c6e6b3e219b18afb0fd21bfc14d1d9a872b165fcd2dd1c26052a9f57b84dad77c4a5a14c3efa08f62d74f0ab34467
7
- data.tar.gz: 8cd6db263235736e26163b84568ad870de5835da3002e6f71226d961fc21e19d9eaef3cf2e02598a3907cb2701665e72a7e21ecea05b540f10ba37daa530a391
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/prism_parser.rb'
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 && @show_debug
242
+ return unless backtrace && @show_stack_traces
239
243
 
240
- backtrace.first(3).each do |line|
241
- @stderr.puts indent_text(line.chomp, 1)
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 && @show_debug
84
+ return unless backtrace && @show_stack_traces
84
85
 
85
- backtrace.first(3).each do |line|
86
- @stderr.puts " #{line.chomp}"
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.each_with_index do |failure, index|
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 " #{index + 1}) #{failure.description}"
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 && @show_debug
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
- backtrace.first(10).each do |line|
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
@@ -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 #=1> STDOUT content
30
- #=2> STDERR content #=<> Intentional failure
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 (current default)') { options[:parser] = :prism }
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
@@ -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(file)
165
- return nil if file.nil?
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
- Pathname.new(file).relative_path_from(basepath).to_s
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
- execute_test_code_and_evaluate_exception
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
- # Convert actual_result to string for regex matching
44
- string_result = actual_result.to_s
45
- match_result = string_result =~ pattern
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
- expected_class = eval_expectation_content(@expectation.content, expectation_result)
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[:file_count] += 1
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] || :prism # default to legacy for safe rollout
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
- @global_tally[:total_errors] += 1 if @global_tally
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