Class: Tryouts::TestBatch
- Inherits:
-
Object
- Object
- Tryouts::TestBatch
- Defined in:
- lib/tryouts/test_batch.rb
Overview
Modern TestBatch using Ruby 3.4+ patterns and formatter system
Constant Summary collapse
- OUTPUT_CAPTURE_MONITOR =
$stdout/$stderr are process-global, not Fiber- or thread-local. Under --parallel (Concurrent::ThreadPoolExecutor), concurrent redirects would clobber each other and could leave $stdout pointing at a dead StringIO process-wide. Every redirect path (capture_output for setup/teardown and each test, plus execute_with_output_capture) synchronizes on this lock so the redirect/execute/restore window is serialized across threads.
A Monitor (reentrant) is required because execute_with_output_capture runs nested inside capture_output; a plain Mutex would self-deadlock.
Monitor.new
Instance Attribute Summary collapse
-
#container ⇒ Object
readonly
Returns the value of attribute container.
-
#failed_count ⇒ Object
readonly
Returns the value of attribute failed_count.
-
#formatter ⇒ Object
readonly
Returns the value of attribute formatter.
-
#output_manager ⇒ Object
readonly
Returns the value of attribute output_manager.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
-
#status ⇒ Object
readonly
Returns the value of attribute status.
-
#testrun ⇒ Object
readonly
Returns the value of attribute testrun.
Instance Method Summary collapse
- #completed? ⇒ Boolean
- #empty? ⇒ Boolean
- #failed? ⇒ Boolean
-
#initialize(testrun, **options) ⇒ TestBatch
constructor
A new instance of TestBatch.
- #path ⇒ Object
-
#run(before_test_hook = nil) ⇒ Object
Main execution pipeline using functional composition.
- #size ⇒ Object
- #test_cases ⇒ Object
Constructor Details
#initialize(testrun, **options) ⇒ TestBatch
Returns a new instance of TestBatch.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/tryouts/test_batch.rb', line 41 def initialize(testrun, **) @testrun = testrun @container = Object.new @options = @formatter = Tryouts::CLI::FormatterFactory.create_formatter() @output_manager = [:output_manager] @global_tally = [:global_tally] @failed_count = 0 @status = :pending @results = [] @start_time = nil @test_case_count = 0 @setup_failed = false @orphan_failed = false # Shared-context mode evaluates every block against this one reused # Binding so local variables persist across blocks like a plain Ruby # script. Fresh-context mode keeps per-container instance_eval isolation. @binding = [:shared_context] ? acquire_container_binding(@container) : nil @line_spec = [:line_spec] # For output filtering only # Setup container for fresh context mode - preserves @instance_variables from setup @setup_container = nil # Circuit breaker for batch-level failure protection @consecutive_failures = 0 @max_consecutive_failures = [:max_consecutive_failures] || 10 @circuit_breaker_active = false # Expose context objects for testing - different strategies for each mode @shared_context = if [:shared_context] @container # Shared mode: single container reused across tests else FreshContextFactory.new # Fresh mode: factory that creates new containers end end |
Instance Attribute Details
#container ⇒ Object (readonly)
Returns the value of attribute container.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def container @container end |
#failed_count ⇒ Object (readonly)
Returns the value of attribute failed_count.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def failed_count @failed_count end |
#formatter ⇒ Object (readonly)
Returns the value of attribute formatter.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def formatter @formatter end |
#output_manager ⇒ Object (readonly)
Returns the value of attribute output_manager.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def output_manager @output_manager end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def results @results end |
#status ⇒ Object (readonly)
Returns the value of attribute status.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def status @status end |
#testrun ⇒ Object (readonly)
Returns the value of attribute testrun.
39 40 41 |
# File 'lib/tryouts/test_batch.rb', line 39 def testrun @testrun end |
Instance Method Details
#completed? ⇒ Boolean
206 207 208 |
# File 'lib/tryouts/test_batch.rb', line 206 def completed? @status == :completed end |
#empty? ⇒ Boolean
186 187 188 |
# File 'lib/tryouts/test_batch.rb', line 186 def empty? @testrun.empty? end |
#failed? ⇒ Boolean
202 203 204 |
# File 'lib/tryouts/test_batch.rb', line 202 def failed? @failed_count > 0 end |
#path ⇒ Object
198 199 200 |
# File 'lib/tryouts/test_batch.rb', line 198 def path @testrun.source_file end |
#run(before_test_hook = nil) ⇒ Object
Main execution pipeline using functional composition
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/tryouts/test_batch.rb', line 79 def run(before_test_hook = nil, &) return false if empty? @start_time = Time.now @test_case_count = @testrun.total_tests @output_manager&.execution_phase(@test_case_count) @output_manager&.info("Context: #{@options[:shared_context] ? 'shared' : 'fresh'}", 1) @output_manager&.file_start(path, context: @options[:shared_context] ? :shared : :fresh) if shared_context? @output_manager&.info('Running global setup...', 2) execute_global_setup # Stop execution if setup failed if @setup_failed @output_manager&.error('Stopping batch execution due to setup failure') @status = :failed finalize_results([]) return false end else # Fresh context mode: execute setup once to establish shared @instance_variables @output_manager&.info('Running setup for fresh context...', 2) execute_fresh_context_setup # Stop execution if setup failed if @setup_failed @output_manager&.error('Stopping batch execution due to setup failure') @status = :failed finalize_results([]) return false end end idx = 0 execution_results = test_cases.map do |test_case| # Orphan blocks run for side effects only, in source order. A raise # aborts the batch like a setup failure: every subsequent test's # context is suspect. if test_case.is_a?(OrphanBlock) execute_orphan_block(test_case) break if @orphan_failed next end @output_manager&.trace("Test #{idx + 1}/#{@test_case_count}: #{test_case.description}", 2) idx += 1 # Check circuit breaker before executing test if @circuit_breaker_active @output_manager&.error("Circuit breaker active - skipping remaining tests after #{@consecutive_failures} consecutive failures") break end # Apply line_spec filter for output notifications (test_start/test_end) # but still execute ALL tests regardless of filter if should_display_test_result?(test_case) @output_manager&.test_start(test_case, idx, @test_case_count) end result = execute_single_test(test_case, before_test_hook, &) # runs the test code if should_display_test_result?(test_case) @output_manager&.test_end(test_case, idx, @test_case_count) end # Update circuit breaker state based on result update_circuit_breaker(result) result rescue StandardError => ex if should_display_test_result?(test_case) @output_manager&.test_end(test_case, idx, @test_case_count) end # Create error result packet to maintain consistent data flow error_result = build_error_result(test_case, ex) process_test_result(error_result) # Update circuit breaker for exception cases update_circuit_breaker(error_result) error_result end if @orphan_failed @output_manager&.error('Stopping batch execution due to orphan code block failure') @status = :failed @failed_count += 1 finalize_results(execution_results || []) return false end # Used for a separate purpose then execution_phase. # e.g. the quiet formatter prints a newline after all test dots @output_manager&.file_end(path, context: @options[:shared_context] ? :shared : :fresh) @output_manager&.execution_phase(test_cases.size) execute_global_teardown finalize_results(execution_results) @status = :completed !failed? end |
#size ⇒ Object
190 191 192 |
# File 'lib/tryouts/test_batch.rb', line 190 def size @testrun.total_tests end |
#test_cases ⇒ Object
194 195 196 |
# File 'lib/tryouts/test_batch.rb', line 194 def test_cases @testrun.test_cases end |