tryouts 3.1.1 → 3.2.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.
- checksums.yaml +4 -4
- data/exe/try +3 -3
- data/lib/tryouts/cli/formatters/base.rb +108 -48
- data/lib/tryouts/cli/formatters/compact.rb +97 -105
- data/lib/tryouts/cli/formatters/factory.rb +8 -2
- data/lib/tryouts/cli/formatters/live_status_manager.rb +138 -0
- data/lib/tryouts/cli/formatters/output_manager.rb +78 -66
- data/lib/tryouts/cli/formatters/quiet.rb +54 -102
- data/lib/tryouts/cli/formatters/test_run_state.rb +122 -0
- data/lib/tryouts/cli/formatters/tty_status_display.rb +273 -0
- data/lib/tryouts/cli/formatters/verbose.rb +103 -105
- data/lib/tryouts/cli/formatters.rb +3 -0
- data/lib/tryouts/cli/opts.rb +17 -8
- data/lib/tryouts/cli/tty_detector.rb +92 -0
- data/lib/tryouts/console.rb +1 -1
- data/lib/tryouts/expectation_evaluators/boolean.rb +1 -1
- data/lib/tryouts/expectation_evaluators/exception.rb +2 -2
- data/lib/tryouts/expectation_evaluators/expectation_result.rb +3 -3
- data/lib/tryouts/expectation_evaluators/false.rb +1 -1
- data/lib/tryouts/expectation_evaluators/intentional_failure.rb +2 -2
- data/lib/tryouts/expectation_evaluators/output.rb +6 -6
- data/lib/tryouts/expectation_evaluators/performance_time.rb +3 -3
- data/lib/tryouts/expectation_evaluators/regex_match.rb +2 -2
- data/lib/tryouts/expectation_evaluators/regular.rb +1 -1
- data/lib/tryouts/expectation_evaluators/result_type.rb +1 -1
- data/lib/tryouts/expectation_evaluators/true.rb +2 -2
- data/lib/tryouts/failure_collector.rb +109 -0
- data/lib/tryouts/prism_parser.rb +17 -17
- data/lib/tryouts/test_batch.rb +9 -5
- data/lib/tryouts/test_case.rb +4 -4
- data/lib/tryouts/test_runner.rb +12 -1
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +0 -9
- metadata +21 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f66a1e1e61b8c2d5e7201db55fe494954a8ec626bcb333bfc46d0842d469d897
|
4
|
+
data.tar.gz: a6fd7107a2934b4011a7093a1f28807fc5c07e67dfbcfdf266881978b1093294
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8846a206ebdebcbb816f5e9af65c7054f1719b9f511912dbf4b311811557a3124f18d026424e83a7a31150b8de6525c395e19748e45d0297f356465561ed30bb
|
7
|
+
data.tar.gz: f2affd5b231a61f9e5e5f4469c9297718c15da7637d849aad221249d1f83345b75cc17ab8098a1ea9ec9e173990f940c450ef31e3b008be84aa99b3ac9d86af4
|
data/exe/try
CHANGED
@@ -46,8 +46,8 @@ begin
|
|
46
46
|
expanded_files = []
|
47
47
|
files.each do |file_or_dir|
|
48
48
|
if File.directory?(file_or_dir)
|
49
|
-
# If it's a directory, find all *_try.rb files within it
|
50
|
-
dir_files = Dir.glob('**/*_try.rb', base: file_or_dir)
|
49
|
+
# If it's a directory, find all *_try.rb and *.try.rb files within it
|
50
|
+
dir_files = Dir.glob(['**/*_try.rb', '**/*.try.rb'], base: file_or_dir)
|
51
51
|
expanded_files.concat(dir_files.map { |f| File.join(file_or_dir, f) })
|
52
52
|
else
|
53
53
|
# If it's a file, add it as-is
|
@@ -59,7 +59,7 @@ begin
|
|
59
59
|
# Default file discovery if no files specified
|
60
60
|
if files.empty?
|
61
61
|
raw_files = Dir.glob(
|
62
|
-
['{app,apps,lib,try,tryouts}/**/*_try.rb', './*_try.rb'],
|
62
|
+
['{app,apps,lib,try,tryouts}/**/*_try.rb', './*_try.rb', '{app,apps,lib,try,tryouts}/**/*.try.rb', './*.try.rb'],
|
63
63
|
base: Dir.pwd,
|
64
64
|
)
|
65
65
|
|
@@ -1,120 +1,180 @@
|
|
1
1
|
# lib/tryouts/cli/formatters/base.rb
|
2
2
|
|
3
|
-
require_relative 'factory'
|
4
3
|
require_relative 'output_manager'
|
5
4
|
|
6
5
|
class Tryouts
|
7
6
|
class CLI
|
8
7
|
# Enhanced interface for all test output formatting
|
9
8
|
module FormatterInterface
|
9
|
+
attr_reader :stdout, :stderr, :current_indent
|
10
10
|
|
11
|
-
|
11
|
+
def initialize(options = {})
|
12
|
+
@stdout = options.fetch(:stdout, $stdout)
|
13
|
+
@stderr = options.fetch(:stderr, $stderr)
|
14
|
+
@current_indent = 0
|
15
|
+
@options = options
|
16
|
+
end
|
12
17
|
|
13
18
|
# Phase-level output (major sections)
|
14
|
-
def phase_header(message, file_count
|
15
|
-
|
19
|
+
def phase_header(message, file_count: nil)
|
20
|
+
# Default: no output
|
16
21
|
end
|
17
22
|
|
18
23
|
# File-level operations
|
19
|
-
def file_start(file_path, context_info
|
20
|
-
|
24
|
+
def file_start(file_path, context_info: {})
|
25
|
+
# Default: no output
|
21
26
|
end
|
22
27
|
|
23
|
-
def file_end(file_path, context_info
|
24
|
-
|
28
|
+
def file_end(file_path, context_info: {})
|
29
|
+
# Default: no output
|
25
30
|
end
|
26
31
|
|
27
|
-
def file_parsed(file_path, test_count
|
28
|
-
|
32
|
+
def file_parsed(file_path, test_count:, setup_present: false, teardown_present: false)
|
33
|
+
# Default: no output
|
29
34
|
end
|
30
35
|
|
31
|
-
def file_execution_start(file_path, test_count
|
32
|
-
|
36
|
+
def file_execution_start(file_path, test_count:, context_mode:)
|
37
|
+
# Default: no output
|
33
38
|
end
|
34
39
|
|
35
|
-
def file_result(file_path, total_tests
|
36
|
-
|
40
|
+
def file_result(file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
|
41
|
+
# Default: no output
|
37
42
|
end
|
38
43
|
|
39
44
|
# Test-level operations
|
40
|
-
def test_start(test_case
|
41
|
-
|
45
|
+
def test_start(test_case:, index:, total:)
|
46
|
+
# Default: no output
|
42
47
|
end
|
43
48
|
|
44
|
-
def test_end(test_case
|
45
|
-
|
49
|
+
def test_end(test_case:, index:, total:)
|
50
|
+
# Default: no output
|
46
51
|
end
|
47
52
|
|
48
|
-
def test_result(result_packet
|
49
|
-
|
53
|
+
def test_result(result_packet)
|
54
|
+
# Default: no output
|
50
55
|
end
|
51
56
|
|
52
|
-
def test_output(test_case
|
53
|
-
|
57
|
+
def test_output(test_case:, output_text:, result_packet:)
|
58
|
+
# Default: no output
|
54
59
|
end
|
55
60
|
|
56
61
|
# Setup/teardown operations
|
57
|
-
def setup_start(line_range
|
58
|
-
|
62
|
+
def setup_start(line_range:)
|
63
|
+
# Default: no output
|
59
64
|
end
|
60
65
|
|
61
|
-
def setup_output(output_text
|
62
|
-
|
66
|
+
def setup_output(output_text)
|
67
|
+
# Default: no output
|
63
68
|
end
|
64
69
|
|
65
|
-
def teardown_start(line_range
|
66
|
-
|
70
|
+
def teardown_start(line_range:)
|
71
|
+
# Default: no output
|
67
72
|
end
|
68
73
|
|
69
|
-
def teardown_output(output_text
|
70
|
-
|
74
|
+
def teardown_output(output_text)
|
75
|
+
# Default: no output
|
71
76
|
end
|
72
77
|
|
73
78
|
# Summary operations
|
74
|
-
def batch_summary(
|
75
|
-
|
79
|
+
def batch_summary(failure_collector)
|
80
|
+
# Default: no output
|
76
81
|
end
|
77
82
|
|
78
|
-
def grand_total(total_tests
|
79
|
-
|
83
|
+
def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
|
84
|
+
# Default: no output
|
80
85
|
end
|
81
86
|
|
82
87
|
# Debug and diagnostic output
|
83
|
-
def debug_info(message, level
|
84
|
-
|
88
|
+
def debug_info(message, level: 0)
|
89
|
+
# Default: no output
|
90
|
+
end
|
91
|
+
|
92
|
+
def trace_info(message, level: 0)
|
93
|
+
# Default: no output
|
94
|
+
end
|
95
|
+
|
96
|
+
def error_message(message, backtrace: nil)
|
97
|
+
# Default: no output
|
98
|
+
end
|
99
|
+
|
100
|
+
# Live status capability negotiation
|
101
|
+
def live_status_capabilities
|
102
|
+
{
|
103
|
+
supports_coordination: false, # Can work with coordinated output
|
104
|
+
output_frequency: :medium, # :low, :medium, :high
|
105
|
+
requires_tty: false, # Must have TTY to function
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
# Live status integration (optional methods)
|
110
|
+
def set_live_status_manager(manager)
|
111
|
+
@live_status_manager = manager
|
85
112
|
end
|
86
113
|
|
87
|
-
def
|
88
|
-
|
114
|
+
def live_status_manager
|
115
|
+
@live_status_manager
|
89
116
|
end
|
90
117
|
|
91
|
-
|
92
|
-
|
118
|
+
# Standard output methods that coordinate with live status automatically
|
119
|
+
def write(text)
|
120
|
+
if @live_status_manager&.enabled?
|
121
|
+
@live_status_manager.write_string(text)
|
122
|
+
else
|
123
|
+
@stdout.print(text)
|
124
|
+
end
|
93
125
|
end
|
94
126
|
|
95
|
-
|
96
|
-
|
97
|
-
raise NotImplementedError, "#{self.class} must implement #raw_output"
|
127
|
+
def puts(text = '')
|
128
|
+
write("#{text}\n")
|
98
129
|
end
|
99
130
|
|
100
|
-
|
101
|
-
|
131
|
+
# Optional: formatters can implement this to provide custom live status updates
|
132
|
+
def update_live_status(state_updates = {})
|
133
|
+
@live_status_manager&.update_status(state_updates)
|
102
134
|
end
|
103
135
|
|
136
|
+
protected
|
137
|
+
|
138
|
+
# Utility methods for formatters to use
|
104
139
|
def indent_text(text, level = nil)
|
105
140
|
level ||= current_indent || 0
|
106
|
-
indent
|
141
|
+
indent = ' ' * level
|
107
142
|
"#{indent}#{text}"
|
108
143
|
end
|
109
144
|
|
110
145
|
def with_indent(level)
|
111
|
-
old_indent
|
146
|
+
old_indent = @current_indent
|
112
147
|
@current_indent = level
|
113
148
|
yield
|
114
149
|
ensure
|
115
150
|
@current_indent = old_indent
|
116
151
|
end
|
117
|
-
end
|
118
152
|
|
153
|
+
def separator(style = :light)
|
154
|
+
width = @options.fetch(:line_width, 70)
|
155
|
+
case style
|
156
|
+
when :heavy
|
157
|
+
'=' * width
|
158
|
+
when :light
|
159
|
+
'-' * width
|
160
|
+
when :dotted
|
161
|
+
'.' * width
|
162
|
+
else
|
163
|
+
'-' * width
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def format_timing(elapsed_time)
|
168
|
+
return '' unless elapsed_time
|
169
|
+
|
170
|
+
if elapsed_time < 0.001
|
171
|
+
" (#{(elapsed_time * 1_000_000).round}μs)"
|
172
|
+
elsif elapsed_time < 1
|
173
|
+
" (#{(elapsed_time * 1000).round}ms)"
|
174
|
+
else
|
175
|
+
" (#{elapsed_time.round(2)}s)"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
119
179
|
end
|
120
180
|
end
|
@@ -7,38 +7,31 @@ class Tryouts
|
|
7
7
|
include FormatterInterface
|
8
8
|
|
9
9
|
def initialize(options = {})
|
10
|
-
|
11
|
-
@
|
12
|
-
@
|
10
|
+
super
|
11
|
+
@show_debug = options.fetch(:debug, false)
|
12
|
+
@show_trace = options.fetch(:trace, false)
|
13
|
+
@show_passed = options.fetch(:show_passed, true)
|
13
14
|
end
|
14
15
|
|
15
16
|
# Phase-level output - minimal for compact mode
|
16
|
-
def phase_header(message, file_count
|
17
|
+
def phase_header(message, file_count: nil)
|
17
18
|
# Show processing header but skip execution phase headers to avoid empty lines
|
18
|
-
|
19
|
-
when 0
|
19
|
+
if message.include?('PROCESSING')
|
20
20
|
# Main processing header
|
21
21
|
text = file_count ? "#{message}" : "#{message}..."
|
22
|
-
|
23
|
-
|
24
|
-
# Skip execution phase headers - they create unwanted empty lines
|
25
|
-
return
|
26
|
-
else
|
22
|
+
@stderr.puts text
|
23
|
+
elsif !message.include?('EXECUTING')
|
27
24
|
# Other phase headers with minimal formatting
|
28
|
-
|
25
|
+
@stderr.puts message
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
29
|
# File-level operations - compact single lines
|
33
|
-
def file_start(file_path,
|
34
|
-
#
|
35
|
-
end
|
36
|
-
|
37
|
-
def file_end(file_path, context_info = {}, io = $stderr)
|
38
|
-
# No output in compact mode
|
30
|
+
def file_start(file_path, context_info: {})
|
31
|
+
# Output handled in file_execution_start for compact mode
|
39
32
|
end
|
40
33
|
|
41
|
-
def file_parsed(_file_path, test_count
|
34
|
+
def file_parsed(_file_path, test_count:, setup_present: false, teardown_present: false)
|
42
35
|
# Don't show parsing info in compact mode unless debug
|
43
36
|
return unless @show_debug
|
44
37
|
|
@@ -47,25 +40,46 @@ class Tryouts
|
|
47
40
|
extras << 'teardown' if teardown_present
|
48
41
|
suffix = extras.empty? ? '' : " +#{extras.join(',')}"
|
49
42
|
|
50
|
-
|
43
|
+
@stderr.puts indent_text("Parsed #{test_count} tests#{suffix}", 1)
|
51
44
|
end
|
52
45
|
|
53
|
-
def file_execution_start(file_path, test_count
|
46
|
+
def file_execution_start(file_path, test_count:, context_mode:)
|
54
47
|
pretty_path = Console.pretty_path(file_path)
|
55
|
-
|
48
|
+
@stderr.puts "#{pretty_path}: #{test_count} tests"
|
56
49
|
end
|
57
50
|
|
58
|
-
# Summary operations
|
59
|
-
def batch_summary(
|
60
|
-
|
51
|
+
# Summary operations - show failure summary
|
52
|
+
def batch_summary(failure_collector)
|
53
|
+
return unless failure_collector.any_failures?
|
54
|
+
|
55
|
+
puts
|
56
|
+
puts separator
|
57
|
+
puts Console.color(:red, 'Failed Tests:')
|
58
|
+
puts
|
59
|
+
|
60
|
+
failure_collector.failures_by_file.each do |file_path, failures|
|
61
|
+
failures.each do |failure|
|
62
|
+
pretty_path = Console.pretty_path(file_path)
|
63
|
+
|
64
|
+
# Include line number with file path for easy copying/clicking
|
65
|
+
location = if failure.line_number > 0
|
66
|
+
"#{pretty_path}:#{failure.line_number}"
|
67
|
+
else
|
68
|
+
pretty_path
|
69
|
+
end
|
70
|
+
|
71
|
+
puts " #{location}"
|
72
|
+
puts " #{Console.color(:red, '✗')} #{failure.description}"
|
73
|
+
puts " #{failure.failure_reason}"
|
74
|
+
puts
|
75
|
+
end
|
76
|
+
end
|
61
77
|
end
|
62
78
|
|
63
|
-
def file_result(_file_path, total_tests
|
79
|
+
def file_result(_file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
|
64
80
|
issues_count = failed_count + error_count
|
65
81
|
passed_count = total_tests - issues_count
|
66
|
-
details
|
67
|
-
# "#{passed_count} passed",
|
68
|
-
]
|
82
|
+
details = []
|
69
83
|
|
70
84
|
if issues_count > 0
|
71
85
|
status = Console.color(:red, '✗')
|
@@ -76,38 +90,28 @@ class Tryouts
|
|
76
90
|
end
|
77
91
|
|
78
92
|
if error_count > 0
|
79
|
-
status = Console.color(:yellow, '⚠') if error_count == 0
|
80
93
|
details << "#{error_count} errors"
|
81
94
|
end
|
82
95
|
|
83
96
|
if failed_count > 0
|
84
|
-
status = Console.color(:yellow, '⚠') if failed_count == 0
|
85
97
|
details << "#{failed_count} failed"
|
86
98
|
end
|
87
99
|
|
88
|
-
time_str =
|
89
|
-
|
90
|
-
else
|
91
|
-
''
|
92
|
-
end
|
93
|
-
io.puts " #{status} #{details.join(', ')}#{time_str}"
|
100
|
+
time_str = format_timing(elapsed_time)
|
101
|
+
puts " #{status} #{details.join(', ')}#{time_str}"
|
94
102
|
end
|
95
103
|
|
96
104
|
# Test-level operations - only show in debug mode for compact
|
97
|
-
def test_start(test_case
|
105
|
+
def test_start(test_case:, index:, total:)
|
98
106
|
return unless @show_debug
|
99
107
|
|
100
108
|
desc = test_case.description.to_s
|
101
109
|
desc = "test #{index}" if desc.empty?
|
102
110
|
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
def test_end(test_case, index, _total, io = $stdout)
|
107
|
-
# No output for test end
|
111
|
+
puts " Running: #{desc}"
|
108
112
|
end
|
109
113
|
|
110
|
-
def test_result(result_packet
|
114
|
+
def test_result(result_packet)
|
111
115
|
# Only show failed tests in compact mode unless show_passed is true
|
112
116
|
return if result_packet.passed? && !@show_passed
|
113
117
|
|
@@ -118,152 +122,132 @@ class Tryouts
|
|
118
122
|
case result_packet.status
|
119
123
|
when :passed
|
120
124
|
status = Console.color(:green, '✓')
|
121
|
-
|
125
|
+
puts indent_text("#{status} #{desc}", 1)
|
122
126
|
when :failed
|
123
127
|
status = Console.color(:red, '✗')
|
124
|
-
|
128
|
+
puts indent_text("#{status} #{desc}", 1)
|
125
129
|
|
126
130
|
# Show minimal context for failures
|
127
131
|
if result_packet.actual_results.any?
|
128
132
|
failure_info = "got: #{result_packet.first_actual.inspect}"
|
129
|
-
|
133
|
+
puts indent_text(" #{failure_info}", 1)
|
130
134
|
end
|
131
135
|
|
132
136
|
# Show 1-2 lines of test context if available
|
133
137
|
if test_case.source_lines && test_case.source_lines.size <= 3
|
134
138
|
test_case.source_lines.each do |line|
|
135
139
|
next if line.strip.empty? || line.strip.start_with?('#')
|
136
|
-
|
140
|
+
|
141
|
+
puts indent_text(" #{line.strip}", 1)
|
137
142
|
break # Only show first relevant line
|
138
143
|
end
|
139
144
|
end
|
140
145
|
when :skipped
|
141
146
|
status = Console.color(:yellow, '-')
|
142
|
-
|
147
|
+
puts indent_text("#{status} #{desc}", 1)
|
143
148
|
else
|
144
149
|
status = '?'
|
145
|
-
|
150
|
+
puts indent_text("#{status} #{desc}", 1)
|
146
151
|
end
|
147
152
|
end
|
148
153
|
|
149
|
-
def test_output(
|
154
|
+
def test_output(test_case:, output_text:, result_packet:)
|
150
155
|
# In compact mode, only show output for failed tests and only if debug mode is enabled
|
151
156
|
return if output_text.nil? || output_text.strip.empty?
|
152
157
|
return unless @show_debug
|
158
|
+
return if result_packet.passed?
|
153
159
|
|
154
|
-
|
160
|
+
puts " Output: #{output_text.lines.count} lines"
|
155
161
|
if output_text.lines.count <= 3
|
156
162
|
output_text.lines.each do |line|
|
157
|
-
|
163
|
+
puts " #{line.chomp}"
|
158
164
|
end
|
159
165
|
else
|
160
|
-
|
161
|
-
|
162
|
-
|
166
|
+
puts " #{output_text.lines.first.chomp}"
|
167
|
+
puts " ... (#{output_text.lines.count - 2} more lines)"
|
168
|
+
puts " #{output_text.lines.last.chomp}"
|
163
169
|
end
|
164
170
|
end
|
165
171
|
|
166
172
|
# Setup/teardown operations - minimal output
|
167
|
-
def setup_start(
|
173
|
+
def setup_start(line_range:)
|
168
174
|
# No file setup start output for compact
|
169
175
|
end
|
170
176
|
|
171
|
-
def setup_output(output_text
|
177
|
+
def setup_output(output_text)
|
172
178
|
return if output_text.strip.empty?
|
173
179
|
return unless @show_debug
|
174
180
|
|
175
181
|
# In compact mode, just show that there was output
|
176
182
|
lines = output_text.lines.count
|
177
|
-
|
183
|
+
@stderr.puts " Setup output (#{lines} lines)"
|
178
184
|
end
|
179
185
|
|
180
|
-
def teardown_start(
|
186
|
+
def teardown_start(line_range:)
|
181
187
|
return unless @show_debug
|
182
188
|
|
183
|
-
|
189
|
+
@stderr.puts ' Teardown...'
|
184
190
|
end
|
185
191
|
|
186
|
-
def teardown_output(output_text
|
192
|
+
def teardown_output(output_text)
|
187
193
|
return if output_text.strip.empty?
|
188
194
|
return unless @show_debug
|
189
195
|
|
190
196
|
# In compact mode, just show that there was output
|
191
197
|
lines = output_text.lines.count
|
192
|
-
|
198
|
+
@stderr.puts " Teardown output (#{lines} lines)"
|
193
199
|
end
|
194
200
|
|
195
|
-
def grand_total(total_tests
|
196
|
-
|
197
|
-
|
201
|
+
def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
|
202
|
+
@stderr.puts
|
203
|
+
@stderr.puts '=' * 50
|
198
204
|
|
199
205
|
issues_count = failed_count + error_count
|
200
206
|
if issues_count > 0
|
201
|
-
passed
|
207
|
+
passed = [total_tests - issues_count, 0].max # Ensure passed never goes negative
|
202
208
|
details = []
|
203
209
|
details << "#{failed_count} failed" if failed_count > 0
|
204
210
|
details << "#{error_count} errors" if error_count > 0
|
205
|
-
result
|
211
|
+
result = Console.color(:red, "#{details.join(', ')}, #{passed} passed")
|
206
212
|
else
|
207
213
|
result = Console.color(:green, "#{total_tests} tests passed")
|
208
214
|
end
|
209
215
|
|
210
216
|
time_str = format_timing(elapsed_time)
|
211
217
|
|
212
|
-
|
213
|
-
|
218
|
+
@stderr.puts "Total: #{result}#{time_str}"
|
219
|
+
@stderr.puts "Files: #{successful_files} of #{total_files} successful"
|
214
220
|
end
|
215
221
|
|
216
222
|
# Debug and diagnostic output - minimal in compact mode
|
217
|
-
def debug_info(message, level
|
223
|
+
def debug_info(message, level: 0)
|
218
224
|
return unless @show_debug
|
219
225
|
|
220
|
-
|
226
|
+
@stderr.puts indent_text("DEBUG: #{message}", level)
|
221
227
|
end
|
222
228
|
|
223
|
-
def trace_info(message, level
|
229
|
+
def trace_info(message, level: 0)
|
224
230
|
return unless @show_trace
|
225
231
|
|
226
|
-
|
232
|
+
@stderr.puts indent_text("TRACE: #{message}", level)
|
227
233
|
end
|
228
234
|
|
229
|
-
def error_message(message, backtrace
|
230
|
-
|
235
|
+
def error_message(message, backtrace: nil)
|
236
|
+
@stderr.puts Console.color(:red, "ERROR: #{message}")
|
231
237
|
|
232
238
|
return unless backtrace && @show_debug
|
233
239
|
|
234
240
|
backtrace.first(3).each do |line|
|
235
|
-
|
241
|
+
@stderr.puts indent_text(line.chomp, 1)
|
236
242
|
end
|
237
243
|
end
|
238
244
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
case style
|
246
|
-
when :heavy
|
247
|
-
io.puts '=' * 50
|
248
|
-
when :light
|
249
|
-
io.puts '-' * 50
|
250
|
-
when :dotted
|
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)"
|
264
|
-
else
|
265
|
-
" (#{elapsed_time.round(2)}s)"
|
266
|
-
end
|
245
|
+
def live_status_capabilities
|
246
|
+
{
|
247
|
+
supports_coordination: true, # Compact can work with coordinated output
|
248
|
+
output_frequency: :medium, # Outputs at medium frequency
|
249
|
+
requires_tty: false, # Works without TTY
|
250
|
+
}
|
267
251
|
end
|
268
252
|
end
|
269
253
|
|
@@ -279,6 +263,14 @@ class Tryouts
|
|
279
263
|
|
280
264
|
super
|
281
265
|
end
|
266
|
+
|
267
|
+
def live_status_capabilities
|
268
|
+
{
|
269
|
+
supports_coordination: true, # Compact can work with coordinated output
|
270
|
+
output_frequency: :low, # Outputs infrequently, mainly summaries
|
271
|
+
requires_tty: false, # Works without TTY
|
272
|
+
}
|
273
|
+
end
|
282
274
|
end
|
283
275
|
end
|
284
276
|
end
|
@@ -1,19 +1,22 @@
|
|
1
1
|
# lib/tryouts/cli/formatters/factory.rb
|
2
2
|
|
3
|
+
require_relative '../tty_detector'
|
4
|
+
|
3
5
|
class Tryouts
|
4
6
|
class CLI
|
5
7
|
# Factory for creating formatters and output managers
|
6
8
|
class FormatterFactory
|
7
9
|
def self.create_output_manager(options = {})
|
8
10
|
formatter = create_formatter(options)
|
9
|
-
OutputManager.new(formatter)
|
11
|
+
OutputManager.new(formatter, options)
|
10
12
|
end
|
11
13
|
|
12
14
|
def self.create_formatter(options = {})
|
13
15
|
# Map boolean flags to format symbols if format not explicitly set
|
14
16
|
format = options[:format]&.to_sym || determine_format_from_flags(options)
|
15
17
|
|
16
|
-
|
18
|
+
# Create base formatter first
|
19
|
+
base_formatter = case format
|
17
20
|
when :verbose
|
18
21
|
if options[:fails_only]
|
19
22
|
VerboseFailsFormatter.new(options)
|
@@ -35,6 +38,9 @@ class Tryouts
|
|
35
38
|
else
|
36
39
|
CompactFormatter.new(options) # Default to compact
|
37
40
|
end
|
41
|
+
|
42
|
+
# Return base formatter - live status is now handled by OutputManager/LiveStatusManager
|
43
|
+
base_formatter
|
38
44
|
end
|
39
45
|
|
40
46
|
class << self
|