Class: Ace::Test::EndToEndRunner::Organisms::SuiteOrchestrator

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/test/end_to_end_runner/organisms/suite_orchestrator.rb

Overview

Orchestrates E2E test execution across multiple packages

Discovers all E2E tests across the monorepo and executes them either sequentially or in parallel using subprocess isolation. Supports filtering to affected packages based on git diff.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_parallel: 4, base_dir: nil, discoverer: nil, affected_detector: nil, failure_finder: nil, output: $stdout, use_color: nil, progress: false, suite_report_writer: nil, scenario_loader: nil, timestamp_generator: nil) ⇒ SuiteOrchestrator

Returns a new instance of SuiteOrchestrator.

Parameters:

  • max_parallel (Integer) (defaults to: 4)

    Number of parallel workers

  • base_dir (String) (defaults to: nil)

    Base directory for test discovery

  • discoverer (#find_tests, #list_packages) (defaults to: nil)

    Test discoverer (injectable)

  • affected_detector (#detect) (defaults to: nil)

    Affected package detector (injectable)

  • failure_finder (#find_failures_by_scenario) (defaults to: nil)

    Failure finder (injectable)

  • output (IO) (defaults to: $stdout)

    Output stream for progress messages

  • use_color (Boolean) (defaults to: nil)

    Enable ANSI color output (default: auto-detect TTY)

  • progress (Boolean) (defaults to: false)

    Enable animated progress display

  • suite_report_writer (defaults to: nil)

    Suite report writer (injectable)

  • scenario_loader (defaults to: nil)

    Scenario loader (injectable)

  • timestamp_generator (defaults to: nil)

    Timestamp generator (injectable)



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ace/test/end_to_end_runner/organisms/suite_orchestrator.rb', line 32

def initialize(max_parallel: 4, base_dir: nil, discoverer: nil, affected_detector: nil,
  failure_finder: nil, output: $stdout, use_color: nil, progress: false,
  suite_report_writer: nil, scenario_loader: nil, timestamp_generator: nil)
  @max_parallel = max_parallel
  @base_dir = base_dir || Dir.pwd
  @discoverer = discoverer || Molecules::TestDiscoverer.new
  @affected_detector = affected_detector || Molecules::AffectedDetector.new
  @failure_finder = failure_finder || Molecules::FailureFinder.new
  @output = output
  @use_color = use_color.nil? ? output.respond_to?(:tty?) && output.tty? : use_color
  @progress = progress
  config = Molecules::ConfigLoader.load
  @suite_report_writer = suite_report_writer || Molecules::SuiteReportWriter.new(config: config)
  @loader = scenario_loader || Molecules::ScenarioLoader.new
  @timestamp_generator = timestamp_generator || method(:default_timestamp)
end

Instance Attribute Details

#base_dirObject (readonly)

Returns the value of attribute base_dir.



19
20
21
# File 'lib/ace/test/end_to_end_runner/organisms/suite_orchestrator.rb', line 19

def base_dir
  @base_dir
end

#max_parallelObject (readonly)

Returns the value of attribute max_parallel.



19
20
21
# File 'lib/ace/test/end_to_end_runner/organisms/suite_orchestrator.rb', line 19

def max_parallel
  @max_parallel
end

Instance Method Details

#run(options = {}) ⇒ Hash

Run E2E tests across all packages

Parameters:

  • options (Hash) (defaults to: {})

    Execution options

Options Hash (options):

  • :parallel (Boolean)

    Enable parallel execution

  • :affected (Boolean)

    Only test affected packages

  • :only_failures (Boolean)

    Re-run only failed test cases

  • :packages (String)

    Comma-separated package names to filter

  • :cli_args (String)

    Extra args for CLI providers

  • :provider (String)

    LLM provider:model

  • :timeout (Integer)

    Timeout per test in seconds

Returns:

  • (Hash)

    Summary of results



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
# File 'lib/ace/test/end_to_end_runner/organisms/suite_orchestrator.rb', line 60

def run(options = {})
  pre_run_worktree = git_status_snapshot
  packages = @discoverer.list_packages(base_dir: @base_dir)

  if packages.empty?
    @output.puts "No packages with E2E tests found"
    return {total: 0, passed: 0, failed: 0, errors: 0, packages: {}}
  end

  # Filter to specific packages if requested
  if options[:packages]
    requested = options[:packages].split(",").map(&:strip)
    packages &= requested

    if packages.empty?
      @output.puts "No matching packages with E2E tests found"
      return {total: 0, passed: 0, failed: 0, errors: 0, packages: {}}
    end
  end

  # Filter to affected packages if requested
  if options[:affected]
    affected = @affected_detector.detect(base_dir: @base_dir)
    packages &= affected

    if packages.empty?
      @output.puts "No affected packages with E2E tests"
      return {total: 0, passed: 0, failed: 0, errors: 0, packages: {}}
    end

    @output.puts "Affected packages: #{packages.join(", ")}"
  end

  # Collect failures by scenario if --only-failures requested
  scenario_failures = nil
  if options[:only_failures]
    scenario_failures = @failure_finder.find_failures_by_scenario(
      packages: packages, base_dir: @base_dir
    )

    if scenario_failures.empty?
      @output.puts "No failed test scenarios found in cache"
      return {total: 0, passed: 0, failed: 0, errors: 0, packages: {}}
    end

    # Filter packages to only those with failures
    packages &= scenario_failures.keys
    @output.puts "Packages with failed scenarios: #{packages.join(", ")}"
    packages.each do |pkg|
      scenario_failures[pkg].each_key do |test_id|
        @output.puts "  #{pkg}/#{test_id}"
      end
    end
  end

  # Store scenario failures for test discovery filters
  @scenario_failures = scenario_failures
  @discovery_filters = {
    tags: options[:tags],
    exclude_tags: options[:exclude_tags]
  }

  # Discover tests in each package
  package_tests = discover_package_tests(packages)

  total_tests = package_tests.values.flatten.size
  pkg_count = package_tests.keys.size

  # Pre-compute column widths for aligned output
  compute_column_widths(package_tests)

  # Build display manager
  test_queue = build_test_queue(package_tests)
  @display = build_display_manager(test_queue)

  # Print suite header
  @display.show_header(total_tests, pkg_count)

  # Execute tests
  if options[:parallel]
    run_parallel(package_tests, options, pre_run_worktree)
  else
    run_sequential(package_tests, options, pre_run_worktree)
  end
end