Class: Ace::TestRunner::Organisms::TestOrchestrator

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/test_runner/organisms/test_orchestrator.rb

Overview

Main orchestrator that coordinates the entire test execution flow

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ TestOrchestrator

Returns a new instance of TestOrchestrator.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/ace/test_runner/organisms/test_orchestrator.rb', line 16

def initialize(options = {})
  @package_dir = options[:package_dir]
  @original_dir = Dir.pwd
  @options = options

  # Fail fast: validate package_dir exists before proceeding
  if @package_dir && !Dir.exist?(@package_dir)
    raise Error, "Package directory not found: #{@package_dir}"
  end

  # Track if user explicitly specified --report-dir to avoid auto-detection override
  @report_dir_override = options[:report_dir] ? :user_specified : nil

  # Component initialization strategy:
  # - Package mode: defer setup to run() when we're in the correct directory
  # - Non-package mode: set up immediately for backward compatibility
  #   (callers may access @configuration after initialize)
  if @package_dir
    @configuration = nil
    @components_initialized = false
  else
    setup_components
    @components_initialized = true
  end
end

Instance Attribute Details

#configurationObject (readonly)

Returns the value of attribute configuration.



14
15
16
# File 'lib/ace/test_runner/organisms/test_orchestrator.rb', line 14

def configuration
  @configuration
end

#resultObject (readonly)

Returns the value of attribute result.



14
15
16
# File 'lib/ace/test_runner/organisms/test_orchestrator.rb', line 14

def result
  @result
end

Instance Method Details

#runObject



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ace/test_runner/organisms/test_orchestrator.rb', line 42

def run
  # Change to package directory if specified - this is done in run to ensure
  # the ensure block always restores the directory, even on initialization errors
  Dir.chdir(@package_dir) if @package_dir

  # Initialize components if not already done (package mode)
  setup_components unless @components_initialized

  run_with_package_context
ensure
  # Restore original directory if we changed it
  Dir.chdir(@original_dir) if @package_dir && Dir.pwd != @original_dir
end

#run_with_package_contextObject



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
# File 'lib/ace/test_runner/organisms/test_orchestrator.rb', line 56

def run_with_package_context
  validate_configuration!

  start_time = Time.now

  # Print package context if running in different directory
  if @package_dir
    puts "Running tests in #{File.basename(@package_dir)}..."
  end

  # Check if sequential target execution should be used
  if should_execute_sequentially?
    # Use default "fast" target if no target specified in by-target mode
    @configuration.target ||= "fast"
    return run_sequential_targets(start_time)
  end

  # Find test files
  test_files = find_test_files
  if test_files.empty?
    return handle_no_tests
  end

  resolve_and_set_report_dir_context(test_files)

  # Count total available test files (before filtering)
  total_available = count_total_test_files

  # Notify start with both counts
  if @formatter.respond_to?(:on_start_with_totals)
    @formatter.on_start_with_totals(test_files.size, total_available)
  else
    @formatter.on_start(test_files.size)
  end

  # Execute tests
  execution_result = execute_tests(test_files)

  # Check if execution failed with LoadError (stdout is empty means tests didn't run)
  @parsed_result = if !execution_result[:success] && execution_result[:stdout].to_s.empty? && execution_result[:stderr] && !execution_result[:stderr].empty?
    # Handle load errors or other failures that prevented test execution
    {
      summary: {
        runs: 0,
        assertions: 0,
        failures: 0,
        errors: test_files.size,  # Count all test files as errors
        skips: 0,
        passed: 0
      },
      failures: [],
      errors: [{
        message: execution_result[:stderr].strip,
        type: "LoadError",
        files: test_files
      }],
      deprecations: [],
      duration: execution_result[:duration]
    }
  elsif execution_result[:commands] && execution_result[:commands].is_a?(Array)
    # Each file was executed separately, parse and sum them all
    aggregate_individual_results(execution_result[:stdout])
  else
    # Single command execution (by-target)
    @result_parser.parse_output(execution_result[:stdout])
  end

  # Build result object
  @result = build_result(@parsed_result, execution_result, start_time)

  # Analyze failures and errors
  if @result.has_failures?
    # Collect both failures and errors for analysis
    all_failures = @parsed_result[:failures] || []

    # Convert errors to failure format if present
    if @parsed_result[:errors] && @parsed_result[:errors].any?
      error_failures = @parsed_result[:errors].map do |error|
        {
          type: :error,
          test_name: error[:type] || "LoadError",
          message: error[:message] || "Unknown error",
          location: nil,
          full_content: error[:message] || "Unknown error",
          files: error[:files]
        }
      end
      all_failures += error_failures
    end

    analyzed_failures = @failure_analyzer.analyze_all(
      all_failures,
      stderr: @result.stderr
    )
    @result.failures_detail = analyzed_failures
  end

  # Generate and save report
  report = @report_generator.generate(@result, test_files)

  # Save reports if configured
  if @configuration.save_reports
    report_path = save_reports(report)
    # Pass report path to formatter before outputting
    @formatter.report_path = report_path if @formatter.respond_to?(:report_path=)
  end

  # Output to stdout
  @formatter.on_finish(@result)

  # Display profile results if requested
  if @configuration.profile && @parsed_result[:test_times] && !@parsed_result[:test_times].empty?
    display_profile(@parsed_result[:test_times], @configuration.profile)
  end

  # Return exit code
  @result.success? ? 0 : 1
end