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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts/translators/rspec_translator.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
class Tryouts
|
|
4
6
|
module Translators
|
|
@@ -60,6 +62,14 @@ class Tryouts
|
|
|
60
62
|
|
|
61
63
|
# Generate test cases
|
|
62
64
|
testrun.test_cases.each_with_index do |test_case, _index|
|
|
65
|
+
# Orphan blocks become plain statements in the describe body, in source order
|
|
66
|
+
if test_case.is_a?(Tryouts::OrphanBlock)
|
|
67
|
+
unless test_case.empty?
|
|
68
|
+
instance_eval(test_case.code, testrun.source_file, test_case.eval_start_line)
|
|
69
|
+
end
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
|
|
63
73
|
next if test_case.empty? || !test_case.expectations?
|
|
64
74
|
|
|
65
75
|
it test_case.description do
|
|
@@ -112,6 +122,15 @@ class Tryouts
|
|
|
112
122
|
end
|
|
113
123
|
|
|
114
124
|
testrun.test_cases.each_with_index do |test_case, _index|
|
|
125
|
+
# Orphan blocks become plain statements in the describe body, in source order
|
|
126
|
+
if test_case.is_a?(Tryouts::OrphanBlock)
|
|
127
|
+
unless test_case.empty?
|
|
128
|
+
test_case.code.lines.each { |line| lines << " #{line.chomp}" }
|
|
129
|
+
lines << ''
|
|
130
|
+
end
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
|
|
115
134
|
next if test_case.empty? || !test_case.expectations?
|
|
116
135
|
|
|
117
136
|
lines << " it '#{test_case.description}' do"
|
data/lib/tryouts/version.rb
CHANGED
data/lib/tryouts.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# lib/tryouts.rb
|
|
2
|
+
#
|
|
3
|
+
# frozen_string_literal: true
|
|
2
4
|
|
|
3
5
|
require 'stringio'
|
|
4
6
|
require 'timeout'
|
|
@@ -8,7 +10,6 @@ TRYOUTS_LIB_HOME = __dir__ unless defined?(TRYOUTS_LIB_HOME)
|
|
|
8
10
|
require_relative 'tryouts/console'
|
|
9
11
|
require_relative 'tryouts/test_batch'
|
|
10
12
|
require_relative 'tryouts/version'
|
|
11
|
-
require_relative 'tryouts/parsers/legacy_parser'
|
|
12
13
|
require_relative 'tryouts/parsers/enhanced_parser'
|
|
13
14
|
require_relative 'tryouts/cli'
|
|
14
15
|
|
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:
|
|
4
|
+
version: 4.0.0.pre1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
@@ -186,7 +186,6 @@ files:
|
|
|
186
186
|
- lib/tryouts/parsers/CLAUDE.md
|
|
187
187
|
- lib/tryouts/parsers/base_parser.rb
|
|
188
188
|
- lib/tryouts/parsers/enhanced_parser.rb
|
|
189
|
-
- lib/tryouts/parsers/legacy_parser.rb
|
|
190
189
|
- lib/tryouts/parsers/shared_methods.rb
|
|
191
190
|
- lib/tryouts/test_batch.rb
|
|
192
191
|
- lib/tryouts/test_case.rb
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
# lib/tryouts/parsers/legacy_parser.rb
|
|
2
|
-
|
|
3
|
-
require_relative '../test_case'
|
|
4
|
-
require_relative 'base_parser'
|
|
5
|
-
|
|
6
|
-
class Tryouts
|
|
7
|
-
# Legacy parser using line-by-line regex parsing for compatibility
|
|
8
|
-
#
|
|
9
|
-
# The LegacyParser provides a simpler, more straightforward approach to parsing
|
|
10
|
-
# tryout files using sequential line-by-line processing with pattern matching.
|
|
11
|
-
# While less sophisticated than the EnhancedParser, it offers predictable behavior
|
|
12
|
-
# and serves as a fallback option for edge cases.
|
|
13
|
-
#
|
|
14
|
-
# @example Basic usage
|
|
15
|
-
# parser = Tryouts::LegacyParser.new(source_code, file_path)
|
|
16
|
-
# testrun = parser.parse
|
|
17
|
-
# puts testrun.test_cases.length
|
|
18
|
-
#
|
|
19
|
-
# @example Using legacy parser explicitly
|
|
20
|
-
# # In CLI: tryouts --legacy-parser my_test.rb
|
|
21
|
-
# # Or programmatically:
|
|
22
|
-
# parser = Tryouts::LegacyParser.new(source, file)
|
|
23
|
-
# result = parser.parse
|
|
24
|
-
#
|
|
25
|
-
# @!attribute [r] parser_type
|
|
26
|
-
# @return [Symbol] Returns :legacy to identify parser type
|
|
27
|
-
#
|
|
28
|
-
# ## Characteristics
|
|
29
|
-
#
|
|
30
|
-
# ### 1. Simple Line-by-Line Processing
|
|
31
|
-
# - Processes each line sequentially with pattern matching
|
|
32
|
-
# - Straightforward regex-based approach
|
|
33
|
-
# - Easy to understand and debug parsing logic
|
|
34
|
-
#
|
|
35
|
-
# ### 2. Pattern Matching Classification
|
|
36
|
-
# - Uses Ruby 3.4+ pattern matching (`case/in`) for token classification
|
|
37
|
-
# - Modern syntax while maintaining simple parsing approach
|
|
38
|
-
# - Consistent with EnhancedParser's classification logic
|
|
39
|
-
#
|
|
40
|
-
# ### 3. Compatibility Focus
|
|
41
|
-
# - Maintains backward compatibility with older tryout files
|
|
42
|
-
# - Provides fallback parsing when EnhancedParser encounters issues
|
|
43
|
-
# - Useful for debugging parser-specific problems
|
|
44
|
-
#
|
|
45
|
-
# ## Limitations
|
|
46
|
-
#
|
|
47
|
-
# ### 1. HEREDOC Vulnerability
|
|
48
|
-
# - Cannot distinguish between real comments and content inside HEREDOCs
|
|
49
|
-
# - May incorrectly parse string content as tryout syntax
|
|
50
|
-
# - Requires careful handling of complex Ruby syntax
|
|
51
|
-
#
|
|
52
|
-
# ### 2. Limited Inline Comment Support
|
|
53
|
-
# - Basic handling of lines with both code and comments
|
|
54
|
-
# - Less sophisticated than EnhancedParser's multi-comment support
|
|
55
|
-
#
|
|
56
|
-
# ## When to Use
|
|
57
|
-
#
|
|
58
|
-
# - **Debugging**: When EnhancedParser produces unexpected results
|
|
59
|
-
# - **Compatibility**: With older Ruby versions or edge cases
|
|
60
|
-
# - **Simplicity**: When predictable line-by-line behavior is preferred
|
|
61
|
-
# - **Fallback**: As a secondary parsing option
|
|
62
|
-
#
|
|
63
|
-
# @see EnhancedParser For robust syntax-aware parsing (recommended default)
|
|
64
|
-
# @see BaseParser For shared parsing functionality
|
|
65
|
-
# @since 3.0.0
|
|
66
|
-
class LegacyParser < Tryouts::Parsers::BaseParser
|
|
67
|
-
|
|
68
|
-
# Parse source code into a Testrun using line-by-line processing
|
|
69
|
-
#
|
|
70
|
-
# This method provides sequential line-by-line parsing that processes each
|
|
71
|
-
# line with pattern matching to classify tokens. While simpler than
|
|
72
|
-
# EnhancedParser, it may be vulnerable to HEREDOC content parsing issues.
|
|
73
|
-
#
|
|
74
|
-
# @return [Tryouts::Testrun] Structured test data with setup, test cases, teardown, and warnings
|
|
75
|
-
# @raise [Tryouts::TryoutSyntaxError] If source contains syntax errors or strict mode violations
|
|
76
|
-
def parse
|
|
77
|
-
return handle_syntax_errors if @prism_result.failure?
|
|
78
|
-
|
|
79
|
-
tokens = tokenize_content
|
|
80
|
-
test_boundaries = find_test_case_boundaries(tokens)
|
|
81
|
-
tokens = classify_potential_descriptions_with_boundaries(tokens, test_boundaries)
|
|
82
|
-
test_blocks = group_into_test_blocks(tokens)
|
|
83
|
-
process_test_blocks(test_blocks)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
private
|
|
87
|
-
|
|
88
|
-
# Tokenize content using sequential line-by-line pattern matching
|
|
89
|
-
#
|
|
90
|
-
# Processes each line of source code individually, applying pattern matching
|
|
91
|
-
# to classify it as code, comment, expectation, etc. This approach is simple
|
|
92
|
-
# and predictable but cannot distinguish between real comments and content
|
|
93
|
-
# inside string literals or HEREDOCs.
|
|
94
|
-
#
|
|
95
|
-
# @return [Array<Hash>] Array of token hashes with keys :type, :content, :line, etc.
|
|
96
|
-
# @example Token structure
|
|
97
|
-
# [
|
|
98
|
-
# { type: :description, content: "Test case description", line: 5 },
|
|
99
|
-
# { type: :code, content: "result = calculate(x)", line: 6 },
|
|
100
|
-
# { type: :expectation, content: "42", line: 7, ast: <Prism::Node> }
|
|
101
|
-
# ]
|
|
102
|
-
# @note Potential_descriptions are later reclassified based on test boundaries
|
|
103
|
-
def tokenize_content
|
|
104
|
-
tokens = []
|
|
105
|
-
|
|
106
|
-
@lines.each_with_index do |line, index|
|
|
107
|
-
token = case line
|
|
108
|
-
in /^##\s*(.*)$/ # Test description format: ## description
|
|
109
|
-
{ type: :description, content: $1.strip, line: index }
|
|
110
|
-
in /^#\s*TEST\s*\d*:\s*(.*)$/ # rubocop:disable Lint/DuplicateBranch
|
|
111
|
-
{ type: :description, content: $1.strip, line: index }
|
|
112
|
-
in /^#\s*=!>\s*(.*)$/ # Exception expectation (updated for consistency)
|
|
113
|
-
{ type: :exception_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
114
|
-
in /^#\s*=<>\s*(.*)$/ # Intentional failure expectation
|
|
115
|
-
{ type: :intentional_failure_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
116
|
-
in /^#\s*==>\s*(.*)$/ # Boolean true expectation
|
|
117
|
-
{ type: :true_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
118
|
-
in %r{^#\s*=/=>\s*(.*)$} # Boolean false expectation
|
|
119
|
-
{ type: :false_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
120
|
-
in /^#\s*=\|>\s*(.*)$/ # Boolean (true or false) expectation
|
|
121
|
-
{ type: :boolean_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
122
|
-
in /^#\s*=\*>\s*(.*)$/ # Non-nil expectation
|
|
123
|
-
{ type: :non_nil_expectation, content: $1.strip, line: index }
|
|
124
|
-
in /^#\s*=:>\s*(.*)$/ # Result type expectation
|
|
125
|
-
{ type: :result_type_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
126
|
-
in /^#\s*=~>\s*(.*)$/ # Regex match expectation
|
|
127
|
-
{ type: :regex_match_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
128
|
-
in /^#\s*=%>\s*(.*)$/ # Performance time expectation
|
|
129
|
-
{ type: :performance_time_expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
130
|
-
in /^#\s*=(\d+)>\s*(.*)$/ # Output expectation (stdout/stderr with pipe number)
|
|
131
|
-
{ type: :output_expectation, content: $2.strip, pipe: $1.to_i, line: index, ast: parse_expectation($2.strip) }
|
|
132
|
-
in /^#\s*=>\s*(.*)$/ # Regular expectation
|
|
133
|
-
{ type: :expectation, content: $1.strip, line: index, ast: parse_expectation($1.strip) }
|
|
134
|
-
in /^#\s*=([^>=:!~%*|\/\s]+)>\s*(.*)$/ # Malformed expectation - invalid characters between = and >
|
|
135
|
-
syntax = $1
|
|
136
|
-
content_part = $2.strip
|
|
137
|
-
add_warning(ParserWarning.malformed_expectation(
|
|
138
|
-
line_number: index + 1,
|
|
139
|
-
syntax: syntax,
|
|
140
|
-
context: line.strip
|
|
141
|
-
))
|
|
142
|
-
{ type: :malformed_expectation, syntax: syntax, content: content_part, line: index }
|
|
143
|
-
in /^##\s*=>\s*(.*)$/ # Commented out expectation (should be ignored)
|
|
144
|
-
{ type: :comment, content: '=>' + $1.strip, line: index }
|
|
145
|
-
in line if looks_like_malformed_expectation?(line) # Comprehensive malformed expectation detection
|
|
146
|
-
detected_syntax = extract_malformed_syntax(line)
|
|
147
|
-
add_warning(ParserWarning.malformed_expectation(
|
|
148
|
-
line_number: index + 1,
|
|
149
|
-
syntax: detected_syntax,
|
|
150
|
-
context: line.strip
|
|
151
|
-
))
|
|
152
|
-
{ type: :malformed_expectation, syntax: detected_syntax, content: line.strip, line: index }
|
|
153
|
-
in /^#\s*(.*)$/ # Single hash comment - potential description
|
|
154
|
-
{ type: :potential_description, content: $1.strip, line: index }
|
|
155
|
-
in /^\s*$/ # Blank line
|
|
156
|
-
{ type: :blank, line: index }
|
|
157
|
-
else # Ruby code
|
|
158
|
-
{ type: :code, content: line, line: index, ast: parse_ruby_line(line) }
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
tokens << token
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Return tokens with potential_descriptions - they'll be classified later with test boundaries
|
|
165
|
-
tokens
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
# Convert potential_descriptions to descriptions or comments based on context
|
|
170
|
-
def classify_potential_descriptions(tokens)
|
|
171
|
-
tokens.map.with_index do |token, index|
|
|
172
|
-
if token[:type] == :potential_description
|
|
173
|
-
# Check if this looks like a test description based on content and context
|
|
174
|
-
content = token[:content].strip
|
|
175
|
-
|
|
176
|
-
# Skip if it's clearly just a regular comment (short, lowercase, etc.)
|
|
177
|
-
# Test descriptions are typically longer and more descriptive
|
|
178
|
-
looks_like_regular_comment = content.length < 20 &&
|
|
179
|
-
content.downcase == content &&
|
|
180
|
-
!content.match?(/test|example|demonstrate|show/i)
|
|
181
|
-
|
|
182
|
-
# Check if there's code immediately before this (suggesting it's mid-test)
|
|
183
|
-
prev_token = index > 0 ? tokens[index - 1] : nil
|
|
184
|
-
has_code_before = prev_token && prev_token[:type] == :code
|
|
185
|
-
|
|
186
|
-
if looks_like_regular_comment || has_code_before
|
|
187
|
-
# Treat as regular comment
|
|
188
|
-
token.merge(type: :comment)
|
|
189
|
-
else
|
|
190
|
-
# Look ahead for test pattern: code + at least one expectation within reasonable distance
|
|
191
|
-
following_tokens = tokens[(index + 1)..]
|
|
192
|
-
|
|
193
|
-
# Skip blanks and comments to find meaningful content
|
|
194
|
-
meaningful_following = following_tokens.reject { |t| [:blank, :comment].include?(t[:type]) }
|
|
195
|
-
|
|
196
|
-
# Look for test pattern: at least one code token followed by at least one expectation
|
|
197
|
-
# within the next 10 meaningful tokens (to avoid matching setup/teardown)
|
|
198
|
-
test_window = meaningful_following.first(10)
|
|
199
|
-
has_code = test_window.any? { |t| t[:type] == :code }
|
|
200
|
-
has_expectation = test_window.any? { |t| is_expectation_type?(t[:type]) }
|
|
201
|
-
|
|
202
|
-
if has_code && has_expectation
|
|
203
|
-
token.merge(type: :description)
|
|
204
|
-
else
|
|
205
|
-
token.merge(type: :comment)
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
else
|
|
209
|
-
token
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Detect if a comment looks like a malformed expectation attempt
|
|
215
|
-
# This catches patterns that suggest someone tried to write an expectation
|
|
216
|
-
# but got the syntax wrong (missing parts, wrong spacing, extra characters, etc.)
|
|
217
|
-
def looks_like_malformed_expectation?(content)
|
|
218
|
-
# Skip if it's already handled by specific patterns above
|
|
219
|
-
return false if content.match?(/^##\s*/) # Description
|
|
220
|
-
return false if content.match?(/^#\s*TEST\s*\d*:\s*/) # TEST format
|
|
221
|
-
return false if content.match?(/^##\s*=>\s*/) # Commented out expectation
|
|
222
|
-
|
|
223
|
-
# Look for patterns that suggest expectation attempts:
|
|
224
|
-
# 1. Contains = and/or > in suspicious positions
|
|
225
|
-
# 2. Has spaces around = or > suggesting misunderstanding
|
|
226
|
-
# 3. Missing > or = from what looks like expectation syntax
|
|
227
|
-
# 4. Extra characters in expectation-like patterns
|
|
228
|
-
|
|
229
|
-
content.match?(/^#\s*([=><]|.*[=><])/) && # Contains =, >, or < after #
|
|
230
|
-
!content.match?(/^#\s*[^=><]*$/) # Not just a regular comment without expectation chars
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# Extract the malformed syntax portion for warning display
|
|
234
|
-
def extract_malformed_syntax(content)
|
|
235
|
-
# Try to identify what the user was attempting to write
|
|
236
|
-
case content
|
|
237
|
-
when /^#\s*([=><][^=><]*[=><].*?)(\s|$)/ # Pattern with expectation chars
|
|
238
|
-
$1.strip
|
|
239
|
-
when /^#\s*([=><].*?)(\s|$)/ # Simple pattern starting with expectation char
|
|
240
|
-
$1.strip
|
|
241
|
-
when /^#\s*(.*?[=><].*?)(\s|$)/ # Pattern containing expectation chars
|
|
242
|
-
$1.strip
|
|
243
|
-
else
|
|
244
|
-
# Fallback: show the part after #
|
|
245
|
-
content.sub(/^#\s*/, '').split(/\s/).first || 'unknown'
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Parser type identification for metadata
|
|
250
|
-
def parser_type
|
|
251
|
-
:legacy
|
|
252
|
-
end
|
|
253
|
-
end
|
|
254
|
-
end
|