tryouts 3.3.1 → 3.4.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/README.md +15 -4
- data/lib/tryouts/cli/formatters/agent.rb +450 -0
- data/lib/tryouts/cli/formatters/compact.rb +4 -3
- data/lib/tryouts/cli/formatters/factory.rb +5 -0
- data/lib/tryouts/cli/formatters/quiet.rb +4 -3
- data/lib/tryouts/cli/formatters/token_budget.rb +157 -0
- data/lib/tryouts/cli/formatters/verbose.rb +3 -2
- data/lib/tryouts/cli/formatters.rb +2 -0
- data/lib/tryouts/cli/opts.rb +86 -9
- data/lib/tryouts/console.rb +32 -4
- data/lib/tryouts/expectation_evaluators/exception.rb +8 -2
- data/lib/tryouts/expectation_evaluators/non_nil.rb +77 -0
- data/lib/tryouts/expectation_evaluators/registry.rb +2 -0
- data/lib/tryouts/file_processor.rb +6 -2
- data/lib/tryouts/parsers/enhanced_parser.rb +2 -0
- data/lib/tryouts/parsers/prism_parser.rb +2 -0
- data/lib/tryouts/parsers/shared_methods.rb +5 -1
- data/lib/tryouts/test_batch.rb +26 -10
- data/lib/tryouts/test_case.rb +3 -3
- data/lib/tryouts/test_executor.rb +6 -4
- data/lib/tryouts/test_result_aggregator.rb +138 -0
- data/lib/tryouts/test_runner.rb +81 -20
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +5 -1
- metadata +19 -1
@@ -0,0 +1,138 @@
|
|
1
|
+
# lib/tryouts/test_result_aggregator.rb
|
2
|
+
|
3
|
+
require_relative 'failure_collector'
|
4
|
+
require 'concurrent'
|
5
|
+
|
6
|
+
class Tryouts
|
7
|
+
# Centralized test result aggregation to ensure counting consistency
|
8
|
+
# across all formatters and eliminate counting discrepancies
|
9
|
+
class TestResultAggregator
|
10
|
+
def initialize
|
11
|
+
@failure_collector = FailureCollector.new
|
12
|
+
# Use thread-safe atomic counters
|
13
|
+
@test_counts = {
|
14
|
+
total_tests: Concurrent::AtomicFixnum.new(0),
|
15
|
+
passed: Concurrent::AtomicFixnum.new(0),
|
16
|
+
failed: Concurrent::AtomicFixnum.new(0),
|
17
|
+
errors: Concurrent::AtomicFixnum.new(0)
|
18
|
+
}
|
19
|
+
@infrastructure_failures = Concurrent::Array.new
|
20
|
+
@file_counts = {
|
21
|
+
total: Concurrent::AtomicFixnum.new(0),
|
22
|
+
successful: Concurrent::AtomicFixnum.new(0)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :failure_collector
|
27
|
+
|
28
|
+
# Add a test-level result (from individual test execution)
|
29
|
+
def add_test_result(file_path, result_packet)
|
30
|
+
@test_counts[:total_tests].increment
|
31
|
+
|
32
|
+
if result_packet.passed?
|
33
|
+
@test_counts[:passed].increment
|
34
|
+
elsif result_packet.failed?
|
35
|
+
@test_counts[:failed].increment
|
36
|
+
@failure_collector.add_failure(file_path, result_packet)
|
37
|
+
elsif result_packet.error?
|
38
|
+
@test_counts[:errors].increment
|
39
|
+
@failure_collector.add_failure(file_path, result_packet)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add an infrastructure-level failure (setup, teardown, file-level)
|
44
|
+
def add_infrastructure_failure(type, file_path, error_message, exception = nil)
|
45
|
+
@infrastructure_failures << {
|
46
|
+
type: type, # :setup, :teardown, :file_processing
|
47
|
+
file_path: file_path,
|
48
|
+
error_message: error_message,
|
49
|
+
exception: exception
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Atomic increment methods for file-level operations
|
54
|
+
def increment_total_files
|
55
|
+
@file_counts[:total].increment
|
56
|
+
end
|
57
|
+
|
58
|
+
def increment_successful_files
|
59
|
+
@file_counts[:successful].increment
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Get counts that should be displayed in numbered failure lists
|
64
|
+
# These match what actually appears in the failure summary
|
65
|
+
def get_display_counts
|
66
|
+
{
|
67
|
+
total_tests: @test_counts[:total_tests].value,
|
68
|
+
passed: @test_counts[:passed].value,
|
69
|
+
failed: @failure_collector.failure_count,
|
70
|
+
errors: @failure_collector.error_count,
|
71
|
+
total_issues: @failure_collector.total_issues
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get total counts including infrastructure failures
|
76
|
+
# These represent all issues that occurred during test execution
|
77
|
+
def get_total_counts
|
78
|
+
display = get_display_counts
|
79
|
+
{
|
80
|
+
total_tests: display[:total_tests],
|
81
|
+
passed: display[:passed],
|
82
|
+
failed: display[:failed],
|
83
|
+
errors: display[:errors],
|
84
|
+
infrastructure_failures: @infrastructure_failures.size,
|
85
|
+
total_issues: display[:total_issues] + @infrastructure_failures.size
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get file-level statistics
|
90
|
+
def get_file_counts
|
91
|
+
{
|
92
|
+
total: @file_counts[:total].value,
|
93
|
+
successful: @file_counts[:successful].value
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get infrastructure failures for detailed reporting
|
98
|
+
def get_infrastructure_failures
|
99
|
+
@infrastructure_failures.dup
|
100
|
+
end
|
101
|
+
|
102
|
+
# Check if there are any failures at all
|
103
|
+
def any_failures?
|
104
|
+
@failure_collector.any_failures? || !@infrastructure_failures.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check if there are displayable failures (for numbered lists)
|
108
|
+
def any_display_failures?
|
109
|
+
@failure_collector.any_failures?
|
110
|
+
end
|
111
|
+
|
112
|
+
# Reset for testing purposes
|
113
|
+
def clear
|
114
|
+
@failure_collector.clear
|
115
|
+
@test_counts[:total_tests].update { |_| 0 }
|
116
|
+
@test_counts[:passed].update { |_| 0 }
|
117
|
+
@test_counts[:failed].update { |_| 0 }
|
118
|
+
@test_counts[:errors].update { |_| 0 }
|
119
|
+
@infrastructure_failures.clear
|
120
|
+
@file_counts[:total].update { |_| 0 }
|
121
|
+
@file_counts[:successful].update { |_| 0 }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Provide a summary string for debugging
|
125
|
+
def summary
|
126
|
+
display = get_display_counts
|
127
|
+
total = get_total_counts
|
128
|
+
|
129
|
+
parts = []
|
130
|
+
parts << "#{display[:passed]} passed" if display[:passed] > 0
|
131
|
+
parts << "#{display[:failed]} failed" if display[:failed] > 0
|
132
|
+
parts << "#{display[:errors]} errors" if display[:errors] > 0
|
133
|
+
parts << "#{total[:infrastructure_failures]} infrastructure failures" if total[:infrastructure_failures] > 0
|
134
|
+
|
135
|
+
parts.empty? ? "All tests passed" : parts.join(', ')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/tryouts/test_runner.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# lib/tryouts/test_runner.rb
|
2
2
|
|
3
|
+
require 'concurrent'
|
3
4
|
require_relative 'parsers/prism_parser'
|
4
5
|
require_relative 'parsers/enhanced_parser'
|
5
6
|
require_relative 'test_batch'
|
@@ -7,6 +8,7 @@ require_relative 'translators/rspec_translator'
|
|
7
8
|
require_relative 'translators/minitest_translator'
|
8
9
|
require_relative 'file_processor'
|
9
10
|
require_relative 'failure_collector'
|
11
|
+
require_relative 'test_result_aggregator'
|
10
12
|
|
11
13
|
class Tryouts
|
12
14
|
class TestRunner
|
@@ -35,8 +37,14 @@ class Tryouts
|
|
35
37
|
|
36
38
|
result = process_files
|
37
39
|
show_failure_summary
|
38
|
-
show_grand_total if @global_tally[:
|
39
|
-
|
40
|
+
show_grand_total if @global_tally[:aggregator].get_file_counts[:total] > 1
|
41
|
+
|
42
|
+
# For agent critical mode, only count errors as failures
|
43
|
+
if @options[:agent] && (@options[:agent_focus] == :critical || @options[:agent_focus] == 'critical')
|
44
|
+
@global_tally[:aggregator].get_display_counts[:errors]
|
45
|
+
else
|
46
|
+
result
|
47
|
+
end
|
40
48
|
end
|
41
49
|
|
42
50
|
private
|
@@ -70,17 +78,20 @@ class Tryouts
|
|
70
78
|
|
71
79
|
def initialize_global_tally
|
72
80
|
{
|
73
|
-
total_tests: 0,
|
74
|
-
total_failed: 0,
|
75
|
-
total_errors: 0,
|
76
|
-
file_count: 0,
|
77
81
|
start_time: Time.now,
|
78
|
-
|
79
|
-
failure_collector: FailureCollector.new,
|
82
|
+
aggregator: TestResultAggregator.new,
|
80
83
|
}
|
81
84
|
end
|
82
85
|
|
83
86
|
def process_files
|
87
|
+
if @options[:parallel] && @files.length > 1
|
88
|
+
process_files_parallel
|
89
|
+
else
|
90
|
+
process_files_sequential
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def process_files_sequential
|
84
95
|
failure_count = 0
|
85
96
|
|
86
97
|
@files.each_with_index do |file, _idx|
|
@@ -93,37 +104,87 @@ class Tryouts
|
|
93
104
|
failure_count
|
94
105
|
end
|
95
106
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
107
|
+
def process_files_parallel
|
108
|
+
# Determine thread pool size
|
109
|
+
pool_size = @options[:parallel_threads] || Concurrent.processor_count
|
110
|
+
@output_manager.info "Running #{@files.length} files in parallel (#{pool_size} threads)", 1
|
111
|
+
|
112
|
+
# Create thread pool executor
|
113
|
+
executor = Concurrent::ThreadPoolExecutor.new(
|
114
|
+
min_threads: 1,
|
115
|
+
max_threads: pool_size,
|
116
|
+
max_queue: @files.length, # Queue size must accommodate all files
|
117
|
+
fallback_policy: :abort # Raise exception if pool and queue are exhausted
|
118
|
+
)
|
119
|
+
|
120
|
+
# Submit all file processing tasks to the thread pool
|
121
|
+
futures = @files.map do |file|
|
122
|
+
Concurrent::Future.execute(executor: executor) do
|
123
|
+
process_file(file)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Wait for all tasks to complete and collect results
|
128
|
+
failure_count = 0
|
129
|
+
futures.each_with_index do |future, idx|
|
130
|
+
begin
|
131
|
+
result = future.value # This blocks until the future completes
|
132
|
+
failure_count += result unless result.zero?
|
133
|
+
|
134
|
+
status = result.zero? ? Console.color(:green, 'PASS') : Console.color(:red, 'FAIL')
|
135
|
+
file = @files[idx]
|
136
|
+
@output_manager.info "#{status} #{Console.pretty_path(file)} (#{result} failures)", 1
|
137
|
+
rescue StandardError => ex
|
138
|
+
failure_count += 1
|
139
|
+
file = @files[idx]
|
140
|
+
@output_manager.info "#{Console.color(:red, 'ERROR')} #{Console.pretty_path(file)} (#{ex.message})", 1
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Shutdown the thread pool
|
145
|
+
executor.shutdown
|
146
|
+
executor.wait_for_termination(10) # Wait up to 10 seconds for clean shutdown
|
147
|
+
|
148
|
+
failure_count
|
149
|
+
end
|
150
|
+
|
151
|
+
def process_file(file_path)
|
152
|
+
processor = FileProcessor.new(
|
153
|
+
file: file_path,
|
99
154
|
options: @options,
|
100
155
|
output_manager: @output_manager,
|
101
156
|
translator: @translator,
|
102
157
|
global_tally: @global_tally,
|
103
158
|
)
|
104
|
-
|
159
|
+
processor.process
|
105
160
|
rescue StandardError => ex
|
106
161
|
handle_file_error(ex)
|
107
|
-
@global_tally[:
|
162
|
+
@global_tally[:aggregator].add_infrastructure_failure(
|
163
|
+
:file_processing, file_path, ex.message, ex
|
164
|
+
)
|
108
165
|
1
|
109
166
|
end
|
110
167
|
|
111
168
|
def show_failure_summary
|
112
169
|
# Show failure summary if any failures exist
|
113
|
-
|
114
|
-
|
170
|
+
aggregator = @global_tally[:aggregator]
|
171
|
+
if aggregator.any_display_failures?
|
172
|
+
@output_manager.batch_summary(aggregator.failure_collector)
|
115
173
|
end
|
116
174
|
end
|
117
175
|
|
118
176
|
def show_grand_total
|
119
177
|
elapsed_time = Time.now - @global_tally[:start_time]
|
178
|
+
aggregator = @global_tally[:aggregator]
|
179
|
+
display_counts = aggregator.get_display_counts
|
180
|
+
file_counts = aggregator.get_file_counts
|
120
181
|
|
121
182
|
@output_manager.grand_total(
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
183
|
+
display_counts[:total_tests],
|
184
|
+
display_counts[:failed],
|
185
|
+
display_counts[:errors],
|
186
|
+
file_counts[:successful],
|
187
|
+
file_counts[:total],
|
127
188
|
elapsed_time,
|
128
189
|
)
|
129
190
|
end
|
data/lib/tryouts/version.rb
CHANGED
data/lib/tryouts.rb
CHANGED
@@ -23,13 +23,17 @@ class Tryouts
|
|
23
23
|
|
24
24
|
module ClassMethods
|
25
25
|
attr_accessor :container, :quiet, :noisy, :fails
|
26
|
-
attr_writer :debug
|
26
|
+
attr_writer :debug, :stack_traces
|
27
27
|
attr_reader :cases, :testcase_io
|
28
28
|
|
29
29
|
def debug?
|
30
30
|
@debug == true
|
31
31
|
end
|
32
32
|
|
33
|
+
def stack_traces?
|
34
|
+
@stack_traces == true || debug? # Debug mode auto-enables stack traces
|
35
|
+
end
|
36
|
+
|
33
37
|
def update_load_path(lib_glob)
|
34
38
|
Dir.glob(lib_glob).each { |dir| $LOAD_PATH.unshift(dir) }
|
35
39
|
end
|
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.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '1.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: concurrent-ruby
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: minitest
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,6 +137,7 @@ files:
|
|
123
137
|
- lib/tryouts.rb
|
124
138
|
- lib/tryouts/cli.rb
|
125
139
|
- lib/tryouts/cli/formatters.rb
|
140
|
+
- lib/tryouts/cli/formatters/agent.rb
|
126
141
|
- lib/tryouts/cli/formatters/base.rb
|
127
142
|
- lib/tryouts/cli/formatters/compact.rb
|
128
143
|
- lib/tryouts/cli/formatters/factory.rb
|
@@ -130,6 +145,7 @@ files:
|
|
130
145
|
- lib/tryouts/cli/formatters/output_manager.rb
|
131
146
|
- lib/tryouts/cli/formatters/quiet.rb
|
132
147
|
- lib/tryouts/cli/formatters/test_run_state.rb
|
148
|
+
- lib/tryouts/cli/formatters/token_budget.rb
|
133
149
|
- lib/tryouts/cli/formatters/tty_status_display.rb
|
134
150
|
- lib/tryouts/cli/formatters/verbose.rb
|
135
151
|
- lib/tryouts/cli/modes/generate.rb
|
@@ -143,6 +159,7 @@ files:
|
|
143
159
|
- lib/tryouts/expectation_evaluators/expectation_result.rb
|
144
160
|
- lib/tryouts/expectation_evaluators/false.rb
|
145
161
|
- lib/tryouts/expectation_evaluators/intentional_failure.rb
|
162
|
+
- lib/tryouts/expectation_evaluators/non_nil.rb
|
146
163
|
- lib/tryouts/expectation_evaluators/output.rb
|
147
164
|
- lib/tryouts/expectation_evaluators/performance_time.rb
|
148
165
|
- lib/tryouts/expectation_evaluators/regex_match.rb
|
@@ -159,6 +176,7 @@ files:
|
|
159
176
|
- lib/tryouts/test_batch.rb
|
160
177
|
- lib/tryouts/test_case.rb
|
161
178
|
- lib/tryouts/test_executor.rb
|
179
|
+
- lib/tryouts/test_result_aggregator.rb
|
162
180
|
- lib/tryouts/test_runner.rb
|
163
181
|
- lib/tryouts/translators/minitest_translator.rb
|
164
182
|
- lib/tryouts/translators/rspec_translator.rb
|