@@ -105,6 +105,21 @@ func New(ctx context.Context, config *Config, version string, shutdownCallback f
105105 return nil , errors .New ("config is required" )
106106 }
107107
108+ // In report-from-events mode, we only need config + log dir.
109+ // Skip registry, runner, orchestrator, and addons setup.
110+ if config .ReportFromEvents != "" {
111+ config .Log .Info ("Report-from-events mode: skipping test runner setup" )
112+ return & nat {
113+ ctx : ctx ,
114+ config : config ,
115+ version : version ,
116+ done : make (chan struct {}),
117+ shutdownCallback : shutdownCallback ,
118+ networkName : "report" ,
119+ tracer : otel .Tracer ("op-acceptor" ),
120+ }, nil
121+ }
122+
108123 reg , err := registry .NewRegistry (registry.Config {
109124 Log : config .Log ,
110125 ValidatorConfigFile : config .ValidatorConfig ,
@@ -182,6 +197,8 @@ func New(ctx context.Context, config *Config, version string, shutdownCallback f
182197 Concurrency : config .Concurrency ,
183198 ShowProgress : config .ShowProgress ,
184199 ProgressInterval : config .ProgressInterval ,
200+ SplitTotal : config .SplitTotal ,
201+ SplitIndex : config .SplitIndex ,
185202 })
186203 if err != nil {
187204 return nil , fmt .Errorf ("failed to create test runner: %w" , err )
@@ -307,6 +324,8 @@ func (n *nat) Start(ctx context.Context) error {
307324 "concurrency" , snap .Runner .Concurrency ,
308325 "show_progress" , snap .Runner .ShowProgress ,
309326 "progress_interval" , snap .Runner .ProgressInterval ,
327+ "split_total" , n .config .SplitTotal ,
328+ "split_index" , n .config .SplitIndex ,
310329 "test_log_level" , snap .Logging .TestLogLevel ,
311330 "output_realtime_logs" , snap .Logging .OutputRealtimeLogs ,
312331 "network" , snap .NetworkName ,
@@ -318,6 +337,18 @@ func (n *nat) Start(ctx context.Context) error {
318337 "config.ValidatorConfig" , n .config .ValidatorConfig ,
319338 "config.LogDir" , n .config .LogDir )
320339
340+ // Report-from-events mode: generate HTML report from a raw events file, then exit.
341+ if n .config .ReportFromEvents != "" {
342+ err := n .reportFromEvents ()
343+ if err != nil {
344+ return err
345+ }
346+ go func () {
347+ n .shutdownCallback (nil )
348+ }()
349+ return nil
350+ }
351+
321352 // Run tests immediately on startup (or dry-run)
322353 if n .config .DryRun {
323354 err := n .dryRun (ctx )
@@ -1011,9 +1042,39 @@ func (n *nat) dryRun(ctx context.Context) error {
10111042 gateValidators [gateID ] = gateTests
10121043 }
10131044
1045+ // Apply CI split filtering if configured (mirrors runner.collectTestWork behavior)
1046+ if n .config .SplitTotal > 0 {
1047+ // Collect all work items in the same way as the runner
1048+ var allWork []runner.TestWork
1049+ for gateName , gateTests := range gateValidators {
1050+ for _ , v := range gateTests {
1051+ allWork = append (allWork , runner.TestWork {
1052+ Validator : v ,
1053+ GateID : gateName ,
1054+ SuiteID : v .Suite ,
1055+ })
1056+ }
1057+ }
1058+ filtered := runner .ApplySplitFilter (allWork , n .config .SplitTotal , n .config .SplitIndex )
1059+
1060+ // Rebuild gateValidators from the filtered work items
1061+ gateValidators = make (map [string ][]types.ValidatorMetadata )
1062+ for _ , w := range filtered {
1063+ gateValidators [w .GateID ] = append (gateValidators [w .GateID ], w .Validator )
1064+ }
1065+ n .config .Log .Info ("DRY RUN: Applied CI split filter" ,
1066+ "splitTotal" , n .config .SplitTotal ,
1067+ "splitIndex" , n .config .SplitIndex ,
1068+ "workItems" , len (filtered ))
1069+ }
1070+
10141071 t := table .NewWriter ()
10151072 t .SetOutputMirror (os .Stdout )
1016- t .SetTitle ("DRY RUN: Planned Test Execution (network: " + n .networkName + ")" )
1073+ titleSuffix := ""
1074+ if n .config .SplitTotal > 0 {
1075+ titleSuffix = fmt .Sprintf (" [split %d/%d]" , n .config .SplitIndex , n .config .SplitTotal )
1076+ }
1077+ t .SetTitle ("DRY RUN: Planned Test Execution (network: " + n .networkName + ")" + titleSuffix )
10171078
10181079 headers := []interface {}{"TYPE" , "ID" , "TESTS" , "STATUS" }
10191080 t .AppendHeader (table .Row (headers ))
@@ -1100,6 +1161,52 @@ func (n *nat) dryRun(ctx context.Context) error {
11001161 return nil
11011162}
11021163
1164+ // reportFromEvents reads a raw_go_events.log file (possibly merged from multiple
1165+ // parallel nodes) and generates a consolidated report without running any tests.
1166+ // It reuses the standard FileLogger pipeline so that all sinks (HTML, text summary,
1167+ // per-test logs, etc.) are exercised through the same code path as normal execution.
1168+ func (n * nat ) reportFromEvents () error {
1169+ eventsPath := n .config .ReportFromEvents
1170+ n .config .Log .Info ("Generating report from events file" , "path" , eventsPath )
1171+
1172+ f , err := os .Open (eventsPath )
1173+ if err != nil {
1174+ return fmt .Errorf ("failed to open events file %s: %w" , eventsPath , err )
1175+ }
1176+ defer f .Close ()
1177+
1178+ results , err := runner .ParseMultiPackageEvents (f )
1179+ if err != nil {
1180+ return fmt .Errorf ("failed to parse events: %w" , err )
1181+ }
1182+
1183+ n .config .Log .Info ("Parsed test results from events" , "packages" , len (results ))
1184+
1185+ // Use the same FileLogger pipeline as normal test execution
1186+ runID := uuid .New ().String ()
1187+ fileLogger , err := logging .NewFileLogger (n .config .LogDir , runID , n .networkName , n .config .TargetGate )
1188+ if err != nil {
1189+ return fmt .Errorf ("failed to create file logger: %w" , err )
1190+ }
1191+
1192+ // Feed all parsed results through the standard sink pipeline
1193+ for _ , result := range results {
1194+ if err := fileLogger .LogTestResult (result , runID ); err != nil {
1195+ return fmt .Errorf ("failed to log result for %s: %w" , result .Metadata .Package , err )
1196+ }
1197+ }
1198+
1199+ // Finalize all sinks (generates HTML, text summary, etc.)
1200+ if err := fileLogger .Complete (runID ); err != nil {
1201+ return fmt .Errorf ("failed to complete report: %w" , err )
1202+ }
1203+
1204+ logDir , _ := fileLogger .GetDirectoryForRunID (runID )
1205+ n .config .Log .Info ("Consolidated report generated" , "path" , logDir )
1206+
1207+ return nil
1208+ }
1209+
11031210// WaitForShutdown waits for all goroutines to finish
11041211func (n * nat ) WaitForShutdown (ctx context.Context ) error {
11051212 timeout := time .NewTimer (time .Second * 5 )
0 commit comments