tryouts 3.0.0 → 3.1.0

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -115
  3. data/exe/try +25 -4
  4. data/lib/tryouts/cli/formatters/base.rb +33 -21
  5. data/lib/tryouts/cli/formatters/compact.rb +122 -84
  6. data/lib/tryouts/cli/formatters/factory.rb +1 -1
  7. data/lib/tryouts/cli/formatters/output_manager.rb +13 -2
  8. data/lib/tryouts/cli/formatters/quiet.rb +22 -16
  9. data/lib/tryouts/cli/formatters/verbose.rb +101 -60
  10. data/lib/tryouts/console.rb +53 -17
  11. data/lib/tryouts/expectation_evaluators/base.rb +101 -0
  12. data/lib/tryouts/expectation_evaluators/boolean.rb +60 -0
  13. data/lib/tryouts/expectation_evaluators/exception.rb +61 -0
  14. data/lib/tryouts/expectation_evaluators/expectation_result.rb +67 -0
  15. data/lib/tryouts/expectation_evaluators/false.rb +60 -0
  16. data/lib/tryouts/expectation_evaluators/intentional_failure.rb +74 -0
  17. data/lib/tryouts/expectation_evaluators/output.rb +101 -0
  18. data/lib/tryouts/expectation_evaluators/performance_time.rb +81 -0
  19. data/lib/tryouts/expectation_evaluators/regex_match.rb +57 -0
  20. data/lib/tryouts/expectation_evaluators/registry.rb +66 -0
  21. data/lib/tryouts/expectation_evaluators/regular.rb +67 -0
  22. data/lib/tryouts/expectation_evaluators/result_type.rb +51 -0
  23. data/lib/tryouts/expectation_evaluators/true.rb +58 -0
  24. data/lib/tryouts/prism_parser.rb +112 -15
  25. data/lib/tryouts/test_executor.rb +6 -4
  26. data/lib/tryouts/test_runner.rb +1 -1
  27. data/lib/tryouts/testbatch.rb +288 -98
  28. data/lib/tryouts/testcase.rb +141 -0
  29. data/lib/tryouts/translators/minitest_translator.rb +40 -11
  30. data/lib/tryouts/translators/rspec_translator.rb +47 -12
  31. data/lib/tryouts/version.rb +1 -1
  32. data/lib/tryouts.rb +42 -0
  33. metadata +16 -3
@@ -13,26 +13,32 @@ class Tryouts
13
13
  end
14
14
 
15
15
  # Phase-level output - minimal for compact mode
16
- def phase_header(message, _file_count = nil, level = 0)
17
- # Skip execution phase headers in compact mode - they create unwanted empty lines
18
- return if level >= 1
19
-
20
- text = "#{message}..."
21
- puts indent_text(text, level)
16
+ def phase_header(message, file_count = nil, level = 0, io = $stderr)
17
+ # Show processing header but skip execution phase headers to avoid empty lines
18
+ case level
19
+ when 0
20
+ # Main processing header
21
+ text = file_count ? "#{message}" : "#{message}..."
22
+ io.puts text
23
+ when 1
24
+ # Skip execution phase headers - they create unwanted empty lines
25
+ return
26
+ else
27
+ # Other phase headers with minimal formatting
28
+ io.puts indent_text(message, level - 1)
29
+ end
22
30
  end
23
31
 
24
32
  # File-level operations - compact single lines
25
- def file_start(file_path, context_info = {})
26
- return unless @show_debug
27
-
28
- framework = context_info[:framework] || :direct
29
- context = context_info[:context] || :fresh
30
- pretty_path = Console.pretty_path(file_path)
33
+ def file_start(file_path, _context_info = {}, io = $stderr)
34
+ # See file_execution_start
35
+ end
31
36
 
32
- puts indent_text("Running: #{pretty_path} (#{framework}/#{context})", 1)
37
+ def file_end(file_path, context_info = {}, io = $stderr)
38
+ # No output in compact mode
33
39
  end
34
40
 
35
- def file_parsed(_file_path, test_count, setup_present: false, teardown_present: false)
41
+ def file_parsed(_file_path, test_count, io = $stderr, setup_present: false, teardown_present: false)
36
42
  # Don't show parsing info in compact mode unless debug
