tryouts 3.3.0 → 3.3.2
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 +1 -1
- data/lib/tryouts/cli/formatters/compact.rb +8 -4
- data/lib/tryouts/cli/formatters/quiet.rb +4 -3
- data/lib/tryouts/cli/formatters/verbose.rb +8 -4
- data/lib/tryouts/cli/opts.rb +14 -4
- 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/regex_match.rb +11 -3
- data/lib/tryouts/expectation_evaluators/registry.rb +2 -0
- data/lib/tryouts/expectation_evaluators/result_type.rb +9 -1
- data/lib/tryouts/file_processor.rb +9 -5
- data/lib/tryouts/parsers/base_parser.rb +23 -0
- data/lib/tryouts/parsers/enhanced_parser.rb +115 -0
- data/lib/tryouts/parsers/prism_parser.rb +122 -0
- data/lib/tryouts/parsers/shared_methods.rb +416 -0
- data/lib/tryouts/test_batch.rb +54 -13
- 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 +76 -20
- data/lib/tryouts/version.rb +1 -1
- data/lib/tryouts.rb +7 -2
- metadata +21 -3
- data/lib/tryouts/enhanced_parser.rb +0 -461
- data/lib/tryouts/prism_parser.rb +0 -516
@@ -53,10 +53,12 @@ class Tryouts
|
|
53
53
|
file_failed_count = test_results.count { |r| r.failed? }
|
54
54
|
file_error_count = test_results.count { |r| r.error? }
|
55
55
|
executed_test_count = test_results.size
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
|
57
|
+
# Note: Individual test results are added to the aggregator in TestBatch
|
58
|
+
# Here we just update the file success count atomically
|
59
|
+
if success
|
60
|
+
@global_tally[:aggregator].increment_successful_files
|
61
|
+
end
|
60
62
|
|
61
63
|
duration = Time.now.to_f - @file_start.to_f
|
62
64
|
@output_manager.file_success(@file, executed_test_count, file_failed_count, file_error_count, duration)
|
@@ -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,11 +1,14 @@
|
|
1
1
|
# lib/tryouts/test_runner.rb
|
2
2
|
|
3
|
-
|
3
|
+
require 'concurrent'
|
4
|
+
require_relative 'parsers/prism_parser'
|
5
|
+
require_relative 'parsers/enhanced_parser'
|
4
6
|
require_relative 'test_batch'
|
5
7
|
require_relative 'translators/rspec_translator'
|
6
8
|
require_relative 'translators/minitest_translator'
|
7
9
|
require_relative 'file_processor'
|
8
10
|
require_relative 'failure_collector'
|
11
|
+
require_relative 'test_result_aggregator'
|
9
12
|
|
10
13
|
class Tryouts
|
11
14
|
class TestRunner
|
@@ -34,7 +37,7 @@ class Tryouts
|
|
34
37
|
|
35
38
|
result = process_files
|
36
39
|
show_failure_summary
|
37
|
-
show_grand_total if @global_tally[:
|
40
|
+
show_grand_total if @global_tally[:aggregator].get_file_counts[:total] > 1
|
38
41
|
result
|
39
42
|
end
|
40
43
|
|
@@ -69,17 +72,20 @@ class Tryouts
|
|
69
72
|
|
70
73
|
def initialize_global_tally
|
71
74
|
{
|
72
|
-
total_tests: 0,
|
73
|
-
total_failed: 0,
|
74
|
-
total_errors: 0,
|
75
|
-
file_count: 0,
|
76
75
|
start_time: Time.now,
|
77
|
-
|
78
|
-
failure_collector: FailureCollector.new,
|
76
|
+
aggregator: TestResultAggregator.new,
|
79
77
|
}
|
80
78
|
end
|
81
79
|
|
82
80
|
def process_files
|
81
|
+
if @options[:parallel] && @files.length > 1
|
82
|
+
process_files_parallel
|
83
|
+
else
|
84
|
+
process_files_sequential
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_files_sequential
|
83
89
|
failure_count = 0
|
84
90
|
|
85
91
|
@files.each_with_index do |file, _idx|
|
@@ -92,37 +98,87 @@ class Tryouts
|
|
92
98
|
failure_count
|
93
99
|
end
|
94
100
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
101
|
+
def process_files_parallel
|
102
|
+
# Determine thread pool size
|
103
|
+
pool_size = @options[:parallel_threads] || Concurrent.processor_count
|
104
|
+
@output_manager.info "Running #{@files.length} files in parallel (#{pool_size} threads)", 1
|
105
|
+
|
106
|
+
# Create thread pool executor
|
107
|
+
executor = Concurrent::ThreadPoolExecutor.new(
|
108
|
+
min_threads: 1,
|
109
|
+
max_threads: pool_size,
|
110
|
+
max_queue: pool_size * 2, # Reasonable queue size
|
111
|
+
fallback_policy: :abort # Raise exception if pool and queue are exhausted
|
112
|
+
)
|
113
|
+
|
114
|
+
# Submit all file processing tasks to the thread pool
|
115
|
+
futures = @files.map do |file|
|
116
|
+
Concurrent::Future.execute(executor: executor) do
|
117
|
+
process_file(file)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Wait for all tasks to complete and collect results
|
122
|
+
failure_count = 0
|
123
|
+
futures.each_with_index do |future, idx|
|
124
|
+
begin
|
125
|
+
result = future.value # This blocks until the future completes
|
126
|
+
failure_count += result unless result.zero?
|
127
|
+
|
128
|
+
status = result.zero? ? Console.color(:green, 'PASS') : Console.color(:red, 'FAIL')
|
129
|
+
file = @files[idx]
|
130
|
+
@output_manager.info "#{status} #{Console.pretty_path(file)} (#{result} failures)", 1
|
131
|
+
rescue StandardError => ex
|
132
|
+
failure_count += 1
|
133
|
+
file = @files[idx]
|
134
|
+
@output_manager.info "#{Console.color(:red, 'ERROR')} #{Console.pretty_path(file)} (#{ex.message})", 1
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Shutdown the thread pool
|
139
|
+
executor.shutdown
|
140
|
+
executor.wait_for_termination(10) # Wait up to 10 seconds for clean shutdown
|
141
|
+
|
142
|
+
failure_count
|
143
|
+
end
|
144
|
+
|
145
|
+
def process_file(file_path)
|
146
|
+
processor = FileProcessor.new(
|
147
|
+
file: file_path,
|
98
148
|
options: @options,
|
99
149
|
output_manager: @output_manager,
|
100
150
|
translator: @translator,
|
101
151
|
global_tally: @global_tally,
|
102
152
|
)
|
103
|
-
|
153
|
+
processor.process
|
104
154
|
rescue StandardError => ex
|
105
155
|
handle_file_error(ex)
|
106
|
-
@global_tally[:
|
156
|
+
@global_tally[:aggregator].add_infrastructure_failure(
|
157
|
+
:file_processing, file_path, ex.message, ex
|
158
|
+
)
|
107
159
|
1
|
108
160
|
end
|
109
161
|
|
110
162
|
def show_failure_summary
|
111
163
|
# Show failure summary if any failures exist
|
112
|
-
|
113
|
-
|
164
|
+
aggregator = @global_tally[:aggregator]
|
165
|
+
if aggregator.any_display_failures?
|
166
|
+
@output_manager.batch_summary(aggregator.failure_collector)
|
114
167
|
end
|
115
168
|
end
|
116
169
|
|
117
170
|
def show_grand_total
|
118
171
|
elapsed_time = Time.now - @global_tally[:start_time]
|
172
|
+
aggregator = @global_tally[:aggregator]
|
173
|
+
display_counts = aggregator.get_display_counts
|
174
|
+
file_counts = aggregator.get_file_counts
|
119
175
|
|
120
176
|
@output_manager.grand_total(
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
177
|
+
display_counts[:total_tests],
|
178
|
+
display_counts[:failed],
|
179
|
+
display_counts[:errors],
|
180
|
+
file_counts[:successful],
|
181
|
+
file_counts[:total],
|
126
182
|
elapsed_time,
|
127
183
|
)
|
128
184
|
end
|
data/lib/tryouts/version.rb
CHANGED
data/lib/tryouts.rb
CHANGED
@@ -8,7 +8,8 @@ TRYOUTS_LIB_HOME = __dir__ unless defined?(TRYOUTS_LIB_HOME)
|
|
8
8
|
require_relative 'tryouts/console'
|
9
9
|
require_relative 'tryouts/test_batch'
|
10
10
|
require_relative 'tryouts/version'
|
11
|
-
require_relative 'tryouts/prism_parser'
|
11
|
+
require_relative 'tryouts/parsers/prism_parser'
|
12
|
+
require_relative 'tryouts/parsers/enhanced_parser'
|
12
13
|
require_relative 'tryouts/cli'
|
13
14
|
|
14
15
|
class Tryouts
|
@@ -22,13 +23,17 @@ class Tryouts
|
|
22
23
|
|
23
24
|
module ClassMethods
|
24
25
|
attr_accessor :container, :quiet, :noisy, :fails
|
25
|
-
attr_writer :debug
|
26
|
+
attr_writer :debug, :stack_traces
|
26
27
|
attr_reader :cases, :testcase_io
|
27
28
|
|
28
29
|
def debug?
|
29
30
|
@debug == true
|
30
31
|
end
|
31
32
|
|
33
|
+
def stack_traces?
|
34
|
+
@stack_traces == true || debug? # Debug mode auto-enables stack traces
|
35
|
+
end
|
36
|
+
|
32
37
|
def update_load_path(lib_glob)
|
33
38
|
Dir.glob(lib_glob).each { |dir| $LOAD_PATH.unshift(dir) }
|
34
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.3.
|
4
|
+
version: 3.3.2
|
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
|
@@ -137,13 +151,13 @@ files:
|
|
137
151
|
- lib/tryouts/cli/opts.rb
|
138
152
|
- lib/tryouts/cli/tty_detector.rb
|
139
153
|
- lib/tryouts/console.rb
|
140
|
-
- lib/tryouts/enhanced_parser.rb
|
141
154
|
- lib/tryouts/expectation_evaluators/base.rb
|
142
155
|
- lib/tryouts/expectation_evaluators/boolean.rb
|
143
156
|
- lib/tryouts/expectation_evaluators/exception.rb
|
144
157
|
- lib/tryouts/expectation_evaluators/expectation_result.rb
|
145
158
|
- lib/tryouts/expectation_evaluators/false.rb
|
146
159
|
- lib/tryouts/expectation_evaluators/intentional_failure.rb
|
160
|
+
- lib/tryouts/expectation_evaluators/non_nil.rb
|
147
161
|
- lib/tryouts/expectation_evaluators/output.rb
|
148
162
|
- lib/tryouts/expectation_evaluators/performance_time.rb
|
149
163
|
- lib/tryouts/expectation_evaluators/regex_match.rb
|
@@ -153,10 +167,14 @@ files:
|
|
153
167
|
- lib/tryouts/expectation_evaluators/true.rb
|
154
168
|
- lib/tryouts/failure_collector.rb
|
155
169
|
- lib/tryouts/file_processor.rb
|
156
|
-
- lib/tryouts/
|
170
|
+
- lib/tryouts/parsers/base_parser.rb
|
171
|
+
- lib/tryouts/parsers/enhanced_parser.rb
|
172
|
+
- lib/tryouts/parsers/prism_parser.rb
|
173
|
+
- lib/tryouts/parsers/shared_methods.rb
|
157
174
|
- lib/tryouts/test_batch.rb
|
158
175
|
- lib/tryouts/test_case.rb
|
159
176
|
- lib/tryouts/test_executor.rb
|
177
|
+
- lib/tryouts/test_result_aggregator.rb
|
160
178
|
- lib/tryouts/test_runner.rb
|
161
179
|
- lib/tryouts/translators/minitest_translator.rb
|
162
180
|
- lib/tryouts/translators/rspec_translator.rb
|