tryouts 3.1.2 → 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/lib/tryouts/cli/formatters/base.rb +109 -47
- data/lib/tryouts/cli/formatters/compact.rb +98 -107
- 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 +53 -101
- 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 +99 -103
- 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/failure_collector.rb +109 -0
- data/lib/tryouts/test_batch.rb +9 -5
- data/lib/tryouts/test_runner.rb +11 -0
- data/lib/tryouts/version.rb +1 -1
- metadata +22 -3
@@ -0,0 +1,138 @@
|
|
1
|
+
# lib/tryouts/cli/formatters/live_status_manager.rb
|
2
|
+
|
3
|
+
require_relative 'test_run_state'
|
4
|
+
require_relative 'tty_status_display'
|
5
|
+
|
6
|
+
class Tryouts
|
7
|
+
class CLI
|
8
|
+
# Centralized manager for live status display across all formatters
|
9
|
+
# Replaces the decorator pattern with native integration
|
10
|
+
class LiveStatusManager
|
11
|
+
def initialize(formatter, options = {})
|
12
|
+
@formatter = formatter
|
13
|
+
@enabled = should_enable_live_status?(formatter, options)
|
14
|
+
@show_debug = options.fetch(:debug, false)
|
15
|
+
|
16
|
+
return unless @enabled
|
17
|
+
|
18
|
+
# Initialize state tracking and display
|
19
|
+
@state = TestRunState.empty
|
20
|
+
@display = TTYStatusDisplay.new(@formatter.stdout, options)
|
21
|
+
@status_reserved = false
|
22
|
+
|
23
|
+
debug_log('LiveStatusManager: Enabled with native integration')
|
24
|
+
end
|
25
|
+
|
26
|
+
def enabled?
|
27
|
+
@enabled
|
28
|
+
end
|
29
|
+
|
30
|
+
# Check if formatter and environment support live status
|
31
|
+
def should_enable_live_status?(formatter, options)
|
32
|
+
# Must be explicitly requested
|
33
|
+
return false unless options[:live_status] || options[:live]
|
34
|
+
|
35
|
+
# Check formatter capabilities
|
36
|
+
capabilities = formatter.live_status_capabilities
|
37
|
+
return false unless capabilities[:supports_coordination]
|
38
|
+
|
39
|
+
# Check TTY availability
|
40
|
+
require_relative '../tty_detector'
|
41
|
+
tty_check = TTYDetector.check_tty_support(debug: options[:debug])
|
42
|
+
|
43
|
+
unless tty_check[:available]
|
44
|
+
debug_log("Live status disabled: #{tty_check[:reason]}")
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Main event handling - called by OutputManager for each formatter event
|
52
|
+
def handle_event(event_type, *args, **)
|
53
|
+
return unless @enabled
|
54
|
+
|
55
|
+
# Update state based on the event
|
56
|
+
@state = @state.update_from_event(event_type, *args, **)
|
57
|
+
|
58
|
+
# Handle special events that need display coordination
|
59
|
+
case event_type
|
60
|
+
when :phase_header
|
61
|
+
message, file_count, level = args
|
62
|
+
if level == 0 && message.include?('PROCESSING') && file_count
|
63
|
+
reserve_status_area
|
64
|
+
end
|
65
|
+
when :file_start, :file_end, :test_result
|
66
|
+
update_display
|
67
|
+
when :batch_summary
|
68
|
+
# Clear status area before showing batch summary to avoid interference
|
69
|
+
clear_status_area
|
70
|
+
when :grand_total
|
71
|
+
# Ensure status area is cleared (redundant safety check)
|
72
|
+
clear_status_area if @status_reserved
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Allow formatter to directly update live status (optional integration point)
|
77
|
+
def update_status(state_updates = {})
|
78
|
+
return unless @enabled
|
79
|
+
|
80
|
+
@state = @state.with(**state_updates) unless state_updates.empty?
|
81
|
+
update_display
|
82
|
+
end
|
83
|
+
|
84
|
+
# Output coordination methods
|
85
|
+
def write_output
|
86
|
+
return yield unless @enabled
|
87
|
+
|
88
|
+
# If status area is reserved, coordinate the output
|
89
|
+
if @status_reserved
|
90
|
+
@display.write_scrolling(yield)
|
91
|
+
else
|
92
|
+
yield
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_string(text)
|
97
|
+
return @formatter.stdout.print(text) unless @enabled
|
98
|
+
|
99
|
+
if @status_reserved
|
100
|
+
@display.write_scrolling(text)
|
101
|
+
else
|
102
|
+
@formatter.stdout.print(text)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def reserve_status_area
|
109
|
+
return unless @enabled && @display.available?
|
110
|
+
|
111
|
+
debug_log('Reserving status area for live display')
|
112
|
+
@display.reserve_status_area
|
113
|
+
@status_reserved = true
|
114
|
+
update_display
|
115
|
+
end
|
116
|
+
|
117
|
+
def update_display
|
118
|
+
return unless @enabled && @status_reserved
|
119
|
+
|
120
|
+
@display.update_status(@state)
|
121
|
+
end
|
122
|
+
|
123
|
+
def clear_status_area
|
124
|
+
return unless @enabled && @status_reserved
|
125
|
+
|
126
|
+
debug_log('Clearing status area for final output')
|
127
|
+
@display.clear_status_area
|
128
|
+
@status_reserved = false
|
129
|
+
end
|
130
|
+
|
131
|
+
def debug_log(message)
|
132
|
+
return unless @show_debug
|
133
|
+
|
134
|
+
@formatter.stderr.puts "DEBUG: #{message}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -1,91 +1,114 @@
|
|
1
1
|
# lib/tryouts/cli/formatters/output_manager.rb
|
2
2
|
|
3
|
+
require_relative 'live_status_manager'
|
4
|
+
|
3
5
|
class Tryouts
|
4
6
|
class CLI
|
5
7
|
# Output manager that coordinates all output through formatters
|
6
8
|
class OutputManager
|
7
|
-
attr_reader :formatter
|
9
|
+
attr_reader :formatter, :live_status_manager
|
10
|
+
|
11
|
+
def initialize(formatter, options = {})
|
12
|
+
@formatter = formatter
|
13
|
+
@live_status_manager = LiveStatusManager.new(formatter, options)
|
8
14
|
|
9
|
-
|
10
|
-
@formatter
|
11
|
-
@indent_level = 0
|
15
|
+
# Connect the formatter to the live status manager
|
16
|
+
@formatter.set_live_status_manager(@live_status_manager)
|
12
17
|
end
|
13
18
|
|
14
19
|
# Phase-level methods
|
15
|
-
def processing_phase(file_count
|
16
|
-
|
20
|
+
def processing_phase(file_count)
|
21
|
+
message = "PROCESSING #{file_count} FILES"
|
22
|
+
@live_status_manager.handle_event(:phase_header, message, file_count, 0)
|
23
|
+
@formatter.phase_header(message, file_count: file_count)
|
17
24
|
end
|
18
25
|
|
19
|
-
def execution_phase(test_count
|
20
|
-
|
26
|
+
def execution_phase(test_count)
|
27
|
+
message = "EXECUTING #{test_count} TESTS"
|
28
|
+
@live_status_manager.handle_event(:phase_header, message, test_count, 1)
|
29
|
+
@formatter.phase_header(message, file_count: test_count)
|
21
30
|
end
|
22
31
|
|
23
|
-
def error_phase
|
24
|
-
|
32
|
+
def error_phase
|
33
|
+
message = 'ERROR DETAILS'
|
34
|
+
@live_status_manager.handle_event(:phase_header, message, nil, 2)
|
35
|
+
@formatter.phase_header(message)
|
25
36
|
end
|
26
37
|
|
27
38
|
# File-level methods
|
28
39
|
def file_start(file_path, framework: :direct, context: :fresh)
|
29
40
|
context_info = { framework: framework, context: context }
|
30
|
-
@
|
41
|
+
@live_status_manager.handle_event(:file_start, file_path, context_info)
|
42
|
+
@formatter.file_start(file_path, context_info: context_info)
|
31
43
|
end
|
32
44
|
|
33
45
|
def file_end(file_path, framework: :direct, context: :fresh)
|
34
46
|
context_info = { framework: framework, context: context }
|
35
|
-
@
|
47
|
+
@live_status_manager.handle_event(:file_end, file_path, context_info)
|
48
|
+
@formatter.file_end(file_path, context_info: context_info)
|
36
49
|
end
|
37
50
|
|
38
51
|
def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
@formatter.file_parsed(
|
53
|
+
file_path,
|
54
|
+
test_count: test_count,
|
55
|
+
setup_present: setup_present,
|
56
|
+
teardown_present: teardown_present
|
57
|
+
)
|
45
58
|
end
|
46
59
|
|
47
60
|
def file_execution_start(file_path, test_count, context_mode)
|
48
|
-
@formatter.file_execution_start(
|
61
|
+
@formatter.file_execution_start(
|
62
|
+
file_path,
|
63
|
+
test_count: test_count,
|
64
|
+
context_mode: context_mode
|
65
|
+
)
|
49
66
|
end
|
50
67
|
|
51
68
|
def file_success(file_path, total_tests, failed_count, error_count, elapsed_time)
|
52
|
-
|
53
|
-
|
54
|
-
|
69
|
+
@formatter.file_result(
|
70
|
+
file_path,
|
71
|
+
total_tests: total_tests,
|
72
|
+
failed_count: failed_count,
|
73
|
+
error_count: error_count,
|
74
|
+
elapsed_time: elapsed_time
|
75
|
+
)
|
55
76
|
end
|
56
77
|
|
57
78
|
def file_failure(file_path, error_message, backtrace = nil)
|
58
|
-
|
59
|
-
|
60
|
-
|
79
|
+
@formatter.error_message(
|
80
|
+
"#{Console.pretty_path(file_path)}: #{error_message}",
|
81
|
+
backtrace: backtrace
|
82
|
+
)
|
61
83
|
end
|
62
84
|
|
63
85
|
# Test-level methods
|
64
86
|
def test_start(test_case, index, total)
|
65
|
-
|
66
|
-
|
67
|
-
end
|
87
|
+
@live_status_manager.handle_event(:test_start, test_case, index, total)
|
88
|
+
@formatter.test_start(test_case: test_case, index: index, total: total)
|
68
89
|
end
|
69
90
|
|
70
91
|
def test_end(test_case, index, total)
|
71
|
-
|
72
|
-
|
73
|
-
end
|
92
|
+
@live_status_manager.handle_event(:test_end, test_case, index, total)
|
93
|
+
@formatter.test_end(test_case: test_case, index: index, total: total)
|
74
94
|
end
|
75
95
|
|
76
96
|
def test_result(result_packet)
|
97
|
+
@live_status_manager.handle_event(:test_result, result_packet)
|
77
98
|
@formatter.test_result(result_packet)
|
78
99
|
end
|
79
100
|
|
80
|
-
def test_output(test_case, output_text)
|
81
|
-
@formatter.test_output(
|
101
|
+
def test_output(test_case, output_text, result_packet)
|
102
|
+
@formatter.test_output(
|
103
|
+
test_case: test_case,
|
104
|
+
output_text: output_text,
|
105
|
+
result_packet: result_packet
|
106
|
+
)
|
82
107
|
end
|
83
108
|
|
84
109
|
# Setup/teardown methods
|
85
110
|
def setup_start(line_range)
|
86
|
-
|
87
|
-
@formatter.setup_start(line_range)
|
88
|
-
end
|
111
|
+
@formatter.setup_start(line_range: line_range)
|
89
112
|
end
|
90
113
|
|
91
114
|
def setup_output(output_text)
|
@@ -93,9 +116,7 @@ class Tryouts
|
|
93
116
|
end
|
94
117
|
|
95
118
|
def teardown_start(line_range)
|
96
|
-
|
97
|
-
@formatter.teardown_start(line_range)
|
98
|
-
end
|
119
|
+
@formatter.teardown_start(line_range: line_range)
|
99
120
|
end
|
100
121
|
|
101
122
|
def teardown_output(output_text)
|
@@ -103,48 +124,39 @@ class Tryouts
|
|
103
124
|
end
|
104
125
|
|
105
126
|
# Summary methods
|
106
|
-
def batch_summary(
|
107
|
-
@
|
127
|
+
def batch_summary(failure_collector)
|
128
|
+
@live_status_manager.handle_event(:batch_summary, failure_collector)
|
129
|
+
@formatter.batch_summary(failure_collector)
|
108
130
|
end
|
109
131
|
|
110
132
|
def grand_total(total_tests, failed_count, error_count, successful_files, total_files, elapsed_time)
|
111
|
-
@
|
133
|
+
@live_status_manager.handle_event(:grand_total, total_tests, failed_count, error_count, successful_files, total_files, elapsed_time)
|
134
|
+
@formatter.grand_total(
|
135
|
+
total_tests: total_tests,
|
136
|
+
failed_count: failed_count,
|
137
|
+
error_count: error_count,
|
138
|
+
successful_files: successful_files,
|
139
|
+
total_files: total_files,
|
140
|
+
elapsed_time: elapsed_time
|
141
|
+
)
|
112
142
|
end
|
113
143
|
|
114
144
|
# Debug methods
|
115
145
|
def info(message, level = 0)
|
116
|
-
|
117
|
-
@formatter.debug_info(message, level)
|
118
|
-
end
|
146
|
+
@formatter.debug_info(message, level: level)
|
119
147
|
end
|
120
148
|
|
121
149
|
def trace(message, level = 0)
|
122
|
-
|
123
|
-
@formatter.trace_info(message, level)
|
124
|
-
end
|
150
|
+
@formatter.trace_info(message, level: level)
|
125
151
|
end
|
126
152
|
|
127
153
|
def error(message, backtrace = nil)
|
128
|
-
@formatter.error_message(message, backtrace)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Utility methods
|
132
|
-
def raw(text)
|
133
|
-
@formatter.raw_output(text)
|
154
|
+
@formatter.error_message(message, backtrace: backtrace)
|
134
155
|
end
|
135
156
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def with_indent(level)
|
143
|
-
old_level = @indent_level
|
144
|
-
@indent_level = level
|
145
|
-
yield
|
146
|
-
ensure
|
147
|
-
@indent_level = old_level
|
157
|
+
# Raw output method (bypasses formatting)
|
158
|
+
def raw(message)
|
159
|
+
@formatter.stdout.puts(message)
|
148
160
|
end
|
149
161
|
end
|
150
162
|
end
|
@@ -7,102 +7,50 @@ class Tryouts
|
|
7
7
|
include FormatterInterface
|
8
8
|
|
9
9
|
def initialize(options = {})
|
10
|
+
super
|
10
11
|
@show_errors = options.fetch(:show_errors, true)
|
11
12
|
@show_final_summary = options.fetch(:show_final_summary, true)
|
12
13
|
@current_file = nil
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
def phase_header(message, file_count = nil, level = nil)
|
17
|
-
# Silent in quiet mode
|
18
|
-
end
|
19
|
-
|
20
|
-
# File-level operations - minimal
|
21
|
-
def file_start(file_path, context_info = {})
|
22
|
-
# Silent in quiet mode
|
23
|
-
end
|
24
|
-
|
25
|
-
def file_end(_file_path, _context_info = {}, io = $stderr)
|
26
|
-
io.puts # add newline after all dots
|
27
|
-
end
|
28
|
-
|
29
|
-
def file_parsed(file_path, test_count, setup_present: false, teardown_present: false)
|
30
|
-
# Silent in quiet mode
|
31
|
-
end
|
32
|
-
|
33
|
-
def file_execution_start(file_path, _test_count, _context_mode)
|
16
|
+
def file_execution_start(file_path, test_count:, context_mode:)
|
34
17
|
@current_file = file_path
|
35
18
|
end
|
36
19
|
|
37
|
-
def
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
# Test-level operations - dot notation
|
42
|
-
def test_start(test_case, index, total)
|
43
|
-
# Silent in quiet mode
|
44
|
-
end
|
45
|
-
|
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
|
52
|
-
when :passed
|
53
|
-
io.print Console.color(:green, '.')
|
54
|
-
when :failed
|
55
|
-
io.print Console.color(:red, 'F')
|
56
|
-
when :error
|
57
|
-
io.print Console.color(:red, 'E')
|
58
|
-
when :skipped
|
59
|
-
io.print Console.color(:yellow, 'S')
|
60
|
-
else
|
61
|
-
io.print '?'
|
62
|
-
end
|
63
|
-
io.flush
|
64
|
-
end
|
65
|
-
|
66
|
-
def test_output(test_case, output_text)
|
67
|
-
# Silent in quiet mode - could optionally show output for failed tests only
|
68
|
-
# For now, keeping it completely silent
|
69
|
-
end
|
70
|
-
|
71
|
-
# Setup/teardown operations - silent
|
72
|
-
def setup_start(line_range)
|
73
|
-
# Silent in quiet mode
|
74
|
-
end
|
75
|
-
|
76
|
-
def setup_output(output_text)
|
77
|
-
# Silent in quiet mode
|
78
|
-
end
|
79
|
-
|
80
|
-
def teardown_start(line_range)
|
81
|
-
# Silent in quiet mode
|
82
|
-
end
|
83
|
-
|
84
|
-
def teardown_output(output_text)
|
85
|
-
# Silent in quiet mode
|
86
|
-
end
|
87
|
-
|
88
|
-
# Summary operations - show results
|
89
|
-
def batch_summary(total_tests, failed_count, elapsed_time, io = $stderr)
|
90
|
-
return unless @show_final_summary
|
91
|
-
|
92
|
-
if failed_count > 0
|
93
|
-
passed = total_tests - failed_count
|
94
|
-
time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
|
95
|
-
io.puts "#{failed_count} failed, #{passed} passed#{time_str}"
|
96
|
-
else
|
97
|
-
time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
|
98
|
-
io.puts "#{total_tests} passed#{time_str}"
|
99
|
-
end
|
20
|
+
def file_end(_file_path, context_info: {})
|
21
|
+
# Always use coordinated output through puts() method
|
22
|
+
# puts # add newline after all dots
|
100
23
|
end
|
101
24
|
|
102
|
-
def
|
25
|
+
def test_result(result_packet)
|
26
|
+
char = case result_packet.status
|
27
|
+
when :passed
|
28
|
+
Console.color(:green, '.')
|
29
|
+
when :failed
|
30
|
+
Console.color(:red, 'F')
|
31
|
+
when :error
|
32
|
+
Console.color(:red, 'E')
|
33
|
+
when :skipped
|
34
|
+
Console.color(:yellow, 'S')
|
35
|
+
else
|
36
|
+
'?'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Always use coordinated output through write() method
|
40
|
+
write(char)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Summary operations - quiet mode skips failure summary
|
44
|
+
def batch_summary(failure_collector)
|
45
|
+
# Quiet formatter defaults to no failure summary
|
46
|
+
# Users can override with --failure-summary if needed
|
47
|
+
end
|
48
|
+
|
49
|
+
def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
|
103
50
|
return unless @show_final_summary
|
104
51
|
|
105
52
|
puts
|
53
|
+
puts # Add newline after dots
|
106
54
|
|
107
55
|
time_str = if elapsed_time < 2
|
108
56
|
"#{(elapsed_time * 1000).to_i}ms"
|
@@ -126,29 +74,25 @@ class Tryouts
|
|
126
74
|
end
|
127
75
|
end
|
128
76
|
|
129
|
-
|
130
|
-
def debug_info(message, level = 0)
|
131
|
-
# Silent in quiet mode
|
132
|
-
end
|
133
|
-
|
134
|
-
def trace_info(message, level = 0)
|
135
|
-
# Silent in quiet mode
|
136
|
-
end
|
137
|
-
|
138
|
-
def error_message(message, _details = nil)
|
77
|
+
def error_message(message, backtrace: nil)
|
139
78
|
return unless @show_errors
|
140
79
|
|
141
|
-
puts
|
142
|
-
puts Console.color(:red, "ERROR: #{message}")
|
143
|
-
|
80
|
+
@stderr.puts
|
81
|
+
@stderr.puts Console.color(:red, "ERROR: #{message}")
|
82
|
+
|
83
|
+
return unless backtrace && @show_debug
|
144
84
|
|
145
|
-
|
146
|
-
|
147
|
-
|
85
|
+
backtrace.first(3).each do |line|
|
86
|
+
@stderr.puts " #{line.chomp}"
|
87
|
+
end
|
148
88
|
end
|
149
89
|
|
150
|
-
def
|
151
|
-
|
90
|
+
def live_status_capabilities
|
91
|
+
{
|
92
|
+
supports_coordination: true, # Quiet can work with coordinated output
|
93
|
+
output_frequency: :low, # Very minimal output, mainly dots
|
94
|
+
requires_tty: false, # Works without TTY
|
95
|
+
}
|
152
96
|
end
|
153
97
|
end
|
154
98
|
|
@@ -160,6 +104,14 @@ class Tryouts
|
|
160
104
|
|
161
105
|
super
|
162
106
|
end
|
107
|
+
|
108
|
+
def live_status_capabilities
|
109
|
+
{
|
110
|
+
supports_coordination: true, # QuietFails can work with coordinated output
|
111
|
+
output_frequency: :low, # Very minimal output
|
112
|
+
requires_tty: false, # Works without TTY
|
113
|
+
}
|
114
|
+
end
|
163
115
|
end
|
164
116
|
end
|
165
117
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# lib/tryouts/cli/formatters/test_run_state.rb
|
2
|
+
|
3
|
+
class Tryouts
|
4
|
+
class CLI
|
5
|
+
# Immutable state tracking for test runs using modern Ruby Data.define
|
6
|
+
class TestRunState < Data.define(
|
7
|
+
:total_tests,
|
8
|
+
:passed,
|
9
|
+
:failed,
|
10
|
+
:errors,
|
11
|
+
:files_completed,
|
12
|
+
:total_files,
|
13
|
+
:current_file,
|
14
|
+
:current_test,
|
15
|
+
:start_time,
|
16
|
+
)
|
17
|
+
def self.empty
|
18
|
+
new(
|
19
|
+
total_tests: 0,
|
20
|
+
passed: 0,
|
21
|
+
failed: 0,
|
22
|
+
errors: 0,
|
23
|
+
files_completed: 0,
|
24
|
+
total_files: 0,
|
25
|
+
current_file: nil,
|
26
|
+
current_test: nil,
|
27
|
+
start_time: nil,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.initial(total_files: 0)
|
32
|
+
empty.with(
|
33
|
+
total_files: total_files,
|
34
|
+
start_time: Time.now,
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Update state based on formatter events using pattern matching
|
39
|
+
def update_from_event(event_type, *args, **_kwargs)
|
40
|
+
case event_type
|
41
|
+
in :phase_header
|
42
|
+
_, file_count, level = args
|
43
|
+
if level == 0 && file_count
|
44
|
+
with(total_files: file_count, start_time: Time.now)
|
45
|
+
else
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
in :file_start
|
50
|
+
file_path = args[0]
|
51
|
+
pretty_path = Console.pretty_path(file_path)
|
52
|
+
with(current_file: pretty_path)
|
53
|
+
|
54
|
+
in :file_end
|
55
|
+
with(
|
56
|
+
files_completed: files_completed + 1,
|
57
|
+
current_file: nil,
|
58
|
+
)
|
59
|
+
|
60
|
+
in :test_start
|
61
|
+
test_case = args[0]
|
62
|
+
desc = test_case.description.to_s
|
63
|
+
desc = "test #{args[1]}" if desc.empty?
|
64
|
+
with(current_test: desc)
|
65
|
+
|
66
|
+
in :test_end
|
67
|
+
with(current_test: nil)
|
68
|
+
|
69
|
+
in :test_result
|
70
|
+
result_packet = args[0]
|
71
|
+
updated_state = with(total_tests: total_tests + 1)
|
72
|
+
|
73
|
+
case result_packet.status
|
74
|
+
when :passed
|
75
|
+
updated_state.with(passed: passed + 1)
|
76
|
+
when :failed
|
77
|
+
updated_state.with(failed: failed + 1)
|
78
|
+
when :error
|
79
|
+
updated_state.with(errors: errors + 1)
|
80
|
+
else
|
81
|
+
updated_state
|
82
|
+
end
|
83
|
+
|
84
|
+
else
|
85
|
+
# Unknown event, return unchanged state
|
86
|
+
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Computed properties
|
91
|
+
def elapsed_time
|
92
|
+
start_time ? Time.now - start_time : 0
|
93
|
+
end
|
94
|
+
|
95
|
+
def issues_count
|
96
|
+
failed + errors
|
97
|
+
end
|
98
|
+
|
99
|
+
def passed_count
|
100
|
+
total_tests - issues_count
|
101
|
+
end
|
102
|
+
|
103
|
+
def has_issues?
|
104
|
+
issues_count > 0
|
105
|
+
end
|
106
|
+
|
107
|
+
def tests_run?
|
108
|
+
total_tests > 0
|
109
|
+
end
|
110
|
+
|
111
|
+
def files_remaining
|
112
|
+
total_files - files_completed
|
113
|
+
end
|
114
|
+
|
115
|
+
def completion_percentage
|
116
|
+
return 0 if total_files == 0
|
117
|
+
|
118
|
+
(files_completed.to_f / total_files * 100).round(1)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|