37
43
  return unless @show_debug
38
44
 
@@ -41,90 +47,119 @@ class Tryouts
41
47
  extras << 'teardown' if teardown_present
42
48
  suffix = extras.empty? ? '' : " +#{extras.join(',')}"
43
49
 
44
- puts indent_text("Parsed #{test_count} tests#{suffix}", 1)
50
+ io.puts indent_text("Parsed #{test_count} tests#{suffix}", 1)
45
51
  end
46
52
 
47
- def file_execution_start(file_path, test_count, _context_mode)
53
+ def file_execution_start(file_path, test_count, _context_mode, io = $stderr)
48
54
  pretty_path = Console.pretty_path(file_path)
49
- puts
50
- puts "#{pretty_path}: #{test_count} tests"
55
+ io.puts "#{pretty_path}: #{test_count} tests"
51
56
  end
52
57
 
53
- def file_result(_file_path, total_tests, failed_count, error_count, elapsed_time)
54
- detail = []
55
- if failed_count > 0
58
+ # Summary operations
59
+ def batch_summary(total_tests, failed_count, elapsed_time)
60
+ # Skip - file_result already shows this information with better alignment
61
+ end
62
+
63
+ def file_result(_file_path, total_tests, failed_count, error_count, elapsed_time, io = $stdout)
64
+ issues_count = failed_count + error_count
65
+ passed_count = total_tests - issues_count
66
+ details = [
67
+ # "#{passed_count} passed",
68
+ ]
69
+
70
+ if issues_count > 0
56
71
  status = Console.color(:red, '✗')
57
- detail << "#{failed_count}/#{total_tests} failed"
72
+ details << "#{passed_count}/#{total_tests} passed"
58
73
  else
59
74
  status = Console.color(:green, '✓')
60
- detail << "#{total_tests} passed"
75
+ details << "#{total_tests} passed"
61
76
  end
62
77
 
63
78
  if error_count > 0
79
+ status = Console.color(:yellow, '⚠') if error_count == 0
80
+ details << "#{error_count} errors"
81
+ end
82
+
83
+ if failed_count > 0
64
84
  status = Console.color(:yellow, '⚠') if failed_count == 0
65
- detail << "#{error_count} errors"
85
+ details << "#{failed_count} failed"
66
86
  end
67
87
 
68
88
  time_str = if elapsed_time
69
- if elapsed_time < 2
70
- " (#{(elapsed_time * 1000).to_i}ms)"
71
- else
72
- " (#{elapsed_time.round(2)}s)"
73
- end
89
+ format_timing(elapsed_time)
74
90
  else
75
91
  ''
76
92
  end
77
- puts " #{status} #{detail.join(', ')}#{time_str}"
93
+ io.puts " #{status} #{details.join(', ')}#{time_str}"
78
94
  end
79
95
 
80
96
  # Test-level operations - only show in debug mode for compact
81
- def test_start(test_case, index, _total)
97
+ def test_start(test_case, index, _total, io = $stdout)
82
98
  return unless @show_debug
83
99
 
84
100
  desc = test_case.description.to_s
85
101
  desc = "test #{index}" if desc.empty?
86
- puts " Running: #{desc}"
102
+
103
+ io.puts " Running: #{desc}"
104
+ end
105
+
106
+ def test_end(test_case, index, _total, io = $stdout)
107
+ # No output for test end
87
108
  end
88
109
 
89
- def test_result(test_case, result_status, actual_results = [], _elapsed_time = nil)
110
+ def test_result(result_packet, io = $stdout)
90
111
  # Only show failed tests in compact mode unless show_passed is true
91
- return if result_status == :passed && !@show_passed
112
+ return if result_packet.passed? && !@show_passed
92
113
 
114
+ test_case = result_packet.test_case
93
115
  desc = test_case.description.to_s
94
116
  desc = 'unnamed test' if desc.empty?
95
117
 
96
- case result_status
118
+ case result_packet.status
97
119
  when :passed
98
120
  status = Console.color(:green, '✓')
121
+ io.puts indent_text("#{status} #{desc}", 1)
99
122
  when :failed
