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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/tryouts/cli/formatters/agent.rb +12 -16
  4. data/lib/tryouts/cli/formatters/base.rb +2 -0
  5. data/lib/tryouts/cli/formatters/compact.rb +2 -0
  6. data/lib/tryouts/cli/formatters/factory.rb +2 -0
  7. data/lib/tryouts/cli/formatters/live_status_manager.rb +2 -0
  8. data/lib/tryouts/cli/formatters/output_manager.rb +2 -0
  9. data/lib/tryouts/cli/formatters/quiet.rb +2 -0
  10. data/lib/tryouts/cli/formatters/test_run_state.rb +2 -0
  11. data/lib/tryouts/cli/formatters/token_budget.rb +2 -0
  12. data/lib/tryouts/cli/formatters/tty_status_display.rb +2 -0
  13. data/lib/tryouts/cli/formatters/verbose.rb +2 -0
  14. data/lib/tryouts/cli/formatters.rb +2 -0
  15. data/lib/tryouts/cli/line_spec_parser.rb +2 -0
  16. data/lib/tryouts/cli/modes/generate.rb +2 -0
  17. data/lib/tryouts/cli/modes/inspect.rb +15 -3
  18. data/lib/tryouts/cli/opts.rb +8 -6
  19. data/lib/tryouts/cli/tty_detector.rb +2 -0
  20. data/lib/tryouts/cli.rb +2 -0
  21. data/lib/tryouts/console.rb +2 -0
  22. data/lib/tryouts/expectation_evaluators/base.rb +2 -0
  23. data/lib/tryouts/expectation_evaluators/boolean.rb +2 -0
  24. data/lib/tryouts/expectation_evaluators/exception.rb +2 -0
  25. data/lib/tryouts/expectation_evaluators/expectation_result.rb +2 -0
  26. data/lib/tryouts/expectation_evaluators/false.rb +2 -0
  27. data/lib/tryouts/expectation_evaluators/intentional_failure.rb +2 -0
  28. data/lib/tryouts/expectation_evaluators/non_nil.rb +2 -0
  29. data/lib/tryouts/expectation_evaluators/output.rb +2 -0
  30. data/lib/tryouts/expectation_evaluators/performance_time.rb +2 -0
  31. data/lib/tryouts/expectation_evaluators/regex_match.rb +2 -0
  32. data/lib/tryouts/expectation_evaluators/registry.rb +2 -0
  33. data/lib/tryouts/expectation_evaluators/regular.rb +2 -0
  34. data/lib/tryouts/expectation_evaluators/result_type.rb +2 -0
  35. data/lib/tryouts/expectation_evaluators/true.rb +2 -0
  36. data/lib/tryouts/failure_collector.rb +2 -0
  37. data/lib/tryouts/file_processor.rb +3 -4
  38. data/lib/tryouts/parser_warning.rb +2 -0
  39. data/lib/tryouts/parsers/base_parser.rb +3 -3
  40. data/lib/tryouts/parsers/enhanced_parser.rb +57 -3
  41. data/lib/tryouts/parsers/shared_methods.rb +67 -17
  42. data/lib/tryouts/test_batch.rb +155 -43
  43. data/lib/tryouts/test_case.rb +54 -6
  44. data/lib/tryouts/test_executor.rb +2 -0
  45. data/lib/tryouts/test_result_aggregator.rb +2 -0
  46. data/lib/tryouts/test_runner.rb +2 -1
  47. data/lib/tryouts/translators/minitest_translator.rb +36 -12
  48. data/lib/tryouts/translators/rspec_translator.rb +19 -0
  49. data/lib/tryouts/version.rb +3 -1
  50. data/lib/tryouts.rb +2 -1
  51. metadata +1 -2
  52. 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"
@@ -1,5 +1,7 @@
1
1
  # lib/tryouts/version.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  class Tryouts
4
- VERSION = '3.7.0'
6
+ VERSION = '4.0.0.pre1'
5
7
  end
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: 3.7.0
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