Reactorcide

← Back to Jobs

test-python

failed exit: 1

Triggered by eval job 019c7e62-70b6-fed3-189a-1f82c8a6cbe4

Job ID
019c7e62-9e33-b561-6ceb-644a11add684
Created
2026-02-21 04:08:38 UTC
Updated
2026-02-21 04:08:38 UTC
Duration
3m 36s
Source Ref
844d2169f9020f68d43d5a8587683b94a62346ea
Source URL
https://github.com/catalystcommunity/reactorcide.git
Runner Image
10.16.0.1:5000/public/reactorcide/runnerbase:dev
Priority
10
Queue
reactorcide-jobs

Logs

2026-02-21T04:08:49.347556211ZCloning into '/workspace'...
2026-02-21T04:09:45.058062219ZUpdating files: 10% (32/305) Updating files: 11% (34/305) Updating files: 12% (37/305) Updating files: 13% (40/305) Updating files: 14% (43/305) Updating files: 15% (46/305) Updating files: 16% (49/305) Updating files: 17% (52/305) Updating files: 18% (55/305) Updating files: 19% (58/305) Updating files: 20% (61/305) Updating files: 21% (65/305) Updating files: 22% (68/305) Updating files: 23% (71/305) Updating files: 24% (74/305) Updating files: 25% (77/305) Updating files: 26% (80/305) Updating files: 27% (83/305) Updating files: 28% (86/305) Updating files: 29% (89/305) Updating files: 30% (92/305) Updating files: 31% (95/305) Updating files: 32% (98/305) Updating files: 33% (101/305) Updating files: 34% (104/305) Updating files: 35% (107/305) Updating files: 36% (110/305) Updating files: 37% (113/305) Updating files: 38% (116/305) Updating files: 39% (119/305) Updating files: 40% (122/305) Updating files: 41% (126/305) Updating files: 42% (129/305) Updating files: 43% (132/305) Updating files: 44% (135/305) Updating files: 45% (138/305) Updating files: 46% (141/305) Updating files: 47% (144/305) Updating files: 48% (147/305) Updating files: 49% (150/305) Updating files: 50% (153/305) Updating files: 51% (156/305) Updating files: 52% (159/305) Updating files: 53% (162/305) Updating files: 54% (165/305) Updating files: 55% (168/305) Updating files: 56% (171/305) Updating files: 57% (174/305) Updating files: 58% (177/305) Updating files: 59% (180/305) Updating files: 60% (183/305) Updating files: 61% (187/305) Updating files: 62% (190/305) Updating files: 63% (193/305) Updating files: 64% (196/305) Updating files: 65% (199/305) Updating files: 66% (202/305) Updating files: 67% (205/305) Updating files: 68% (208/305) Updating files: 69% (211/305) Updating files: 70% (214/305) Updating files: 71% (217/305) Updating files: 72% (220/305) Updating files: 73% (223/305) Updating files: 74% (226/305) Updating files: 75% (229/305) Updating files: 76% (232/305) Updating files: 77% (235/305) Updating files: 78% (238/305) Updating files: 79% (241/305) Updating files: 80% (244/305) Updating files: 81% (248/305) Updating files: 82% (251/305) Updating files: 83% (254/305) Updating files: 84% (257/305) Updating files: 85% (260/305) Updating files: 86% (263/305) Updating files: 87% (266/305) Updating files: 88% (269/305) Updating files: 89% (272/305) Updating files: 90% (275/305) Updating files: 91% (278/305) Updating files: 92% (281/305) Updating files: 93% (284/305) Updating files: 94% (287/305) Updating files: 95% (290/305) Updating files: 96% (293/305) Updating files: 97% (296/305) Updating files: 98% (299/305) Updating files: 99% (302/305) Updating files: 100% (305/305) Updating files: 100% (305/305), done.
2026-02-21T04:09:45.062435761Z=== Running Python Tests ===
2026-02-21T04:09:45.369676156ZUsing CPython 3.13.12 interpreter at: /usr/local/bin/python3.13
2026-02-21T04:09:45.369687142ZCreating virtual environment at: .venv
2026-02-21T04:09:45.426581711Z Building runnerlib @ file:///workspace/runnerlib
2026-02-21T04:09:45.530744567ZDownloading cryptography (4.3MiB)
2026-02-21T04:09:45.532058403ZDownloading pygments (1.2MiB)
2026-02-21T04:09:46.247559171Z Downloaded pygments
2026-02-21T04:09:46.57165484Z Downloaded cryptography
2026-02-21T04:09:47.131372034Z Built runnerlib @ file:///workspace/runnerlib
2026-02-21T04:09:47.137012868Zwarning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
2026-02-21T04:09:47.137024711Z If the cache and target directories are on different filesystems, hardlinking may not be supported.
2026-02-21T04:09:47.137292126Z If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
2026-02-21T04:09:47.259484869ZInstalled 22 packages in 124ms
2026-02-21T04:09:51.103086683Z============================= test session starts ==============================
2026-02-21T04:09:51.103096274Zplatform linux -- Python 3.13.12, pytest-8.3.5, pluggy-1.6.0
2026-02-21T04:09:51.103100438Zrootdir: /workspace/runnerlib
2026-02-21T04:09:51.103104141Zconfigfile: pyproject.toml
2026-02-21T04:09:51.103107794Zplugins: cov-7.0.0
2026-02-21T04:09:51.103110728Zcollected 397 items
2026-02-21T04:09:51.103113281Z
2026-02-21T04:09:51.137053087Ztests/test_config.py .................... [ 5%]
2026-02-21T04:09:51.151178176Ztests/test_container_advanced.py ......... [ 7%]
2026-02-21T04:09:51.211633224Ztests/test_container_isolation.py ...F [ 8%]
2026-02-21T04:09:51.242643572Ztests/test_container_validation.py ................... [ 13%]
2026-02-21T04:09:51.380055994Ztests/test_directory_operations.py ......F..... [ 16%]
2026-02-21T04:09:57.741389005Ztests/test_docker_execution.py FFFFFFFFFF [ 18%]
2026-02-21T04:09:59.473369612Ztests/test_dynamic_secret_masking.py FFF [ 19%]
2026-02-21T04:10:00.743246428Ztests/test_dynamic_secrets.py FsF [ 20%]
2026-02-21T04:10:00.930032596Ztests/test_eval.py ..................................................... [ 33%]
2026-02-21T04:10:00.992101577Z............. [ 36%]
2026-02-21T04:11:41.805630195Ztests/test_eval_cli.py .........FFF....F.... [ 42%]
2026-02-21T04:11:44.385896437Ztests/test_git_operations.py ........F. [ 44%]
2026-02-21T04:11:44.943946396Ztests/test_git_ops.py ....... [ 46%]
2026-02-21T04:11:45.519402524Ztests/test_integration.py ...F....... [ 49%]
2026-02-21T04:11:46.209774474Ztests/test_job_isolation.py FF.F [ 50%]
2026-02-21T04:11:46.323796774Ztests/test_plugins.py ....................... [ 55%]
2026-02-21T04:11:46.379393237Ztests/test_register_secret.py ............ [ 58%]
2026-02-21T04:11:46.891510293Ztests/test_secrets.py ................... [ 63%]
2026-02-21T04:11:51.316999646Ztests/test_secrets_local.py ............................ [ 70%]
2026-02-21T04:11:51.347942945Ztests/test_secrets_resolver.py ............................. [ 78%]
2026-02-21T04:11:56.819781551Ztests/test_secrets_server.py ........ [ 80%]
2026-02-21T04:12:18.333053604Ztests/test_source_preparation.py .FF.FF.FF...... [ 83%]
2026-02-21T04:12:18.381978057Ztests/test_validation.py ......................F.... [ 90%]
2026-02-21T04:12:18.464778513Ztests/test_workflow.py ..................................... [100%]
2026-02-21T04:12:18.46478519Z
2026-02-21T04:12:18.464789336Z=================================== FAILURES ===================================
2026-02-21T04:12:18.46479291Z______ TestContainerIsolation.test_work_directory_isolation_with_prepare _______
2026-02-21T04:12:18.464794902Z
2026-02-21T04:12:18.464799681Zself = <runnerlib.tests.test_container_isolation.TestContainerIsolation object at 0x7f87abef9e00>
2026-02-21T04:12:18.464801542Z
2026-02-21T04:12:18.464805005Z def test_work_directory_isolation_with_prepare(self):
2026-02-21T04:12:18.464810222Z """Test that prepare_job_directory respects work directory changes."""
2026-02-21T04:12:18.464815225Z with tempfile.TemporaryDirectory() as work_dir1:
2026-02-21T04:12:18.466684295Z with tempfile.TemporaryDirectory() as work_dir2:
2026-02-21T04:12:18.466689666Z original_cwd = os.getcwd()
2026-02-21T04:12:18.466692274Z
2026-02-21T04:12:18.466696038Z try:
2026-02-21T04:12:18.466699374Z # Prepare job 1
2026-02-21T04:12:18.466702183Z os.chdir(work_dir1)
2026-02-21T04:12:18.466707623Z config1 = RunnerConfig(
2026-02-21T04:12:18.466710857Z code_dir="/job/src",
2026-02-21T04:12:18.466714202Z job_dir="/job/src",
2026-02-21T04:12:18.466717172Z job_command="echo job1",
2026-02-21T04:12:18.466720503Z runner_image="alpine:latest"
2026-02-21T04:12:18.466723118Z )
2026-02-21T04:12:18.466733843Z job_path1 = prepare_job_directory(config1)
2026-02-21T04:12:18.466737392Z assert job_path1.exists()
2026-02-21T04:12:18.466740144Z> assert str(job_path1).startswith(work_dir1)
2026-02-21T04:12:18.466743496ZE AssertionError: assert False
2026-02-21T04:12:18.466749936ZE + where False = <built-in method startswith of str object at 0x7f87abb72fd0>('/tmp/tmphyh4bqab')
2026-02-21T04:12:18.466753987ZE + where <built-in method startswith of str object at 0x7f87abb72fd0> = '/job'.startswith
2026-02-21T04:12:18.466757212ZE + where '/job' = str(PosixPath('/job'))
2026-02-21T04:12:18.466759458Z
2026-02-21T04:12:18.466762967Ztests/test_container_isolation.py:158: AssertionError
2026-02-21T04:12:18.466766876Z__________ TestDirectoryOperations.test_cleanup_removes_job_directory __________
2026-02-21T04:12:18.46676859Z
2026-02-21T04:12:18.466772681Zself = <runnerlib.tests.test_directory_operations.TestDirectoryOperations object at 0x7f87abf29d00>
2026-02-21T04:12:18.466776213Z
2026-02-21T04:12:18.466779404Z def test_cleanup_removes_job_directory(self):
2026-02-21T04:12:18.466782529Z """Test that cleanup removes the job directory."""
2026-02-21T04:12:18.466785327Z job_dir = Path("./job")
2026-02-21T04:12:18.466788293Z job_dir.mkdir(exist_ok=True)
2026-02-21T04:12:18.466791061Z
2026-02-21T04:12:18.466793707Z # Create some files
2026-02-21T04:12:18.466796853Z (job_dir / "file.txt").write_text("Content")
2026-02-21T04:12:18.466799752Z (job_dir / "subdir").mkdir(exist_ok=True)
2026-02-21T04:12:18.466802635Z (job_dir / "subdir" / "nested.txt").write_text("Nested")
2026-02-21T04:12:18.466804116Z
2026-02-21T04:12:18.466806495Z # Perform cleanup
2026-02-21T04:12:18.466808729Z cleanup_job_directory()
2026-02-21T04:12:18.46681045Z
2026-02-21T04:12:18.466815787Z # Job directory should be gone
2026-02-21T04:12:18.466818433Z> assert not job_dir.exists()
2026-02-21T04:12:18.466821481ZE AssertionError: assert not True
2026-02-21T04:12:18.466824117ZE + where True = exists()
2026-02-21T04:12:18.466826872ZE + where exists = PosixPath('job').exists
2026-02-21T04:12:18.466828635Z
2026-02-21T04:12:18.466831955Ztests/test_directory_operations.py:172: AssertionError
2026-02-21T04:12:18.466835769Z_________________________ test_basic_docker_execution __________________________
2026-02-21T04:12:18.466837632Z
2026-02-21T04:12:18.466840243Z def test_basic_docker_execution():
2026-02-21T04:12:18.466843763Z """Test that we can execute a simple container with Docker."""
2026-02-21T04:12:18.466845752Z
2026-02-21T04:12:18.466848606Z # Create a temporary working directory
2026-02-21T04:12:18.466851569Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.466854313Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.466856304Z
2026-02-21T04:12:18.466859475Z # Create job directory structure
2026-02-21T04:12:18.466861873Z job_dir = work_dir / "job"
2026-02-21T04:12:18.466864692Z job_dir.mkdir()
2026-02-21T04:12:18.466866306Z
2026-02-21T04:12:18.466869104Z # Create a simple test script
2026-02-21T04:12:18.466871913Z test_script = job_dir / "test.sh"
2026-02-21T04:12:18.466877078Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.466880098Z echo "Hello from Docker container"
2026-02-21T04:12:18.46688288Z echo "Current directory: $(pwd)"
2026-02-21T04:12:18.466885724Z echo "Job directory contents:"
2026-02-21T04:12:18.466890435Z ls -la /job/
2026-02-21T04:12:18.466893678Z exit 0
2026-02-21T04:12:18.466896047Z """)
2026-02-21T04:12:18.466899055Z test_script.chmod(0o755)
2026-02-21T04:12:18.466902761Z
2026-02-21T04:12:18.466905761Z # Run the container using runnerlib CLI
2026-02-21T04:12:18.466908527Z result = subprocess.run(
2026-02-21T04:12:18.466911241Z [
2026-02-21T04:12:18.466920193Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.466923345Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.466926484Z "--job-command", "sh /job/test.sh",
2026-02-21T04:12:18.466929453Z "--code-dir", "/job",
2026-02-21T04:12:18.466932284Z "--job-dir", "/job",
2026-02-21T04:12:18.466934733Z ],
2026-02-21T04:12:18.466937785Z capture_output=True,
2026-02-21T04:12:18.466947859Z text=True,
2026-02-21T04:12:18.466951035Z cwd=work_dir, # Run from the temp directory
2026-02-21T04:12:18.466955099Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.466958172Z )
2026-02-21T04:12:18.466960182Z
2026-02-21T04:12:18.466963165Z print("STDOUT:", result.stdout)
2026-02-21T04:12:18.466965864Z print("STDERR:", result.stderr)
2026-02-21T04:12:18.466968984Z print("Return code:", result.returncode)
2026-02-21T04:12:18.466970716Z
2026-02-21T04:12:18.466975356Z # Verify the execution
2026-02-21T04:12:18.466985141Z> assert result.returncode == 0, f"Container execution failed with code {result.returncode}"
2026-02-21T04:12:18.466988685ZE AssertionError: Container execution failed with code 1
2026-02-21T04:12:18.466991476ZE assert 1 == 0
2026-02-21T04:12:18.466998821ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.467000955Z
2026-02-21T04:12:18.467003867Ztests/test_docker_execution.py:51: AssertionError
2026-02-21T04:12:18.467007762Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.467011072ZSTDOUT:
2026-02-21T04:12:18.467014169ZSTDERR: 2026-02-21T04:09:52.012995+00:00 Configuration validation failed:
2026-02-21T04:12:18.467017972Z2026-02-21T04:09:52.013106+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.467021258Z • system: docker is not available in PATH
2026-02-21T04:12:18.467024324Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.467026239Z
2026-02-21T04:12:18.467029065Z⚠️ Configuration warnings:
2026-02-21T04:12:18.467032002Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.467035719Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.467037453Z
2026-02-21T04:12:18.467039547Z
2026-02-21T04:12:18.467042362ZReturn code: 1
2026-02-21T04:12:18.467045881Z____________________ test_docker_with_environment_variables ____________________
2026-02-21T04:12:18.467047556Z
2026-02-21T04:12:18.467050735Z def test_docker_with_environment_variables():
2026-02-21T04:12:18.467053261Z """Test Docker execution with environment variables."""
2026-02-21T04:12:18.467055385Z
2026-02-21T04:12:18.467057977Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.4670626Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.467064325Z
2026-02-21T04:12:18.467069042Z # Create job directory
2026-02-21T04:12:18.467072034Z job_dir = work_dir / "job"
2026-02-21T04:12:18.46707506Z job_dir.mkdir()
2026-02-21T04:12:18.46707717Z
2026-02-21T04:12:18.467080567Z # Create script that uses environment variables
2026-02-21T04:12:18.467083388Z test_script = job_dir / "env_test.sh"
2026-02-21T04:12:18.467086442Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.467089859Z echo "TEST_VAR=$TEST_VAR"
2026-02-21T04:12:18.467094563Z echo "CUSTOM_VAR=$CUSTOM_VAR"
2026-02-21T04:12:18.467097937Z if [ "$TEST_VAR" = "test_value" ]; then
2026-02-21T04:12:18.467101008Z echo "Environment variables work!"
2026-02-21T04:12:18.467104023Z exit 0
2026-02-21T04:12:18.467107247Z else
2026-02-21T04:12:18.467110388Z echo "Environment variables failed"
2026-02-21T04:12:18.46711316Z exit 1
2026-02-21T04:12:18.467116083Z fi
2026-02-21T04:12:18.467119005Z """)
2026-02-21T04:12:18.46712212Z test_script.chmod(0o755)
2026-02-21T04:12:18.467124231Z
2026-02-21T04:12:18.467127403Z # Create env file (use relative path from working directory)
2026-02-21T04:12:18.467130505Z env_file = job_dir / "test.env"
2026-02-21T04:12:18.467133863Z env_file.write_text("""# Test environment
2026-02-21T04:12:18.467136559Z TEST_VAR=test_value
2026-02-21T04:12:18.475468818Z CUSTOM_VAR=custom_value
2026-02-21T04:12:18.475494614Z """)
2026-02-21T04:12:18.475500484Z
2026-02-21T04:12:18.475511055Z # Run with environment file - needs to be relative path starting with ./job/
2026-02-21T04:12:18.475517179Z result = subprocess.run(
2026-02-21T04:12:18.475522655Z [
2026-02-21T04:12:18.475529499Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.475535602Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.475541702Z "--job-command", "sh /job/env_test.sh",
2026-02-21T04:12:18.475578942Z "--code-dir", "/job",
2026-02-21T04:12:18.475585822Z "--job-dir", "/job",
2026-02-21T04:12:18.475593635Z "--job-env", "./job/test.env",
2026-02-21T04:12:18.475598835Z ],
2026-02-21T04:12:18.475604165Z capture_output=True,
2026-02-21T04:12:18.475610429Z text=True,
2026-02-21T04:12:18.475615732Z cwd=work_dir,
2026-02-21T04:12:18.475622392Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.475628035Z )
2026-02-21T04:12:18.475632099Z
2026-02-21T04:12:18.475638169Z print("ENV TEST STDOUT:", result.stdout)
2026-02-21T04:12:18.475643542Z print("ENV TEST STDERR:", result.stderr)
2026-02-21T04:12:18.475647335Z
2026-02-21T04:12:18.475655472Z> assert result.returncode == 0, f"Environment test failed with code {result.returncode}"
2026-02-21T04:12:18.475661832ZE AssertionError: Environment test failed with code 1
2026-02-21T04:12:18.475667175ZE assert 1 == 0
2026-02-21T04:12:18.47567887ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.475682923Z
2026-02-21T04:12:18.47569031Ztests/test_docker_execution.py:108: AssertionError
2026-02-21T04:12:18.475701966Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.475707486ZENV TEST STDOUT:
2026-02-21T04:12:18.475713216ZENV TEST STDERR: 2026-02-21T04:09:52.513571+00:00 Configuration validation failed:
2026-02-21T04:12:18.475719353Z2026-02-21T04:09:52.513652+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.475725516Z • system: docker is not available in PATH
2026-02-21T04:12:18.4757346Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.475738293Z
2026-02-21T04:12:18.475743313Z⚠️ Configuration warnings:
2026-02-21T04:12:18.475750763Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.47575806Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.475761483Z
2026-02-21T04:12:18.475764653Z
2026-02-21T04:12:18.475770476Z___________________________ test_docker_with_python ____________________________
2026-02-21T04:12:18.475782633Z
2026-02-21T04:12:18.47578795Z def test_docker_with_python():
2026-02-21T04:12:18.47579408Z """Test running Python code in a container."""
2026-02-21T04:12:18.475797816Z
2026-02-21T04:12:18.475803356Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.475808573Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.475812386Z
2026-02-21T04:12:18.47581727Z # Create job directory
2026-02-21T04:12:18.475822766Z job_dir = work_dir / "job"
2026-02-21T04:12:18.475828156Z job_dir.mkdir()
2026-02-21T04:12:18.475831993Z
2026-02-21T04:12:18.475838174Z # Create Python script
2026-02-21T04:12:18.475844127Z py_script = job_dir / "test.py"
2026-02-21T04:12:18.475848777Z py_script.write_text("""
2026-02-21T04:12:18.475853291Z import sys
2026-02-21T04:12:18.475857684Z import os
2026-02-21T04:12:18.475861841Z
2026-02-21T04:12:18.475867567Z print(f"Python version: {sys.version.split()[0]}")
2026-02-21T04:12:18.475873344Z print(f"Working directory: {os.getcwd()}")
2026-02-21T04:12:18.475881164Z print(f"Job files: {os.listdir('/job')}")
2026-02-21T04:12:18.475885434Z
2026-02-21T04:12:18.475890341Z # Test that we can write output
2026-02-21T04:12:18.475895621Z with open('/job/output.txt', 'w') as f:
2026-02-21T04:12:18.475901524Z f.write("Test output from Python container\\n")
2026-02-21T04:12:18.475905671Z
2026-02-21T04:12:18.475910307Z print("Successfully wrote output file")
2026-02-21T04:12:18.475914981Z sys.exit(0)
2026-02-21T04:12:18.475919544Z """)
2026-02-21T04:12:18.475922997Z
2026-02-21T04:12:18.475928934Z # Run Python container
2026-02-21T04:12:18.475934607Z result = subprocess.run(
2026-02-21T04:12:18.475939141Z [
2026-02-21T04:12:18.475945234Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.475951024Z "--runner-image", "python:3.11-alpine",
2026-02-21T04:12:18.475957017Z "--job-command", "python /job/test.py",
2026-02-21T04:12:18.475965311Z "--code-dir", "/job",
2026-02-21T04:12:18.475973121Z "--job-dir", "/job",
2026-02-21T04:12:18.475978057Z ],
2026-02-21T04:12:18.475982921Z capture_output=True,
2026-02-21T04:12:18.475988811Z text=True,
2026-02-21T04:12:18.475994804Z cwd=work_dir,
2026-02-21T04:12:18.476002462Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.476007668Z )
2026-02-21T04:12:18.476011485Z
2026-02-21T04:12:18.476022282Z print("PYTHON TEST STDOUT:", result.stdout)
2026-02-21T04:12:18.476027485Z print("PYTHON TEST STDERR:", result.stderr)
2026-02-21T04:12:18.476031692Z
2026-02-21T04:12:18.476039689Z> assert result.returncode == 0, f"Python container failed with code {result.returncode}"
2026-02-21T04:12:18.476046292ZE AssertionError: Python container failed with code 1
2026-02-21T04:12:18.476051585ZE assert 1 == 0
2026-02-21T04:12:18.476063302ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.11...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.476066405Z
2026-02-21T04:12:18.476073692Ztests/test_docker_execution.py:162: AssertionError
2026-02-21T04:12:18.476080629Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.476085609ZPYTHON TEST STDOUT:
2026-02-21T04:12:18.476135392ZPYTHON TEST STDERR: 2026-02-21T04:09:53.218561+00:00 Configuration validation failed:
2026-02-21T04:12:18.476150573Z2026-02-21T04:09:53.218678+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.476156906Z • system: docker is not available in PATH
2026-02-21T04:12:18.476162686Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.476166867Z
2026-02-21T04:12:18.476170584Z
2026-02-21T04:12:18.47617702Z_________________________ test_docker_failure_handling _________________________
2026-02-21T04:12:18.47618041Z
2026-02-21T04:12:18.47618659Z def test_docker_failure_handling():
2026-02-21T04:12:18.47619415Z """Test that container failures are properly reported."""
2026-02-21T04:12:18.47619777Z
2026-02-21T04:12:18.476203017Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.476207557Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.47621098Z
2026-02-21T04:12:18.476215367Z # Create job directory
2026-02-21T04:12:18.476220087Z job_dir = work_dir / "job"
2026-02-21T04:12:18.476224784Z job_dir.mkdir()
2026-02-21T04:12:18.47622853Z
2026-02-21T04:12:18.476233957Z # Create a script that fails
2026-02-21T04:12:18.47623933Z fail_script = job_dir / "fail.sh"
2026-02-21T04:12:18.476245124Z fail_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.47625022Z echo "This script will fail"
2026-02-21T04:12:18.476255947Z echo "Error: Something went wrong" >&2
2026-02-21T04:12:18.476261804Z exit 42
2026-02-21T04:12:18.47626632Z """)
2026-02-21T04:12:18.476272647Z fail_script.chmod(0o755)
2026-02-21T04:12:18.47627648Z
2026-02-21T04:12:18.47628218Z # Run container that should fail
2026-02-21T04:12:18.47628668Z result = subprocess.run(
2026-02-21T04:12:18.47629244Z [
2026-02-21T04:12:18.4762983Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.47630425Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.476309437Z "--job-command", "sh /job/fail.sh",
2026-02-21T04:12:18.47631538Z "--code-dir", "/job",
2026-02-21T04:12:18.476401048Z "--job-dir", "/job",
2026-02-21T04:12:18.476408785Z ],
2026-02-21T04:12:18.476414128Z capture_output=True,
2026-02-21T04:12:18.476419945Z text=True,
2026-02-21T04:12:18.476424558Z cwd=work_dir,
2026-02-21T04:12:18.476431751Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.476437005Z )
2026-02-21T04:12:18.476440665Z
2026-02-21T04:12:18.476445965Z print("FAIL TEST STDOUT:", result.stdout)
2026-02-21T04:12:18.476451468Z print("FAIL TEST STDERR:", result.stderr)
2026-02-21T04:12:18.476457221Z print("FAIL TEST RETURN CODE:", result.returncode)
2026-02-21T04:12:18.476460905Z
2026-02-21T04:12:18.476466428Z # Should propagate the exit code
2026-02-21T04:12:18.476478205Z> assert result.returncode == 42, f"Expected exit code 42, got {result.returncode}"
2026-02-21T04:12:18.476483425ZE AssertionError: Expected exit code 42, got 1
2026-02-21T04:12:18.476488531ZE assert 1 == 42
2026-02-21T04:12:18.476499999ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.476503122Z
2026-02-21T04:12:18.476508759Ztests/test_docker_execution.py:212: AssertionError
2026-02-21T04:12:18.476517379Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.476522026ZFAIL TEST STDOUT:
2026-02-21T04:12:18.476528529ZFAIL TEST STDERR: 2026-02-21T04:09:53.972497+00:00 Configuration validation failed:
2026-02-21T04:12:18.476534512Z2026-02-21T04:09:53.972611+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.476571852Z • system: docker is not available in PATH
2026-02-21T04:12:18.476580586Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.476584459Z
2026-02-21T04:12:18.476589392Z⚠️ Configuration warnings:
2026-02-21T04:12:18.476594279Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.476600182Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.476603572Z
2026-02-21T04:12:18.476606529Z
2026-02-21T04:12:18.476611859ZFAIL TEST RETURN CODE: 1
2026-02-21T04:12:18.476617409Z____________________________ test_docker_available _____________________________
2026-02-21T04:12:18.476620722Z
2026-02-21T04:12:18.476625686Z def test_docker_available():
2026-02-21T04:12:18.476631579Z """Test that Docker is available and working."""
2026-02-21T04:12:18.476636626Z> result = subprocess.run(
2026-02-21T04:12:18.476646196Z ["docker", "version", "--format", "{{.Server.Version}}"],
2026-02-21T04:12:18.476651242Z capture_output=True,
2026-02-21T04:12:18.476674573Z text=True
2026-02-21T04:12:18.476680123Z )
2026-02-21T04:12:18.476683057Z
2026-02-21T04:12:18.476687647Ztests/test_docker_execution.py:242:
2026-02-21T04:12:18.47669521Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.476699793Z/usr/local/lib/python3.13/subprocess.py:554: in run
2026-02-21T04:12:18.47670439Z with Popen(*popenargs, **kwargs) as process:
2026-02-21T04:12:18.476709967Z/usr/local/lib/python3.13/subprocess.py:1039: in __init__
2026-02-21T04:12:18.47671629Z self._execute_child(args, executable, preexec_fn, close_fds,
2026-02-21T04:12:18.47672133Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.476724537Z
2026-02-21T04:12:18.47673082Zself = <Popen: returncode: 255 args: ['docker', 'version', '--format', '{{.Server.V...>
2026-02-21T04:12:18.476737707Zargs = ['docker', 'version', '--format', '{{.Server.Version}}']
2026-02-21T04:12:18.47674353Zexecutable = b'docker', preexec_fn = None, close_fds = True, pass_fds = ()
2026-02-21T04:12:18.476749993Zcwd = None, env = None, startupinfo = None, creationflags = 0, shell = False
2026-02-21T04:12:18.47675607Zp2cread = -1, p2cwrite = -1, c2pread = 11, c2pwrite = 12, errread = 13
2026-02-21T04:12:18.476762997Zerrwrite = 14, restore_signals = True, gid = None, gids = None, uid = None
2026-02-21T04:12:18.476767943Zumask = -1, start_new_session = False, process_group = -1
2026-02-21T04:12:18.476771343Z
2026-02-21T04:12:18.476817103Z def _execute_child(self, args, executable, preexec_fn, close_fds,
2026-02-21T04:12:18.476823533Z pass_fds, cwd, env,
2026-02-21T04:12:18.47682895Z startupinfo, creationflags, shell,
2026-02-21T04:12:18.476835098Z p2cread, p2cwrite,
2026-02-21T04:12:18.476840331Z c2pread, c2pwrite,
2026-02-21T04:12:18.476851501Z errread, errwrite,
2026-02-21T04:12:18.476857144Z restore_signals,
2026-02-21T04:12:18.476861954Z gid, gids, uid, umask,
2026-02-21T04:12:18.476867254Z start_new_session, process_group):
2026-02-21T04:12:18.476873521Z """Execute program (POSIX version)"""
2026-02-21T04:12:18.476877418Z
2026-02-21T04:12:18.476883248Z if isinstance(args, (str, bytes)):
2026-02-21T04:12:18.476888511Z args = [args]
2026-02-21T04:12:18.476894058Z elif isinstance(args, os.PathLike):
2026-02-21T04:12:18.476899175Z if shell:
2026-02-21T04:12:18.476904838Z raise TypeError('path-like args is not allowed when '
2026-02-21T04:12:18.476913805Z 'shell is [REDACTED]')
2026-02-21T04:12:18.476918148Z args = [args]
2026-02-21T04:12:18.476923235Z else:
2026-02-21T04:12:18.476928065Z args = list(args)
2026-02-21T04:12:18.476931308Z
2026-02-21T04:12:18.476939565Z if shell:
2026-02-21T04:12:18.476946002Z # On Android the default shell is at '/system/bin/sh'.
2026-02-21T04:12:18.476951498Z unix_shell = ('/system/bin/sh' if
2026-02-21T04:12:18.476957818Z hasattr(sys, 'getandroidapilevel') else '/bin/sh')
2026-02-21T04:12:18.476963642Z args = [unix_shell, "-c"] + args
2026-02-21T04:12:18.476968538Z if executable:
2026-02-21T04:12:18.476973455Z args[0] = executable
2026-02-21T04:12:18.476977025Z
2026-02-21T04:12:18.476982558Z if executable is None:
2026-02-21T04:12:18.476987562Z executable = args[0]
2026-02-21T04:12:18.476991122Z
2026-02-21T04:12:18.476997578Z sys.audit("subprocess.Popen", executable, args, cwd, env)
2026-02-21T04:12:18.477000843Z
2026-02-21T04:12:18.477005643Z if (_USE_POSIX_SPAWN
2026-02-21T04:12:18.477010943Z and os.path.dirname(executable)
2026-02-21T04:12:18.47701605Z and preexec_fn is None
2026-02-21T04:12:18.477022493Z and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
2026-02-21T04:12:18.4770275Z and not pass_fds
2026-02-21T04:12:18.477033256Z and cwd is None
2026-02-21T04:12:18.47703876Z and (p2cread == -1 or p2cread > 2)
2026-02-21T04:12:18.47704483Z and (c2pwrite == -1 or c2pwrite > 2)
2026-02-21T04:12:18.47705052Z and (errwrite == -1 or errwrite > 2)
2026-02-21T04:12:18.47705563Z and not start_new_session
2026-02-21T04:12:18.477060713Z and process_group == -1
2026-02-21T04:12:18.477065576Z and gid is None
2026-02-21T04:12:18.477070176Z and gids is None
2026-02-21T04:12:18.477075073Z and uid is None
2026-02-21T04:12:18.477079856Z and umask < 0):
2026-02-21T04:12:18.47708615Z self._posix_spawn(args, executable, env, restore_signals, close_fds,
2026-02-21T04:12:18.47709223Z p2cread, p2cwrite,
2026-02-21T04:12:18.47709825Z c2pread, c2pwrite,
2026-02-21T04:12:18.477103513Z errread, errwrite)
2026-02-21T04:12:18.47710871Z return
2026-02-21T04:12:18.47711226Z
2026-02-21T04:12:18.477118603Z orig_executable = executable
2026-02-21T04:12:18.47712235Z
2026-02-21T04:12:18.4771292Z # For transferring possible exec failure from child to parent.
2026-02-21T04:12:18.47713477Z # Data format: "exception name:hex errno:description"
2026-02-21T04:12:18.477141913Z # Pickle is not used; it is complex and involves memory allocation.
2026-02-21T04:12:18.477146896Z errpipe_read, errpipe_write = os.pipe()
2026-02-21T04:12:18.477153513Z # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
2026-02-21T04:12:18.477158243Z low_fds_to_close = []
2026-02-21T04:12:18.477163183Z while errpipe_write < 3:
2026-02-21T04:12:18.477168381Z low_fds_to_close.append(errpipe_write)
2026-02-21T04:12:18.477173501Z errpipe_write = os.dup(errpipe_write)
2026-02-21T04:12:18.477178864Z for low_fd in low_fds_to_close:
2026-02-21T04:12:18.477184057Z os.close(low_fd)
2026-02-21T04:12:18.477189227Z try:
2026-02-21T04:12:18.477194634Z try:
2026-02-21T04:12:18.477200337Z # We must avoid complex work that could involve
2026-02-21T04:12:18.477207151Z # malloc or free in the child process to avoid
2026-02-21T04:12:18.477216704Z # potential deadlocks, thus we do all this here.
2026-02-21T04:12:18.477222401Z # and pass it to fork_exec()
2026-02-21T04:12:18.477226517Z
2026-02-21T04:12:18.477231941Z if env is not None:
2026-02-21T04:12:18.477237301Z env_list = []
2026-02-21T04:12:18.477245867Z for k, v in env.items():
2026-02-21T04:12:18.477251794Z k = os.fsencode(k)
2026-02-21T04:12:18.477257271Z if b'=' in k:
2026-02-21T04:12:18.477264217Z raise ValueError("illegal environment variable name")
2026-02-21T04:12:18.477272377Z env_list.append(k + b'=' + os.fsencode(v))
2026-02-21T04:12:18.477276857Z else:
2026-02-21T04:12:18.477281787Z env_list = None # Use execv instead of execve.
2026-02-21T04:12:18.477286681Z executable = os.fsencode(executable)
2026-02-21T04:12:18.477291681Z if os.path.dirname(executable):
2026-02-21T04:12:18.477296711Z executable_list = (executable,)
2026-02-21T04:12:18.477302001Z else:
2026-02-21T04:12:18.477307287Z # This matches the behavior of os._execvpe().
2026-02-21T04:12:18.477313124Z executable_list = tuple(
2026-02-21T04:12:18.477318827Z os.path.join(os.fsencode(dir), executable)
2026-02-21T04:12:18.477324927Z for dir in os.get_exec_path(env))
2026-02-21T04:12:18.477330297Z fds_to_keep = set(pass_fds)
2026-02-21T04:12:18.477336658Z fds_to_keep.add(errpipe_write)
2026-02-21T04:12:18.477341972Z self.pid = _fork_exec(
2026-02-21T04:12:18.477347528Z args, executable_list,
2026-02-21T04:12:18.477391492Z close_fds, tuple(sorted(map(int, fds_to_keep))),
2026-02-21T04:12:18.477398152Z cwd, env_list,
2026-02-21T04:12:18.477403415Z p2cread, p2cwrite, c2pread, c2pwrite,
2026-02-21T04:12:18.477408302Z errread, errwrite,
2026-02-21T04:12:18.477413692Z errpipe_read, errpipe_write,
2026-02-21T04:12:18.477419908Z restore_signals, start_new_session,
2026-02-21T04:12:18.477425022Z process_group, gid, gids, uid, umask,
2026-02-21T04:12:18.477431638Z preexec_fn, _USE_VFORK)
2026-02-21T04:12:18.477436818Z self._child_created = True
2026-02-21T04:12:18.477442988Z finally:
2026-02-21T04:12:18.477448442Z # be sure the FD is closed no matter what
2026-02-21T04:12:18.477454962Z os.close(errpipe_write)
2026-02-21T04:12:18.477459228Z
2026-02-21T04:12:18.477464822Z self._close_pipe_fds(p2cread, p2cwrite,
2026-02-21T04:12:18.477470265Z c2pread, c2pwrite,
2026-02-21T04:12:18.477475688Z errread, errwrite)
2026-02-21T04:12:18.477479368Z
2026-02-21T04:12:18.477485082Z # Wait for exec to fail or succeed; possibly raising an
2026-02-21T04:12:18.477490452Z # exception (limited in size)
2026-02-21T04:12:18.477495835Z errpipe_data = bytearray()
2026-02-21T04:12:18.477501122Z while True:
2026-02-21T04:12:18.477506743Z part = os.read(errpipe_read, 50000)
2026-02-21T04:12:18.477511333Z errpipe_data += part
2026-02-21T04:12:18.477516433Z if not part or len(errpipe_data) > 50000:
2026-02-21T04:12:18.477521039Z break
2026-02-21T04:12:18.477526229Z finally:
2026-02-21T04:12:18.477531079Z # be sure the FD is closed no matter what
2026-02-21T04:12:18.477536209Z os.close(errpipe_read)
2026-02-21T04:12:18.477539649Z
2026-02-21T04:12:18.477566729Z if errpipe_data:
2026-02-21T04:12:18.477572279Z try:
2026-02-21T04:12:18.477577583Z pid, sts = os.waitpid(self.pid, 0)
2026-02-21T04:12:18.477582523Z if pid == self.pid:
2026-02-21T04:12:18.477587979Z self._handle_exitstatus(sts)
2026-02-21T04:12:18.483289298Z else:
2026-02-21T04:12:18.483296967Z self.returncode = sys.maxsize
2026-02-21T04:12:18.483300401Z except ChildProcessError:
2026-02-21T04:12:18.483310804Z pass
2026-02-21T04:12:18.48331327Z
2026-02-21T04:12:18.483317261Z try:
2026-02-21T04:12:18.48332121Z exception_name, hex_errno, err_msg = (
2026-02-21T04:12:18.48332465Z errpipe_data.split(b':', 2))
2026-02-21T04:12:18.483328221Z # The encoding here should match the encoding
2026-02-21T04:12:18.483331781Z # written in by the subprocess implementations
2026-02-21T04:12:18.48333495Z # like _posixsubprocess
2026-02-21T04:12:18.483340919Z err_msg = err_msg.decode()
2026-02-21T04:12:18.483344256Z except ValueError:
2026-02-21T04:12:18.48334755Z exception_name = b'SubprocessError'
2026-02-21T04:12:18.48335187Z hex_errno = b'0'
2026-02-21T04:12:18.483355878Z err_msg = 'Bad exception data from child: {!r}'.format(
2026-02-21T04:12:18.483358644Z bytes(errpipe_data))
2026-02-21T04:12:18.483361632Z child_exception_type = getattr(
2026-02-21T04:12:18.483364573Z builtins, exception_name.decode('ascii'),
2026-02-21T04:12:18.483367516Z SubprocessError)
2026-02-21T04:12:18.483371246Z if issubclass(child_exception_type, OSError) and hex_errno:
2026-02-21T04:12:18.483374605Z errno_num = int(hex_errno, 16)
2026-02-21T04:12:18.483377608Z if err_msg == "noexec:chdir":
2026-02-21T04:12:18.483380965Z err_msg = ""
2026-02-21T04:12:18.483384409Z # The error must be from chdir(cwd).
2026-02-21T04:12:18.48338742Z err_filename = cwd
2026-02-21T04:12:18.483392451Z elif err_msg == "noexec":
2026-02-21T04:12:18.483395574Z err_msg = ""
2026-02-21T04:12:18.483398546Z err_filename = None
2026-02-21T04:12:18.483404122Z else:
2026-02-21T04:12:18.483407708Z err_filename = orig_executable
2026-02-21T04:12:18.48341038Z if errno_num != 0:
2026-02-21T04:12:18.483413743Z err_msg = os.strerror(errno_num)
2026-02-21T04:12:18.483416853Z if err_filename is not None:
2026-02-21T04:12:18.483420973Z> raise child_exception_type(errno_num, err_msg, err_filename)
2026-02-21T04:12:18.483425493ZE FileNotFoundError: [Errno 2] No such file or directory: 'docker'
2026-02-21T04:12:18.483427674Z
2026-02-21T04:12:18.483431042Z/usr/local/lib/python3.13/subprocess.py:1991: FileNotFoundError
2026-02-21T04:12:18.483435057Z____________________ test_container_with_working_directory _____________________
2026-02-21T04:12:18.483437506Z
2026-02-21T04:12:18.48344072Z def test_container_with_working_directory():
2026-02-21T04:12:18.483448014Z """Test that working directory is set correctly in container."""
2026-02-21T04:12:18.483451391Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.483454534Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.483456725Z
2026-02-21T04:12:18.48346019Z # Create job directory with subdirectory
2026-02-21T04:12:18.483463354Z job_dir = work_dir / "job"
2026-02-21T04:12:18.483466885Z job_dir.mkdir()
2026-02-21T04:12:18.483469976Z sub_dir = job_dir / "subdir"
2026-02-21T04:12:18.483472596Z sub_dir.mkdir()
2026-02-21T04:12:18.483474813Z
2026-02-21T04:12:18.483477914Z # Create test file in subdirectory
2026-02-21T04:12:18.483481087Z test_file = sub_dir / "data.txt"
2026-02-21T04:12:18.483484053Z test_file.write_text("test data")
2026-02-21T04:12:18.483486145Z
2026-02-21T04:12:18.483489748Z # Create script that checks working directory
2026-02-21T04:12:18.483492828Z test_script = job_dir / "pwd_test.sh"
2026-02-21T04:12:18.483495951Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.483498471Z echo "Current directory: $(pwd)"
2026-02-21T04:12:18.483503173Z echo "Directory contents:"
2026-02-21T04:12:18.483506368Z ls -la
2026-02-21T04:12:18.483510865Z echo "Subdir exists:"
2026-02-21T04:12:18.483513739Z ls -d subdir
2026-02-21T04:12:18.48351667Z exit 0
2026-02-21T04:12:18.483519097Z """)
2026-02-21T04:12:18.483522296Z test_script.chmod(0o755)
2026-02-21T04:12:18.483524691Z
2026-02-21T04:12:18.483560305Z # Run with working directory set to /job
2026-02-21T04:12:18.483563437Z result = subprocess.run(
2026-02-21T04:12:18.483566252Z [
2026-02-21T04:12:18.483569477Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.48357304Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.483577668Z "--job-command", "sh pwd_test.sh", # Note: no /job/ prefix since we're in that dir
2026-02-21T04:12:18.483580811Z "--code-dir", "/job",
2026-02-21T04:12:18.483583855Z "--job-dir", "/job",
2026-02-21T04:12:18.483587125Z ],
2026-02-21T04:12:18.483590274Z capture_output=True,
2026-02-21T04:12:18.483596241Z text=True,
2026-02-21T04:12:18.483599291Z cwd=work_dir,
2026-02-21T04:12:18.483603355Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.483606471Z )
2026-02-21T04:12:18.483608751Z
2026-02-21T04:12:18.483612014Z print("WORKING DIR TEST:", result.stdout)
2026-02-21T04:12:18.483615251Z print("STDERR:", result.stderr)
2026-02-21T04:12:18.483617192Z
2026-02-21T04:12:18.483620851Z> assert result.returncode == 0, f"Container execution failed: {result.stderr}"
2026-02-21T04:12:18.483625097ZE AssertionError: Container execution failed: 2026-02-21T04:09:54.899122+00:00 Configuration validation failed:
2026-02-21T04:12:18.483628771ZE 2026-02-21T04:09:54.899219+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.483631825ZE • system: docker is not available in PATH
2026-02-21T04:12:18.483635468ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.483638249ZE
2026-02-21T04:12:18.483641562ZE ⚠️ Configuration warnings:
2026-02-21T04:12:18.483645234ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.483648703ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.483651517ZE
2026-02-21T04:12:18.48365424ZE
2026-02-21T04:12:18.483657074ZE assert 1 == 0
2026-02-21T04:12:18.483662855ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.483664814Z
2026-02-21T04:12:18.483668468Ztests/test_docker_execution.py:296: AssertionError
2026-02-21T04:12:18.483672291Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.483675178ZWORKING DIR TEST:
2026-02-21T04:12:18.483678488ZSTDERR: 2026-02-21T04:09:54.899122+00:00 Configuration validation failed:
2026-02-21T04:12:18.483682411Z2026-02-21T04:09:54.899219+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.4836866Z • system: docker is not available in PATH
2026-02-21T04:12:18.483689985Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.483691729Z
2026-02-21T04:12:18.483694784Z⚠️ Configuration warnings:
2026-02-21T04:12:18.483697972Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.483701103Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.48370261Z
2026-02-21T04:12:18.483704338Z
2026-02-21T04:12:18.483706943Z______________________________ test_dry_run_mode _______________________________
2026-02-21T04:12:18.483708915Z
2026-02-21T04:12:18.483711666Z def test_dry_run_mode():
2026-02-21T04:12:18.483722033Z """Test dry-run mode doesn't actually execute container."""
2026-02-21T04:12:18.483725458Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.483728073Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.483729992Z
2026-02-21T04:12:18.483733066Z # Create job directory
2026-02-21T04:12:18.483735749Z job_dir = work_dir / "job"
2026-02-21T04:12:18.483738526Z job_dir.mkdir()
2026-02-21T04:12:18.48374098Z
2026-02-21T04:12:18.483743984Z # Create a script that should NOT run
2026-02-21T04:12:18.483746677Z test_script = job_dir / "should_not_run.sh"
2026-02-21T04:12:18.483749215Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.483752032Z echo "ERROR: This should not execute in dry-run mode!"
2026-02-21T04:12:18.483755041Z exit 1
2026-02-21T04:12:18.483757832Z """)
2026-02-21T04:12:18.483760603Z test_script.chmod(0o755)
2026-02-21T04:12:18.483762452Z
2026-02-21T04:12:18.48376533Z # Run in dry-run mode
2026-02-21T04:12:18.483768229Z result = subprocess.run(
2026-02-21T04:12:18.483770955Z [
2026-02-21T04:12:18.483774101Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.48377746Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.483781004Z "--job-command", "sh /job/should_not_run.sh",
2026-02-21T04:12:18.483784046Z "--code-dir", "/job",
2026-02-21T04:12:18.483787244Z "--job-dir", "/job",
2026-02-21T04:12:18.483790484Z "--dry-run",
2026-02-21T04:12:18.483793175Z ],
2026-02-21T04:12:18.483795993Z capture_output=True,
2026-02-21T04:12:18.483798866Z text=True,
2026-02-21T04:12:18.483803593Z cwd=work_dir,
2026-02-21T04:12:18.483807429Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.483810323Z )
2026-02-21T04:12:18.48381236Z
2026-02-21T04:12:18.483815577Z print("DRY RUN OUTPUT:", result.stdout)
2026-02-21T04:12:18.48381837Z print("DRY RUN STDERR:", result.stderr)
2026-02-21T04:12:18.483820817Z
2026-02-21T04:12:18.483824058Z> assert result.returncode == 0, f"Dry-run failed: {result.stderr}"
2026-02-21T04:12:18.483827804ZE AssertionError: Dry-run failed: 2026-02-21T04:09:55.378357+00:00 Configuration validation failed:
2026-02-21T04:12:18.483831049ZE 2026-02-21T04:09:55.378444+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.483834312ZE • system: docker is not available in PATH
2026-02-21T04:12:18.483838497ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.483841577ZE
2026-02-21T04:12:18.483861471ZE ⚠️ Configuration warnings:
2026-02-21T04:12:18.483865582ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.483871259ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.483874185ZE
2026-02-21T04:12:18.483876715ZE
2026-02-21T04:12:18.483879581ZE assert 1 == 0
2026-02-21T04:12:18.483884519ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.483886893Z
2026-02-21T04:12:18.483890312Ztests/test_docker_execution.py:338: AssertionError
2026-02-21T04:12:18.483893627Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.483896142ZDRY RUN OUTPUT:
2026-02-21T04:12:18.483899302ZDRY RUN STDERR: 2026-02-21T04:09:55.378357+00:00 Configuration validation failed:
2026-02-21T04:12:18.483902885Z2026-02-21T04:09:55.378444+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.483906081Z • system: docker is not available in PATH
2026-02-21T04:12:18.483909001Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.483912964Z
2026-02-21T04:12:18.483937576Z⚠️ Configuration warnings:
2026-02-21T04:12:18.48394069Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.483946012Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.483947976Z
2026-02-21T04:12:18.483949724Z
2026-02-21T04:12:18.483952993Z_____________________________ test_node_container ______________________________
2026-02-21T04:12:18.483954715Z
2026-02-21T04:12:18.483957627Z def test_node_container():
2026-02-21T04:12:18.483966684Z """Test Node.js container execution."""
2026-02-21T04:12:18.483969752Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.483972202Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.483974247Z
2026-02-21T04:12:18.483977455Z # Create job directory
2026-02-21T04:12:18.483980296Z job_dir = work_dir / "job"
2026-02-21T04:12:18.48398341Z job_dir.mkdir()
2026-02-21T04:12:18.483985458Z
2026-02-21T04:12:18.483988136Z # Create Node.js script
2026-02-21T04:12:18.483991027Z js_script = job_dir / "test.js"
2026-02-21T04:12:18.483993682Z js_script.write_text("""
2026-02-21T04:12:18.483996599Z console.log('Node version:', process.version);
2026-02-21T04:12:18.483999179Z console.log('Platform:', process.platform);
2026-02-21T04:12:18.484002081Z console.log('Working dir:', process.cwd());
2026-02-21T04:12:18.484004438Z process.exit(0);
2026-02-21T04:12:18.484006642Z """)
2026-02-21T04:12:18.484008558Z
2026-02-21T04:12:18.48401129Z # Run Node container
2026-02-21T04:12:18.484013947Z result = subprocess.run(
2026-02-21T04:12:18.484016393Z [
2026-02-21T04:12:18.484019741Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.484023163Z "--runner-image", "node:18-alpine",
2026-02-21T04:12:18.4840261Z "--job-command", "node /job/test.js",
2026-02-21T04:12:18.484030828Z "--code-dir", "/job",
2026-02-21T04:12:18.484033497Z "--job-dir", "/job",
2026-02-21T04:12:18.484036391Z ],
2026-02-21T04:12:18.484039311Z capture_output=True,
2026-02-21T04:12:18.484042303Z text=True,
2026-02-21T04:12:18.484045219Z cwd=work_dir,
2026-02-21T04:12:18.484048639Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.484051574Z )
2026-02-21T04:12:18.484053528Z
2026-02-21T04:12:18.484056673Z print("NODE TEST OUTPUT:", result.stdout)
2026-02-21T04:12:18.484059794Z print("NODE TEST STDERR:", result.stderr)
2026-02-21T04:12:18.484062042Z
2026-02-21T04:12:18.48406515Z> assert result.returncode == 0, f"Node container failed: {result.stderr}"
2026-02-21T04:12:18.48406913ZE AssertionError: Node container failed: 2026-02-21T04:09:56.108515+00:00 Configuration validation failed:
2026-02-21T04:12:18.484072343ZE 2026-02-21T04:09:56.108623+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.484075231ZE • system: docker is not available in PATH
2026-02-21T04:12:18.484078644ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.484081196ZE
2026-02-21T04:12:18.484083784ZE
2026-02-21T04:12:18.484086487ZE assert 1 == 0
2026-02-21T04:12:18.484091587ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'node:18-alp...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.484093528Z
2026-02-21T04:12:18.484096479Ztests/test_docker_execution.py:382: AssertionError
2026-02-21T04:12:18.484099313Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.484102967ZNODE TEST OUTPUT:
2026-02-21T04:12:18.484106151ZNODE TEST STDERR: 2026-02-21T04:09:56.108515+00:00 Configuration validation failed:
2026-02-21T04:12:18.484109464Z2026-02-21T04:09:56.108623+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.484112204Z • system: docker is not available in PATH
2026-02-21T04:12:18.48411524Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.484117177Z
2026-02-21T04:12:18.484118617Z
2026-02-21T04:12:18.484122014Z____________________ test_container_with_multiple_env_vars _____________________
2026-02-21T04:12:18.484123782Z
2026-02-21T04:12:18.48412638Z def test_container_with_multiple_env_vars():
2026-02-21T04:12:18.484129819Z """Test passing multiple environment variables via CLI."""
2026-02-21T04:12:18.484132548Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.484135539Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.48413749Z
2026-02-21T04:12:18.484139997Z # Create job directory
2026-02-21T04:12:18.484142651Z job_dir = work_dir / "job"
2026-02-21T04:12:18.484145542Z job_dir.mkdir()
2026-02-21T04:12:18.484147573Z
2026-02-21T04:12:18.484150287Z # Create test script
2026-02-21T04:12:18.484170702Z test_script = job_dir / "multi_env.sh"
2026-02-21T04:12:18.484173916Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.484176636Z echo "VAR1=$VAR1"
2026-02-21T04:12:18.484179241Z echo "VAR2=$VAR2"
2026-02-21T04:12:18.484181694Z echo "VAR3=$VAR3"
2026-02-21T04:12:18.484185031Z if [ "$VAR1" = "value1" ] && [ "$VAR2" = "value2" ] && [ "$VAR3" = "value3" ]; then
2026-02-21T04:12:18.484187797Z echo "All environment variables set correctly!"
2026-02-21T04:12:18.484190382Z exit 0
2026-02-21T04:12:18.484192917Z else
2026-02-21T04:12:18.484195998Z echo "Environment variables not set correctly"
2026-02-21T04:12:18.484198991Z exit 1
2026-02-21T04:12:18.484203808Z fi
2026-02-21T04:12:18.484206366Z """)
2026-02-21T04:12:18.487847643Z test_script.chmod(0o755)
2026-02-21T04:12:18.487857653Z
2026-02-21T04:12:18.487867617Z # Run with multiple env vars in a single --job-env (newline separated)
2026-02-21T04:12:18.487875993Z result = subprocess.run(
2026-02-21T04:12:18.48788137Z [
2026-02-21T04:12:18.48788786Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.487895367Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.48790816Z "--job-command", "sh /job/multi_env.sh",
2026-02-21T04:12:18.487914627Z "--code-dir", "/job",
2026-02-21T04:12:18.487920623Z "--job-dir", "/job",
2026-02-21T04:12:18.487927267Z "--job-env", "VAR1=value1\nVAR2=value2\nVAR3=value3",
2026-02-21T04:12:18.487932983Z ],
2026-02-21T04:12:18.487938387Z capture_output=True,
2026-02-21T04:12:18.48794381Z text=True,
2026-02-21T04:12:18.48794926Z cwd=work_dir,
2026-02-21T04:12:18.487956953Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.487972441Z )
2026-02-21T04:12:18.487976734Z
2026-02-21T04:12:18.487984011Z> assert result.returncode == 0, f"Multi-env test failed: {result.stderr}"
2026-02-21T04:12:18.487992001ZE AssertionError: Multi-env test failed: 2026-02-21T04:09:56.896037+00:00 Configuration validation failed:
2026-02-21T04:12:18.487999118ZE 2026-02-21T04:09:56.896142+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.488004351ZE • system: docker is not available in PATH
2026-02-21T04:12:18.488010551ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.488015094ZE
2026-02-21T04:12:18.488023691ZE ⚠️ Configuration warnings:
2026-02-21T04:12:18.488030224ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.488036521ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.488041521ZE
2026-02-21T04:12:18.488045918ZE
2026-02-21T04:12:18.488051034ZE assert 1 == 0
2026-02-21T04:12:18.488062608ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.488066101Z
2026-02-21T04:12:18.488072718Ztests/test_docker_execution.py:429: AssertionError
2026-02-21T04:12:18.488079034Z________________________ test_selective_secret_masking _________________________
2026-02-21T04:12:18.488082428Z
2026-02-21T04:12:18.488089091Z def test_selective_secret_masking():
2026-02-21T04:12:18.488096214Z """Test selective masking of secrets using --secret-values-list."""
2026-02-21T04:12:18.488102004Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.488107908Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.488112438Z
2026-02-21T04:12:18.488121084Z # Create job directory
2026-02-21T04:12:18.488126094Z job_dir = work_dir / "job"
2026-02-21T04:12:18.488131274Z job_dir.mkdir()
2026-02-21T04:12:18.488134955Z
2026-02-21T04:12:18.488141725Z # Create test script that prints environment variables
2026-02-21T04:12:18.488147376Z test_script = job_dir / "selective_test.sh"
2026-02-21T04:12:18.488152806Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.488157726Z echo "API_KEY=$API_KEY"
2026-02-21T04:12:18.488162806Z echo "PUBLIC_VALUE=$PUBLIC_VALUE"
2026-02-21T04:12:18.488168069Z echo "SECRET_TOKEN=$SECRET_TOKEN"
2026-02-21T04:12:18.488172852Z echo "CONFIG_PATH=$CONFIG_PATH"
2026-02-21T04:12:18.488177619Z exit 0
2026-02-21T04:12:18.488182369Z """)
2026-02-21T04:12:18.488187546Z test_script.chmod(0o755)
2026-02-21T04:12:18.488191412Z
2026-02-21T04:12:18.488198459Z # Run with environment vars and explicitly mark only some as secrets
2026-02-21T04:12:18.488203942Z result = subprocess.run(
2026-02-21T04:12:18.488209006Z [
2026-02-21T04:12:18.488215033Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.488220903Z "--runner-image", "alpine:latest",
2026-02-21T04:12:18.488227556Z "--job-command", "sh /job/selective_test.sh",
2026-02-21T04:12:18.488233153Z "--code-dir", "/job",
2026-02-21T04:12:18.488238733Z "--job-dir", "/job",
2026-02-21T04:12:18.488246999Z "--job-env", "API_KEY=my-secret-api-key-123\nPUBLIC_VALUE=not-a-secret\nSECRET_TOKEN=super-secret-token\nCONFIG_PATH=/etc/config",
2026-02-21T04:12:18.488255036Z "--secret-values-list", "my-secret-api-key-123,super-secret-token", # Only mask these specific values
2026-02-21T04:12:18.488260643Z ],
2026-02-21T04:12:18.488266273Z capture_output=True,
2026-02-21T04:12:18.488271883Z text=True,
2026-02-21T04:12:18.488277346Z cwd=work_dir,
2026-02-21T04:12:18.488284106Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.48829262Z )
2026-02-21T04:12:18.488296971Z
2026-02-21T04:12:18.488304471Z> assert result.returncode == 0, f"Selective masking test failed: {result.stderr}"
2026-02-21T04:12:18.488313694ZE AssertionError: Selective masking test failed: 2026-02-21T04:09:57.640289+00:00 Configuration validation failed:
2026-02-21T04:12:18.492258645ZE 2026-02-21T04:09:57.640454+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.492265145ZE • system: docker is not available in PATH
2026-02-21T04:12:18.492269587ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.49227344ZE
2026-02-21T04:12:18.492277633ZE ⚠️ Configuration warnings:
2026-02-21T04:12:18.492281568ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T04:12:18.49229336ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T04:12:18.492296277ZE
2026-02-21T04:12:18.49229897ZE
2026-02-21T04:12:18.492301785ZE assert 1 == 0
2026-02-21T04:12:18.492307557ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'alpine:late...mage: Using 'latest' tag or no tag specified\n 💡 Consider using a specific version tag for reproducible builds\n\n").returncode
2026-02-21T04:12:18.492309699Z
2026-02-21T04:12:18.492313507Ztests/test_docker_execution.py:474: AssertionError
2026-02-21T04:12:18.492317051Z________________________ test_value_printed_then_masked ________________________
2026-02-21T04:12:18.492342929Z
2026-02-21T04:12:18.492346842Z def test_value_printed_then_masked():
2026-02-21T04:12:18.492351834Z """Test that dynamic registration masks values in subsequent output.
2026-02-21T04:12:18.492354151Z
2026-02-21T04:12:18.492358463Z Due to the nature of streaming output and socket communication, we cannot
2026-02-21T04:12:18.492363154Z guarantee that output printed immediately before registration will be unmasked.
2026-02-21T04:12:18.492366143Z However, we CAN demonstrate that:
2026-02-21T04:12:18.492370074Z 1. Values not in the initial secrets list are not masked initially
2026-02-21T04:12:18.492373962Z 2. After dynamic registration, those values ARE masked in new output
2026-02-21T04:12:18.49237648Z """
2026-02-21T04:12:18.492378698Z
2026-02-21T04:12:18.492382274Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.492385726Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.49238788Z
2026-02-21T04:12:18.492390794Z # Create job directory
2026-02-21T04:12:18.492393549Z job_dir = work_dir / "job"
2026-02-21T04:12:18.492396645Z job_dir.mkdir()
2026-02-21T04:12:18.492398711Z
2026-02-21T04:12:18.492401975Z # Create a script that demonstrates dynamic masking
2026-02-21T04:12:18.492405098Z test_script = job_dir / "show_masking.py"
2026-02-21T04:12:18.492408314Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T04:12:18.492411492Z import socket
2026-02-21T04:12:18.49241438Z import json
2026-02-21T04:12:18.492416989Z import struct
2026-02-21T04:12:18.492420272Z import os
2026-02-21T04:12:18.492422922Z import time
2026-02-21T04:12:18.492425628Z import sys
2026-02-21T04:12:18.492428485Z import subprocess
2026-02-21T04:12:18.492430899Z
2026-02-21T04:12:18.492434143Z # This is our sensitive value that we'll get at runtime
2026-02-21T04:12:18.492437086Z api_token = "UNIQUEVALUE-abc123xyz789-ENDUNIQUE"
2026-02-21T04:12:18.492439012Z
2026-02-21T04:12:18.492441691Z print("=" * 50)
2026-02-21T04:12:18.492444852Z print("DEMONSTRATION OF DYNAMIC SECRET MASKING")
2026-02-21T04:12:18.492447414Z print("=" * 50)
2026-02-21T04:12:18.492449822Z
2026-02-21T04:12:18.492454123Z # First, show that without registration, the value appears in subprocess output
2026-02-21T04:12:18.492460031Z print("\\n1. Running subprocess BEFORE registration:")
2026-02-21T04:12:18.492462635Z sys.stdout.flush()
2026-02-21T04:12:18.492465665Z result = subprocess.run(
2026-02-21T04:12:18.492470057Z ["sh", "-c", f"echo 'Token is: {api_token}'"],
2026-02-21T04:12:18.49247302Z capture_output=True,
2026-02-21T04:12:18.492883039Z text=True
2026-02-21T04:12:18.492892019Z )
2026-02-21T04:12:18.492899596Z print(f" Subprocess output: {result.stdout.strip()}")
2026-02-21T04:12:18.492905396Z sys.stdout.flush()
2026-02-21T04:12:18.492910129Z
2026-02-21T04:12:18.492916249Z # Now register this value as a secret
2026-02-21T04:12:18.492930156Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T04:12:18.492936356Z if socket_path:
2026-02-21T04:12:18.492942603Z print(f"\\n2. Registering secret via socket...")
2026-02-21T04:12:18.492947399Z sys.stdout.flush()
2026-02-21T04:12:18.492951406Z
2026-02-21T04:12:18.492958183Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T04:12:18.492970086Z sock.connect(socket_path)
2026-02-21T04:12:18.492977816Z msg = json.dumps({'action': 'register', 'secrets': [api_token]}).encode()
2026-02-21T04:12:18.492983827Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T04:12:18.492988837Z sock.send(msg)
2026-02-21T04:12:18.492994797Z response = sock.recv(1024)
2026-02-21T04:12:18.49300215Z print(f" Registration response: {response.decode().strip()}")
2026-02-21T04:12:18.493007867Z sock.close()
2026-02-21T04:12:18.493011704Z
2026-02-21T04:12:18.49301713Z # Give it a moment to process
2026-02-21T04:12:18.4930222Z time.sleep(0.2)
2026-02-21T04:12:18.49302586Z
2026-02-21T04:12:18.493032204Z # Now show that the value IS masked in new output
2026-02-21T04:12:18.493037697Z print("\\n3. After registration, value is masked:")
2026-02-21T04:12:18.49304288Z print(f" API Token: {api_token}")
2026-02-21T04:12:18.49304854Z print(f" Authorization: Bearer {api_token}")
2026-02-21T04:12:18.49305311Z sys.stdout.flush()
2026-02-21T04:12:18.493058057Z else:
2026-02-21T04:12:18.493070154Z print("ERROR: No secrets socket available!")
2026-02-21T04:12:18.493075497Z exit(1)
2026-02-21T04:12:18.493079207Z
2026-02-21T04:12:18.493084497Z print("\\n" + "=" * 50)
2026-02-21T04:12:18.493089417Z print("TEST COMPLETE")
2026-02-21T04:12:18.493093987Z print("=" * 50)
2026-02-21T04:12:18.493100064Z """)
2026-02-21T04:12:18.4931059Z test_script.chmod(0o755)
2026-02-21T04:12:18.495777662Z
2026-02-21T04:12:18.497618172Z # Run the job with an explicit empty secrets list to prevent default masking
2026-02-21T04:12:18.497626092Z result = subprocess.run(
2026-02-21T04:12:18.497629627Z [
2026-02-21T04:12:18.497633244Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.49763708Z "--runner-image", "python:3.9-alpine",
2026-02-21T04:12:18.49764211Z "--job-command", "python3 -u /job/show_masking.py", # -u for unbuffered output
2026-02-21T04:12:18.497645335Z "--code-dir", "/job",
2026-02-21T04:12:18.497648324Z "--job-dir", "/job",
2026-02-21T04:12:18.497652817Z "--secret-values-list", "", # Empty list prevents default masking of all values
2026-02-21T04:12:18.497655438Z ],
2026-02-21T04:12:18.49765824Z capture_output=True,
2026-02-21T04:12:18.497661533Z text=True,
2026-02-21T04:12:18.497664715Z cwd=work_dir,
2026-02-21T04:12:18.497668363Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.497671355Z )
2026-02-21T04:12:18.497673553Z
2026-02-21T04:12:18.49767672Z print("\n--- OUTPUT ---")
2026-02-21T04:12:18.49768235Z print(result.stdout)
2026-02-21T04:12:18.497685055Z print("\n--- ERRORS ---")
2026-02-21T04:12:18.497691987Z print(result.stderr)
2026-02-21T04:12:18.497695467Z
2026-02-21T04:12:18.497698117Z # Verify the behavior
2026-02-21T04:12:18.497701803Z> assert result.returncode == 0, f"Script failed with code {result.returncode}"
2026-02-21T04:12:18.497704587ZE AssertionError: Script failed with code 1
2026-02-21T04:12:18.497707741ZE assert 1 == 0
2026-02-21T04:12:18.497714997ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.9-...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.497716794Z
2026-02-21T04:12:18.49772073Ztests/test_dynamic_secret_masking.py:110: AssertionError
2026-02-21T04:12:18.497725035Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.497727178Z
2026-02-21T04:12:18.497729838Z--- OUTPUT ---
2026-02-21T04:12:18.497731927Z
2026-02-21T04:12:18.497733729Z
2026-02-21T04:12:18.497736347Z--- ERRORS ---
2026-02-21T04:12:18.497739927Z2026-02-21T04:09:58.124103+00:00 Configuration validation failed:
2026-02-21T04:12:18.49774323Z2026-02-21T04:09:58.124176+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.49774704Z • system: docker is not available in PATH
2026-02-21T04:12:18.497749983Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.497751871Z
2026-02-21T04:12:18.497753686Z
2026-02-21T04:12:18.497757671Z________________ test_multiple_values_masked_after_registration ________________
2026-02-21T04:12:18.497759707Z
2026-02-21T04:12:18.497762765Z def test_multiple_values_masked_after_registration():
2026-02-21T04:12:18.497766416Z """Test masking multiple values registered at different times."""
2026-02-21T04:12:18.497768644Z
2026-02-21T04:12:18.497772019Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.497775056Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.497777144Z
2026-02-21T04:12:18.497780504Z # Create job directory
2026-02-21T04:12:18.497783802Z job_dir = work_dir / "job"
2026-02-21T04:12:18.497788321Z job_dir.mkdir()
2026-02-21T04:12:18.497790498Z
2026-02-21T04:12:18.497793373Z # Create test script
2026-02-21T04:12:18.497796612Z test_script = job_dir / "progressive_masking.sh"
2026-02-21T04:12:18.497799896Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.497802173Z
2026-02-21T04:12:18.497805368Z # Function to register a secret
2026-02-21T04:12:18.497808328Z register_secret() {
2026-02-21T04:12:18.497811019Z python3 -c "
2026-02-21T04:12:18.497813685Z import socket, json, struct, os
2026-02-21T04:12:18.497816696Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T04:12:18.497819612Z sock.connect(os.environ['REACTORCIDE_SECRETS_SOCKET'])
2026-02-21T04:12:18.497825498Z msg = json.dumps({'action': 'register', 'secrets': ['$1']}).encode()
2026-02-21T04:12:18.497828485Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T04:12:18.497831081Z sock.send(msg)
2026-02-21T04:12:18.497834196Z sock.close()
2026-02-21T04:12:18.497836415Z "
2026-02-21T04:12:18.497839269Z sleep 0.5
2026-02-21T04:12:18.497842236Z }
2026-02-21T04:12:18.497844258Z
2026-02-21T04:12:18.497847192Z # First secret
2026-02-21T04:12:18.497850284Z SECRET1="database-pass-123"
2026-02-21T04:12:18.497874333Z echo "Step 1: Database password is: $SECRET1"
2026-02-21T04:12:18.497876849Z
2026-02-21T04:12:18.497879501Z # Register first secret
2026-02-21T04:12:18.497882182Z register_secret "$SECRET1"
2026-02-21T04:12:18.497885722Z
2026-02-21T04:12:18.497888567Z echo "Step 2: Database password is: $SECRET1"
2026-02-21T04:12:18.497890538Z
2026-02-21T04:12:18.497892946Z # Second secret
2026-02-21T04:12:18.497895572Z SECRET2="api-key-456"
2026-02-21T04:12:18.497898395Z echo "Step 3: API key is: $SECRET2"
2026-02-21T04:12:18.497900179Z
2026-02-21T04:12:18.497902616Z # Register second secret
2026-02-21T04:12:18.49790505Z register_secret "$SECRET2"
2026-02-21T04:12:18.497906824Z
2026-02-21T04:12:18.497910118Z echo "Step 4: Database password is: $SECRET1"
2026-02-21T04:12:18.49791253Z echo "Step 5: API key is: $SECRET2"
2026-02-21T04:12:18.497914493Z
2026-02-21T04:12:18.497917078Z # Third secret
2026-02-21T04:12:18.497919952Z SECRET3="webhook-token-789"
2026-02-21T04:12:18.497922832Z echo "Step 6: Webhook token is: $SECRET3"
2026-02-21T04:12:18.497924789Z
2026-02-21T04:12:18.497927345Z register_secret "$SECRET3"
2026-02-21T04:12:18.49792937Z
2026-02-21T04:12:18.497932045Z echo "Step 7: All secrets:"
2026-02-21T04:12:18.497934573Z echo " Database: $SECRET1"
2026-02-21T04:12:18.49793732Z echo " API: $SECRET2"
2026-02-21T04:12:18.49793974Z echo " Webhook: $SECRET3"
2026-02-21T04:12:18.497942633Z """)
2026-02-21T04:12:18.497945611Z test_script.chmod(0o755)
2026-02-21T04:12:18.497947496Z
2026-02-21T04:12:18.497951096Z # Run the job with an explicit empty secrets list
2026-02-21T04:12:18.497953899Z result = subprocess.run(
2026-02-21T04:12:18.497956362Z [
2026-02-21T04:12:18.497959593Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.497962573Z "--runner-image", "python:3.9-alpine",
2026-02-21T04:12:18.497966364Z "--job-command", "sh /job/progressive_masking.sh",
2026-02-21T04:12:18.497969499Z "--code-dir", "/job",
2026-02-21T04:12:18.497972362Z "--job-dir", "/job",
2026-02-21T04:12:18.497976531Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-21T04:12:18.497978985Z ],
2026-02-21T04:12:18.497981811Z capture_output=True,
2026-02-21T04:12:18.497984605Z text=True,
2026-02-21T04:12:18.49798731Z cwd=work_dir,
2026-02-21T04:12:18.497990764Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.49799395Z )
2026-02-21T04:12:18.49799584Z
2026-02-21T04:12:18.497998856Z print("\n--- OUTPUT ---")
2026-02-21T04:12:18.498001687Z print(result.stdout)
2026-02-21T04:12:18.498003553Z
2026-02-21T04:12:18.498006302Z> assert result.returncode == 0
2026-02-21T04:12:18.499166731ZE AssertionError: assert 1 == 0
2026-02-21T04:12:18.499178538ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.9-...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.499181051Z
2026-02-21T04:12:18.499192754Ztests/test_dynamic_secret_masking.py:209: AssertionError
2026-02-21T04:12:18.499198324Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.499202248Z
2026-02-21T04:12:18.499210784Z--- OUTPUT ---
2026-02-21T04:12:18.499261151Z
2026-02-21T04:12:18.499270189Z__________________ test_immediate_masking_in_streaming_output __________________
2026-02-21T04:12:18.499274155Z
2026-02-21T04:12:18.499283625Z def test_immediate_masking_in_streaming_output():
2026-02-21T04:12:18.499291232Z """Test that masking applies immediately to streaming output."""
2026-02-21T04:12:18.499293962Z
2026-02-21T04:12:18.499297952Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.503752701Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.503757716Z
2026-02-21T04:12:18.503760634Z # Create job directory
2026-02-21T04:12:18.503763365Z job_dir = work_dir / "job"
2026-02-21T04:12:18.503766419Z job_dir.mkdir()
2026-02-21T04:12:18.503768501Z
2026-02-21T04:12:18.503772024Z # Create a script that outputs continuously
2026-02-21T04:12:18.503775044Z test_script = job_dir / "streaming_test.py"
2026-02-21T04:12:18.50377869Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T04:12:18.503784293Z import socket
2026-02-21T04:12:18.503787594Z import json
2026-02-21T04:12:18.50379021Z import struct
2026-02-21T04:12:18.503792785Z import os
2026-02-21T04:12:18.503795321Z import time
2026-02-21T04:12:18.503797842Z import sys
2026-02-21T04:12:18.503800058Z
2026-02-21T04:12:18.503802859Z # Flush output immediately
2026-02-21T04:12:18.50380539Z sys.stdout.flush()
2026-02-21T04:12:18.503807259Z
2026-02-21T04:12:18.503810104Z secret_value = "streaming-secret-999"
2026-02-21T04:12:18.503812173Z
2026-02-21T04:12:18.503815102Z # Output the secret multiple times before registration
2026-02-21T04:12:18.503817668Z for i in range(3):
2026-02-21T04:12:18.503820891Z print(f"Before [{i}]: secret={secret_value}")
2026-02-21T04:12:18.503823601Z sys.stdout.flush()
2026-02-21T04:12:18.503826033Z time.sleep(0.1)
2026-02-21T04:12:18.503828205Z
2026-02-21T04:12:18.503830715Z # Register the secret
2026-02-21T04:12:18.503833844Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T04:12:18.503836028Z if socket_path:
2026-02-21T04:12:18.503838811Z print("\\nRegistering secret...")
2026-02-21T04:12:18.50384165Z sys.stdout.flush()
2026-02-21T04:12:18.503843541Z
2026-02-21T04:12:18.503847004Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T04:12:18.503849578Z sock.connect(socket_path)
2026-02-21T04:12:18.503853547Z msg = json.dumps({'action': 'register', 'secrets': [secret_value]}).encode()
2026-02-21T04:12:18.503857353Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T04:12:18.503859991Z sock.send(msg)
2026-02-21T04:12:18.503862507Z response = sock.recv(1024)
2026-02-21T04:12:18.503865016Z sock.close()
2026-02-21T04:12:18.503866862Z
2026-02-21T04:12:18.503869707Z print("Secret registered!\\n")
2026-02-21T04:12:18.503872564Z sys.stdout.flush()
2026-02-21T04:12:18.503874395Z
2026-02-21T04:12:18.503877199Z # Wait for registration to process
2026-02-21T04:12:18.503879852Z time.sleep(0.5)
2026-02-21T04:12:18.503881564Z
2026-02-21T04:12:18.503884979Z # Output the secret multiple times after registration
2026-02-21T04:12:18.503888155Z for i in range(3):
2026-02-21T04:12:18.503891142Z print(f"After [{i}]: secret={secret_value}")
2026-02-21T04:12:18.503893828Z sys.stdout.flush()
2026-02-21T04:12:18.50389641Z time.sleep(0.1)
2026-02-21T04:12:18.503901018Z """)
2026-02-21T04:12:18.503904355Z test_script.chmod(0o755)
2026-02-21T04:12:18.503906495Z
2026-02-21T04:12:18.503909356Z # Run the job with an explicit empty secrets list
2026-02-21T04:12:18.503912179Z result = subprocess.run(
2026-02-21T04:12:18.503915003Z [
2026-02-21T04:12:18.503917713Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.503920925Z "--runner-image", "python:3.9-alpine",
2026-02-21T04:12:18.503925009Z "--job-command", "python3 /job/streaming_test.py",
2026-02-21T04:12:18.503927943Z "--code-dir", "/job",
2026-02-21T04:12:18.503930882Z "--job-dir", "/job",
2026-02-21T04:12:18.503935274Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-21T04:12:18.503938176Z ],
2026-02-21T04:12:18.503941002Z capture_output=True,
2026-02-21T04:12:18.503943737Z text=True,
2026-02-21T04:12:18.503946333Z cwd=work_dir,
2026-02-21T04:12:18.503949571Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.503952331Z )
2026-02-21T04:12:18.503954391Z
2026-02-21T04:12:18.503957046Z print("\n--- STREAMING OUTPUT ---")
2026-02-21T04:12:18.503960085Z print(result.stdout)
2026-02-21T04:12:18.503963606Z
2026-02-21T04:12:18.503966562Z> assert result.returncode == 0
2026-02-21T04:12:18.503969077ZE AssertionError: assert 1 == 0
2026-02-21T04:12:18.503975442ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.9-...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.503977314Z
2026-02-21T04:12:18.503980768Ztests/test_dynamic_secret_masking.py:305: AssertionError
2026-02-21T04:12:18.503984336Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.503986276Z
2026-02-21T04:12:18.503989122Z--- STREAMING OUTPUT ---
2026-02-21T04:12:18.503990814Z
2026-02-21T04:12:18.503994242Z_______________________ test_dynamic_secret_registration _______________________
2026-02-21T04:12:18.503996057Z
2026-02-21T04:12:18.503998942Z def test_dynamic_secret_registration():
2026-02-21T04:12:18.504002779Z """Test that jobs can register secrets dynamically via socket."""
2026-02-21T04:12:18.504004763Z
2026-02-21T04:12:18.504007916Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.504010823Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.504012977Z
2026-02-21T04:12:18.504015574Z # Create job directory
2026-02-21T04:12:18.504018097Z job_dir = work_dir / "job"
2026-02-21T04:12:18.504021113Z job_dir.mkdir()
2026-02-21T04:12:18.504023034Z
2026-02-21T04:12:18.504026574Z # Create a test script that fetches and uses a secret
2026-02-21T04:12:18.504029713Z test_script = job_dir / "dynamic_secret_test.sh"
2026-02-21T04:12:18.504032807Z test_script.write_text("""#!/bin/sh
2026-02-21T04:12:18.504035594Z # Simulate fetching a secret from an external service
2026-02-21T04:12:18.504038439Z FETCHED_SECRET="super-dynamic-secret-12345"
2026-02-21T04:12:18.504040496Z
2026-02-21T04:12:18.504043293Z echo "Before registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-21T04:12:18.50404497Z
2026-02-21T04:12:18.504047836Z # Register the secret so it gets masked
2026-02-21T04:12:18.504050994Z if [ -n "$REACTORCIDE_SECRETS_SOCKET" ]; then
2026-02-21T04:12:18.504053928Z echo "Socket available at: $REACTORCIDE_SECRETS_SOCKET"
2026-02-21T04:12:18.504056617Z # Use Python to register the secret
2026-02-21T04:12:18.504059096Z python3 -c "
2026-02-21T04:12:18.50406327Z import socket, json, struct
2026-02-21T04:12:18.50406615Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T04:12:18.504070614Z sock.connect('$REACTORCIDE_SECRETS_SOCKET')
2026-02-21T04:12:18.504093998Z msg = json.dumps({'action': 'register', 'secrets': ['$FETCHED_SECRET']}).encode()
2026-02-21T04:12:18.504097197Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T04:12:18.504100055Z sock.send(msg)
2026-02-21T04:12:18.504102792Z response = sock.recv(1024)
2026-02-21T04:12:18.504105498Z print('Registration response:', response.decode())
2026-02-21T04:12:18.504108517Z sock.close()
2026-02-21T04:12:18.50411056Z "
2026-02-21T04:12:18.504112992Z
2026-02-21T04:12:18.504116061Z # Give the server a moment to process
2026-02-21T04:12:18.504118675Z sleep 0.5
2026-02-21T04:12:18.504121415Z else
2026-02-21T04:12:18.504124205Z echo "Warning: No secrets socket available"
2026-02-21T04:12:18.504126948Z fi
2026-02-21T04:12:18.504129052Z
2026-02-21T04:12:18.504132143Z # Now use the secret again - it should be masked
2026-02-21T04:12:18.504134843Z echo "After registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-21T04:12:18.504138798Z echo "Using secret in command: curl -H 'Authorization: Bearer $FETCHED_SECRET' example.com"
2026-02-21T04:12:18.504141295Z """)
2026-02-21T04:12:18.50414454Z test_script.chmod(0o755)
2026-02-21T04:12:18.504146491Z
2026-02-21T04:12:18.504149552Z # Run the container with our test script
2026-02-21T04:12:18.504152348Z result = subprocess.run(
2026-02-21T04:12:18.504155154Z [
2026-02-21T04:12:18.50415842Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.504164592Z "--runner-image", "python:3.9-alpine", # Has Python for our registration
2026-02-21T04:12:18.504168182Z "--job-command", "sh /job/dynamic_secret_test.sh",
2026-02-21T04:12:18.504171409Z "--code-dir", "/job",
2026-02-21T04:12:18.504174112Z "--job-dir", "/job",
2026-02-21T04:12:18.504177988Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-21T04:12:18.504180597Z ],
2026-02-21T04:12:18.504183362Z capture_output=True,
2026-02-21T04:12:18.504186006Z text=True,
2026-02-21T04:12:18.504188775Z cwd=work_dir,
2026-02-21T04:12:18.504192128Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.504195075Z )
2026-02-21T04:12:18.504196997Z
2026-02-21T04:12:18.504199766Z print("STDOUT:", result.stdout)
2026-02-21T04:12:18.504202772Z print("STDERR:", result.stderr)
2026-02-21T04:12:18.504204666Z
2026-02-21T04:12:18.504209091Z> assert result.returncode == 0, f"Dynamic secret test failed with code {result.returncode}"
2026-02-21T04:12:18.504212642ZE AssertionError: Dynamic secret test failed with code 1
2026-02-21T04:12:18.504215312ZE assert 1 == 0
2026-02-21T04:12:18.504220712ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.9-...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.504222703Z
2026-02-21T04:12:18.504225925Ztests/test_dynamic_secrets.py:75: AssertionError
2026-02-21T04:12:18.504229542Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.50423206ZSTDOUT:
2026-02-21T04:12:18.5042353ZSTDERR: 2026-02-21T04:10:00.136236+00:00 Configuration validation failed:
2026-02-21T04:12:18.504238837Z2026-02-21T04:10:00.136400+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.504242493Z • system: docker is not available in PATH
2026-02-21T04:12:18.504245589Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.504247572Z
2026-02-21T04:12:18.50424939Z
2026-02-21T04:12:18.504254229Z________________________ test_multiple_dynamic_secrets _________________________
2026-02-21T04:12:18.504256053Z
2026-02-21T04:12:18.504260656Z def test_multiple_dynamic_secrets():
2026-02-21T04:12:18.504263667Z """Test registering multiple secrets dynamically."""
2026-02-21T04:12:18.50426558Z
2026-02-21T04:12:18.504268343Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.504270815Z work_dir = Path(tmpdir)
2026-02-21T04:12:18.504272598Z
2026-02-21T04:12:18.504275358Z # Create job directory
2026-02-21T04:12:18.504277992Z job_dir = work_dir / "job"
2026-02-21T04:12:18.504280712Z job_dir.mkdir()
2026-02-21T04:12:18.504282623Z
2026-02-21T04:12:18.50428529Z # Create test script
2026-02-21T04:12:18.50428823Z test_script = job_dir / "multi_secret_test.py"
2026-02-21T04:12:18.504290975Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T04:12:18.50429355Z import socket
2026-02-21T04:12:18.504296784Z import json
2026-02-21T04:12:18.504299352Z import struct
2026-02-21T04:12:18.504302449Z import os
2026-02-21T04:12:18.50430506Z import time
2026-02-21T04:12:18.504307086Z
2026-02-21T04:12:18.504309957Z # Simulate getting multiple secrets
2026-02-21T04:12:18.504312518Z secrets = [
2026-02-21T04:12:18.504315417Z "database-password-abc123",
2026-02-21T04:12:18.504378064Z "api-key-def456",
2026-02-21T04:12:18.50438819Z "webhook-secret-ghi789"
2026-02-21T04:12:18.504390835Z ]
2026-02-21T04:12:18.50439272Z
2026-02-21T04:12:18.50439561Z print("Obtained secrets:", secrets)
2026-02-21T04:12:18.504397337Z
2026-02-21T04:12:18.50440011Z # Register them all at once
2026-02-21T04:12:18.504403277Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T04:12:18.504406121Z if socket_path:
2026-02-21T04:12:18.504409402Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T04:12:18.504412167Z sock.connect(socket_path)
2026-02-21T04:12:18.504414053Z
2026-02-21T04:12:18.504417762Z msg = json.dumps({'action': 'register', 'secrets': secrets}).encode()
2026-02-21T04:12:18.504420561Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T04:12:18.50442321Z sock.send(msg)
2026-02-21T04:12:18.504425255Z
2026-02-21T04:12:18.504428075Z response = sock.recv(1024)
2026-02-21T04:12:18.504430993Z print("Registration response:", response.decode())
2026-02-21T04:12:18.504435838Z sock.close()
2026-02-21T04:12:18.50443785Z
2026-02-21T04:12:18.504440622Z # Wait for processing
2026-02-21T04:12:18.504442835Z time.sleep(0.5)
2026-02-21T04:12:18.504444544Z
2026-02-21T04:12:18.504447015Z # Now use them - should all be masked
2026-02-21T04:12:18.504450627Z print("Database connection: password=database-password-abc123")
2026-02-21T04:12:18.504453541Z print("API header: X-API-Key=api-key-def456")
2026-02-21T04:12:18.504456565Z print("Webhook validation: secret=webhook-secret-ghi789")
2026-02-21T04:12:18.504459218Z else:
2026-02-21T04:12:18.504462524Z print("No secrets socket available")
2026-02-21T04:12:18.504464892Z """)
2026-02-21T04:12:18.504467742Z test_script.chmod(0o755)
2026-02-21T04:12:18.504469798Z
2026-02-21T04:12:18.504492285Z # Run the test
2026-02-21T04:12:18.504495687Z result = subprocess.run(
2026-02-21T04:12:18.504498084Z [
2026-02-21T04:12:18.504501172Z sys.executable, "-m", "src.cli", "run",
2026-02-21T04:12:18.504504533Z "--runner-image", "python:3.9-alpine",
2026-02-21T04:12:18.504508095Z "--job-command", "python3 /job/multi_secret_test.py",
2026-02-21T04:12:18.504511287Z "--code-dir", "/job",
2026-02-21T04:12:18.504514358Z "--job-dir", "/job",
2026-02-21T04:12:18.504518684Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-21T04:12:18.504521375Z ],
2026-02-21T04:12:18.504524062Z capture_output=True,
2026-02-21T04:12:18.50452705Z text=True,
2026-02-21T04:12:18.504529765Z cwd=work_dir,
2026-02-21T04:12:18.504532807Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T04:12:18.504535729Z )
2026-02-21T04:12:18.504537767Z
2026-02-21T04:12:18.50454039Z print("STDOUT:", result.stdout)
2026-02-21T04:12:18.504554556Z print("STDERR:", result.stderr)
2026-02-21T04:12:18.504556915Z
2026-02-21T04:12:18.504559789Z> assert result.returncode == 0
2026-02-21T04:12:18.504562458ZE AssertionError: assert 1 == 0
2026-02-21T04:12:18.504568104ZE + where 1 = CompletedProcess(args=['/workspace/runnerlib/.venv/bin/python', '-m', 'src.cli', 'run', '--runner-image', 'python:3.9-...s errors:\n • system: docker is not available in PATH\n 💡 Install docker: https://docs.docker.com/get-docker/\n\n').returncode
2026-02-21T04:12:18.504570319Z
2026-02-21T04:12:18.504573118Ztests/test_dynamic_secrets.py:233: AssertionError
2026-02-21T04:12:18.504576363Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.504580559ZSTDOUT:
2026-02-21T04:12:18.504583463ZSTDERR: 2026-02-21T04:10:00.668071+00:00 Configuration validation failed:
2026-02-21T04:12:18.504586796Z2026-02-21T04:10:00.668151+00:00 ❌ Configuration has errors:
2026-02-21T04:12:18.504590296Z • system: docker is not available in PATH
2026-02-21T04:12:18.504593422Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T04:12:18.504595268Z
2026-02-21T04:12:18.50459698Z
2026-02-21T04:12:18.504600359Z_____________ TestEvalCommand.test_eval_pr_uses_base_ref_for_diff ______________
2026-02-21T04:12:18.504602333Z
2026-02-21T04:12:18.504606133Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f87ab6c6990>
2026-02-21T04:12:18.504612094Ztemp_dirs = (PosixPath('/tmp/tmpe76y8ksw/ci'), PosixPath('/tmp/tmpe76y8ksw/src'), PosixPath('/tmp/tmpe76y8ksw/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpe76y8ksw/triggers.json'))
2026-02-21T04:12:18.504614048Z
2026-02-21T04:12:18.504617091Z def test_eval_pr_uses_base_ref_for_diff(self, temp_dirs):
2026-02-21T04:12:18.50462075Z """Test that PR events use pr_base_ref for changed files diff."""
2026-02-21T04:12:18.504623859Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T04:12:18.504625939Z
2026-02-21T04:12:18.504630594Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T04:12:18.504633359Z "name": "test",
2026-02-21T04:12:18.50463644Z "triggers": {"events": ["pull_request_opened"]},
2026-02-21T04:12:18.504639834Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T04:12:18.504642357Z })
2026-02-21T04:12:18.504644365Z
2026-02-21T04:12:18.50464765Z (src_dir / ".git").mkdir()
2026-02-21T04:12:18.50464962Z
2026-02-21T04:12:18.504653362Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-21T04:12:18.504658136Z result = runner.invoke(app, [
2026-02-21T04:12:18.504661008Z "eval",
2026-02-21T04:12:18.504664257Z "--ci-source-dir", str(ci_dir),
2026-02-21T04:12:18.504667325Z "--source-dir", str(src_dir),
2026-02-21T04:12:18.50467076Z "--event-type", "pull_request_opened",
2026-02-21T04:12:18.504946073Z "--branch", "feature/foo",
2026-02-21T04:12:18.50496015Z "--pr-base-ref", "[REDACTED]",
2026-02-21T04:12:18.50496912Z "--triggers-file", str(triggers_file),
2026-02-21T04:12:18.504975436Z ])
2026-02-21T04:12:18.504980123Z
2026-02-21T04:12:18.504989983Z # Verify it was called with origin/[REDACTED] as the from_ref
2026-02-21T04:12:18.504995716Z> mock_changed.assert_called_once_with(
2026-02-21T04:12:18.505003116Z "origin/[REDACTED]", "HEAD", str(src_dir)
2026-02-21T04:12:18.50500882Z )
2026-02-21T04:12:18.505012413Z
2026-02-21T04:12:18.505018363Ztests/test_eval_cli.py:344:
2026-02-21T04:12:18.505032043Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.505039166Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-21T04:12:18.505044583Z return self.assert_called_with(*args, **kwargs)
2026-02-21T04:12:18.505049736Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.505054196Z
2026-02-21T04:12:18.505060123Zself = <MagicMock name='changed_files' id='140220671750704'>
2026-02-21T04:12:18.505067886Zargs = ('origin/[REDACTED]', 'HEAD', '/tmp/tmpe76y8ksw/src'), kwargs = {}
2026-02-21T04:12:18.50507534Zexpected = call('origin/[REDACTED]', 'HEAD', '/tmp/tmpe76y8ksw/src')
2026-02-21T04:12:18.505083854Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmpe76y8ksw/src')
2026-02-21T04:12:18.505096177Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f87ab420b80>
2026-02-21T04:12:18.505102024Zcause = None
2026-02-21T04:12:18.505105681Z
2026-02-21T04:12:18.505111377Z def assert_called_with(self, /, *args, **kwargs):
2026-02-21T04:12:18.505122687Z """assert that the last call was made with the specified arguments.
2026-02-21T04:12:18.505126634Z
2026-02-21T04:12:18.505134057Z Raises an AssertionError if the args and keyword args passed in are
2026-02-21T04:12:18.505139961Z different to the last call to the mock."""
2026-02-21T04:12:18.505148184Z if self.call_args is None:
2026-02-21T04:12:18.505154227Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-21T04:12:18.505159087Z actual = 'not called.'
2026-02-21T04:12:18.505165947Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-21T04:12:18.505171771Z % (expected, actual))
2026-02-21T04:12:18.505177404Z raise AssertionError(error_message)
2026-02-21T04:12:18.505181834Z
2026-02-21T04:12:18.505188441Z def _error_message():
2026-02-21T04:12:18.505195011Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-21T04:12:18.505201111Z return msg
2026-02-21T04:12:18.505208134Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-21T04:12:18.508110867Z actual = self._call_matcher(self.call_args)
2026-02-21T04:12:18.508115593Z if actual != expected:
2026-02-21T04:12:18.50812025Z cause = expected if isinstance(expected, Exception) else None
2026-02-21T04:12:18.508123689Z> raise AssertionError(_error_message()) from cause
2026-02-21T04:12:18.508127047ZE AssertionError: expected call not found.
2026-02-21T04:12:18.50813321ZE Expected: changed_files('origin/[REDACTED]', 'HEAD', '/tmp/tmpe76y8ksw/src')
2026-02-21T04:12:18.508138378ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmpe76y8ksw/src')
2026-02-21T04:12:18.508140689Z
2026-02-21T04:12:18.508143924Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-21T04:12:18.50814781Z___________ TestEvalCommand.test_eval_push_uses_head_parent_for_diff ___________
2026-02-21T04:12:18.508149915Z
2026-02-21T04:12:18.508153598Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f87ab6c6a80>
2026-02-21T04:12:18.508160087Ztemp_dirs = (PosixPath('/tmp/tmpk4g1njen/ci'), PosixPath('/tmp/tmpk4g1njen/src'), PosixPath('/tmp/tmpk4g1njen/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpk4g1njen/triggers.json'))
2026-02-21T04:12:18.508162535Z
2026-02-21T04:12:18.508166119Z def test_eval_push_uses_head_parent_for_diff(self, temp_dirs):
2026-02-21T04:12:18.508171061Z """Test that push events use HEAD^ for changed files diff."""
2026-02-21T04:12:18.508174621Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T04:12:18.508177304Z
2026-02-21T04:12:18.508180779Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T04:12:18.508184032Z "name": "test",
2026-02-21T04:12:18.508187235Z "triggers": {"events": ["push"]},
2026-02-21T04:12:18.508191221Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T04:12:18.508194607Z })
2026-02-21T04:12:18.50819679Z
2026-02-21T04:12:18.508200067Z (src_dir / ".git").mkdir()
2026-02-21T04:12:18.508202569Z
2026-02-21T04:12:18.508208802Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-21T04:12:18.508212953Z result = runner.invoke(app, [
2026-02-21T04:12:18.50821615Z "eval",
2026-02-21T04:12:18.508219886Z "--ci-source-dir", str(ci_dir),
2026-02-21T04:12:18.508223087Z "--source-dir", str(src_dir),
2026-02-21T04:12:18.508227389Z "--event-type", "push",
2026-02-21T04:12:18.508230979Z "--branch", "[REDACTED]",
2026-02-21T04:12:18.508234727Z "--triggers-file", str(triggers_file),
2026-02-21T04:12:18.508238046Z ])
2026-02-21T04:12:18.508240316Z
2026-02-21T04:12:18.508243281Z> mock_changed.assert_called_once_with(
2026-02-21T04:12:18.508246741Z "HEAD^", "HEAD", str(src_dir)
2026-02-21T04:12:18.508249752Z )
2026-02-21T04:12:18.508251923Z
2026-02-21T04:12:18.50825515Ztests/test_eval_cli.py:372:
2026-02-21T04:12:18.508259884Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.508264149Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-21T04:12:18.50826726Z return self.assert_called_with(*args, **kwargs)
2026-02-21T04:12:18.508270514Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.508273485Z
2026-02-21T04:12:18.508276644Zself = <MagicMock name='changed_files' id='140220671747008'>
2026-02-21T04:12:18.508298994Zargs = ('HEAD^', 'HEAD', '/tmp/tmpk4g1njen/src'), kwargs = {}
2026-02-21T04:12:18.50830291Zexpected = call('HEAD^', 'HEAD', '/tmp/tmpk4g1njen/src')
2026-02-21T04:12:18.508307828Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmpk4g1njen/src')
2026-02-21T04:12:18.508311954Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f87ab421120>
2026-02-21T04:12:18.508315237Zcause = None
2026-02-21T04:12:18.508346201Z
2026-02-21T04:12:18.50835153Z def assert_called_with(self, /, *args, **kwargs):
2026-02-21T04:12:18.508355933Z """assert that the last call was made with the specified arguments.
2026-02-21T04:12:18.508358324Z
2026-02-21T04:12:18.508361605Z Raises an AssertionError if the args and keyword args passed in are
2026-02-21T04:12:18.508365124Z different to the last call to the mock."""
2026-02-21T04:12:18.508368099Z if self.call_args is None:
2026-02-21T04:12:18.508371836Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-21T04:12:18.508374847Z actual = 'not called.'
2026-02-21T04:12:18.50837929Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-21T04:12:18.508382536Z % (expected, actual))
2026-02-21T04:12:18.508385787Z raise AssertionError(error_message)
2026-02-21T04:12:18.508388234Z
2026-02-21T04:12:18.508391681Z def _error_message():
2026-02-21T04:12:18.508395318Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-21T04:12:18.508398376Z return msg
2026-02-21T04:12:18.508401921Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-21T04:12:18.508405273Z actual = self._call_matcher(self.call_args)
2026-02-21T04:12:18.508408278Z if actual != expected:
2026-02-21T04:12:18.508411885Z cause = expected if isinstance(expected, Exception) else None
2026-02-21T04:12:18.508415102Z> raise AssertionError(_error_message()) from cause
2026-02-21T04:12:18.508418184ZE AssertionError: expected call not found.
2026-02-21T04:12:18.50842223ZE Expected: changed_files('HEAD^', 'HEAD', '/tmp/tmpk4g1njen/src')
2026-02-21T04:12:18.508426915ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmpk4g1njen/src')
2026-02-21T04:12:18.508428941Z
2026-02-21T04:12:18.508432883Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-21T04:12:18.50843666Z___________ TestEvalCommand.test_eval_no_git_dir_skips_changed_files ___________
2026-02-21T04:12:18.508438788Z
2026-02-21T04:12:18.508443114Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f87ab6e3310>
2026-02-21T04:12:18.508449172Ztemp_dirs = (PosixPath('/tmp/tmphvv7wn3w/ci'), PosixPath('/tmp/tmphvv7wn3w/src'), PosixPath('/tmp/tmphvv7wn3w/ci/.reactorcide/jobs'), PosixPath('/tmp/tmphvv7wn3w/triggers.json'))
2026-02-21T04:12:18.508451116Z
2026-02-21T04:12:18.50845484Z def test_eval_no_git_dir_skips_changed_files(self, temp_dirs):
2026-02-21T04:12:18.508458659Z """Test that eval skips changed files detection when no .git dir exists."""
2026-02-21T04:12:18.508462152Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T04:12:18.508464386Z
2026-02-21T04:12:18.508467388Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T04:12:18.508470832Z "name": "test",
2026-02-21T04:12:18.508473934Z "triggers": {"events": ["push"]},
2026-02-21T04:12:18.508477582Z "paths": {"include": ["src/**"]},
2026-02-21T04:12:18.508481529Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T04:12:18.508484522Z })
2026-02-21T04:12:18.508486902Z
2026-02-21T04:12:18.508490649Z # No .git directory - should skip changed files and still match
2026-02-21T04:12:18.508494159Z # (path filtering is skipped when changed_files is None)
2026-02-21T04:12:18.508497116Z result = runner.invoke(app, [
2026-02-21T04:12:18.508502322Z "eval",
2026-02-21T04:12:18.508505757Z "--ci-source-dir", str(ci_dir),
2026-02-21T04:12:18.508508868Z "--source-dir", str(src_dir),
2026-02-21T04:12:18.508512126Z "--event-type", "push",
2026-02-21T04:12:18.508516046Z "--triggers-file", str(triggers_file),
2026-02-21T04:12:18.508518782Z ])
2026-02-21T04:12:18.508520953Z
2026-02-21T04:12:18.50852398Z assert result.exit_code == 0
2026-02-21T04:12:18.508527085Z> assert triggers_file.exists()
2026-02-21T04:12:18.509362086ZE AssertionError: assert False
2026-02-21T04:12:18.509368831ZE + where False = exists()
2026-02-21T04:12:18.509373174ZE + where exists = PosixPath('/tmp/tmphvv7wn3w/triggers.json').exists
2026-02-21T04:12:18.509375426Z
2026-02-21T04:12:18.509378945Ztests/test_eval_cli.py:400: AssertionError
2026-02-21T04:12:18.509382808Z________ TestEvalSourcePreparation.test_eval_clones_source_when_missing ________
2026-02-21T04:12:18.509384719Z
2026-02-21T04:12:18.509388666Zself = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7f87abbe7750>
2026-02-21T04:12:18.509390717Z
2026-02-21T04:12:18.5093939Z def test_eval_clones_source_when_missing(self):
2026-02-21T04:12:18.509397845Z """Test that eval clones source repo when .git dir doesn't exist."""
2026-02-21T04:12:18.509400999Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T04:12:18.509403826Z base = Path(tmpdir)
2026-02-21T04:12:18.5094069Z ci_dir = base / "ci"
2026-02-21T04:12:18.5094097Z src_dir = base / "src"
2026-02-21T04:12:18.509413349Z src_dir.mkdir() # Exists but no .git (like worker creates)
2026-02-21T04:12:18.509416531Z jobs_dir = ci_dir / ".reactorcide" / "jobs"
2026-02-21T04:12:18.509419877Z jobs_dir.mkdir(parents=True)
2026-02-21T04:12:18.509424533Z triggers_file = base / "triggers.json"
2026-02-21T04:12:18.509426807Z
2026-02-21T04:12:18.50942969Z # Create a fake "remote" source repo
2026-02-21T04:12:18.50943278Z remote_dir = base / "remote_src"
2026-02-21T04:12:18.509435663Z remote_dir.mkdir()
2026-02-21T04:12:18.509438664Z from git import Repo
2026-02-21T04:12:18.509441584Z remote_repo = Repo.init(remote_dir)
2026-02-21T04:12:18.509446032Z (remote_dir / "[REDACTED].py").write_text("print('hello')")
2026-02-21T04:12:18.509449443Z remote_repo.index.add(["[REDACTED].py"])
2026-02-21T04:12:18.509452523Z remote_repo.index.commit("Initial commit")
2026-02-21T04:12:18.50945473Z
2026-02-21T04:12:18.509457953Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T04:12:18.509460986Z "name": "test",
2026-02-21T04:12:18.509464095Z "triggers": {"events": ["push"]},
2026-02-21T04:12:18.509467638Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T04:12:18.50947086Z })
2026-02-21T04:12:18.509472849Z
2026-02-21T04:12:18.509475757Z result = runner.invoke(app, [
2026-02-21T04:12:18.509478774Z "eval",
2026-02-21T04:12:18.509482197Z "--ci-source-dir", str(ci_dir),
2026-02-21T04:12:18.50948534Z "--source-dir", str(src_dir),
2026-02-21T04:12:18.50948848Z "--event-type", "push",
2026-02-21T04:12:18.509492067Z "--branch", "[REDACTED]",
2026-02-21T04:12:18.509494907Z "--source-url", str(remote_dir),
2026-02-21T04:12:18.509498034Z "--triggers-file", str(triggers_file),
2026-02-21T04:12:18.50950078Z ])
2026-02-21T04:12:18.509502921Z
2026-02-21T04:12:18.509505995Z> assert result.exit_code == 0
2026-02-21T04:12:18.509508843ZE AssertionError: assert 1 == 0
2026-02-21T04:12:18.509514461ZE + where 1 = <Result GitCommandError('git checkout [REDACTED]', 128)>.exit_code
2026-02-21T04:12:18.50951652Z
2026-02-21T04:12:18.509519609Ztests/test_eval_cli.py:567: AssertionError
2026-02-21T04:12:18.50952332Z____________ TestGitOperations.test_checkout_creates_job_directory _____________
2026-02-21T04:12:18.509525341Z
2026-02-21T04:12:18.50952955Zself = <runnerlib.tests.test_git_operations.TestGitOperations object at 0x7f87abb68a50>
2026-02-21T04:12:18.509532407Ztest_repo = '/tmp/tmpytjh8zu6'
2026-02-21T04:12:18.50953763Zjob_config = RunnerConfig(code_dir='/job/src', job_dir='/job', job_command='echo test', runner_image='alpine:latest', job_env=None,...=None, source_type=None, source_url=None, source_ref=None, ci_source_type=None, ci_source_url=None, ci_source_ref=None)
2026-02-21T04:12:18.509539494Z
2026-02-21T04:12:18.509556955Z def test_checkout_creates_job_directory(self, test_repo, job_config):
2026-02-21T04:12:18.510280444Z """Test that checkout creates the job directory structure."""
2026-02-21T04:12:18.510293898Z # Ensure job dir doesn't exist initially
2026-02-21T04:12:18.510300174Z if Path("./job").exists():
2026-02-21T04:12:18.510305628Z shutil.rmtree("./job")
2026-02-21T04:12:18.510310178Z
2026-02-21T04:12:18.510317621Z # Try [REDACTED] first, fallback to master
2026-02-21T04:12:18.510323308Z try:
2026-02-21T04:12:18.510331018Z checkout_git_repo(test_repo, "[REDACTED]", job_config)
2026-02-21T04:12:18.510335651Z except Exception:
2026-02-21T04:12:18.510342328Z checkout_git_repo(test_repo, "master", job_config)
2026-02-21T04:12:18.510345854Z
2026-02-21T04:12:18.510351504Z # Verify job directory was created
2026-02-21T04:12:18.510357104Z> assert Path("./job").exists()
2026-02-21T04:12:18.510362468ZE AssertionError: assert False
2026-02-21T04:12:18.510367904ZE + where False = exists()
2026-02-21T04:12:18.510374004ZE + where exists = PosixPath('job').exists
2026-02-21T04:12:18.510379714ZE + where PosixPath('job') = Path('./job')
2026-02-21T04:12:18.510383594Z
2026-02-21T04:12:18.510389441Ztests/test_git_operations.py:213: AssertionError
2026-02-21T04:12:18.510396264Z---------------------------- Captured stdout setup -----------------------------
2026-02-21T04:12:18.510401971ZInitialized empty Git repository in /tmp/tmpytjh8zu6/.git/
2026-02-21T04:12:18.510408545Z[master (root-commit) 160a9ef] Initial commit
2026-02-21T04:12:18.510414245Z 1 file changed, 1 insertion(+)
2026-02-21T04:12:18.510419139Z create mode 100644 test.txt
2026-02-21T04:12:18.510425272Z[feature 9b3f37a] Feature changes
2026-02-21T04:12:18.510435055Z 2 files changed, 2 insertions(+), 1 deletion(-)
2026-02-21T04:12:18.510440079Z create mode 100644 new.txt
2026-02-21T04:12:18.510446065Z---------------------------- Captured stderr setup -----------------------------
2026-02-21T04:12:18.514757652Zhint: Using 'master' as the name for the initial branch. This default branch name
2026-02-21T04:12:18.51476487Zhint: is subject to change. To configure the initial branch name to use in all
2026-02-21T04:12:18.51476835Zhint: of your new repositories, which will suppress this warning, call:
2026-02-21T04:12:18.514771204Zhint:
2026-02-21T04:12:18.514774573Zhint: git config --global init.defaultBranch <name>
2026-02-21T04:12:18.514777185Zhint:
2026-02-21T04:12:18.514782287Zhint: Names commonly chosen instead of 'master' are '[REDACTED]', 'trunk' and
2026-02-21T04:12:18.514785601Zhint: 'development'. The just-created branch can be renamed via this command:
2026-02-21T04:12:18.514793522Zhint:
2026-02-21T04:12:18.514796365Zhint: git branch -m <name>
2026-02-21T04:12:18.514799533ZSwitched to a new branch 'feature'
2026-02-21T04:12:18.51480307Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.514807398Z2026-02-21T04:11:43.579088+00:00 Cloning repository: /tmp/tmpytjh8zu6
2026-02-21T04:12:18.514811518Z2026-02-21T04:11:43.622676+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.514814684Z2026-02-21T04:11:43.643590+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T04:12:18.514818413Z2026-02-21T04:11:43.659038+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T04:12:18.51482379Z2026-02-21T04:11:43.673282+00:00 Fetching all remote refs...
2026-02-21T04:12:18.514827492Z2026-02-21T04:11:43.718648+00:00 Cloning repository: /tmp/tmpytjh8zu6
2026-02-21T04:12:18.514830418Z2026-02-21T04:11:43.760636+00:00 Checking out ref: master
2026-02-21T04:12:18.514833713Z2026-02-21T04:11:43.770926+00:00 Repository checked out to: /job/src
2026-02-21T04:12:18.514839329Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.514845618Z2026-02-21T04:11:43.578941+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpytjh8zu6 ref=[REDACTED]
2026-02-21T04:12:18.514853464Z2026-02-21T04:11:43.709277+00:00 [ERROR] [runnerlib] Failed to clone repository url=/tmp/tmpytjh8zu6 error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.514856762Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.514860779Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.514865001Z2026-02-21T04:11:43.709453+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.514868032Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.514873562Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.514879619Z2026-02-21T04:11:43.718525+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpytjh8zu6 ref=master
2026-02-21T04:12:18.514883521Z2026-02-21T04:11:43.770799+00:00 [INFO] [runnerlib] Repository cloned successfully path=/job/src
2026-02-21T04:12:18.520518449Z_ TestDirectoryManagementIntegration.test_directory_validation_with_real_filesystem _
2026-02-21T04:12:18.520523038Z
2026-02-21T04:12:18.520528316Zself = <runnerlib.tests.test_integration.TestDirectoryManagementIntegration object at 0x7f87abbcc690>
2026-02-21T04:12:18.520530632Z
2026-02-21T04:12:18.520534067Z def test_directory_validation_with_real_filesystem(self):
2026-02-21T04:12:18.520537944Z """Test directory validation against real filesystem."""
2026-02-21T04:12:18.520540935Z config = get_config(
2026-02-21T04:12:18.520556266Z code_dir='/job/src',
2026-02-21T04:12:18.520559879Z job_dir='/job/work',
2026-02-21T04:12:18.520571539Z job_command='test',
2026-02-21T04:12:18.520574876Z runner_image='test:image'
2026-02-21T04:12:18.520577992Z )
2026-02-21T04:12:18.520580079Z
2026-02-21T04:12:18.520583187Z # Test validation without directories
2026-02-21T04:12:18.52058729Z with patch('shutil.which', return_value="/usr/bin/docker"):
2026-02-21T04:12:18.520590821Z result = validate_config(config, check_files=True)
2026-02-21T04:12:18.520592756Z
2026-02-21T04:12:18.520595807Z # Should have warnings about missing directories
2026-02-21T04:12:18.52059859Z> assert result.has_warnings
2026-02-21T04:12:18.520601599ZE assert False
2026-02-21T04:12:18.520605163ZE + where False = ValidationResult(is_valid=True, errors=[], warnings=[]).has_warnings
2026-02-21T04:12:18.520607189Z
2026-02-21T04:12:18.52061075Z/workspace/runnerlib/tests/test_integration.py:121: AssertionError
2026-02-21T04:12:18.520614336Z___________________ TestJobIsolation.test_work_dir_isolation ___________________
2026-02-21T04:12:18.520616118Z
2026-02-21T04:12:18.520619512Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7f87abbcd1d0>
2026-02-21T04:12:18.520621286Z
2026-02-21T04:12:18.520624455Z def test_work_dir_isolation(self):
2026-02-21T04:12:18.520627258Z """Test that jobs use separate work directories."""
2026-02-21T04:12:18.520630601Z with tempfile.TemporaryDirectory() as temp_dir1:
2026-02-21T04:12:18.520633424Z with tempfile.TemporaryDirectory() as temp_dir2:
2026-02-21T04:12:18.520636401Z # Change to first temp directory
2026-02-21T04:12:18.520639192Z original_cwd = os.getcwd()
2026-02-21T04:12:18.520641501Z
2026-02-21T04:12:18.520644966Z try:
2026-02-21T04:12:18.520648236Z # Test job 1 in temp_dir1
2026-02-21T04:12:18.520651341Z os.chdir(temp_dir1)
2026-02-21T04:12:18.520654527Z config1 = RunnerConfig(
2026-02-21T04:12:18.520657713Z code_dir="/job/src",
2026-02-21T04:12:18.520663273Z job_dir="/job/src",
2026-02-21T04:12:18.520668156Z job_command="echo 'job1'",
2026-02-21T04:12:18.520671364Z runner_image="alpine:latest"
2026-02-21T04:12:18.520673975Z )
2026-02-21T04:12:18.52067725Z job_path1 = prepare_job_directory(config1)
2026-02-21T04:12:18.520680903Z assert job_path1.exists()
2026-02-21T04:12:18.520684389Z> assert str(job_path1).startswith(temp_dir1)
2026-02-21T04:12:18.520687171ZE AssertionError: assert False
2026-02-21T04:12:18.52069315ZE + where False = <built-in method startswith of str object at 0x7f87abe9cf00>('/tmp/tmpp1y9a8y_')
2026-02-21T04:12:18.52069709ZE + where <built-in method startswith of str object at 0x7f87abe9cf00> = '/job'.startswith
2026-02-21T04:12:18.52070055ZE + where '/job' = str(PosixPath('/job'))
2026-02-21T04:12:18.520702144Z
2026-02-21T04:12:18.52070565Ztests/test_job_isolation.py:35: AssertionError
2026-02-21T04:12:18.520709044Z________________ TestJobIsolation.test_concurrent_job_isolation ________________
2026-02-21T04:12:18.520711025Z
2026-02-21T04:12:18.520714545Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7f87abbcd310>
2026-02-21T04:12:18.520716405Z
2026-02-21T04:12:18.520719573Z def test_concurrent_job_isolation(self):
2026-02-21T04:12:18.52072281Z """Test that concurrent jobs don't interfere with each other."""
2026-02-21T04:12:18.520725274Z import threading
2026-02-21T04:12:18.520728311Z import time
2026-02-21T04:12:18.52073017Z
2026-02-21T04:12:18.520733074Z results = {}
2026-02-21T04:12:18.520735558Z errors = {}
2026-02-21T04:12:18.520737739Z
2026-02-21T04:12:18.520740647Z def run_job(job_id: str, work_dir: str):
2026-02-21T04:12:18.520743639Z """Run a job in its own work directory."""
2026-02-21T04:12:18.520746213Z try:
2026-02-21T04:12:18.52074935Z original_cwd = os.getcwd()
2026-02-21T04:12:18.520752193Z os.chdir(work_dir)
2026-02-21T04:12:18.520754215Z
2026-02-21T04:12:18.520757275Z config = RunnerConfig(
2026-02-21T04:12:18.520760685Z code_dir="/job/src",
2026-02-21T04:12:18.520763651Z job_dir="/job/src",
2026-02-21T04:12:18.520766805Z job_command=f"echo 'job-{job_id}'",
2026-02-21T04:12:18.520769607Z runner_image="alpine:latest"
2026-02-21T04:12:18.52077271Z )
2026-02-21T04:12:18.520774725Z
2026-02-21T04:12:18.520777888Z job_path = prepare_job_directory(config)
2026-02-21T04:12:18.520779681Z
2026-02-21T04:12:18.520782718Z # Create a unique file for this job
2026-02-21T04:12:18.520785941Z test_file = job_path / f"job-{job_id}.txt"
2026-02-21T04:12:18.520788718Z test_file.write_text(f"Data for job {job_id}")
2026-02-21T04:12:18.520790656Z
2026-02-21T04:12:18.520793719Z # Simulate some work
2026-02-21T04:12:18.520797056Z time.sleep(0.1)
2026-02-21T04:12:18.520799153Z
2026-02-21T04:12:18.520802308Z # Verify the file still exists and has correct content
2026-02-21T04:12:18.520805398Z assert test_file.exists()
2026-02-21T04:12:18.520808685Z assert test_file.read_text() == f"Data for job {job_id}"
2026-02-21T04:12:18.520810616Z
2026-02-21T04:12:18.520813662Z # Check no files from other jobs exist
2026-02-21T04:12:18.520817139Z other_files = list(job_path.glob("job-*.txt"))
2026-02-21T04:12:18.52081999Z assert len(other_files) == 1
2026-02-21T04:12:18.520823561Z assert other_files[0].name == f"job-{job_id}.txt"
2026-02-21T04:12:18.520825541Z
2026-02-21T04:12:18.520828865Z results[job_id] = True
2026-02-21T04:12:18.520830998Z
2026-02-21T04:12:18.520833918Z except Exception as e:
2026-02-21T04:12:18.520837127Z errors[job_id] = str(e)
2026-02-21T04:12:18.520866869Z finally:
2026-02-21T04:12:18.520870613Z os.chdir(original_cwd)
2026-02-21T04:12:18.520872729Z
2026-02-21T04:12:18.52087536Z # Create temporary directories for each job
2026-02-21T04:12:18.52088028Z temp_dirs = []
2026-02-21T04:12:18.520883073Z threads = []
2026-02-21T04:12:18.520884803Z
2026-02-21T04:12:18.520887402Z try:
2026-02-21T04:12:18.520890625Z # Start multiple jobs concurrently
2026-02-21T04:12:18.520893128Z for i in range(5):
2026-02-21T04:12:18.520896154Z temp_dir = tempfile.mkdtemp(prefix=f"job-{i}-")
2026-02-21T04:12:18.520898668Z temp_dirs.append(temp_dir)
2026-02-21T04:12:18.520900711Z
2026-02-21T04:12:18.520903586Z thread = threading.Thread(
2026-02-21T04:12:18.520906517Z target=run_job,
2026-02-21T04:12:18.52090913Z args=(str(i), temp_dir)
2026-02-21T04:12:18.520912019Z )
2026-02-21T04:12:18.520915171Z thread.start()
2026-02-21T04:12:18.52091789Z threads.append(thread)
2026-02-21T04:12:18.520919774Z
2026-02-21T04:12:18.520922706Z # Wait for all jobs to complete
2026-02-21T04:12:18.520925582Z for thread in threads:
2026-02-21T04:12:18.520928388Z thread.join(timeout=5)
2026-02-21T04:12:18.5209302Z
2026-02-21T04:12:18.520933054Z # Verify all jobs succeeded
2026-02-21T04:12:18.52093592Z> assert len(errors) == 0, f"Jobs failed: {errors}"
2026-02-21T04:12:18.52094714ZE AssertionError: Jobs failed: {'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '2': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '1': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '3': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '4': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])"}
2026-02-21T04:12:18.52094983ZE assert 5 == 0
2026-02-21T04:12:18.520954694ZE + where 5 = len({'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job...xPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", ...})
2026-02-21T04:12:18.520956634Z
2026-02-21T04:12:18.520959817Z/workspace/runnerlib/tests/test_job_isolation.py:138: AssertionError
2026-02-21T04:12:18.520963131Z_______________ TestJobIsolation.test_container_mount_isolation ________________
2026-02-21T04:12:18.520965437Z
2026-02-21T04:12:18.520974291Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7f87ab6b7820>
2026-02-21T04:12:18.52097766Zmock_popen = <MagicMock name='Popen' id='140220671752048'>
2026-02-21T04:12:18.52097946Z
2026-02-21T04:12:18.520984131Z @patch('subprocess.Popen')
2026-02-21T04:12:18.520987353Z def test_container_mount_isolation(self, mock_popen):
2026-02-21T04:12:18.52099065Z """Test that containers mount only their job's directory."""
2026-02-21T04:12:18.52099371Z # Mock the Popen object with proper behavior
2026-02-21T04:12:18.52099634Z mock_process = MagicMock()
2026-02-21T04:12:18.52099973Z mock_process.poll.side_effect = [None, None, 0] # Running, running, then finished
2026-02-21T04:12:18.521002831Z mock_process.returncode = 0
2026-02-21T04:12:18.521012534Z mock_process.stdout.readline.return_value = '' # No output (text mode)
2026-02-21T04:12:18.521015562Z mock_process.stderr.readline.return_value = '' # No errors
2026-02-21T04:12:18.521020514Z mock_process.communicate.return_value = ('', '') # Empty re[REDACTED]ing output
2026-02-21T04:12:18.521023318Z mock_popen.return_value = mock_process
2026-02-21T04:12:18.521025697Z
2026-02-21T04:12:18.521028915Z with tempfile.TemporaryDirectory() as temp_dir:
2026-02-21T04:12:18.521031906Z # Save original cwd if possible
2026-02-21T04:12:18.521034792Z try:
2026-02-21T04:12:18.521037598Z original_cwd = os.getcwd()
2026-02-21T04:12:18.521040372Z except FileNotFoundError:
2026-02-21T04:12:18.521043354Z # If current dir doesn't exist, use temp dir as fallback
2026-02-21T04:12:18.521046109Z original_cwd = temp_dir
2026-02-21T04:12:18.521047878Z
2026-02-21T04:12:18.521052841Z try:
2026-02-21T04:12:18.521055931Z os.chdir(temp_dir)
2026-02-21T04:12:18.521058041Z
2026-02-21T04:12:18.521060872Z config = RunnerConfig(
2026-02-21T04:12:18.521187514Z code_dir="/job/src",
2026-02-21T04:12:18.521401104Z job_dir="/job/src",
2026-02-21T04:12:18.521405755Z job_command="echo test",
2026-02-21T04:12:18.521410939Z runner_image="alpine:latest"
2026-02-21T04:12:18.521415224Z )
2026-02-21T04:12:18.521418504Z
2026-02-21T04:12:18.521422413Z # Prepare job directory
2026-02-21T04:12:18.521433485Z job_path = prepare_job_directory(config)
2026-02-21T04:12:18.521435942Z
2026-02-21T04:12:18.521439064Z # Create a test file
2026-02-21T04:12:18.521442444Z test_file = job_path / "test.txt"
2026-02-21T04:12:18.521445335Z test_file.write_text("test data")
2026-02-21T04:12:18.521447521Z
2026-02-21T04:12:18.521450145Z # Run container
2026-02-21T04:12:18.521459209Z> run_container(config)
2026-02-21T04:12:18.521461164Z
2026-02-21T04:12:18.521464301Z/workspace/runnerlib/tests/test_job_isolation.py:257:
2026-02-21T04:12:18.522623362Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.522631879Z
2026-02-21T04:12:18.522646816Zconfig = RunnerConfig(code_dir='/job/src', job_dir='/job/src', job_command='echo test', runner_image='alpine:latest', job_env=N...=None, source_type=None, source_url=None, source_ref=None, ci_source_type=None, ci_source_url=None, ci_source_ref=None)
2026-02-21T04:12:18.522654332Zadditional_args = None
2026-02-21T04:12:18.522687592Z
2026-02-21T04:12:18.522694516Z def run_container(
2026-02-21T04:12:18.52270266Z config: RunnerConfig,
2026-02-21T04:12:18.522709443Z additional_args: Optional[List[str]] = None
2026-02-21T04:12:18.52271701Z ) -> int:
2026-02-21T04:12:18.522726693Z """Run the job container using docker with full configuration support.
2026-02-21T04:12:18.522748307Z
2026-02-21T04:12:18.522756213Z Args:
2026-02-21T04:12:18.522765123Z config: Runner configuration
2026-02-21T04:12:18.522774913Z additional_args: Additional arguments to pass to the job command
2026-02-21T04:12:18.522780623Z
2026-02-21T04:12:18.522787983Z Returns:
2026-02-21T04:12:18.522796517Z Exit code of the container process
2026-02-21T04:12:18.522818804Z
2026-02-21T04:12:18.522826154Z Raises:
2026-02-21T04:12:18.522834857Z ValueError: If configuration is invalid
2026-02-21T04:12:18.52284284Z FileNotFoundError: If docker is not available
2026-02-21T04:12:18.522850347Z """
2026-02-21T04:12:18.52285776Z # Create plugin context for the execution
2026-02-21T04:12:18.522882525Z plugin_context = PluginContext(
2026-02-21T04:12:18.522889848Z config=config,
2026-02-21T04:12:18.522898025Z phase=PluginPhase.PRE_SOURCE_PREP,
2026-02-21T04:12:18.522906165Z metadata={}
2026-02-21T04:12:18.522913075Z )
2026-02-21T04:12:18.522918968Z
2026-02-21T04:12:18.522926468Z try:
2026-02-21T04:12:18.522946875Z # Execute pre-source-prep plugins
2026-02-21T04:12:18.522955175Z plugin_manager.execute_phase(PluginPhase.PRE_SOURCE_PREP, plugin_context)
2026-02-21T04:12:18.522959235Z
2026-02-21T04:12:18.522966355Z # Basic validation is handled by CLI layer
2026-02-21T04:12:18.522970375Z
2026-02-21T04:12:18.522976742Z # Check if docker is available
2026-02-21T04:12:18.522982609Z if not shutil.which("docker"):
2026-02-21T04:12:18.523004122Z logger.error("Docker is not available in PATH")
2026-02-21T04:12:18.523010839Z> raise FileNotFoundError("docker is not available in PATH")
2026-02-21T04:12:18.523017625ZE FileNotFoundError: docker is not available in PATH
2026-02-21T04:12:18.523022542Z
2026-02-21T04:12:18.523028592Z/workspace/runnerlib/src/container.py:114: FileNotFoundError
2026-02-21T04:12:18.523036053Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.5230585Z2026-02-21T04:11:46.165099+00:00 [ERROR] [runnerlib] Docker is not available in PATH
2026-02-21T04:12:18.523066243Z___________ TestSourcePreparation.test_no_source_preparation_default ___________
2026-02-21T04:12:18.523070516Z
2026-02-21T04:12:18.52307933Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87ab4ed450>
2026-02-21T04:12:18.5230835Z
2026-02-21T04:12:18.523089873Z def test_no_source_preparation_default(self):
2026-02-21T04:12:18.523097Z """Test job with no source preparation (default - source_type not set)."""
2026-02-21T04:12:18.523117586Z # Configure without specifying source_type
2026-02-21T04:12:18.52312439Z config = get_config(job_command="echo 'hello'")
2026-02-21T04:12:18.523128903Z
2026-02-21T04:12:18.523161493Z # Prepare source should return None
2026-02-21T04:12:18.523171876Z result = prepare_source(config)
2026-02-21T04:12:18.52317835Z> assert result is None
2026-02-21T04:12:18.523199273ZE AssertionError: assert PosixPath('/job/src') is None
2026-02-21T04:12:18.523204341Z
2026-02-21T04:12:18.523212454Z/workspace/runnerlib/tests/test_source_preparation.py:89: AssertionError
2026-02-21T04:12:18.523219061Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.523232341Z2026-02-21T04:11:56.809090+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-21T04:12:18.523240734Z2026-02-21T04:12:06.333236+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.523264367Z2026-02-21T04:12:06.944668+00:00 Repository checked out to: /job/src
2026-02-21T04:12:18.523271011Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.523284964Z2026-02-21T04:11:56.808865+00:00 [INFO] [runnerlib] Preparing source type=git url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T04:12:18.523302841Z2026-02-21T04:11:56.809021+00:00 [INFO] [runnerlib] Preparing git source url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED] target=/job/src
2026-02-21T04:12:18.523311724Z2026-02-21T04:12:06.944578+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-21T04:12:18.523319467Z______________ TestSourcePreparation.test_git_source_preparation _______________
2026-02-21T04:12:18.523339234Z
2026-02-21T04:12:18.523350514Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87ab3efce0>
2026-02-21T04:12:18.523354967Z
2026-02-21T04:12:18.523361634Z def test_git_source_preparation(self):
2026-02-21T04:12:18.523367957Z """Test git source preparation."""
2026-02-21T04:12:18.523374432Z # Create a test git repository
2026-02-21T04:12:18.523382288Z test_repo_dir = Path(self.temp_dir) / "test_repo"
2026-02-21T04:12:18.523402712Z test_repo_dir.mkdir()
2026-02-21T04:12:18.523408805Z repo = Repo.init(test_repo_dir)
2026-02-21T04:12:18.523413592Z
2026-02-21T04:12:18.523419932Z # Add a test file
2026-02-21T04:12:18.523426795Z test_file = test_repo_dir / "test.txt"
2026-02-21T04:12:18.523432945Z test_file.write_text("test content")
2026-02-21T04:12:18.523454982Z repo.index.add([str(test_file)])
2026-02-21T04:12:18.523461682Z repo.index.commit("Initial commit")
2026-02-21T04:12:18.523466565Z
2026-02-21T04:12:18.523472502Z # Configure with git source
2026-02-21T04:12:18.523797761Z config = get_config(
2026-02-21T04:12:18.523821075Z job_command="cat /job/src/test.txt",
2026-02-21T04:12:18.523828001Z source_type="git",
2026-02-21T04:12:18.523835255Z source_url=str(test_repo_dir),
2026-02-21T04:12:18.523843311Z source_ref="[REDACTED]"
2026-02-21T04:12:18.523850178Z )
2026-02-21T04:12:18.523855355Z
2026-02-21T04:12:18.523861428Z # Prepare source
2026-02-21T04:12:18.523881512Z> result = prepare_source(config)
2026-02-21T04:12:18.523886406Z
2026-02-21T04:12:18.523893362Z/workspace/runnerlib/tests/test_source_preparation.py:113:
2026-02-21T04:12:18.523900622Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.523907662Z/workspace/runnerlib/src/source_prep.py:563: in prepare_source
2026-02-21T04:12:18.523916532Z return _prepare_git_source(config.source_url, config.source_ref, target_path)
2026-02-21T04:12:18.523938769Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T04:12:18.523944549Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T04:12:18.523951139Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.523955432Z
2026-02-21T04:12:18.523963102Zrepo = <git.repo.base.Repo '/job/src/.git'>, source_ref = '[REDACTED]'
2026-02-21T04:12:18.523967429Z
2026-02-21T04:12:18.523975146Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T04:12:18.523996839Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T04:12:18.524001779Z
2026-02-21T04:12:18.524008676Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T04:12:18.524015512Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T04:12:18.524029016Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T04:12:18.524037136Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T04:12:18.524042089Z
2026-02-21T04:12:18.52406402Z Args:
2026-02-21T04:12:18.524071713Z repo: GitPython Repo instance (already cloned)
2026-02-21T04:12:18.524079843Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T04:12:18.524084673Z
2026-02-21T04:12:18.524091153Z Raises:
2026-02-21T04:12:18.524097647Z GitCommandError: If all checkout attempts fail
2026-02-21T04:12:18.526140534Z """
2026-02-21T04:12:18.526265068Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T04:12:18.526695452Z try:
2026-02-21T04:12:18.526705759Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.526711906Z return
2026-02-21T04:12:18.526716649Z except GitCommandError:
2026-02-21T04:12:18.526725409Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T04:12:18.526729466Z
2026-02-21T04:12:18.526735849Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T04:12:18.526740882Z try:
2026-02-21T04:12:18.526747329Z repo.git.fetch("origin", source_ref)
2026-02-21T04:12:18.526752386Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.526760139Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T04:12:18.526765406Z return
2026-02-21T04:12:18.526770776Z except GitCommandError:
2026-02-21T04:12:18.526777376Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T04:12:18.526781182Z
2026-02-21T04:12:18.526787456Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T04:12:18.526792726Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T04:12:18.526797289Z if pr_number:
2026-02-21T04:12:18.526803116Z # GitHub: refs/pull/<N>/head
2026-02-21T04:12:18.526807726Z pr_refs = [
2026-02-21T04:12:18.526813202Z f"refs/pull/{pr_number}/head",
2026-02-21T04:12:18.526819136Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T04:12:18.526824066Z ]
2026-02-21T04:12:18.526828836Z for pr_ref in pr_refs:
2026-02-21T04:12:18.526833756Z try:
2026-02-21T04:12:18.526838739Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T04:12:18.526846913Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T04:12:18.526852727Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.52685884Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T04:12:18.526864423Z return
2026-02-21T04:12:18.5268693Z except GitCommandError:
2026-02-21T04:12:18.5268761Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T04:12:18.526880123Z
2026-02-21T04:12:18.526886347Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T04:12:18.526891337Z try:
2026-02-21T04:12:18.52689699Z log_stdout("Fetching all remote refs...")
2026-02-21T04:12:18.526904Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T04:12:18.526909047Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.526915333Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T04:12:18.52692007Z return
2026-02-21T04:12:18.526927193Z except GitCommandError:
2026-02-21T04:12:18.526931987Z pass
2026-02-21T04:12:18.526935717Z
2026-02-21T04:12:18.526941153Z # Nothing worked — raise with a clear message
2026-02-21T04:12:18.52694568Z> raise GitCommandError(
2026-02-21T04:12:18.526950673Z f"git checkout {source_ref}",
2026-02-21T04:12:18.526956363Z 128,
2026-02-21T04:12:18.52696391Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T04:12:18.52696936Z )
2026-02-21T04:12:18.526975957ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.526982533ZE cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.526989733ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.526993663Z
2026-02-21T04:12:18.526998547Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T04:12:18.52700487Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.527028348Z2026-02-21T04:12:06.996945+00:00 Cloning git repository: /tmp/tmpqh9l1nig/test_repo
2026-02-21T04:12:18.527034908Z2026-02-21T04:12:07.062195+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.527040764Z2026-02-21T04:12:07.076410+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T04:12:18.527047534Z2026-02-21T04:12:07.084605+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T04:12:18.527052821Z2026-02-21T04:12:07.095278+00:00 Fetching all remote refs...
2026-02-21T04:12:18.527058954Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.527071498Z2026-02-21T04:12:06.996821+00:00 [INFO] [runnerlib] Preparing source type=git url=/tmp/tmpqh9l1nig/test_repo ref=[REDACTED]
2026-02-21T04:12:18.527082104Z2026-02-21T04:12:06.996910+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpqh9l1nig/test_repo ref=[REDACTED] target=/job/src
2026-02-21T04:12:18.527093628Z2026-02-21T04:12:07.112667+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpqh9l1nig/test_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.527099594Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.527106778Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.527114781Z2026-02-21T04:12:07.112759+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.527119598Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.527125858Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.527132458Z______________ TestSourcePreparation.test_dual_source_preparation ______________
2026-02-21T04:12:18.527136204Z
2026-02-21T04:12:18.527142134Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87ab57d130>
2026-02-21T04:12:18.527145371Z
2026-02-21T04:12:18.527151335Z def test_dual_source_preparation(self):
2026-02-21T04:12:18.527157021Z """Test preparation of both source and ci_source."""
2026-02-21T04:12:18.527162585Z # Create source repo (untrusted code)
2026-02-21T04:12:18.527168278Z source_repo_dir = Path(self.temp_dir) / "source_repo"
2026-02-21T04:12:18.527173942Z source_repo_dir.mkdir()
2026-02-21T04:12:18.527179482Z source_repo = Repo.init(source_repo_dir)
2026-02-21T04:12:18.527186246Z (source_repo_dir / "app.py").write_text("print('hello from PR')")
2026-02-21T04:12:18.527191396Z source_repo.index.add(["app.py"])
2026-02-21T04:12:18.527196596Z source_repo.index.commit("PR commit")
2026-02-21T04:12:18.527200446Z
2026-02-21T04:12:18.527205332Z # Create CI repo (trusted code)
2026-02-21T04:12:18.527210796Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-21T04:12:18.527217716Z ci_repo_dir.mkdir()
2026-02-21T04:12:18.527223729Z ci_repo = Repo.init(ci_repo_dir)
2026-02-21T04:12:18.527230416Z (ci_repo_dir / "pipeline.py").write_text("print('running tests')")
2026-02-21T04:12:18.527235943Z ci_repo.index.add(["pipeline.py"])
2026-02-21T04:12:18.527241143Z ci_repo.index.commit("CI commit")
2026-02-21T04:12:18.527244769Z
2026-02-21T04:12:18.527249859Z # Configure with both sources
2026-02-21T04:12:18.527254726Z config = get_config(
2026-02-21T04:12:18.527261069Z job_command="python /job/ci/pipeline.py",
2026-02-21T04:12:18.527265976Z source_type="git",
2026-02-21T04:12:18.527271346Z source_url=str(source_repo_dir),
2026-02-21T04:12:18.527276619Z source_ref="[REDACTED]",
2026-02-21T04:12:18.527281556Z ci_source_type="git",
2026-02-21T04:12:18.52728726Z ci_source_url=str(ci_repo_dir),
2026-02-21T04:12:18.52729267Z ci_source_ref="[REDACTED]"
2026-02-21T04:12:18.5272977Z )
2026-02-21T04:12:18.527301196Z
2026-02-21T04:12:18.527308346Z # Prepare CI source first (as the CLI does)
2026-02-21T04:12:18.527312926Z> ci_result = prepare_ci_source(config)
2026-02-21T04:12:18.527316146Z
2026-02-21T04:12:18.527321343Z/workspace/runnerlib/tests/test_source_preparation.py:170:
2026-02-21T04:12:18.527327283Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.5273351Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-21T04:12:18.527342261Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-21T04:12:18.527348924Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T04:12:18.527356334Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T04:12:18.527362247Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.527365597Z
2026-02-21T04:12:18.527372781Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-21T04:12:18.527375781Z
2026-02-21T04:12:18.527383261Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T04:12:18.527389467Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T04:12:18.527393131Z
2026-02-21T04:12:18.527399587Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T04:12:18.527405801Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T04:12:18.527412301Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T04:12:18.527421007Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T04:12:18.527425097Z
2026-02-21T04:12:18.527431331Z Args:
2026-02-21T04:12:18.527436924Z repo: GitPython Repo instance (already cloned)
2026-02-21T04:12:18.527443327Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T04:12:18.527447527Z
2026-02-21T04:12:18.527452587Z Raises:
2026-02-21T04:12:18.527458727Z GitCommandError: If all checkout attempts fail
2026-02-21T04:12:18.527463454Z """
2026-02-21T04:12:18.527470591Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T04:12:18.527476211Z try:
2026-02-21T04:12:18.527482234Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.531308951Z return
2026-02-21T04:12:18.531314081Z except GitCommandError:
2026-02-21T04:12:18.531318661Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T04:12:18.531320928Z
2026-02-21T04:12:18.531324446Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T04:12:18.53132816Z try:
2026-02-21T04:12:18.531331337Z repo.git.fetch("origin", source_ref)
2026-02-21T04:12:18.531334101Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.53133732Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T04:12:18.531340774Z return
2026-02-21T04:12:18.531343441Z except GitCommandError:
2026-02-21T04:12:18.531346646Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T04:12:18.531348566Z
2026-02-21T04:12:18.531351435Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T04:12:18.531354Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T04:12:18.531356477Z if pr_number:
2026-02-21T04:12:18.531359245Z # GitHub: refs/pull/<N>/head
2026-02-21T04:12:18.531362166Z pr_refs = [
2026-02-21T04:12:18.531365278Z f"refs/pull/{pr_number}/head",
2026-02-21T04:12:18.53136864Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T04:12:18.53137124Z ]
2026-02-21T04:12:18.531374449Z for pr_ref in pr_refs:
2026-02-21T04:12:18.531377032Z try:
2026-02-21T04:12:18.531379851Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T04:12:18.532213044Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T04:12:18.532217889Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.532222021Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T04:12:18.532226208Z return
2026-02-21T04:12:18.532229797Z except GitCommandError:
2026-02-21T04:12:18.532233048Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T04:12:18.532235351Z
2026-02-21T04:12:18.532238958Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T04:12:18.532242404Z try:
2026-02-21T04:12:18.532245581Z log_stdout("Fetching all remote refs...")
2026-02-21T04:12:18.532249058Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T04:12:18.532251901Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.535323799Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T04:12:18.535330492Z return
2026-02-21T04:12:18.535333353Z except GitCommandError:
2026-02-21T04:12:18.535335904Z pass
2026-02-21T04:12:18.535338159Z
2026-02-21T04:12:18.535341329Z # Nothing worked — raise with a clear message
2026-02-21T04:12:18.535343792Z> raise GitCommandError(
2026-02-21T04:12:18.535346804Z f"git checkout {source_ref}",
2026-02-21T04:12:18.535349864Z 128,
2026-02-21T04:12:18.53535391Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T04:12:18.535356724Z )
2026-02-21T04:12:18.535360564ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535364376ZE cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535368402ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535370359Z
2026-02-21T04:12:18.535373521Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T04:12:18.535377361Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.53538087Z2026-02-21T04:12:07.213333+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-21T04:12:18.535384887Z2026-02-21T04:12:07.213420+00:00 Cloning git repository: /tmp/tmpiynp6uhc/ci_repo
2026-02-21T04:12:18.535388438Z2026-02-21T04:12:07.232746+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.535391602Z2026-02-21T04:12:07.248301+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T04:12:18.535394915Z2026-02-21T04:12:07.256395+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T04:12:18.535398004Z2026-02-21T04:12:07.264411+00:00 Fetching all remote refs...
2026-02-21T04:12:18.535401372Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.535406572Z2026-02-21T04:12:07.213259+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmpiynp6uhc/ci_repo ref=[REDACTED]
2026-02-21T04:12:18.535411687Z2026-02-21T04:12:07.213390+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpiynp6uhc/ci_repo ref=[REDACTED] target=/job/ci
2026-02-21T04:12:18.53541723Z2026-02-21T04:12:07.282953+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpiynp6uhc/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535420489Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535424586Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535429947Z2026-02-21T04:12:07.283033+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535433676Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535437258Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535440581Z__________________ TestSourcePreparation.test_ci_source_only ___________________
2026-02-21T04:12:18.535442429Z
2026-02-21T04:12:18.53544631Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87abb59e10>
2026-02-21T04:12:18.535448296Z
2026-02-21T04:12:18.535451112Z def test_ci_source_only(self):
2026-02-21T04:12:18.535454687Z """Test preparation of CI source without regular source."""
2026-02-21T04:12:18.535457198Z # Create CI repo
2026-02-21T04:12:18.535460294Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-21T04:12:18.535463436Z ci_repo_dir.mkdir()
2026-02-21T04:12:18.535467251Z ci_repo = Repo.init(ci_repo_dir)
2026-02-21T04:12:18.535470953Z (ci_repo_dir / "deploy.sh").write_text("#!/bin/bash\necho deploying")
2026-02-21T04:12:18.535478325Z ci_repo.index.add(["deploy.sh"])
2026-02-21T04:12:18.535481716Z ci_repo.index.commit("CI commit")
2026-02-21T04:12:18.535484194Z
2026-02-21T04:12:18.535487947Z # Configure with only CI source
2026-02-21T04:12:18.535490841Z config = get_config(
2026-02-21T04:12:18.535493947Z job_command="bash /job/ci/deploy.sh",
2026-02-21T04:12:18.535496384Z ci_source_type="git",
2026-02-21T04:12:18.535499573Z ci_source_url=str(ci_repo_dir),
2026-02-21T04:12:18.535503119Z ci_source_ref="[REDACTED]"
2026-02-21T04:12:18.535505659Z )
2026-02-21T04:12:18.535507791Z
2026-02-21T04:12:18.535510581Z # Prepare CI source
2026-02-21T04:12:18.535513407Z> ci_result = prepare_ci_source(config)
2026-02-21T04:12:18.535515165Z
2026-02-21T04:12:18.535518136Z/workspace/runnerlib/tests/test_source_preparation.py:206:
2026-02-21T04:12:18.535521291Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.535524476Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-21T04:12:18.535528261Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-21T04:12:18.535531596Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T04:12:18.535534496Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T04:12:18.535537256Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.535540588Z
2026-02-21T04:12:18.535558001Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-21T04:12:18.535560114Z
2026-02-21T04:12:18.535563373Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T04:12:18.535566905Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T04:12:18.535569174Z
2026-02-21T04:12:18.535572844Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T04:12:18.535576187Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T04:12:18.535580833Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T04:12:18.535584418Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T04:12:18.535586945Z
2026-02-21T04:12:18.535589885Z Args:
2026-02-21T04:12:18.535593742Z repo: GitPython Repo instance (already cloned)
2026-02-21T04:12:18.535597042Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T04:12:18.535599001Z
2026-02-21T04:12:18.535602142Z Raises:
2026-02-21T04:12:18.535605673Z GitCommandError: If all checkout attempts fail
2026-02-21T04:12:18.535608316Z """
2026-02-21T04:12:18.535612271Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T04:12:18.535615478Z try:
2026-02-21T04:12:18.535618545Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.535621475Z return
2026-02-21T04:12:18.535624436Z except GitCommandError:
2026-02-21T04:12:18.535628176Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T04:12:18.535630543Z
2026-02-21T04:12:18.535634128Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T04:12:18.535637317Z try:
2026-02-21T04:12:18.535640308Z repo.git.fetch("origin", source_ref)
2026-02-21T04:12:18.535643432Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.535646851Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T04:12:18.535649752Z return
2026-02-21T04:12:18.535652648Z except GitCommandError:
2026-02-21T04:12:18.535655999Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T04:12:18.535657976Z
2026-02-21T04:12:18.535661277Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T04:12:18.535663997Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T04:12:18.53566758Z if pr_number:
2026-02-21T04:12:18.5356706Z # GitHub: refs/pull/<N>/head
2026-02-21T04:12:18.535673557Z pr_refs = [
2026-02-21T04:12:18.535676939Z f"refs/pull/{pr_number}/head",
2026-02-21T04:12:18.535680254Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T04:12:18.535683393Z ]
2026-02-21T04:12:18.535686837Z for pr_ref in pr_refs:
2026-02-21T04:12:18.535689473Z try:
2026-02-21T04:12:18.535692523Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T04:12:18.535696142Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T04:12:18.535699542Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.535702716Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T04:12:18.535705523Z return
2026-02-21T04:12:18.535708088Z except GitCommandError:
2026-02-21T04:12:18.535711294Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T04:12:18.535713362Z
2026-02-21T04:12:18.535717211Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T04:12:18.535719976Z try:
2026-02-21T04:12:18.535723771Z log_stdout("Fetching all remote refs...")
2026-02-21T04:12:18.535727789Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T04:12:18.535730699Z repo.git.checkout(source_ref)
2026-02-21T04:12:18.535733959Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T04:12:18.53573688Z return
2026-02-21T04:12:18.535739557Z except GitCommandError:
2026-02-21T04:12:18.535742426Z pass
2026-02-21T04:12:18.535744313Z
2026-02-21T04:12:18.535747466Z # Nothing worked — raise with a clear message
2026-02-21T04:12:18.53575015Z> raise GitCommandError(
2026-02-21T04:12:18.535752668Z f"git checkout {source_ref}",
2026-02-21T04:12:18.535755314Z 128,
2026-02-21T04:12:18.535759131Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T04:12:18.535761791Z )
2026-02-21T04:12:18.53576523ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535768382ZE cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535771982ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535774154Z
2026-02-21T04:12:18.535777168Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T04:12:18.535803758Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.535807434Z2026-02-21T04:12:07.340218+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-21T04:12:18.535810948Z2026-02-21T04:12:07.340314+00:00 Cloning git repository: /tmp/tmpwdn5oazg/ci_repo
2026-02-21T04:12:18.535814458Z2026-02-21T04:12:07.365384+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.535817544Z2026-02-21T04:12:07.376551+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T04:12:18.535821104Z2026-02-21T04:12:07.388462+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T04:12:18.535824169Z2026-02-21T04:12:07.397229+00:00 Fetching all remote refs...
2026-02-21T04:12:18.535892235Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.535897472Z2026-02-21T04:12:07.340127+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmpwdn5oazg/ci_repo ref=[REDACTED]
2026-02-21T04:12:18.535902341Z2026-02-21T04:12:07.340281+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpwdn5oazg/ci_repo ref=[REDACTED] target=/job/ci
2026-02-21T04:12:18.535907283Z2026-02-21T04:12:07.432614+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpwdn5oazg/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535910277Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535913528Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535917628Z2026-02-21T04:12:07.432734+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T04:12:18.535920443Z cmdline: git checkout [REDACTED]
2026-02-21T04:12:18.535924257Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T04:12:18.535929038Z______________ TestSourcePreparation.test_git_source_missing_url _______________
2026-02-21T04:12:18.535930901Z
2026-02-21T04:12:18.535934471Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87ab53c650>
2026-02-21T04:12:18.535936585Z
2026-02-21T04:12:18.535939531Z def test_git_source_missing_url(self):
2026-02-21T04:12:18.535942728Z """Test that git source without URL raises ValueError."""
2026-02-21T04:12:18.535945905Z config = get_config(
2026-02-21T04:12:18.535948512Z job_command="echo 'test'",
2026-02-21T04:12:18.535951331Z source_type="git"
2026-02-21T04:12:18.535954088Z # source_url not provided
2026-02-21T04:12:18.535956692Z )
2026-02-21T04:12:18.535958606Z
2026-02-21T04:12:18.535962749Z> with pytest.raises(ValueError, match="source_url is required"):
2026-02-21T04:12:18.535965313ZE Failed: DID NOT RAISE <class 'ValueError'>
2026-02-21T04:12:18.535966829Z
2026-02-21T04:12:18.535970073Z/workspace/runnerlib/tests/test_source_preparation.py:233: Failed
2026-02-21T04:12:18.535972933Z----------------------------- Captured stdout call -----------------------------
2026-02-21T04:12:18.535977246Z2026-02-21T04:12:07.469588+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-21T04:12:18.53598091Z2026-02-21T04:12:17.363324+00:00 Checking out ref: [REDACTED]
2026-02-21T04:12:18.535984407Z2026-02-21T04:12:17.880291+00:00 Repository checked out to: /job/src
2026-02-21T04:12:18.535987549Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.535993824Z2026-02-21T04:12:07.469481+00:00 [INFO] [runnerlib] Preparing source type=git url=[REDACTED] ref=[REDACTED]
2026-02-21T04:12:18.536005867Z2026-02-21T04:12:07.469553+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/job/src
2026-02-21T04:12:18.536010019Z2026-02-21T04:12:17.880160+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-21T04:12:18.536013175Z______________ TestSourcePreparation.test_copy_source_missing_url ______________
2026-02-21T04:12:18.536014956Z
2026-02-21T04:12:18.536018Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f87ab53c750>
2026-02-21T04:12:18.536019843Z
2026-02-21T04:12:18.536022904Z def test_copy_source_missing_url(self):
2026-02-21T04:12:18.536026113Z """Test that copy source without URL raises ValueError."""
2026-02-21T04:12:18.53602891Z config = get_config(
2026-02-21T04:12:18.536031738Z job_command="echo 'test'",
2026-02-21T04:12:18.536034095Z source_type="copy"
2026-02-21T04:12:18.536037524Z # source_url not provided
2026-02-21T04:12:18.536039872Z )
2026-02-21T04:12:18.536041901Z
2026-02-21T04:12:18.53604482Z with pytest.raises(ValueError, match="source_url is required"):
2026-02-21T04:12:18.536047263Z> prepare_source(config)
2026-02-21T04:12:18.536049113Z
2026-02-21T04:12:18.536051764Z/workspace/runnerlib/tests/test_source_preparation.py:245:
2026-02-21T04:12:18.53605453Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.536057663Z/workspace/runnerlib/src/source_prep.py:568: in prepare_source
2026-02-21T04:12:18.538669858Z return _prepare_copy_source(config.source_url, target_path)
2026-02-21T04:12:18.538680242Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T04:12:18.538686745Z
2026-02-21T04:12:18.538697875Zsource_url = 'https://[REDACTED].com/[REDACTED].git'
2026-02-21T04:12:18.538704472Ztarget_path = PosixPath('/job/src')
2026-02-21T04:12:18.538708345Z
2026-02-21T04:12:18.538717042Z def _prepare_copy_source(source_url: str, target_path: Path) -> Path:
2026-02-21T04:12:18.538723968Z """Prepare source code by copying from a local directory.
2026-02-21T04:12:18.538728652Z
2026-02-21T04:12:18.538734838Z Args:
2026-02-21T04:12:18.538744972Z source_url: Path to source directory
2026-02-21T04:12:18.538751178Z target_path: Where to copy the directory
2026-02-21T04:12:18.538755615Z
2026-02-21T04:12:18.538761688Z Returns:
2026-02-21T04:12:18.538767988Z Path to the copied directory
2026-02-21T04:12:18.538773125Z """
2026-02-21T04:12:18.538778705Z source_path = Path(source_url).resolve()
2026-02-21T04:12:18.538782658Z
2026-02-21T04:12:18.538788495Z if not source_path.exists():
2026-02-21T04:12:18.538795615Z> raise FileNotFoundError(f"Source directory does not exist: {source_path}")
2026-02-21T04:12:18.538806263ZE FileNotFoundError: Source directory does not exist: /tmp/tmpo7euhd05/https:/[REDACTED].com/[REDACTED].git
2026-02-21T04:12:18.538810096Z
2026-02-21T04:12:18.538816919Z/workspace/runnerlib/src/source_prep.py:433: FileNotFoundError
2026-02-21T04:12:18.538823733Z----------------------------- Captured stderr call -----------------------------
2026-02-21T04:12:18.538837479Z2026-02-21T04:12:17.893577+00:00 [INFO] [runnerlib] Preparing source type=copy url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T04:12:18.538844713Z_____ TestConfigValidator.test_validate_file_system_job_directory_missing ______
2026-02-21T04:12:18.538848639Z
2026-02-21T04:12:18.538856006Zself = <runnerlib.tests.test_validation.TestConfigValidator object at 0x7f87abb66c50>
2026-02-21T04:12:18.538859539Z
2026-02-21T04:12:18.538866106Z def test_validate_file_system_job_directory_missing(self):
2026-02-21T04:12:18.538872283Z """Test file system validation when job directory is missing."""
2026-02-21T04:12:18.538878256Z # Ensure ./job doesn't exist
2026-02-21T04:12:18.538884173Z job_path = Path("./job")
2026-02-21T04:12:18.538889409Z if job_path.exists():
2026-02-21T04:12:18.538895059Z shutil.rmtree(job_path)
2026-02-21T04:12:18.538899073Z
2026-02-21T04:12:18.538904993Z try:
2026-02-21T04:12:18.538913903Z errors, warnings = self.validator._validate_file_system(self.valid_config)
2026-02-21T04:12:18.538918596Z
2026-02-21T04:12:18.538925006Z # Should have warning about missing directory
2026-02-21T04:12:18.538930446Z> assert len(warnings) >= 1
2026-02-21T04:12:18.538935976ZE assert 0 >= 1
2026-02-21T04:12:18.538941346ZE + where 0 = len([])
2026-02-21T04:12:18.538945013Z
2026-02-21T04:12:18.538951679Z/workspace/runnerlib/tests/test_validation.py:304: AssertionError
2026-02-21T04:12:18.538958166Z=========================== short test summary info ============================
2026-02-21T04:12:18.538965896ZFAILED tests/test_container_isolation.py::TestContainerIsolation::test_work_directory_isolation_with_prepare
2026-02-21T04:12:18.538973947ZFAILED tests/test_directory_operations.py::TestDirectoryOperations::test_cleanup_removes_job_directory
2026-02-21T04:12:18.53898135ZFAILED tests/test_docker_execution.py::test_basic_docker_execution - Assertio...
2026-02-21T04:12:18.538989934ZFAILED tests/test_docker_execution.py::test_docker_with_environment_variables
2026-02-21T04:12:18.53899662ZFAILED tests/test_docker_execution.py::test_docker_with_python - AssertionErr...
2026-02-21T04:12:18.539002867ZFAILED tests/test_docker_execution.py::test_docker_failure_handling - Asserti...
2026-02-21T04:12:18.539009967ZFAILED tests/test_docker_execution.py::test_docker_available - FileNotFoundEr...
2026-02-21T04:12:18.53901654ZFAILED tests/test_docker_execution.py::test_container_with_working_directory
2026-02-21T04:12:18.53902268ZFAILED tests/test_docker_execution.py::test_dry_run_mode - AssertionError: Dr...
2026-02-21T04:12:18.539028934ZFAILED tests/test_docker_execution.py::test_node_container - AssertionError: ...
2026-02-21T04:12:18.539035957ZFAILED tests/test_docker_execution.py::test_container_with_multiple_env_vars
2026-02-21T04:12:18.539042644ZFAILED tests/test_docker_execution.py::test_selective_secret_masking - Assert...
2026-02-21T04:12:18.5390498ZFAILED tests/test_dynamic_secret_masking.py::test_value_printed_then_masked
2026-02-21T04:12:18.539057067ZFAILED tests/test_dynamic_secret_masking.py::test_multiple_values_masked_after_registration
2026-02-21T04:12:18.539065024ZFAILED tests/test_dynamic_secret_masking.py::test_immediate_masking_in_streaming_output
2026-02-21T04:12:18.539074317ZFAILED tests/test_dynamic_secrets.py::test_dynamic_secret_registration - Asse...
2026-02-21T04:12:18.539081734ZFAILED tests/test_dynamic_secrets.py::test_multiple_dynamic_secrets - Asserti...
2026-02-21T04:12:18.53908798ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_pr_uses_base_ref_for_diff
2026-02-21T04:12:18.539095397ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_push_uses_head_parent_for_diff
2026-02-21T04:12:18.53910148ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_no_git_dir_skips_changed_files
2026-02-21T04:12:18.53910848ZFAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_source_when_missing
2026-02-21T04:12:18.53911527ZFAILED tests/test_git_operations.py::TestGitOperations::test_checkout_creates_job_directory
2026-02-21T04:12:18.539123947ZFAILED tests/test_integration.py::TestDirectoryManagementIntegration::test_directory_validation_with_real_filesystem
2026-02-21T04:12:18.539167781ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_work_dir_isolation
2026-02-21T04:12:18.539175835ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_concurrent_job_isolation
2026-02-21T04:12:18.539183155ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_container_mount_isolation
2026-02-21T04:12:18.539194668ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_no_source_preparation_default
2026-02-21T04:12:18.539201998ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_preparation
2026-02-21T04:12:18.539211491ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_dual_source_preparation
2026-02-21T04:12:18.539217701ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_ci_source_only
2026-02-21T04:12:18.539224788ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_missing_url
2026-02-21T04:12:18.539232488ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_copy_source_missing_url
2026-02-21T04:12:18.539241265ZFAILED tests/test_validation.py::TestConfigValidator::test_validate_file_system_job_directory_missing
2026-02-21T04:12:18.539247531Z============ 33 failed, 363 passed, 1 skipped in 149.85s (0:02:29) =============