100
123
  status = Console.color(:red, '✗')
101
- if actual_results.any?
102
- failure_info = " (got: #{actual_results.first.inspect})"
103
- desc += failure_info
124
+ io.puts indent_text("#{status} #{desc}", 1)
125
+
126
+ # Show minimal context for failures
127
+ if result_packet.actual_results.any?
128
+ failure_info = "got: #{result_packet.first_actual.inspect}"
129
+ io.puts indent_text(" #{failure_info}", 1)
130
+ end
131
+
132
+ # Show 1-2 lines of test context if available
133
+ if test_case.source_lines && test_case.source_lines.size <= 3
134
+ test_case.source_lines.each do |line|
135
+ next if line.strip.empty? || line.strip.start_with?('#')
136
+ io.puts indent_text(" #{line.strip}", 1)
137
+ break # Only show first relevant line
138
+ end
104
139
  end
105
140
  when :skipped
106
141
  status = Console.color(:yellow, '-')
142
+ io.puts indent_text("#{status} #{desc}", 1)
107
143
  else
108
144
  status = '?'
145
+ io.puts indent_text("#{status} #{desc}", 1)
109
146
  end
110
-
111
- puts indent_text("#{status} #{desc}", 1)
112
147
  end
113
148
 
114
- def test_output(test_case, output_text)
149
+ def test_output(_test_case, output_text, io = $stdout)
115
150
  # In compact mode, only show output for failed tests and only if debug mode is enabled
116
151
  return if output_text.nil? || output_text.strip.empty?
117
152
  return unless @show_debug
118
153
 
119
- puts " Output: #{output_text.lines.count} lines"
154
+ io.puts " Output: #{output_text.lines.count} lines"
120
155
  if output_text.lines.count <= 3
121
156
  output_text.lines.each do |line|
122
- puts " #{line.chomp}"
157
+ io.puts " #{line.chomp}"
123
158
  end
124
159
  else
125
- puts " #{output_text.lines.first.chomp}"
126
- puts " ... (#{output_text.lines.count - 2} more lines)"
127
- puts " #{output_text.lines.last.chomp}"
160
+ io.puts " #{output_text.lines.first.chomp}"
161
+ io.puts " ... (#{output_text.lines.count - 2} more lines)"
162
+ io.puts " #{output_text.lines.last.chomp}"
128
163
  end
129
164
  end
130
165
 
@@ -133,42 +168,37 @@ class Tryouts
133
168
  # No file setup start output for compact
134
169
  end
135
170
 
136
- def setup_output(output_text)
171
+ def setup_output(output_text, io = $stderr)
137
172
  return if output_text.strip.empty?
138
173
  return unless @show_debug
139
174
 
140
175
  # In compact mode, just show that there was output
141
176
  lines = output_text.lines.count
142
- puts " Setup output (#{lines} lines)"
177
+ io.puts " Setup output (#{lines} lines)"
143
178
  end
144
179
 
145
- def teardown_start(_line_range)
180
+ def teardown_start(_line_range, io = $stderr)
146
181
  return unless @show_debug
147
182
 
148
- puts ' Teardown...'
183
+ io.puts ' Teardown...'
149
184
  end
150
185
 
151
- def teardown_output(output_text)
186
+ def teardown_output(output_text, io = $stderr)
152
187
  return if output_text.strip.empty?
153
188
  return unless @show_debug
154
189
 
155
190
  # In compact mode, just show that there was output
156
191
  lines = output_text.lines.count
157
- puts " Teardown output (#{lines} lines)"
192
+ io.puts " Teardown output (#{lines} lines)"
158
193
  end
159
194
 
160
- # Summary operations
161
- def batch_summary(total_tests, failed_count, elapsed_time)
162
- # Skip - file_result already shows this information with better alignment
163
- end
164
-
165
- def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time)
166
- puts
167
- puts '=' * 50
195
+ def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time, io = $stderr)
196
+ io.puts
197
+ io.puts '=' * 50
168
198
 
169
199
  issues_count = failed_count + error_count
170
200
  if issues_count > 0
171
- passed = total_tests - issues_count
201
+ passed = [total_tests - issues_count, 0].max # Ensure passed never goes negative
172
202
  details = []
