Reactorcide

← Back to Jobs

test-python

failed exit: 1

Triggered by eval job 019c7975-413b-954e-6d2d-58fb47d5434c

Job ID
019c7975-ab02-4175-23e1-9c4311cb7b67
Created
2026-02-20 05:11:20 UTC
Updated
2026-02-20 05:11:20 UTC
Duration
4m 55s
Source Ref
a52ff5e7cac22c52bc6645c286160a9757f7f115
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-20T05:11:32.108069523ZCloning into '/workspace'...
2026-02-20T05:12:19.629059337Z=== Running Python Tests ===
2026-02-20T05:12:19.951898456ZUsing CPython 3.13.12 interpreter at: /usr/local/bin/python3.13
2026-02-20T05:12:19.951908483ZCreating virtual environment at: .venv
2026-02-20T05:12:20.018510381Z Building runnerlib @ file:///workspace/runnerlib
2026-02-20T05:12:20.198588918ZDownloading cryptography (4.3MiB)
2026-02-20T05:12:20.204010541ZDownloading pygments (1.2MiB)
2026-02-20T05:12:22.68011871Z Downloaded pygments
2026-02-20T05:12:22.837737861Z Built runnerlib @ file:///workspace/runnerlib
2026-02-20T05:12:24.907475264Z Downloaded cryptography
2026-02-20T05:12:24.913825964Zwarning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
2026-02-20T05:12:24.913838454Z If the cache and target directories are on different filesystems, hardlinking may not be supported.
2026-02-20T05:12:24.91384772Z If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
2026-02-20T05:12:25.020589637ZInstalled 22 packages in 111ms
2026-02-20T05:12:28.461527943Z============================= test session starts ==============================
2026-02-20T05:12:28.461537983Zplatform linux -- Python 3.13.12, pytest-8.3.5, pluggy-1.6.0
2026-02-20T05:12:28.461542236Zrootdir: /workspace/runnerlib
2026-02-20T05:12:28.461547963Zconfigfile: pyproject.toml
2026-02-20T05:12:28.461551839Zplugins: cov-7.0.0
2026-02-20T05:12:28.461555893Zcollected 397 items
2026-02-20T05:12:28.461558356Z
2026-02-20T05:12:28.497145381Ztests/test_config.py .................... [ 5%]
2026-02-20T05:12:28.511820628Ztests/test_container_advanced.py ......... [ 7%]
2026-02-20T05:12:28.57045021Ztests/test_container_isolation.py ...F [ 8%]
2026-02-20T05:12:28.601575817Ztests/test_container_validation.py ................... [ 13%]
2026-02-20T05:12:28.731868655Ztests/test_directory_operations.py ......F..... [ 16%]
2026-02-20T05:12:33.800133791Ztests/test_docker_execution.py FFFFFFFFFF [ 18%]
2026-02-20T05:12:35.449804266Ztests/test_dynamic_secret_masking.py FFF [ 19%]
2026-02-20T05:12:36.981903308Ztests/test_dynamic_secrets.py FsF [ 20%]
2026-02-20T05:12:37.151015917Ztests/test_eval.py ..................................................... [ 33%]
2026-02-20T05:12:37.20648654Z............. [ 36%]
2026-02-20T05:15:11.104192885Ztests/test_eval_cli.py F...FFFF.FFFFFFFF.FFF [ 42%]
2026-02-20T05:15:13.544211054Ztests/test_git_operations.py ........F. [ 44%]
2026-02-20T05:15:14.075001349Ztests/test_git_ops.py ....... [ 46%]
2026-02-20T05:15:14.654798456Ztests/test_integration.py ...F....... [ 49%]
2026-02-20T05:15:15.847691158Ztests/test_job_isolation.py FF.F [ 50%]
2026-02-20T05:15:15.961772892Ztests/test_plugins.py ....................... [ 55%]
2026-02-20T05:15:16.016397869Ztests/test_register_secret.py ............ [ 58%]
2026-02-20T05:15:16.507825278Ztests/test_secrets.py ................... [ 63%]
2026-02-20T05:15:22.68598833Ztests/test_secrets_local.py ............................ [ 70%]
2026-02-20T05:15:22.720627135Ztests/test_secrets_resolver.py ............................. [ 78%]
2026-02-20T05:15:28.18691559Ztests/test_secrets_server.py ........ [ 80%]
2026-02-20T05:16:22.282588427Ztests/test_source_preparation.py .FF.FF.FF...... [ 83%]
2026-02-20T05:16:22.371142381Ztests/test_validation.py ......................F.... [ 90%]
2026-02-20T05:16:22.998589067Ztests/test_workflow.py ........FF......F.........F.......FFF [100%]
2026-02-20T05:16:22.998604097Z
2026-02-20T05:16:22.9986093Z=================================== FAILURES ===================================
2026-02-20T05:16:22.998618357Z______ TestContainerIsolation.test_work_directory_isolation_with_prepare _______
2026-02-20T05:16:22.998621074Z
2026-02-20T05:16:22.998945887Zself = <runnerlib.tests.test_container_isolation.TestContainerIsolation object at 0x7fdefca1dcd0>
2026-02-20T05:16:22.998949777Z
2026-02-20T05:16:22.998955687Z def test_work_directory_isolation_with_prepare(self):
2026-02-20T05:16:22.999811111Z """Test that prepare_job_directory respects work directory changes."""
2026-02-20T05:16:22.999820561Z with tempfile.TemporaryDirectory() as work_dir1:
2026-02-20T05:16:22.999825811Z with tempfile.TemporaryDirectory() as work_dir2:
2026-02-20T05:16:22.999830319Z original_cwd = os.getcwd()
2026-02-20T05:16:23.000807685Z
2026-02-20T05:16:23.000817748Z try:
2026-02-20T05:16:23.000822525Z # Prepare job 1
2026-02-20T05:16:23.000826945Z os.chdir(work_dir1)
2026-02-20T05:16:23.000836705Z config1 = RunnerConfig(
2026-02-20T05:16:23.000840765Z code_dir="/job/src",
2026-02-20T05:16:23.000844358Z job_dir="/job/src",
2026-02-20T05:16:23.000847958Z job_command="echo job1",
2026-02-20T05:16:23.000852355Z runner_image="alpine:latest"
2026-02-20T05:16:23.000856293Z )
2026-02-20T05:16:23.000860233Z job_path1 = prepare_job_directory(config1)
2026-02-20T05:16:23.000985128Z assert job_path1.exists()
2026-02-20T05:16:23.000989774Z> assert str(job_path1).startswith(work_dir1)
2026-02-20T05:16:23.000993321ZE AssertionError: assert False
2026-02-20T05:16:23.001005194ZE + where False = <built-in method startswith of str object at 0x7fdefc6054d0>('/tmp/tmpir9fdn7u')
2026-02-20T05:16:23.001018328ZE + where <built-in method startswith of str object at 0x7fdefc6054d0> = '/job'.startswith
2026-02-20T05:16:23.001025318ZE + where '/job' = str(PosixPath('/job'))
2026-02-20T05:16:23.00127316Z
2026-02-20T05:16:23.0012907Ztests/test_container_isolation.py:158: AssertionError
2026-02-20T05:16:23.002051958Z__________ TestDirectoryOperations.test_cleanup_removes_job_directory __________
2026-02-20T05:16:23.002059421Z
2026-02-20T05:16:23.002069301Zself = <runnerlib.tests.test_directory_operations.TestDirectoryOperations object at 0x7fdefca4dd00>
2026-02-20T05:16:23.002085024Z
2026-02-20T05:16:23.002092464Z def test_cleanup_removes_job_directory(self):
2026-02-20T05:16:23.002099918Z """Test that cleanup removes the job directory."""
2026-02-20T05:16:23.002106332Z job_dir = Path("./job")
2026-02-20T05:16:23.002112185Z job_dir.mkdir(exist_ok=True)
2026-02-20T05:16:23.002243796Z
2026-02-20T05:16:23.002252843Z # Create some files
2026-02-20T05:16:23.002259013Z (job_dir / "file.txt").write_text("Content")
2026-02-20T05:16:23.002264793Z (job_dir / "subdir").mkdir(exist_ok=True)
2026-02-20T05:16:23.002270936Z (job_dir / "subdir" / "nested.txt").write_text("Nested")
2026-02-20T05:16:23.002365391Z
2026-02-20T05:16:23.002376734Z # Perform cleanup
2026-02-20T05:16:23.002382351Z cleanup_job_directory()
2026-02-20T05:16:23.002386141Z
2026-02-20T05:16:23.002392071Z # Job directory should be gone
2026-02-20T05:16:23.002820852Z> assert not job_dir.exists()
2026-02-20T05:16:23.002828226ZE AssertionError: assert not True
2026-02-20T05:16:23.002833386ZE + where True = exists()
2026-02-20T05:16:23.002838896ZE + where exists = PosixPath('job').exists
2026-02-20T05:16:23.002842599Z
2026-02-20T05:16:23.002849419Ztests/test_directory_operations.py:172: AssertionError
2026-02-20T05:16:23.002855156Z_________________________ test_basic_docker_execution __________________________
2026-02-20T05:16:23.003168495Z
2026-02-20T05:16:23.003178772Z def test_basic_docker_execution():
2026-02-20T05:16:23.003187239Z """Test that we can execute a simple container with Docker."""
2026-02-20T05:16:23.003956707Z
2026-02-20T05:16:23.003967567Z # Create a temporary working directory
2026-02-20T05:16:23.003974753Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.003987273Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.003992073Z
2026-02-20T05:16:23.003997817Z # Create job directory structure
2026-02-20T05:16:23.004002837Z job_dir = work_dir / "job"
2026-02-20T05:16:23.004153199Z job_dir.mkdir()
2026-02-20T05:16:23.004400868Z
2026-02-20T05:16:23.004414545Z # Create a simple test script
2026-02-20T05:16:23.004421702Z test_script = job_dir / "test.sh"
2026-02-20T05:16:23.004428865Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.004443205Z echo "Hello from Docker container"
2026-02-20T05:16:23.005664817Z echo "Current directory: $(pwd)"
2026-02-20T05:16:23.005670437Z echo "Job directory contents:"
2026-02-20T05:16:23.005681517Z ls -la /job/
2026-02-20T05:16:23.00568566Z exit 0
2026-02-20T05:16:23.005689504Z """)
2026-02-20T05:16:23.00569355Z test_script.chmod(0o755)
2026-02-20T05:16:23.005696737Z
2026-02-20T05:16:23.005701147Z # Run the container using runnerlib CLI
2026-02-20T05:16:23.005704867Z result = subprocess.run(
2026-02-20T05:16:23.005990267Z [
2026-02-20T05:16:23.006001003Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.006008893Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.00601523Z "--job-command", "sh /job/test.sh",
2026-02-20T05:16:23.006021957Z "--code-dir", "/job",
2026-02-20T05:16:23.006029174Z "--job-dir", "/job",
2026-02-20T05:16:23.006205666Z ],
2026-02-20T05:16:23.006213713Z capture_output=True,
2026-02-20T05:16:23.006220613Z text=True,
2026-02-20T05:16:23.006227253Z cwd=work_dir, # Run from the temp directory
2026-02-20T05:16:23.006241646Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.006491962Z )
2026-02-20T05:16:23.006502978Z
2026-02-20T05:16:23.006508555Z print("STDOUT:", result.stdout)
2026-02-20T05:16:23.006515042Z print("STDERR:", result.stderr)
2026-02-20T05:16:23.006519308Z print("Return code:", result.returncode)
2026-02-20T05:16:23.007466175Z
2026-02-20T05:16:23.007474518Z # Verify the execution
2026-02-20T05:16:23.007480322Z> assert result.returncode == 0, f"Container execution failed with code {result.returncode}"
2026-02-20T05:16:23.007484898ZE AssertionError: Container execution failed with code 1
2026-02-20T05:16:23.007488332ZE assert 1 == 0
2026-02-20T05:16:23.007502615ZE + 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-20T05:16:23.007504842Z
2026-02-20T05:16:23.007509172Ztests/test_docker_execution.py:51: AssertionError
2026-02-20T05:16:23.008823136Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.008828602ZSTDOUT:
2026-02-20T05:16:23.008833036ZSTDERR: 2026-02-20T05:12:29.345997+00:00 Configuration validation failed:
2026-02-20T05:16:23.008838112Z2026-02-20T05:12:29.346086+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.008843536Z • system: docker is not available in PATH
2026-02-20T05:16:23.008848082Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.008851186Z
2026-02-20T05:16:23.008857086Z⚠️ Configuration warnings:
2026-02-20T05:16:23.008862942Z • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.008870629Z 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.008874962Z
2026-02-20T05:16:23.008878732Z
2026-02-20T05:16:23.008882396ZReturn code: 1
2026-02-20T05:16:23.008887489Z____________________ test_docker_with_environment_variables ____________________
2026-02-20T05:16:23.00901783Z
2026-02-20T05:16:23.009029018Z def test_docker_with_environment_variables():
2026-02-20T05:16:23.009034061Z """Test Docker execution with environment variables."""
2026-02-20T05:16:23.009037101Z
2026-02-20T05:16:23.00934095Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.009346556Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.009507559Z
2026-02-20T05:16:23.009514402Z # Create job directory
2026-02-20T05:16:23.009851859Z job_dir = work_dir / "job"
2026-02-20T05:16:23.009859652Z job_dir.mkdir()
2026-02-20T05:16:23.010523689Z
2026-02-20T05:16:23.010531803Z # Create script that uses environment variables
2026-02-20T05:16:23.010536276Z test_script = job_dir / "env_test.sh"
2026-02-20T05:16:23.010540153Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.010544143Z echo "TEST_VAR=$TEST_VAR"
2026-02-20T05:16:23.011278613Z echo "CUSTOM_VAR=$CUSTOM_VAR"
2026-02-20T05:16:23.011288996Z if [ "$TEST_VAR" = "test_value" ]; then
2026-02-20T05:16:23.011295363Z echo "Environment variables work!"
2026-02-20T05:16:23.011301903Z exit 0
2026-02-20T05:16:23.011307456Z else
2026-02-20T05:16:23.011313063Z echo "Environment variables failed"
2026-02-20T05:16:23.011326914Z exit 1
2026-02-20T05:16:23.011332797Z fi
2026-02-20T05:16:23.011338177Z """)
2026-02-20T05:16:23.011867401Z test_script.chmod(0o755)
2026-02-20T05:16:23.011872038Z
2026-02-20T05:16:23.011877258Z # Create env file (use relative path from working directory)
2026-02-20T05:16:23.011883631Z env_file = job_dir / "test.env"
2026-02-20T05:16:23.011887411Z env_file.write_text("""# Test environment
2026-02-20T05:16:23.011891328Z TEST_VAR=test_value
2026-02-20T05:16:23.011894641Z CUSTOM_VAR=custom_value
2026-02-20T05:16:23.012192462Z """)
2026-02-20T05:16:23.012196608Z
2026-02-20T05:16:23.012204468Z # Run with environment file - needs to be relative path starting with ./job/
2026-02-20T05:16:23.012208328Z result = subprocess.run(
2026-02-20T05:16:23.012212159Z [
2026-02-20T05:16:23.012521988Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.012528612Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.012532682Z "--job-command", "sh /job/env_test.sh",
2026-02-20T05:16:23.012726487Z "--code-dir", "/job",
2026-02-20T05:16:23.012731874Z "--job-dir", "/job",
2026-02-20T05:16:23.012931696Z "--job-env", "./job/test.env",
2026-02-20T05:16:23.012936516Z ],
2026-02-20T05:16:23.012940186Z capture_output=True,
2026-02-20T05:16:23.013653736Z text=True,
2026-02-20T05:16:23.013659513Z cwd=work_dir,
2026-02-20T05:16:23.013664979Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.013668886Z )
2026-02-20T05:16:23.013682296Z
2026-02-20T05:16:23.013687339Z print("ENV TEST STDOUT:", result.stdout)
2026-02-20T05:16:23.014201949Z print("ENV TEST STDERR:", result.stderr)
2026-02-20T05:16:23.014209562Z
2026-02-20T05:16:23.014218429Z> assert result.returncode == 0, f"Environment test failed with code {result.returncode}"
2026-02-20T05:16:23.014228469ZE AssertionError: Environment test failed with code 1
2026-02-20T05:16:23.014240352ZE assert 1 == 0
2026-02-20T05:16:23.014268309ZE + 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-20T05:16:23.014275912Z
2026-02-20T05:16:23.014284059Ztests/test_docker_execution.py:108: AssertionError
2026-02-20T05:16:23.014292413Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.014298136ZENV TEST STDOUT:
2026-02-20T05:16:23.014848135ZENV TEST STDERR: 2026-02-20T05:12:29.731052+00:00 Configuration validation failed:
2026-02-20T05:16:23.014857216Z2026-02-20T05:12:29.731126+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.014865119Z • system: docker is not available in PATH
2026-02-20T05:16:23.014870656Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.014874502Z
2026-02-20T05:16:23.014879439Z⚠️ Configuration warnings:
2026-02-20T05:16:23.014885249Z • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.014892526Z 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.015389056Z
2026-02-20T05:16:23.015393083Z
2026-02-20T05:16:23.015448857Z___________________________ test_docker_with_python ____________________________
2026-02-20T05:16:23.015452951Z
2026-02-20T05:16:23.015457344Z def test_docker_with_python():
2026-02-20T05:16:23.015516457Z """Test running Python code in a container."""
2026-02-20T05:16:23.015522124Z
2026-02-20T05:16:23.015908332Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.015915846Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.015920192Z
2026-02-20T05:16:23.015925656Z # Create job directory
2026-02-20T05:16:23.016188204Z job_dir = work_dir / "job"
2026-02-20T05:16:23.016515155Z job_dir.mkdir()
2026-02-20T05:16:23.016519048Z
2026-02-20T05:16:23.016529148Z # Create Python script
2026-02-20T05:16:23.016533368Z py_script = job_dir / "test.py"
2026-02-20T05:16:23.016791508Z py_script.write_text("""
2026-02-20T05:16:23.016796568Z import sys
2026-02-20T05:16:23.016799888Z import os
2026-02-20T05:16:23.016803441Z
2026-02-20T05:16:23.017292766Z print(f"Python version: {sys.version.split()[0]}")
2026-02-20T05:16:23.017297786Z print(f"Working directory: {os.getcwd()}")
2026-02-20T05:16:23.017301556Z print(f"Job files: {os.listdir('/job')}")
2026-02-20T05:16:23.017304799Z
2026-02-20T05:16:23.017308382Z # Test that we can write output
2026-02-20T05:16:23.017595219Z with open('/job/output.txt', 'w') as f:
2026-02-20T05:16:23.017605063Z f.write("Test output from Python container\\n")
2026-02-20T05:16:23.017610156Z
2026-02-20T05:16:23.017616243Z print("Successfully wrote output file")
2026-02-20T05:16:23.018205408Z sys.exit(0)
2026-02-20T05:16:23.018210435Z """)
2026-02-20T05:16:23.018213382Z
2026-02-20T05:16:23.018221088Z # Run Python container
2026-02-20T05:16:23.018224505Z result = subprocess.run(
2026-02-20T05:16:23.018228135Z [
2026-02-20T05:16:23.018785937Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.019213902Z "--runner-image", "python:3.11-alpine",
2026-02-20T05:16:23.019222352Z "--job-command", "python /job/test.py",
2026-02-20T05:16:23.019228389Z "--code-dir", "/job",
2026-02-20T05:16:23.019234652Z "--job-dir", "/job",
2026-02-20T05:16:23.019240719Z ],
2026-02-20T05:16:23.019324306Z capture_output=True,
2026-02-20T05:16:23.01933353Z text=True,
2026-02-20T05:16:23.019339316Z cwd=work_dir,
2026-02-20T05:16:23.01934645Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.019917702Z )
2026-02-20T05:16:23.019926822Z
2026-02-20T05:16:23.019931568Z print("PYTHON TEST STDOUT:", result.stdout)
2026-02-20T05:16:23.019939342Z print("PYTHON TEST STDERR:", result.stderr)
2026-02-20T05:16:23.020233999Z
2026-02-20T05:16:23.020250619Z> assert result.returncode == 0, f"Python container failed with code {result.returncode}"
2026-02-20T05:16:23.020258279ZE AssertionError: Python container failed with code 1
2026-02-20T05:16:23.020263222ZE assert 1 == 0
2026-02-20T05:16:23.020996486ZE + 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-20T05:16:23.021000112Z
2026-02-20T05:16:23.021005469Ztests/test_docker_execution.py:162: AssertionError
2026-02-20T05:16:23.021010492Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.021014229ZPYTHON TEST STDOUT:
2026-02-20T05:16:23.021018669ZPYTHON TEST STDERR: 2026-02-20T05:12:30.136473+00:00 Configuration validation failed:
2026-02-20T05:16:23.021023929Z2026-02-20T05:12:30.136548+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.021028692Z • system: docker is not available in PATH
2026-02-20T05:16:23.021032593Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.021034597Z
2026-02-20T05:16:23.02103691Z
2026-02-20T05:16:23.02104116Z_________________________ test_docker_failure_handling _________________________
2026-02-20T05:16:23.021043847Z
2026-02-20T05:16:23.021274249Z def test_docker_failure_handling():
2026-02-20T05:16:23.021280605Z """Test that container failures are properly reported."""
2026-02-20T05:16:23.021283359Z
2026-02-20T05:16:23.021912935Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.021921035Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.021925961Z
2026-02-20T05:16:23.021931901Z # Create job directory
2026-02-20T05:16:23.022403086Z job_dir = work_dir / "job"
2026-02-20T05:16:23.022413146Z job_dir.mkdir()
2026-02-20T05:16:23.022416086Z
2026-02-20T05:16:23.02242037Z # Create a script that fails
2026-02-20T05:16:23.02242605Z fail_script = job_dir / "fail.sh"
2026-02-20T05:16:23.022432763Z fail_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.022876275Z echo "This script will fail"
2026-02-20T05:16:23.022886208Z echo "Error: Something went wrong" >&2
2026-02-20T05:16:23.022892095Z exit 42
2026-02-20T05:16:23.022898045Z """)
2026-02-20T05:16:23.022903925Z fail_script.chmod(0o755)
2026-02-20T05:16:23.022908468Z
2026-02-20T05:16:23.022914058Z # Run container that should fail
2026-02-20T05:16:23.022920091Z result = subprocess.run(
2026-02-20T05:16:23.022925041Z [
2026-02-20T05:16:23.023195514Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.023204334Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.023210621Z "--job-command", "sh /job/fail.sh",
2026-02-20T05:16:23.023216484Z "--code-dir", "/job",
2026-02-20T05:16:23.023222888Z "--job-dir", "/job",
2026-02-20T05:16:23.023581972Z ],
2026-02-20T05:16:23.023589632Z capture_output=True,
2026-02-20T05:16:23.023595625Z text=True,
2026-02-20T05:16:23.023902715Z cwd=work_dir,
2026-02-20T05:16:23.023912028Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.023918075Z )
2026-02-20T05:16:23.025465787Z
2026-02-20T05:16:23.025477617Z print("FAIL TEST STDOUT:", result.stdout)
2026-02-20T05:16:23.025483117Z print("FAIL TEST STDERR:", result.stderr)
2026-02-20T05:16:23.025489321Z print("FAIL TEST RETURN CODE:", result.returncode)
2026-02-20T05:16:23.026162284Z
2026-02-20T05:16:23.026173888Z # Should propagate the exit code
2026-02-20T05:16:23.026182298Z> assert result.returncode == 42, f"Expected exit code 42, got {result.returncode}"
2026-02-20T05:16:23.026188004ZE AssertionError: Expected exit code 42, got 1
2026-02-20T05:16:23.026192954ZE assert 1 == 42
2026-02-20T05:16:23.026204921ZE + 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-20T05:16:23.026208784Z
2026-02-20T05:16:23.026215828Ztests/test_docker_execution.py:212: AssertionError
2026-02-20T05:16:23.026223341Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.026228608ZFAIL TEST STDOUT:
2026-02-20T05:16:23.026234884ZFAIL TEST STDERR: 2026-02-20T05:12:30.508373+00:00 Configuration validation failed:
2026-02-20T05:16:23.026248432Z2026-02-20T05:12:30.508440+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.026255619Z • system: docker is not available in PATH
2026-02-20T05:16:23.026261422Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.026264829Z
2026-02-20T05:16:23.026270055Z⚠️ Configuration warnings:
2026-02-20T05:16:23.026275375Z • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.026650662Z 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.026656615Z
2026-02-20T05:16:23.026659485Z
2026-02-20T05:16:23.026665282ZFAIL TEST RETURN CODE: 1
2026-02-20T05:16:23.026672485Z____________________________ test_docker_available _____________________________
2026-02-20T05:16:23.026676122Z
2026-02-20T05:16:23.026682135Z def test_docker_available():
2026-02-20T05:16:23.026687945Z """Test that Docker is available and working."""
2026-02-20T05:16:23.026693098Z> result = subprocess.run(
2026-02-20T05:16:23.02773134Z ["docker", "version", "--format", "{{.Server.Version}}"],
2026-02-20T05:16:23.02773785Z capture_output=True,
2026-02-20T05:16:23.027763923Z text=True
2026-02-20T05:16:23.027768053Z )
2026-02-20T05:16:23.02777038Z
2026-02-20T05:16:23.027776893Ztests/test_docker_execution.py:242:
2026-02-20T05:16:23.027780397Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.027784577Z/usr/local/lib/python3.13/subprocess.py:554: in run
2026-02-20T05:16:23.027788133Z with Popen(*popenargs, **kwargs) as process:
2026-02-20T05:16:23.027792667Z/usr/local/lib/python3.13/subprocess.py:1039: in __init__
2026-02-20T05:16:23.028668469Z self._execute_child(args, executable, preexec_fn, close_fds,
2026-02-20T05:16:23.028680599Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.028685009Z
2026-02-20T05:16:23.028692536Zself = <Popen: returncode: 255 args: ['docker', 'version', '--format', '{{.Server.V...>
2026-02-20T05:16:23.028698659Zargs = ['docker', 'version', '--format', '{{.Server.Version}}']
2026-02-20T05:16:23.028705643Zexecutable = b'docker', preexec_fn = None, close_fds = True, pass_fds = ()
2026-02-20T05:16:23.028712926Zcwd = None, env = None, startupinfo = None, creationflags = 0, shell = False
2026-02-20T05:16:23.028718149Zp2cread = -1, p2cwrite = -1, c2pread = 11, c2pwrite = 12, errread = 13
2026-02-20T05:16:23.028722976Zerrwrite = 14, restore_signals = True, gid = None, gids = None, uid = None
2026-02-20T05:16:23.028727363Zumask = -1, start_new_session = False, process_group = -1
2026-02-20T05:16:23.028730333Z
2026-02-20T05:16:23.028737416Z def _execute_child(self, args, executable, preexec_fn, close_fds,
2026-02-20T05:16:23.028744127Z pass_fds, cwd, env,
2026-02-20T05:16:23.02875011Z startupinfo, creationflags, shell,
2026-02-20T05:16:23.028857488Z p2cread, p2cwrite,
2026-02-20T05:16:23.028862708Z c2pread, c2pwrite,
2026-02-20T05:16:23.028866225Z errread, errwrite,
2026-02-20T05:16:23.028870135Z restore_signals,
2026-02-20T05:16:23.028873698Z gid, gids, uid, umask,
2026-02-20T05:16:23.028878368Z start_new_session, process_group):
2026-02-20T05:16:23.028881981Z """Execute program (POSIX version)"""
2026-02-20T05:16:23.028884935Z
2026-02-20T05:16:23.028888958Z if isinstance(args, (str, bytes)):
2026-02-20T05:16:23.02954149Z args = [args]
2026-02-20T05:16:23.029551462Z elif isinstance(args, os.PathLike):
2026-02-20T05:16:23.029557322Z if shell:
2026-02-20T05:16:23.029564652Z raise TypeError('path-like args is not allowed when '
2026-02-20T05:16:23.029573478Z 'shell is [REDACTED]')
2026-02-20T05:16:23.029578815Z args = [args]
2026-02-20T05:16:23.029584232Z else:
2026-02-20T05:16:23.029590002Z args = list(args)
2026-02-20T05:16:23.029595255Z
2026-02-20T05:16:23.029601442Z if shell:
2026-02-20T05:16:23.029608312Z # On Android the default shell is at '/system/bin/sh'.
2026-02-20T05:16:23.029886844Z unix_shell = ('/system/bin/sh' if
2026-02-20T05:16:23.029903578Z hasattr(sys, 'getandroidapilevel') else '/bin/sh')
2026-02-20T05:16:23.029909671Z args = [unix_shell, "-c"] + args
2026-02-20T05:16:23.029915075Z if executable:
2026-02-20T05:16:23.030292078Z args[0] = executable
2026-02-20T05:16:23.030307878Z
2026-02-20T05:16:23.030314141Z if executable is None:
2026-02-20T05:16:23.030319641Z executable = args[0]
2026-02-20T05:16:23.030323835Z
2026-02-20T05:16:23.030331161Z sys.audit("subprocess.Popen", executable, args, cwd, env)
2026-02-20T05:16:23.030344451Z
2026-02-20T05:16:23.030349795Z if (_USE_POSIX_SPAWN
2026-02-20T05:16:23.030911434Z and os.path.dirname(executable)
2026-02-20T05:16:23.030921257Z and preexec_fn is None
2026-02-20T05:16:23.030928755Z and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
2026-02-20T05:16:23.030939681Z and not pass_fds
2026-02-20T05:16:23.030945598Z and cwd is None
2026-02-20T05:16:23.030951885Z and (p2cread == -1 or p2cread > 2)
2026-02-20T05:16:23.030957971Z and (c2pwrite == -1 or c2pwrite > 2)
2026-02-20T05:16:23.031800516Z and (errwrite == -1 or errwrite > 2)
2026-02-20T05:16:23.031809496Z and not start_new_session
2026-02-20T05:16:23.031815753Z and process_group == -1
2026-02-20T05:16:23.031821229Z and gid is None
2026-02-20T05:16:23.031826303Z and gids is None
2026-02-20T05:16:23.031831176Z and uid is None
2026-02-20T05:16:23.033336179Z and umask < 0):
2026-02-20T05:16:23.034463373Z self._posix_spawn(args, executable, env, restore_signals, close_fds,
2026-02-20T05:16:23.0344725Z p2cread, p2cwrite,
2026-02-20T05:16:23.034478346Z c2pread, c2pwrite,
2026-02-20T05:16:23.034485333Z errread, errwrite)
2026-02-20T05:16:23.034491586Z return
2026-02-20T05:16:23.03449609Z
2026-02-20T05:16:23.0345019Z orig_executable = executable
2026-02-20T05:16:23.03451411Z
2026-02-20T05:16:23.034522366Z # For transferring possible exec failure from child to parent.
2026-02-20T05:16:23.034529493Z # Data format: "exception name:hex errno:description"
2026-02-20T05:16:23.034536013Z # Pickle is not used; it is complex and involves memory allocation.
2026-02-20T05:16:23.034543266Z errpipe_read, errpipe_write = os.pipe()
2026-02-20T05:16:23.034549976Z # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
2026-02-20T05:16:23.034555426Z low_fds_to_close = []
2026-02-20T05:16:23.03456038Z while errpipe_write < 3:
2026-02-20T05:16:23.034566546Z low_fds_to_close.append(errpipe_write)
2026-02-20T05:16:23.03457184Z errpipe_write = os.dup(errpipe_write)
2026-02-20T05:16:23.034578031Z for low_fd in low_fds_to_close:
2026-02-20T05:16:23.034588224Z os.close(low_fd)
2026-02-20T05:16:23.034595847Z try:
2026-02-20T05:16:23.034601081Z try:
2026-02-20T05:16:23.034608804Z # We must avoid complex work that could involve
2026-02-20T05:16:23.034614814Z # malloc or free in the child process to avoid
2026-02-20T05:16:23.034621791Z # potential deadlocks, thus we do all this here.
2026-02-20T05:16:23.034627774Z # and pass it to fork_exec()
2026-02-20T05:16:23.034631514Z
2026-02-20T05:16:23.034636978Z if env is not None:
2026-02-20T05:16:23.034642748Z env_list = []
2026-02-20T05:16:23.034649698Z for k, v in env.items():
2026-02-20T05:16:23.034655254Z k = os.fsencode(k)
2026-02-20T05:16:23.034661924Z if b'=' in k:
2026-02-20T05:16:23.034669351Z raise ValueError("illegal environment variable name")
2026-02-20T05:16:23.035449139Z env_list.append(k + b'=' + os.fsencode(v))
2026-02-20T05:16:23.035460419Z else:
2026-02-20T05:16:23.035468189Z env_list = None # Use execv instead of execve.
2026-02-20T05:16:23.035475832Z executable = os.fsencode(executable)
2026-02-20T05:16:23.035481913Z if os.path.dirname(executable):
2026-02-20T05:16:23.036606757Z executable_list = (executable,)
2026-02-20T05:16:23.036613594Z else:
2026-02-20T05:16:23.036619255Z # This matches the behavior of os._execvpe().
2026-02-20T05:16:23.036623472Z executable_list = tuple(
2026-02-20T05:16:23.036627782Z os.path.join(os.fsencode(dir), executable)
2026-02-20T05:16:23.03686155Z for dir in os.get_exec_path(env))
2026-02-20T05:16:23.036871177Z fds_to_keep = set(pass_fds)
2026-02-20T05:16:23.036877317Z fds_to_keep.add(errpipe_write)
2026-02-20T05:16:23.036884594Z self.pid = _fork_exec(
2026-02-20T05:16:23.036890344Z args, executable_list,
2026-02-20T05:16:23.036898424Z close_fds, tuple(sorted(map(int, fds_to_keep))),
2026-02-20T05:16:23.03690395Z cwd, env_list,
2026-02-20T05:16:23.03691046Z p2cread, p2cwrite, c2pread, c2pwrite,
2026-02-20T05:16:23.036916077Z errread, errwrite,
2026-02-20T05:16:23.036921954Z errpipe_read, errpipe_write,
2026-02-20T05:16:23.036928214Z restore_signals, start_new_session,
2026-02-20T05:16:23.03693371Z process_group, gid, gids, uid, umask,
2026-02-20T05:16:23.036939244Z preexec_fn, _USE_VFORK)
2026-02-20T05:16:23.036944527Z self._child_created = True
2026-02-20T05:16:23.03695603Z finally:
2026-02-20T05:16:23.036962401Z # be sure the FD is closed no matter what
2026-02-20T05:16:23.036968015Z os.close(errpipe_write)
2026-02-20T05:16:23.036972961Z
2026-02-20T05:16:23.037403325Z self._close_pipe_fds(p2cread, p2cwrite,
2026-02-20T05:16:23.037415025Z c2pread, c2pwrite,
2026-02-20T05:16:23.037418685Z errread, errwrite)
2026-02-20T05:16:23.037421575Z
2026-02-20T05:16:23.0374267Z # Wait for exec to fail or succeed; possibly raising an
2026-02-20T05:16:23.037430706Z # exception (limited in size)
2026-02-20T05:16:23.03743678Z errpipe_data = bytearray()
2026-02-20T05:16:23.037440456Z while True:
2026-02-20T05:16:23.03744472Z part = os.read(errpipe_read, 50000)
2026-02-20T05:16:23.03744865Z errpipe_data += part
2026-02-20T05:16:23.037757258Z if not part or len(errpipe_data) > 50000:
2026-02-20T05:16:23.037762145Z break
2026-02-20T05:16:23.037765628Z finally:
2026-02-20T05:16:23.037769656Z # be sure the FD is closed no matter what
2026-02-20T05:16:23.037773623Z os.close(errpipe_read)
2026-02-20T05:16:23.038072135Z
2026-02-20T05:16:23.038078402Z if errpipe_data:
2026-02-20T05:16:23.038082368Z try:
2026-02-20T05:16:23.038642604Z pid, sts = os.waitpid(self.pid, 0)
2026-02-20T05:16:23.038647761Z if pid == self.pid:
2026-02-20T05:16:23.038651454Z self._handle_exitstatus(sts)
2026-02-20T05:16:23.038655124Z else:
2026-02-20T05:16:23.038658714Z self.returncode = sys.maxsize
2026-02-20T05:16:23.038662031Z except ChildProcessError:
2026-02-20T05:16:23.038665777Z pass
2026-02-20T05:16:23.039131962Z
2026-02-20T05:16:23.039139189Z try:
2026-02-20T05:16:23.039143696Z exception_name, hex_errno, err_msg = (
2026-02-20T05:16:23.039633741Z errpipe_data.split(b':', 2))
2026-02-20T05:16:23.039639361Z # The encoding here should match the encoding
2026-02-20T05:16:23.039643147Z # written in by the subprocess implementations
2026-02-20T05:16:23.039646981Z # like _posixsubprocess
2026-02-20T05:16:23.039650617Z err_msg = err_msg.decode()
2026-02-20T05:16:23.039654461Z except ValueError:
2026-02-20T05:16:23.039658274Z exception_name = b'SubprocessError'
2026-02-20T05:16:23.040025451Z hex_errno = b'0'
2026-02-20T05:16:23.040031511Z err_msg = 'Bad exception data from child: {!r}'.format(
2026-02-20T05:16:23.040035225Z bytes(errpipe_data))
2026-02-20T05:16:23.040039275Z child_exception_type = getattr(
2026-02-20T05:16:23.040043138Z builtins, exception_name.decode('ascii'),
2026-02-20T05:16:23.040047255Z SubprocessError)
2026-02-20T05:16:23.040051355Z if issubclass(child_exception_type, OSError) and hex_errno:
2026-02-20T05:16:23.040295021Z errno_num = int(hex_errno, 16)
2026-02-20T05:16:23.040299991Z if err_msg == "noexec:chdir":
2026-02-20T05:16:23.040303594Z err_msg = ""
2026-02-20T05:16:23.04060086Z # The error must be from chdir(cwd).
2026-02-20T05:16:23.040605483Z err_filename = cwd
2026-02-20T05:16:23.040609126Z elif err_msg == "noexec":
2026-02-20T05:16:23.040871057Z err_msg = ""
2026-02-20T05:16:23.041341434Z err_filename = None
2026-02-20T05:16:23.041346144Z else:
2026-02-20T05:16:23.041350128Z err_filename = orig_executable
2026-02-20T05:16:23.041353628Z if errno_num != 0:
2026-02-20T05:16:23.041357591Z err_msg = os.strerror(errno_num)
2026-02-20T05:16:23.041361164Z if err_filename is not None:
2026-02-20T05:16:23.041366698Z> raise child_exception_type(errno_num, err_msg, err_filename)
2026-02-20T05:16:23.041627217ZE FileNotFoundError: [Errno 2] No such file or directory: 'docker'
2026-02-20T05:16:23.041632624Z
2026-02-20T05:16:23.041640367Z/usr/local/lib/python3.13/subprocess.py:1991: FileNotFoundError
2026-02-20T05:16:23.042100491Z____________________ test_container_with_working_directory _____________________
2026-02-20T05:16:23.042107391Z
2026-02-20T05:16:23.042139662Z def test_container_with_working_directory():
2026-02-20T05:16:23.042148736Z """Test that working directory is set correctly in container."""
2026-02-20T05:16:23.042154992Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.042572404Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.042580334Z
2026-02-20T05:16:23.042585707Z # Create job directory with subdirectory
2026-02-20T05:16:23.042589064Z job_dir = work_dir / "job"
2026-02-20T05:16:23.042592664Z job_dir.mkdir()
2026-02-20T05:16:23.042636584Z sub_dir = job_dir / "subdir"
2026-02-20T05:16:23.04353948Z sub_dir.mkdir()
2026-02-20T05:16:23.043543816Z
2026-02-20T05:16:23.043548553Z # Create test file in subdirectory
2026-02-20T05:16:23.043552426Z test_file = sub_dir / "data.txt"
2026-02-20T05:16:23.043556183Z test_file.write_text("test data")
2026-02-20T05:16:23.04355944Z
2026-02-20T05:16:23.043563786Z # Create script that checks working directory
2026-02-20T05:16:23.043567576Z test_script = job_dir / "pwd_test.sh"
2026-02-20T05:16:23.044074415Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.044083855Z echo "Current directory: $(pwd)"
2026-02-20T05:16:23.044089715Z echo "Directory contents:"
2026-02-20T05:16:23.044095638Z ls -la
2026-02-20T05:16:23.044104995Z echo "Subdir exists:"
2026-02-20T05:16:23.044494786Z ls -d subdir
2026-02-20T05:16:23.044502076Z exit 0
2026-02-20T05:16:23.044507693Z """)
2026-02-20T05:16:23.044513986Z test_script.chmod(0o755)
2026-02-20T05:16:23.04451892Z
2026-02-20T05:16:23.044525806Z # Run with working directory set to /job
2026-02-20T05:16:23.044536333Z result = subprocess.run(
2026-02-20T05:16:23.044541423Z [
2026-02-20T05:16:23.044548343Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.044554613Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.044996037Z "--job-command", "sh pwd_test.sh", # Note: no /job/ prefix since we're in that dir
2026-02-20T05:16:23.045005127Z "--code-dir", "/job",
2026-02-20T05:16:23.04501123Z "--job-dir", "/job",
2026-02-20T05:16:23.045017184Z ],
2026-02-20T05:16:23.045356631Z capture_output=True,
2026-02-20T05:16:23.045364901Z text=True,
2026-02-20T05:16:23.045371521Z cwd=work_dir,
2026-02-20T05:16:23.045380858Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.046219287Z )
2026-02-20T05:16:23.046225453Z
2026-02-20T05:16:23.04623212Z print("WORKING DIR TEST:", result.stdout)
2026-02-20T05:16:23.046238353Z print("STDERR:", result.stderr)
2026-02-20T05:16:23.046242133Z
2026-02-20T05:16:23.046452625Z> assert result.returncode == 0, f"Container execution failed: {result.stderr}"
2026-02-20T05:16:23.046467825ZE AssertionError: Container execution failed: 2026-02-20T05:12:31.249533+00:00 Configuration validation failed:
2026-02-20T05:16:23.046474719ZE 2026-02-20T05:12:31.249654+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.046480825ZE • system: docker is not available in PATH
2026-02-20T05:16:23.046487805ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.046492989ZE
2026-02-20T05:16:23.046498749ZE ⚠️ Configuration warnings:
2026-02-20T05:16:23.046505635ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.046513352ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.046519462ZE
2026-02-20T05:16:23.046524352ZE
2026-02-20T05:16:23.047056568ZE assert 1 == 0
2026-02-20T05:16:23.047068131ZE + 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-20T05:16:23.047070888Z
2026-02-20T05:16:23.047076115Ztests/test_docker_execution.py:296: AssertionError
2026-02-20T05:16:23.047089928Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.047093995ZWORKING DIR TEST:
2026-02-20T05:16:23.047577429ZSTDERR: 2026-02-20T05:12:31.249533+00:00 Configuration validation failed:
2026-02-20T05:16:23.047584792Z2026-02-20T05:12:31.249654+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.047589689Z • system: docker is not available in PATH
2026-02-20T05:16:23.047594162Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.047601553Z
2026-02-20T05:16:23.047605406Z⚠️ Configuration warnings:
2026-02-20T05:16:23.04760974Z • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.047615247Z 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.047736638Z
2026-02-20T05:16:23.047739775Z
2026-02-20T05:16:23.047746275Z______________________________ test_dry_run_mode _______________________________
2026-02-20T05:16:23.048047434Z
2026-02-20T05:16:23.048060061Z def test_dry_run_mode():
2026-02-20T05:16:23.048074185Z """Test dry-run mode doesn't actually execute container."""
2026-02-20T05:16:23.048210579Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.048624333Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.048628617Z
2026-02-20T05:16:23.048633877Z # Create job directory
2026-02-20T05:16:23.048637611Z job_dir = work_dir / "job"
2026-02-20T05:16:23.048641344Z job_dir.mkdir()
2026-02-20T05:16:23.049351907Z
2026-02-20T05:16:23.049365824Z # Create a script that should NOT run
2026-02-20T05:16:23.049373237Z test_script = job_dir / "should_not_run.sh"
2026-02-20T05:16:23.049380617Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.04938596Z echo "ERROR: This should not execute in dry-run mode!"
2026-02-20T05:16:23.049391467Z exit 1
2026-02-20T05:16:23.04939682Z """)
2026-02-20T05:16:23.049407557Z test_script.chmod(0o755)
2026-02-20T05:16:23.0494116Z
2026-02-20T05:16:23.049424035Z # Run in dry-run mode
2026-02-20T05:16:23.049430822Z result = subprocess.run(
2026-02-20T05:16:23.049445732Z [
2026-02-20T05:16:23.049451945Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.049650254Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.049659444Z "--job-command", "sh /job/should_not_run.sh",
2026-02-20T05:16:23.050213666Z "--code-dir", "/job",
2026-02-20T05:16:23.050219679Z "--job-dir", "/job",
2026-02-20T05:16:23.050223686Z "--dry-run",
2026-02-20T05:16:23.050228193Z ],
2026-02-20T05:16:23.050231966Z capture_output=True,
2026-02-20T05:16:23.050235673Z text=True,
2026-02-20T05:16:23.050238969Z cwd=work_dir,
2026-02-20T05:16:23.050673401Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.050678947Z )
2026-02-20T05:16:23.050681807Z
2026-02-20T05:16:23.050686107Z print("DRY RUN OUTPUT:", result.stdout)
2026-02-20T05:16:23.050689891Z print("DRY RUN STDERR:", result.stderr)
2026-02-20T05:16:23.050692197Z
2026-02-20T05:16:23.050697207Z> assert result.returncode == 0, f"Dry-run failed: {result.stderr}"
2026-02-20T05:16:23.05107527ZE AssertionError: Dry-run failed: 2026-02-20T05:12:32.042184+00:00 Configuration validation failed:
2026-02-20T05:16:23.051080844ZE 2026-02-20T05:12:32.042332+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.051090434ZE • system: docker is not available in PATH
2026-02-20T05:16:23.051095171ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.051099021ZE
2026-02-20T05:16:23.051430068ZE ⚠️ Configuration warnings:
2026-02-20T05:16:23.051437614ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.051442868ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.051446748ZE
2026-02-20T05:16:23.051961323ZE
2026-02-20T05:16:23.051966313ZE assert 1 == 0
2026-02-20T05:16:23.051975503ZE + 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-20T05:16:23.05197831Z
2026-02-20T05:16:23.051983323Ztests/test_docker_execution.py:338: AssertionError
2026-02-20T05:16:23.051988623Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.052528709ZDRY RUN OUTPUT:
2026-02-20T05:16:23.052535089ZDRY RUN STDERR: 2026-02-20T05:12:32.042184+00:00 Configuration validation failed:
2026-02-20T05:16:23.052540292Z2026-02-20T05:12:32.042332+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.052545369Z • system: docker is not available in PATH
2026-02-20T05:16:23.052549369Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.052830962Z
2026-02-20T05:16:23.052885472Z⚠️ Configuration warnings:
2026-02-20T05:16:23.052893306Z • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.052905619Z 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.052909796Z
2026-02-20T05:16:23.052912092Z
2026-02-20T05:16:23.052917906Z_____________________________ test_node_container ______________________________
2026-02-20T05:16:23.053433451Z
2026-02-20T05:16:23.053444281Z def test_node_container():
2026-02-20T05:16:23.053450594Z """Test Node.js container execution."""
2026-02-20T05:16:23.053457181Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.05368357Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.053689593Z
2026-02-20T05:16:23.053695517Z # Create job directory
2026-02-20T05:16:23.05370142Z job_dir = work_dir / "job"
2026-02-20T05:16:23.05370666Z job_dir.mkdir()
2026-02-20T05:16:23.053710817Z
2026-02-20T05:16:23.0537168Z # Create Node.js script
2026-02-20T05:16:23.05372375Z js_script = job_dir / "test.js"
2026-02-20T05:16:23.054042464Z js_script.write_text("""
2026-02-20T05:16:23.054394603Z console.log('Node version:', process.version);
2026-02-20T05:16:23.054406871Z console.log('Platform:', process.platform);
2026-02-20T05:16:23.054411254Z console.log('Working dir:', process.cwd());
2026-02-20T05:16:23.054416558Z process.exit(0);
2026-02-20T05:16:23.054421784Z """)
2026-02-20T05:16:23.054432161Z
2026-02-20T05:16:23.054438014Z # Run Node container
2026-02-20T05:16:23.054446254Z result = subprocess.run(
2026-02-20T05:16:23.054771065Z [
2026-02-20T05:16:23.054781998Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.055038517Z "--runner-image", "node:18-alpine",
2026-02-20T05:16:23.055054284Z "--job-command", "node /job/test.js",
2026-02-20T05:16:23.05506138Z "--code-dir", "/job",
2026-02-20T05:16:23.055066647Z "--job-dir", "/job",
2026-02-20T05:16:23.055742757Z ],
2026-02-20T05:16:23.055751241Z capture_output=True,
2026-02-20T05:16:23.055757257Z text=True,
2026-02-20T05:16:23.055762514Z cwd=work_dir,
2026-02-20T05:16:23.055776578Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.056019557Z )
2026-02-20T05:16:23.05602537Z
2026-02-20T05:16:23.05603253Z print("NODE TEST OUTPUT:", result.stdout)
2026-02-20T05:16:23.056037547Z print("NODE TEST STDERR:", result.stderr)
2026-02-20T05:16:23.056042247Z
2026-02-20T05:16:23.056049334Z> assert result.returncode == 0, f"Node container failed: {result.stderr}"
2026-02-20T05:16:23.056496762ZE AssertionError: Node container failed: 2026-02-20T05:12:32.569718+00:00 Configuration validation failed:
2026-02-20T05:16:23.056505509ZE 2026-02-20T05:12:32.569843+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.056512502ZE • system: docker is not available in PATH
2026-02-20T05:16:23.056518765ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.056523879ZE
2026-02-20T05:16:23.056528865ZE
2026-02-20T05:16:23.056973979ZE assert 1 == 0
2026-02-20T05:16:23.056987503ZE + 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-20T05:16:23.056991183Z
2026-02-20T05:16:23.056998039Ztests/test_docker_execution.py:382: AssertionError
2026-02-20T05:16:23.05709664Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.057103874ZNODE TEST OUTPUT:
2026-02-20T05:16:23.05711091ZNODE TEST STDERR: 2026-02-20T05:12:32.569718+00:00 Configuration validation failed:
2026-02-20T05:16:23.057629196Z2026-02-20T05:12:32.569843+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.057636706Z • system: docker is not available in PATH
2026-02-20T05:16:23.0576416Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.057644306Z
2026-02-20T05:16:23.057883658Z
2026-02-20T05:16:23.057893218Z____________________ test_container_with_multiple_env_vars _____________________
2026-02-20T05:16:23.057895368Z
2026-02-20T05:16:23.057906405Z def test_container_with_multiple_env_vars():
2026-02-20T05:16:23.058418264Z """Test passing multiple environment variables via CLI."""
2026-02-20T05:16:23.058425441Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.058429888Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.058432548Z
2026-02-20T05:16:23.058677403Z # Create job directory
2026-02-20T05:16:23.0586822Z job_dir = work_dir / "job"
2026-02-20T05:16:23.058685626Z job_dir.mkdir()
2026-02-20T05:16:23.05868803Z
2026-02-20T05:16:23.059228289Z # Create test script
2026-02-20T05:16:23.059234676Z test_script = job_dir / "multi_env.sh"
2026-02-20T05:16:23.059238709Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.059248282Z echo "VAR1=$VAR1"
2026-02-20T05:16:23.060190965Z echo "VAR2=$VAR2"
2026-02-20T05:16:23.060199511Z echo "VAR3=$VAR3"
2026-02-20T05:16:23.060206975Z if [ "$VAR1" = "value1" ] && [ "$VAR2" = "value2" ] && [ "$VAR3" = "value3" ]; then
2026-02-20T05:16:23.060214245Z echo "All environment variables set correctly!"
2026-02-20T05:16:23.060219901Z exit 0
2026-02-20T05:16:23.060224685Z else
2026-02-20T05:16:23.060230098Z echo "Environment variables not set correctly"
2026-02-20T05:16:23.060235271Z exit 1
2026-02-20T05:16:23.060240652Z fi
2026-02-20T05:16:23.060252802Z """)
2026-02-20T05:16:23.060259316Z test_script.chmod(0o755)
2026-02-20T05:16:23.060263559Z
2026-02-20T05:16:23.060274259Z # Run with multiple env vars in a single --job-env (newline separated)
2026-02-20T05:16:23.061828641Z result = subprocess.run(
2026-02-20T05:16:23.061836708Z [
2026-02-20T05:16:23.061843728Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.061850958Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.061856902Z "--job-command", "sh /job/multi_env.sh",
2026-02-20T05:16:23.061862818Z "--code-dir", "/job",
2026-02-20T05:16:23.061869322Z "--job-dir", "/job",
2026-02-20T05:16:23.061876178Z "--job-env", "VAR1=value1\nVAR2=value2\nVAR3=value3",
2026-02-20T05:16:23.061881238Z ],
2026-02-20T05:16:23.061886242Z capture_output=True,
2026-02-20T05:16:23.061891428Z text=True,
2026-02-20T05:16:23.061896668Z cwd=work_dir,
2026-02-20T05:16:23.061903695Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.061910308Z )
2026-02-20T05:16:23.061914185Z
2026-02-20T05:16:23.061922015Z> assert result.returncode == 0, f"Multi-env test failed: {result.stderr}"
2026-02-20T05:16:23.062145797ZE AssertionError: Multi-env test failed: 2026-02-20T05:12:32.960219+00:00 Configuration validation failed:
2026-02-20T05:16:23.062152764ZE 2026-02-20T05:12:32.960292+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.062157314ZE • system: docker is not available in PATH
2026-02-20T05:16:23.062659863ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.06266648ZE
2026-02-20T05:16:23.06267099ZE ⚠️ Configuration warnings:
2026-02-20T05:16:23.062675623ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.062680183ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.06268348ZE
2026-02-20T05:16:23.062917308ZE
2026-02-20T05:16:23.062922688ZE assert 1 == 0
2026-02-20T05:16:23.062931885ZE + 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-20T05:16:23.062934188Z
2026-02-20T05:16:23.062939145Ztests/test_docker_execution.py:429: AssertionError
2026-02-20T05:16:23.063521461Z________________________ test_selective_secret_masking _________________________
2026-02-20T05:16:23.063525844Z
2026-02-20T05:16:23.063530009Z def test_selective_secret_masking():
2026-02-20T05:16:23.063535025Z """Test selective masking of secrets using --secret-values-list."""
2026-02-20T05:16:23.063539289Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.063542655Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.063545532Z
2026-02-20T05:16:23.063820671Z # Create job directory
2026-02-20T05:16:23.063825524Z job_dir = work_dir / "job"
2026-02-20T05:16:23.063828904Z job_dir.mkdir()
2026-02-20T05:16:23.063831227Z
2026-02-20T05:16:23.063843407Z # Create test script that prints environment variables
2026-02-20T05:16:23.063847831Z test_script = job_dir / "selective_test.sh"
2026-02-20T05:16:23.064029809Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.064034419Z echo "API_KEY=$API_KEY"
2026-02-20T05:16:23.064038356Z echo "PUBLIC_VALUE=$PUBLIC_VALUE"
2026-02-20T05:16:23.066430457Z echo "SECRET_TOKEN=$SECRET_TOKEN"
2026-02-20T05:16:23.06643818Z echo "CONFIG_PATH=$CONFIG_PATH"
2026-02-20T05:16:23.066442273Z exit 0
2026-02-20T05:16:23.06644588Z """)
2026-02-20T05:16:23.06645024Z test_script.chmod(0o755)
2026-02-20T05:16:23.066453183Z
2026-02-20T05:16:23.066463487Z # Run with environment vars and explicitly mark only some as secrets
2026-02-20T05:16:23.066467033Z result = subprocess.run(
2026-02-20T05:16:23.06647014Z [
2026-02-20T05:16:23.06647492Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.066479427Z "--runner-image", "alpine:latest",
2026-02-20T05:16:23.066484627Z "--job-command", "sh /job/selective_test.sh",
2026-02-20T05:16:23.066488637Z "--code-dir", "/job",
2026-02-20T05:16:23.06649257Z "--job-dir", "/job",
2026-02-20T05:16:23.066498283Z "--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-20T05:16:23.066504277Z "--secret-values-list", "my-secret-api-key-123,super-secret-token", # Only mask these specific values
2026-02-20T05:16:23.066507541Z ],
2026-02-20T05:16:23.066511324Z capture_output=True,
2026-02-20T05:16:23.066514648Z text=True,
2026-02-20T05:16:23.066518168Z cwd=work_dir,
2026-02-20T05:16:23.066522658Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.066558048Z )
2026-02-20T05:16:23.066561144Z
2026-02-20T05:16:23.066566361Z> assert result.returncode == 0, f"Selective masking test failed: {result.stderr}"
2026-02-20T05:16:23.066571538ZE AssertionError: Selective masking test failed: 2026-02-20T05:12:33.715702+00:00 Configuration validation failed:
2026-02-20T05:16:23.066575444ZE 2026-02-20T05:12:33.715840+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.066579734ZE • system: docker is not available in PATH
2026-02-20T05:16:23.066584341ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.066588024ZE
2026-02-20T05:16:23.066591838ZE ⚠️ Configuration warnings:
2026-02-20T05:16:23.066596224ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-20T05:16:23.066823637ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-20T05:16:23.066833437ZE
2026-02-20T05:16:23.066846297ZE
2026-02-20T05:16:23.066852532ZE assert 1 == 0
2026-02-20T05:16:23.067334209ZE + 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-20T05:16:23.067339792Z
2026-02-20T05:16:23.067349249Ztests/test_docker_execution.py:474: AssertionError
2026-02-20T05:16:23.067356036Z________________________ test_value_printed_then_masked ________________________
2026-02-20T05:16:23.067359496Z
2026-02-20T05:16:23.067364829Z def test_value_printed_then_masked():
2026-02-20T05:16:23.067562671Z """Test that dynamic registration masks values in subsequent output.
2026-02-20T05:16:23.067568668Z
2026-02-20T05:16:23.067578385Z Due to the nature of streaming output and socket communication, we cannot
2026-02-20T05:16:23.067588348Z guarantee that output printed immediately before registration will be unmasked.
2026-02-20T05:16:23.068582762Z However, we CAN demonstrate that:
2026-02-20T05:16:23.068594405Z 1. Values not in the initial secrets list are not masked initially
2026-02-20T05:16:23.068601889Z 2. After dynamic registration, those values ARE masked in new output
2026-02-20T05:16:23.068607955Z """
2026-02-20T05:16:23.068612512Z
2026-02-20T05:16:23.068625519Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.068631522Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.068637505Z
2026-02-20T05:16:23.068643652Z # Create job directory
2026-02-20T05:16:23.068648649Z job_dir = work_dir / "job"
2026-02-20T05:16:23.068653579Z job_dir.mkdir()
2026-02-20T05:16:23.068657052Z
2026-02-20T05:16:23.068663712Z # Create a script that demonstrates dynamic masking
2026-02-20T05:16:23.069082059Z test_script = job_dir / "show_masking.py"
2026-02-20T05:16:23.069088316Z test_script.write_text("""#!/usr/bin/env python3
2026-02-20T05:16:23.069092119Z import socket
2026-02-20T05:16:23.069095513Z import json
2026-02-20T05:16:23.069098563Z import struct
2026-02-20T05:16:23.069101499Z import os
2026-02-20T05:16:23.069104459Z import time
2026-02-20T05:16:23.06914194Z import sys
2026-02-20T05:16:23.06914765Z import subprocess
2026-02-20T05:16:23.069150334Z
2026-02-20T05:16:23.069933115Z # This is our sensitive value that we'll get at runtime
2026-02-20T05:16:23.069938585Z api_token = "UNIQUEVALUE-abc123xyz789-ENDUNIQUE"
2026-02-20T05:16:23.069940952Z
2026-02-20T05:16:23.069950152Z print("=" * 50)
2026-02-20T05:16:23.069953892Z print("DEMONSTRATION OF DYNAMIC SECRET MASKING")
2026-02-20T05:16:23.069956938Z print("=" * 50)
2026-02-20T05:16:23.069959898Z
2026-02-20T05:16:23.069966155Z # First, show that without registration, the value appears in subprocess output
2026-02-20T05:16:23.069969898Z print("\\n1. Running subprocess BEFORE registration:")
2026-02-20T05:16:23.070073656Z sys.stdout.flush()
2026-02-20T05:16:23.070082326Z result = subprocess.run(
2026-02-20T05:16:23.070091176Z ["sh", "-c", f"echo 'Token is: {api_token}'"],
2026-02-20T05:16:23.070097479Z capture_output=True,
2026-02-20T05:16:23.070102943Z text=True
2026-02-20T05:16:23.07015094Z )
2026-02-20T05:16:23.070158874Z print(f" Subprocess output: {result.stdout.strip()}")
2026-02-20T05:16:23.070163344Z sys.stdout.flush()
2026-02-20T05:16:23.070992895Z
2026-02-20T05:16:23.071002682Z # Now register this value as a secret
2026-02-20T05:16:23.071007495Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-20T05:16:23.071010792Z if socket_path:
2026-02-20T05:16:23.071015092Z print(f"\\n2. Registering secret via socket...")
2026-02-20T05:16:23.071018299Z sys.stdout.flush()
2026-02-20T05:16:23.071020535Z
2026-02-20T05:16:23.071024765Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-20T05:16:23.071028032Z sock.connect(socket_path)
2026-02-20T05:16:23.071032889Z msg = json.dumps({'action': 'register', 'secrets': [api_token]}).encode()
2026-02-20T05:16:23.071036455Z sock.send(struct.pack('!I', len(msg)))
2026-02-20T05:16:23.071039335Z sock.send(msg)
2026-02-20T05:16:23.071042562Z response = sock.recv(1024)
2026-02-20T05:16:23.071157584Z print(f" Registration response: {response.decode().strip()}")
2026-02-20T05:16:23.071166577Z sock.close()
2026-02-20T05:16:23.071171017Z
2026-02-20T05:16:23.071515807Z # Give it a moment to process
2026-02-20T05:16:23.071521384Z time.sleep(0.2)
2026-02-20T05:16:23.071524511Z
2026-02-20T05:16:23.071528854Z # Now show that the value IS masked in new output
2026-02-20T05:16:23.071533388Z print("\\n3. After registration, value is masked:")
2026-02-20T05:16:23.071872457Z print(f" API Token: {api_token}")
2026-02-20T05:16:23.071877837Z print(f" Authorization: Bearer {api_token}")
2026-02-20T05:16:23.071882191Z sys.stdout.flush()
2026-02-20T05:16:23.071885711Z else:
2026-02-20T05:16:23.071889211Z print("ERROR: No secrets socket available!")
2026-02-20T05:16:23.071892431Z exit(1)
2026-02-20T05:16:23.071894824Z
2026-02-20T05:16:23.071897958Z print("\\n" + "=" * 50)
2026-02-20T05:16:23.072177074Z print("TEST COMPLETE")
2026-02-20T05:16:23.0721814Z print("=" * 50)
2026-02-20T05:16:23.072185154Z """)
2026-02-20T05:16:23.072523823Z test_script.chmod(0o755)
2026-02-20T05:16:23.072527387Z
2026-02-20T05:16:23.07253419Z # Run the job with an explicit empty secrets list to prevent default masking
2026-02-20T05:16:23.072654762Z result = subprocess.run(
2026-02-20T05:16:23.072659342Z [
2026-02-20T05:16:23.072663499Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.072668259Z "--runner-image", "python:3.9-alpine",
2026-02-20T05:16:23.072674042Z "--job-command", "python3 -u /job/show_masking.py", # -u for unbuffered output
2026-02-20T05:16:23.073292048Z "--code-dir", "/job",
2026-02-20T05:16:23.073298461Z "--job-dir", "/job",
2026-02-20T05:16:23.073304831Z "--secret-values-list", "", # Empty list prevents default masking of all values
2026-02-20T05:16:23.073308738Z ],
2026-02-20T05:16:23.073314048Z capture_output=True,
2026-02-20T05:16:23.073320991Z text=True,
2026-02-20T05:16:23.073327155Z cwd=work_dir,
2026-02-20T05:16:23.073334529Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.073685923Z )
2026-02-20T05:16:23.073693013Z
2026-02-20T05:16:23.073700059Z print("\n--- OUTPUT ---")
2026-02-20T05:16:23.073705776Z print(result.stdout)
2026-02-20T05:16:23.073711046Z print("\n--- ERRORS ---")
2026-02-20T05:16:23.073716599Z print(result.stderr)
2026-02-20T05:16:23.073721219Z
2026-02-20T05:16:23.073942355Z # Verify the behavior
2026-02-20T05:16:23.073949858Z> assert result.returncode == 0, f"Script failed with code {result.returncode}"
2026-02-20T05:16:23.073953445ZE AssertionError: Script failed with code 1
2026-02-20T05:16:23.073956658ZE assert 1 == 0
2026-02-20T05:16:23.073964912ZE + 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-20T05:16:23.074152957Z
2026-02-20T05:16:23.07416216Ztests/test_dynamic_secret_masking.py:110: AssertionError
2026-02-20T05:16:23.07416725Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.07416951Z
2026-02-20T05:16:23.074434689Z--- OUTPUT ---
2026-02-20T05:16:23.074438976Z
2026-02-20T05:16:23.074441126Z
2026-02-20T05:16:23.074444509Z--- ERRORS ---
2026-02-20T05:16:23.074735123Z2026-02-20T05:12:34.488227+00:00 Configuration validation failed:
2026-02-20T05:16:23.074741436Z2026-02-20T05:12:34.488354+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.074746463Z • system: docker is not available in PATH
2026-02-20T05:16:23.07475031Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.07475336Z
2026-02-20T05:16:23.074853234Z
2026-02-20T05:16:23.074866774Z________________ test_multiple_values_masked_after_registration ________________
2026-02-20T05:16:23.0752314Z
2026-02-20T05:16:23.07524025Z def test_multiple_values_masked_after_registration():
2026-02-20T05:16:23.0752459Z """Test masking multiple values registered at different times."""
2026-02-20T05:16:23.075248767Z
2026-02-20T05:16:23.076061616Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.076067333Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.076070019Z
2026-02-20T05:16:23.076073239Z # Create job directory
2026-02-20T05:16:23.076076856Z job_dir = work_dir / "job"
2026-02-20T05:16:23.076080226Z job_dir.mkdir()
2026-02-20T05:16:23.076082423Z
2026-02-20T05:16:23.076085759Z # Create test script
2026-02-20T05:16:23.076092686Z test_script = job_dir / "progressive_masking.sh"
2026-02-20T05:16:23.076096953Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.076518838Z
2026-02-20T05:16:23.076529174Z # Function to register a secret
2026-02-20T05:16:23.076533068Z register_secret() {
2026-02-20T05:16:23.076536621Z python3 -c "
2026-02-20T05:16:23.076540204Z import socket, json, struct, os
2026-02-20T05:16:23.076544988Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-20T05:16:23.07677524Z sock.connect(os.environ['REACTORCIDE_SECRETS_SOCKET'])
2026-02-20T05:16:23.07678628Z msg = json.dumps({'action': 'register', 'secrets': ['$1']}).encode()
2026-02-20T05:16:23.076792093Z sock.send(struct.pack('!I', len(msg)))
2026-02-20T05:16:23.077393222Z sock.send(msg)
2026-02-20T05:16:23.077402446Z sock.close()
2026-02-20T05:16:23.077405789Z "
2026-02-20T05:16:23.077409286Z sleep 0.5
2026-02-20T05:16:23.077412679Z }
2026-02-20T05:16:23.077415159Z
2026-02-20T05:16:23.077418639Z # First secret
2026-02-20T05:16:23.077426819Z SECRET1="database-pass-123"
2026-02-20T05:16:23.077431387Z echo "Step 1: Database password is: $SECRET1"
2026-02-20T05:16:23.077993501Z
2026-02-20T05:16:23.078005392Z # Register first secret
2026-02-20T05:16:23.078009825Z register_secret "$SECRET1"
2026-02-20T05:16:23.078012152Z
2026-02-20T05:16:23.078016202Z echo "Step 2: Database password is: $SECRET1"
2026-02-20T05:16:23.078018442Z
2026-02-20T05:16:23.078021766Z # Second secret
2026-02-20T05:16:23.078024792Z SECRET2="api-key-456"
2026-02-20T05:16:23.07889439Z echo "Step 3: API key is: $SECRET2"
2026-02-20T05:16:23.07889995Z
2026-02-20T05:16:23.0789041Z # Register second secret
2026-02-20T05:16:23.078908107Z register_secret "$SECRET2"
2026-02-20T05:16:23.078910614Z
2026-02-20T05:16:23.07891523Z echo "Step 4: Database password is: $SECRET1"
2026-02-20T05:16:23.078918777Z echo "Step 5: API key is: $SECRET2"
2026-02-20T05:16:23.07892127Z
2026-02-20T05:16:23.079869267Z # Third secret
2026-02-20T05:16:23.079876031Z SECRET3="webhook-token-789"
2026-02-20T05:16:23.079881694Z echo "Step 6: Webhook token is: $SECRET3"
2026-02-20T05:16:23.079884731Z
2026-02-20T05:16:23.079889074Z register_secret "$SECRET3"
2026-02-20T05:16:23.079891557Z
2026-02-20T05:16:23.079895017Z echo "Step 7: All secrets:"
2026-02-20T05:16:23.079898164Z echo " Database: $SECRET1"
2026-02-20T05:16:23.079901511Z echo " API: $SECRET2"
2026-02-20T05:16:23.079904794Z echo " Webhook: $SECRET3"
2026-02-20T05:16:23.079908507Z """)
2026-02-20T05:16:23.079912727Z test_script.chmod(0o755)
2026-02-20T05:16:23.079914841Z
2026-02-20T05:16:23.079919234Z # Run the job with an explicit empty secrets list
2026-02-20T05:16:23.079922374Z result = subprocess.run(
2026-02-20T05:16:23.079925221Z [
2026-02-20T05:16:23.079929174Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.079933001Z "--runner-image", "python:3.9-alpine",
2026-02-20T05:16:23.079943797Z "--job-command", "sh /job/progressive_masking.sh",
2026-02-20T05:16:23.079947977Z "--code-dir", "/job",
2026-02-20T05:16:23.079951787Z "--job-dir", "/job",
2026-02-20T05:16:23.080087906Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-20T05:16:23.080092819Z ],
2026-02-20T05:16:23.0818271Z capture_output=True,
2026-02-20T05:16:23.08183618Z text=True,
2026-02-20T05:16:23.081841683Z cwd=work_dir,
2026-02-20T05:16:23.081849103Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.081856106Z )
2026-02-20T05:16:23.081860166Z
2026-02-20T05:16:23.081866266Z print("\n--- OUTPUT ---")
2026-02-20T05:16:23.082024708Z print(result.stdout)
2026-02-20T05:16:23.082029992Z
2026-02-20T05:16:23.082035072Z> assert result.returncode == 0
2026-02-20T05:16:23.082388485ZE AssertionError: assert 1 == 0
2026-02-20T05:16:23.082402335ZE + 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-20T05:16:23.082407429Z
2026-02-20T05:16:23.082415709Ztests/test_dynamic_secret_masking.py:209: AssertionError
2026-02-20T05:16:23.083071115Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.083077338Z
2026-02-20T05:16:23.083083302Z--- OUTPUT ---
2026-02-20T05:16:23.083169493Z
2026-02-20T05:16:23.083180306Z__________________ test_immediate_masking_in_streaming_output __________________
2026-02-20T05:16:23.08318349Z
2026-02-20T05:16:23.083190773Z def test_immediate_masking_in_streaming_output():
2026-02-20T05:16:23.083399796Z """Test that masking applies immediately to streaming output."""
2026-02-20T05:16:23.083405516Z
2026-02-20T05:16:23.083412612Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.083417062Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.083419682Z
2026-02-20T05:16:23.083423799Z # Create job directory
2026-02-20T05:16:23.083427666Z job_dir = work_dir / "job"
2026-02-20T05:16:23.08357238Z job_dir.mkdir()
2026-02-20T05:16:23.083866246Z
2026-02-20T05:16:23.08387734Z # Create a script that outputs continuously
2026-02-20T05:16:23.083883503Z test_script = job_dir / "streaming_test.py"
2026-02-20T05:16:23.083890293Z test_script.write_text("""#!/usr/bin/env python3
2026-02-20T05:16:23.083895533Z import socket
2026-02-20T05:16:23.083900866Z import json
2026-02-20T05:16:23.083914573Z import struct
2026-02-20T05:16:23.083997351Z import os
2026-02-20T05:16:23.084004811Z import time
2026-02-20T05:16:23.084437726Z import sys
2026-02-20T05:16:23.084444069Z
2026-02-20T05:16:23.084449896Z # Flush output immediately
2026-02-20T05:16:23.084455212Z sys.stdout.flush()
2026-02-20T05:16:23.084458926Z
2026-02-20T05:16:23.084766355Z secret_value = "streaming-secret-999"
2026-02-20T05:16:23.084771149Z
2026-02-20T05:16:23.084776049Z # Output the secret multiple times before registration
2026-02-20T05:16:23.084779809Z for i in range(3):
2026-02-20T05:16:23.084784822Z print(f"Before [{i}]: secret={secret_value}")
2026-02-20T05:16:23.084790352Z sys.stdout.flush()
2026-02-20T05:16:23.084874186Z time.sleep(0.1)
2026-02-20T05:16:23.084881023Z
2026-02-20T05:16:23.08522381Z # Register the secret
2026-02-20T05:16:23.08522978Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-20T05:16:23.085242697Z if socket_path:
2026-02-20T05:16:23.085246884Z print("\\nRegistering secret...")
2026-02-20T05:16:23.085249977Z sys.stdout.flush()
2026-02-20T05:16:23.085252497Z
2026-02-20T05:16:23.08561979Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-20T05:16:23.08562543Z sock.connect(socket_path)
2026-02-20T05:16:23.08563084Z msg = json.dumps({'action': 'register', 'secrets': [secret_value]}).encode()
2026-02-20T05:16:23.085634727Z sock.send(struct.pack('!I', len(msg)))
2026-02-20T05:16:23.08563825Z sock.send(msg)
2026-02-20T05:16:23.085641573Z response = sock.recv(1024)
2026-02-20T05:16:23.08564495Z sock.close()
2026-02-20T05:16:23.085647243Z
2026-02-20T05:16:23.085650543Z print("Secret registered!\\n")
2026-02-20T05:16:23.086190416Z sys.stdout.flush()
2026-02-20T05:16:23.086194529Z
2026-02-20T05:16:23.086198656Z # Wait for registration to process
2026-02-20T05:16:23.086202186Z time.sleep(0.5)
2026-02-20T05:16:23.086204456Z
2026-02-20T05:16:23.086214183Z # Output the secret multiple times after registration
2026-02-20T05:16:23.086609493Z for i in range(3):
2026-02-20T05:16:23.086618864Z print(f"After [{i}]: secret={secret_value}")
2026-02-20T05:16:23.086623664Z sys.stdout.flush()
2026-02-20T05:16:23.08662895Z time.sleep(0.1)
2026-02-20T05:16:23.08663437Z """)
2026-02-20T05:16:23.08664931Z test_script.chmod(0o755)
2026-02-20T05:16:23.08665329Z
2026-02-20T05:16:23.086666967Z # Run the job with an explicit empty secrets list
2026-02-20T05:16:23.087071668Z result = subprocess.run(
2026-02-20T05:16:23.087076025Z [
2026-02-20T05:16:23.087080168Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.087084298Z "--runner-image", "python:3.9-alpine",
2026-02-20T05:16:23.087090045Z "--job-command", "python3 /job/streaming_test.py",
2026-02-20T05:16:23.087221203Z "--code-dir", "/job",
2026-02-20T05:16:23.087228306Z "--job-dir", "/job",
2026-02-20T05:16:23.087234463Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-20T05:16:23.08757053Z ],
2026-02-20T05:16:23.087578183Z capture_output=True,
2026-02-20T05:16:23.08758384Z text=True,
2026-02-20T05:16:23.08758932Z cwd=work_dir,
2026-02-20T05:16:23.087851826Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.087857446Z )
2026-02-20T05:16:23.087864946Z
2026-02-20T05:16:23.08786915Z print("\n--- STREAMING OUTPUT ---")
2026-02-20T05:16:23.087872733Z print(result.stdout)
2026-02-20T05:16:23.087874926Z
2026-02-20T05:16:23.088380614Z> assert result.returncode == 0
2026-02-20T05:16:23.088385841ZE AssertionError: assert 1 == 0
2026-02-20T05:16:23.088393921ZE + 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-20T05:16:23.088396131Z
2026-02-20T05:16:23.088401237Ztests/test_dynamic_secret_masking.py:305: AssertionError
2026-02-20T05:16:23.088406111Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.088408057Z
2026-02-20T05:16:23.088730311Z--- STREAMING OUTPUT ---
2026-02-20T05:16:23.088734744Z
2026-02-20T05:16:23.088740114Z_______________________ test_dynamic_secret_registration _______________________
2026-02-20T05:16:23.088747281Z
2026-02-20T05:16:23.089017291Z def test_dynamic_secret_registration():
2026-02-20T05:16:23.089027837Z """Test that jobs can register secrets dynamically via socket."""
2026-02-20T05:16:23.089031087Z
2026-02-20T05:16:23.089035264Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.089038581Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.089278466Z
2026-02-20T05:16:23.089285537Z # Create job directory
2026-02-20T05:16:23.089288787Z job_dir = work_dir / "job"
2026-02-20T05:16:23.08960751Z job_dir.mkdir()
2026-02-20T05:16:23.08961108Z
2026-02-20T05:16:23.089616363Z # Create a test script that fetches and uses a secret
2026-02-20T05:16:23.08962069Z test_script = job_dir / "dynamic_secret_test.sh"
2026-02-20T05:16:23.089625207Z test_script.write_text("""#!/bin/sh
2026-02-20T05:16:23.089629547Z # Simulate fetching a secret from an external service
2026-02-20T05:16:23.08999815Z FETCHED_SECRET="super-dynamic-secret-12345"
2026-02-20T05:16:23.090001726Z
2026-02-20T05:16:23.090005756Z echo "Before registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-20T05:16:23.090008077Z
2026-02-20T05:16:23.090088011Z # Register the secret so it gets masked
2026-02-20T05:16:23.090092284Z if [ -n "$REACTORCIDE_SECRETS_SOCKET" ]; then
2026-02-20T05:16:23.090096141Z echo "Socket available at: $REACTORCIDE_SECRETS_SOCKET"
2026-02-20T05:16:23.090099448Z # Use Python to register the secret
2026-02-20T05:16:23.090751448Z python3 -c "
2026-02-20T05:16:23.090760431Z import socket, json, struct
2026-02-20T05:16:23.090767744Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-20T05:16:23.090773598Z sock.connect('$REACTORCIDE_SECRETS_SOCKET')
2026-02-20T05:16:23.090781464Z msg = json.dumps({'action': 'register', 'secrets': ['$FETCHED_SECRET']}).encode()
2026-02-20T05:16:23.093236713Z sock.send(struct.pack('!I', len(msg)))
2026-02-20T05:16:23.093244043Z sock.send(msg)
2026-02-20T05:16:23.093249223Z response = sock.recv(1024)
2026-02-20T05:16:23.093255326Z print('Registration response:', response.decode())
2026-02-20T05:16:23.093260773Z sock.close()
2026-02-20T05:16:23.093264893Z "
2026-02-20T05:16:23.093268856Z
2026-02-20T05:16:23.093275783Z # Give the server a moment to process
2026-02-20T05:16:23.093280963Z sleep 0.5
2026-02-20T05:16:23.093286946Z else
2026-02-20T05:16:23.093291269Z echo "Warning: No secrets socket available"
2026-02-20T05:16:23.09329481Z fi
2026-02-20T05:16:23.09329705Z
2026-02-20T05:16:23.09330072Z # Now use the secret again - it should be masked
2026-02-20T05:16:23.09330976Z echo "After registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-20T05:16:23.09332376Z echo "Using secret in command: curl -H 'Authorization: Bearer $FETCHED_SECRET' example.com"
2026-02-20T05:16:23.09332737Z """)
2026-02-20T05:16:23.093330934Z test_script.chmod(0o755)
2026-02-20T05:16:23.09333325Z
2026-02-20T05:16:23.093338644Z # Run the container with our test script
2026-02-20T05:16:23.093341937Z result = subprocess.run(
2026-02-20T05:16:23.093344847Z [
2026-02-20T05:16:23.09334879Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.093354847Z "--runner-image", "python:3.9-alpine", # Has Python for our registration
2026-02-20T05:16:23.09336268Z "--job-command", "sh /job/dynamic_secret_test.sh",
2026-02-20T05:16:23.09336642Z "--code-dir", "/job",
2026-02-20T05:16:23.093370167Z "--job-dir", "/job",
2026-02-20T05:16:23.093375027Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-20T05:16:23.093378177Z ],
2026-02-20T05:16:23.093381474Z capture_output=True,
2026-02-20T05:16:23.093384977Z text=True,
2026-02-20T05:16:23.0933882Z cwd=work_dir,
2026-02-20T05:16:23.09390798Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.094679721Z )
2026-02-20T05:16:23.094684411Z
2026-02-20T05:16:23.094689534Z print("STDOUT:", result.stdout)
2026-02-20T05:16:23.094693654Z print("STDERR:", result.stderr)
2026-02-20T05:16:23.094696271Z
2026-02-20T05:16:23.094702248Z> assert result.returncode == 0, f"Dynamic secret test failed with code {result.returncode}"
2026-02-20T05:16:23.094706734ZE AssertionError: Dynamic secret test failed with code 1
2026-02-20T05:16:23.094710194ZE assert 1 == 0
2026-02-20T05:16:23.094718274ZE + 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-20T05:16:23.094720414Z
2026-02-20T05:16:23.094729008Ztests/test_dynamic_secrets.py:75: AssertionError
2026-02-20T05:16:23.094733484Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.094736798ZSTDOUT:
2026-02-20T05:16:23.094741308ZSTDERR: 2026-02-20T05:12:36.167887+00:00 Configuration validation failed:
2026-02-20T05:16:23.094745918Z2026-02-20T05:12:36.168021+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.094750151Z • system: docker is not available in PATH
2026-02-20T05:16:23.094754591Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.094756771Z
2026-02-20T05:16:23.094758704Z
2026-02-20T05:16:23.094762998Z________________________ test_multiple_dynamic_secrets _________________________
2026-02-20T05:16:23.094765018Z
2026-02-20T05:16:23.094768505Z def test_multiple_dynamic_secrets():
2026-02-20T05:16:23.094772165Z """Test registering multiple secrets dynamically."""
2026-02-20T05:16:23.094774412Z
2026-02-20T05:16:23.094783882Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.094787009Z work_dir = Path(tmpdir)
2026-02-20T05:16:23.095702801Z
2026-02-20T05:16:23.095712521Z # Create job directory
2026-02-20T05:16:23.095716524Z job_dir = work_dir / "job"
2026-02-20T05:16:23.095720718Z job_dir.mkdir()
2026-02-20T05:16:23.095723241Z
2026-02-20T05:16:23.095727348Z # Create test script
2026-02-20T05:16:23.095732411Z test_script = job_dir / "multi_secret_test.py"
2026-02-20T05:16:23.095736521Z test_script.write_text("""#!/usr/bin/env python3
2026-02-20T05:16:23.095739868Z import socket
2026-02-20T05:16:23.095743708Z import json
2026-02-20T05:16:23.095959946Z import struct
2026-02-20T05:16:23.09596416Z import os
2026-02-20T05:16:23.09596795Z import time
2026-02-20T05:16:23.09597035Z
2026-02-20T05:16:23.095974083Z # Simulate getting multiple secrets
2026-02-20T05:16:23.096686693Z secrets = [
2026-02-20T05:16:23.096694453Z "database-password-abc123",
2026-02-20T05:16:23.096698833Z "api-key-def456",
2026-02-20T05:16:23.096710831Z "webhook-secret-ghi789"
2026-02-20T05:16:23.096714298Z ]
2026-02-20T05:16:23.096717188Z
2026-02-20T05:16:23.096724774Z print("Obtained secrets:", secrets)
2026-02-20T05:16:23.096727371Z
2026-02-20T05:16:23.098326834Z # Register them all at once
2026-02-20T05:16:23.09833464Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-20T05:16:23.09833834Z if socket_path:
2026-02-20T05:16:23.098342984Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-20T05:16:23.098346737Z sock.connect(socket_path)
2026-02-20T05:16:23.098349554Z
2026-02-20T05:16:23.0983549Z msg = json.dumps({'action': 'register', 'secrets': secrets}).encode()
2026-02-20T05:16:23.09835884Z sock.send(struct.pack('!I', len(msg)))
2026-02-20T05:16:23.0983622Z sock.send(msg)
2026-02-20T05:16:23.098364657Z
2026-02-20T05:16:23.098368544Z response = sock.recv(1024)
2026-02-20T05:16:23.098372787Z print("Registration response:", response.decode())
2026-02-20T05:16:23.098376257Z sock.close()
2026-02-20T05:16:23.098378607Z
2026-02-20T05:16:23.098382097Z # Wait for processing
2026-02-20T05:16:23.098385637Z time.sleep(0.5)
2026-02-20T05:16:23.098387677Z
2026-02-20T05:16:23.098391714Z # Now use them - should all be masked
2026-02-20T05:16:23.098396557Z print("Database connection: password=database-password-abc123")
2026-02-20T05:16:23.098400097Z print("API header: X-API-Key=api-key-def456")
2026-02-20T05:16:23.098404144Z print("Webhook validation: secret=webhook-secret-ghi789")
2026-02-20T05:16:23.098407667Z else:
2026-02-20T05:16:23.098411587Z print("No secrets socket available")
2026-02-20T05:16:23.098415044Z """)
2026-02-20T05:16:23.099207285Z test_script.chmod(0o755)
2026-02-20T05:16:23.099213012Z
2026-02-20T05:16:23.099217542Z # Run the test
2026-02-20T05:16:23.099221628Z result = subprocess.run(
2026-02-20T05:16:23.100173428Z [
2026-02-20T05:16:23.100182705Z sys.executable, "-m", "src.cli", "run",
2026-02-20T05:16:23.100187328Z "--runner-image", "python:3.9-alpine",
2026-02-20T05:16:23.100192925Z "--job-command", "python3 /job/multi_secret_test.py",
2026-02-20T05:16:23.100197218Z "--code-dir", "/job",
2026-02-20T05:16:23.100200982Z "--job-dir", "/job",
2026-02-20T05:16:23.100206898Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-20T05:16:23.100210345Z ],
2026-02-20T05:16:23.100214655Z capture_output=True,
2026-02-20T05:16:23.100218275Z text=True,
2026-02-20T05:16:23.100221412Z cwd=work_dir,
2026-02-20T05:16:23.100226175Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-20T05:16:23.100229872Z )
2026-02-20T05:16:23.100232508Z
2026-02-20T05:16:23.100441367Z print("STDOUT:", result.stdout)
2026-02-20T05:16:23.10045571Z print("STDERR:", result.stderr)
2026-02-20T05:16:23.100459357Z
2026-02-20T05:16:23.100464184Z> assert result.returncode == 0
2026-02-20T05:16:23.10046744ZE AssertionError: assert 1 == 0
2026-02-20T05:16:23.10047591ZE + 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-20T05:16:23.10047816Z
2026-02-20T05:16:23.100482711Ztests/test_dynamic_secrets.py:233: AssertionError
2026-02-20T05:16:23.100487557Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.100491188ZSTDOUT:
2026-02-20T05:16:23.10102582ZSTDERR: 2026-02-20T05:12:36.913276+00:00 Configuration validation failed:
2026-02-20T05:16:23.10103263Z2026-02-20T05:12:36.913423+00:00 ❌ Configuration has errors:
2026-02-20T05:16:23.101036983Z • system: docker is not available in PATH
2026-02-20T05:16:23.101040836Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-20T05:16:23.101043163Z
2026-02-20T05:16:23.10104543Z
2026-02-20T05:16:23.101050176Z__________________ TestEvalCommand.test_basic_eval_with_match __________________
2026-02-20T05:16:23.101555392Z
2026-02-20T05:16:23.101563676Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc420cd0>
2026-02-20T05:16:23.101583589Ztemp_dirs = (PosixPath('/tmp/tmprh934gqt/ci'), PosixPath('/tmp/tmprh934gqt/src'), PosixPath('/tmp/tmprh934gqt/ci/.reactorcide/jobs'), PosixPath('/tmp/tmprh934gqt/triggers.json'))
2026-02-20T05:16:23.101587726Z
2026-02-20T05:16:23.101594089Z def test_basic_eval_with_match(self, temp_dirs):
2026-02-20T05:16:23.101604156Z """Test eval command with a matching job definition."""
2026-02-20T05:16:23.101610606Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.101614456Z
2026-02-20T05:16:23.101618149Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.102137397Z "name": "test",
2026-02-20T05:16:23.102142854Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.102148237Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.102152051Z })
2026-02-20T05:16:23.102154577Z
2026-02-20T05:16:23.102158064Z result = runner.invoke(app, [
2026-02-20T05:16:23.102161424Z "eval",
2026-02-20T05:16:23.102460931Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.102468548Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.102474015Z "--event-type", "push",
2026-02-20T05:16:23.102481805Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.103060736Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.103068413Z ])
2026-02-20T05:16:23.103072816Z
2026-02-20T05:16:23.103077706Z assert result.exit_code == 0
2026-02-20T05:16:23.103089307Z> assert triggers_file.exists()
2026-02-20T05:16:23.103095067ZE AssertionError: assert False
2026-02-20T05:16:23.103099538ZE + where False = exists()
2026-02-20T05:16:23.103107298ZE + where exists = PosixPath('/tmp/tmprh934gqt/triggers.json').exists
2026-02-20T05:16:23.103893279Z
2026-02-20T05:16:23.103907545Ztests/test_eval_cli.py:69: AssertionError
2026-02-20T05:16:23.103916782Z__________________ TestEvalCommand.test_eval_multiple_matches __________________
2026-02-20T05:16:23.103920342Z
2026-02-20T05:16:23.103926959Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc63b890>
2026-02-20T05:16:23.103940839Ztemp_dirs = (PosixPath('/tmp/tmplb0mla92/ci'), PosixPath('/tmp/tmplb0mla92/src'), PosixPath('/tmp/tmplb0mla92/ci/.reactorcide/jobs'), PosixPath('/tmp/tmplb0mla92/triggers.json'))
2026-02-20T05:16:23.103944225Z
2026-02-20T05:16:23.103950306Z def test_eval_multiple_matches(self, temp_dirs):
2026-02-20T05:16:23.103957709Z """Test eval command with multiple matching definitions."""
2026-02-20T05:16:23.104172008Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.104178418Z
2026-02-20T05:16:23.104183762Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.104189278Z "name": "test",
2026-02-20T05:16:23.104195535Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.104516481Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.104524545Z })
2026-02-20T05:16:23.104805554Z _write_yaml(jobs_dir / "lint.yaml", {
2026-02-20T05:16:23.104812601Z "name": "lint",
2026-02-20T05:16:23.104816585Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.104821878Z "job": {"image": "python:3.11", "command": "ruff check"},
2026-02-20T05:16:23.105180065Z })
2026-02-20T05:16:23.105186332Z
2026-02-20T05:16:23.105192782Z result = runner.invoke(app, [
2026-02-20T05:16:23.105198818Z "eval",
2026-02-20T05:16:23.105909385Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.139369831Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.139377217Z "--event-type", "push",
2026-02-20T05:16:23.139383417Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.139387971Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.139393487Z ])
2026-02-20T05:16:23.139396704Z
2026-02-20T05:16:23.139400594Z assert result.exit_code == 0
2026-02-20T05:16:23.139407504Z> assert triggers_file.exists()
2026-02-20T05:16:23.13967809ZE AssertionError: assert False
2026-02-20T05:16:23.139684137ZE + where False = exists()
2026-02-20T05:16:23.139698237ZE + where exists = PosixPath('/tmp/tmplb0mla92/triggers.json').exists
2026-02-20T05:16:23.1397008Z
2026-02-20T05:16:23.139734861Ztests/test_eval_cli.py:157: AssertionError
2026-02-20T05:16:23.139747335Z___________________ TestEvalCommand.test_eval_branch_filter ____________________
2026-02-20T05:16:23.139751525Z
2026-02-20T05:16:23.139759288Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc33f9b0>
2026-02-20T05:16:23.139772045Ztemp_dirs = (PosixPath('/tmp/tmpbtn9z6bx/ci'), PosixPath('/tmp/tmpbtn9z6bx/src'), PosixPath('/tmp/tmpbtn9z6bx/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpbtn9z6bx/triggers.json'))
2026-02-20T05:16:23.139775895Z
2026-02-20T05:16:23.139781975Z def test_eval_branch_filter(self, temp_dirs):
2026-02-20T05:16:23.139788405Z """Test eval command respects branch filtering."""
2026-02-20T05:16:23.139794688Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.139798925Z
2026-02-20T05:16:23.139804415Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-20T05:16:23.139809955Z "name": "deploy",
2026-02-20T05:16:23.139819292Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-20T05:16:23.139826352Z "job": {"image": "deploy:latest", "command": "deploy.sh"},
2026-02-20T05:16:23.139831982Z })
2026-02-20T05:16:23.139835609Z
2026-02-20T05:16:23.139840832Z # Push to feature branch should not match
2026-02-20T05:16:23.139846922Z result = runner.invoke(app, [
2026-02-20T05:16:23.139852106Z "eval",
2026-02-20T05:16:23.139857992Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.139863659Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.139869162Z "--event-type", "push",
2026-02-20T05:16:23.139874856Z "--branch", "feature/foo",
2026-02-20T05:16:23.139880769Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.139886586Z ])
2026-02-20T05:16:23.139898179Z
2026-02-20T05:16:23.139905049Z assert result.exit_code == 0
2026-02-20T05:16:23.139910496Z assert "No jobs matched" in result.stdout
2026-02-20T05:16:23.139915892Z assert not triggers_file.exists()
2026-02-20T05:16:23.139919856Z
2026-02-20T05:16:23.139926359Z # Push to [REDACTED] should match
2026-02-20T05:16:23.139932203Z result = runner.invoke(app, [
2026-02-20T05:16:23.139937217Z "eval",
2026-02-20T05:16:23.13994321Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.13994903Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.139954883Z "--event-type", "push",
2026-02-20T05:16:23.13996093Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.139966587Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.139972257Z ])
2026-02-20T05:16:23.139976103Z
2026-02-20T05:16:23.13998073Z assert result.exit_code == 0
2026-02-20T05:16:23.139985937Z> assert triggers_file.exists()
2026-02-20T05:16:23.139990793ZE AssertionError: assert False
2026-02-20T05:16:23.139995713ZE + where False = exists()
2026-02-20T05:16:23.140003437ZE + where exists = PosixPath('/tmp/tmpbtn9z6bx/triggers.json').exists
2026-02-20T05:16:23.140006833Z
2026-02-20T05:16:23.140012477Ztests/test_eval_cli.py:202: AssertionError
2026-02-20T05:16:23.14001965Z_________________ TestEvalCommand.test_eval_full_event_context _________________
2026-02-20T05:16:23.140023803Z
2026-02-20T05:16:23.140034807Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc2438a0>
2026-02-20T05:16:23.140046078Ztemp_dirs = (PosixPath('/tmp/tmpl2_kqzt2/ci'), PosixPath('/tmp/tmpl2_kqzt2/src'), PosixPath('/tmp/tmpl2_kqzt2/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpl2_kqzt2/triggers.json'))
2026-02-20T05:16:23.140049194Z
2026-02-20T05:16:23.140054991Z def test_eval_full_event_context(self, temp_dirs):
2026-02-20T05:16:23.140061798Z """Test eval command passes full event context to triggers."""
2026-02-20T05:16:23.140068621Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.140072904Z
2026-02-20T05:16:23.140079014Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.140085371Z "name": "test",
2026-02-20T05:16:23.140091164Z "triggers": {"events": ["pull_request_opened"]},
2026-02-20T05:16:23.140098561Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.140104034Z "environment": {"BUILD_TYPE": "test"},
2026-02-20T05:16:23.140108868Z })
2026-02-20T05:16:23.140141661Z
2026-02-20T05:16:23.140150414Z # Create .git dir so eval doesn't try to clone from the fake URL
2026-02-20T05:16:23.140155839Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.140159499Z
2026-02-20T05:16:23.140164672Z result = runner.invoke(app, [
2026-02-20T05:16:23.140169985Z "eval",
2026-02-20T05:16:23.140175832Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.140181042Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.140187399Z "--event-type", "pull_request_opened",
2026-02-20T05:16:23.140194529Z "--branch", "feature/foo",
2026-02-20T05:16:23.140203175Z "--pr-base-ref", "[REDACTED]",
2026-02-20T05:16:23.140206899Z "--pr-number", "42",
2026-02-20T05:16:23.140211452Z "--source-url", "https://[REDACTED].com/org/repo.git",
2026-02-20T05:16:23.140214672Z "--source-ref", "abc123",
2026-02-20T05:16:23.140218732Z "--ci-source-url", "https://[REDACTED].com/org/ci.git",
2026-02-20T05:16:23.140222315Z "--ci-source-ref", "def456",
2026-02-20T05:16:23.140226015Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.140229019Z ])
2026-02-20T05:16:23.140231329Z
2026-02-20T05:16:23.140237022Z assert result.exit_code == 0
2026-02-20T05:16:23.140240125Z> assert triggers_file.exists()
2026-02-20T05:16:23.140243589ZE AssertionError: assert False
2026-02-20T05:16:23.140247569ZE + where False = exists()
2026-02-20T05:16:23.140252292ZE + where exists = PosixPath('/tmp/tmpl2_kqzt2/triggers.json').exists
2026-02-20T05:16:23.140254582Z
2026-02-20T05:16:23.140258272Ztests/test_eval_cli.py:239: AssertionError
2026-02-20T05:16:23.140262859Z_________________ TestEvalCommand.test_eval_with_changed_files _________________
2026-02-20T05:16:23.140264769Z
2026-02-20T05:16:23.140269033Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc2eeb50>
2026-02-20T05:16:23.140276613Ztemp_dirs = (PosixPath('/tmp/tmpzqimguod/ci'), PosixPath('/tmp/tmpzqimguod/src'), PosixPath('/tmp/tmpzqimguod/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpzqimguod/triggers.json'))
2026-02-20T05:16:23.140278743Z
2026-02-20T05:16:23.140282343Z def test_eval_with_changed_files(self, temp_dirs):
2026-02-20T05:16:23.14028634Z """Test eval command uses git changed files for path filtering."""
2026-02-20T05:16:23.140360773Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.1403636Z
2026-02-20T05:16:23.140367163Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.140374357Z "name": "test",
2026-02-20T05:16:23.140377657Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.140381467Z "paths": {"include": ["src/**"]},
2026-02-20T05:16:23.140385604Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.140388971Z })
2026-02-20T05:16:23.140391238Z
2026-02-20T05:16:23.140395711Z # Create a fake .git dir so the code tries to get changed files
2026-02-20T05:16:23.140399178Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.140401371Z
2026-02-20T05:16:23.140406574Z with patch("src.workflow.changed_files", return_value=["src/[REDACTED].py"]):
2026-02-20T05:16:23.140409971Z result = runner.invoke(app, [
2026-02-20T05:16:23.140413314Z "eval",
2026-02-20T05:16:23.140417065Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.140420468Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.140423838Z "--event-type", "push",
2026-02-20T05:16:23.140427658Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.140431221Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.140434325Z ])
2026-02-20T05:16:23.140436851Z
2026-02-20T05:16:23.140439591Z assert result.exit_code == 0
2026-02-20T05:16:23.140442658Z> assert triggers_file.exists()
2026-02-20T05:16:23.140445608ZE AssertionError: assert False
2026-02-20T05:16:23.140454221ZE + where False = exists()
2026-02-20T05:16:23.140458628ZE + where exists = PosixPath('/tmp/tmpzqimguod/triggers.json').exists
2026-02-20T05:16:23.140460721Z
2026-02-20T05:16:23.140464255Ztests/test_eval_cli.py:287: AssertionError
2026-02-20T05:16:23.140468721Z_____________ TestEvalCommand.test_eval_pr_uses_base_ref_for_diff ______________
2026-02-20T05:16:23.140470631Z
2026-02-20T05:16:23.140474935Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc582990>
2026-02-20T05:16:23.140482262Ztemp_dirs = (PosixPath('/tmp/tmp104isg0d/ci'), PosixPath('/tmp/tmp104isg0d/src'), PosixPath('/tmp/tmp104isg0d/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp104isg0d/triggers.json'))
2026-02-20T05:16:23.140484418Z
2026-02-20T05:16:23.140488182Z def test_eval_pr_uses_base_ref_for_diff(self, temp_dirs):
2026-02-20T05:16:23.140492568Z """Test that PR events use pr_base_ref for changed files diff."""
2026-02-20T05:16:23.140496033Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.140498553Z
2026-02-20T05:16:23.140501619Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.140504643Z "name": "test",
2026-02-20T05:16:23.140507959Z "triggers": {"events": ["pull_request_opened"]},
2026-02-20T05:16:23.140511586Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.140516473Z })
2026-02-20T05:16:23.140518569Z
2026-02-20T05:16:23.140521523Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.140523813Z
2026-02-20T05:16:23.140528496Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-20T05:16:23.140531473Z result = runner.invoke(app, [
2026-02-20T05:16:23.140536856Z "eval",
2026-02-20T05:16:23.140540176Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.140543326Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.140547109Z "--event-type", "pull_request_opened",
2026-02-20T05:16:23.140550276Z "--branch", "feature/foo",
2026-02-20T05:16:23.140554239Z "--pr-base-ref", "[REDACTED]",
2026-02-20T05:16:23.140557573Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.140560816Z ])
2026-02-20T05:16:23.140562973Z
2026-02-20T05:16:23.140567753Z # Verify it was called with origin/[REDACTED] as the from_ref
2026-02-20T05:16:23.140571009Z> mock_changed.assert_called_once_with(
2026-02-20T05:16:23.140577876Z "origin/[REDACTED]", "HEAD", str(src_dir)
2026-02-20T05:16:23.140581096Z )
2026-02-20T05:16:23.140583283Z
2026-02-20T05:16:23.140586426Ztests/test_eval_cli.py:344:
2026-02-20T05:16:23.140589823Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.140594446Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-20T05:16:23.140597626Z return self.assert_called_with(*args, **kwargs)
2026-02-20T05:16:23.140600903Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.140603229Z
2026-02-20T05:16:23.140606449Zself = <MagicMock name='changed_files' id='140595688269408'>
2026-02-20T05:16:23.140611187Zargs = ('origin/[REDACTED]', 'HEAD', '/tmp/tmp104isg0d/src'), kwargs = {}
2026-02-20T05:16:23.14061557Zexpected = call('origin/[REDACTED]', 'HEAD', '/tmp/tmp104isg0d/src')
2026-02-20T05:16:23.140620337Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmp104isg0d/src')
2026-02-20T05:16:23.140625564Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fdefbffa340>
2026-02-20T05:16:23.140628484Zcause = None
2026-02-20T05:16:23.140630157Z
2026-02-20T05:16:23.140633427Z def assert_called_with(self, /, *args, **kwargs):
2026-02-20T05:16:23.14063783Z """assert that the last call was made with the specified arguments.
2026-02-20T05:16:23.14063988Z
2026-02-20T05:16:23.140643587Z Raises an AssertionError if the args and keyword args passed in are
2026-02-20T05:16:23.140646637Z different to the last call to the mock."""
2026-02-20T05:16:23.140649324Z if self.call_args is None:
2026-02-20T05:16:23.140653244Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-20T05:16:23.140656164Z actual = 'not called.'
2026-02-20T05:16:23.14066066Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-20T05:16:23.140663764Z % (expected, actual))
2026-02-20T05:16:23.140666804Z raise AssertionError(error_message)
2026-02-20T05:16:23.140668904Z
2026-02-20T05:16:23.140671917Z def _error_message():
2026-02-20T05:16:23.140675817Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-20T05:16:23.140678854Z return msg
2026-02-20T05:16:23.140682894Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-20T05:16:23.140686337Z actual = self._call_matcher(self.call_args)
2026-02-20T05:16:23.140712017Z if actual != expected:
2026-02-20T05:16:23.14071646Z cause = expected if isinstance(expected, Exception) else None
2026-02-20T05:16:23.14071991Z> raise AssertionError(_error_message()) from cause
2026-02-20T05:16:23.140723288ZE AssertionError: expected call not found.
2026-02-20T05:16:23.140730961ZE Expected: changed_files('origin/[REDACTED]', 'HEAD', '/tmp/tmp104isg0d/src')
2026-02-20T05:16:23.140735935ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmp104isg0d/src')
2026-02-20T05:16:23.140740011Z
2026-02-20T05:16:23.140744061Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-20T05:16:23.140748108Z___________ TestEvalCommand.test_eval_push_uses_head_parent_for_diff ___________
2026-02-20T05:16:23.140750198Z
2026-02-20T05:16:23.140754768Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc582a80>
2026-02-20T05:16:23.140761781Ztemp_dirs = (PosixPath('/tmp/tmpvplbmnxo/ci'), PosixPath('/tmp/tmpvplbmnxo/src'), PosixPath('/tmp/tmpvplbmnxo/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpvplbmnxo/triggers.json'))
2026-02-20T05:16:23.140763668Z
2026-02-20T05:16:23.140767538Z def test_eval_push_uses_head_parent_for_diff(self, temp_dirs):
2026-02-20T05:16:23.140771491Z """Test that push events use HEAD^ for changed files diff."""
2026-02-20T05:16:23.140774791Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.140776915Z
2026-02-20T05:16:23.140780325Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.140783488Z "name": "test",
2026-02-20T05:16:23.140787135Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.140791158Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.140794601Z })
2026-02-20T05:16:23.140796708Z
2026-02-20T05:16:23.140800015Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.140802055Z
2026-02-20T05:16:23.140806891Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-20T05:16:23.140810095Z result = runner.invoke(app, [
2026-02-20T05:16:23.140813445Z "eval",
2026-02-20T05:16:23.140816671Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.140819985Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.140823141Z "--event-type", "push",
2026-02-20T05:16:23.140826541Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.140829915Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.140833101Z ])
2026-02-20T05:16:23.140835198Z
2026-02-20T05:16:23.140838212Z> mock_changed.assert_called_once_with(
2026-02-20T05:16:23.140841272Z "HEAD^", "HEAD", str(src_dir)
2026-02-20T05:16:23.140844819Z )
2026-02-20T05:16:23.140846759Z
2026-02-20T05:16:23.140849759Ztests/test_eval_cli.py:372:
2026-02-20T05:16:23.140853052Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.140857199Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-20T05:16:23.140860306Z return self.assert_called_with(*args, **kwargs)
2026-02-20T05:16:23.140863446Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.140865186Z
2026-02-20T05:16:23.140868586Zself = <MagicMock name='changed_files' id='140595688270416'>
2026-02-20T05:16:23.140871589Zargs = ('HEAD^', 'HEAD', '/tmp/tmpvplbmnxo/src'), kwargs = {}
2026-02-20T05:16:23.140875026Zexpected = call('HEAD^', 'HEAD', '/tmp/tmpvplbmnxo/src')
2026-02-20T05:16:23.140878839Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmpvplbmnxo/src')
2026-02-20T05:16:23.140883506Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fdefbffa0c0>
2026-02-20T05:16:23.140886236Zcause = None
2026-02-20T05:16:23.140887916Z
2026-02-20T05:16:23.140891056Z def assert_called_with(self, /, *args, **kwargs):
2026-02-20T05:16:23.140895692Z """assert that the last call was made with the specified arguments.
2026-02-20T05:16:23.140897786Z
2026-02-20T05:16:23.140901326Z Raises an AssertionError if the args and keyword args passed in are
2026-02-20T05:16:23.140904409Z different to the last call to the mock."""
2026-02-20T05:16:23.140907296Z if self.call_args is None:
2026-02-20T05:16:23.140911026Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-20T05:16:23.140915419Z actual = 'not called.'
2026-02-20T05:16:23.140919489Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-20T05:16:23.140922776Z % (expected, actual))
2026-02-20T05:16:23.140927396Z raise AssertionError(error_message)
2026-02-20T05:16:23.140929436Z
2026-02-20T05:16:23.140932162Z def _error_message():
2026-02-20T05:16:23.140935726Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-20T05:16:23.140938466Z return msg
2026-02-20T05:16:23.140942052Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-20T05:16:23.140944932Z actual = self._call_matcher(self.call_args)
2026-02-20T05:16:23.140948016Z if actual != expected:
2026-02-20T05:16:23.140951633Z cause = expected if isinstance(expected, Exception) else None
2026-02-20T05:16:23.140954877Z> raise AssertionError(_error_message()) from cause
2026-02-20T05:16:23.140957863ZE AssertionError: expected call not found.
2026-02-20T05:16:23.140961583ZE Expected: changed_files('HEAD^', 'HEAD', '/tmp/tmpvplbmnxo/src')
2026-02-20T05:16:23.140969057ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmpvplbmnxo/src')
2026-02-20T05:16:23.140970793Z
2026-02-20T05:16:23.140974057Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-20T05:16:23.14098068Z___________ TestEvalCommand.test_eval_no_git_dir_skips_changed_files ___________
2026-02-20T05:16:23.140982523Z
2026-02-20T05:16:23.140986157Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc64f310>
2026-02-20T05:16:23.14101254Ztemp_dirs = (PosixPath('/tmp/tmp_ecayry9/ci'), PosixPath('/tmp/tmp_ecayry9/src'), PosixPath('/tmp/tmp_ecayry9/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp_ecayry9/triggers.json'))
2026-02-20T05:16:23.1410147Z
2026-02-20T05:16:23.14101874Z def test_eval_no_git_dir_skips_changed_files(self, temp_dirs):
2026-02-20T05:16:23.141023647Z """Test that eval skips changed files detection when no .git dir exists."""
2026-02-20T05:16:23.14102673Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.141028693Z
2026-02-20T05:16:23.141031943Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.14103485Z "name": "test",
2026-02-20T05:16:23.141038127Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.14104137Z "paths": {"include": ["src/**"]},
2026-02-20T05:16:23.141045433Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.141048427Z })
2026-02-20T05:16:23.141050647Z
2026-02-20T05:16:23.14105488Z # No .git directory - should skip changed files and still match
2026-02-20T05:16:23.141059067Z # (path filtering is skipped when changed_files is None)
2026-02-20T05:16:23.141062011Z result = runner.invoke(app, [
2026-02-20T05:16:23.141065028Z "eval",
2026-02-20T05:16:23.141070214Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.141073268Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.141076544Z "--event-type", "push",
2026-02-20T05:16:23.141081171Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.141084231Z ])
2026-02-20T05:16:23.141086138Z
2026-02-20T05:16:23.141089188Z assert result.exit_code == 0
2026-02-20T05:16:23.141091981Z> assert triggers_file.exists()
2026-02-20T05:16:23.141095148ZE AssertionError: assert False
2026-02-20T05:16:23.141097818ZE + where False = exists()
2026-02-20T05:16:23.141102464ZE + where exists = PosixPath('/tmp/tmp_ecayry9/triggers.json').exists
2026-02-20T05:16:23.141104578Z
2026-02-20T05:16:23.141108131Ztests/test_eval_cli.py:400: AssertionError
2026-02-20T05:16:23.141132534Z______________________ TestEvalCommand.test_eval_env_vars ______________________
2026-02-20T05:16:23.141136844Z
2026-02-20T05:16:23.141143134Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc64eeb0>
2026-02-20T05:16:23.141150734Ztemp_dirs = (PosixPath('/tmp/tmpojzslh1g/ci'), PosixPath('/tmp/tmpojzslh1g/src'), PosixPath('/tmp/tmpojzslh1g/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpojzslh1g/triggers.json'))
2026-02-20T05:16:23.141152841Z
2026-02-20T05:16:23.141157014Z def test_eval_env_vars(self, temp_dirs):
2026-02-20T05:16:23.141161001Z """Test that eval reads options from environment variables."""
2026-02-20T05:16:23.141164754Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.141169788Z
2026-02-20T05:16:23.141173134Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.141176379Z "name": "test",
2026-02-20T05:16:23.141180102Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.141184235Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.141187479Z })
2026-02-20T05:16:23.141189815Z
2026-02-20T05:16:23.141193222Z env = {
2026-02-20T05:16:23.141198525Z "REACTORCIDE_CI_SOURCE_DIR": str(ci_dir),
2026-02-20T05:16:23.141202119Z "REACTORCIDE_SOURCE_DIR": str(src_dir),
2026-02-20T05:16:23.141205612Z "REACTORCIDE_EVENT_TYPE": "push",
2026-02-20T05:16:23.141209902Z "REACTORCIDE_BRANCH": "[REDACTED]",
2026-02-20T05:16:23.141212809Z }
2026-02-20T05:16:23.141215436Z
2026-02-20T05:16:23.141218672Z result = runner.invoke(app, [
2026-02-20T05:16:23.141221636Z "eval",
2026-02-20T05:16:23.141225029Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.141227756Z ], env=env)
2026-02-20T05:16:23.141229722Z
2026-02-20T05:16:23.141232766Z assert result.exit_code == 0
2026-02-20T05:16:23.141235406Z> assert triggers_file.exists()
2026-02-20T05:16:23.141238136ZE AssertionError: assert False
2026-02-20T05:16:23.141240762ZE + where False = exists()
2026-02-20T05:16:23.141244509ZE + where exists = PosixPath('/tmp/tmpojzslh1g/triggers.json').exists
2026-02-20T05:16:23.141246402Z
2026-02-20T05:16:23.141249386Ztests/test_eval_cli.py:425: AssertionError
2026-02-20T05:16:23.141253086Z______________ TestEvalCommand.test_eval_job_priority_and_timeout ______________
2026-02-20T05:16:23.141255326Z
2026-02-20T05:16:23.141259172Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc629980>
2026-02-20T05:16:23.141266989Ztemp_dirs = (PosixPath('/tmp/tmpsa74jwjk/ci'), PosixPath('/tmp/tmpsa74jwjk/src'), PosixPath('/tmp/tmpsa74jwjk/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpsa74jwjk/triggers.json'))
2026-02-20T05:16:23.141268942Z
2026-02-20T05:16:23.141272432Z def test_eval_job_priority_and_timeout(self, temp_dirs):
2026-02-20T05:16:23.141276676Z """Test that job priority and timeout are passed through to triggers."""
2026-02-20T05:16:23.141280022Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.141282033Z
2026-02-20T05:16:23.141285233Z _write_yaml(jobs_dir / "build.yaml", {
2026-02-20T05:16:23.141288229Z "name": "build",
2026-02-20T05:16:23.141291237Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.14129412Z "job": {
2026-02-20T05:16:23.141297257Z "image": "gcc:latest",
2026-02-20T05:16:23.141300117Z "command": "make",
2026-02-20T05:16:23.141303264Z "timeout": 3600,
2026-02-20T05:16:23.14130631Z "priority": 20,
2026-02-20T05:16:23.141309544Z },
2026-02-20T05:16:23.141312217Z })
2026-02-20T05:16:23.141314144Z
2026-02-20T05:16:23.141316844Z result = runner.invoke(app, [
2026-02-20T05:16:23.14131957Z "eval",
2026-02-20T05:16:23.14132282Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.141326004Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.141328894Z "--event-type", "push",
2026-02-20T05:16:23.141331964Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.141334587Z ])
2026-02-20T05:16:23.14133662Z
2026-02-20T05:16:23.141339504Z assert result.exit_code == 0
2026-02-20T05:16:23.14134142Z
2026-02-20T05:16:23.141345987Z> with open(triggers_file) as f:
2026-02-20T05:16:23.141350317ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpsa74jwjk/triggers.json'
2026-02-20T05:16:23.141352204Z
2026-02-20T05:16:23.141355604Ztests/test_eval_cli.py:452: FileNotFoundError
2026-02-20T05:16:23.141359497Z________________ TestEvalCommand.test_eval_git_error_continues _________________
2026-02-20T05:16:23.141361397Z
2026-02-20T05:16:23.141387444Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fdefc616090>
2026-02-20T05:16:23.141394551Ztemp_dirs = (PosixPath('/tmp/tmp38kit4qv/ci'), PosixPath('/tmp/tmp38kit4qv/src'), PosixPath('/tmp/tmp38kit4qv/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp38kit4qv/triggers.json'))
2026-02-20T05:16:23.141396591Z
2026-02-20T05:16:23.141400197Z def test_eval_git_error_continues(self, temp_dirs):
2026-02-20T05:16:23.141404942Z """Test that git errors during changed files detection don't fail the command."""
2026-02-20T05:16:23.141410462Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.141412635Z
2026-02-20T05:16:23.141415975Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.141418975Z "name": "test",
2026-02-20T05:16:23.141422245Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.141426532Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.141429628Z })
2026-02-20T05:16:23.141431725Z
2026-02-20T05:16:23.141435428Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.141440475Z
2026-02-20T05:16:23.141445265Z with patch("src.workflow.changed_files", side_effect=Exception("git error")):
2026-02-20T05:16:23.141448452Z result = runner.invoke(app, [
2026-02-20T05:16:23.141451572Z "eval",
2026-02-20T05:16:23.14376788Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.143774714Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.143999853Z "--event-type", "push",
2026-02-20T05:16:23.14400543Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.144009494Z ])
2026-02-20T05:16:23.144012631Z
2026-02-20T05:16:23.144017424Z assert result.exit_code == 0
2026-02-20T05:16:23.144020581Z> assert triggers_file.exists()
2026-02-20T05:16:23.144023454ZE AssertionError: assert False
2026-02-20T05:16:23.144027018ZE + where False = exists()
2026-02-20T05:16:23.144032574ZE + where exists = PosixPath('/tmp/tmp38kit4qv/triggers.json').exists
2026-02-20T05:16:23.144034928Z
2026-02-20T05:16:23.144039008Ztests/test_eval_cli.py:480: AssertionError
2026-02-20T05:16:23.144044304Z______ TestEvalSourcePreparation.test_eval_clones_ci_source_when_missing _______
2026-02-20T05:16:23.144046654Z
2026-02-20T05:16:23.144051978Zself = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7fdefc71f610>
2026-02-20T05:16:23.144054028Z
2026-02-20T05:16:23.144058131Z def test_eval_clones_ci_source_when_missing(self):
2026-02-20T05:16:23.144062391Z """Test that eval clones CI source when the jobs dir doesn't exist."""
2026-02-20T05:16:23.144066151Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.144069484Z base = Path(tmpdir)
2026-02-20T05:16:23.144072978Z ci_dir = base / "ci" # Does not exist yet
2026-02-20T05:16:23.144076661Z src_dir = base / "src"
2026-02-20T05:16:23.144079711Z src_dir.mkdir()
2026-02-20T05:16:23.144082728Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.144086321Z triggers_file = base / "triggers.json"
2026-02-20T05:16:23.144088698Z
2026-02-20T05:16:23.144092474Z # Create a fake "remote" repo with job definitions
2026-02-20T05:16:23.144095848Z remote_dir = base / "remote"
2026-02-20T05:16:23.144098858Z remote_dir.mkdir()
2026-02-20T05:16:23.144102174Z from git import Repo
2026-02-20T05:16:23.144105324Z remote_repo = Repo.init(remote_dir)
2026-02-20T05:16:23.144109098Z remote_jobs_dir = remote_dir / ".reactorcide" / "jobs"
2026-02-20T05:16:23.144135762Z remote_jobs_dir.mkdir(parents=True)
2026-02-20T05:16:23.144146715Z _write_yaml(remote_jobs_dir / "test.yaml", {
2026-02-20T05:16:23.144149955Z "name": "test",
2026-02-20T05:16:23.144153379Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.144158662Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.144162382Z })
2026-02-20T05:16:23.146080028Z remote_repo.index.add([str(remote_jobs_dir / "test.yaml")])
2026-02-20T05:16:23.146087221Z remote_repo.index.commit("Add job def")
2026-02-20T05:16:23.146090258Z
2026-02-20T05:16:23.146097384Z result = runner.invoke(app, [
2026-02-20T05:16:23.146101821Z "eval",
2026-02-20T05:16:23.146105838Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.146109674Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.146136001Z "--event-type", "push",
2026-02-20T05:16:23.146141544Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.146145408Z "--ci-source-url", str(remote_dir),
2026-02-20T05:16:23.146149004Z "--ci-source-ref", "",
2026-02-20T05:16:23.146152524Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.146157041Z ])
2026-02-20T05:16:23.146159924Z
2026-02-20T05:16:23.146163608Z assert result.exit_code == 0
2026-02-20T05:16:23.146166938Z> assert triggers_file.exists()
2026-02-20T05:16:23.146170211ZE AssertionError: assert False
2026-02-20T05:16:23.146173304ZE + where False = exists()
2026-02-20T05:16:23.146178821ZE + where exists = PosixPath('/tmp/tmpiuzkkr9l/triggers.json').exists
2026-02-20T05:16:23.146180924Z
2026-02-20T05:16:23.146185118Ztests/test_eval_cli.py:523: AssertionError
2026-02-20T05:16:23.146190412Z________ TestEvalSourcePreparation.test_eval_clones_source_when_missing ________
2026-02-20T05:16:23.146192599Z
2026-02-20T05:16:23.146197235Zself = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7fdefc71f750>
2026-02-20T05:16:23.146199425Z
2026-02-20T05:16:23.146202989Z def test_eval_clones_source_when_missing(self):
2026-02-20T05:16:23.146207409Z """Test that eval clones source repo when .git dir doesn't exist."""
2026-02-20T05:16:23.146210959Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.146214582Z base = Path(tmpdir)
2026-02-20T05:16:23.146217532Z ci_dir = base / "ci"
2026-02-20T05:16:23.146220399Z src_dir = base / "src"
2026-02-20T05:16:23.146224335Z src_dir.mkdir() # Exists but no .git (like worker creates)
2026-02-20T05:16:23.146227685Z jobs_dir = ci_dir / ".reactorcide" / "jobs"
2026-02-20T05:16:23.146230812Z jobs_dir.mkdir(parents=True)
2026-02-20T05:16:23.146234542Z triggers_file = base / "triggers.json"
2026-02-20T05:16:23.146765508Z
2026-02-20T05:16:23.146775601Z # Create a fake "remote" source repo
2026-02-20T05:16:23.146784005Z remote_dir = base / "remote_src"
2026-02-20T05:16:23.146787781Z remote_dir.mkdir()
2026-02-20T05:16:23.146791635Z from git import Repo
2026-02-20T05:16:23.146796081Z remote_repo = Repo.init(remote_dir)
2026-02-20T05:16:23.146802391Z (remote_dir / "[REDACTED].py").write_text("print('hello')")
2026-02-20T05:16:23.146807648Z remote_repo.index.add(["[REDACTED].py"])
2026-02-20T05:16:23.15168526Z remote_repo.index.commit("Initial commit")
2026-02-20T05:16:23.151693193Z
2026-02-20T05:16:23.15170075Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.151708013Z "name": "test",
2026-02-20T05:16:23.15171463Z "triggers": {"events": ["push"]},
2026-02-20T05:16:23.15173029Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-20T05:16:23.151736817Z })
2026-02-20T05:16:23.151741273Z
2026-02-20T05:16:23.151753557Z result = runner.invoke(app, [
2026-02-20T05:16:23.15175846Z "eval",
2026-02-20T05:16:23.151762427Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.151766384Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.151769907Z "--event-type", "push",
2026-02-20T05:16:23.151775168Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.151778738Z "--source-url", str(remote_dir),
2026-02-20T05:16:23.151782391Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.151785921Z ])
2026-02-20T05:16:23.151788251Z
2026-02-20T05:16:23.151791768Z> assert result.exit_code == 0
2026-02-20T05:16:23.151795031ZE AssertionError: assert 1 == 0
2026-02-20T05:16:23.151802781ZE + where 1 = <Result GitCommandError('git checkout [REDACTED]', 128)>.exit_code
2026-02-20T05:16:23.151805481Z
2026-02-20T05:16:23.151809395Ztests/test_eval_cli.py:567: AssertionError
2026-02-20T05:16:23.151813968Z___________ TestEvalEndToEnd.test_pr_opened_triggers_test_not_deploy ___________
2026-02-20T05:16:23.151816265Z
2026-02-20T05:16:23.151820741Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fdefc71f890>
2026-02-20T05:16:23.151828708Ztemp_dirs = (PosixPath('/tmp/tmpssjwbqnf/ci'), PosixPath('/tmp/tmpssjwbqnf/src'), PosixPath('/tmp/tmpssjwbqnf/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpssjwbqnf/triggers.json'))
2026-02-20T05:16:23.151830885Z
2026-02-20T05:16:23.151836078Z def test_pr_opened_triggers_test_not_deploy(self, temp_dirs):
2026-02-20T05:16:23.151840308Z """Test full pipeline: PR opened triggers test job but not deploy."""
2026-02-20T05:16:23.151844595Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.151846805Z
2026-02-20T05:16:23.151850858Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.151854405Z "name": "test",
2026-02-20T05:16:23.151857658Z "description": "Run tests",
2026-02-20T05:16:23.151860861Z "triggers": {
2026-02-20T05:16:23.151865305Z "events": ["pull_request_opened", "[REDACTED]"],
2026-02-20T05:16:23.151869778Z "branches": ["[REDACTED]", "feature/*"],
2026-02-20T05:16:23.151875011Z },
2026-02-20T05:16:23.151880058Z "job": {"image": "python:3.11", "command": "pytest", "timeout": 1800},
2026-02-20T05:16:23.151883611Z "environment": {"PYTEST_ARGS": "-v"},
2026-02-20T05:16:23.151886679Z })
2026-02-20T05:16:23.151890226Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-20T05:16:23.151893616Z "name": "deploy",
2026-02-20T05:16:23.151896869Z "description": "Deploy to production",
2026-02-20T05:16:23.151901552Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-20T05:16:23.151906162Z "job": {"image": "deploy:latest", "command": "deploy.sh", "priority": 5},
2026-02-20T05:16:23.151909009Z })
2026-02-20T05:16:23.151911372Z
2026-02-20T05:16:23.157090793Z # Create .git dir so eval doesn't try to clone from the fake URL
2026-02-20T05:16:23.15709924Z (src_dir / ".git").mkdir()
2026-02-20T05:16:23.15710242Z
2026-02-20T05:16:23.157138693Z result = runner.invoke(app, [
2026-02-20T05:16:23.157147077Z "eval",
2026-02-20T05:16:23.15715361Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.157160531Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.157164738Z "--event-type", "pull_request_opened",
2026-02-20T05:16:23.157168388Z "--branch", "feature/my-feature",
2026-02-20T05:16:23.157177864Z "--source-url", "https://[REDACTED].com/org/repo.git",
2026-02-20T05:16:23.157181391Z "--source-ref", "abc123",
2026-02-20T05:16:23.157185668Z "--pr-base-ref", "[REDACTED]",
2026-02-20T05:16:23.157189378Z "--pr-number", "42",
2026-02-20T05:16:23.157193171Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.157196998Z ])
2026-02-20T05:16:23.157199531Z
2026-02-20T05:16:23.157204571Z assert result.exit_code == 0
2026-02-20T05:16:23.157207928Z> assert triggers_file.exists()
2026-02-20T05:16:23.157210974ZE AssertionError: assert False
2026-02-20T05:16:23.157214018ZE + where False = exists()
2026-02-20T05:16:23.157219104ZE + where exists = PosixPath('/tmp/tmpssjwbqnf/triggers.json').exists
2026-02-20T05:16:23.157221318Z
2026-02-20T05:16:23.157225174Ztests/test_eval_cli.py:643: AssertionError
2026-02-20T05:16:23.157231391Z______________ TestEvalEndToEnd.test_push_to_[REDACTED]_triggers_deploy ______________
2026-02-20T05:16:23.157233371Z
2026-02-20T05:16:23.157238074Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fdefc71f9d0>
2026-02-20T05:16:23.157246268Ztemp_dirs = (PosixPath('/tmp/tmp42tvxsug/ci'), PosixPath('/tmp/tmp42tvxsug/src'), PosixPath('/tmp/tmp42tvxsug/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp42tvxsug/triggers.json'))
2026-02-20T05:16:23.157248344Z
2026-02-20T05:16:23.157253691Z def test_push_to_[REDACTED]_triggers_deploy(self, temp_dirs):
2026-02-20T05:16:23.157260594Z """Test full pipeline: push to [REDACTED] triggers deploy but not PR test."""
2026-02-20T05:16:23.157264434Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.157267171Z
2026-02-20T05:16:23.157271515Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-20T05:16:23.157274955Z "name": "test",
2026-02-20T05:16:23.157279042Z "triggers": {"events": ["pull_request_opened"]},
2026-02-20T05:16:23.157282332Z })
2026-02-20T05:16:23.157285975Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-20T05:16:23.157289282Z "name": "deploy",
2026-02-20T05:16:23.157296219Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-20T05:16:23.157300595Z "job": {"image": "deploy:latest", "command": "deploy.sh"},
2026-02-20T05:16:23.157303679Z })
2026-02-20T05:16:23.157305972Z
2026-02-20T05:16:23.157309325Z result = runner.invoke(app, [
2026-02-20T05:16:23.157312682Z "eval",
2026-02-20T05:16:23.157316579Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.157319825Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.157322872Z "--event-type", "push",
2026-02-20T05:16:23.157326532Z "--branch", "[REDACTED]",
2026-02-20T05:16:23.157330332Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.157334935Z ])
2026-02-20T05:16:23.157338789Z
2026-02-20T05:16:23.157343692Z assert result.exit_code == 0
2026-02-20T05:16:23.157347165Z
2026-02-20T05:16:23.157351705Z> with open(triggers_file) as f:
2026-02-20T05:16:23.157359169ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmp42tvxsug/triggers.json'
2026-02-20T05:16:23.157363369Z
2026-02-20T05:16:23.157368655Ztests/test_eval_cli.py:681: FileNotFoundError
2026-02-20T05:16:23.157375382Z______________ TestEvalEndToEnd.test_tag_created_triggers_release ______________
2026-02-20T05:16:23.157379102Z
2026-02-20T05:16:23.157386306Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fdefc5f6fd0>
2026-02-20T05:16:23.157393976Ztemp_dirs = (PosixPath('/tmp/tmp33qp52b1/ci'), PosixPath('/tmp/tmp33qp52b1/src'), PosixPath('/tmp/tmp33qp52b1/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp33qp52b1/triggers.json'))
2026-02-20T05:16:23.15739699Z
2026-02-20T05:16:23.15740368Z def test_tag_created_triggers_release(self, temp_dirs):
2026-02-20T05:16:23.15741004Z """Test full pipeline: tag_created triggers release job."""
2026-02-20T05:16:23.157416333Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-20T05:16:23.157420206Z
2026-02-20T05:16:23.15742818Z _write_yaml(jobs_dir / "release.yaml", {
2026-02-20T05:16:23.157434266Z "name": "release",
2026-02-20T05:16:23.157438413Z "triggers": {"events": ["tag_created"]},
2026-02-20T05:16:23.157442746Z "job": {"image": "builder:latest", "command": "make release"},
2026-02-20T05:16:23.15744591Z })
2026-02-20T05:16:23.158550731Z
2026-02-20T05:16:23.158564051Z result = runner.invoke(app, [
2026-02-20T05:16:23.158570744Z "eval",
2026-02-20T05:16:23.158577518Z "--ci-source-dir", str(ci_dir),
2026-02-20T05:16:23.158585291Z "--source-dir", str(src_dir),
2026-02-20T05:16:23.158591908Z "--event-type", "tag_created",
2026-02-20T05:16:23.158597958Z "--triggers-file", str(triggers_file),
2026-02-20T05:16:23.158603634Z ])
2026-02-20T05:16:23.158608164Z
2026-02-20T05:16:23.158613618Z assert result.exit_code == 0
2026-02-20T05:16:23.158617918Z
2026-02-20T05:16:23.158623688Z> with open(triggers_file) as f:
2026-02-20T05:16:23.158632742ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmp33qp52b1/triggers.json'
2026-02-20T05:16:23.158636842Z
2026-02-20T05:16:23.158643502Ztests/test_eval_cli.py:707: FileNotFoundError
2026-02-20T05:16:23.158652266Z____________ TestGitOperations.test_checkout_creates_job_directory _____________
2026-02-20T05:16:23.158656316Z
2026-02-20T05:16:23.158664659Zself = <runnerlib.tests.test_git_operations.TestGitOperations object at 0x7fdefc72ca50>
2026-02-20T05:16:23.158670966Ztest_repo = '/tmp/tmpchp8oopw'
2026-02-20T05:16:23.158681673Zjob_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-20T05:16:23.158685723Z
2026-02-20T05:16:23.158692483Z def test_checkout_creates_job_directory(self, test_repo, job_config):
2026-02-20T05:16:23.158700139Z """Test that checkout creates the job directory structure."""
2026-02-20T05:16:23.158705039Z # Ensure job dir doesn't exist initially
2026-02-20T05:16:23.15879275Z if Path("./job").exists():
2026-02-20T05:16:23.158797487Z shutil.rmtree("./job")
2026-02-20T05:16:23.15879987Z
2026-02-20T05:16:23.15880553Z # Try [REDACTED] first, fallback to master
2026-02-20T05:16:23.15880952Z try:
2026-02-20T05:16:23.159685549Z checkout_git_repo(test_repo, "[REDACTED]", job_config)
2026-02-20T05:16:23.159692446Z except Exception:
2026-02-20T05:16:23.159699053Z checkout_git_repo(test_repo, "master", job_config)
2026-02-20T05:16:23.159703719Z
2026-02-20T05:16:23.159710199Z # Verify job directory was created
2026-02-20T05:16:23.159715779Z> assert Path("./job").exists()
2026-02-20T05:16:23.15978961ZE AssertionError: assert False
2026-02-20T05:16:23.161958088ZE + where False = exists()
2026-02-20T05:16:23.161965318ZE + where exists = PosixPath('job').exists
2026-02-20T05:16:23.161969355ZE + where PosixPath('job') = Path('./job')
2026-02-20T05:16:23.161972295Z
2026-02-20T05:16:23.161976851Ztests/test_git_operations.py:213: AssertionError
2026-02-20T05:16:23.161982495Z---------------------------- Captured stdout setup -----------------------------
2026-02-20T05:16:23.161987065ZInitialized empty Git repository in /tmp/tmpchp8oopw/.git/
2026-02-20T05:16:23.161991708Z[master (root-commit) b92f176] Initial commit
2026-02-20T05:16:23.162001831Z 1 file changed, 1 insertion(+)
2026-02-20T05:16:23.162006141Z create mode 100644 test.txt
2026-02-20T05:16:23.162009955Z[feature 017c382] Feature changes
2026-02-20T05:16:23.162014002Z 2 files changed, 2 insertions(+), 1 deletion(-)
2026-02-20T05:16:23.162017478Z create mode 100644 new.txt
2026-02-20T05:16:23.162022002Z---------------------------- Captured stderr setup -----------------------------
2026-02-20T05:16:23.162027252Zhint: Using 'master' as the name for the initial branch. This default branch name
2026-02-20T05:16:23.162032626Zhint: is subject to change. To configure the initial branch name to use in all
2026-02-20T05:16:23.162037009Zhint: of your new repositories, which will suppress this warning, call:
2026-02-20T05:16:23.162040539Zhint:
2026-02-20T05:16:23.162044703Zhint: git config --global init.defaultBranch <name>
2026-02-20T05:16:23.162047876Zhint:
2026-02-20T05:16:23.162054486Zhint: Names commonly chosen instead of 'master' are '[REDACTED]', 'trunk' and
2026-02-20T05:16:23.162058849Zhint: 'development'. The just-created branch can be renamed via this command:
2026-02-20T05:16:23.162061549Zhint:
2026-02-20T05:16:23.162064573Zhint: git branch -m <name>
2026-02-20T05:16:23.162172061ZSwitched to a new branch 'feature'
2026-02-20T05:16:23.162184881Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.162193718Z2026-02-20T05:15:12.783176+00:00 Cloning repository: /tmp/tmpchp8oopw
2026-02-20T05:16:23.162210938Z2026-02-20T05:15:12.831796+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.162592267Z2026-02-20T05:15:12.852219+00:00 Fetching PR ref: refs/pull/47/head
2026-02-20T05:16:23.162599082Z2026-02-20T05:15:12.867528+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-20T05:16:23.162602985Z2026-02-20T05:15:12.881989+00:00 Fetching all remote refs...
2026-02-20T05:16:23.162856804Z2026-02-20T05:15:12.919743+00:00 Cloning repository: /tmp/tmpchp8oopw
2026-02-20T05:16:23.162862047Z2026-02-20T05:15:12.961793+00:00 Checking out ref: master
2026-02-20T05:16:23.162865627Z2026-02-20T05:15:12.975239+00:00 Repository checked out to: /job/src
2026-02-20T05:16:23.162870067Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.162882211Z2026-02-20T05:15:12.783039+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpchp8oopw ref=[REDACTED]
2026-02-20T05:16:23.16725491Z2026-02-20T05:15:12.913133+00:00 [ERROR] [runnerlib] Failed to clone repository url=/tmp/tmpchp8oopw error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.16726634Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.167275047Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.167284498Z2026-02-20T05:15:12.913261+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.167291072Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.169100372Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.169139243Z2026-02-20T05:15:12.919639+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpchp8oopw ref=master
2026-02-20T05:16:23.169153733Z2026-02-20T05:15:12.975121+00:00 [INFO] [runnerlib] Repository cloned successfully path=/job/src
2026-02-20T05:16:23.169164547Z_ TestDirectoryManagementIntegration.test_directory_validation_with_real_filesystem _
2026-02-20T05:16:23.16919903Z
2026-02-20T05:16:23.169209727Zself = <runnerlib.tests.test_integration.TestDirectoryManagementIntegration object at 0x7fdefc678690>
2026-02-20T05:16:23.16921451Z
2026-02-20T05:16:23.169222337Z def test_directory_validation_with_real_filesystem(self):
2026-02-20T05:16:23.169230008Z """Test directory validation against real filesystem."""
2026-02-20T05:16:23.169236614Z config = get_config(
2026-02-20T05:16:23.169242591Z code_dir='/job/src',
2026-02-20T05:16:23.169266291Z job_dir='/job/work',
2026-02-20T05:16:23.169272644Z job_command='test',
2026-02-20T05:16:23.169279268Z runner_image='test:image'
2026-02-20T05:16:23.169286071Z )
2026-02-20T05:16:23.169291441Z
2026-02-20T05:16:23.169298224Z # Test validation without directories
2026-02-20T05:16:23.169321778Z with patch('shutil.which', return_value="/usr/bin/docker"):
2026-02-20T05:16:23.169329348Z result = validate_config(config, check_files=True)
2026-02-20T05:16:23.169335134Z
2026-02-20T05:16:23.169343329Z # Should have warnings about missing directories
2026-02-20T05:16:23.169349239Z> assert result.has_warnings
2026-02-20T05:16:23.169360552ZE assert False
2026-02-20T05:16:23.169368559ZE + where False = ValidationResult(is_valid=True, errors=[], warnings=[]).has_warnings
2026-02-20T05:16:23.169416849Z
2026-02-20T05:16:23.169425569Z/workspace/runnerlib/tests/test_integration.py:121: AssertionError
2026-02-20T05:16:23.169433465Z___________________ TestJobIsolation.test_work_dir_isolation ___________________
2026-02-20T05:16:23.169438122Z
2026-02-20T05:16:23.169445965Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fdefc6791d0>
2026-02-20T05:16:23.169449719Z
2026-02-20T05:16:23.169471143Z def test_work_dir_isolation(self):
2026-02-20T05:16:23.169478726Z """Test that jobs use separate work directories."""
2026-02-20T05:16:23.169485553Z with tempfile.TemporaryDirectory() as temp_dir1:
2026-02-20T05:16:23.169492196Z with tempfile.TemporaryDirectory() as temp_dir2:
2026-02-20T05:16:23.16949979Z # Change to first temp directory
2026-02-20T05:16:23.169506853Z original_cwd = os.getcwd()
2026-02-20T05:16:23.169511803Z
2026-02-20T05:16:23.16953444Z try:
2026-02-20T05:16:23.169540903Z # Test job 1 in temp_dir1
2026-02-20T05:16:23.169547163Z os.chdir(temp_dir1)
2026-02-20T05:16:23.16955401Z config1 = RunnerConfig(
2026-02-20T05:16:23.169560546Z code_dir="/job/src",
2026-02-20T05:16:23.169567561Z job_dir="/job/src",
2026-02-20T05:16:23.169588957Z job_command="echo 'job1'",
2026-02-20T05:16:23.169595964Z runner_image="alpine:latest"
2026-02-20T05:16:23.169602594Z )
2026-02-20T05:16:23.169609667Z job_path1 = prepare_job_directory(config1)
2026-02-20T05:16:23.169616434Z assert job_path1.exists()
2026-02-20T05:16:23.169623601Z> assert str(job_path1).startswith(temp_dir1)
2026-02-20T05:16:23.169629687ZE AssertionError: assert False
2026-02-20T05:16:23.169654251ZE + where False = <built-in method startswith of str object at 0x7fdefc61d9e0>('/tmp/tmphnnx5g6l')
2026-02-20T05:16:23.169663524ZE + where <built-in method startswith of str object at 0x7fdefc61d9e0> = '/job'.startswith
2026-02-20T05:16:23.169670627ZE + where '/job' = str(PosixPath('/job'))
2026-02-20T05:16:23.169674694Z
2026-02-20T05:16:23.169681645Ztests/test_job_isolation.py:35: AssertionError
2026-02-20T05:16:23.169693012Z________________ TestJobIsolation.test_concurrent_job_isolation ________________
2026-02-20T05:16:23.169713198Z
2026-02-20T05:16:23.169720958Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fdefc679310>
2026-02-20T05:16:23.169725172Z
2026-02-20T05:16:23.170045059Z def test_concurrent_job_isolation(self):
2026-02-20T05:16:23.170059779Z """Test that concurrent jobs don't interfere with each other."""
2026-02-20T05:16:23.170069239Z import threading
2026-02-20T05:16:23.170108526Z import time
2026-02-20T05:16:23.170227577Z
2026-02-20T05:16:23.170239387Z results = {}
2026-02-20T05:16:23.170453969Z errors = {}
2026-02-20T05:16:23.170462115Z
2026-02-20T05:16:23.170491866Z def run_job(job_id: str, work_dir: str):
2026-02-20T05:16:23.170499436Z """Run a job in its own work directory."""
2026-02-20T05:16:23.170506353Z try:
2026-02-20T05:16:23.17051338Z original_cwd = os.getcwd()
2026-02-20T05:16:23.170883687Z os.chdir(work_dir)
2026-02-20T05:16:23.17090882Z
2026-02-20T05:16:23.170916047Z config = RunnerConfig(
2026-02-20T05:16:23.170923357Z code_dir="/job/src",
2026-02-20T05:16:23.170929838Z job_dir="/job/src",
2026-02-20T05:16:23.170945261Z job_command=f"echo 'job-{job_id}'",
2026-02-20T05:16:23.170952331Z runner_image="alpine:latest"
2026-02-20T05:16:23.170974468Z )
2026-02-20T05:16:23.170979225Z
2026-02-20T05:16:23.170992441Z job_path = prepare_job_directory(config)
2026-02-20T05:16:23.170996965Z
2026-02-20T05:16:23.171003725Z # Create a unique file for this job
2026-02-20T05:16:23.171883267Z test_file = job_path / f"job-{job_id}.txt"
2026-02-20T05:16:23.171895007Z test_file.write_text(f"Data for job {job_id}")
2026-02-20T05:16:23.17190058Z
2026-02-20T05:16:23.171909677Z # Simulate some work
2026-02-20T05:16:23.171916637Z time.sleep(0.1)
2026-02-20T05:16:23.17192146Z
2026-02-20T05:16:23.17193024Z # Verify the file still exists and has correct content
2026-02-20T05:16:23.171961865Z assert test_file.exists()
2026-02-20T05:16:23.171970368Z assert test_file.read_text() == f"Data for job {job_id}"
2026-02-20T05:16:23.171975841Z
2026-02-20T05:16:23.172748239Z # Check no files from other jobs exist
2026-02-20T05:16:23.172776383Z other_files = list(job_path.glob("job-*.txt"))
2026-02-20T05:16:23.172784339Z assert len(other_files) == 1
2026-02-20T05:16:23.172798539Z assert other_files[0].name == f"job-{job_id}.txt"
2026-02-20T05:16:23.172805026Z
2026-02-20T05:16:23.173393468Z results[job_id] = True
2026-02-20T05:16:23.173400722Z
2026-02-20T05:16:23.173407288Z except Exception as e:
2026-02-20T05:16:23.173414265Z errors[job_id] = str(e)
2026-02-20T05:16:23.173422069Z finally:
2026-02-20T05:16:23.173428519Z os.chdir(original_cwd)
2026-02-20T05:16:23.173433395Z
2026-02-20T05:16:23.173457306Z # Create temporary directories for each job
2026-02-20T05:16:23.17346383Z temp_dirs = []
2026-02-20T05:16:23.17347016Z threads = []
2026-02-20T05:16:23.173474476Z
2026-02-20T05:16:23.17396456Z try:
2026-02-20T05:16:23.173974834Z # Start multiple jobs concurrently
2026-02-20T05:16:23.17398225Z for i in range(5):
2026-02-20T05:16:23.173995397Z temp_dir = tempfile.mkdtemp(prefix=f"job-{i}-")
2026-02-20T05:16:23.174001954Z temp_dirs.append(temp_dir)
2026-02-20T05:16:23.174468143Z
2026-02-20T05:16:23.17447807Z thread = threading.Thread(
2026-02-20T05:16:23.174484326Z target=run_job,
2026-02-20T05:16:23.17449082Z args=(str(i), temp_dir)
2026-02-20T05:16:23.17449781Z )
2026-02-20T05:16:23.174520086Z thread.start()
2026-02-20T05:16:23.174526686Z threads.append(thread)
2026-02-20T05:16:23.175812628Z
2026-02-20T05:16:23.175824325Z # Wait for all jobs to complete
2026-02-20T05:16:23.175849339Z for thread in threads:
2026-02-20T05:16:23.175856249Z thread.join(timeout=5)
2026-02-20T05:16:23.175938206Z
2026-02-20T05:16:23.175947617Z # Verify all jobs succeeded
2026-02-20T05:16:23.175972334Z> assert len(errors) == 0, f"Jobs failed: {errors}"
2026-02-20T05:16:23.175996571ZE AssertionError: Jobs failed: {'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])", '2': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])", '4': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])", '1': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])", '3': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])"}
2026-02-20T05:16:23.176428592ZE assert 5 == 0
2026-02-20T05:16:23.176441189ZE + where 5 = len({'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-4.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job...xPath('/job/job-2.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt'), PosixPath('/job/job-1.txt')])", ...})
2026-02-20T05:16:23.176445809Z
2026-02-20T05:16:23.176470822Z/workspace/runnerlib/tests/test_job_isolation.py:138: AssertionError
2026-02-20T05:16:23.177376658Z_______________ TestJobIsolation.test_container_mount_isolation ________________
2026-02-20T05:16:23.177384618Z
2026-02-20T05:16:23.177394011Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fdefc5f76f0>
2026-02-20T05:16:23.177403471Zmock_popen = <MagicMock name='Popen' id='140595688270416'>
2026-02-20T05:16:23.177407441Z
2026-02-20T05:16:23.177413274Z @patch('subprocess.Popen')
2026-02-20T05:16:23.177420571Z def test_container_mount_isolation(self, mock_popen):
2026-02-20T05:16:23.177430151Z """Test that containers mount only their job's directory."""
2026-02-20T05:16:23.177436368Z # Mock the Popen object with proper behavior
2026-02-20T05:16:23.177442212Z mock_process = MagicMock()
2026-02-20T05:16:23.177669368Z mock_process.poll.side_effect = [None, None, 0] # Running, running, then finished
2026-02-20T05:16:23.177677161Z mock_process.returncode = 0
2026-02-20T05:16:23.177685888Z mock_process.stdout.readline.return_value = '' # No output (text mode)
2026-02-20T05:16:23.177692915Z mock_process.stderr.readline.return_value = '' # No errors
2026-02-20T05:16:23.177702975Z mock_process.communicate.return_value = ('', '') # Empty re[REDACTED]ing output
2026-02-20T05:16:23.177710378Z mock_popen.return_value = mock_process
2026-02-20T05:16:23.177714805Z
2026-02-20T05:16:23.177721688Z with tempfile.TemporaryDirectory() as temp_dir:
2026-02-20T05:16:23.178221969Z # Save original cwd if possible
2026-02-20T05:16:23.178230789Z try:
2026-02-20T05:16:23.17823724Z original_cwd = os.getcwd()
2026-02-20T05:16:23.178241497Z except FileNotFoundError:
2026-02-20T05:16:23.178246997Z # If current dir doesn't exist, use temp dir as fallback
2026-02-20T05:16:23.178299573Z original_cwd = temp_dir
2026-02-20T05:16:23.178302847Z
2026-02-20T05:16:23.178894623Z try:
2026-02-20T05:16:23.178899923Z os.chdir(temp_dir)
2026-02-20T05:16:23.178902583Z
2026-02-20T05:16:23.178906143Z config = RunnerConfig(
2026-02-20T05:16:23.178909993Z code_dir="/job/src",
2026-02-20T05:16:23.181024167Z job_dir="/job/src",
2026-02-20T05:16:23.18102988Z job_command="echo test",
2026-02-20T05:16:23.181034494Z runner_image="alpine:latest"
2026-02-20T05:16:23.18104023Z )
2026-02-20T05:16:23.18104297Z
2026-02-20T05:16:23.181046667Z # Prepare job directory
2026-02-20T05:16:23.181050707Z job_path = prepare_job_directory(config)
2026-02-20T05:16:23.18105352Z
2026-02-20T05:16:23.181056944Z # Create a test file
2026-02-20T05:16:23.18106094Z test_file = job_path / "test.txt"
2026-02-20T05:16:23.18106465Z test_file.write_text("test data")
2026-02-20T05:16:23.181066774Z
2026-02-20T05:16:23.181070075Z # Run container
2026-02-20T05:16:23.181074995Z> run_container(config)
2026-02-20T05:16:23.181077285Z
2026-02-20T05:16:23.181081202Z/workspace/runnerlib/tests/test_job_isolation.py:257:
2026-02-20T05:16:23.181085162Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.181087115Z
2026-02-20T05:16:23.181095245Zconfig = 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-20T05:16:23.181098652Zadditional_args = None
2026-02-20T05:16:23.181102432Z
2026-02-20T05:16:23.181310044Z def run_container(
2026-02-20T05:16:23.181318211Z config: RunnerConfig,
2026-02-20T05:16:23.181325158Z additional_args: Optional[List[str]] = None
2026-02-20T05:16:23.181330338Z ) -> int:
2026-02-20T05:16:23.181687117Z """Run the job container using docker with full configuration support.
2026-02-20T05:16:23.181693607Z
2026-02-20T05:16:23.181700947Z Args:
2026-02-20T05:16:23.182282827Z config: Runner configuration
2026-02-20T05:16:23.182292353Z additional_args: Additional arguments to pass to the job command
2026-02-20T05:16:23.182297007Z
2026-02-20T05:16:23.18230298Z Returns:
2026-02-20T05:16:23.182310003Z Exit code of the container process
2026-02-20T05:16:23.182503715Z
2026-02-20T05:16:23.182512982Z Raises:
2026-02-20T05:16:23.182523139Z ValueError: If configuration is invalid
2026-02-20T05:16:23.184547539Z FileNotFoundError: If docker is not available
2026-02-20T05:16:23.184555362Z """
2026-02-20T05:16:23.184561832Z # Create plugin context for the execution
2026-02-20T05:16:23.184567849Z plugin_context = PluginContext(
2026-02-20T05:16:23.184573256Z config=config,
2026-02-20T05:16:23.184578842Z phase=PluginPhase.PRE_SOURCE_PREP,
2026-02-20T05:16:23.184584226Z metadata={}
2026-02-20T05:16:23.184589497Z )
2026-02-20T05:16:23.184593743Z
2026-02-20T05:16:23.184599957Z try:
2026-02-20T05:16:23.184605837Z # Execute pre-source-prep plugins
2026-02-20T05:16:23.184616764Z plugin_manager.execute_phase(PluginPhase.PRE_SOURCE_PREP, plugin_context)
2026-02-20T05:16:23.18462227Z
2026-02-20T05:16:23.18462856Z # Basic validation is handled by CLI layer
2026-02-20T05:16:23.185547126Z
2026-02-20T05:16:23.185558273Z # Check if docker is available
2026-02-20T05:16:23.185564476Z if not shutil.which("docker"):
2026-02-20T05:16:23.185571333Z logger.error("Docker is not available in PATH")
2026-02-20T05:16:23.185578183Z> raise FileNotFoundError("docker is not available in PATH")
2026-02-20T05:16:23.18558393ZE FileNotFoundError: docker is not available in PATH
2026-02-20T05:16:23.18558765Z
2026-02-20T05:16:23.18559329Z/workspace/runnerlib/src/container.py:114: FileNotFoundError
2026-02-20T05:16:23.185600536Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.185608566Z2026-02-20T05:15:15.829687+00:00 [ERROR] [runnerlib] Docker is not available in PATH
2026-02-20T05:16:23.18561571Z___________ TestSourcePreparation.test_no_source_preparation_default ___________
2026-02-20T05:16:23.1856189Z
2026-02-20T05:16:23.185625924Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefc0b5450>
2026-02-20T05:16:23.185629324Z
2026-02-20T05:16:23.185640217Z def test_no_source_preparation_default(self):
2026-02-20T05:16:23.186791965Z """Test job with no source preparation (default - source_type not set)."""
2026-02-20T05:16:23.186801442Z # Configure without specifying source_type
2026-02-20T05:16:23.186806408Z config = get_config(job_command="echo 'hello'")
2026-02-20T05:16:23.186809605Z
2026-02-20T05:16:23.186813645Z # Prepare source should return None
2026-02-20T05:16:23.186817745Z result = prepare_source(config)
2026-02-20T05:16:23.190964966Z> assert result is None
2026-02-20T05:16:23.190974379ZE AssertionError: assert PosixPath('/job/src') is None
2026-02-20T05:16:23.190982916Z
2026-02-20T05:16:23.193564069Z/workspace/runnerlib/tests/test_source_preparation.py:89: AssertionError
2026-02-20T05:16:23.193573685Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.193585769Z2026-02-20T05:15:28.205549+00:00 Cloning git repository: [REDACTED]
2026-02-20T05:16:23.193594165Z2026-02-20T05:15:51.666313+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.193600429Z2026-02-20T05:15:52.185097+00:00 Repository checked out to: /job/src
2026-02-20T05:16:23.193606379Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.19361957Z2026-02-20T05:15:28.205323+00:00 [INFO] [runnerlib] Preparing source type=git url=[REDACTED] ref=[REDACTED]
2026-02-20T05:16:23.193634073Z2026-02-20T05:15:28.205485+00:00 [INFO] [runnerlib] Preparing git source url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED] target=/job/src
2026-02-20T05:16:23.193641073Z2026-02-20T05:15:52.184945+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-20T05:16:23.193647433Z______________ TestSourcePreparation.test_git_source_preparation _______________
2026-02-20T05:16:23.19365244Z
2026-02-20T05:16:23.193660233Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefbf0bbb0>
2026-02-20T05:16:23.193664236Z
2026-02-20T05:16:23.193671056Z def test_git_source_preparation(self):
2026-02-20T05:16:23.19367672Z """Test git source preparation."""
2026-02-20T05:16:23.19368259Z # Create a test git repository
2026-02-20T05:16:23.193689736Z test_repo_dir = Path(self.temp_dir) / "test_repo"
2026-02-20T05:16:23.193695423Z test_repo_dir.mkdir()
2026-02-20T05:16:23.193701193Z repo = Repo.init(test_repo_dir)
2026-02-20T05:16:23.193705423Z
2026-02-20T05:16:23.194418674Z # Add a test file
2026-02-20T05:16:23.194426364Z test_file = test_repo_dir / "test.txt"
2026-02-20T05:16:23.194431138Z test_file.write_text("test content")
2026-02-20T05:16:23.194434964Z repo.index.add([str(test_file)])
2026-02-20T05:16:23.194439634Z repo.index.commit("Initial commit")
2026-02-20T05:16:23.194442538Z
2026-02-20T05:16:23.195211655Z # Configure with git source
2026-02-20T05:16:23.195218115Z config = get_config(
2026-02-20T05:16:23.195222799Z job_command="cat /job/src/test.txt",
2026-02-20T05:16:23.195226789Z source_type="git",
2026-02-20T05:16:23.195230772Z source_url=str(test_repo_dir),
2026-02-20T05:16:23.198628175Z source_ref="[REDACTED]"
2026-02-20T05:16:23.198634582Z )
2026-02-20T05:16:23.198637639Z
2026-02-20T05:16:23.198642085Z # Prepare source
2026-02-20T05:16:23.200629309Z> result = prepare_source(config)
2026-02-20T05:16:23.201234421Z
2026-02-20T05:16:23.201245378Z/workspace/runnerlib/tests/test_source_preparation.py:113:
2026-02-20T05:16:23.201250271Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.204201778Z/workspace/runnerlib/src/source_prep.py:563: in prepare_source
2026-02-20T05:16:23.204211435Z return _prepare_git_source(config.source_url, config.source_ref, target_path)
2026-02-20T05:16:23.204216975Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-20T05:16:23.204221031Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-20T05:16:23.204226021Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.204228778Z
2026-02-20T05:16:23.204234605Zrepo = <git.repo.base.Repo '/job/src/.git'>, source_ref = '[REDACTED]'
2026-02-20T05:16:23.204236921Z
2026-02-20T05:16:23.204241995Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-20T05:16:23.204246905Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-20T05:16:23.204249481Z
2026-02-20T05:16:23.204254468Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-20T05:16:23.204258331Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-20T05:16:23.204262871Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-20T05:16:23.204267165Z checkout first, then falls back to fetching the specific PR ref.
2026-02-20T05:16:23.204269881Z
2026-02-20T05:16:23.204273735Z Args:
2026-02-20T05:16:23.204277831Z repo: GitPython Repo instance (already cloned)
2026-02-20T05:16:23.204286031Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-20T05:16:23.204288661Z
2026-02-20T05:16:23.204292131Z Raises:
2026-02-20T05:16:23.204295945Z GitCommandError: If all checkout attempts fail
2026-02-20T05:16:23.204298885Z """
2026-02-20T05:16:23.204304441Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-20T05:16:23.204308228Z try:
2026-02-20T05:16:23.204311808Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.204317536Z return
2026-02-20T05:16:23.204385506Z except GitCommandError:
2026-02-20T05:16:23.204392306Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-20T05:16:23.204394859Z
2026-02-20T05:16:23.204400119Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-20T05:16:23.204404699Z try:
2026-02-20T05:16:23.204408736Z repo.git.fetch("origin", source_ref)
2026-02-20T05:16:23.204412666Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.204417162Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-20T05:16:23.204420966Z return
2026-02-20T05:16:23.204424346Z except GitCommandError:
2026-02-20T05:16:23.204428593Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-20T05:16:23.204430877Z
2026-02-20T05:16:23.20443506Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-20T05:16:23.204438607Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-20T05:16:23.20446555Z if pr_number:
2026-02-20T05:16:23.204469833Z # GitHub: refs/pull/<N>/head
2026-02-20T05:16:23.204473213Z pr_refs = [
2026-02-20T05:16:23.204477077Z f"refs/pull/{pr_number}/head",
2026-02-20T05:16:23.204481744Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-20T05:16:23.204487904Z ]
2026-02-20T05:16:23.205440633Z for pr_ref in pr_refs:
2026-02-20T05:16:23.2054507Z try:
2026-02-20T05:16:23.205459743Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-20T05:16:23.205469054Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-20T05:16:23.205476101Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.205483404Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-20T05:16:23.205489264Z return
2026-02-20T05:16:23.205495281Z except GitCommandError:
2026-02-20T05:16:23.205501704Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-20T05:16:23.205507401Z
2026-02-20T05:16:23.205512401Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-20T05:16:23.205516741Z try:
2026-02-20T05:16:23.206341935Z log_stdout("Fetching all remote refs...")
2026-02-20T05:16:23.206352992Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-20T05:16:23.206359842Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.206368849Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-20T05:16:23.206375139Z return
2026-02-20T05:16:23.20638072Z except GitCommandError:
2026-02-20T05:16:23.20638719Z pass
2026-02-20T05:16:23.20639142Z
2026-02-20T05:16:23.2067986Z # Nothing worked — raise with a clear message
2026-02-20T05:16:23.2068053Z> raise GitCommandError(
2026-02-20T05:16:23.20681153Z f"git checkout {source_ref}",
2026-02-20T05:16:23.20681777Z 128,
2026-02-20T05:16:23.206826043Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-20T05:16:23.206831636Z )
2026-02-20T05:16:23.208476197ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.208484433ZE cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.20849102ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.20849376Z
2026-02-20T05:16:23.208498447Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-20T05:16:23.208503793Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.208510477Z2026-02-20T05:15:52.245408+00:00 Cloning git repository: /tmp/tmpav4t8kzv/test_repo
2026-02-20T05:16:23.208515223Z2026-02-20T05:15:52.365983+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.20852033Z2026-02-20T05:15:52.384194+00:00 Fetching PR ref: refs/pull/47/head
2026-02-20T05:16:23.208524903Z2026-02-20T05:15:52.395929+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-20T05:16:23.20852893Z2026-02-20T05:15:52.409631+00:00 Fetching all remote refs...
2026-02-20T05:16:23.208532857Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.208618451Z2026-02-20T05:15:52.245134+00:00 [INFO] [runnerlib] Preparing source type=git url=/tmp/tmpav4t8kzv/test_repo ref=[REDACTED]
2026-02-20T05:16:23.208629148Z2026-02-20T05:15:52.245315+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpav4t8kzv/test_repo ref=[REDACTED] target=/job/src
2026-02-20T05:16:23.208637251Z2026-02-20T05:15:52.439851+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpav4t8kzv/test_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.208642081Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.208648138Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.208654121Z2026-02-20T05:15:52.439981+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.208658411Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.208663691Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.208670898Z______________ TestSourcePreparation.test_dual_source_preparation ______________
2026-02-20T05:16:23.208673541Z
2026-02-20T05:16:23.209547697Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefc095130>
2026-02-20T05:16:23.209552647Z
2026-02-20T05:16:23.20955777Z def test_dual_source_preparation(self):
2026-02-20T05:16:23.209562507Z """Test preparation of both source and ci_source."""
2026-02-20T05:16:23.209566683Z # Create source repo (untrusted code)
2026-02-20T05:16:23.209570947Z source_repo_dir = Path(self.temp_dir) / "source_repo"
2026-02-20T05:16:23.209575317Z source_repo_dir.mkdir()
2026-02-20T05:16:23.210109856Z source_repo = Repo.init(source_repo_dir)
2026-02-20T05:16:23.210143843Z (source_repo_dir / "app.py").write_text("print('hello from PR')")
2026-02-20T05:16:23.210150534Z source_repo.index.add(["app.py"])
2026-02-20T05:16:23.210156907Z source_repo.index.commit("PR commit")
2026-02-20T05:16:23.21016144Z
2026-02-20T05:16:23.210168547Z # Create CI repo (trusted code)
2026-02-20T05:16:23.210931454Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-20T05:16:23.210939267Z ci_repo_dir.mkdir()
2026-02-20T05:16:23.210946232Z ci_repo = Repo.init(ci_repo_dir)
2026-02-20T05:16:23.210953892Z (ci_repo_dir / "pipeline.py").write_text("print('running tests')")
2026-02-20T05:16:23.210960248Z ci_repo.index.add(["pipeline.py"])
2026-02-20T05:16:23.210965418Z ci_repo.index.commit("CI commit")
2026-02-20T05:16:23.210970082Z
2026-02-20T05:16:23.210977178Z # Configure with both sources
2026-02-20T05:16:23.210983498Z config = get_config(
2026-02-20T05:16:23.210990818Z job_command="python /job/ci/pipeline.py",
2026-02-20T05:16:23.211000615Z source_type="git",
2026-02-20T05:16:23.211006288Z source_url=str(source_repo_dir),
2026-02-20T05:16:23.211013795Z source_ref="[REDACTED]",
2026-02-20T05:16:23.213029405Z ci_source_type="git",
2026-02-20T05:16:23.213036458Z ci_source_url=str(ci_repo_dir),
2026-02-20T05:16:23.213044812Z ci_source_ref="[REDACTED]"
2026-02-20T05:16:23.213048732Z )
2026-02-20T05:16:23.213051628Z
2026-02-20T05:16:23.213056055Z # Prepare CI source first (as the CLI does)
2026-02-20T05:16:23.213059682Z> ci_result = prepare_ci_source(config)
2026-02-20T05:16:23.213062245Z
2026-02-20T05:16:23.213067145Z/workspace/runnerlib/tests/test_source_preparation.py:170:
2026-02-20T05:16:23.213071038Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.213075705Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-20T05:16:23.213080538Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-20T05:16:23.213085318Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-20T05:16:23.213088952Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-20T05:16:23.213095138Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.213097288Z
2026-02-20T05:16:23.213102858Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-20T05:16:23.213104995Z
2026-02-20T05:16:23.213110136Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-20T05:16:23.213137573Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-20T05:16:23.213140339Z
2026-02-20T05:16:23.213147806Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-20T05:16:23.213153436Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-20T05:16:23.213825616Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-20T05:16:23.213833766Z checkout first, then falls back to fetching the specific PR ref.
2026-02-20T05:16:23.214973988Z
2026-02-20T05:16:23.215591357Z Args:
2026-02-20T05:16:23.21560263Z repo: GitPython Repo instance (already cloned)
2026-02-20T05:16:23.216713558Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-20T05:16:23.216719578Z
2026-02-20T05:16:23.216724292Z Raises:
2026-02-20T05:16:23.216729255Z GitCommandError: If all checkout attempts fail
2026-02-20T05:16:23.216733132Z """
2026-02-20T05:16:23.216739352Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-20T05:16:23.216743925Z try:
2026-02-20T05:16:23.216748172Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.216751772Z return
2026-02-20T05:16:23.216755432Z except GitCommandError:
2026-02-20T05:16:23.216761229Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-20T05:16:23.216763845Z
2026-02-20T05:16:23.216768699Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-20T05:16:23.216771975Z try:
2026-02-20T05:16:23.216776062Z repo.git.fetch("origin", source_ref)
2026-02-20T05:16:23.216779896Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.216788866Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-20T05:16:23.216792166Z return
2026-02-20T05:16:23.216795673Z except GitCommandError:
2026-02-20T05:16:23.216800253Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-20T05:16:23.216802706Z
2026-02-20T05:16:23.21680677Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-20T05:16:23.21681255Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-20T05:16:23.216816036Z if pr_number:
2026-02-20T05:16:23.21682032Z # GitHub: refs/pull/<N>/head
2026-02-20T05:16:23.21682376Z pr_refs = [
2026-02-20T05:16:23.216828113Z f"refs/pull/{pr_number}/head",
2026-02-20T05:16:23.21683214Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-20T05:16:23.2168359Z ]
2026-02-20T05:16:23.216846853Z for pr_ref in pr_refs:
2026-02-20T05:16:23.21685047Z try:
2026-02-20T05:16:23.216854313Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-20T05:16:23.216859893Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-20T05:16:23.216863763Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.216870266Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-20T05:16:23.21688054Z return
2026-02-20T05:16:23.216884536Z except GitCommandError:
2026-02-20T05:16:23.217991594Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-20T05:16:23.217997154Z
2026-02-20T05:16:23.218003154Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-20T05:16:23.218008231Z try:
2026-02-20T05:16:23.218012861Z log_stdout("Fetching all remote refs...")
2026-02-20T05:16:23.218018064Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-20T05:16:23.218022667Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.218027268Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-20T05:16:23.219521133Z return
2026-02-20T05:16:23.21952815Z except GitCommandError:
2026-02-20T05:16:23.219532103Z pass
2026-02-20T05:16:23.219535253Z
2026-02-20T05:16:23.219540863Z # Nothing worked — raise with a clear message
2026-02-20T05:16:23.21954449Z> raise GitCommandError(
2026-02-20T05:16:23.219548873Z f"git checkout {source_ref}",
2026-02-20T05:16:23.219552843Z 128,
2026-02-20T05:16:23.219558537Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-20T05:16:23.219562047Z )
2026-02-20T05:16:23.219567377ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.21957272ZE cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.219578507ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.219580627Z
2026-02-20T05:16:23.219584703Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-20T05:16:23.219589753Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.219595363Z2026-02-20T05:15:52.606859+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-20T05:16:23.21960454Z2026-02-20T05:15:52.607053+00:00 Cloning git repository: /tmp/tmpsyds7bff/ci_repo
2026-02-20T05:16:23.219608963Z2026-02-20T05:15:52.646269+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.219612904Z2026-02-20T05:15:52.667430+00:00 Fetching PR ref: refs/pull/47/head
2026-02-20T05:16:23.219618074Z2026-02-20T05:15:52.681388+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-20T05:16:23.219622124Z2026-02-20T05:15:52.694981+00:00 Fetching all remote refs...
2026-02-20T05:16:23.219628194Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.219635724Z2026-02-20T05:15:52.606713+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmpsyds7bff/ci_repo ref=[REDACTED]
2026-02-20T05:16:23.219642928Z2026-02-20T05:15:52.606976+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpsyds7bff/ci_repo ref=[REDACTED] target=/job/ci
2026-02-20T05:16:23.220082065Z2026-02-20T05:15:52.728463+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpsyds7bff/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.220088639Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.220094039Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.220101219Z2026-02-20T05:15:52.728609+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.220105402Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.220110695Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.223142258Z__________________ TestSourcePreparation.test_ci_source_only ___________________
2026-02-20T05:16:23.223149172Z
2026-02-20T05:16:23.223157285Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefc6dde10>
2026-02-20T05:16:23.223159555Z
2026-02-20T05:16:23.223163786Z def test_ci_source_only(self):
2026-02-20T05:16:23.223169166Z """Test preparation of CI source without regular source."""
2026-02-20T05:16:23.223172993Z # Create CI repo
2026-02-20T05:16:23.223177089Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-20T05:16:23.223180266Z ci_repo_dir.mkdir()
2026-02-20T05:16:23.223184566Z ci_repo = Repo.init(ci_repo_dir)
2026-02-20T05:16:23.223188953Z (ci_repo_dir / "deploy.sh").write_text("#!/bin/bash\necho deploying")
2026-02-20T05:16:23.223192196Z ci_repo.index.add(["deploy.sh"])
2026-02-20T05:16:23.223195649Z ci_repo.index.commit("CI commit")
2026-02-20T05:16:23.223198216Z
2026-02-20T05:16:23.223201896Z # Configure with only CI source
2026-02-20T05:16:23.223205289Z config = get_config(
2026-02-20T05:16:23.223209359Z job_command="bash /job/ci/deploy.sh",
2026-02-20T05:16:23.223212599Z ci_source_type="git",
2026-02-20T05:16:23.223215956Z ci_source_url=str(ci_repo_dir),
2026-02-20T05:16:23.223220249Z ci_source_ref="[REDACTED]"
2026-02-20T05:16:23.223223463Z )
2026-02-20T05:16:23.223225649Z
2026-02-20T05:16:23.223228689Z # Prepare CI source
2026-02-20T05:16:23.223231966Z> ci_result = prepare_ci_source(config)
2026-02-20T05:16:23.223233773Z
2026-02-20T05:16:23.223237373Z/workspace/runnerlib/tests/test_source_preparation.py:206:
2026-02-20T05:16:23.223241119Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.223245003Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-20T05:16:23.223249813Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-20T05:16:23.223260326Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-20T05:16:23.223263739Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-20T05:16:23.223266823Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.223269049Z
2026-02-20T05:16:23.223274096Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-20T05:16:23.223276016Z
2026-02-20T05:16:23.223280414Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-20T05:16:23.22328477Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-20T05:16:23.2232871Z
2026-02-20T05:16:23.223291464Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-20T05:16:23.224200199Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-20T05:16:23.224213693Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-20T05:16:23.224788782Z checkout first, then falls back to fetching the specific PR ref.
2026-02-20T05:16:23.224795292Z
2026-02-20T05:16:23.224801729Z Args:
2026-02-20T05:16:23.224809142Z repo: GitPython Repo instance (already cloned)
2026-02-20T05:16:23.224815745Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-20T05:16:23.225759541Z
2026-02-20T05:16:23.225768774Z Raises:
2026-02-20T05:16:23.225779621Z GitCommandError: If all checkout attempts fail
2026-02-20T05:16:23.225785998Z """
2026-02-20T05:16:23.225794745Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-20T05:16:23.225802075Z try:
2026-02-20T05:16:23.225808452Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.225813782Z return
2026-02-20T05:16:23.225819532Z except GitCommandError:
2026-02-20T05:16:23.225827635Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-20T05:16:23.225831645Z
2026-02-20T05:16:23.225838889Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-20T05:16:23.225843022Z try:
2026-02-20T05:16:23.225846995Z repo.git.fetch("origin", source_ref)
2026-02-20T05:16:23.225850699Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.225856415Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-20T05:16:23.225859985Z return
2026-02-20T05:16:23.225863619Z except GitCommandError:
2026-02-20T05:16:23.225867879Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-20T05:16:23.225870875Z
2026-02-20T05:16:23.225877019Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-20T05:16:23.226697051Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-20T05:16:23.226701991Z if pr_number:
2026-02-20T05:16:23.226706174Z # GitHub: refs/pull/<N>/head
2026-02-20T05:16:23.226709854Z pr_refs = [
2026-02-20T05:16:23.226713748Z f"refs/pull/{pr_number}/head",
2026-02-20T05:16:23.226721368Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-20T05:16:23.226725291Z ]
2026-02-20T05:16:23.229371204Z for pr_ref in pr_refs:
2026-02-20T05:16:23.229377054Z try:
2026-02-20T05:16:23.22938095Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-20T05:16:23.22938657Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-20T05:16:23.229390484Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.229395334Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-20T05:16:23.22940281Z return
2026-02-20T05:16:23.229406367Z except GitCommandError:
2026-02-20T05:16:23.229410577Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-20T05:16:23.229413074Z
2026-02-20T05:16:23.229417858Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-20T05:16:23.229421838Z try:
2026-02-20T05:16:23.229427665Z log_stdout("Fetching all remote refs...")
2026-02-20T05:16:23.229432405Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-20T05:16:23.229435945Z repo.git.checkout(source_ref)
2026-02-20T05:16:23.229440481Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-20T05:16:23.229443408Z return
2026-02-20T05:16:23.229447411Z except GitCommandError:
2026-02-20T05:16:23.229450418Z pass
2026-02-20T05:16:23.229452541Z
2026-02-20T05:16:23.229456555Z # Nothing worked — raise with a clear message
2026-02-20T05:16:23.229459391Z> raise GitCommandError(
2026-02-20T05:16:23.229462425Z f"git checkout {source_ref}",
2026-02-20T05:16:23.231511032Z 128,
2026-02-20T05:16:23.231520329Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-20T05:16:23.231524242Z )
2026-02-20T05:16:23.231529542ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.231534626ZE cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.231540062ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.231542462Z
2026-02-20T05:16:23.231546886Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-20T05:16:23.231551616Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.231557016Z2026-02-20T05:15:52.859203+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-20T05:16:23.231562939Z2026-02-20T05:15:52.859418+00:00 Cloning git repository: /tmp/tmpk0eo8pdc/ci_repo
2026-02-20T05:16:23.231567662Z2026-02-20T05:15:52.906261+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.231571367Z2026-02-20T05:15:52.926833+00:00 Fetching PR ref: refs/pull/47/head
2026-02-20T05:16:23.23157617Z2026-02-20T05:15:52.942485+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-20T05:16:23.231580213Z2026-02-20T05:15:52.957264+00:00 Fetching all remote refs...
2026-02-20T05:16:23.231584097Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.231593523Z2026-02-20T05:15:52.859061+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmpk0eo8pdc/ci_repo ref=[REDACTED]
2026-02-20T05:16:23.235798271Z2026-02-20T05:15:52.859323+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpk0eo8pdc/ci_repo ref=[REDACTED] target=/job/ci
2026-02-20T05:16:23.235808917Z2026-02-20T05:15:52.990069+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpk0eo8pdc/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.235814075Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.235819749Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.235828665Z2026-02-20T05:15:52.990248+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-20T05:16:23.235834859Z cmdline: git checkout [REDACTED]
2026-02-20T05:16:23.235840362Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-20T05:16:23.235845529Z______________ TestSourcePreparation.test_git_source_missing_url _______________
2026-02-20T05:16:23.235848242Z
2026-02-20T05:16:23.235854445Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefc0a4650>
2026-02-20T05:16:23.235856772Z
2026-02-20T05:16:23.235860892Z def test_git_source_missing_url(self):
2026-02-20T05:16:23.235865829Z """Test that git source without URL raises ValueError."""
2026-02-20T05:16:23.235869899Z config = get_config(
2026-02-20T05:16:23.235873665Z job_command="echo 'test'",
2026-02-20T05:16:23.235877295Z source_type="git"
2026-02-20T05:16:23.235881239Z # source_url not provided
2026-02-20T05:16:23.235885079Z )
2026-02-20T05:16:23.235887645Z
2026-02-20T05:16:23.235892525Z> with pytest.raises(ValueError, match="source_url is required"):
2026-02-20T05:16:23.235896475ZE Failed: DID NOT RAISE <class 'ValueError'>
2026-02-20T05:16:23.235898459Z
2026-02-20T05:16:23.235903062Z/workspace/runnerlib/tests/test_source_preparation.py:233: Failed
2026-02-20T05:16:23.235907605Z----------------------------- Captured stdout call -----------------------------
2026-02-20T05:16:23.235914935Z2026-02-20T05:15:53.064419+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-20T05:16:23.235919732Z2026-02-20T05:16:20.749706+00:00 Checking out ref: [REDACTED]
2026-02-20T05:16:23.235923355Z2026-02-20T05:16:21.609637+00:00 Repository checked out to: /job/src
2026-02-20T05:16:23.23592731Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.235934656Z2026-02-20T05:15:53.064179+00:00 [INFO] [runnerlib] Preparing source type=git url=[REDACTED] ref=[REDACTED]
2026-02-20T05:16:23.235942083Z2026-02-20T05:15:53.064323+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/job/src
2026-02-20T05:16:23.235946813Z2026-02-20T05:16:21.609494+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-20T05:16:23.235953056Z______________ TestSourcePreparation.test_copy_source_missing_url ______________
2026-02-20T05:16:23.235955286Z
2026-02-20T05:16:23.235959786Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fdefc0a4750>
2026-02-20T05:16:23.235961606Z
2026-02-20T05:16:23.235965293Z def test_copy_source_missing_url(self):
2026-02-20T05:16:23.235968846Z """Test that copy source without URL raises ValueError."""
2026-02-20T05:16:23.235971993Z config = get_config(
2026-02-20T05:16:23.23597508Z job_command="echo 'test'",
2026-02-20T05:16:23.235978826Z source_type="copy"
2026-02-20T05:16:23.2359823Z # source_url not provided
2026-02-20T05:16:23.235985813Z )
2026-02-20T05:16:23.235988066Z
2026-02-20T05:16:23.235992416Z with pytest.raises(ValueError, match="source_url is required"):
2026-02-20T05:16:23.235995753Z> prepare_source(config)
2026-02-20T05:16:23.239367627Z
2026-02-20T05:16:23.23937878Z/workspace/runnerlib/tests/test_source_preparation.py:245:
2026-02-20T05:16:23.239383687Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.23938792Z/workspace/runnerlib/src/source_prep.py:568: in prepare_source
2026-02-20T05:16:23.239392427Z return _prepare_copy_source(config.source_url, target_path)
2026-02-20T05:16:23.239395987Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-20T05:16:23.239398377Z
2026-02-20T05:16:23.239404777Zsource_url = 'https://[REDACTED].com/[REDACTED].git'
2026-02-20T05:16:23.239408804Ztarget_path = PosixPath('/job/src')
2026-02-20T05:16:23.239411467Z
2026-02-20T05:16:23.23941693Z def _prepare_copy_source(source_url: str, target_path: Path) -> Path:
2026-02-20T05:16:23.239429787Z """Prepare source code by copying from a local directory.
2026-02-20T05:16:23.239995812Z
2026-02-20T05:16:23.240003129Z Args:
2026-02-20T05:16:23.240007572Z source_url: Path to source directory
2026-02-20T05:16:23.24001208Z target_path: Where to copy the directory
2026-02-20T05:16:23.2400147Z
2026-02-20T05:16:23.240018137Z Returns:
2026-02-20T05:16:23.240029343Z Path to the copied directory
2026-02-20T05:16:23.24003288Z """
2026-02-20T05:16:23.240036777Z source_path = Path(source_url).resolve()
2026-02-20T05:16:23.24004134Z
2026-02-20T05:16:23.240045143Z if not source_path.exists():
2026-02-20T05:16:23.240050977Z> raise FileNotFoundError(f"Source directory does not exist: {source_path}")
2026-02-20T05:16:23.240065527ZE FileNotFoundError: Source directory does not exist: /tmp/tmp_hsu1psf/https:/[REDACTED].com/[REDACTED].git
2026-02-20T05:16:23.24006939Z
2026-02-20T05:16:23.240076157Z/workspace/runnerlib/src/source_prep.py:433: FileNotFoundError
2026-02-20T05:16:23.240083727Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.24009508Z2026-02-20T05:16:21.629556+00:00 [INFO] [runnerlib] Preparing source type=copy url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-20T05:16:23.240100457Z_____ TestConfigValidator.test_validate_file_system_job_directory_missing ______
2026-02-20T05:16:23.24010286Z
2026-02-20T05:16:23.240107697Zself = <runnerlib.tests.test_validation.TestConfigValidator object at 0x7fdefc6d2c50>
2026-02-20T05:16:23.240109974Z
2026-02-20T05:16:23.240140961Z def test_validate_file_system_job_directory_missing(self):
2026-02-20T05:16:23.240146221Z """Test file system validation when job directory is missing."""
2026-02-20T05:16:23.240149702Z # Ensure ./job doesn't exist
2026-02-20T05:16:23.240156108Z job_path = Path("./job")
2026-02-20T05:16:23.240159965Z if job_path.exists():
2026-02-20T05:16:23.240163418Z shutil.rmtree(job_path)
2026-02-20T05:16:23.240166542Z
2026-02-20T05:16:23.240170592Z try:
2026-02-20T05:16:23.240175302Z errors, warnings = self.validator._validate_file_system(self.valid_config)
2026-02-20T05:16:23.240177822Z
2026-02-20T05:16:23.240181725Z # Should have warning about missing directory
2026-02-20T05:16:23.240254899Z> assert len(warnings) >= 1
2026-02-20T05:16:23.240258579ZE assert 0 >= 1
2026-02-20T05:16:23.240261739ZE + where 0 = len([])
2026-02-20T05:16:23.240264023Z
2026-02-20T05:16:23.24077863Z/workspace/runnerlib/tests/test_validation.py:304: AssertionError
2026-02-20T05:16:23.24104001Z_____________ TestWorkflowContext.test_flush_triggers_creates_file _____________
2026-02-20T05:16:23.241484704Z
2026-02-20T05:16:23.241493637Zself = <runnerlib.tests.test_workflow.TestWorkflowContext object at 0x7fdefc6dee00>
2026-02-20T05:16:23.24149631Z
2026-02-20T05:16:23.241501391Z def test_flush_triggers_creates_file(self):
2026-02-20T05:16:23.241505925Z """Test that flush_triggers creates a JSON file."""
2026-02-20T05:16:23.241509721Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.241824881Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.241834091Z ctx = WorkflowContext(triggers_file=str(triggers_file))
2026-02-20T05:16:23.241838541Z
2026-02-20T05:16:23.241845521Z ctx.trigger_job("test")
2026-02-20T05:16:23.241852525Z ctx.trigger_job("deploy", depends_on=["test"])
2026-02-20T05:16:23.242232192Z ctx.flush_triggers()
2026-02-20T05:16:23.242237815Z
2026-02-20T05:16:23.242244998Z> assert triggers_file.exists()
2026-02-20T05:16:23.242448074ZE AssertionError: assert False
2026-02-20T05:16:23.24245302ZE + where False = exists()
2026-02-20T05:16:23.242459097ZE + where exists = PosixPath('/tmp/tmpj9opddkc/triggers.json').exists
2026-02-20T05:16:23.242896132Z
2026-02-20T05:16:23.242907479Z/workspace/runnerlib/tests/test_workflow.py:160: AssertionError
2026-02-20T05:16:23.242914905Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.242919975Z✓ Scheduled job: test
2026-02-20T05:16:23.242927632Z✓ Scheduled job: deploy
2026-02-20T05:16:23.243317325Z✓ Wrote 2 job trigger(s) to /tmp/tmpj9opddkc/triggers.json
2026-02-20T05:16:23.243322322Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.243327835Z_________ TestWorkflowContext.test_flush_triggers_appends_to_existing __________
2026-02-20T05:16:23.243330418Z
2026-02-20T05:16:23.243745377Zself = <runnerlib.tests.test_workflow.TestWorkflowContext object at 0x7fdefc6debe0>
2026-02-20T05:16:23.243748943Z
2026-02-20T05:16:23.243752987Z def test_flush_triggers_appends_to_existing(self):
2026-02-20T05:16:23.2437579Z """Test that flush_triggers appends to existing file."""
2026-02-20T05:16:23.244059563Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.244064986Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.24406847Z
2026-02-20T05:16:23.244072553Z # Create existing file
2026-02-20T05:16:23.244551538Z existing_data = {
2026-02-20T05:16:23.244559951Z "type": "trigger_job",
2026-02-20T05:16:23.244566971Z "jobs": [{"job_name": "existing"}]
2026-02-20T05:16:23.244572978Z }
2026-02-20T05:16:23.244752273Z with open(triggers_file, 'w') as f:
2026-02-20T05:16:23.244757177Z json.dump(existing_data, f)
2026-02-20T05:16:23.245318409Z
2026-02-20T05:16:23.245328172Z # Add new triggers
2026-02-20T05:16:23.245336316Z ctx = WorkflowContext(triggers_file=str(triggers_file))
2026-02-20T05:16:23.245340316Z ctx.trigger_job("new")
2026-02-20T05:16:23.245536815Z ctx.flush_triggers()
2026-02-20T05:16:23.245542351Z
2026-02-20T05:16:23.245546901Z # Verify both exist
2026-02-20T05:16:23.246256295Z> with open(triggers_file) as f:
2026-02-20T05:16:23.246264202ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmp22uz74vm/triggers.json'
2026-02-20T05:16:23.247207938Z
2026-02-20T05:16:23.247220702Z/workspace/runnerlib/tests/test_workflow.py:189: FileNotFoundError
2026-02-20T05:16:23.247226878Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.247232095Z✓ Scheduled job: new
2026-02-20T05:16:23.247237375Z✓ Wrote 1 job trigger(s) to /tmp/tmp22uz74vm/triggers.json
2026-02-20T05:16:23.247245815Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.247251195Z___________ TestModuleLevelFunctions.test_flush_triggers_convenience ___________
2026-02-20T05:16:23.247253878Z
2026-02-20T05:16:23.247259178Zself = <runnerlib.tests.test_workflow.TestModuleLevelFunctions object at 0x7fdefbfdce90>
2026-02-20T05:16:23.247261882Z
2026-02-20T05:16:23.247265938Z def test_flush_triggers_convenience(self):
2026-02-20T05:16:23.247270435Z """Test module-level flush_triggers function."""
2026-02-20T05:16:23.247715026Z self.setUp()
2026-02-20T05:16:23.247718749Z
2026-02-20T05:16:23.247724429Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.247729286Z os.environ["TRIGGERS_FILE"] = str(Path(tmpdir) / "triggers.json")
2026-02-20T05:16:23.247734409Z
2026-02-20T05:16:23.247739086Z trigger_job("test")
2026-02-20T05:16:23.250421136Z
2026-02-20T05:16:23.25043514Z # Override the global context's triggers file
2026-02-20T05:16:23.25044021Z ctx = _get_context()
2026-02-20T05:16:23.250445233Z ctx.triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.250448023Z
2026-02-20T05:16:23.25045255Z flush_triggers()
2026-02-20T05:16:23.25045523Z
2026-02-20T05:16:23.250459333Z> assert ctx.triggers_file.exists()
2026-02-20T05:16:23.250463696ZE AssertionError: assert False
2026-02-20T05:16:23.250467296ZE + where False = exists()
2026-02-20T05:16:23.250472793ZE + where exists = PosixPath('/tmp/tmpppyu7ml_/triggers.json').exists
2026-02-20T05:16:23.250479386ZE + where PosixPath('/tmp/tmpppyu7ml_/triggers.json') = <src.workflow.WorkflowContext object at 0x7fdefc087f50>.triggers_file
2026-02-20T05:16:23.25048154Z
2026-02-20T05:16:23.25048577Z/workspace/runnerlib/tests/test_workflow.py:273: AssertionError
2026-02-20T05:16:23.250490627Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.250494883Z✓ Scheduled job: test
2026-02-20T05:16:23.250499004Z✓ Wrote 1 job trigger(s) to /tmp/tmpppyu7ml_/triggers.json
2026-02-20T05:16:23.250502828Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.250507828Z___________ TestWorkflowContextManager.test_context_manager_success ____________
2026-02-20T05:16:23.250509914Z
2026-02-20T05:16:23.250514828Zself = <runnerlib.tests.test_workflow.TestWorkflowContextManager object at 0x7fdefc0b6ad0>
2026-02-20T05:16:23.250516928Z
2026-02-20T05:16:23.250520718Z def test_context_manager_success(self):
2026-02-20T05:16:23.250524821Z """Test context manager flushes on success."""
2026-02-20T05:16:23.250529041Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.250533308Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.250535608Z
2026-02-20T05:16:23.250867971Z with workflow_context(str(triggers_file)) as ctx:
2026-02-20T05:16:23.250873218Z ctx.trigger_job("test")
2026-02-20T05:16:23.250875794Z
2026-02-20T05:16:23.250880314Z # Verify triggers were flushed
2026-02-20T05:16:23.250884094Z> assert triggers_file.exists()
2026-02-20T05:16:23.250889038ZE AssertionError: assert False
2026-02-20T05:16:23.250892824ZE + where False = exists()
2026-02-20T05:16:23.251042962ZE + where exists = PosixPath('/tmp/tmpncppchqr/triggers.json').exists
2026-02-20T05:16:23.251331872Z
2026-02-20T05:16:23.251340039Z/workspace/runnerlib/tests/test_workflow.py:408: AssertionError
2026-02-20T05:16:23.251990742Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.251997455Z✓ Scheduled job: test
2026-02-20T05:16:23.252002452Z✓ Wrote 1 job trigger(s) to /tmp/tmpncppchqr/triggers.json
2026-02-20T05:16:23.252007315Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.252016132Z_____________ TestIntegrationPatterns.test_simple_pipeline_pattern _____________
2026-02-20T05:16:23.252020262Z
2026-02-20T05:16:23.252028405Zself = <runnerlib.tests.test_workflow.TestIntegrationPatterns object at 0x7fdefc0b6fd0>
2026-02-20T05:16:23.252032569Z
2026-02-20T05:16:23.252042735Z def test_simple_pipeline_pattern(self):
2026-02-20T05:16:23.252312186Z """Test simple test-then-deploy pattern."""
2026-02-20T05:16:23.252388816Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.252394969Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.252549291Z
2026-02-20T05:16:23.252559305Z with patch.dict(os.environ, {"REACTORCIDE_GIT_BRANCH": "[REDACTED]"}):
2026-02-20T05:16:23.252563678Z with workflow_context(str(triggers_file)) as ctx:
2026-02-20T05:16:23.252567811Z # Simulate test passing
2026-02-20T05:16:23.252690856Z test_passed = True
2026-02-20T05:16:23.252952674Z
2026-02-20T05:16:23.252962754Z if test_passed and ctx.branch == "[REDACTED]":
2026-02-20T05:16:23.253652778Z ctx.trigger_job("deploy", env={"TARGET": "production"})
2026-02-20T05:16:23.253656888Z
2026-02-20T05:16:23.253662078Z # Verify deploy was triggered
2026-02-20T05:16:23.253667425Z> with open(triggers_file) as f:
2026-02-20T05:16:23.253675491ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmp9v7r37yn/triggers.json'
2026-02-20T05:16:23.253679085Z
2026-02-20T05:16:23.253686178Z/workspace/runnerlib/tests/test_workflow.py:598: FileNotFoundError
2026-02-20T05:16:23.253744179Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.253749432Z✓ Scheduled job: deploy
2026-02-20T05:16:23.253753752Z✓ Wrote 1 job trigger(s) to /tmp/tmp9v7r37yn/triggers.json
2026-02-20T05:16:23.253757379Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.254010022Z____________ TestIntegrationPatterns.test_parallel_pipeline_pattern ____________
2026-02-20T05:16:23.254015655Z
2026-02-20T05:16:23.254264082Zself = <runnerlib.tests.test_workflow.TestIntegrationPatterns object at 0x7fdefc0b7110>
2026-02-20T05:16:23.254267845Z
2026-02-20T05:16:23.254674088Z def test_parallel_pipeline_pattern(self):
2026-02-20T05:16:23.254681248Z """Test parallel jobs with dependencies."""
2026-02-20T05:16:23.254686768Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.255384648Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.255389535Z
2026-02-20T05:16:23.255393972Z with workflow_context(str(triggers_file)) as ctx:
2026-02-20T05:16:23.255397962Z # Trigger parallel jobs
2026-02-20T05:16:23.255405905Z ctx.trigger_job("test", env={"SUITE": "unit"})
2026-02-20T05:16:23.255409862Z ctx.trigger_job("lint", env={"TOOL": "ruff"})
2026-02-20T05:16:23.255413079Z
2026-02-20T05:16:23.255417189Z # Trigger job that depends on both
2026-02-20T05:16:23.255421056Z ctx.trigger_job(
2026-02-20T05:16:23.255557777Z "build",
2026-02-20T05:16:23.255562804Z depends_on=["test", "lint"],
2026-02-20T05:16:23.255566257Z condition="all_success"
2026-02-20T05:16:23.25556979Z )
2026-02-20T05:16:23.25581654Z
2026-02-20T05:16:23.25582446Z # Verify all three were triggered
2026-02-20T05:16:23.256690626Z> with open(triggers_file) as f:
2026-02-20T05:16:23.256700989ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmprxxvtq9t/triggers.json'
2026-02-20T05:16:23.256703326Z
2026-02-20T05:16:23.256707689Z/workspace/runnerlib/tests/test_workflow.py:623: FileNotFoundError
2026-02-20T05:16:23.256711909Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.256716219Z✓ Scheduled job: test
2026-02-20T05:16:23.256719892Z✓ Scheduled job: lint
2026-02-20T05:16:23.256722899Z✓ Scheduled job: build
2026-02-20T05:16:23.256731582Z✓ Wrote 3 job trigger(s) to /tmp/tmprxxvtq9t/triggers.json
2026-02-20T05:16:23.256735936Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.257464512Z___________ TestIntegrationPatterns.test_conditional_deploy_pattern ____________
2026-02-20T05:16:23.257467903Z
2026-02-20T05:16:23.25747306Zself = <runnerlib.tests.test_workflow.TestIntegrationPatterns object at 0x7fdefbfdd6e0>
2026-02-20T05:16:23.257475373Z
2026-02-20T05:16:23.257479573Z def test_conditional_deploy_pattern(self):
2026-02-20T05:16:23.257483674Z """Test conditional deploy based on branch."""
2026-02-20T05:16:23.25748763Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-20T05:16:23.257938578Z triggers_file = Path(tmpdir) / "triggers.json"
2026-02-20T05:16:23.257942698Z
2026-02-20T05:16:23.257946918Z # Test on feature branch - should not deploy
2026-02-20T05:16:23.257952774Z with patch.dict(os.environ, {"REACTORCIDE_GIT_BRANCH": "feature/test"}):
2026-02-20T05:16:23.257957271Z with workflow_context(str(triggers_file)) as ctx:
2026-02-20T05:16:23.258268908Z ctx.trigger_job("test")
2026-02-20T05:16:23.258272801Z
2026-02-20T05:16:23.258277641Z if ctx.branch == "[REDACTED]":
2026-02-20T05:16:23.258281461Z ctx.trigger_job("deploy")
2026-02-20T05:16:23.259545244Z
2026-02-20T05:16:23.259554457Z> with open(triggers_file) as f:
2026-02-20T05:16:23.259560554ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpg5hszw0a/triggers.json'
2026-02-20T05:16:23.259562927Z
2026-02-20T05:16:23.259567634Z/workspace/runnerlib/tests/test_workflow.py:651: FileNotFoundError
2026-02-20T05:16:23.259572407Z----------------------------- Captured stderr call -----------------------------
2026-02-20T05:16:23.25957687Z✓ Scheduled job: test
2026-02-20T05:16:23.25958105Z✓ Wrote 1 job trigger(s) to /tmp/tmpg5hszw0a/triggers.json
2026-02-20T05:16:23.259584727Z✓ Triggers submitted via API, removed triggers.json
2026-02-20T05:16:23.25958916Z=========================== short test summary info ============================
2026-02-20T05:16:23.259595574ZFAILED tests/test_container_isolation.py::TestContainerIsolation::test_work_directory_isolation_with_prepare
2026-02-20T05:16:23.259601127ZFAILED tests/test_directory_operations.py::TestDirectoryOperations::test_cleanup_removes_job_directory
2026-02-20T05:16:23.260035498ZFAILED tests/test_docker_execution.py::test_basic_docker_execution - Assertio...
2026-02-20T05:16:23.260042145ZFAILED tests/test_docker_execution.py::test_docker_with_environment_variables
2026-02-20T05:16:23.260287541ZFAILED tests/test_docker_execution.py::test_docker_with_python - AssertionErr...
2026-02-20T05:16:23.260296077ZFAILED tests/test_docker_execution.py::test_docker_failure_handling - Asserti...
2026-02-20T05:16:23.260300705ZFAILED tests/test_docker_execution.py::test_docker_available - FileNotFoundEr...
2026-02-20T05:16:23.260305688ZFAILED tests/test_docker_execution.py::test_container_with_working_directory
2026-02-20T05:16:23.260310682ZFAILED tests/test_docker_execution.py::test_dry_run_mode - AssertionError: Dr...
2026-02-20T05:16:23.260362698ZFAILED tests/test_docker_execution.py::test_node_container - AssertionError: ...
2026-02-20T05:16:23.260368802ZFAILED tests/test_docker_execution.py::test_container_with_multiple_env_vars
2026-02-20T05:16:23.260715075ZFAILED tests/test_docker_execution.py::test_selective_secret_masking - Assert...
2026-02-20T05:16:23.260721465ZFAILED tests/test_dynamic_secret_masking.py::test_value_printed_then_masked
2026-02-20T05:16:23.260727625ZFAILED tests/test_dynamic_secret_masking.py::test_multiple_values_masked_after_registration
2026-02-20T05:16:23.260733185ZFAILED tests/test_dynamic_secret_masking.py::test_immediate_masking_in_streaming_output
2026-02-20T05:16:23.261325007ZFAILED tests/test_dynamic_secrets.py::test_dynamic_secret_registration - Asse...
2026-02-20T05:16:23.261331687ZFAILED tests/test_dynamic_secrets.py::test_multiple_dynamic_secrets - Asserti...
2026-02-20T05:16:23.261336232ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_basic_eval_with_match - ...
2026-02-20T05:16:23.261340238ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_multiple_matches - ...
2026-02-20T05:16:23.261344362ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_branch_filter - Ass...
2026-02-20T05:16:23.261348532ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_full_event_context
2026-02-20T05:16:23.261352108ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_with_changed_files
2026-02-20T05:16:23.261774795ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_pr_uses_base_ref_for_diff
2026-02-20T05:16:23.261781065ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_push_uses_head_parent_for_diff
2026-02-20T05:16:23.261787019ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_no_git_dir_skips_changed_files
2026-02-20T05:16:23.261802036ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_env_vars - Assertio...
2026-02-20T05:16:23.261809653ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_job_priority_and_timeout
2026-02-20T05:16:23.262350531ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_git_error_continues
2026-02-20T05:16:23.262357614ZFAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_ci_source_when_missing
2026-02-20T05:16:23.262362387ZFAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_source_when_missing
2026-02-20T05:16:23.262367592ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_pr_opened_triggers_test_not_deploy
2026-02-20T05:16:23.262715456ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_push_to_[REDACTED]_triggers_deploy
2026-02-20T05:16:23.262721506ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_tag_created_triggers_release
2026-02-20T05:16:23.262728912ZFAILED tests/test_git_operations.py::TestGitOperations::test_checkout_creates_job_directory
2026-02-20T05:16:23.262737909ZFAILED tests/test_integration.py::TestDirectoryManagementIntegration::test_directory_validation_with_real_filesystem
2026-02-20T05:16:23.262745419ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_work_dir_isolation
2026-02-20T05:16:23.263903067ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_concurrent_job_isolation
2026-02-20T05:16:23.263909163ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_container_mount_isolation
2026-02-20T05:16:23.263914483ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_no_source_preparation_default
2026-02-20T05:16:23.26392041ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_preparation
2026-02-20T05:16:23.263927577ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_dual_source_preparation
2026-02-20T05:16:23.263934207ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_ci_source_only
2026-02-20T05:16:23.263946817ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_missing_url
2026-02-20T05:16:23.26395392ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_copy_source_missing_url
2026-02-20T05:16:23.26396282ZFAILED tests/test_validation.py::TestConfigValidator::test_validate_file_system_job_directory_missing
2026-02-20T05:16:23.263967967ZFAILED tests/test_workflow.py::TestWorkflowContext::test_flush_triggers_creates_file
2026-02-20T05:16:23.263972737ZFAILED tests/test_workflow.py::TestWorkflowContext::test_flush_triggers_appends_to_existing
2026-02-20T05:16:23.263977251ZFAILED tests/test_workflow.py::TestModuleLevelFunctions::test_flush_triggers_convenience
2026-02-20T05:16:23.263982284ZFAILED tests/test_workflow.py::TestWorkflowContextManager::test_context_manager_success
2026-02-20T05:16:23.263987168ZFAILED tests/test_workflow.py::TestIntegrationPatterns::test_simple_pipeline_pattern
2026-02-20T05:16:23.26421125ZFAILED tests/test_workflow.py::TestIntegrationPatterns::test_parallel_pipeline_pattern
2026-02-20T05:16:23.264218896ZFAILED tests/test_workflow.py::TestIntegrationPatterns::test_conditional_deploy_pattern
2026-02-20T05:16:23.2642282Z============ 52 failed, 344 passed, 1 skipped in 237.34s (0:03:57) =============