tryouts 3.7.0 → 4.0.0.pre1
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 +3 -2
- data/lib/tryouts/cli/formatters/agent.rb +12 -16
- data/lib/tryouts/cli/formatters/base.rb +2 -0
- data/lib/tryouts/cli/formatters/compact.rb +2 -0
- data/lib/tryouts/cli/formatters/factory.rb +2 -0
- data/lib/tryouts/cli/formatters/live_status_manager.rb +2 -0
- data/lib/tryouts/cli/formatters/output_manager.rb +2 -0
- data/lib/tryouts/cli/formatters/quiet.rb +2 -0
- data/lib/tryouts/cli/formatters/test_run_state.rb +2 -0
- data/lib/tryouts/cli/formatters/token_budget.rb +2 -0
- data/lib/tryouts/cli/formatters/tty_status_display.rb +2 -0
- data/lib/tryouts/cli/formatters/verbose.rb +2 -0
- data/lib/tryouts/cli/formatters.rb +2 -0
- data/lib/tryouts/cli/line_spec_parser.rb +2 -0
- data/lib/tryouts/cli/modes/generate.rb +2 -0
- data/lib/tryouts/cli/modes/inspect.rb +15 -3
- data/lib/tryouts/cli/opts.rb +8 -6
- data/lib/tryouts/cli/tty_detector.rb +2 -0
- data/lib/tryouts/cli.rb +2 -0
- data/lib/tryouts/console.rb +2 -0
- data/lib/tryouts/expectation_evaluators/base.rb +2 -0
- data/lib/tryouts/expectation_evaluators/boolean.rb +2 -0
- data/lib/tryouts/expectation_evaluators/exception.rb +2 -0
- data/lib/tryouts/expectation_evaluators/expectation_result.rb +2 -0
- data/lib/tryouts/expectation_evaluators/false.rb +2 -0
- data/lib/tryouts/expectation_evaluators/intentional_failure.rb +2 -0
- data/lib/tryouts/expectation_evaluators/non_nil.rb +2 -0
- data/lib/tryouts/expectation_evaluators/output.rb +2 -0
- data/lib/tryouts/expectation_evaluators/performance_time.rb +2 -0
- data/lib/tryouts/expectation_evaluators/regex_match.rb +2 -0
- data/lib/tryouts/expectation_evaluators/registry.rb +2 -0
- data/lib/tryouts/expectation_evaluators/regular.rb +2 -0
- data/lib/tryouts/expectation_evaluators/result_type.rb +2 -0
- data/lib/tryouts/expectation_evaluators/true.rb +2 -0
- data/lib/tryouts/failure_collector.rb +2 -0
- data/lib/tryouts/file_processor.rb +3 -4
- data/lib/tryouts/parser_warning.rb +2 -0
- data/lib/tryouts/parsers/base_parser.rb +3 -3
- data/lib/tryouts/parsers/enhanced_parser.rb +57 -3
- data/lib/tryouts/parsers/shared_methods.rb +67 -17
- data/lib/tryouts/test_batch.rb +155 -43
- data/lib/tryouts/test_case.rb +54 -6
- data/lib/tryouts/test_executor.rb +2 -0
- data/lib/tryouts/test_result_aggregator.rb +2 -0
- data/lib/tryouts/test_runner.rb +2 -1
- data/lib/tryouts/translators/minitest_translator.rb +36 -12
- data/lib/tryouts/translators/rspec_translator.rb +19 -0
- data/lib/tryouts/version.rb +3 -1
- data/lib/tryouts.rb +2 -1
- metadata +1 -2
- data/lib/tryouts/parsers/legacy_parser.rb +0 -254
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b43c7d018d4bb7bd546bb516a584903d7922d87a616dbec87d2e4b6d00e06652
|
|
4
|
+
data.tar.gz: d4068b53bbd8428f88bf1a4d4af62aa7db0ff4ecccfecca0c0b268f1d8fb0381
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c11a368db40e8335a3d9eae238581ad6e0b4ea8b56862bb82f10adbb9746626762db1a89247be6e8eb2473aff90506f151a5b18452ab625d2a6e2f2753968f2d
|
|
7
|
+
data.tar.gz: 7ebec1c8923c423cd3ec2117d78e3a4a95fec48139bb1af2ecee48ebdfea7d02d1ceccfa8608fb5914cb21c762dfafc04a1f81835fc18723c3fbe79d495540db
|
data/README.md
CHANGED
|
@@ -42,7 +42,7 @@ try try/core/basic_syntax_try.rb
|
|
|
42
42
|
|
|
43
43
|
## Writing Tests
|
|
44
44
|
|
|
45
|
-
Tryouts use a comment-based syntax for
|
|
45
|
+
Tryouts use a comment-based syntax for expectations:
|
|
46
46
|
|
|
47
47
|
```ruby
|
|
48
48
|
# Setup code runs before all tests
|
|
@@ -98,9 +98,10 @@ Each test file is made up of three sections:
|
|
|
98
98
|
| `#=:>` | Must match result type | `#=:> String` | result, _ |
|
|
99
99
|
| `#=~>` | Must match regex pattern | `#=~> /^[^@]+@[^@]+\.[^@]+$/` | result, _ |
|
|
100
100
|
| `#=%>` | Must complete within time | `#=%> 2000 # in milliseconds` | result, _ |
|
|
101
|
+
| `#=*>` | Must be non-nil | `#=*> result` | result, _ |
|
|
101
102
|
| `#=1>` | Match content in STDOUT | `#=1> "You have great success"` | result, _ |
|
|
102
103
|
| `#=2>` | Match content in STDERR | `#=2> /[a-zA-Z0-9]+-?[0-9]{1,5}` | result, _ |
|
|
103
|
-
| `#=<>` | Fails on purpose |
|
|
104
|
+
| `#=<>` | Fails on purpose | `#=<> result.include?(4)` | result, _ |
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
### Using other test runners
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/cli/formatters/agent.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative 'token_budget'
|
|
4
6
|
|
|
@@ -89,10 +91,11 @@ class Tryouts
|
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
def file_parsed(_file_path, test_count:, setup_present: false, teardown_present: false)
|
|
94
|
+
# Store per-file parsed test count for reference
|
|
95
|
+
# Total executed tests come from TestResultAggregator via grand_total()
|
|
92
96
|
if @current_file_data
|
|
93
97
|
@current_file_data[:tests] = test_count
|
|
94
98
|
end
|
|
95
|
-
@total_stats[:tests] += test_count
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
def parser_warnings(file_path, warnings:)
|
|
@@ -117,9 +120,7 @@ class Tryouts
|
|
|
117
120
|
end
|
|
118
121
|
|
|
119
122
|
def file_result(file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
|
|
120
|
-
#
|
|
121
|
-
@total_stats[:failures] += failed_count
|
|
122
|
-
@total_stats[:errors] += error_count
|
|
123
|
+
# Update elapsed time (other stats come from TestResultAggregator via grand_total)
|
|
123
124
|
@total_stats[:elapsed_time] += elapsed_time if elapsed_time
|
|
124
125
|
|
|
125
126
|
# Update per-file data - file_result is called AFTER file_end, so data is in @collected_files
|
|
@@ -168,24 +169,19 @@ class Tryouts
|
|
|
168
169
|
|
|
169
170
|
# Summary operations - reliable trigger for rendering
|
|
170
171
|
def batch_summary(failure_collector)
|
|
171
|
-
#
|
|
172
|
-
|
|
173
|
-
total_tests: @total_stats[:tests],
|
|
174
|
-
failed_count: @collected_files.sum { |f| f[:failures].size },
|
|
175
|
-
error_count: @collected_files.sum { |f| f[:errors].size },
|
|
176
|
-
successful_files: @collected_files.size - @collected_files.count { |f| f[:failures].any? || f[:errors].any? },
|
|
177
|
-
total_files: @collected_files.size,
|
|
178
|
-
elapsed_time: @total_stats[:elapsed_time]
|
|
179
|
-
) unless @output_rendered
|
|
172
|
+
# NOTE: We don't render here because test_runner.rb calls grand_total directly
|
|
173
|
+
# with accurate counts from the TestResultAggregator
|
|
180
174
|
end
|
|
181
175
|
|
|
182
176
|
def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
|
|
183
177
|
return if @output_rendered # Prevent double rendering
|
|
184
178
|
|
|
179
|
+
# Use the accurate counts from TestResultAggregator (passed as parameters)
|
|
180
|
+
# NOT the parsed counts we accumulated in file_parsed()
|
|
185
181
|
@total_stats.merge!(
|
|
186
|
-
tests: total_tests,
|
|
187
|
-
failures: failed_count,
|
|
188
|
-
errors: error_count,
|
|
182
|
+
tests: total_tests, # Actual executed test count from aggregator
|
|
183
|
+
failures: failed_count, # Actual failures from aggregator
|
|
184
|
+
errors: error_count, # Actual errors from aggregator
|
|
189
185
|
successful_files: successful_files,
|
|
190
186
|
total_files: total_files,
|
|
191
187
|
elapsed_time: elapsed_time,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/cli/modes/inspect.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
class Tryouts
|
|
4
6
|
class CLI
|
|
@@ -13,14 +15,24 @@ class Tryouts
|
|
|
13
15
|
|
|
14
16
|
def handle
|
|
15
17
|
@output_manager.raw("Inspecting: #{@file}")
|
|
16
|
-
@output_manager.
|
|
18
|
+
@output_manager.raw('=' * 60)
|
|
17
19
|
@output_manager.raw("Found #{@testrun.total_tests} test cases")
|
|
18
20
|
@output_manager.raw("Setup code: #{@testrun.setup.empty? ? 'None' : 'Present'}")
|
|
19
21
|
@output_manager.raw("Teardown code: #{@testrun.teardown.empty? ? 'None' : 'Present'}")
|
|
20
22
|
@output_manager.raw('')
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
test_number = 0
|
|
25
|
+
@testrun.test_cases.each do |tc|
|
|
26
|
+
if tc.is_a?(Tryouts::OrphanBlock)
|
|
27
|
+
@output_manager.raw("Orphan code block (no description, no expectations)")
|
|
28
|
+
@output_manager.raw(" Code lines: #{tc.code.lines.count}")
|
|
29
|
+
@output_manager.raw(" Range: #{tc.line_range}")
|
|
30
|
+
@output_manager.raw('')
|
|
31
|
+
next
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test_number += 1
|
|
35
|
+
@output_manager.raw("Test #{test_number}: #{tc.description}")
|
|
24
36
|
@output_manager.raw(" Code lines: #{tc.code.lines.count}")
|
|
25
37
|
@output_manager.raw(" Expectations: #{tc.expectations.size}")
|
|
26
38
|
@output_manager.raw(" Range: #{tc.line_range}")
|
data/lib/tryouts/cli/opts.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/cli/opts.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
class Tryouts
|
|
4
6
|
class CLI
|
|
@@ -73,17 +75,18 @@ class Tryouts
|
|
|
73
75
|
#=1> STDOUT content #=2> STDERR content #=<> Intentional failure
|
|
74
76
|
|
|
75
77
|
Exception Testing:
|
|
76
|
-
|
|
78
|
+
## Method 1: Rescue and test exception
|
|
77
79
|
begin
|
|
78
80
|
risky_operation
|
|
79
|
-
rescue
|
|
81
|
+
rescue MySpecificError => e
|
|
80
82
|
e.class
|
|
81
83
|
end
|
|
82
|
-
#=>
|
|
84
|
+
#=> MySpecificError
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
## Method 2: Let it raise and test with #=!> and #=~>
|
|
85
87
|
risky_operation
|
|
86
|
-
#=!>
|
|
88
|
+
#=!> MySpecificError
|
|
89
|
+
#=~> /Could not complete action/
|
|
87
90
|
HELP
|
|
88
91
|
|
|
89
92
|
class << self
|
|
@@ -160,7 +163,6 @@ class Tryouts
|
|
|
160
163
|
|
|
161
164
|
opts.separator "\nParser Options:"
|
|
162
165
|
opts.on('--enhanced-parser', 'Use enhanced parser with inhouse comment extraction (default)') { options[:parser] = :enhanced }
|
|
163
|
-
opts.on('--legacy-parser', 'Use legacy prism parser') { options[:parser] = :prism }
|
|
164
166
|
|
|
165
167
|
opts.separator "\nInspection Options:"
|
|
166
168
|
opts.on('-i', '--inspect', 'Inspect file structure without running tests') { options[:inspect] = true }
|
data/lib/tryouts/cli.rb
CHANGED
data/lib/tryouts/console.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# lib/tryouts/file_processor.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
|
-
require_relative 'parsers/legacy_parser'
|
|
4
5
|
require_relative 'parsers/enhanced_parser'
|
|
5
6
|
require_relative 'test_executor'
|
|
6
7
|
require_relative 'cli/modes/inspect'
|
|
@@ -9,7 +10,7 @@ require_relative 'cli/modes/generate'
|
|
|
9
10
|
class Tryouts
|
|
10
11
|
class FileProcessor
|
|
11
12
|
# Supported parser types for validation and documentation
|
|
12
|
-
PARSER_TYPES = [:enhanced
|
|
13
|
+
PARSER_TYPES = [:enhanced].freeze
|
|
13
14
|
def initialize(file:, options:, output_manager:, translator:, global_tally:)
|
|
14
15
|
@file = file
|
|
15
16
|
@options = options
|
|
@@ -52,8 +53,6 @@ class Tryouts
|
|
|
52
53
|
case parser_type
|
|
53
54
|
when :enhanced
|
|
54
55
|
EnhancedParser.new(file, options)
|
|
55
|
-
when :prism
|
|
56
|
-
LegacyParser.new(file, options)
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/parsers/base_parser.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'prism'
|
|
4
6
|
|
|
@@ -11,8 +13,7 @@ class Tryouts
|
|
|
11
13
|
#
|
|
12
14
|
# BaseParser establishes the foundation for parsing tryout files by handling
|
|
13
15
|
# file loading, Prism integration, and providing shared parsing infrastructure.
|
|
14
|
-
# All concrete parser implementations (EnhancedParser
|
|
15
|
-
# from this class.
|
|
16
|
+
# All concrete parser implementations (EnhancedParser) inherit from this class.
|
|
16
17
|
#
|
|
17
18
|
# @abstract Subclass and implement {#parse} to create a concrete parser
|
|
18
19
|
# @example Implementing a custom parser
|
|
@@ -75,7 +76,6 @@ class Tryouts
|
|
|
75
76
|
# 4. Provide a unique `parser_type` identifier
|
|
76
77
|
#
|
|
77
78
|
# @see EnhancedParser For Prism-based comment extraction
|
|
78
|
-
# @see LegacyParser For line-by-line parsing approach
|
|
79
79
|
# @see SharedMethods For common parsing utilities
|
|
80
80
|
# @since 3.0.0
|
|
81
81
|
class BaseParser
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/parsers/enhanced_parser.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require_relative '../test_case'
|
|
4
6
|
require_relative 'base_parser'
|
|
@@ -31,7 +33,7 @@ class Tryouts
|
|
|
31
33
|
# @!attribute [r] parser_type
|
|
32
34
|
# @return [Symbol] Returns :enhanced to identify parser type
|
|
33
35
|
#
|
|
34
|
-
# ## Key Benefits
|
|
36
|
+
# ## Key Benefits
|
|
35
37
|
#
|
|
36
38
|
# ### 1. HEREDOC Safety
|
|
37
39
|
# - Uses Prism's `parse_comments()` to extract only actual Ruby comments
|
|
@@ -56,7 +58,6 @@ class Tryouts
|
|
|
56
58
|
# Uses Ruby 3.4+ pattern matching (`case/in`) for token classification,
|
|
57
59
|
# providing clean, modern syntax for expectation type detection.
|
|
58
60
|
#
|
|
59
|
-
# @see LegacyParser For simpler regex-based parsing (legacy compatibility)
|
|
60
61
|
# @see BaseParser For shared parsing functionality
|
|
61
62
|
# @since 3.2.0
|
|
62
63
|
class EnhancedParser < Tryouts::Parsers::BaseParser
|
|
@@ -72,6 +73,12 @@ class Tryouts
|
|
|
72
73
|
def parse
|
|
73
74
|
return handle_syntax_errors if @prism_result.failure?
|
|
74
75
|
|
|
76
|
+
# Line ranges of multi-line statements, computed once from the whole-file
|
|
77
|
+
# parse. A column-0 comment strictly inside one of these spans (e.g. a
|
|
78
|
+
# #=>-shaped line inside an unclosed array literal) cannot be a real
|
|
79
|
+
# block boundary or expectation, no matter what it looks like lexically.
|
|
80
|
+
@multiline_statement_ranges = multiline_statement_ranges
|
|
81
|
+
|
|
75
82
|
# Use inhouse comment extraction instead of line-by-line regex parsing
|
|
76
83
|
# This automatically excludes HEREDOC content!
|
|
77
84
|
tokens = tokenize_content_with_inhouse_extraction
|
|
@@ -104,22 +111,41 @@ class Tryouts
|
|
|
104
111
|
comments = Prism.parse_comments(@source)
|
|
105
112
|
comment_by_line = comments.group_by { |comment| comment.location.start_line }
|
|
106
113
|
|
|
114
|
+
# A =begin/=end block comment is one Prism comment node spanning multiple
|
|
115
|
+
# lines, but comment_by_line only keys its start line. Its interior lines
|
|
116
|
+
# must be skipped entirely: re-emitting them as :code duplicates them, and
|
|
117
|
+
# running the marker regex cascade against the raw multi-line slice lets an
|
|
118
|
+
# interior #=>-shaped line match via ^/$ line anchors.
|
|
119
|
+
multiline_comment_spans = comments.filter_map do |comment|
|
|
120
|
+
loc = comment.location
|
|
121
|
+
((loc.start_line + 1)..loc.end_line) if loc.end_line > loc.start_line
|
|
122
|
+
end
|
|
123
|
+
|
|
107
124
|
# Process each line, handling multiple comments per line
|
|
108
125
|
@lines.each_with_index do |line, index|
|
|
109
126
|
line_number = index + 1
|
|
110
127
|
|
|
128
|
+
next if multiline_comment_spans.any? { |span| span.cover?(line_number) }
|
|
129
|
+
|
|
111
130
|
if (comments_for_line = comment_by_line[line_number]) && !comments_for_line.empty?
|
|
112
131
|
emitted_code = false
|
|
113
132
|
comments_for_line.sort_by! { |c| c.location.start_column }
|
|
114
133
|
comments_for_line.each do |comment|
|
|
115
134
|
comment_content = comment.slice.strip
|
|
116
|
-
if comment.location.
|
|
135
|
+
if comment.location.end_line > comment.location.start_line
|
|
136
|
+
# Whole =begin/=end block: one plain comment token for the span
|
|
137
|
+
tokens << { type: :comment, content: comment_content, line: index }
|
|
138
|
+
elsif comment.location.start_column > 0
|
|
117
139
|
unless emitted_code
|
|
118
140
|
tokens << { type: :code, content: line, line: index, ast: parse_ruby_line(line) }
|
|
119
141
|
emitted_code = true
|
|
120
142
|
end
|
|
121
143
|
# Inline comment (after code) - treat as regular comment, not expectation
|
|
122
144
|
tokens << { type: :comment, content: comment_content.sub(/^#\s*/, ''), line: line_number - 1 }
|
|
145
|
+
elsif strictly_inside_multiline_statement?(index)
|
|
146
|
+
# Column-0 comment inside a multi-line statement (unclosed
|
|
147
|
+
# array/hash/call/...) - force plain :comment regardless of shape
|
|
148
|
+
tokens << { type: :comment, content: comment_content.sub(/^#\s*/, ''), line: line_number - 1 }
|
|
123
149
|
else
|
|
124
150
|
tokens << classify_comment_inhousely(comment_content, line_number)
|
|
125
151
|
end
|
|
@@ -140,6 +166,34 @@ class Tryouts
|
|
|
140
166
|
tokens
|
|
141
167
|
end
|
|
142
168
|
|
|
169
|
+
# Collect 0-based, inclusive line ranges for every AST node that spans more
|
|
170
|
+
# than one physical line, walking the existing whole-file @prism_result.
|
|
171
|
+
# Built once per parse; no second Prism.parse call.
|
|
172
|
+
def multiline_statement_ranges
|
|
173
|
+
ranges = []
|
|
174
|
+
root = @prism_result.value
|
|
175
|
+
return ranges if root.nil?
|
|
176
|
+
|
|
177
|
+
visit = lambda do |node|
|
|
178
|
+
next if node.nil?
|
|
179
|
+
|
|
180
|
+
loc = node.location
|
|
181
|
+
ranges << ((loc.start_line - 1)..(loc.end_line - 1)) if loc.start_line != loc.end_line
|
|
182
|
+
node.compact_child_nodes.each { |child| visit.call(child) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
root.statements.body.each { |stmt| visit.call(stmt) }
|
|
186
|
+
ranges
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# True if the 0-based line index falls STRICTLY inside a multi-line
|
|
190
|
+
# statement span. A comment on the same line as the opening or closing
|
|
191
|
+
# delimiter is still eligible to be a real boundary; one on a line fully
|
|
192
|
+
# swallowed by the statement is not.
|
|
193
|
+
def strictly_inside_multiline_statement?(line_idx)
|
|
194
|
+
@multiline_statement_ranges.any? { |range| line_idx > range.first && line_idx < range.last }
|
|
195
|
+
end
|
|
196
|
+
|
|
143
197
|
# Classify comment content into specific token types using pattern matching
|
|
144
198
|
#
|
|
145
199
|
# Takes a raw comment string and determines what type of tryout token it
|