173
203
  details << "#{failed_count} failed" if failed_count > 0
174
204
  details << "#{error_count} errors" if error_count > 0
@@ -177,54 +207,62 @@ class Tryouts
177
207
  result = Console.color(:green, "#{total_tests} tests passed")
178
208
  end
179
209
 
180
- time_str = if elapsed_time < 2
181
- "#{(elapsed_time * 1000).to_i}ms"
182
- else
183
- "#{elapsed_time.round(2)}s"
184
- end
210
+ time_str = format_timing(elapsed_time)
185
211
 
186
- puts "Total: #{result} (#{time_str})"
187
- puts "Files: #{successful_files} of #{total_files} successful"
212
+ io.puts "Total: #{result} (#{time_str})"
213
+ io.puts "Files: #{successful_files} of #{total_files} successful"
188
214
  end
189
215
 
190
216
  # Debug and diagnostic output - minimal in compact mode
191
- def debug_info(message, level = 0)
217
+ def debug_info(message, level = 0, io = $stderr)
192
218
  return unless @show_debug
193
219
 
194
- puts indent_text("DEBUG: #{message}", level)
220
+ io.puts indent_text("DEBUG: #{message}", level)
195
221
  end
196
222
 
197
- def trace_info(message, level = 0)
223
+ def trace_info(message, level = 0, io = $stderr)
198
224
  return unless @show_trace
199
225
 
200
- puts indent_text("TRACE: #{message}", level)
226
+ io.puts indent_text("TRACE: #{message}", level)
201
227
  end
202
228
 
203
- def error_message(message, backtrace = nil)
204
- puts Console.color(:red, "ERROR: #{message}")
229
+ def error_message(message, backtrace = nil, io = $stderr)
230
+ io.puts Console.color(:red, "ERROR: #{message}")
205
231
 
206
232
  return unless backtrace && @show_debug
207
233
 
208
234
  backtrace.first(3).each do |line|
209
- puts indent_text(line.chomp, 1)
235
+ io.puts indent_text(line.chomp, 1)
210
236
  end
211
237
  end
212
238
 
213
239
  # Utility methods
214
- def raw_output(text)
215
- puts text
240
+ def raw_output(text, io = $stdout)
241
+ io.puts text
216
242
  end
217
243
 
218
- def separator(style = :light)
244
+ def separator(style = :light, io = $stdout)
219
245
  case style
220
246
  when :heavy
221
- puts '=' * 50
247
+ io.puts '=' * 50
222
248
  when :light
223
- puts '-' * 50
249
+ io.puts '-' * 50
224
250
  when :dotted
225
- puts '.' * 50
251
+ io.puts '.' * 50
252
+ else
253
+ io.puts '-' * 50
254
+ end
255
+ end
256
+
257
+ private
258
+
259
+ def format_timing(elapsed_time)
260
+ if elapsed_time < 0.001
261
+ " (#{(elapsed_time * 1_000_000).round}μs)"
262
+ elsif elapsed_time < 1
263
+ " (#{(elapsed_time * 1000).round}ms)"
226
264
  else
227
- puts '-' * 50
265
+ " (#{elapsed_time.round(2)}s)"
228
266
  end
229
267
  end
230
268
  end
@@ -235,9 +273,9 @@ class Tryouts
235
273
  super(options.merge(show_passed: false))
236
274
  end
237
275
 
238
- def test_result(test_case, result_status, actual_results = [], elapsed_time = nil)
276
+ def test_result(result_packet)
239
277
  # Only show failed/error tests
240
- return if result_status == :passed
278
+ return if result_packet.passed?
241
279
 
242
280
  super
243
281
  end
@@ -33,7 +33,7 @@ class Tryouts
33
33
  QuietFormatter.new(options)
34
34
  end
35
35
  else
36
- VerboseFormatter.new(options) # Default to verbose
36
+ CompactFormatter.new(options) # Default to compact
37
37
  end
38
38
  end
39
39
 
@@ -30,6 +30,11 @@ class Tryouts
30
30
  @formatter.file_start(file_path, context_info)
31
31
  end
32
32
 
