@@ -2126,8 +2126,76 @@ def _process() -> None:
21262126 raise converted_error from exc
21272127
21282128 gh = _read_github_context ()
2129+
2130+ # --- CI_TEST_MODE: leverage DRY_RUN infrastructure ---
2131+ # CI_TEST_MODE reuses the existing DRY_RUN + G2G_DRYRUN_DISABLE_NETWORK
2132+ # code paths so that all tool logic runs but Gerrit network operations
2133+ # are no-ops. Cleanup tasks (abandoned-PR / Gerrit-change sweeps) are
2134+ # also suppressed because they would hit a non-existent server.
2135+ ci_test_mode = env_bool ("CI_TEST_MODE" , False )
2136+
2137+ # G2G_TEST_MODE is an internal unit-test flag that short-circuits after
2138+ # config validation (no network at all). It must also suppress the
2139+ # early DNS probe to avoid failures against placeholder hostnames.
2140+ g2g_test_mode = env_bool ("G2G_TEST_MODE" , False )
2141+
2142+ if ci_test_mode :
2143+ log .info (
2144+ "🧪 CI_TEST_MODE enabled: forcing DRY_RUN=true and "
2145+ "G2G_DRYRUN_DISABLE_NETWORK=true"
2146+ )
2147+ os .environ ["DRY_RUN" ] = "true"
2148+ os .environ ["G2G_DRYRUN_DISABLE_NETWORK" ] = "true"
2149+ # Rebuild inputs so the rest of the pipeline sees dry_run=True
2150+ data = _load_effective_inputs ()
2151+
2152+ # Display config AFTER CI_TEST_MODE evaluation so the table
2153+ # reflects the actual runtime values (e.g. DRY_RUN forced true).
21292154 _display_effective_config (data , gh )
21302155
2156+ # --- Early Gerrit server DNS validation (fast-fail) ---
2157+ # Validate the Gerrit server BEFORE any PR processing or workspace setup.
2158+ # This prevents wasted work when the server is unreachable or bogus.
2159+ # Runs for both normal and DRY_RUN modes — DRY_RUN still queries Gerrit
2160+ # for read-only checks (preflight probes, cleanup scans) so the server
2161+ # must actually resolve.
2162+ #
2163+ # Skipped when:
2164+ # - CI_TEST_MODE: no real Gerrit server exists for this repository
2165+ # - G2G_TEST_MODE: internal unit-test short-circuit; never reaches Gerrit
2166+ # - G2G_DRYRUN_DISABLE_NETWORK: explicitly opted out of all network probes
2167+ # (consistent with its effect inside _dry_run_preflight)
2168+ dryrun_disable_network = env_bool ("G2G_DRYRUN_DISABLE_NETWORK" , False )
2169+
2170+ if not ci_test_mode and not g2g_test_mode and not dryrun_disable_network :
2171+ gerrit_host = data .gerrit_server or ""
2172+ if gerrit_host :
2173+ try :
2174+ _validator = Orchestrator (workspace = Path ("." ))
2175+ _validator .validate_gerrit_server (gerrit_host )
2176+ log .debug (
2177+ "✅ Gerrit server '%s' DNS validation passed" ,
2178+ gerrit_host .strip (),
2179+ )
2180+ except OrchestratorError as exc :
2181+ log .warning (
2182+ "❌ Gerrit server validation failed for '%s'" ,
2183+ gerrit_host ,
2184+ )
2185+ safe_console_print (
2186+ f"❌ Gerrit server '{ gerrit_host } ' could not be resolved. "
2187+ f"Cannot proceed without a valid Gerrit server." ,
2188+ style = "red" ,
2189+ )
2190+ exit_with_error (
2191+ ExitCode .CONFIGURATION_ERROR ,
2192+ message = (
2193+ f"Gerrit server DNS validation failed for "
2194+ f"'{ gerrit_host } '"
2195+ ),
2196+ exception = exc ,
2197+ )
2198+
21312199 # Detect PR operation mode for routing
21322200 operation_mode = gh .get_operation_mode ()
21332201 if operation_mode != models .PROperationMode .UNKNOWN :
@@ -2164,8 +2232,10 @@ def _process() -> None:
21642232 )
21652233
21662234 # First, abandon the specific Gerrit change for this closed PR
2235+ # Skip in CI_TEST_MODE: no Gerrit server to query
21672236 if (
2168- gh .pr_number
2237+ not ci_test_mode
2238+ and gh .pr_number
21692239 and data .gerrit_server
21702240 and data .gerrit_project
21712241 and gh .repository
@@ -2209,7 +2279,8 @@ def _process() -> None:
22092279 )
22102280
22112281 # Run abandoned PR cleanup if enabled
2212- if FORCE_ABANDONED_CLEANUP :
2282+ # Skip in CI_TEST_MODE: no Gerrit server to query
2283+ if FORCE_ABANDONED_CLEANUP and not ci_test_mode :
22132284 try :
22142285 log .debug ("Running abandoned PR cleanup..." )
22152286 if gh .repository and "/" in gh .repository :
@@ -2225,7 +2296,8 @@ def _process() -> None:
22252296 log .warning ("Abandoned PR cleanup failed: %s" , exc )
22262297
22272298 # Run Gerrit cleanup if enabled
2228- if FORCE_GERRIT_CLEANUP :
2299+ # Skip in CI_TEST_MODE: no Gerrit server to query
2300+ if FORCE_GERRIT_CLEANUP and not ci_test_mode :
22292301 try :
22302302 log .debug ("Running Gerrit cleanup for closed GitHub PRs..." )
22312303 if data .gerrit_server and data .gerrit_project :
@@ -2285,7 +2357,8 @@ def _process() -> None:
22852357 # Run cleanup tasks for Gerrit events and legacy G2G_GERRIT_CHANGE_URL
22862358 if gerrit_event_change_url or gerrit_change_url :
22872359 # Run abandoned PR cleanup if enabled
2288- if FORCE_ABANDONED_CLEANUP :
2360+ # Skip in CI_TEST_MODE: no Gerrit server to query
2361+ if FORCE_ABANDONED_CLEANUP and not ci_test_mode :
22892362 try :
22902363 log .debug ("Running abandoned PR cleanup..." )
22912364 if gh .repository and "/" in gh .repository :
@@ -2301,7 +2374,8 @@ def _process() -> None:
23012374 log .warning ("Abandoned PR cleanup failed: %s" , exc )
23022375
23032376 # Run Gerrit cleanup if enabled
2304- if FORCE_GERRIT_CLEANUP :
2377+ # Skip in CI_TEST_MODE: no Gerrit server to query
2378+ if FORCE_GERRIT_CLEANUP and not ci_test_mode :
23052379 try :
23062380 log .debug ("Running Gerrit cleanup for closed GitHub PRs..." )
23072381 if data .gerrit_server and data .gerrit_project :
@@ -2322,7 +2396,8 @@ def _process() -> None:
23222396 _process_close_merged_prs (data , gh )
23232397
23242398 # Run abandoned PR cleanup if enabled
2325- if FORCE_ABANDONED_CLEANUP :
2399+ # Skip in CI_TEST_MODE: no Gerrit server to query
2400+ if FORCE_ABANDONED_CLEANUP and not ci_test_mode :
23262401 try :
23272402 log .debug ("Running abandoned PR cleanup..." )
23282403 if gh .repository and "/" in gh .repository :
@@ -2338,7 +2413,8 @@ def _process() -> None:
23382413 log .warning ("Abandoned PR cleanup failed: %s" , exc )
23392414
23402415 # Run Gerrit cleanup if enabled
2341- if FORCE_GERRIT_CLEANUP :
2416+ # Skip in CI_TEST_MODE: no Gerrit server to query
2417+ if FORCE_GERRIT_CLEANUP and not ci_test_mode :
23422418 try :
23432419 log .info ("Running Gerrit cleanup for closed GitHub PRs..." )
23442420 if data .gerrit_server and data .gerrit_project :
@@ -2383,7 +2459,8 @@ def _process() -> None:
23832459 log .debug ("Processing completed ✅" )
23842460
23852461 # Run abandoned PR cleanup if enabled
2386- if FORCE_ABANDONED_CLEANUP :
2462+ # Skip in CI_TEST_MODE: no Gerrit server to query
2463+ if FORCE_ABANDONED_CLEANUP and not ci_test_mode :
23872464 try :
23882465 log .debug ("Running abandoned PR cleanup..." )
23892466 if gh .repository and "/" in gh .repository :
@@ -2399,7 +2476,8 @@ def _process() -> None:
23992476 log .warning ("Abandoned PR cleanup failed: %s" , exc )
24002477
24012478 # Run Gerrit cleanup if enabled
2402- if FORCE_GERRIT_CLEANUP :
2479+ # Skip in CI_TEST_MODE: no Gerrit server to query
2480+ if FORCE_GERRIT_CLEANUP and not ci_test_mode :
24032481 try :
24042482 log .info ("Running Gerrit cleanup for closed GitHub PRs..." )
24052483 if data .gerrit_server and data .gerrit_project :
@@ -2567,7 +2645,8 @@ def _process() -> None:
25672645 pipeline_success , result = _process_single (data , gh , progress_tracker )
25682646
25692647 # Run abandoned PR cleanup if enabled and pipeline was successful
2570- if pipeline_success and FORCE_ABANDONED_CLEANUP :
2648+ # Skip in CI_TEST_MODE: no Gerrit server to query
2649+ if pipeline_success and FORCE_ABANDONED_CLEANUP and not ci_test_mode :
25712650 try :
25722651 log .debug ("Running abandoned PR cleanup..." )
25732652 # Extract owner and repo from gh.repository (format: "owner/repo")
@@ -2585,7 +2664,8 @@ def _process() -> None:
25852664 log .warning ("Abandoned PR cleanup failed: %s" , exc )
25862665
25872666 # Run Gerrit cleanup if enabled and pipeline was successful
2588- if pipeline_success and FORCE_GERRIT_CLEANUP :
2667+ # Skip in CI_TEST_MODE: no Gerrit server to query
2668+ if pipeline_success and FORCE_GERRIT_CLEANUP and not ci_test_mode :
25892669 try :
25902670 log .debug ("Running Gerrit cleanup for closed GitHub PRs..." )
25912671 if data .gerrit_server and data .gerrit_project :
@@ -2612,10 +2692,11 @@ def _process() -> None:
26122692 # Show summary after progress tracker is stopped
26132693 if show_progress and RICH_AVAILABLE :
26142694 summary = progress_tracker .get_summary () if progress_tracker else {}
2695+ print ()
26152696 safe_console_print (
2616- "\n ✅ Operation completed!"
2697+ "✅ Operation completed!"
26172698 if pipeline_success
2618- else "\n ❌ Operation failed!" ,
2699+ else "❌ Operation failed!" ,
26192700 style = "green" if pipeline_success else "red" ,
26202701 )
26212702 safe_console_print (
@@ -2864,6 +2945,9 @@ def _get_ssh_agent_status() -> str:
28642945
28652946def _display_effective_config (data : Inputs , gh : GitHubContext ) -> None :
28662947 """Display effective configuration in a formatted table."""
2948+ # Use env_bool for consistent boolean parsing across the codebase
2949+ ci_test_mode_enabled = env_bool ("CI_TEST_MODE" , False )
2950+
28672951 # Detect mode and display prominently
28682952 github_mode = _is_github_mode ()
28692953 mode_label = "GITHUB_MODE" if github_mode else "CLI_MODE"
@@ -2924,6 +3008,11 @@ def _display_effective_config(data: Inputs, gh: GitHubContext) -> None:
29243008 # Mode first - always show
29253009 config_info [mode_label ] = mode_description
29263010
3011+ # Show CI_TEST_MODE regardless of operation mode so logs always
3012+ # indicate when the run is using test infrastructure.
3013+ if ci_test_mode_enabled :
3014+ config_info ["CI_TEST_MODE" ] = "🧪"
3015+
29273016 if is_closing_pr_mode :
29283017 # In PR closing mode, only show minimal relevant config
29293018 if data .dry_run :
0 commit comments