33
+ def file_end(file_path, framework: :direct, context: :fresh)
34
+ context_info = { framework: framework, context: context }
35
+ @formatter.file_end(file_path, context_info)
36
+ end
37
+
33
38
  def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
34
39
  with_indent(1) do
35
40
  @formatter.file_parsed(file_path, test_count,
@@ -62,8 +67,14 @@ class Tryouts
62
67
  end
63
68
  end
64
69
 
65
- def test_result(test_case, result_status, actual_results = [], elapsed_time = nil)
66
- @formatter.test_result(test_case, result_status, actual_results, elapsed_time)
70
+ def test_end(test_case, index, total)
71
+ with_indent(2) do
72
+ @formatter.test_end(test_case, index, total)
73
+ end
74
+ end
75
+
76
+ def test_result(result_packet)
77
+ @formatter.test_result(result_packet)
67
78
  end
68
79
 
69
80
  def test_output(test_case, output_text)
@@ -22,6 +22,10 @@ class Tryouts
22
22
  # Silent in quiet mode
23
23
  end
24
24
 
25
+ def file_end(_file_path, _context_info = {}, io = $stderr)
26
+ io.puts # add newline after all dots
27
+ end
28
+
25
29
  def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
26
30
  # Silent in quiet mode
27
31
  end
@@ -39,20 +43,24 @@ class Tryouts
39
43
  # Silent in quiet mode
40
44
  end
41
45
 
42
- def test_result(_test_case, result_status, _actual_results = [], _elapsed_time = nil)
43
- case result_status
46
+ def test_end(test_case, index, total, io = $stderr)
47
+ # Silent in quiet mode
48
+ end
49
+
50
+ def test_result(result_packet, io = $stderr)
51
+ case result_packet.status
44
52
  when :passed
45
- print Console.color(:green, '.')
53
+ io.print Console.color(:green, '.')
46
54
  when :failed
47
- print Console.color(:red, 'F')
55
+ io.print Console.color(:red, 'F')
48
56
  when :error
49
- print Console.color(:red, 'E')
57
+ io.print Console.color(:red, 'E')
50
58
  when :skipped
51
- print Console.color(:yellow, 'S')
59
+ io.print Console.color(:yellow, 'S')
52
60
  else
53
- print '?'
61
+ io.print '?'
54
62
  end
55
- $stdout.flush
63
+ io.flush
56
64
  end
57
65
 
58
66
  def test_output(test_case, output_text)
@@ -78,18 +86,16 @@ class Tryouts
78
86
  end
79
87
 
80
88
  # Summary operations - show results
81
- def batch_summary(total_tests, failed_count, elapsed_time)
89
+ def batch_summary(total_tests, failed_count, elapsed_time, io = $stderr)
82
90
  return unless @show_final_summary
83
91
 
84
- puts # New line after dots
85
-
86
92
  if failed_count > 0
87
93
  passed = total_tests - failed_count
88
94
  time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
89
- puts "#{failed_count} failed, #{passed} passed#{time_str}"
95
+ io.puts "#{failed_count} failed, #{passed} passed#{time_str}"
90
96
  else
91
97
  time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
92
- puts "#{total_tests} passed#{time_str}"
98
+ io.puts "#{total_tests} passed#{time_str}"
93
99
  end
94
100
  end
95
101
 
@@ -106,7 +112,7 @@ class Tryouts
106
112
 
107
113
  issues_count = failed_count + error_count
108
114
  if issues_count > 0
109
- passed = total_tests - issues_count
115
+ passed = [total_tests - issues_count, 0].max # Ensure passed never goes negative
110
116
  details = []
111
117
  details << "#{failed_count} failed" if failed_count > 0
112
118
  details << "#{error_count} errors" if error_count > 0
@@ -148,9 +154,9 @@ class Tryouts
148
154
 
149
155
  # Quiet formatter that only shows dots for failures and errors
150
156
  class QuietFailsFormatter < QuietFormatter
151
- def test_result(test_case, result_status, actual_results = [], elapsed_time = nil)
157
+ def test_result(result_packet)
152
158
  # Only show non-pass dots in fails mode
153
- return if result_status == :passed
159
+ return if result_packet.passed?
154
160
 
155
161
  super
156
162
  end