Reactorcide

← Back to Jobs

test-python

failed exit: 1

Triggered by eval job 019c7e27-00ad-8031-4f14-f8a9ce2054bc

Job ID
019c7e27-2cfd-2335-34a0-0721cdafe8ff
Created
2026-02-21 03:03:42 UTC
Updated
2026-02-21 03:03:42 UTC
Duration
6m 3s
Source Ref
52a2d5deaba04f5ac695bee167612080e6001722
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-21T03:03:54.18534564ZCloning into '/workspace'...
2026-02-21T03:05:03.784551218Z=== Running Python Tests ===
2026-02-21T03:05:04.069996641ZUsing CPython 3.13.12 interpreter at: /usr/local/bin/python3.13
2026-02-21T03:05:04.070005197ZCreating virtual environment at: .venv
2026-02-21T03:05:04.115312357Z Building runnerlib @ file:///workspace/runnerlib
2026-02-21T03:05:04.264188048ZDownloading cryptography (4.3MiB)
2026-02-21T03:05:04.265687176ZDownloading pygments (1.2MiB)
2026-02-21T03:05:05.132369065Z Downloaded cryptography
2026-02-21T03:05:05.176699695Z Downloaded pygments
2026-02-21T03:05:05.804936175Z Built runnerlib @ file:///workspace/runnerlib
2026-02-21T03:05:05.809467044Zwarning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
2026-02-21T03:05:05.80947403Z If the cache and target directories are on different filesystems, hardlinking may not be supported.
2026-02-21T03:05:05.809481345Z If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
2026-02-21T03:05:05.913103269ZInstalled 22 packages in 104ms
2026-02-21T03:05:08.822895146Z============================= test session starts ==============================
2026-02-21T03:05:08.823126158Zplatform linux -- Python 3.13.12, pytest-8.3.5, pluggy-1.6.0
2026-02-21T03:05:08.823131191Zrootdir: /workspace/runnerlib
2026-02-21T03:05:08.823134574Zconfigfile: pyproject.toml
2026-02-21T03:05:08.823137604Zplugins: cov-7.0.0
2026-02-21T03:05:08.823140638Zcollected 397 items
2026-02-21T03:05:08.823143061Z
2026-02-21T03:05:08.855591416Ztests/test_config.py .................... [ 5%]
2026-02-21T03:05:08.867923883Ztests/test_container_advanced.py ......... [ 7%]
2026-02-21T03:05:08.919157572Ztests/test_container_isolation.py ...F [ 8%]
2026-02-21T03:05:08.94302564Ztests/test_container_validation.py ................... [ 13%]
2026-02-21T03:05:09.053183812Ztests/test_directory_operations.py ......F..... [ 16%]
2026-02-21T03:05:13.526100503Ztests/test_docker_execution.py FFFFFFFFFF [ 18%]
2026-02-21T03:05:15.725295514Ztests/test_dynamic_secret_masking.py FFF [ 19%]
2026-02-21T03:05:17.324686825Ztests/test_dynamic_secrets.py FsF [ 20%]
2026-02-21T03:05:17.499246251Ztests/test_eval.py ..................................................... [ 33%]
2026-02-21T03:05:17.559116632Z............. [ 36%]
2026-02-21T03:09:09.293826556Ztests/test_eval_cli.py F...FFFF.FFFFFFFF.FFF [ 42%]
2026-02-21T03:09:11.797959927Ztests/test_git_operations.py ........F. [ 44%]
2026-02-21T03:09:12.33086429Ztests/test_git_ops.py ....... [ 46%]
2026-02-21T03:09:12.913542973Ztests/test_integration.py ...F....... [ 49%]
2026-02-21T03:09:14.10448088Ztests/test_job_isolation.py FF.F [ 50%]
2026-02-21T03:09:14.223367214Ztests/test_plugins.py ....................... [ 55%]
2026-02-21T03:09:14.286419711Ztests/test_register_secret.py ............ [ 58%]
2026-02-21T03:09:14.789057243Ztests/test_secrets.py ................... [ 63%]
2026-02-21T03:09:18.952630234Ztests/test_secrets_local.py ............................ [ 70%]
2026-02-21T03:09:18.978999044Ztests/test_secrets_resolver.py ............................. [ 78%]
2026-02-21T03:09:24.448503344Ztests/test_secrets_server.py ........ [ 80%]
2026-02-21T03:09:52.72456282Ztests/test_source_preparation.py .FF.FF.FF...... [ 83%]
2026-02-21T03:09:52.824724703Ztests/test_validation.py ......................F.... [ 90%]
2026-02-21T03:09:52.990077136Ztests/test_workflow.py ..................................... [100%]
2026-02-21T03:09:52.990083436Z
2026-02-21T03:09:52.990165106Z=================================== FAILURES ===================================
2026-02-21T03:09:52.990171846Z______ TestContainerIsolation.test_work_directory_isolation_with_prepare _______
2026-02-21T03:09:52.990174343Z
2026-02-21T03:09:52.990634584Zself = <runnerlib.tests.test_container_isolation.TestContainerIsolation object at 0x7fc091d0dcd0>
2026-02-21T03:09:52.990640921Z
2026-02-21T03:09:52.990695079Z def test_work_directory_isolation_with_prepare(self):
2026-02-21T03:09:52.990703885Z """Test that prepare_job_directory respects work directory changes."""
2026-02-21T03:09:52.990708612Z with tempfile.TemporaryDirectory() as work_dir1:
2026-02-21T03:09:52.990942954Z with tempfile.TemporaryDirectory() as work_dir2:
2026-02-21T03:09:52.991401441Z original_cwd = os.getcwd()
2026-02-21T03:09:52.991406885Z
2026-02-21T03:09:52.991412161Z try:
2026-02-21T03:09:52.991416195Z # Prepare job 1
2026-02-21T03:09:52.992693712Z os.chdir(work_dir1)
2026-02-21T03:09:52.992703179Z config1 = RunnerConfig(
2026-02-21T03:09:52.994547263Z code_dir="/job/src",
2026-02-21T03:09:52.994555729Z job_dir="/job/src",
2026-02-21T03:09:52.994561343Z job_command="echo job1",
2026-02-21T03:09:52.994567926Z runner_image="alpine:latest"
2026-02-21T03:09:52.994579996Z )
2026-02-21T03:09:52.99458649Z job_path1 = prepare_job_directory(config1)
2026-02-21T03:09:52.994614867Z assert job_path1.exists()
2026-02-21T03:09:52.994621054Z> assert str(job_path1).startswith(work_dir1)
2026-02-21T03:09:52.994626624ZE AssertionError: assert False
2026-02-21T03:09:52.994635777ZE + where False = <built-in method startswith of str object at 0x7fc0918913b0>('/tmp/tmpl4f6y6_2')
2026-02-21T03:09:52.994644694ZE + where <built-in method startswith of str object at 0x7fc0918913b0> = '/job'.startswith
2026-02-21T03:09:52.994651578ZE + where '/job' = str(PosixPath('/job'))
2026-02-21T03:09:52.994656328Z
2026-02-21T03:09:52.994664094Ztests/test_container_isolation.py:158: AssertionError
2026-02-21T03:09:52.994676004Z__________ TestDirectoryOperations.test_cleanup_removes_job_directory __________
2026-02-21T03:09:52.994679811Z
2026-02-21T03:09:52.994688771Zself = <runnerlib.tests.test_directory_operations.TestDirectoryOperations object at 0x7fc091d35d00>
2026-02-21T03:09:52.994696061Z
2026-02-21T03:09:52.994701804Z def test_cleanup_removes_job_directory(self):
2026-02-21T03:09:52.994708471Z """Test that cleanup removes the job directory."""
2026-02-21T03:09:52.994714128Z job_dir = Path("./job")
2026-02-21T03:09:52.994719709Z job_dir.mkdir(exist_ok=True)
2026-02-21T03:09:52.994722939Z
2026-02-21T03:09:52.995061004Z # Create some files
2026-02-21T03:09:52.995066824Z (job_dir / "file.txt").write_text("Content")
2026-02-21T03:09:52.995656664Z (job_dir / "subdir").mkdir(exist_ok=True)
2026-02-21T03:09:52.995662827Z (job_dir / "subdir" / "nested.txt").write_text("Nested")
2026-02-21T03:09:52.99566573Z
2026-02-21T03:09:52.995670337Z # Perform cleanup
2026-02-21T03:09:52.995673934Z cleanup_job_directory()
2026-02-21T03:09:52.995676397Z
2026-02-21T03:09:52.995684647Z # Job directory should be gone
2026-02-21T03:09:52.995688144Z> assert not job_dir.exists()
2026-02-21T03:09:52.99569179ZE AssertionError: assert not True
2026-02-21T03:09:52.99569527ZE + where True = exists()
2026-02-21T03:09:52.996273395ZE + where exists = PosixPath('job').exists
2026-02-21T03:09:52.996278332Z
2026-02-21T03:09:52.996283549Ztests/test_directory_operations.py:172: AssertionError
2026-02-21T03:09:52.996288469Z_________________________ test_basic_docker_execution __________________________
2026-02-21T03:09:52.996290749Z
2026-02-21T03:09:52.997118233Z def test_basic_docker_execution():
2026-02-21T03:09:52.997125773Z """Test that we can execute a simple container with Docker."""
2026-02-21T03:09:52.997128597Z
2026-02-21T03:09:52.99713382Z # Create a temporary working directory
2026-02-21T03:09:52.99714263Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:52.997146147Z work_dir = Path(tmpdir)
2026-02-21T03:09:52.9971487Z
2026-02-21T03:09:52.99715287Z # Create job directory structure
2026-02-21T03:09:52.99715657Z job_dir = work_dir / "job"
2026-02-21T03:09:52.997160207Z job_dir.mkdir()
2026-02-21T03:09:52.99716229Z
2026-02-21T03:09:52.997311915Z # Create a simple test script
2026-02-21T03:09:52.997991858Z test_script = job_dir / "test.sh"
2026-02-21T03:09:52.997996808Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:52.998005015Z echo "Hello from Docker container"
2026-02-21T03:09:52.998008171Z echo "Current directory: $(pwd)"
2026-02-21T03:09:52.998011608Z echo "Job directory contents:"
2026-02-21T03:09:52.998021168Z ls -la /job/
2026-02-21T03:09:52.998025455Z exit 0
2026-02-21T03:09:52.998307814Z """)
2026-02-21T03:09:52.998314448Z test_script.chmod(0o755)
2026-02-21T03:09:52.998316978Z
2026-02-21T03:09:52.998321238Z # Run the container using runnerlib CLI
2026-02-21T03:09:52.998324361Z result = subprocess.run(
2026-02-21T03:09:52.998816595Z [
2026-02-21T03:09:52.998823715Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:52.998828718Z "--runner-image", "alpine:latest",
2026-02-21T03:09:52.998832802Z "--job-command", "sh /job/test.sh",
2026-02-21T03:09:52.998836532Z "--code-dir", "/job",
2026-02-21T03:09:52.999232633Z "--job-dir", "/job",
2026-02-21T03:09:52.999238143Z ],
2026-02-21T03:09:52.999241453Z capture_output=True,
2026-02-21T03:09:52.99924509Z text=True,
2026-02-21T03:09:52.999248957Z cwd=work_dir, # Run from the temp directory
2026-02-21T03:09:52.999254453Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:52.999258247Z )
2026-02-21T03:09:52.999702613Z
2026-02-21T03:09:52.999716056Z print("STDOUT:", result.stdout)
2026-02-21T03:09:52.99972241Z print("STDERR:", result.stderr)
2026-02-21T03:09:52.999730031Z print("Return code:", result.returncode)
2026-02-21T03:09:52.999734497Z
2026-02-21T03:09:53.000243246Z # Verify the execution
2026-02-21T03:09:53.000253576Z> assert result.returncode == 0, f"Container execution failed with code {result.returncode}"
2026-02-21T03:09:53.000259876ZE AssertionError: Container execution failed with code 1
2026-02-21T03:09:53.000265526ZE assert 1 == 0
2026-02-21T03:09:53.000280236ZE + 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-21T03:09:53.000284076Z
2026-02-21T03:09:53.000474007Ztests/test_docker_execution.py:51: AssertionError
2026-02-21T03:09:53.000521534Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.000529784ZSTDOUT:
2026-02-21T03:09:53.000537094ZSTDERR: 2026-02-21T03:05:09.514793+00:00 Configuration validation failed:
2026-02-21T03:09:53.000544931Z2026-02-21T03:05:09.514871+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.000876642Z • system: docker is not available in PATH
2026-02-21T03:09:53.000888918Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.000893962Z
2026-02-21T03:09:53.000900315Z⚠️ Configuration warnings:
2026-02-21T03:09:53.000906162Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.001152404Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.001158244Z
2026-02-21T03:09:53.001644015Z
2026-02-21T03:09:53.001673125ZReturn code: 1
2026-02-21T03:09:53.001681532Z____________________ test_docker_with_environment_variables ____________________
2026-02-21T03:09:53.001684882Z
2026-02-21T03:09:53.001691885Z def test_docker_with_environment_variables():
2026-02-21T03:09:53.001698059Z """Test Docker execution with environment variables."""
2026-02-21T03:09:53.00288196Z
2026-02-21T03:09:53.0028944Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.00290058Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.002904773Z
2026-02-21T03:09:53.00291072Z # Create job directory
2026-02-21T03:09:53.004551221Z job_dir = work_dir / "job"
2026-02-21T03:09:53.004559835Z job_dir.mkdir()
2026-02-21T03:09:53.004564408Z
2026-02-21T03:09:53.004572761Z # Create script that uses environment variables
2026-02-21T03:09:53.004579201Z test_script = job_dir / "env_test.sh"
2026-02-21T03:09:53.004585751Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.004616086Z echo "TEST_VAR=$TEST_VAR"
2026-02-21T03:09:53.004621769Z echo "CUSTOM_VAR=$CUSTOM_VAR"
2026-02-21T03:09:53.004627352Z if [ "$TEST_VAR" = "test_value" ]; then
2026-02-21T03:09:53.004632626Z echo "Environment variables work!"
2026-02-21T03:09:53.004638726Z exit 0
2026-02-21T03:09:53.004644206Z else
2026-02-21T03:09:53.004649736Z echo "Environment variables failed"
2026-02-21T03:09:53.004655616Z exit 1
2026-02-21T03:09:53.005258272Z fi
2026-02-21T03:09:53.005264462Z """)
2026-02-21T03:09:53.005269302Z test_script.chmod(0o755)
2026-02-21T03:09:53.005272505Z
2026-02-21T03:09:53.005278828Z # Create env file (use relative path from working directory)
2026-02-21T03:09:53.005282802Z env_file = job_dir / "test.env"
2026-02-21T03:09:53.005286755Z env_file.write_text("""# Test environment
2026-02-21T03:09:53.005290042Z TEST_VAR=test_value
2026-02-21T03:09:53.005293508Z CUSTOM_VAR=custom_value
2026-02-21T03:09:53.005315822Z """)
2026-02-21T03:09:53.005318955Z
2026-02-21T03:09:53.005324805Z # Run with environment file - needs to be relative path starting with ./job/
2026-02-21T03:09:53.005328668Z result = subprocess.run(
2026-02-21T03:09:53.005331762Z [
2026-02-21T03:09:53.005336298Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.005340458Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.005343935Z "--job-command", "sh /job/env_test.sh",
2026-02-21T03:09:53.005347745Z "--code-dir", "/job",
2026-02-21T03:09:53.005351072Z "--job-dir", "/job",
2026-02-21T03:09:53.005354478Z "--job-env", "./job/test.env",
2026-02-21T03:09:53.005357205Z ],
2026-02-21T03:09:53.005360348Z capture_output=True,
2026-02-21T03:09:53.005542494Z text=True,
2026-02-21T03:09:53.005548444Z cwd=work_dir,
2026-02-21T03:09:53.0055538Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.00555776Z )
2026-02-21T03:09:53.005560584Z
2026-02-21T03:09:53.005727655Z print("ENV TEST STDOUT:", result.stdout)
2026-02-21T03:09:53.005737775Z print("ENV TEST STDERR:", result.stderr)
2026-02-21T03:09:53.005914457Z
2026-02-21T03:09:53.005923224Z> assert result.returncode == 0, f"Environment test failed with code {result.returncode}"
2026-02-21T03:09:53.005935091ZE AssertionError: Environment test failed with code 1
2026-02-21T03:09:53.006264347ZE assert 1 == 0
2026-02-21T03:09:53.006422278ZE + 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-21T03:09:53.006747744Z
2026-02-21T03:09:53.006756487Ztests/test_docker_execution.py:108: AssertionError
2026-02-21T03:09:53.006761564Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.006765277ZENV TEST STDOUT:
2026-02-21T03:09:53.006769997ZENV TEST STDERR: 2026-02-21T03:05:10.161456+00:00 Configuration validation failed:
2026-02-21T03:09:53.006774408Z2026-02-21T03:05:10.161606+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.006779292Z • system: docker is not available in PATH
2026-02-21T03:09:53.006869619Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.007115091Z
2026-02-21T03:09:53.007121798Z⚠️ Configuration warnings:
2026-02-21T03:09:53.007465517Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.007471561Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.007522804Z
2026-02-21T03:09:53.007525547Z
2026-02-21T03:09:53.007748317Z___________________________ test_docker_with_python ____________________________
2026-02-21T03:09:53.00775219Z
2026-02-21T03:09:53.007756544Z def test_docker_with_python():
2026-02-21T03:09:53.00776107Z """Test running Python code in a container."""
2026-02-21T03:09:53.007763954Z
2026-02-21T03:09:53.00776804Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.007991852Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.007995529Z
2026-02-21T03:09:53.007999736Z # Create job directory
2026-02-21T03:09:53.008437793Z job_dir = work_dir / "job"
2026-02-21T03:09:53.00844638Z job_dir.mkdir()
2026-02-21T03:09:53.008450723Z
2026-02-21T03:09:53.008896524Z # Create Python script
2026-02-21T03:09:53.008905357Z py_script = job_dir / "test.py"
2026-02-21T03:09:53.00891102Z py_script.write_text("""
2026-02-21T03:09:53.008916907Z import sys
2026-02-21T03:09:53.009348671Z import os
2026-02-21T03:09:53.009354551Z
2026-02-21T03:09:53.009361598Z print(f"Python version: {sys.version.split()[0]}")
2026-02-21T03:09:53.010999436Z print(f"Working directory: {os.getcwd()}")
2026-02-21T03:09:53.011006349Z print(f"Job files: {os.listdir('/job')}")
2026-02-21T03:09:53.011010683Z
2026-02-21T03:09:53.011020723Z # Test that we can write output
2026-02-21T03:09:53.011027186Z with open('/job/output.txt', 'w') as f:
2026-02-21T03:09:53.011034839Z f.write("Test output from Python container\\n")
2026-02-21T03:09:53.011039463Z
2026-02-21T03:09:53.011044843Z print("Successfully wrote output file")
2026-02-21T03:09:53.011050176Z sys.exit(0)
2026-02-21T03:09:53.011055394Z """)
2026-02-21T03:09:53.011059534Z
2026-02-21T03:09:53.011065254Z # Run Python container
2026-02-21T03:09:53.011071354Z result = subprocess.run(
2026-02-21T03:09:53.011433484Z [
2026-02-21T03:09:53.011443928Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.011450564Z "--runner-image", "python:3.11-alpine",
2026-02-21T03:09:53.011457324Z "--job-command", "python /job/test.py",
2026-02-21T03:09:53.011463128Z "--code-dir", "/job",
2026-02-21T03:09:53.011469198Z "--job-dir", "/job",
2026-02-21T03:09:53.011474488Z ],
2026-02-21T03:09:53.011480628Z capture_output=True,
2026-02-21T03:09:53.011490364Z text=True,
2026-02-21T03:09:53.011496048Z cwd=work_dir,
2026-02-21T03:09:53.011836551Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.011855037Z )
2026-02-21T03:09:53.011859314Z
2026-02-21T03:09:53.011866134Z print("PYTHON TEST STDOUT:", result.stdout)
2026-02-21T03:09:53.011971282Z print("PYTHON TEST STDERR:", result.stderr)
2026-02-21T03:09:53.011976672Z
2026-02-21T03:09:53.012191498Z> assert result.returncode == 0, f"Python container failed with code {result.returncode}"
2026-02-21T03:09:53.012201368ZE AssertionError: Python container failed with code 1
2026-02-21T03:09:53.012207161ZE assert 1 == 0
2026-02-21T03:09:53.012974895ZE + 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-21T03:09:53.012980818Z
2026-02-21T03:09:53.012988978Ztests/test_docker_execution.py:162: AssertionError
2026-02-21T03:09:53.012996545Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.013002058ZPYTHON TEST STDOUT:
2026-02-21T03:09:53.013008095ZPYTHON TEST STDERR: 2026-02-21T03:05:10.757033+00:00 Configuration validation failed:
2026-02-21T03:09:53.013015958Z2026-02-21T03:05:10.757127+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.013023095Z • system: docker is not available in PATH
2026-02-21T03:09:53.0135881Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.01361047Z
2026-02-21T03:09:53.01362043Z
2026-02-21T03:09:53.013625547Z_________________________ test_docker_failure_handling _________________________
2026-02-21T03:09:53.013627743Z
2026-02-21T03:09:53.013631487Z def test_docker_failure_handling():
2026-02-21T03:09:53.013636403Z """Test that container failures are properly reported."""
2026-02-21T03:09:53.01432131Z
2026-02-21T03:09:53.01433211Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.014336037Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.014338417Z
2026-02-21T03:09:53.014341854Z # Create job directory
2026-02-21T03:09:53.014345477Z job_dir = work_dir / "job"
2026-02-21T03:09:53.014569666Z job_dir.mkdir()
2026-02-21T03:09:53.014574049Z
2026-02-21T03:09:53.014578906Z # Create a script that fails
2026-02-21T03:09:53.01458327Z fail_script = job_dir / "fail.sh"
2026-02-21T03:09:53.015245362Z fail_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.015251758Z echo "This script will fail"
2026-02-21T03:09:53.015255608Z echo "Error: Something went wrong" >&2
2026-02-21T03:09:53.015259595Z exit 42
2026-02-21T03:09:53.015263048Z """)
2026-02-21T03:09:53.015572606Z fail_script.chmod(0o755)
2026-02-21T03:09:53.015576782Z
2026-02-21T03:09:53.015581082Z # Run container that should fail
2026-02-21T03:09:53.015584646Z result = subprocess.run(
2026-02-21T03:09:53.015588232Z [
2026-02-21T03:09:53.016078883Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.016085076Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.01608942Z "--job-command", "sh /job/fail.sh",
2026-02-21T03:09:53.01609352Z "--code-dir", "/job",
2026-02-21T03:09:53.016097466Z "--job-dir", "/job",
2026-02-21T03:09:53.016430393Z ],
2026-02-21T03:09:53.016434899Z capture_output=True,
2026-02-21T03:09:53.016442074Z text=True,
2026-02-21T03:09:53.016445424Z cwd=work_dir,
2026-02-21T03:09:53.016808029Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.016813273Z )
2026-02-21T03:09:53.01681604Z
2026-02-21T03:09:53.01682014Z print("FAIL TEST STDOUT:", result.stdout)
2026-02-21T03:09:53.017031498Z print("FAIL TEST STDERR:", result.stderr)
2026-02-21T03:09:53.017677917Z print("FAIL TEST RETURN CODE:", result.returncode)
2026-02-21T03:09:53.017682007Z
2026-02-21T03:09:53.017686717Z # Should propagate the exit code
2026-02-21T03:09:53.017691905Z> assert result.returncode == 42, f"Expected exit code 42, got {result.returncode}"
2026-02-21T03:09:53.017696628ZE AssertionError: Expected exit code 42, got 1
2026-02-21T03:09:53.017700175ZE assert 1 == 42
2026-02-21T03:09:53.017708128ZE + 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-21T03:09:53.017877336Z
2026-02-21T03:09:53.017888282Ztests/test_docker_execution.py:212: AssertionError
2026-02-21T03:09:53.017894479Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.017898162ZFAIL TEST STDOUT:
2026-02-21T03:09:53.018476122ZFAIL TEST STDERR: 2026-02-21T03:05:11.174316+00:00 Configuration validation failed:
2026-02-21T03:09:53.018487075Z2026-02-21T03:05:11.174388+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.018495122Z • system: docker is not available in PATH
2026-02-21T03:09:53.018501572Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.018882858Z
2026-02-21T03:09:53.018892878Z⚠️ Configuration warnings:
2026-02-21T03:09:53.018899665Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.018908225Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.018912015Z
2026-02-21T03:09:53.019184724Z
2026-02-21T03:09:53.019196762ZFAIL TEST RETURN CODE: 1
2026-02-21T03:09:53.019205589Z____________________________ test_docker_available _____________________________
2026-02-21T03:09:53.019279065Z
2026-02-21T03:09:53.019288299Z def test_docker_available():
2026-02-21T03:09:53.019295502Z """Test that Docker is available and working."""
2026-02-21T03:09:53.019839444Z> result = subprocess.run(
2026-02-21T03:09:53.019854341Z ["docker", "version", "--format", "{{.Server.Version}}"],
2026-02-21T03:09:53.019860431Z capture_output=True,
2026-02-21T03:09:53.019890525Z text=True
2026-02-21T03:09:53.019906511Z )
2026-02-21T03:09:53.02013428Z
2026-02-21T03:09:53.020150223Ztests/test_docker_execution.py:242:
2026-02-21T03:09:53.020455676Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.02046142Z/usr/local/lib/python3.13/subprocess.py:554: in run
2026-02-21T03:09:53.020465573Z with Popen(*popenargs, **kwargs) as process:
2026-02-21T03:09:53.020687408Z/usr/local/lib/python3.13/subprocess.py:1039: in __init__
2026-02-21T03:09:53.020693025Z self._execute_child(args, executable, preexec_fn, close_fds,
2026-02-21T03:09:53.020697108Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.021119189Z
2026-02-21T03:09:53.021130736Zself = <Popen: returncode: 255 args: ['docker', 'version', '--format', '{{.Server.V...>
2026-02-21T03:09:53.021137722Zargs = ['docker', 'version', '--format', '{{.Server.Version}}']
2026-02-21T03:09:53.02122263Zexecutable = b'docker', preexec_fn = None, close_fds = True, pass_fds = ()
2026-02-21T03:09:53.02122865Zcwd = None, env = None, startupinfo = None, creationflags = 0, shell = False
2026-02-21T03:09:53.021457678Zp2cread = -1, p2cwrite = -1, c2pread = 11, c2pwrite = 12, errread = 13
2026-02-21T03:09:53.021466432Zerrwrite = 14, restore_signals = True, gid = None, gids = None, uid = None
2026-02-21T03:09:53.021474032Zumask = -1, start_new_session = False, process_group = -1
2026-02-21T03:09:53.021611794Z
2026-02-21T03:09:53.021938403Z def _execute_child(self, args, executable, preexec_fn, close_fds,
2026-02-21T03:09:53.022384707Z pass_fds, cwd, env,
2026-02-21T03:09:53.022397907Z startupinfo, creationflags, shell,
2026-02-21T03:09:53.022404147Z p2cread, p2cwrite,
2026-02-21T03:09:53.022409614Z c2pread, c2pwrite,
2026-02-21T03:09:53.022415791Z errread, errwrite,
2026-02-21T03:09:53.022421897Z restore_signals,
2026-02-21T03:09:53.022427881Z gid, gids, uid, umask,
2026-02-21T03:09:53.022775141Z start_new_session, process_group):
2026-02-21T03:09:53.022782864Z """Execute program (POSIX version)"""
2026-02-21T03:09:53.022787967Z
2026-02-21T03:09:53.022794057Z if isinstance(args, (str, bytes)):
2026-02-21T03:09:53.02337514Z args = [args]
2026-02-21T03:09:53.023381017Z elif isinstance(args, os.PathLike):
2026-02-21T03:09:53.023384603Z if shell:
2026-02-21T03:09:53.02338966Z raise TypeError('path-like args is not allowed when '
2026-02-21T03:09:53.023707055Z 'shell is [REDACTED]')
2026-02-21T03:09:53.023715332Z args = [args]
2026-02-21T03:09:53.023721522Z else:
2026-02-21T03:09:53.023727355Z args = list(args)
2026-02-21T03:09:53.02417474Z
2026-02-21T03:09:53.024181697Z if shell:
2026-02-21T03:09:53.024187027Z # On Android the default shell is at '/system/bin/sh'.
2026-02-21T03:09:53.024191454Z unix_shell = ('/system/bin/sh' if
2026-02-21T03:09:53.02420035Z hasattr(sys, 'getandroidapilevel') else '/bin/sh')
2026-02-21T03:09:53.024466199Z args = [unix_shell, "-c"] + args
2026-02-21T03:09:53.024473096Z if executable:
2026-02-21T03:09:53.024485452Z args[0] = executable
2026-02-21T03:09:53.024490012Z
2026-02-21T03:09:53.024913327Z if executable is None:
2026-02-21T03:09:53.02492021Z executable = args[0]
2026-02-21T03:09:53.024925053Z
2026-02-21T03:09:53.025179186Z sys.audit("subprocess.Popen", executable, args, cwd, env)
2026-02-21T03:09:53.025184763Z
2026-02-21T03:09:53.025190346Z if (_USE_POSIX_SPAWN
2026-02-21T03:09:53.025195993Z and os.path.dirname(executable)
2026-02-21T03:09:53.025201719Z and preexec_fn is None
2026-02-21T03:09:53.025758843Z and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
2026-02-21T03:09:53.025766534Z and not pass_fds
2026-02-21T03:09:53.025771968Z and cwd is None
2026-02-21T03:09:53.025778101Z and (p2cread == -1 or p2cread > 2)
2026-02-21T03:09:53.025783511Z and (c2pwrite == -1 or c2pwrite > 2)
2026-02-21T03:09:53.026067631Z and (errwrite == -1 or errwrite > 2)
2026-02-21T03:09:53.026074291Z and not start_new_session
2026-02-21T03:09:53.026078121Z and process_group == -1
2026-02-21T03:09:53.026082087Z and gid is None
2026-02-21T03:09:53.026090167Z and gids is None
2026-02-21T03:09:53.026098404Z and uid is None
2026-02-21T03:09:53.026587898Z and umask < 0):
2026-02-21T03:09:53.026616135Z self._posix_spawn(args, executable, env, restore_signals, close_fds,
2026-02-21T03:09:53.026620805Z p2cread, p2cwrite,
2026-02-21T03:09:53.026932655Z c2pread, c2pwrite,
2026-02-21T03:09:53.026942512Z errread, errwrite)
2026-02-21T03:09:53.026946342Z return
2026-02-21T03:09:53.026950209Z
2026-02-21T03:09:53.026954509Z orig_executable = executable
2026-02-21T03:09:53.026957542Z
2026-02-21T03:09:53.026963019Z # For transferring possible exec failure from child to parent.
2026-02-21T03:09:53.027332465Z # Data format: "exception name:hex errno:description"
2026-02-21T03:09:53.027342328Z # Pickle is not used; it is complex and involves memory allocation.
2026-02-21T03:09:53.027356352Z errpipe_read, errpipe_write = os.pipe()
2026-02-21T03:09:53.027362775Z # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
2026-02-21T03:09:53.027368318Z low_fds_to_close = []
2026-02-21T03:09:53.027639175Z while errpipe_write < 3:
2026-02-21T03:09:53.027646386Z low_fds_to_close.append(errpipe_write)
2026-02-21T03:09:53.027661562Z errpipe_write = os.dup(errpipe_write)
2026-02-21T03:09:53.027894461Z for low_fd in low_fds_to_close:
2026-02-21T03:09:53.027901918Z os.close(low_fd)
2026-02-21T03:09:53.027967258Z try:
2026-02-21T03:09:53.027973698Z try:
2026-02-21T03:09:53.028226763Z # We must avoid complex work that could involve
2026-02-21T03:09:53.028233936Z # malloc or free in the child process to avoid
2026-02-21T03:09:53.028472679Z # potential deadlocks, thus we do all this here.
2026-02-21T03:09:53.028480206Z # and pass it to fork_exec()
2026-02-21T03:09:53.028484656Z
2026-02-21T03:09:53.028490992Z if env is not None:
2026-02-21T03:09:53.029004047Z env_list = []
2026-02-21T03:09:53.0290131Z for k, v in env.items():
2026-02-21T03:09:53.029018164Z k = os.fsencode(k)
2026-02-21T03:09:53.029024058Z if b'=' in k:
2026-02-21T03:09:53.029031048Z raise ValueError("illegal environment variable name")
2026-02-21T03:09:53.029376954Z env_list.append(k + b'=' + os.fsencode(v))
2026-02-21T03:09:53.029385044Z else:
2026-02-21T03:09:53.029392135Z env_list = None # Use execv instead of execve.
2026-02-21T03:09:53.029626839Z executable = os.fsencode(executable)
2026-02-21T03:09:53.029634349Z if os.path.dirname(executable):
2026-02-21T03:09:53.029888681Z executable_list = (executable,)
2026-02-21T03:09:53.029994209Z else:
2026-02-21T03:09:53.030003582Z # This matches the behavior of os._execvpe().
2026-02-21T03:09:53.030164168Z executable_list = tuple(
2026-02-21T03:09:53.030170211Z os.path.join(os.fsencode(dir), executable)
2026-02-21T03:09:53.0303849Z for dir in os.get_exec_path(env))
2026-02-21T03:09:53.030390293Z fds_to_keep = set(pass_fds)
2026-02-21T03:09:53.030394207Z fds_to_keep.add(errpipe_write)
2026-02-21T03:09:53.030562855Z self.pid = _fork_exec(
2026-02-21T03:09:53.030569568Z args, executable_list,
2026-02-21T03:09:53.030574958Z close_fds, tuple(sorted(map(int, fds_to_keep))),
2026-02-21T03:09:53.030933331Z cwd, env_list,
2026-02-21T03:09:53.030942485Z p2cread, p2cwrite, c2pread, c2pwrite,
2026-02-21T03:09:53.030947978Z errread, errwrite,
2026-02-21T03:09:53.031189158Z errpipe_read, errpipe_write,
2026-02-21T03:09:53.031197728Z restore_signals, start_new_session,
2026-02-21T03:09:53.031204284Z process_group, gid, gids, uid, umask,
2026-02-21T03:09:53.031209928Z preexec_fn, _USE_VFORK)
2026-02-21T03:09:53.031215608Z self._child_created = True
2026-02-21T03:09:53.031222591Z finally:
2026-02-21T03:09:53.031391322Z # be sure the FD is closed no matter what
2026-02-21T03:09:53.031676305Z os.close(errpipe_write)
2026-02-21T03:09:53.031680888Z
2026-02-21T03:09:53.031684972Z self._close_pipe_fds(p2cread, p2cwrite,
2026-02-21T03:09:53.031689125Z c2pread, c2pwrite,
2026-02-21T03:09:53.031945194Z errread, errwrite)
2026-02-21T03:09:53.031949471Z
2026-02-21T03:09:53.031954361Z # Wait for exec to fail or succeed; possibly raising an
2026-02-21T03:09:53.031992635Z # exception (limited in size)
2026-02-21T03:09:53.032003591Z errpipe_data = bytearray()
2026-02-21T03:09:53.032007498Z while True:
2026-02-21T03:09:53.032375818Z part = os.read(errpipe_read, 50000)
2026-02-21T03:09:53.032381228Z errpipe_data += part
2026-02-21T03:09:53.032385234Z if not part or len(errpipe_data) > 50000:
2026-02-21T03:09:53.032585823Z break
2026-02-21T03:09:53.03260924Z finally:
2026-02-21T03:09:53.032613766Z # be sure the FD is closed no matter what
2026-02-21T03:09:53.03290446Z os.close(errpipe_read)
2026-02-21T03:09:53.03290826Z
2026-02-21T03:09:53.033147939Z if errpipe_data:
2026-02-21T03:09:53.033294453Z try:
2026-02-21T03:09:53.033300093Z pid, sts = os.waitpid(self.pid, 0)
2026-02-21T03:09:53.033304737Z if pid == self.pid:
2026-02-21T03:09:53.033308897Z self._handle_exitstatus(sts)
2026-02-21T03:09:53.033418261Z else:
2026-02-21T03:09:53.033821045Z self.returncode = sys.maxsize
2026-02-21T03:09:53.033826342Z except ChildProcessError:
2026-02-21T03:09:53.033830038Z pass
2026-02-21T03:09:53.033835075Z
2026-02-21T03:09:53.033840628Z try:
2026-02-21T03:09:53.033844965Z exception_name, hex_errno, err_msg = (
2026-02-21T03:09:53.034321389Z errpipe_data.split(b':', 2))
2026-02-21T03:09:53.034329296Z # The encoding here should match the encoding
2026-02-21T03:09:53.034345042Z # written in by the subprocess implementations
2026-02-21T03:09:53.034441417Z # like _posixsubprocess
2026-02-21T03:09:53.034449744Z err_msg = err_msg.decode()
2026-02-21T03:09:53.034455477Z except ValueError:
2026-02-21T03:09:53.034636455Z exception_name = b'SubprocessError'
2026-02-21T03:09:53.034641192Z hex_errno = b'0'
2026-02-21T03:09:53.034929592Z err_msg = 'Bad exception data from child: {!r}'.format(
2026-02-21T03:09:53.034935665Z bytes(errpipe_data))
2026-02-21T03:09:53.034939265Z child_exception_type = getattr(
2026-02-21T03:09:53.034943658Z builtins, exception_name.decode('ascii'),
2026-02-21T03:09:53.035331955Z SubprocessError)
2026-02-21T03:09:53.035340595Z if issubclass(child_exception_type, OSError) and hex_errno:
2026-02-21T03:09:53.035457983Z errno_num = int(hex_errno, 16)
2026-02-21T03:09:53.03546601Z if err_msg == "noexec:chdir":
2026-02-21T03:09:53.035779583Z err_msg = ""
2026-02-21T03:09:53.03578497Z # The error must be from chdir(cwd).
2026-02-21T03:09:53.035788823Z err_filename = cwd
2026-02-21T03:09:53.035793036Z elif err_msg == "noexec":
2026-02-21T03:09:53.035796503Z err_msg = ""
2026-02-21T03:09:53.036140755Z err_filename = None
2026-02-21T03:09:53.036145335Z else:
2026-02-21T03:09:53.036148962Z err_filename = orig_executable
2026-02-21T03:09:53.036152119Z if errno_num != 0:
2026-02-21T03:09:53.036155943Z err_msg = os.strerror(errno_num)
2026-02-21T03:09:53.036557283Z if err_filename is not None:
2026-02-21T03:09:53.036568737Z> raise child_exception_type(errno_num, err_msg, err_filename)
2026-02-21T03:09:53.03657487ZE FileNotFoundError: [Errno 2] No such file or directory: 'docker'
2026-02-21T03:09:53.036579047Z
2026-02-21T03:09:53.036586553Z/usr/local/lib/python3.13/subprocess.py:1991: FileNotFoundError
2026-02-21T03:09:53.036700521Z____________________ test_container_with_working_directory _____________________
2026-02-21T03:09:53.036721971Z
2026-02-21T03:09:53.036729948Z def test_container_with_working_directory():
2026-02-21T03:09:53.036740204Z """Test that working directory is set correctly in container."""
2026-02-21T03:09:53.036965856Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.03729485Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.03730075Z
2026-02-21T03:09:53.03779099Z # Create job directory with subdirectory
2026-02-21T03:09:53.037799407Z job_dir = work_dir / "job"
2026-02-21T03:09:53.037805851Z job_dir.mkdir()
2026-02-21T03:09:53.037811511Z sub_dir = job_dir / "subdir"
2026-02-21T03:09:53.038046676Z sub_dir.mkdir()
2026-02-21T03:09:53.03805274Z
2026-02-21T03:09:53.038058877Z # Create test file in subdirectory
2026-02-21T03:09:53.038065404Z test_file = sub_dir / "data.txt"
2026-02-21T03:09:53.038071504Z test_file.write_text("test data")
2026-02-21T03:09:53.038475144Z
2026-02-21T03:09:53.03849119Z # Create script that checks working directory
2026-02-21T03:09:53.038497897Z test_script = job_dir / "pwd_test.sh"
2026-02-21T03:09:53.038821354Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.039388638Z echo "Current directory: $(pwd)"
2026-02-21T03:09:53.039396372Z echo "Directory contents:"
2026-02-21T03:09:53.039402552Z ls -la
2026-02-21T03:09:53.039407978Z echo "Subdir exists:"
2026-02-21T03:09:53.039413352Z ls -d subdir
2026-02-21T03:09:53.039419322Z exit 0
2026-02-21T03:09:53.039431878Z """)
2026-02-21T03:09:53.039437932Z test_script.chmod(0o755)
2026-02-21T03:09:53.039442328Z
2026-02-21T03:09:53.039449295Z # Run with working directory set to /job
2026-02-21T03:09:53.039829806Z result = subprocess.run(
2026-02-21T03:09:53.03983693Z [
2026-02-21T03:09:53.039844127Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.039850763Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.040205049Z "--job-command", "sh pwd_test.sh", # Note: no /job/ prefix since we're in that dir
2026-02-21T03:09:53.040211282Z "--code-dir", "/job",
2026-02-21T03:09:53.040215685Z "--job-dir", "/job",
2026-02-21T03:09:53.040219643Z ],
2026-02-21T03:09:53.040223383Z capture_output=True,
2026-02-21T03:09:53.040227183Z text=True,
2026-02-21T03:09:53.040576529Z cwd=work_dir,
2026-02-21T03:09:53.040806741Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.040818781Z )
2026-02-21T03:09:53.040821675Z
2026-02-21T03:09:53.040826351Z print("WORKING DIR TEST:", result.stdout)
2026-02-21T03:09:53.041112448Z print("STDERR:", result.stderr)
2026-02-21T03:09:53.041116458Z
2026-02-21T03:09:53.041475688Z> assert result.returncode == 0, f"Container execution failed: {result.stderr}"
2026-02-21T03:09:53.041482725ZE AssertionError: Container execution failed: 2026-02-21T03:05:11.679761+00:00 Configuration validation failed:
2026-02-21T03:09:53.041486868ZE 2026-02-21T03:05:11.679843+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.04178561ZE • system: docker is not available in PATH
2026-02-21T03:09:53.04179276ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.041796847ZE
2026-02-21T03:09:53.041801124ZE ⚠️ Configuration warnings:
2026-02-21T03:09:53.04216285ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.042326185ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.042331745ZE
2026-02-21T03:09:53.042335472ZE
2026-02-21T03:09:53.042338978ZE assert 1 == 0
2026-02-21T03:09:53.042350639ZE + 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-21T03:09:53.043021368Z
2026-02-21T03:09:53.043037838Ztests/test_docker_execution.py:296: AssertionError
2026-02-21T03:09:53.043043775Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.043049508ZWORKING DIR TEST:
2026-02-21T03:09:53.043054065ZSTDERR: 2026-02-21T03:05:11.679761+00:00 Configuration validation failed:
2026-02-21T03:09:53.043255138Z2026-02-21T03:05:11.679843+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.043958946Z • system: docker is not available in PATH
2026-02-21T03:09:53.043968263Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.043973883Z
2026-02-21T03:09:53.043979904Z⚠️ Configuration warnings:
2026-02-21T03:09:53.043986287Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.043993634Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.043997281Z
2026-02-21T03:09:53.044001054Z
2026-02-21T03:09:53.044008917Z______________________________ test_dry_run_mode _______________________________
2026-02-21T03:09:53.044108102Z
2026-02-21T03:09:53.044115459Z def test_dry_run_mode():
2026-02-21T03:09:53.044120669Z """Test dry-run mode doesn't actually execute container."""
2026-02-21T03:09:53.044124796Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.044511579Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.044516905Z
2026-02-21T03:09:53.044521149Z # Create job directory
2026-02-21T03:09:53.044524845Z job_dir = work_dir / "job"
2026-02-21T03:09:53.044528632Z job_dir.mkdir()
2026-02-21T03:09:53.04461894Z
2026-02-21T03:09:53.045039464Z # Create a script that should NOT run
2026-02-21T03:09:53.04505589Z test_script = job_dir / "should_not_run.sh"
2026-02-21T03:09:53.045062824Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.04506879Z echo "ERROR: This should not execute in dry-run mode!"
2026-02-21T03:09:53.045074597Z exit 1
2026-02-21T03:09:53.04508004Z """)
2026-02-21T03:09:53.04509535Z test_script.chmod(0o755)
2026-02-21T03:09:53.045457917Z
2026-02-21T03:09:53.045468127Z # Run in dry-run mode
2026-02-21T03:09:53.04547606Z result = subprocess.run(
2026-02-21T03:09:53.045481738Z [
2026-02-21T03:09:53.046016546Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.046025533Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.046033673Z "--job-command", "sh /job/should_not_run.sh",
2026-02-21T03:09:53.046245585Z "--code-dir", "/job",
2026-02-21T03:09:53.046254088Z "--job-dir", "/job",
2026-02-21T03:09:53.046259945Z "--dry-run",
2026-02-21T03:09:53.046265525Z ],
2026-02-21T03:09:53.046271171Z capture_output=True,
2026-02-21T03:09:53.046875264Z text=True,
2026-02-21T03:09:53.046883824Z cwd=work_dir,
2026-02-21T03:09:53.046892164Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.046898617Z )
2026-02-21T03:09:53.046902967Z
2026-02-21T03:09:53.046909331Z print("DRY RUN OUTPUT:", result.stdout)
2026-02-21T03:09:53.046915997Z print("DRY RUN STDERR:", result.stderr)
2026-02-21T03:09:53.047103148Z
2026-02-21T03:09:53.047112713Z> assert result.returncode == 0, f"Dry-run failed: {result.stderr}"
2026-02-21T03:09:53.047355235ZE AssertionError: Dry-run failed: 2026-02-21T03:05:12.043273+00:00 Configuration validation failed:
2026-02-21T03:09:53.047361018ZE 2026-02-21T03:05:12.043358+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.047863123ZE • system: docker is not available in PATH
2026-02-21T03:09:53.047872613ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.047878273ZE
2026-02-21T03:09:53.048053877ZE ⚠️ Configuration warnings:
2026-02-21T03:09:53.048062091ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.048075257ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.048080864ZE
2026-02-21T03:09:53.048085774ZE
2026-02-21T03:09:53.048462385ZE assert 1 == 0
2026-02-21T03:09:53.048472425ZE + 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-21T03:09:53.048474718Z
2026-02-21T03:09:53.048479768Ztests/test_docker_execution.py:338: AssertionError
2026-02-21T03:09:53.048735121Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.048934539ZDRY RUN OUTPUT:
2026-02-21T03:09:53.048942392ZDRY RUN STDERR: 2026-02-21T03:05:12.043273+00:00 Configuration validation failed:
2026-02-21T03:09:53.048948109Z2026-02-21T03:05:12.043358+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.049301759Z • system: docker is not available in PATH
2026-02-21T03:09:53.049310616Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.049315053Z
2026-02-21T03:09:53.049356336Z⚠️ Configuration warnings:
2026-02-21T03:09:53.049721532Z • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.049735923Z 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.04973965Z
2026-02-21T03:09:53.049902471Z
2026-02-21T03:09:53.049912811Z_____________________________ test_node_container ______________________________
2026-02-21T03:09:53.049915254Z
2026-02-21T03:09:53.050294455Z def test_node_container():
2026-02-21T03:09:53.050300435Z """Test Node.js container execution."""
2026-02-21T03:09:53.050304758Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.050308288Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.051056952Z
2026-02-21T03:09:53.051063702Z # Create job directory
2026-02-21T03:09:53.051071249Z job_dir = work_dir / "job"
2026-02-21T03:09:53.051074989Z job_dir.mkdir()
2026-02-21T03:09:53.051077352Z
2026-02-21T03:09:53.051080569Z # Create Node.js script
2026-02-21T03:09:53.051084442Z js_script = job_dir / "test.js"
2026-02-21T03:09:53.051087639Z js_script.write_text("""
2026-02-21T03:09:53.051091105Z console.log('Node version:', process.version);
2026-02-21T03:09:53.051409778Z console.log('Platform:', process.platform);
2026-02-21T03:09:53.051414248Z console.log('Working dir:', process.cwd());
2026-02-21T03:09:53.051417998Z process.exit(0);
2026-02-21T03:09:53.051422402Z """)
2026-02-21T03:09:53.051424818Z
2026-02-21T03:09:53.05166153Z # Run Node container
2026-02-21T03:09:53.05166632Z result = subprocess.run(
2026-02-21T03:09:53.051669837Z [
2026-02-21T03:09:53.051866843Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.051942023Z "--runner-image", "node:18-alpine",
2026-02-21T03:09:53.052106878Z "--job-command", "node /job/test.js",
2026-02-21T03:09:53.052115499Z "--code-dir", "/job",
2026-02-21T03:09:53.052975389Z "--job-dir", "/job",
2026-02-21T03:09:53.052985222Z ],
2026-02-21T03:09:53.052995939Z capture_output=True,
2026-02-21T03:09:53.05300292Z text=True,
2026-02-21T03:09:53.0530095Z cwd=work_dir,
2026-02-21T03:09:53.053018223Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.053024267Z )
2026-02-21T03:09:53.05302905Z
2026-02-21T03:09:53.053521741Z print("NODE TEST OUTPUT:", result.stdout)
2026-02-21T03:09:53.053529971Z print("NODE TEST STDERR:", result.stderr)
2026-02-21T03:09:53.053534568Z
2026-02-21T03:09:53.053543581Z> assert result.returncode == 0, f"Node container failed: {result.stderr}"
2026-02-21T03:09:53.053551834ZE AssertionError: Node container failed: 2026-02-21T03:05:12.402540+00:00 Configuration validation failed:
2026-02-21T03:09:53.053558601ZE 2026-02-21T03:05:12.402610+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.053564904ZE • system: docker is not available in PATH
2026-02-21T03:09:53.053571874ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.053577645ZE
2026-02-21T03:09:53.053773147ZE
2026-02-21T03:09:53.05377855ZE assert 1 == 0
2026-02-21T03:09:53.05378686ZE + 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-21T03:09:53.05378922Z
2026-02-21T03:09:53.053793957Ztests/test_docker_execution.py:382: AssertionError
2026-02-21T03:09:53.054307358Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.054322621ZNODE TEST OUTPUT:
2026-02-21T03:09:53.054329454ZNODE TEST STDERR: 2026-02-21T03:05:12.402540+00:00 Configuration validation failed:
2026-02-21T03:09:53.054336958Z2026-02-21T03:05:12.402610+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.054346211Z • system: docker is not available in PATH
2026-02-21T03:09:53.054576374Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.054889679Z
2026-02-21T03:09:53.054895659Z
2026-02-21T03:09:53.054910257Z____________________ test_container_with_multiple_env_vars _____________________
2026-02-21T03:09:53.05491399Z
2026-02-21T03:09:53.054920927Z def test_container_with_multiple_env_vars():
2026-02-21T03:09:53.055210179Z """Test passing multiple environment variables via CLI."""
2026-02-21T03:09:53.055220726Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.055226566Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.055468752Z
2026-02-21T03:09:53.05555046Z # Create job directory
2026-02-21T03:09:53.05555798Z job_dir = work_dir / "job"
2026-02-21T03:09:53.055564123Z job_dir.mkdir()
2026-02-21T03:09:53.055829392Z
2026-02-21T03:09:53.055836822Z # Create test script
2026-02-21T03:09:53.055841902Z test_script = job_dir / "multi_env.sh"
2026-02-21T03:09:53.055845515Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.055848968Z echo "VAR1=$VAR1"
2026-02-21T03:09:53.056215159Z echo "VAR2=$VAR2"
2026-02-21T03:09:53.056219759Z echo "VAR3=$VAR3"
2026-02-21T03:09:53.056224962Z if [ "$VAR1" = "value1" ] && [ "$VAR2" = "value2" ] && [ "$VAR3" = "value3" ]; then
2026-02-21T03:09:53.056513601Z echo "All environment variables set correctly!"
2026-02-21T03:09:53.056520824Z exit 0
2026-02-21T03:09:53.056525768Z else
2026-02-21T03:09:53.056531324Z echo "Environment variables not set correctly"
2026-02-21T03:09:53.056867677Z exit 1
2026-02-21T03:09:53.056875074Z fi
2026-02-21T03:09:53.056880431Z """)
2026-02-21T03:09:53.057038739Z test_script.chmod(0o755)
2026-02-21T03:09:53.057042645Z
2026-02-21T03:09:53.057385026Z # Run with multiple env vars in a single --job-env (newline separated)
2026-02-21T03:09:53.057392999Z result = subprocess.run(
2026-02-21T03:09:53.057673321Z [
2026-02-21T03:09:53.057686242Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.057695772Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.057703559Z "--job-command", "sh /job/multi_env.sh",
2026-02-21T03:09:53.058015252Z "--code-dir", "/job",
2026-02-21T03:09:53.058023982Z "--job-dir", "/job",
2026-02-21T03:09:53.058031698Z "--job-env", "VAR1=value1\nVAR2=value2\nVAR3=value3",
2026-02-21T03:09:53.058038145Z ],
2026-02-21T03:09:53.058102139Z capture_output=True,
2026-02-21T03:09:53.058109079Z text=True,
2026-02-21T03:09:53.058115923Z cwd=work_dir,
2026-02-21T03:09:53.060386003Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.060393949Z )
2026-02-21T03:09:53.060398476Z
2026-02-21T03:09:53.060405816Z> assert result.returncode == 0, f"Multi-env test failed: {result.stderr}"
2026-02-21T03:09:53.060414089ZE AssertionError: Multi-env test failed: 2026-02-21T03:05:12.755743+00:00 Configuration validation failed:
2026-02-21T03:09:53.060418523ZE 2026-02-21T03:05:12.755817+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.060422636ZE • system: docker is not available in PATH
2026-02-21T03:09:53.060426706ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.060430173ZE
2026-02-21T03:09:53.060434626ZE ⚠️ Configuration warnings:
2026-02-21T03:09:53.060438834ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.060442824ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.060445801ZE
2026-02-21T03:09:53.060448401ZE
2026-02-21T03:09:53.060451497ZE assert 1 == 0
2026-02-21T03:09:53.060491701ZE + 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-21T03:09:53.060496681Z
2026-02-21T03:09:53.060502901Ztests/test_docker_execution.py:429: AssertionError
2026-02-21T03:09:53.060509484Z________________________ test_selective_secret_masking _________________________
2026-02-21T03:09:53.060513128Z
2026-02-21T03:09:53.060519611Z def test_selective_secret_masking():
2026-02-21T03:09:53.060527091Z """Test selective masking of secrets using --secret-values-list."""
2026-02-21T03:09:53.060531268Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.060534581Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.060537101Z
2026-02-21T03:09:53.060540858Z # Create job directory
2026-02-21T03:09:53.060658182Z job_dir = work_dir / "job"
2026-02-21T03:09:53.060664152Z job_dir.mkdir()
2026-02-21T03:09:53.060667162Z
2026-02-21T03:09:53.060672849Z # Create test script that prints environment variables
2026-02-21T03:09:53.061032229Z test_script = job_dir / "selective_test.sh"
2026-02-21T03:09:53.061037292Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.061041269Z echo "API_KEY=$API_KEY"
2026-02-21T03:09:53.061115786Z echo "PUBLIC_VALUE=$PUBLIC_VALUE"
2026-02-21T03:09:53.061119746Z echo "SECRET_TOKEN=$SECRET_TOKEN"
2026-02-21T03:09:53.061433912Z echo "CONFIG_PATH=$CONFIG_PATH"
2026-02-21T03:09:53.061695402Z exit 0
2026-02-21T03:09:53.061704816Z """)
2026-02-21T03:09:53.061712149Z test_script.chmod(0o755)
2026-02-21T03:09:53.061724942Z
2026-02-21T03:09:53.061785739Z # Run with environment vars and explicitly mark only some as secrets
2026-02-21T03:09:53.062199477Z result = subprocess.run(
2026-02-21T03:09:53.062205367Z [
2026-02-21T03:09:53.062778021Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.062786025Z "--runner-image", "alpine:latest",
2026-02-21T03:09:53.062791248Z "--job-command", "sh /job/selective_test.sh",
2026-02-21T03:09:53.062795241Z "--code-dir", "/job",
2026-02-21T03:09:53.062799095Z "--job-dir", "/job",
2026-02-21T03:09:53.062805198Z "--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-21T03:09:53.063114408Z "--secret-values-list", "my-secret-api-key-123,super-secret-token", # Only mask these specific values
2026-02-21T03:09:53.063119658Z ],
2026-02-21T03:09:53.063123408Z capture_output=True,
2026-02-21T03:09:53.063127062Z text=True,
2026-02-21T03:09:53.063130858Z cwd=work_dir,
2026-02-21T03:09:53.063414887Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.063420267Z )
2026-02-21T03:09:53.06380024Z
2026-02-21T03:09:53.06380894Z> assert result.returncode == 0, f"Selective masking test failed: {result.stderr}"
2026-02-21T03:09:53.063814187ZE AssertionError: Selective masking test failed: 2026-02-21T03:05:13.450894+00:00 Configuration validation failed:
2026-02-21T03:09:53.063818935ZE 2026-02-21T03:05:13.450990+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.063823368ZE • system: docker is not available in PATH
2026-02-21T03:09:53.066425761ZE 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.066475079ZE
2026-02-21T03:09:53.066479785ZE ⚠️ Configuration warnings:
2026-02-21T03:09:53.066484295ZE • runner_image: Using 'latest' tag or no tag specified
2026-02-21T03:09:53.066488679ZE 💡 Consider using a specific version tag for reproducible builds
2026-02-21T03:09:53.066491729ZE
2026-02-21T03:09:53.066495469ZE
2026-02-21T03:09:53.066498775ZE assert 1 == 0
2026-02-21T03:09:53.066506625ZE + 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-21T03:09:53.066509022Z
2026-02-21T03:09:53.066513899Ztests/test_docker_execution.py:474: AssertionError
2026-02-21T03:09:53.066517936Z________________________ test_value_printed_then_masked ________________________
2026-02-21T03:09:53.066520972Z
2026-02-21T03:09:53.066524902Z def test_value_printed_then_masked():
2026-02-21T03:09:53.066530099Z """Test that dynamic registration masks values in subsequent output.
2026-02-21T03:09:53.066532489Z
2026-02-21T03:09:53.066537856Z Due to the nature of streaming output and socket communication, we cannot
2026-02-21T03:09:53.066543459Z guarantee that output printed immediately before registration will be unmasked.
2026-02-21T03:09:53.066546992Z However, we CAN demonstrate that:
2026-02-21T03:09:53.066552189Z 1. Values not in the initial secrets list are not masked initially
2026-02-21T03:09:53.066556899Z 2. After dynamic registration, those values ARE masked in new output
2026-02-21T03:09:53.066559992Z """
2026-02-21T03:09:53.066562426Z
2026-02-21T03:09:53.066566146Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.066569429Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.066571573Z
2026-02-21T03:09:53.066574877Z # Create job directory
2026-02-21T03:09:53.066869626Z job_dir = work_dir / "job"
2026-02-21T03:09:53.066874539Z job_dir.mkdir()
2026-02-21T03:09:53.066877186Z
2026-02-21T03:09:53.066882589Z # Create a script that demonstrates dynamic masking
2026-02-21T03:09:53.066886993Z test_script = job_dir / "show_masking.py"
2026-02-21T03:09:53.066891386Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T03:09:53.066894693Z import socket
2026-02-21T03:09:53.067108718Z import json
2026-02-21T03:09:53.067113925Z import struct
2026-02-21T03:09:53.067117181Z import os
2026-02-21T03:09:53.067120341Z import time
2026-02-21T03:09:53.067123708Z import sys
2026-02-21T03:09:53.067471438Z import subprocess
2026-02-21T03:09:53.067475368Z
2026-02-21T03:09:53.067479808Z # This is our sensitive value that we'll get at runtime
2026-02-21T03:09:53.069056502Z api_token = "UNIQUEVALUE-abc123xyz789-ENDUNIQUE"
2026-02-21T03:09:53.069062898Z
2026-02-21T03:09:53.069068412Z print("=" * 50)
2026-02-21T03:09:53.069074342Z print("DEMONSTRATION OF DYNAMIC SECRET MASKING")
2026-02-21T03:09:53.069079262Z print("=" * 50)
2026-02-21T03:09:53.069083765Z
2026-02-21T03:09:53.069090259Z # First, show that without registration, the value appears in subprocess output
2026-02-21T03:09:53.069094539Z print("\\n1. Running subprocess BEFORE registration:")
2026-02-21T03:09:53.069102186Z sys.stdout.flush()
2026-02-21T03:09:53.069106313Z result = subprocess.run(
2026-02-21T03:09:53.069123116Z ["sh", "-c", f"echo 'Token is: {api_token}'"],
2026-02-21T03:09:53.06912875Z capture_output=True,
2026-02-21T03:09:53.06913465Z text=True
2026-02-21T03:09:53.069139756Z )
2026-02-21T03:09:53.069402776Z print(f" Subprocess output: {result.stdout.strip()}")
2026-02-21T03:09:53.069409379Z sys.stdout.flush()
2026-02-21T03:09:53.069413826Z
2026-02-21T03:09:53.069419886Z # Now register this value as a secret
2026-02-21T03:09:53.069742082Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T03:09:53.069750225Z if socket_path:
2026-02-21T03:09:53.069757252Z print(f"\\n2. Registering secret via socket...")
2026-02-21T03:09:53.069762739Z sys.stdout.flush()
2026-02-21T03:09:53.069766639Z
2026-02-21T03:09:53.069772549Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T03:09:53.069777552Z sock.connect(socket_path)
2026-02-21T03:09:53.070890639Z msg = json.dumps({'action': 'register', 'secrets': [api_token]}).encode()
2026-02-21T03:09:53.070899156Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T03:09:53.070911829Z sock.send(msg)
2026-02-21T03:09:53.070917536Z response = sock.recv(1024)
2026-02-21T03:09:53.070924666Z print(f" Registration response: {response.decode().strip()}")
2026-02-21T03:09:53.070929906Z sock.close()
2026-02-21T03:09:53.071963992Z
2026-02-21T03:09:53.071976425Z # Give it a moment to process
2026-02-21T03:09:53.071980689Z time.sleep(0.2)
2026-02-21T03:09:53.071983142Z
2026-02-21T03:09:53.071987662Z # Now show that the value IS masked in new output
2026-02-21T03:09:53.071991862Z print("\\n3. After registration, value is masked:")
2026-02-21T03:09:53.071995575Z print(f" API Token: {api_token}")
2026-02-21T03:09:53.071999462Z print(f" Authorization: Bearer {api_token}")
2026-02-21T03:09:53.072002892Z sys.stdout.flush()
2026-02-21T03:09:53.072006992Z else:
2026-02-21T03:09:53.072010523Z print("ERROR: No secrets socket available!")
2026-02-21T03:09:53.07201344Z exit(1)
2026-02-21T03:09:53.07201608Z
2026-02-21T03:09:53.072019536Z print("\\n" + "=" * 50)
2026-02-21T03:09:53.073414548Z print("TEST COMPLETE")
2026-02-21T03:09:53.073419115Z print("=" * 50)
2026-02-21T03:09:53.073422682Z """)
2026-02-21T03:09:53.073426535Z test_script.chmod(0o755)
2026-02-21T03:09:53.073429152Z
2026-02-21T03:09:53.073435255Z # Run the job with an explicit empty secrets list to prevent default masking
2026-02-21T03:09:53.073443625Z result = subprocess.run(
2026-02-21T03:09:53.073447032Z [
2026-02-21T03:09:53.073451272Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.073455602Z "--runner-image", "python:3.9-alpine",
2026-02-21T03:09:53.073461192Z "--job-command", "python3 -u /job/show_masking.py", # -u for unbuffered output
2026-02-21T03:09:53.073465515Z "--code-dir", "/job",
2026-02-21T03:09:53.073469052Z "--job-dir", "/job",
2026-02-21T03:09:53.073474255Z "--secret-values-list", "", # Empty list prevents default masking of all values
2026-02-21T03:09:53.073477132Z ],
2026-02-21T03:09:53.073584417Z capture_output=True,
2026-02-21T03:09:53.07361227Z text=True,
2026-02-21T03:09:53.073615854Z cwd=work_dir,
2026-02-21T03:09:53.07362043Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.073623727Z )
2026-02-21T03:09:53.073626107Z
2026-02-21T03:09:53.0736297Z print("\n--- OUTPUT ---")
2026-02-21T03:09:53.073633147Z print(result.stdout)
2026-02-21T03:09:53.073636284Z print("\n--- ERRORS ---")
2026-02-21T03:09:53.0736391Z print(result.stderr)
2026-02-21T03:09:53.073664795Z
2026-02-21T03:09:53.073668865Z # Verify the behavior
2026-02-21T03:09:53.073673408Z> assert result.returncode == 0, f"Script failed with code {result.returncode}"
2026-02-21T03:09:53.073738151ZE AssertionError: Script failed with code 1
2026-02-21T03:09:53.07645987ZE assert 1 == 0
2026-02-21T03:09:53.076470696ZE + 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-21T03:09:53.078374427Z
2026-02-21T03:09:53.07838594Ztests/test_dynamic_secret_masking.py:110: AssertionError
2026-02-21T03:09:53.078398113Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.078400744Z
2026-02-21T03:09:53.078404388Z--- OUTPUT ---
2026-02-21T03:09:53.078406911Z
2026-02-21T03:09:53.078409101Z
2026-02-21T03:09:53.078412594Z--- ERRORS ---
2026-02-21T03:09:53.078417931Z2026-02-21T03:05:14.161794+00:00 Configuration validation failed:
2026-02-21T03:09:53.078422261Z2026-02-21T03:05:14.161908+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.078466281Z • system: docker is not available in PATH
2026-02-21T03:09:53.078472814Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.078474984Z
2026-02-21T03:09:53.078477518Z
2026-02-21T03:09:53.078482901Z________________ test_multiple_values_masked_after_registration ________________
2026-02-21T03:09:53.078485211Z
2026-02-21T03:09:53.078489281Z def test_multiple_values_masked_after_registration():
2026-02-21T03:09:53.078495231Z """Test masking multiple values registered at different times."""
2026-02-21T03:09:53.078497821Z
2026-02-21T03:09:53.078502488Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.078506371Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.078508841Z
2026-02-21T03:09:53.078512724Z # Create job directory
2026-02-21T03:09:53.078516014Z job_dir = work_dir / "job"
2026-02-21T03:09:53.078519351Z job_dir.mkdir()
2026-02-21T03:09:53.078521364Z
2026-02-21T03:09:53.078524451Z # Create test script
2026-02-21T03:09:53.079296052Z test_script = job_dir / "progressive_masking.sh"
2026-02-21T03:09:53.079304529Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.079309309Z
2026-02-21T03:09:53.079315376Z # Function to register a secret
2026-02-21T03:09:53.079320949Z register_secret() {
2026-02-21T03:09:53.079327006Z python3 -c "
2026-02-21T03:09:53.079332342Z import socket, json, struct, os
2026-02-21T03:09:53.079338729Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T03:09:53.079344506Z sock.connect(os.environ['REACTORCIDE_SECRETS_SOCKET'])
2026-02-21T03:09:53.079352469Z msg = json.dumps({'action': 'register', 'secrets': ['$1']}).encode()
2026-02-21T03:09:53.079359402Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T03:09:53.079364772Z sock.send(msg)
2026-02-21T03:09:53.079369842Z sock.close()
2026-02-21T03:09:53.079374339Z "
2026-02-21T03:09:53.079391356Z sleep 0.5
2026-02-21T03:09:53.079397019Z }
2026-02-21T03:09:53.079401517Z
2026-02-21T03:09:53.079406283Z # First secret
2026-02-21T03:09:53.079411447Z SECRET1="database-pass-123"
2026-02-21T03:09:53.07941776Z echo "Step 1: Database password is: $SECRET1"
2026-02-21T03:09:53.07942196Z
2026-02-21T03:09:53.079426787Z # Register first secret
2026-02-21T03:09:53.079431963Z register_secret "$SECRET1"
2026-02-21T03:09:53.079435667Z
2026-02-21T03:09:53.079444423Z echo "Step 2: Database password is: $SECRET1"
2026-02-21T03:09:53.07944842Z
2026-02-21T03:09:53.07945356Z # Second secret
2026-02-21T03:09:53.07945861Z SECRET2="api-key-456"
2026-02-21T03:09:53.079464203Z echo "Step 3: API key is: $SECRET2"
2026-02-21T03:09:53.07947952Z
2026-02-21T03:09:53.079485313Z # Register second secret
2026-02-21T03:09:53.07949024Z register_secret "$SECRET2"
2026-02-21T03:09:53.07949401Z
2026-02-21T03:09:53.07949912Z echo "Step 4: Database password is: $SECRET1"
2026-02-21T03:09:53.079528628Z echo "Step 5: API key is: $SECRET2"
2026-02-21T03:09:53.079535031Z
2026-02-21T03:09:53.080097719Z # Third secret
2026-02-21T03:09:53.080105209Z SECRET3="webhook-token-789"
2026-02-21T03:09:53.080111369Z echo "Step 6: Webhook token is: $SECRET3"
2026-02-21T03:09:53.080115746Z
2026-02-21T03:09:53.080121389Z register_secret "$SECRET3"
2026-02-21T03:09:53.080125193Z
2026-02-21T03:09:53.080636938Z echo "Step 7: All secrets:"
2026-02-21T03:09:53.080641808Z echo " Database: $SECRET1"
2026-02-21T03:09:53.080645861Z echo " API: $SECRET2"
2026-02-21T03:09:53.080649241Z echo " Webhook: $SECRET3"
2026-02-21T03:09:53.080652621Z """)
2026-02-21T03:09:53.08087089Z test_script.chmod(0o755)
2026-02-21T03:09:53.080874963Z
2026-02-21T03:09:53.08088561Z # Run the job with an explicit empty secrets list
2026-02-21T03:09:53.081063855Z result = subprocess.run(
2026-02-21T03:09:53.081069255Z [
2026-02-21T03:09:53.081415136Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.081422959Z "--runner-image", "python:3.9-alpine",
2026-02-21T03:09:53.081428222Z "--job-command", "sh /job/progressive_masking.sh",
2026-02-21T03:09:53.081663071Z "--code-dir", "/job",
2026-02-21T03:09:53.081668148Z "--job-dir", "/job",
2026-02-21T03:09:53.081673631Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-21T03:09:53.08199941Z ],
2026-02-21T03:09:53.082004256Z capture_output=True,
2026-02-21T03:09:53.08200802Z text=True,
2026-02-21T03:09:53.082325914Z cwd=work_dir,
2026-02-21T03:09:53.082857411Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.082862908Z )
2026-02-21T03:09:53.082865964Z
2026-02-21T03:09:53.082875438Z print("\n--- OUTPUT ---")
2026-02-21T03:09:53.082879034Z print(result.stdout)
2026-02-21T03:09:53.082881504Z
2026-02-21T03:09:53.082885184Z> assert result.returncode == 0
2026-02-21T03:09:53.08309606ZE AssertionError: assert 1 == 0
2026-02-21T03:09:53.083105017ZE + 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-21T03:09:53.083107407Z
2026-02-21T03:09:53.083112397Ztests/test_dynamic_secret_masking.py:209: AssertionError
2026-02-21T03:09:53.083116907Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.08353147Z
2026-02-21T03:09:53.083539501Z--- OUTPUT ---
2026-02-21T03:09:53.083588111Z
2026-02-21T03:09:53.083613235Z__________________ test_immediate_masking_in_streaming_output __________________
2026-02-21T03:09:53.083615451Z
2026-02-21T03:09:53.083627521Z def test_immediate_masking_in_streaming_output():
2026-02-21T03:09:53.083632935Z """Test that masking applies immediately to streaming output."""
2026-02-21T03:09:53.084101186Z
2026-02-21T03:09:53.084108733Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.08414806Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.08415282Z
2026-02-21T03:09:53.08415637Z # Create job directory
2026-02-21T03:09:53.084451433Z job_dir = work_dir / "job"
2026-02-21T03:09:53.084456366Z job_dir.mkdir()
2026-02-21T03:09:53.084747972Z
2026-02-21T03:09:53.084806843Z # Create a script that outputs continuously
2026-02-21T03:09:53.084814973Z test_script = job_dir / "streaming_test.py"
2026-02-21T03:09:53.084818716Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T03:09:53.08490436Z import socket
2026-02-21T03:09:53.084996754Z import json
2026-02-21T03:09:53.085401047Z import struct
2026-02-21T03:09:53.085405891Z import os
2026-02-21T03:09:53.085409207Z import time
2026-02-21T03:09:53.085412188Z import sys
2026-02-21T03:09:53.085414778Z
2026-02-21T03:09:53.085418955Z # Flush output immediately
2026-02-21T03:09:53.085427098Z sys.stdout.flush()
2026-02-21T03:09:53.085429452Z
2026-02-21T03:09:53.085433352Z secret_value = "streaming-secret-999"
2026-02-21T03:09:53.085690731Z
2026-02-21T03:09:53.085760351Z # Output the secret multiple times before registration
2026-02-21T03:09:53.085887299Z for i in range(3):
2026-02-21T03:09:53.085895449Z print(f"Before [{i}]: secret={secret_value}")
2026-02-21T03:09:53.085899742Z sys.stdout.flush()
2026-02-21T03:09:53.086276399Z time.sleep(0.1)
2026-02-21T03:09:53.086280279Z
2026-02-21T03:09:53.086283479Z # Register the secret
2026-02-21T03:09:53.086643729Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T03:09:53.086648156Z if socket_path:
2026-02-21T03:09:53.086651956Z print("\\nRegistering secret...")
2026-02-21T03:09:53.086660789Z sys.stdout.flush()
2026-02-21T03:09:53.086663444Z
2026-02-21T03:09:53.087387646Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T03:09:53.087392579Z sock.connect(socket_path)
2026-02-21T03:09:53.087397899Z msg = json.dumps({'action': 'register', 'secrets': [secret_value]}).encode()
2026-02-21T03:09:53.087401516Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T03:09:53.087404783Z sock.send(msg)
2026-02-21T03:09:53.087408019Z response = sock.recv(1024)
2026-02-21T03:09:53.087766419Z sock.close()
2026-02-21T03:09:53.087770926Z
2026-02-21T03:09:53.087774629Z print("Secret registered!\\n")
2026-02-21T03:09:53.087777776Z sys.stdout.flush()
2026-02-21T03:09:53.088161536Z
2026-02-21T03:09:53.088168493Z # Wait for registration to process
2026-02-21T03:09:53.088172123Z time.sleep(0.5)
2026-02-21T03:09:53.088174583Z
2026-02-21T03:09:53.088805319Z # Output the secret multiple times after registration
2026-02-21T03:09:53.088809999Z for i in range(3):
2026-02-21T03:09:53.088813813Z print(f"After [{i}]: secret={secret_value}")
2026-02-21T03:09:53.088817233Z sys.stdout.flush()
2026-02-21T03:09:53.088821056Z time.sleep(0.1)
2026-02-21T03:09:53.088824563Z """)
2026-02-21T03:09:53.088827843Z test_script.chmod(0o755)
2026-02-21T03:09:53.088830283Z
2026-02-21T03:09:53.089239533Z # Run the job with an explicit empty secrets list
2026-02-21T03:09:53.089244777Z result = subprocess.run(
2026-02-21T03:09:53.089248007Z [
2026-02-21T03:09:53.08962051Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.089626633Z "--runner-image", "python:3.9-alpine",
2026-02-21T03:09:53.08963178Z "--job-command", "python3 /job/streaming_test.py",
2026-02-21T03:09:53.08963553Z "--code-dir", "/job",
2026-02-21T03:09:53.08963952Z "--job-dir", "/job",
2026-02-21T03:09:53.090137855Z "--secret-values-list", "", # Empty list prevents default masking
2026-02-21T03:09:53.090143128Z ],
2026-02-21T03:09:53.090146591Z capture_output=True,
2026-02-21T03:09:53.090150195Z text=True,
2026-02-21T03:09:53.090153461Z cwd=work_dir,
2026-02-21T03:09:53.090452401Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.090459084Z )
2026-02-21T03:09:53.090461891Z
2026-02-21T03:09:53.090466724Z print("\n--- STREAMING OUTPUT ---")
2026-02-21T03:09:53.090470454Z print(result.stdout)
2026-02-21T03:09:53.090472981Z
2026-02-21T03:09:53.092487856Z> assert result.returncode == 0
2026-02-21T03:09:53.092493066ZE AssertionError: assert 1 == 0
2026-02-21T03:09:53.092501192ZE + 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-21T03:09:53.092504209Z
2026-02-21T03:09:53.092509359Ztests/test_dynamic_secret_masking.py:305: AssertionError
2026-02-21T03:09:53.092514096Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.092516459Z
2026-02-21T03:09:53.092519793Z--- STREAMING OUTPUT ---
2026-02-21T03:09:53.092521833Z
2026-02-21T03:09:53.092526519Z_______________________ test_dynamic_secret_registration _______________________
2026-02-21T03:09:53.092528479Z
2026-02-21T03:09:53.092532316Z def test_dynamic_secret_registration():
2026-02-21T03:09:53.092537453Z """Test that jobs can register secrets dynamically via socket."""
2026-02-21T03:09:53.092540093Z
2026-02-21T03:09:53.092543873Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.092547156Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.093169358Z
2026-02-21T03:09:53.093178315Z # Create job directory
2026-02-21T03:09:53.093186745Z job_dir = work_dir / "job"
2026-02-21T03:09:53.093190535Z job_dir.mkdir()
2026-02-21T03:09:53.093192945Z
2026-02-21T03:09:53.093199052Z # Create a test script that fetches and uses a secret
2026-02-21T03:09:53.093203722Z test_script = job_dir / "dynamic_secret_test.sh"
2026-02-21T03:09:53.093210108Z test_script.write_text("""#!/bin/sh
2026-02-21T03:09:53.093214115Z # Simulate fetching a secret from an external service
2026-02-21T03:09:53.093218188Z FETCHED_SECRET="super-dynamic-secret-12345"
2026-02-21T03:09:53.093469931Z
2026-02-21T03:09:53.093477038Z echo "Before registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-21T03:09:53.093479178Z
2026-02-21T03:09:53.093483174Z # Register the secret so it gets masked
2026-02-21T03:09:53.093486782Z if [ -n "$REACTORCIDE_SECRETS_SOCKET" ]; then
2026-02-21T03:09:53.093936869Z echo "Socket available at: $REACTORCIDE_SECRETS_SOCKET"
2026-02-21T03:09:53.094042306Z # Use Python to register the secret
2026-02-21T03:09:53.094047133Z python3 -c "
2026-02-21T03:09:53.094050663Z import socket, json, struct
2026-02-21T03:09:53.09405907Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T03:09:53.09406272Z sock.connect('$REACTORCIDE_SECRETS_SOCKET')
2026-02-21T03:09:53.094343449Z msg = json.dumps({'action': 'register', 'secrets': ['$FETCHED_SECRET']}).encode()
2026-02-21T03:09:53.09443461Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T03:09:53.094438694Z sock.send(msg)
2026-02-21T03:09:53.094442147Z response = sock.recv(1024)
2026-02-21T03:09:53.094841067Z print('Registration response:', response.decode())
2026-02-21T03:09:53.094845553Z sock.close()
2026-02-21T03:09:53.094848437Z "
2026-02-21T03:09:53.095130063Z
2026-02-21T03:09:53.09513707Z # Give the server a moment to process
2026-02-21T03:09:53.095140513Z sleep 0.5
2026-02-21T03:09:53.095458049Z else
2026-02-21T03:09:53.095464376Z echo "Warning: No secrets socket available"
2026-02-21T03:09:53.095467833Z fi
2026-02-21T03:09:53.09556136Z
2026-02-21T03:09:53.096039431Z # Now use the secret again - it should be masked
2026-02-21T03:09:53.096045152Z echo "After registration: FETCHED_SECRET=$FETCHED_SECRET"
2026-02-21T03:09:53.096050472Z echo "Using secret in command: curl -H 'Authorization: Bearer $FETCHED_SECRET' example.com"
2026-02-21T03:09:53.096053765Z """)
2026-02-21T03:09:53.096057538Z test_script.chmod(0o755)
2026-02-21T03:09:53.096096565Z
2026-02-21T03:09:53.096476388Z # Run the container with our test script
2026-02-21T03:09:53.096483215Z result = subprocess.run(
2026-02-21T03:09:53.096493063Z [
2026-02-21T03:09:53.096498143Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.096707877Z "--runner-image", "python:3.9-alpine", # Has Python for our registration
2026-02-21T03:09:53.096868839Z "--job-command", "sh /job/dynamic_secret_test.sh",
2026-02-21T03:09:53.096874753Z "--code-dir", "/job",
2026-02-21T03:09:53.097076378Z "--job-dir", "/job",
2026-02-21T03:09:53.097083221Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-21T03:09:53.097136299Z ],
2026-02-21T03:09:53.097635746Z capture_output=True,
2026-02-21T03:09:53.097641156Z text=True,
2026-02-21T03:09:53.097644439Z cwd=work_dir,
2026-02-21T03:09:53.097649159Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.097652753Z )
2026-02-21T03:09:53.097655316Z
2026-02-21T03:09:53.097976126Z print("STDOUT:", result.stdout)
2026-02-21T03:09:53.097980962Z print("STDERR:", result.stderr)
2026-02-21T03:09:53.097984079Z
2026-02-21T03:09:53.097990146Z> assert result.returncode == 0, f"Dynamic secret test failed with code {result.returncode}"
2026-02-21T03:09:53.098127258ZE AssertionError: Dynamic secret test failed with code 1
2026-02-21T03:09:53.098132588ZE assert 1 == 0
2026-02-21T03:09:53.098753647ZE + 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-21T03:09:53.098758404Z
2026-02-21T03:09:53.098763707Ztests/test_dynamic_secrets.py:75: AssertionError
2026-02-21T03:09:53.098768947Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.099143697ZSTDOUT:
2026-02-21T03:09:53.099150377ZSTDERR: 2026-02-21T03:05:16.429779+00:00 Configuration validation failed:
2026-02-21T03:09:53.099155257Z2026-02-21T03:05:16.429876+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.099160433Z • system: docker is not available in PATH
2026-02-21T03:09:53.099171183Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.099370695Z
2026-02-21T03:09:53.099374295Z
2026-02-21T03:09:53.099381389Z________________________ test_multiple_dynamic_secrets _________________________
2026-02-21T03:09:53.099787733Z
2026-02-21T03:09:53.099795786Z def test_multiple_dynamic_secrets():
2026-02-21T03:09:53.099800176Z """Test registering multiple secrets dynamically."""
2026-02-21T03:09:53.099802573Z
2026-02-21T03:09:53.100203246Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.100208139Z work_dir = Path(tmpdir)
2026-02-21T03:09:53.100211079Z
2026-02-21T03:09:53.10054082Z # Create job directory
2026-02-21T03:09:53.10054549Z job_dir = work_dir / "job"
2026-02-21T03:09:53.10054871Z job_dir.mkdir()
2026-02-21T03:09:53.100551256Z
2026-02-21T03:09:53.10055461Z # Create test script
2026-02-21T03:09:53.100938769Z test_script = job_dir / "multi_secret_test.py"
2026-02-21T03:09:53.100949229Z test_script.write_text("""#!/usr/bin/env python3
2026-02-21T03:09:53.100952676Z import socket
2026-02-21T03:09:53.10130712Z import json
2026-02-21T03:09:53.101311997Z import struct
2026-02-21T03:09:53.101315093Z import os
2026-02-21T03:09:53.101318277Z import time
2026-02-21T03:09:53.10132078Z
2026-02-21T03:09:53.101653696Z # Simulate getting multiple secrets
2026-02-21T03:09:53.101658346Z secrets = [
2026-02-21T03:09:53.10166272Z "database-password-abc123",
2026-02-21T03:09:53.101666236Z "api-key-def456",
2026-02-21T03:09:53.102017476Z "webhook-secret-ghi789"
2026-02-21T03:09:53.102023996Z ]
2026-02-21T03:09:53.102026599Z
2026-02-21T03:09:53.102310869Z print("Obtained secrets:", secrets)
2026-02-21T03:09:53.102315292Z
2026-02-21T03:09:53.102318656Z # Register them all at once
2026-02-21T03:09:53.103004622Z socket_path = os.environ.get('REACTORCIDE_SECRETS_SOCKET')
2026-02-21T03:09:53.103013332Z if socket_path:
2026-02-21T03:09:53.103017469Z sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
2026-02-21T03:09:53.103104249Z sock.connect(socket_path)
2026-02-21T03:09:53.103109063Z
2026-02-21T03:09:53.103114419Z msg = json.dumps({'action': 'register', 'secrets': secrets}).encode()
2026-02-21T03:09:53.103195177Z sock.send(struct.pack('!I', len(msg)))
2026-02-21T03:09:53.103439376Z sock.send(msg)
2026-02-21T03:09:53.103444562Z
2026-02-21T03:09:53.103448952Z response = sock.recv(1024)
2026-02-21T03:09:53.103454222Z print("Registration response:", response.decode())
2026-02-21T03:09:53.103770415Z sock.close()
2026-02-21T03:09:53.103774539Z
2026-02-21T03:09:53.103778336Z # Wait for processing
2026-02-21T03:09:53.103782356Z time.sleep(0.5)
2026-02-21T03:09:53.103784726Z
2026-02-21T03:09:53.104093555Z # Now use them - should all be masked
2026-02-21T03:09:53.104100138Z print("Database connection: password=database-password-abc123")
2026-02-21T03:09:53.104218659Z print("API header: X-API-Key=api-key-def456")
2026-02-21T03:09:53.104224416Z print("Webhook validation: secret=webhook-secret-ghi789")
2026-02-21T03:09:53.104228069Z else:
2026-02-21T03:09:53.104232276Z print("No secrets socket available")
2026-02-21T03:09:53.104235986Z """)
2026-02-21T03:09:53.104525036Z test_script.chmod(0o755)
2026-02-21T03:09:53.104629959Z
2026-02-21T03:09:53.104635973Z # Run the test
2026-02-21T03:09:53.10464072Z result = subprocess.run(
2026-02-21T03:09:53.10464439Z [
2026-02-21T03:09:53.105114597Z sys.executable, "-m", "src.cli", "run",
2026-02-21T03:09:53.105120291Z "--runner-image", "python:3.9-alpine",
2026-02-21T03:09:53.105444587Z "--job-command", "python3 /job/multi_secret_test.py",
2026-02-21T03:09:53.105450904Z "--code-dir", "/job",
2026-02-21T03:09:53.10545463Z "--job-dir", "/job",
2026-02-21T03:09:53.105628215Z "--secret-values-list", "", # Empty list to prevent default masking
2026-02-21T03:09:53.105861741Z ],
2026-02-21T03:09:53.105870411Z capture_output=True,
2026-02-21T03:09:53.105876231Z text=True,
2026-02-21T03:09:53.105879921Z cwd=work_dir,
2026-02-21T03:09:53.106162131Z env={**subprocess.os.environ, "PYTHONPATH": str(Path(__file__).parent.parent)}
2026-02-21T03:09:53.106167647Z )
2026-02-21T03:09:53.106171754Z
2026-02-21T03:09:53.106178761Z print("STDOUT:", result.stdout)
2026-02-21T03:09:53.106823553Z print("STDERR:", result.stderr)
2026-02-21T03:09:53.10682921Z
2026-02-21T03:09:53.106833577Z> assert result.returncode == 0
2026-02-21T03:09:53.106837397ZE AssertionError: assert 1 == 0
2026-02-21T03:09:53.10684542ZE + 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-21T03:09:53.10684798Z
2026-02-21T03:09:53.106852313Ztests/test_dynamic_secrets.py:233: AssertionError
2026-02-21T03:09:53.106857247Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.10686058ZSTDOUT:
2026-02-21T03:09:53.106870473ZSTDERR: 2026-02-21T03:05:17.233288+00:00 Configuration validation failed:
2026-02-21T03:09:53.107373601Z2026-02-21T03:05:17.233431+00:00 ❌ Configuration has errors:
2026-02-21T03:09:53.108144591Z • system: docker is not available in PATH
2026-02-21T03:09:53.108150808Z 💡 Install docker: https://docs.docker.com/get-docker/
2026-02-21T03:09:53.108153268Z
2026-02-21T03:09:53.108155358Z
2026-02-21T03:09:53.108160215Z__________________ TestEvalCommand.test_basic_eval_with_match __________________
2026-02-21T03:09:53.108162478Z
2026-02-21T03:09:53.108166858Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc091708cd0>
2026-02-21T03:09:53.108226843Ztemp_dirs = (PosixPath('/tmp/tmpowcrghii/ci'), PosixPath('/tmp/tmpowcrghii/src'), PosixPath('/tmp/tmpowcrghii/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpowcrghii/triggers.json'))
2026-02-21T03:09:53.108229759Z
2026-02-21T03:09:53.108234239Z def test_basic_eval_with_match(self, temp_dirs):
2026-02-21T03:09:53.108238336Z """Test eval command with a matching job definition."""
2026-02-21T03:09:53.108242023Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.108244626Z
2026-02-21T03:09:53.10824834Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.108252376Z "name": "test",
2026-02-21T03:09:53.108480765Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.108490488Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.108497425Z })
2026-02-21T03:09:53.108905505Z
2026-02-21T03:09:53.108914948Z result = runner.invoke(app, [
2026-02-21T03:09:53.108919955Z "eval",
2026-02-21T03:09:53.108924698Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.109453911Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.109459327Z "--event-type", "push",
2026-02-21T03:09:53.109774529Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.109780123Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.109784606Z ])
2026-02-21T03:09:53.109946719Z
2026-02-21T03:09:53.109952955Z assert result.exit_code == 0
2026-02-21T03:09:53.109956372Z> assert triggers_file.exists()
2026-02-21T03:09:53.109959542ZE AssertionError: assert False
2026-02-21T03:09:53.110685791ZE + where False = exists()
2026-02-21T03:09:53.110694057ZE + where exists = PosixPath('/tmp/tmpowcrghii/triggers.json').exists
2026-02-21T03:09:53.110697222Z
2026-02-21T03:09:53.110701875Ztests/test_eval_cli.py:69: AssertionError
2026-02-21T03:09:53.110706778Z__________________ TestEvalCommand.test_eval_multiple_matches __________________
2026-02-21T03:09:53.110709188Z
2026-02-21T03:09:53.111210183Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc091853890>
2026-02-21T03:09:53.11122379Ztemp_dirs = (PosixPath('/tmp/tmpze9462tn/ci'), PosixPath('/tmp/tmpze9462tn/src'), PosixPath('/tmp/tmpze9462tn/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpze9462tn/triggers.json'))
2026-02-21T03:09:53.111226453Z
2026-02-21T03:09:53.111231493Z def test_eval_multiple_matches(self, temp_dirs):
2026-02-21T03:09:53.111236767Z """Test eval command with multiple matching definitions."""
2026-02-21T03:09:53.111240983Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.111243377Z
2026-02-21T03:09:53.111578537Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.11158433Z "name": "test",
2026-02-21T03:09:53.111588533Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.11161027Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.111845003Z })
2026-02-21T03:09:53.111851373Z _write_yaml(jobs_dir / "lint.yaml", {
2026-02-21T03:09:53.111855283Z "name": "lint",
2026-02-21T03:09:53.111859489Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.112246012Z "job": {"image": "python:3.11", "command": "ruff check"},
2026-02-21T03:09:53.112251302Z })
2026-02-21T03:09:53.112254709Z
2026-02-21T03:09:53.112258706Z result = runner.invoke(app, [
2026-02-21T03:09:53.112641843Z "eval",
2026-02-21T03:09:53.11264721Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.112651316Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.112654973Z "--event-type", "push",
2026-02-21T03:09:53.113230365Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.113236981Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.113240885Z ])
2026-02-21T03:09:53.113243885Z
2026-02-21T03:09:53.113504364Z assert result.exit_code == 0
2026-02-21T03:09:53.113508968Z> assert triggers_file.exists()
2026-02-21T03:09:53.113511991ZE AssertionError: assert False
2026-02-21T03:09:53.113515218ZE + where False = exists()
2026-02-21T03:09:53.113945554ZE + where exists = PosixPath('/tmp/tmpze9462tn/triggers.json').exists
2026-02-21T03:09:53.113949771Z
2026-02-21T03:09:53.113954192Ztests/test_eval_cli.py:157: AssertionError
2026-02-21T03:09:53.114161359Z___________________ TestEvalCommand.test_eval_branch_filter ____________________
2026-02-21T03:09:53.114164826Z
2026-02-21T03:09:53.114169426Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc0916439b0>
2026-02-21T03:09:53.11707567Ztemp_dirs = (PosixPath('/tmp/tmpdk5hrabh/ci'), PosixPath('/tmp/tmpdk5hrabh/src'), PosixPath('/tmp/tmpdk5hrabh/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpdk5hrabh/triggers.json'))
2026-02-21T03:09:53.117080333Z
2026-02-21T03:09:53.117085644Z def test_eval_branch_filter(self, temp_dirs):
2026-02-21T03:09:53.117090611Z """Test eval command respects branch filtering."""
2026-02-21T03:09:53.118274867Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.118280244Z
2026-02-21T03:09:53.118284804Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-21T03:09:53.118289634Z "name": "deploy",
2026-02-21T03:09:53.118296431Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-21T03:09:53.118301251Z "job": {"image": "deploy:latest", "command": "deploy.sh"},
2026-02-21T03:09:53.118304984Z })
2026-02-21T03:09:53.118308294Z
2026-02-21T03:09:53.118312411Z # Push to feature branch should not match
2026-02-21T03:09:53.118316054Z result = runner.invoke(app, [
2026-02-21T03:09:53.118319844Z "eval",
2026-02-21T03:09:53.118323931Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.118327817Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.118331157Z "--event-type", "push",
2026-02-21T03:09:53.118334335Z "--branch", "feature/foo",
2026-02-21T03:09:53.118337968Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.118341305Z ])
2026-02-21T03:09:53.118349368Z
2026-02-21T03:09:53.118352712Z assert result.exit_code == 0
2026-02-21T03:09:53.118356432Z assert "No jobs matched" in result.stdout
2026-02-21T03:09:53.118359968Z assert not triggers_file.exists()
2026-02-21T03:09:53.118362175Z
2026-02-21T03:09:53.118366172Z # Push to [REDACTED] should match
2026-02-21T03:09:53.118369568Z result = runner.invoke(app, [
2026-02-21T03:09:53.118373038Z "eval",
2026-02-21T03:09:53.118378398Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.118381958Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.118385188Z "--event-type", "push",
2026-02-21T03:09:53.118388995Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.118392755Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.118395855Z ])
2026-02-21T03:09:53.118398098Z
2026-02-21T03:09:53.118401358Z assert result.exit_code == 0
2026-02-21T03:09:53.118404478Z> assert triggers_file.exists()
2026-02-21T03:09:53.118407185ZE AssertionError: assert False
2026-02-21T03:09:53.118412308ZE + where False = exists()
2026-02-21T03:09:53.118417122ZE + where exists = PosixPath('/tmp/tmpdk5hrabh/triggers.json').exists
2026-02-21T03:09:53.118419335Z
2026-02-21T03:09:53.118423062Ztests/test_eval_cli.py:202: AssertionError
2026-02-21T03:09:53.118427858Z_________________ TestEvalCommand.test_eval_full_event_context _________________
2026-02-21T03:09:53.118429795Z
2026-02-21T03:09:53.118434415Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc0917cb8a0>
2026-02-21T03:09:53.118549583Ztemp_dirs = (PosixPath('/tmp/tmpxsesnm40/ci'), PosixPath('/tmp/tmpxsesnm40/src'), PosixPath('/tmp/tmpxsesnm40/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpxsesnm40/triggers.json'))
2026-02-21T03:09:53.118552623Z
2026-02-21T03:09:53.11855692Z def test_eval_full_event_context(self, temp_dirs):
2026-02-21T03:09:53.118562146Z """Test eval command passes full event context to triggers."""
2026-02-21T03:09:53.118566153Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.119296036Z
2026-02-21T03:09:53.119313566Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.119318256Z "name": "test",
2026-02-21T03:09:53.119322613Z "triggers": {"events": ["pull_request_opened"]},
2026-02-21T03:09:53.119368061Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.119373151Z "environment": {"BUILD_TYPE": "test"},
2026-02-21T03:09:53.119377017Z })
2026-02-21T03:09:53.119379411Z
2026-02-21T03:09:53.119384361Z # Create .git dir so eval doesn't try to clone from the fake URL
2026-02-21T03:09:53.119388058Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.119390444Z
2026-02-21T03:09:53.119401614Z result = runner.invoke(app, [
2026-02-21T03:09:53.119404958Z "eval",
2026-02-21T03:09:53.119408608Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.119412348Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.119415858Z "--event-type", "pull_request_opened",
2026-02-21T03:09:53.119537456Z "--branch", "feature/foo",
2026-02-21T03:09:53.119544019Z "--pr-base-ref", "[REDACTED]",
2026-02-21T03:09:53.119547649Z "--pr-number", "42",
2026-02-21T03:09:53.119553063Z "--source-url", "https://[REDACTED].com/org/repo.git",
2026-02-21T03:09:53.119563983Z "--source-ref", "abc123",
2026-02-21T03:09:53.119833068Z "--ci-source-url", "https://[REDACTED].com/org/ci.git",
2026-02-21T03:09:53.119846422Z "--ci-source-ref", "def456",
2026-02-21T03:09:53.119851186Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.119855636Z ])
2026-02-21T03:09:53.120275122Z
2026-02-21T03:09:53.120282686Z assert result.exit_code == 0
2026-02-21T03:09:53.120286332Z> assert triggers_file.exists()
2026-02-21T03:09:53.120289862ZE AssertionError: assert False
2026-02-21T03:09:53.120293252ZE + where False = exists()
2026-02-21T03:09:53.120453104ZE + where exists = PosixPath('/tmp/tmpxsesnm40/triggers.json').exists
2026-02-21T03:09:53.120457877Z
2026-02-21T03:09:53.120664539Ztests/test_eval_cli.py:239: AssertionError
2026-02-21T03:09:53.120671266Z_________________ TestEvalCommand.test_eval_with_changed_files _________________
2026-02-21T03:09:53.120673829Z
2026-02-21T03:09:53.120678513Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc0915dab50>
2026-02-21T03:09:53.120687709Ztemp_dirs = (PosixPath('/tmp/tmpiqdjkjr1/ci'), PosixPath('/tmp/tmpiqdjkjr1/src'), PosixPath('/tmp/tmpiqdjkjr1/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpiqdjkjr1/triggers.json'))
2026-02-21T03:09:53.121099919Z
2026-02-21T03:09:53.121108067Z def test_eval_with_changed_files(self, temp_dirs):
2026-02-21T03:09:53.12111352Z """Test eval command uses git changed files for path filtering."""
2026-02-21T03:09:53.12111777Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.12112067Z
2026-02-21T03:09:53.12112485Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.121482517Z "name": "test",
2026-02-21T03:09:53.121498381Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.121501814Z "paths": {"include": ["src/**"]},
2026-02-21T03:09:53.121687168Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.121697108Z })
2026-02-21T03:09:53.121703658Z
2026-02-21T03:09:53.121709042Z # Create a fake .git dir so the code tries to get changed files
2026-02-21T03:09:53.121712992Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.121821856Z
2026-02-21T03:09:53.121835999Z with patch("src.workflow.changed_files", return_value=["src/[REDACTED].py"]):
2026-02-21T03:09:53.121842423Z result = runner.invoke(app, [
2026-02-21T03:09:53.121989727Z "eval",
2026-02-21T03:09:53.121995615Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.121999338Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.122714461Z "--event-type", "push",
2026-02-21T03:09:53.122721741Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.122726234Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.122730534Z ])
2026-02-21T03:09:53.122733044Z
2026-02-21T03:09:53.122740964Z assert result.exit_code == 0
2026-02-21T03:09:53.122744677Z> assert triggers_file.exists()
2026-02-21T03:09:53.123198302ZE AssertionError: assert False
2026-02-21T03:09:53.123202636ZE + where False = exists()
2026-02-21T03:09:53.123212246ZE + where exists = PosixPath('/tmp/tmpiqdjkjr1/triggers.json').exists
2026-02-21T03:09:53.123214702Z
2026-02-21T03:09:53.123219182Ztests/test_eval_cli.py:287: AssertionError
2026-02-21T03:09:53.123224296Z_____________ TestEvalCommand.test_eval_pr_uses_base_ref_for_diff ______________
2026-02-21T03:09:53.123226866Z
2026-02-21T03:09:53.123934073Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc0918f2990>
2026-02-21T03:09:53.123944309Ztemp_dirs = (PosixPath('/tmp/tmpfof10vz7/ci'), PosixPath('/tmp/tmpfof10vz7/src'), PosixPath('/tmp/tmpfof10vz7/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpfof10vz7/triggers.json'))
2026-02-21T03:09:53.123946743Z
2026-02-21T03:09:53.123951389Z def test_eval_pr_uses_base_ref_for_diff(self, temp_dirs):
2026-02-21T03:09:53.123956736Z """Test that PR events use pr_base_ref for changed files diff."""
2026-02-21T03:09:53.123960823Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.123963623Z
2026-02-21T03:09:53.123967346Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.123971956Z "name": "test",
2026-02-21T03:09:53.123976129Z "triggers": {"events": ["pull_request_opened"]},
2026-02-21T03:09:53.124221518Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.124227105Z })
2026-02-21T03:09:53.124229758Z
2026-02-21T03:09:53.124234288Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.124836537Z
2026-02-21T03:09:53.124845031Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-21T03:09:53.124849397Z result = runner.invoke(app, [
2026-02-21T03:09:53.124853351Z "eval",
2026-02-21T03:09:53.124857611Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.124861431Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.124866001Z "--event-type", "pull_request_opened",
2026-02-21T03:09:53.124870484Z "--branch", "feature/foo",
2026-02-21T03:09:53.124875891Z "--pr-base-ref", "[REDACTED]",
2026-02-21T03:09:53.125315785Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.125322041Z ])
2026-02-21T03:09:53.125325205Z
2026-02-21T03:09:53.125331648Z # Verify it was called with origin/[REDACTED] as the from_ref
2026-02-21T03:09:53.125335578Z> mock_changed.assert_called_once_with(
2026-02-21T03:09:53.125340325Z "origin/[REDACTED]", "HEAD", str(src_dir)
2026-02-21T03:09:53.125969457Z )
2026-02-21T03:09:53.125973941Z
2026-02-21T03:09:53.125977827Ztests/test_eval_cli.py:344:
2026-02-21T03:09:53.125981867Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.125987054Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-21T03:09:53.125990907Z return self.assert_called_with(*args, **kwargs)
2026-02-21T03:09:53.125994647Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.125997791Z
2026-02-21T03:09:53.126001581Zself = <MagicMock name='changed_files' id='140465047020480'>
2026-02-21T03:09:53.126007394Zargs = ('origin/[REDACTED]', 'HEAD', '/tmp/tmpfof10vz7/src'), kwargs = {}
2026-02-21T03:09:53.126020887Zexpected = call('origin/[REDACTED]', 'HEAD', '/tmp/tmpfof10vz7/src')
2026-02-21T03:09:53.127765417Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmpfof10vz7/src')
2026-02-21T03:09:53.127773137Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fc091156c00>
2026-02-21T03:09:53.127776953Zcause = None
2026-02-21T03:09:53.12777954Z
2026-02-21T03:09:53.127784308Z def assert_called_with(self, /, *args, **kwargs):
2026-02-21T03:09:53.127790924Z """assert that the last call was made with the specified arguments.
2026-02-21T03:09:53.127794001Z
2026-02-21T03:09:53.127798354Z Raises an AssertionError if the args and keyword args passed in are
2026-02-21T03:09:53.127802208Z different to the last call to the mock."""
2026-02-21T03:09:53.127811244Z if self.call_args is None:
2026-02-21T03:09:53.127815978Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-21T03:09:53.13129307Z actual = 'not called.'
2026-02-21T03:09:53.131310113Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-21T03:09:53.131317013Z % (expected, actual))
2026-02-21T03:09:53.131323883Z raise AssertionError(error_message)
2026-02-21T03:09:53.131328396Z
2026-02-21T03:09:53.131334176Z def _error_message():
2026-02-21T03:09:53.13134746Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-21T03:09:53.13135392Z return msg
2026-02-21T03:09:53.131361263Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-21T03:09:53.131367136Z actual = self._call_matcher(self.call_args)
2026-02-21T03:09:53.131443397Z if actual != expected:
2026-02-21T03:09:53.131451141Z cause = expected if isinstance(expected, Exception) else None
2026-02-21T03:09:53.131456914Z> raise AssertionError(_error_message()) from cause
2026-02-21T03:09:53.131462497ZE AssertionError: expected call not found.
2026-02-21T03:09:53.131472024ZE Expected: changed_files('origin/[REDACTED]', 'HEAD', '/tmp/tmpfof10vz7/src')
2026-02-21T03:09:53.131481787ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmpfof10vz7/src')
2026-02-21T03:09:53.131485221Z
2026-02-21T03:09:53.131490921Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-21T03:09:53.131498257Z___________ TestEvalCommand.test_eval_push_uses_head_parent_for_diff ___________
2026-02-21T03:09:53.131501747Z
2026-02-21T03:09:53.131508341Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc0918f2a80>
2026-02-21T03:09:53.131521374Ztemp_dirs = (PosixPath('/tmp/tmppgjlr3nc/ci'), PosixPath('/tmp/tmppgjlr3nc/src'), PosixPath('/tmp/tmppgjlr3nc/ci/.reactorcide/jobs'), PosixPath('/tmp/tmppgjlr3nc/triggers.json'))
2026-02-21T03:09:53.131526814Z
2026-02-21T03:09:53.131533628Z def test_eval_push_uses_head_parent_for_diff(self, temp_dirs):
2026-02-21T03:09:53.131540482Z """Test that push events use HEAD^ for changed files diff."""
2026-02-21T03:09:53.131546489Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.131550905Z
2026-02-21T03:09:53.131556919Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.131564629Z "name": "test",
2026-02-21T03:09:53.131568319Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.131572629Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.131576629Z })
2026-02-21T03:09:53.131578952Z
2026-02-21T03:09:53.131582212Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.131584522Z
2026-02-21T03:09:53.131610879Z with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
2026-02-21T03:09:53.131615242Z result = runner.invoke(app, [
2026-02-21T03:09:53.131619379Z "eval",
2026-02-21T03:09:53.131623549Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.131627276Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.131630759Z "--event-type", "push",
2026-02-21T03:09:53.131634819Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.131638962Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.131642209Z ])
2026-02-21T03:09:53.131644459Z
2026-02-21T03:09:53.131647796Z> mock_changed.assert_called_once_with(
2026-02-21T03:09:53.131651026Z "HEAD^", "HEAD", str(src_dir)
2026-02-21T03:09:53.131654149Z )
2026-02-21T03:09:53.131656333Z
2026-02-21T03:09:53.131659809Ztests/test_eval_cli.py:372:
2026-02-21T03:09:53.131663399Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.132078723Z/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
2026-02-21T03:09:53.132084237Z return self.assert_called_with(*args, **kwargs)
2026-02-21T03:09:53.132087207Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.132089437Z
2026-02-21T03:09:53.13209313Zself = <MagicMock name='changed_files' id='140465047022160'>
2026-02-21T03:09:53.13209658Zargs = ('HEAD^', 'HEAD', '/tmp/tmppgjlr3nc/src'), kwargs = {}
2026-02-21T03:09:53.1321009Zexpected = call('HEAD^', 'HEAD', '/tmp/tmppgjlr3nc/src')
2026-02-21T03:09:53.132106387Zactual = call('[REDACTED]', 'HEAD', '/tmp/tmppgjlr3nc/src')
2026-02-21T03:09:53.132110927Z_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7fc091156980>
2026-02-21T03:09:53.132114347Zcause = None
2026-02-21T03:09:53.132116233Z
2026-02-21T03:09:53.13212497Z def assert_called_with(self, /, *args, **kwargs):
2026-02-21T03:09:53.132131393Z """assert that the last call was made with the specified arguments.
2026-02-21T03:09:53.132133993Z
2026-02-21T03:09:53.132139383Z Raises an AssertionError if the args and keyword args passed in are
2026-02-21T03:09:53.13214528Z different to the last call to the mock."""
2026-02-21T03:09:53.132150207Z if self.call_args is None:
2026-02-21T03:09:53.1321568Z expected = self._format_mock_call_signature(args, kwargs)
2026-02-21T03:09:53.132164747Z actual = 'not called.'
2026-02-21T03:09:53.132259091Z error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
2026-02-21T03:09:53.132265018Z % (expected, actual))
2026-02-21T03:09:53.132268624Z raise AssertionError(error_message)
2026-02-21T03:09:53.132271061Z
2026-02-21T03:09:53.132274704Z def _error_message():
2026-02-21T03:09:53.132279134Z msg = self._format_mock_failure_message(args, kwargs)
2026-02-21T03:09:53.132282441Z return msg
2026-02-21T03:09:53.13285517Z expected = self._call_matcher(_Call((args, kwargs), two=True))
2026-02-21T03:09:53.132865697Z actual = self._call_matcher(self.call_args)
2026-02-21T03:09:53.132872494Z if actual != expected:
2026-02-21T03:09:53.13287937Z cause = expected if isinstance(expected, Exception) else None
2026-02-21T03:09:53.13288526Z> raise AssertionError(_error_message()) from cause
2026-02-21T03:09:53.132890834ZE AssertionError: expected call not found.
2026-02-21T03:09:53.13289746ZE Expected: changed_files('HEAD^', 'HEAD', '/tmp/tmppgjlr3nc/src')
2026-02-21T03:09:53.133236963ZE Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmppgjlr3nc/src')
2026-02-21T03:09:53.133241227Z
2026-02-21T03:09:53.133246117Z/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
2026-02-21T03:09:53.133256334Z___________ TestEvalCommand.test_eval_no_git_dir_skips_changed_files ___________
2026-02-21T03:09:53.133258977Z
2026-02-21T03:09:53.133698708Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc091867310>
2026-02-21T03:09:53.133712405Ztemp_dirs = (PosixPath('/tmp/tmpxo2yekre/ci'), PosixPath('/tmp/tmpxo2yekre/src'), PosixPath('/tmp/tmpxo2yekre/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpxo2yekre/triggers.json'))
2026-02-21T03:09:53.133716672Z
2026-02-21T03:09:53.133723595Z def test_eval_no_git_dir_skips_changed_files(self, temp_dirs):
2026-02-21T03:09:53.134089425Z """Test that eval skips changed files detection when no .git dir exists."""
2026-02-21T03:09:53.134099921Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.134104918Z
2026-02-21T03:09:53.134384778Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.134392741Z "name": "test",
2026-02-21T03:09:53.134398718Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.134404528Z "paths": {"include": ["src/**"]},
2026-02-21T03:09:53.134412461Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.136509757Z })
2026-02-21T03:09:53.136514563Z
2026-02-21T03:09:53.136520927Z # No .git directory - should skip changed files and still match
2026-02-21T03:09:53.136525337Z # (path filtering is skipped when changed_files is None)
2026-02-21T03:09:53.13652963Z result = runner.invoke(app, [
2026-02-21T03:09:53.136533643Z "eval",
2026-02-21T03:09:53.13653766Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.136541233Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.13654468Z "--event-type", "push",
2026-02-21T03:09:53.13654881Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.136552164Z ])
2026-02-21T03:09:53.136565548Z
2026-02-21T03:09:53.136569258Z assert result.exit_code == 0
2026-02-21T03:09:53.136572471Z> assert triggers_file.exists()
2026-02-21T03:09:53.136576481ZE AssertionError: assert False
2026-02-21T03:09:53.136579624ZE + where False = exists()
2026-02-21T03:09:53.136584571ZE + where exists = PosixPath('/tmp/tmpxo2yekre/triggers.json').exists
2026-02-21T03:09:53.136586834Z
2026-02-21T03:09:53.136613264Ztests/test_eval_cli.py:400: AssertionError
2026-02-21T03:09:53.136617861Z______________________ TestEvalCommand.test_eval_env_vars ______________________
2026-02-21T03:09:53.136620148Z
2026-02-21T03:09:53.136624478Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc091866eb0>
2026-02-21T03:09:53.136633454Ztemp_dirs = (PosixPath('/tmp/tmptpx4p6go/ci'), PosixPath('/tmp/tmptpx4p6go/src'), PosixPath('/tmp/tmptpx4p6go/ci/.reactorcide/jobs'), PosixPath('/tmp/tmptpx4p6go/triggers.json'))
2026-02-21T03:09:53.136635541Z
2026-02-21T03:09:53.137048238Z def test_eval_env_vars(self, temp_dirs):
2026-02-21T03:09:53.137055258Z """Test that eval reads options from environment variables."""
2026-02-21T03:09:53.137325478Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.137329618Z
2026-02-21T03:09:53.137333501Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.137337638Z "name": "test",
2026-02-21T03:09:53.137341458Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.13782796Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.13783301Z })
2026-02-21T03:09:53.137836266Z
2026-02-21T03:09:53.13783981Z env = {
2026-02-21T03:09:53.137844546Z "REACTORCIDE_CI_SOURCE_DIR": str(ci_dir),
2026-02-21T03:09:53.137850476Z "REACTORCIDE_SOURCE_DIR": str(src_dir),
2026-02-21T03:09:53.137891883Z "REACTORCIDE_EVENT_TYPE": "push",
2026-02-21T03:09:53.138448081Z "REACTORCIDE_BRANCH": "[REDACTED]",
2026-02-21T03:09:53.138453567Z }
2026-02-21T03:09:53.138456017Z
2026-02-21T03:09:53.138830411Z result = runner.invoke(app, [
2026-02-21T03:09:53.138835284Z "eval",
2026-02-21T03:09:53.138839208Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.138842349Z ], env=env)
2026-02-21T03:09:53.138844639Z
2026-02-21T03:09:53.138848329Z assert result.exit_code == 0
2026-02-21T03:09:53.139121801Z> assert triggers_file.exists()
2026-02-21T03:09:53.139475955ZE AssertionError: assert False
2026-02-21T03:09:53.139482061ZE + where False = exists()
2026-02-21T03:09:53.139488325ZE + where exists = PosixPath('/tmp/tmptpx4p6go/triggers.json').exists
2026-02-21T03:09:53.139491738Z
2026-02-21T03:09:53.139509461Ztests/test_eval_cli.py:425: AssertionError
2026-02-21T03:09:53.139910341Z______________ TestEvalCommand.test_eval_job_priority_and_timeout ______________
2026-02-21T03:09:53.139916141Z
2026-02-21T03:09:53.139923234Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc09185f1e0>
2026-02-21T03:09:53.139932058Ztemp_dirs = (PosixPath('/tmp/tmpmrn21ic7/ci'), PosixPath('/tmp/tmpmrn21ic7/src'), PosixPath('/tmp/tmpmrn21ic7/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpmrn21ic7/triggers.json'))
2026-02-21T03:09:53.139934141Z
2026-02-21T03:09:53.140389173Z def test_eval_job_priority_and_timeout(self, temp_dirs):
2026-02-21T03:09:53.140397029Z """Test that job priority and timeout are passed through to triggers."""
2026-02-21T03:09:53.140407143Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.140409929Z
2026-02-21T03:09:53.140814096Z _write_yaml(jobs_dir / "build.yaml", {
2026-02-21T03:09:53.140824536Z "name": "build",
2026-02-21T03:09:53.140831516Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.140838146Z "job": {
2026-02-21T03:09:53.140843096Z "image": "gcc:latest",
2026-02-21T03:09:53.140848876Z "command": "make",
2026-02-21T03:09:53.141235046Z "timeout": 3600,
2026-02-21T03:09:53.14124053Z "priority": 20,
2026-02-21T03:09:53.141244817Z },
2026-02-21T03:09:53.141633117Z })
2026-02-21T03:09:53.14163721Z
2026-02-21T03:09:53.1416414Z result = runner.invoke(app, [
2026-02-21T03:09:53.142147459Z "eval",
2026-02-21T03:09:53.142154765Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.142162442Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.142166122Z "--event-type", "push",
2026-02-21T03:09:53.142170259Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.142174112Z ])
2026-02-21T03:09:53.142177152Z
2026-02-21T03:09:53.142551238Z assert result.exit_code == 0
2026-02-21T03:09:53.142555375Z
2026-02-21T03:09:53.142559265Z> with open(triggers_file) as f:
2026-02-21T03:09:53.142565048ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpmrn21ic7/triggers.json'
2026-02-21T03:09:53.14311448Z
2026-02-21T03:09:53.143122981Ztests/test_eval_cli.py:452: FileNotFoundError
2026-02-21T03:09:53.143128111Z________________ TestEvalCommand.test_eval_git_error_continues _________________
2026-02-21T03:09:53.143130584Z
2026-02-21T03:09:53.143135054Zself = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7fc091762090>
2026-02-21T03:09:53.143143521Ztemp_dirs = (PosixPath('/tmp/tmprin6cygs/ci'), PosixPath('/tmp/tmprin6cygs/src'), PosixPath('/tmp/tmprin6cygs/ci/.reactorcide/jobs'), PosixPath('/tmp/tmprin6cygs/triggers.json'))
2026-02-21T03:09:53.143145784Z
2026-02-21T03:09:53.143240628Z def test_eval_git_error_continues(self, temp_dirs):
2026-02-21T03:09:53.143629708Z """Test that git errors during changed files detection don't fail the command."""
2026-02-21T03:09:53.143636932Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.143639798Z
2026-02-21T03:09:53.143643462Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.143646985Z "name": "test",
2026-02-21T03:09:53.143653795Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.14385596Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.143861094Z })
2026-02-21T03:09:53.143863487Z
2026-02-21T03:09:53.14386786Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.144140087Z
2026-02-21T03:09:53.144148413Z with patch("src.workflow.changed_files", side_effect=Exception("git error")):
2026-02-21T03:09:53.144155297Z result = runner.invoke(app, [
2026-02-21T03:09:53.144367344Z "eval",
2026-02-21T03:09:53.144372329Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.144669725Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.144674472Z "--event-type", "push",
2026-02-21T03:09:53.144678762Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.144823616Z ])
2026-02-21T03:09:53.145193672Z
2026-02-21T03:09:53.145203022Z assert result.exit_code == 0
2026-02-21T03:09:53.145207072Z> assert triggers_file.exists()
2026-02-21T03:09:53.14527262ZE AssertionError: assert False
2026-02-21T03:09:53.145276197ZE + where False = exists()
2026-02-21T03:09:53.14528185ZE + where exists = PosixPath('/tmp/tmprin6cygs/triggers.json').exists
2026-02-21T03:09:53.145663494Z
2026-02-21T03:09:53.146045657Ztests/test_eval_cli.py:480: AssertionError
2026-02-21T03:09:53.146056807Z______ TestEvalSourcePreparation.test_eval_clones_ci_source_when_missing _______
2026-02-21T03:09:53.14606053Z
2026-02-21T03:09:53.146493061Zself = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7fc091a17610>
2026-02-21T03:09:53.146498928Z
2026-02-21T03:09:53.146505465Z def test_eval_clones_ci_source_when_missing(self):
2026-02-21T03:09:53.146513522Z """Test that eval clones CI source when the jobs dir doesn't exist."""
2026-02-21T03:09:53.146519475Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.146524949Z base = Path(tmpdir)
2026-02-21T03:09:53.146530615Z ci_dir = base / "ci" # Does not exist yet
2026-02-21T03:09:53.146720936Z src_dir = base / "src"
2026-02-21T03:09:53.146726116Z src_dir.mkdir()
2026-02-21T03:09:53.146729543Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.14673425Z triggers_file = base / "triggers.json"
2026-02-21T03:09:53.147393043Z
2026-02-21T03:09:53.14740219Z # Create a fake "remote" repo with job definitions
2026-02-21T03:09:53.147406523Z remote_dir = base / "remote"
2026-02-21T03:09:53.147410357Z remote_dir.mkdir()
2026-02-21T03:09:53.147413883Z from git import Repo
2026-02-21T03:09:53.147418377Z remote_repo = Repo.init(remote_dir)
2026-02-21T03:09:53.147716902Z remote_jobs_dir = remote_dir / ".reactorcide" / "jobs"
2026-02-21T03:09:53.147733459Z remote_jobs_dir.mkdir(parents=True)
2026-02-21T03:09:53.148130064Z _write_yaml(remote_jobs_dir / "test.yaml", {
2026-02-21T03:09:53.14813587Z "name": "test",
2026-02-21T03:09:53.14813994Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.148825682Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.148831319Z })
2026-02-21T03:09:53.148836285Z remote_repo.index.add([str(remote_jobs_dir / "test.yaml")])
2026-02-21T03:09:53.148840429Z remote_repo.index.commit("Add job def")
2026-02-21T03:09:53.148843465Z
2026-02-21T03:09:53.148847119Z result = runner.invoke(app, [
2026-02-21T03:09:53.148850726Z "eval",
2026-02-21T03:09:53.148854269Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.149108572Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.149113322Z "--event-type", "push",
2026-02-21T03:09:53.149486665Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.149492328Z "--ci-source-url", str(remote_dir),
2026-02-21T03:09:53.149495788Z "--ci-source-ref", "",
2026-02-21T03:09:53.149499591Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.150291146Z ])
2026-02-21T03:09:53.150295603Z
2026-02-21T03:09:53.150299626Z assert result.exit_code == 0
2026-02-21T03:09:53.150303153Z> assert triggers_file.exists()
2026-02-21T03:09:53.150306503ZE AssertionError: assert False
2026-02-21T03:09:53.150310123ZE + where False = exists()
2026-02-21T03:09:53.150315436ZE + where exists = PosixPath('/tmp/tmp9cc0xynv/triggers.json').exists
2026-02-21T03:09:53.150317839Z
2026-02-21T03:09:53.150659143Ztests/test_eval_cli.py:523: AssertionError
2026-02-21T03:09:53.1506674Z________ TestEvalSourcePreparation.test_eval_clones_source_when_missing ________
2026-02-21T03:09:53.15067082Z
2026-02-21T03:09:53.150675763Zself = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7fc091a17750>
2026-02-21T03:09:53.150684206Z
2026-02-21T03:09:53.150688246Z def test_eval_clones_source_when_missing(self):
2026-02-21T03:09:53.151157997Z """Test that eval clones source repo when .git dir doesn't exist."""
2026-02-21T03:09:53.151165124Z with tempfile.TemporaryDirectory() as tmpdir:
2026-02-21T03:09:53.151168811Z base = Path(tmpdir)
2026-02-21T03:09:53.151172201Z ci_dir = base / "ci"
2026-02-21T03:09:53.151175581Z src_dir = base / "src"
2026-02-21T03:09:53.151180361Z src_dir.mkdir() # Exists but no .git (like worker creates)
2026-02-21T03:09:53.151183837Z jobs_dir = ci_dir / ".reactorcide" / "jobs"
2026-02-21T03:09:53.151662409Z jobs_dir.mkdir(parents=True)
2026-02-21T03:09:53.151673659Z triggers_file = base / "triggers.json"
2026-02-21T03:09:53.151676632Z
2026-02-21T03:09:53.151680352Z # Create a fake "remote" source repo
2026-02-21T03:09:53.151683796Z remote_dir = base / "remote_src"
2026-02-21T03:09:53.151687982Z remote_dir.mkdir()
2026-02-21T03:09:53.151975674Z from git import Repo
2026-02-21T03:09:53.151980258Z remote_repo = Repo.init(remote_dir)
2026-02-21T03:09:53.151987158Z (remote_dir / "[REDACTED].py").write_text("print('hello')")
2026-02-21T03:09:53.151991474Z remote_repo.index.add(["[REDACTED].py"])
2026-02-21T03:09:53.152559847Z remote_repo.index.commit("Initial commit")
2026-02-21T03:09:53.152563967Z
2026-02-21T03:09:53.152568174Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.15257229Z "name": "test",
2026-02-21T03:09:53.152650155Z "triggers": {"events": ["push"]},
2026-02-21T03:09:53.152655371Z "job": {"image": "alpine:latest", "command": "make test"},
2026-02-21T03:09:53.152659275Z })
2026-02-21T03:09:53.153024301Z
2026-02-21T03:09:53.153033271Z result = runner.invoke(app, [
2026-02-21T03:09:53.153244159Z "eval",
2026-02-21T03:09:53.153249876Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.153254253Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.153258239Z "--event-type", "push",
2026-02-21T03:09:53.153646856Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.153651806Z "--source-url", str(remote_dir),
2026-02-21T03:09:53.15365606Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.153660907Z ])
2026-02-21T03:09:53.153758944Z
2026-02-21T03:09:53.153765844Z> assert result.exit_code == 0
2026-02-21T03:09:53.154149224ZE AssertionError: assert 1 == 0
2026-02-21T03:09:53.154161664ZE + where 1 = <Result GitCommandError('git checkout [REDACTED]', 128)>.exit_code
2026-02-21T03:09:53.154164248Z
2026-02-21T03:09:53.154168258Ztests/test_eval_cli.py:567: AssertionError
2026-02-21T03:09:53.154232469Z___________ TestEvalEndToEnd.test_pr_opened_triggers_test_not_deploy ___________
2026-02-21T03:09:53.154237085Z
2026-02-21T03:09:53.154721676Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fc091a17890>
2026-02-21T03:09:53.154731773Ztemp_dirs = (PosixPath('/tmp/tmpaet63x0c/ci'), PosixPath('/tmp/tmpaet63x0c/src'), PosixPath('/tmp/tmpaet63x0c/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpaet63x0c/triggers.json'))
2026-02-21T03:09:53.154734089Z
2026-02-21T03:09:53.154739093Z def test_pr_opened_triggers_test_not_deploy(self, temp_dirs):
2026-02-21T03:09:53.155027662Z """Test full pipeline: PR opened triggers test job but not deploy."""
2026-02-21T03:09:53.155039792Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.155043195Z
2026-02-21T03:09:53.155047249Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.155050896Z "name": "test",
2026-02-21T03:09:53.155460156Z "description": "Run tests",
2026-02-21T03:09:53.155464863Z "triggers": {
2026-02-21T03:09:53.155471193Z "events": ["pull_request_opened", "[REDACTED]"],
2026-02-21T03:09:53.155475823Z "branches": ["[REDACTED]", "feature/*"],
2026-02-21T03:09:53.155781285Z },
2026-02-21T03:09:53.155788332Z "job": {"image": "python:3.11", "command": "pytest", "timeout": 1800},
2026-02-21T03:09:53.155792065Z "environment": {"PYTEST_ARGS": "-v"},
2026-02-21T03:09:53.156044241Z })
2026-02-21T03:09:53.156049498Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-21T03:09:53.156054038Z "name": "deploy",
2026-02-21T03:09:53.156371444Z "description": "Deploy to production",
2026-02-21T03:09:53.156380038Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-21T03:09:53.157025974Z "job": {"image": "deploy:latest", "command": "deploy.sh", "priority": 5},
2026-02-21T03:09:53.15703112Z })
2026-02-21T03:09:53.157033874Z
2026-02-21T03:09:53.157038394Z # Create .git dir so eval doesn't try to clone from the fake URL
2026-02-21T03:09:53.15704211Z (src_dir / ".git").mkdir()
2026-02-21T03:09:53.157044607Z
2026-02-21T03:09:53.157341377Z result = runner.invoke(app, [
2026-02-21T03:09:53.157347383Z "eval",
2026-02-21T03:09:53.157351913Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.1573554Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.157359343Z "--event-type", "pull_request_opened",
2026-02-21T03:09:53.1573627Z "--branch", "feature/my-feature",
2026-02-21T03:09:53.157794154Z "--source-url", "https://[REDACTED].com/org/repo.git",
2026-02-21T03:09:53.157803294Z "--source-ref", "abc123",
2026-02-21T03:09:53.157810464Z "--pr-base-ref", "[REDACTED]",
2026-02-21T03:09:53.157989913Z "--pr-number", "42",
2026-02-21T03:09:53.157997193Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.158001599Z ])
2026-02-21T03:09:53.158482511Z
2026-02-21T03:09:53.158493957Z assert result.exit_code == 0
2026-02-21T03:09:53.158499537Z> assert triggers_file.exists()
2026-02-21T03:09:53.158504924ZE AssertionError: assert False
2026-02-21T03:09:53.158868284ZE + where False = exists()
2026-02-21T03:09:53.158878887ZE + where exists = PosixPath('/tmp/tmpaet63x0c/triggers.json').exists
2026-02-21T03:09:53.158883127Z
2026-02-21T03:09:53.158889654Ztests/test_eval_cli.py:643: AssertionError
2026-02-21T03:09:53.160068897Z______________ TestEvalEndToEnd.test_push_to_[REDACTED]_triggers_deploy ______________
2026-02-21T03:09:53.160074987Z
2026-02-21T03:09:53.160082182Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fc091a179d0>
2026-02-21T03:09:53.160094455Ztemp_dirs = (PosixPath('/tmp/tmpv2emic_7/ci'), PosixPath('/tmp/tmpv2emic_7/src'), PosixPath('/tmp/tmpv2emic_7/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpv2emic_7/triggers.json'))
2026-02-21T03:09:53.160098145Z
2026-02-21T03:09:53.160107288Z def test_push_to_[REDACTED]_triggers_deploy(self, temp_dirs):
2026-02-21T03:09:53.160113459Z """Test full pipeline: push to [REDACTED] triggers deploy but not PR test."""
2026-02-21T03:09:53.160119109Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.160123339Z
2026-02-21T03:09:53.160139889Z _write_yaml(jobs_dir / "test.yaml", {
2026-02-21T03:09:53.160146102Z "name": "test",
2026-02-21T03:09:53.160152655Z "triggers": {"events": ["pull_request_opened"]},
2026-02-21T03:09:53.160462792Z })
2026-02-21T03:09:53.160476839Z _write_yaml(jobs_dir / "deploy.yaml", {
2026-02-21T03:09:53.160481842Z "name": "deploy",
2026-02-21T03:09:53.160488462Z "triggers": {"events": ["push"], "branches": ["[REDACTED]"]},
2026-02-21T03:09:53.160495429Z "job": {"image": "deploy:latest", "command": "deploy.sh"},
2026-02-21T03:09:53.16070382Z })
2026-02-21T03:09:53.160708178Z
2026-02-21T03:09:53.160712514Z result = runner.invoke(app, [
2026-02-21T03:09:53.160716404Z "eval",
2026-02-21T03:09:53.161323459Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.161329736Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.16133342Z "--event-type", "push",
2026-02-21T03:09:53.161338564Z "--branch", "[REDACTED]",
2026-02-21T03:09:53.161870775Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.16280583Z ])
2026-02-21T03:09:53.16281233Z
2026-02-21T03:09:53.16281844Z assert result.exit_code == 0
2026-02-21T03:09:53.162823313Z
2026-02-21T03:09:53.162829003Z> with open(triggers_file) as f:
2026-02-21T03:09:53.162836381ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpv2emic_7/triggers.json'
2026-02-21T03:09:53.162839821Z
2026-02-21T03:09:53.162845998Ztests/test_eval_cli.py:681: FileNotFoundError
2026-02-21T03:09:53.162854531Z______________ TestEvalEndToEnd.test_tag_created_triggers_release ______________
2026-02-21T03:09:53.162858124Z
2026-02-21T03:09:53.162864251Zself = <runnerlib.tests.test_eval_cli.TestEvalEndToEnd object at 0x7fc09192afd0>
2026-02-21T03:09:53.163051215Ztemp_dirs = (PosixPath('/tmp/tmpynqio6te/ci'), PosixPath('/tmp/tmpynqio6te/src'), PosixPath('/tmp/tmpynqio6te/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpynqio6te/triggers.json'))
2026-02-21T03:09:53.163055812Z
2026-02-21T03:09:53.163060882Z def test_tag_created_triggers_release(self, temp_dirs):
2026-02-21T03:09:53.163066475Z """Test full pipeline: tag_created triggers release job."""
2026-02-21T03:09:53.163070182Z ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
2026-02-21T03:09:53.163072982Z
2026-02-21T03:09:53.163076975Z _write_yaml(jobs_dir / "release.yaml", {
2026-02-21T03:09:53.163401495Z "name": "release",
2026-02-21T03:09:53.163407375Z "triggers": {"events": ["tag_created"]},
2026-02-21T03:09:53.163716332Z "job": {"image": "builder:latest", "command": "make release"},
2026-02-21T03:09:53.163721356Z })
2026-02-21T03:09:53.163724276Z
2026-02-21T03:09:53.163728022Z result = runner.invoke(app, [
2026-02-21T03:09:53.164059121Z "eval",
2026-02-21T03:09:53.164401567Z "--ci-source-dir", str(ci_dir),
2026-02-21T03:09:53.164406547Z "--source-dir", str(src_dir),
2026-02-21T03:09:53.164409974Z "--event-type", "tag_created",
2026-02-21T03:09:53.164413901Z "--triggers-file", str(triggers_file),
2026-02-21T03:09:53.164786058Z ])
2026-02-21T03:09:53.164791458Z
2026-02-21T03:09:53.164796181Z assert result.exit_code == 0
2026-02-21T03:09:53.164798821Z
2026-02-21T03:09:53.164802625Z> with open(triggers_file) as f:
2026-02-21T03:09:53.165324016ZE FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpynqio6te/triggers.json'
2026-02-21T03:09:53.165330592Z
2026-02-21T03:09:53.165338096Ztests/test_eval_cli.py:707: FileNotFoundError
2026-02-21T03:09:53.16534581Z____________ TestGitOperations.test_checkout_creates_job_directory _____________
2026-02-21T03:09:53.16534805Z
2026-02-21T03:09:53.165920341Zself = <runnerlib.tests.test_git_operations.TestGitOperations object at 0x7fc091998a50>
2026-02-21T03:09:53.165928368Ztest_repo = '/tmp/tmpl8cneb86'
2026-02-21T03:09:53.165938215Zjob_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-21T03:09:53.165941905Z
2026-02-21T03:09:53.165948575Z def test_checkout_creates_job_directory(self, test_repo, job_config):
2026-02-21T03:09:53.165955458Z """Test that checkout creates the job directory structure."""
2026-02-21T03:09:53.166054639Z # Ensure job dir doesn't exist initially
2026-02-21T03:09:53.166061402Z if Path("./job").exists():
2026-02-21T03:09:53.166067302Z shutil.rmtree("./job")
2026-02-21T03:09:53.166631962Z
2026-02-21T03:09:53.166640418Z # Try [REDACTED] first, fallback to master
2026-02-21T03:09:53.166644782Z try:
2026-02-21T03:09:53.166651082Z checkout_git_repo(test_repo, "[REDACTED]", job_config)
2026-02-21T03:09:53.166654598Z except Exception:
2026-02-21T03:09:53.166988151Z checkout_git_repo(test_repo, "master", job_config)
2026-02-21T03:09:53.166991818Z
2026-02-21T03:09:53.166995801Z # Verify job directory was created
2026-02-21T03:09:53.166999358Z> assert Path("./job").exists()
2026-02-21T03:09:53.167002361ZE AssertionError: assert False
2026-02-21T03:09:53.167006035ZE + where False = exists()
2026-02-21T03:09:53.167009668ZE + where exists = PosixPath('job').exists
2026-02-21T03:09:53.167013275ZE + where PosixPath('job') = Path('./job')
2026-02-21T03:09:53.167015415Z
2026-02-21T03:09:53.167628744Ztests/test_git_operations.py:213: AssertionError
2026-02-21T03:09:53.167635504Z---------------------------- Captured stdout setup -----------------------------
2026-02-21T03:09:53.167639701ZInitialized empty Git repository in /tmp/tmpl8cneb86/.git/
2026-02-21T03:09:53.167644281Z[master (root-commit) 46a01ac] Initial commit
2026-02-21T03:09:53.167648227Z 1 file changed, 1 insertion(+)
2026-02-21T03:09:53.167651331Z create mode 100644 test.txt
2026-02-21T03:09:53.167655221Z[feature bb591fc] Feature changes
2026-02-21T03:09:53.167658571Z 2 files changed, 2 insertions(+), 1 deletion(-)
2026-02-21T03:09:53.167885306Z create mode 100644 new.txt
2026-02-21T03:09:53.167892876Z---------------------------- Captured stderr setup -----------------------------
2026-02-21T03:09:53.167898886Zhint: Using 'master' as the name for the initial branch. This default branch name
2026-02-21T03:09:53.167904219Zhint: is subject to change. To configure the initial branch name to use in all
2026-02-21T03:09:53.167908813Zhint: of your new repositories, which will suppress this warning, call:
2026-02-21T03:09:53.168543935Zhint:
2026-02-21T03:09:53.168549852Zhint: git config --global init.defaultBranch <name>
2026-02-21T03:09:53.168552922Zhint:
2026-02-21T03:09:53.168559739Zhint: Names commonly chosen instead of 'master' are '[REDACTED]', 'trunk' and
2026-02-21T03:09:53.168564962Zhint: 'development'. The just-created branch can be renamed via this command:
2026-02-21T03:09:53.168568045Zhint:
2026-02-21T03:09:53.168572499Zhint: git branch -m <name>
2026-02-21T03:09:53.168575685ZSwitched to a new branch 'feature'
2026-02-21T03:09:53.168580389Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.16860991Z2026-02-21T03:09:11.010957+00:00 Cloning repository: /tmp/tmpl8cneb86
2026-02-21T03:09:53.168617336Z2026-02-21T03:09:11.056114+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.168627636Z2026-02-21T03:09:11.079144+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T03:09:53.168919409Z2026-02-21T03:09:11.097397+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T03:09:53.169240532Z2026-02-21T03:09:11.115681+00:00 Fetching all remote refs...
2026-02-21T03:09:53.169248039Z2026-02-21T03:09:11.156925+00:00 Cloning repository: /tmp/tmpl8cneb86
2026-02-21T03:09:53.169252119Z2026-02-21T03:09:11.196232+00:00 Checking out ref: master
2026-02-21T03:09:53.169256472Z2026-02-21T03:09:11.206280+00:00 Repository checked out to: /job/src
2026-02-21T03:09:53.169261242Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.169269502Z2026-02-21T03:09:11.010811+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpl8cneb86 ref=[REDACTED]
2026-02-21T03:09:53.169905565Z2026-02-21T03:09:11.149942+00:00 [ERROR] [runnerlib] Failed to clone repository url=/tmp/tmpl8cneb86 error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.169938092Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.169945248Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.169951248Z2026-02-21T03:09:11.150086+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.169955155Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.169959778Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.17019463Z2026-02-21T03:09:11.156813+00:00 [INFO] [runnerlib] Cloning git repository url=/tmp/tmpl8cneb86 ref=master
2026-02-21T03:09:53.170201494Z2026-02-21T03:09:11.206162+00:00 [INFO] [runnerlib] Repository cloned successfully path=/job/src
2026-02-21T03:09:53.17020747Z_ TestDirectoryManagementIntegration.test_directory_validation_with_real_filesystem _
2026-02-21T03:09:53.170209917Z
2026-02-21T03:09:53.171290444Zself = <runnerlib.tests.test_integration.TestDirectoryManagementIntegration object at 0x7fc091958690>
2026-02-21T03:09:53.171294607Z
2026-02-21T03:09:53.171299054Z def test_directory_validation_with_real_filesystem(self):
2026-02-21T03:09:53.17130327Z """Test directory validation against real filesystem."""
2026-02-21T03:09:53.171306874Z config = get_config(
2026-02-21T03:09:53.171310444Z code_dir='/job/src',
2026-02-21T03:09:53.173090516Z job_dir='/job/work',
2026-02-21T03:09:53.1730953Z job_command='test',
2026-02-21T03:09:53.17309939Z runner_image='test:image'
2026-02-21T03:09:53.173102913Z )
2026-02-21T03:09:53.173105716Z
2026-02-21T03:09:53.173110056Z # Test validation without directories
2026-02-21T03:09:53.173171988Z with patch('shutil.which', return_value="/usr/bin/docker"):
2026-02-21T03:09:53.173178821Z result = validate_config(config, check_files=True)
2026-02-21T03:09:53.173181451Z
2026-02-21T03:09:53.173185315Z # Should have warnings about missing directories
2026-02-21T03:09:53.173189108Z> assert result.has_warnings
2026-02-21T03:09:53.173192525ZE assert False
2026-02-21T03:09:53.173196888ZE + where False = ValidationResult(is_valid=True, errors=[], warnings=[]).has_warnings
2026-02-21T03:09:53.173198985Z
2026-02-21T03:09:53.173203291Z/workspace/runnerlib/tests/test_integration.py:121: AssertionError
2026-02-21T03:09:53.173257328Z___________________ TestJobIsolation.test_work_dir_isolation ___________________
2026-02-21T03:09:53.173260222Z
2026-02-21T03:09:53.173265482Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fc0919591d0>
2026-02-21T03:09:53.173267672Z
2026-02-21T03:09:53.173270992Z def test_work_dir_isolation(self):
2026-02-21T03:09:53.173388449Z """Test that jobs use separate work directories."""
2026-02-21T03:09:53.173396483Z with tempfile.TemporaryDirectory() as temp_dir1:
2026-02-21T03:09:53.173400529Z with tempfile.TemporaryDirectory() as temp_dir2:
2026-02-21T03:09:53.173404526Z # Change to first temp directory
2026-02-21T03:09:53.173408859Z original_cwd = os.getcwd()
2026-02-21T03:09:53.173417624Z
2026-02-21T03:09:53.173422847Z try:
2026-02-21T03:09:53.173428917Z # Test job 1 in temp_dir1
2026-02-21T03:09:53.173434747Z os.chdir(temp_dir1)
2026-02-21T03:09:53.17344036Z config1 = RunnerConfig(
2026-02-21T03:09:53.17344573Z code_dir="/job/src",
2026-02-21T03:09:53.173451094Z job_dir="/job/src",
2026-02-21T03:09:53.17345964Z job_command="echo 'job1'",
2026-02-21T03:09:53.173679399Z runner_image="alpine:latest"
2026-02-21T03:09:53.173684249Z )
2026-02-21T03:09:53.173981628Z job_path1 = prepare_job_directory(config1)
2026-02-21T03:09:53.173991091Z assert job_path1.exists()
2026-02-21T03:09:53.173994855Z> assert str(job_path1).startswith(temp_dir1)
2026-02-21T03:09:53.173999001ZE AssertionError: assert False
2026-02-21T03:09:53.174277884ZE + where False = <built-in method startswith of str object at 0x7fc092c20f90>('/tmp/tmpxjmwxdz6')
2026-02-21T03:09:53.174283981ZE + where <built-in method startswith of str object at 0x7fc092c20f90> = '/job'.startswith
2026-02-21T03:09:53.174288054ZE + where '/job' = str(PosixPath('/job'))
2026-02-21T03:09:53.174290538Z
2026-02-21T03:09:53.174785405Ztests/test_job_isolation.py:35: AssertionError
2026-02-21T03:09:53.174792246Z________________ TestJobIsolation.test_concurrent_job_isolation ________________
2026-02-21T03:09:53.174794679Z
2026-02-21T03:09:53.174799569Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fc091959310>
2026-02-21T03:09:53.175161745Z
2026-02-21T03:09:53.17516972Z def test_concurrent_job_isolation(self):
2026-02-21T03:09:53.175174683Z """Test that concurrent jobs don't interfere with each other."""
2026-02-21T03:09:53.17517836Z import threading
2026-02-21T03:09:53.175181623Z import time
2026-02-21T03:09:53.17518423Z
2026-02-21T03:09:53.17518765Z results = {}
2026-02-21T03:09:53.175190766Z errors = {}
2026-02-21T03:09:53.176083864Z
2026-02-21T03:09:53.176091491Z def run_job(job_id: str, work_dir: str):
2026-02-21T03:09:53.176095258Z """Run a job in its own work directory."""
2026-02-21T03:09:53.176099624Z try:
2026-02-21T03:09:53.176103194Z original_cwd = os.getcwd()
2026-02-21T03:09:53.176106548Z os.chdir(work_dir)
2026-02-21T03:09:53.176108978Z
2026-02-21T03:09:53.176112188Z config = RunnerConfig(
2026-02-21T03:09:53.176115928Z code_dir="/job/src",
2026-02-21T03:09:53.176181905Z job_dir="/job/src",
2026-02-21T03:09:53.176186869Z job_command=f"echo 'job-{job_id}'",
2026-02-21T03:09:53.176190149Z runner_image="alpine:latest"
2026-02-21T03:09:53.176226745Z )
2026-02-21T03:09:53.176229439Z
2026-02-21T03:09:53.176233542Z job_path = prepare_job_directory(config)
2026-02-21T03:09:53.176834441Z
2026-02-21T03:09:53.176843511Z # Create a unique file for this job
2026-02-21T03:09:53.176848515Z test_file = job_path / f"job-{job_id}.txt"
2026-02-21T03:09:53.176852751Z test_file.write_text(f"Data for job {job_id}")
2026-02-21T03:09:53.176855291Z
2026-02-21T03:09:53.176859665Z # Simulate some work
2026-02-21T03:09:53.17714668Z time.sleep(0.1)
2026-02-21T03:09:53.177150947Z
2026-02-21T03:09:53.177155763Z # Verify the file still exists and has correct content
2026-02-21T03:09:53.177159453Z assert test_file.exists()
2026-02-21T03:09:53.177779329Z assert test_file.read_text() == f"Data for job {job_id}"
2026-02-21T03:09:53.177783936Z
2026-02-21T03:09:53.177793719Z # Check no files from other jobs exist
2026-02-21T03:09:53.17779783Z other_files = list(job_path.glob("job-*.txt"))
2026-02-21T03:09:53.17780139Z assert len(other_files) == 1
2026-02-21T03:09:53.17780577Z assert other_files[0].name == f"job-{job_id}.txt"
2026-02-21T03:09:53.177808007Z
2026-02-21T03:09:53.177811537Z results[job_id] = True
2026-02-21T03:09:53.177918964Z
2026-02-21T03:09:53.177930871Z except Exception as e:
2026-02-21T03:09:53.177937171Z errors[job_id] = str(e)
2026-02-21T03:09:53.1782557Z finally:
2026-02-21T03:09:53.178262363Z os.chdir(original_cwd)
2026-02-21T03:09:53.178649431Z
2026-02-21T03:09:53.178657424Z # Create temporary directories for each job
2026-02-21T03:09:53.178661751Z temp_dirs = []
2026-02-21T03:09:53.178665077Z threads = []
2026-02-21T03:09:53.178667747Z
2026-02-21T03:09:53.178671872Z try:
2026-02-21T03:09:53.1790028Z # Start multiple jobs concurrently
2026-02-21T03:09:53.179008634Z for i in range(5):
2026-02-21T03:09:53.17901434Z temp_dir = tempfile.mkdtemp(prefix=f"job-{i}-")
2026-02-21T03:09:53.17901829Z temp_dirs.append(temp_dir)
2026-02-21T03:09:53.179021244Z
2026-02-21T03:09:53.179416491Z thread = threading.Thread(
2026-02-21T03:09:53.179421804Z target=run_job,
2026-02-21T03:09:53.179743637Z args=(str(i), temp_dir)
2026-02-21T03:09:53.180280482Z )
2026-02-21T03:09:53.180285556Z thread.start()
2026-02-21T03:09:53.180289442Z threads.append(thread)
2026-02-21T03:09:53.180292126Z
2026-02-21T03:09:53.180296512Z # Wait for all jobs to complete
2026-02-21T03:09:53.18030023Z for thread in threads:
2026-02-21T03:09:53.180304137Z thread.join(timeout=5)
2026-02-21T03:09:53.180306953Z
2026-02-21T03:09:53.180314757Z # Verify all jobs succeeded
2026-02-21T03:09:53.180367237Z> assert len(errors) == 0, f"Jobs failed: {errors}"
2026-02-21T03:09:53.18039046ZE AssertionError: Jobs failed: {'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '1': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '4': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '3': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", '2': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])"}
2026-02-21T03:09:53.180394127ZE assert 5 == 0
2026-02-21T03:09:53.180627645ZE + where 5 = len({'0': "assert 5 == 1\n + where 5 = len([PosixPath('/job/job-1.txt'), PosixPath('/job/job-2.txt'), PosixPath('/job/job...xPath('/job/job-2.txt'), PosixPath('/job/job-4.txt'), PosixPath('/job/job-0.txt'), PosixPath('/job/job-3.txt')])", ...})
2026-02-21T03:09:53.180632035Z
2026-02-21T03:09:53.180837811Z/workspace/runnerlib/tests/test_job_isolation.py:138: AssertionError
2026-02-21T03:09:53.180844924Z_______________ TestJobIsolation.test_container_mount_isolation ________________
2026-02-21T03:09:53.180847267Z
2026-02-21T03:09:53.181187721Zself = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7fc09192b6f0>
2026-02-21T03:09:53.181198085Zmock_popen = <MagicMock name='Popen' id='140465047022160'>
2026-02-21T03:09:53.181202065Z
2026-02-21T03:09:53.181218545Z @patch('subprocess.Popen')
2026-02-21T03:09:53.18143423Z def test_container_mount_isolation(self, mock_popen):
2026-02-21T03:09:53.18144349Z """Test that containers mount only their job's directory."""
2026-02-21T03:09:53.181831307Z # Mock the Popen object with proper behavior
2026-02-21T03:09:53.181839377Z mock_process = MagicMock()
2026-02-21T03:09:53.181908424Z mock_process.poll.side_effect = [None, None, 0] # Running, running, then finished
2026-02-21T03:09:53.181914787Z mock_process.returncode = 0
2026-02-21T03:09:53.181920351Z mock_process.stdout.readline.return_value = '' # No output (text mode)
2026-02-21T03:09:53.181924794Z mock_process.stderr.readline.return_value = '' # No errors
2026-02-21T03:09:53.182040542Z mock_process.communicate.return_value = ('', '') # Empty re[REDACTED]ing output
2026-02-21T03:09:53.182502836Z mock_popen.return_value = mock_process
2026-02-21T03:09:53.183195936Z
2026-02-21T03:09:53.183207753Z with tempfile.TemporaryDirectory() as temp_dir:
2026-02-21T03:09:53.183214016Z # Save original cwd if possible
2026-02-21T03:09:53.183220899Z try:
2026-02-21T03:09:53.183227239Z original_cwd = os.getcwd()
2026-02-21T03:09:53.183232793Z except FileNotFoundError:
2026-02-21T03:09:53.183239906Z # If current dir doesn't exist, use temp dir as fallback
2026-02-21T03:09:53.183246056Z original_cwd = temp_dir
2026-02-21T03:09:53.183250416Z
2026-02-21T03:09:53.18332971Z try:
2026-02-21T03:09:53.183338527Z os.chdir(temp_dir)
2026-02-21T03:09:53.183669897Z
2026-02-21T03:09:53.183680348Z config = RunnerConfig(
2026-02-21T03:09:53.183694701Z code_dir="/job/src",
2026-02-21T03:09:53.183790891Z job_dir="/job/src",
2026-02-21T03:09:53.183798411Z job_command="echo test",
2026-02-21T03:09:53.183807409Z runner_image="alpine:latest"
2026-02-21T03:09:53.183819362Z )
2026-02-21T03:09:53.183823775Z
2026-02-21T03:09:53.184019086Z # Prepare job directory
2026-02-21T03:09:53.18402497Z job_path = prepare_job_directory(config)
2026-02-21T03:09:53.18402786Z
2026-02-21T03:09:53.184100064Z # Create a test file
2026-02-21T03:09:53.184107677Z test_file = job_path / "test.txt"
2026-02-21T03:09:53.184351133Z test_file.write_text("test data")
2026-02-21T03:09:53.184356003Z
2026-02-21T03:09:53.184566312Z # Run container
2026-02-21T03:09:53.184572482Z> run_container(config)
2026-02-21T03:09:53.184574866Z
2026-02-21T03:09:53.184578762Z/workspace/runnerlib/tests/test_job_isolation.py:257:
2026-02-21T03:09:53.18472883Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.184733557Z
2026-02-21T03:09:53.184741947Zconfig = 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-21T03:09:53.18474567Zadditional_args = None
2026-02-21T03:09:53.18507138Z
2026-02-21T03:09:53.18507855Z def run_container(
2026-02-21T03:09:53.185431229Z config: RunnerConfig,
2026-02-21T03:09:53.185436893Z additional_args: Optional[List[str]] = None
2026-02-21T03:09:53.185957558Z ) -> int:
2026-02-21T03:09:53.185965258Z """Run the job container using docker with full configuration support.
2026-02-21T03:09:53.185968228Z
2026-02-21T03:09:53.185971694Z Args:
2026-02-21T03:09:53.185975954Z config: Runner configuration
2026-02-21T03:09:53.185980661Z additional_args: Additional arguments to pass to the job command
2026-02-21T03:09:53.185983171Z
2026-02-21T03:09:53.186111739Z Returns:
2026-02-21T03:09:53.186121092Z Exit code of the container process
2026-02-21T03:09:53.188896278Z
2026-02-21T03:09:53.188905155Z Raises:
2026-02-21T03:09:53.188909815Z ValueError: If configuration is invalid
2026-02-21T03:09:53.188913681Z FileNotFoundError: If docker is not available
2026-02-21T03:09:53.188950685Z """
2026-02-21T03:09:53.188955198Z # Create plugin context for the execution
2026-02-21T03:09:53.188958758Z plugin_context = PluginContext(
2026-02-21T03:09:53.188962075Z config=config,
2026-02-21T03:09:53.188965705Z phase=PluginPhase.PRE_SOURCE_PREP,
2026-02-21T03:09:53.188968948Z metadata={}
2026-02-21T03:09:53.188972291Z )
2026-02-21T03:09:53.188974715Z
2026-02-21T03:09:53.188978595Z try:
2026-02-21T03:09:53.188988141Z # Execute pre-source-prep plugins
2026-02-21T03:09:53.188993665Z plugin_manager.execute_phase(PluginPhase.PRE_SOURCE_PREP, plugin_context)
2026-02-21T03:09:53.188996738Z
2026-02-21T03:09:53.189000706Z # Basic validation is handled by CLI layer
2026-02-21T03:09:53.189003202Z
2026-02-21T03:09:53.189006946Z # Check if docker is available
2026-02-21T03:09:53.189010466Z if not shutil.which("docker"):
2026-02-21T03:09:53.189014009Z logger.error("Docker is not available in PATH")
2026-02-21T03:09:53.189017976Z> raise FileNotFoundError("docker is not available in PATH")
2026-02-21T03:09:53.189021729ZE FileNotFoundError: docker is not available in PATH
2026-02-21T03:09:53.189023989Z
2026-02-21T03:09:53.189027242Z/workspace/runnerlib/src/container.py:114: FileNotFoundError
2026-02-21T03:09:53.189371361Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.189379375Z2026-02-21T03:09:14.074850+00:00 [ERROR] [runnerlib] Docker is not available in PATH
2026-02-21T03:09:53.189384922Z___________ TestSourcePreparation.test_no_source_preparation_default ___________
2026-02-21T03:09:53.189387385Z
2026-02-21T03:09:53.189930444Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc0913c5450>
2026-02-21T03:09:53.1899343Z
2026-02-21T03:09:53.189938814Z def test_no_source_preparation_default(self):
2026-02-21T03:09:53.189944084Z """Test job with no source preparation (default - source_type not set)."""
2026-02-21T03:09:53.1899477Z # Configure without specifying source_type
2026-02-21T03:09:53.18995153Z config = get_config(job_command="echo 'hello'")
2026-02-21T03:09:53.189954127Z
2026-02-21T03:09:53.190263047Z # Prepare source should return None
2026-02-21T03:09:53.190268327Z result = prepare_source(config)
2026-02-21T03:09:53.190272267Z> assert result is None
2026-02-21T03:09:53.190276127ZE AssertionError: assert PosixPath('/job/src') is None
2026-02-21T03:09:53.1902786Z
2026-02-21T03:09:53.192619134Z/workspace/runnerlib/tests/test_source_preparation.py:89: AssertionError
2026-02-21T03:09:53.192626584Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.192805693Z2026-02-21T03:09:24.452532+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-21T03:09:53.192813216Z2026-02-21T03:09:41.754323+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.192822276Z2026-02-21T03:09:42.455190+00:00 Repository checked out to: /job/src
2026-02-21T03:09:53.192827013Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.192837026Z2026-02-21T03:09:24.452232+00:00 [INFO] [runnerlib] Preparing source type=git url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T03:09:53.19284402Z2026-02-21T03:09:24.452457+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/job/src
2026-02-21T03:09:53.19285022Z2026-02-21T03:09:42.455013+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-21T03:09:53.192855196Z______________ TestSourcePreparation.test_git_source_preparation _______________
2026-02-21T03:09:53.192857636Z
2026-02-21T03:09:53.192866023Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc0911f7bb0>
2026-02-21T03:09:53.192868206Z
2026-02-21T03:09:53.19287315Z def test_git_source_preparation(self):
2026-02-21T03:09:53.192878163Z """Test git source preparation."""
2026-02-21T03:09:53.192882184Z # Create a test git repository
2026-02-21T03:09:53.192886757Z test_repo_dir = Path(self.temp_dir) / "test_repo"
2026-02-21T03:09:53.192890364Z test_repo_dir.mkdir()
2026-02-21T03:09:53.192894361Z repo = Repo.init(test_repo_dir)
2026-02-21T03:09:53.192897241Z
2026-02-21T03:09:53.192900664Z # Add a test file
2026-02-21T03:09:53.192904954Z test_file = test_repo_dir / "test.txt"
2026-02-21T03:09:53.192908301Z test_file.write_text("test content")
2026-02-21T03:09:53.192912201Z repo.index.add([str(test_file)])
2026-02-21T03:09:53.192917254Z repo.index.commit("Initial commit")
2026-02-21T03:09:53.192919504Z
2026-02-21T03:09:53.192922607Z # Configure with git source
2026-02-21T03:09:53.195164492Z config = get_config(
2026-02-21T03:09:53.195204702Z job_command="cat /job/src/test.txt",
2026-02-21T03:09:53.195215605Z source_type="git",
2026-02-21T03:09:53.195219342Z source_url=str(test_repo_dir),
2026-02-21T03:09:53.195224728Z source_ref="[REDACTED]"
2026-02-21T03:09:53.195228432Z )
2026-02-21T03:09:53.195284536Z
2026-02-21T03:09:53.195291249Z # Prepare source
2026-02-21T03:09:53.195294583Z> result = prepare_source(config)
2026-02-21T03:09:53.195296753Z
2026-02-21T03:09:53.195301513Z/workspace/runnerlib/tests/test_source_preparation.py:113:
2026-02-21T03:09:53.195305639Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.195309559Z/workspace/runnerlib/src/source_prep.py:563: in prepare_source
2026-02-21T03:09:53.195314436Z return _prepare_git_source(config.source_url, config.source_ref, target_path)
2026-02-21T03:09:53.195318593Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T03:09:53.195322309Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T03:09:53.195325939Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.195328079Z
2026-02-21T03:09:53.195359456Zrepo = <git.repo.base.Repo '/job/src/.git'>, source_ref = '[REDACTED]'
2026-02-21T03:09:53.195362063Z
2026-02-21T03:09:53.195367599Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T03:09:53.195371896Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T03:09:53.195374463Z
2026-02-21T03:09:53.195379113Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T03:09:53.195382736Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T03:09:53.1953867Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T03:09:53.195391067Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T03:09:53.195393274Z
2026-02-21T03:09:53.19539709Z Args:
2026-02-21T03:09:53.195400697Z repo: GitPython Repo instance (already cloned)
2026-02-21T03:09:53.19540496Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T03:09:53.19540724Z
2026-02-21T03:09:53.19541057Z Raises:
2026-02-21T03:09:53.195414187Z GitCommandError: If all checkout attempts fail
2026-02-21T03:09:53.19541717Z """
2026-02-21T03:09:53.195422147Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T03:09:53.195426124Z try:
2026-02-21T03:09:53.19542957Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.195432654Z return
2026-02-21T03:09:53.19543616Z except GitCommandError:
2026-02-21T03:09:53.195464944Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T03:09:53.195627985Z
2026-02-21T03:09:53.195636516Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T03:09:53.195639752Z try:
2026-02-21T03:09:53.195643426Z repo.git.fetch("origin", source_ref)
2026-02-21T03:09:53.195646636Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.195708949Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T03:09:53.195924335Z return
2026-02-21T03:09:53.195933742Z except GitCommandError:
2026-02-21T03:09:53.195942289Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T03:09:53.195947402Z
2026-02-21T03:09:53.195954515Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T03:09:53.195960575Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T03:09:53.195966065Z if pr_number:
2026-02-21T03:09:53.195972719Z # GitHub: refs/pull/<N>/head
2026-02-21T03:09:53.19607161Z pr_refs = [
2026-02-21T03:09:53.1960768Z f"refs/pull/{pr_number}/head",
2026-02-21T03:09:53.19608078Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T03:09:53.196089596Z ]
2026-02-21T03:09:53.196770603Z for pr_ref in pr_refs:
2026-02-21T03:09:53.19677986Z try:
2026-02-21T03:09:53.196787567Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T03:09:53.196802967Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T03:09:53.196809083Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.19681636Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T03:09:53.196821977Z return
2026-02-21T03:09:53.196827927Z except GitCommandError:
2026-02-21T03:09:53.196836287Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T03:09:53.196971304Z
2026-02-21T03:09:53.196985454Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T03:09:53.196991844Z try:
2026-02-21T03:09:53.196998851Z log_stdout("Fetching all remote refs...")
2026-02-21T03:09:53.197007024Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T03:09:53.197013425Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.197019872Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T03:09:53.197313661Z return
2026-02-21T03:09:53.197320871Z except GitCommandError:
2026-02-21T03:09:53.197325991Z pass
2026-02-21T03:09:53.197535997Z
2026-02-21T03:09:53.197969767Z # Nothing worked — raise with a clear message
2026-02-21T03:09:53.197977093Z> raise GitCommandError(
2026-02-21T03:09:53.197982863Z f"git checkout {source_ref}",
2026-02-21T03:09:53.19798858Z 128,
2026-02-21T03:09:53.197997367Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T03:09:53.198003157Z )
2026-02-21T03:09:53.198010183ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.198121254ZE cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.198133551ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.198137495Z
2026-02-21T03:09:53.198142865Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T03:09:53.198803995Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.198813765Z2026-02-21T03:09:42.519383+00:00 Cloning git repository: /tmp/tmpbhlpnbwp/test_repo
2026-02-21T03:09:53.198819765Z2026-02-21T03:09:42.657496+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.198824888Z2026-02-21T03:09:42.676161+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T03:09:53.198889782Z2026-02-21T03:09:42.690193+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T03:09:53.198894699Z2026-02-21T03:09:42.704794+00:00 Fetching all remote refs...
2026-02-21T03:09:53.198899226Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.198907079Z2026-02-21T03:09:42.519070+00:00 [INFO] [runnerlib] Preparing source type=git url=/tmp/tmpbhlpnbwp/test_repo ref=[REDACTED]
2026-02-21T03:09:53.198914709Z2026-02-21T03:09:42.519266+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpbhlpnbwp/test_repo ref=[REDACTED] target=/job/src
2026-02-21T03:09:53.199680763Z2026-02-21T03:09:42.738211+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmpbhlpnbwp/test_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.199688253Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.199693666Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.199699793Z2026-02-21T03:09:42.738351+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.199703789Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.199974451Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.199980055Z______________ TestSourcePreparation.test_dual_source_preparation ______________
2026-02-21T03:09:53.199982495Z
2026-02-21T03:09:53.199987825Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc0913bd130>
2026-02-21T03:09:53.20047283Z
2026-02-21T03:09:53.200480596Z def test_dual_source_preparation(self):
2026-02-21T03:09:53.200485106Z """Test preparation of both source and ci_source."""
2026-02-21T03:09:53.200488826Z # Create source repo (untrusted code)
2026-02-21T03:09:53.20049299Z source_repo_dir = Path(self.temp_dir) / "source_repo"
2026-02-21T03:09:53.201162317Z source_repo_dir.mkdir()
2026-02-21T03:09:53.201175433Z source_repo = Repo.init(source_repo_dir)
2026-02-21T03:09:53.201180383Z (source_repo_dir / "app.py").write_text("print('hello from PR')")
2026-02-21T03:09:53.20118417Z source_repo.index.add(["app.py"])
2026-02-21T03:09:53.201187447Z source_repo.index.commit("PR commit")
2026-02-21T03:09:53.20119068Z
2026-02-21T03:09:53.20119426Z # Create CI repo (trusted code)
2026-02-21T03:09:53.20119788Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-21T03:09:53.20120672Z ci_repo_dir.mkdir()
2026-02-21T03:09:53.201210167Z ci_repo = Repo.init(ci_repo_dir)
2026-02-21T03:09:53.201570336Z (ci_repo_dir / "pipeline.py").write_text("print('running tests')")
2026-02-21T03:09:53.201575216Z ci_repo.index.add(["pipeline.py"])
2026-02-21T03:09:53.201578313Z ci_repo.index.commit("CI commit")
2026-02-21T03:09:53.201581Z
2026-02-21T03:09:53.201584243Z # Configure with both sources
2026-02-21T03:09:53.20158773Z config = get_config(
2026-02-21T03:09:53.20160759Z job_command="python /job/ci/pipeline.py",
2026-02-21T03:09:53.20195571Z source_type="git",
2026-02-21T03:09:53.201960427Z source_url=str(source_repo_dir),
2026-02-21T03:09:53.20197013Z source_ref="[REDACTED]",
2026-02-21T03:09:53.20202717Z ci_source_type="git",
2026-02-21T03:09:53.202031107Z ci_source_url=str(ci_repo_dir),
2026-02-21T03:09:53.202035667Z ci_source_ref="[REDACTED]"
2026-02-21T03:09:53.2020409Z )
2026-02-21T03:09:53.202044881Z
2026-02-21T03:09:53.202439801Z # Prepare CI source first (as the CLI does)
2026-02-21T03:09:53.202445701Z> ci_result = prepare_ci_source(config)
2026-02-21T03:09:53.202448461Z
2026-02-21T03:09:53.202489741Z/workspace/runnerlib/tests/test_source_preparation.py:170:
2026-02-21T03:09:53.202942089Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.202947803Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-21T03:09:53.202953013Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-21T03:09:53.202957616Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T03:09:53.202962216Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T03:09:53.202971059Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.203233695Z
2026-02-21T03:09:53.203245975Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-21T03:09:53.203475614Z
2026-02-21T03:09:53.207335222Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T03:09:53.207342713Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T03:09:53.207346169Z
2026-02-21T03:09:53.207350976Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T03:09:53.207355719Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T03:09:53.207360026Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T03:09:53.207364639Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T03:09:53.207366876Z
2026-02-21T03:09:53.207370663Z Args:
2026-02-21T03:09:53.207374693Z repo: GitPython Repo instance (already cloned)
2026-02-21T03:09:53.207379846Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T03:09:53.207382003Z
2026-02-21T03:09:53.207385543Z Raises:
2026-02-21T03:09:53.207388906Z GitCommandError: If all checkout attempts fail
2026-02-21T03:09:53.207391963Z """
2026-02-21T03:09:53.207396839Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T03:09:53.207400419Z try:
2026-02-21T03:09:53.207404086Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.207407376Z return
2026-02-21T03:09:53.207410706Z except GitCommandError:
2026-02-21T03:09:53.207415976Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T03:09:53.207419103Z
2026-02-21T03:09:53.207425916Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T03:09:53.207430539Z try:
2026-02-21T03:09:53.207445726Z repo.git.fetch("origin", source_ref)
2026-02-21T03:09:53.207451569Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.207458313Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T03:09:53.207463167Z return
2026-02-21T03:09:53.207468557Z except GitCommandError:
2026-02-21T03:09:53.20747445Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T03:09:53.20747786Z
2026-02-21T03:09:53.207483617Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T03:09:53.207489044Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T03:09:53.207494717Z if pr_number:
2026-02-21T03:09:53.207500517Z # GitHub: refs/pull/<N>/head
2026-02-21T03:09:53.207505777Z pr_refs = [
2026-02-21T03:09:53.20751174Z f"refs/pull/{pr_number}/head",
2026-02-21T03:09:53.207517604Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T03:09:53.207523057Z ]
2026-02-21T03:09:53.20752838Z for pr_ref in pr_refs:
2026-02-21T03:09:53.20753451Z try:
2026-02-21T03:09:53.207539514Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T03:09:53.20754662Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T03:09:53.207552434Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.207559407Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T03:09:53.20756802Z return
2026-02-21T03:09:53.20757356Z except GitCommandError:
2026-02-21T03:09:53.207579724Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T03:09:53.207583504Z
2026-02-21T03:09:53.207613231Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T03:09:53.207619795Z try:
2026-02-21T03:09:53.207625685Z log_stdout("Fetching all remote refs...")
2026-02-21T03:09:53.207632551Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T03:09:53.207637421Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.207644048Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T03:09:53.207649521Z return
2026-02-21T03:09:53.207655811Z except GitCommandError:
2026-02-21T03:09:53.207701611Z pass
2026-02-21T03:09:53.207704778Z
2026-02-21T03:09:53.207708558Z # Nothing worked — raise with a clear message
2026-02-21T03:09:53.207719406Z> raise GitCommandError(
2026-02-21T03:09:53.207725022Z f"git checkout {source_ref}",
2026-02-21T03:09:53.207733419Z 128,
2026-02-21T03:09:53.208236357Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T03:09:53.208242331Z )
2026-02-21T03:09:53.208248361ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.208253224ZE cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.208295081ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.208297977Z
2026-02-21T03:09:53.208302377Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T03:09:53.208306951Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.208947617Z2026-02-21T03:09:42.905196+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-21T03:09:53.209437437Z2026-02-21T03:09:42.905387+00:00 Cloning git repository: /tmp/tmp_64_2b3t/ci_repo
2026-02-21T03:09:53.209448547Z2026-02-21T03:09:42.943551+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.209462827Z2026-02-21T03:09:42.963849+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T03:09:53.209469842Z2026-02-21T03:09:42.977823+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T03:09:53.209476632Z2026-02-21T03:09:42.991943+00:00 Fetching all remote refs...
2026-02-21T03:09:53.209483338Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.209494288Z2026-02-21T03:09:42.905058+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmp_64_2b3t/ci_repo ref=[REDACTED]
2026-02-21T03:09:53.209916499Z2026-02-21T03:09:42.905320+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmp_64_2b3t/ci_repo ref=[REDACTED] target=/job/ci
2026-02-21T03:09:53.209929889Z2026-02-21T03:09:43.025712+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmp_64_2b3t/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.209937169Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.209945142Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.209953659Z2026-02-21T03:09:43.025846+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.209959869Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.209968757Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.209978597Z__________________ TestSourcePreparation.test_ci_source_only ___________________
2026-02-21T03:09:53.210225465Z
2026-02-21T03:09:53.210238039Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc09194de10>
2026-02-21T03:09:53.210525968Z
2026-02-21T03:09:53.210536345Z def test_ci_source_only(self):
2026-02-21T03:09:53.210543625Z """Test preparation of CI source without regular source."""
2026-02-21T03:09:53.210550068Z # Create CI repo
2026-02-21T03:09:53.21113545Z ci_repo_dir = Path(self.temp_dir) / "ci_repo"
2026-02-21T03:09:53.211142743Z ci_repo_dir.mkdir()
2026-02-21T03:09:53.211149127Z ci_repo = Repo.init(ci_repo_dir)
2026-02-21T03:09:53.211685918Z (ci_repo_dir / "deploy.sh").write_text("#!/bin/bash\necho deploying")
2026-02-21T03:09:53.211696265Z ci_repo.index.add(["deploy.sh"])
2026-02-21T03:09:53.211702025Z ci_repo.index.commit("CI commit")
2026-02-21T03:09:53.211706898Z
2026-02-21T03:09:53.211713198Z # Configure with only CI source
2026-02-21T03:09:53.211717915Z config = get_config(
2026-02-21T03:09:53.211723959Z job_command="bash /job/ci/deploy.sh",
2026-02-21T03:09:53.211728076Z ci_source_type="git",
2026-02-21T03:09:53.21219654Z ci_source_url=str(ci_repo_dir),
2026-02-21T03:09:53.212210013Z ci_source_ref="[REDACTED]"
2026-02-21T03:09:53.212213483Z )
2026-02-21T03:09:53.212216543Z
2026-02-21T03:09:53.212222084Z # Prepare CI source
2026-02-21T03:09:53.212225771Z> ci_result = prepare_ci_source(config)
2026-02-21T03:09:53.212228067Z
2026-02-21T03:09:53.212435155Z/workspace/runnerlib/tests/test_source_preparation.py:206:
2026-02-21T03:09:53.212443529Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.212450225Z/workspace/runnerlib/src/source_prep.py:633: in prepare_ci_source
2026-02-21T03:09:53.212792025Z return _prepare_git_source(config.ci_source_url, config.ci_source_ref, target_path)
2026-02-21T03:09:53.212798772Z/workspace/runnerlib/src/source_prep.py:400: in _prepare_git_source
2026-02-21T03:09:53.212803318Z _checkout_with_fetch_fallback(repo, source_ref)
2026-02-21T03:09:53.212807115Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.213085818Z
2026-02-21T03:09:53.213102685Zrepo = <git.repo.base.Repo '/job/ci/.git'>, source_ref = '[REDACTED]'
2026-02-21T03:09:53.213105195Z
2026-02-21T03:09:53.213110735Z def _checkout_with_fetch_fallback(repo: Repo, source_ref: str) -> None:
2026-02-21T03:09:53.21361939Z """Checkout a git ref, fetching PR refs as fallback if needed.
2026-02-21T03:09:53.21362665Z
2026-02-21T03:09:53.213961899Z For PR events, the source_ref SHA may not exist in the default clone
2026-02-21T03:09:53.213970706Z because it lives under refs/pull/<N>/head (GitHub) or
2026-02-21T03:09:53.21397832Z refs/merge-requests/<N>/head (GitLab). This function tries a direct
2026-02-21T03:09:53.213985567Z checkout first, then falls back to fetching the specific PR ref.
2026-02-21T03:09:53.21398959Z
2026-02-21T03:09:53.21440975Z Args:
2026-02-21T03:09:53.21441853Z repo: GitPython Repo instance (already cloned)
2026-02-21T03:09:53.214425517Z source_ref: Git reference to checkout (branch, tag, or commit SHA)
2026-02-21T03:09:53.214430373Z
2026-02-21T03:09:53.214435613Z Raises:
2026-02-21T03:09:53.21512238Z GitCommandError: If all checkout attempts fail
2026-02-21T03:09:53.215129044Z """
2026-02-21T03:09:53.215134744Z # Try direct checkout first — works for branches, tags, and commits on fetched branches
2026-02-21T03:09:53.215139187Z try:
2026-02-21T03:09:53.21514309Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.215146634Z return
2026-02-21T03:09:53.215492113Z except GitCommandError:
2026-02-21T03:09:53.215503353Z logger.debug("Direct checkout failed, trying fetch fallbacks", fields={"ref": source_ref})
2026-02-21T03:09:53.215507987Z
2026-02-21T03:09:53.21551603Z # Try fetching the specific SHA (works if server has uploadpack.allowReachableSHA1InWant)
2026-02-21T03:09:53.215522223Z try:
2026-02-21T03:09:53.21552776Z repo.git.fetch("origin", source_ref)
2026-02-21T03:09:53.215533653Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.215916894Z log_stdout(f"Fetched and checked out ref: {source_ref}")
2026-02-21T03:09:53.215924744Z return
2026-02-21T03:09:53.215930811Z except GitCommandError:
2026-02-21T03:09:53.215939657Z logger.debug("Fetch by SHA failed", fields={"ref": source_ref})
2026-02-21T03:09:53.215943754Z
2026-02-21T03:09:53.215949737Z # Try PR-specific refs using REACTORCIDE_PR_NUMBER
2026-02-21T03:09:53.215955324Z pr_number = os.getenv("REACTORCIDE_PR_NUMBER", "")
2026-02-21T03:09:53.215960137Z if pr_number:
2026-02-21T03:09:53.216518296Z # GitHub: refs/pull/<N>/head
2026-02-21T03:09:53.216526Z pr_refs = [
2026-02-21T03:09:53.216532286Z f"refs/pull/{pr_number}/head",
2026-02-21T03:09:53.216539176Z f"refs/merge-requests/{pr_number}/head", # GitLab
2026-02-21T03:09:53.216544163Z ]
2026-02-21T03:09:53.216549953Z for pr_ref in pr_refs:
2026-02-21T03:09:53.21655588Z try:
2026-02-21T03:09:53.216562236Z log_stdout(f"Fetching PR ref: {pr_ref}")
2026-02-21T03:09:53.216686648Z repo.git.fetch("origin", f"{pr_ref}:refs/remotes/origin/pr-head")
2026-02-21T03:09:53.216695468Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.216703531Z log_stdout(f"Checked out PR ref: {source_ref}")
2026-02-21T03:09:53.216709738Z return
2026-02-21T03:09:53.217063891Z except GitCommandError:
2026-02-21T03:09:53.217079744Z logger.debug("PR ref fetch failed", fields={"pr_ref": pr_ref})
2026-02-21T03:09:53.217084254Z
2026-02-21T03:09:53.21889092Z # Last resort: fetch all remote refs (handles any branch the SHA might be on)
2026-02-21T03:09:53.218896788Z try:
2026-02-21T03:09:53.218900981Z log_stdout("Fetching all remote refs...")
2026-02-21T03:09:53.218905771Z repo.git.fetch("origin", "+refs/heads/*:refs/remotes/origin/*")
2026-02-21T03:09:53.218910051Z repo.git.checkout(source_ref)
2026-02-21T03:09:53.218914118Z log_stdout(f"Checked out ref after full fetch: {source_ref}")
2026-02-21T03:09:53.218917508Z return
2026-02-21T03:09:53.218920938Z except GitCommandError:
2026-02-21T03:09:53.218939868Z pass
2026-02-21T03:09:53.218942808Z
2026-02-21T03:09:53.218947031Z # Nothing worked — raise with a clear message
2026-02-21T03:09:53.218950714Z> raise GitCommandError(
2026-02-21T03:09:53.218954628Z f"git checkout {source_ref}",
2026-02-21T03:09:53.218958211Z 128,
2026-02-21T03:09:53.218962884Z stderr=f"Could not checkout ref '{source_ref}' after all fetch attempts",
2026-02-21T03:09:53.218966151Z )
2026-02-21T03:09:53.218970741ZE git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.218975581ZE cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.218988274ZE stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.218990538Z
2026-02-21T03:09:53.218994271Z/workspace/runnerlib/src/source_prep.py:72: GitCommandError
2026-02-21T03:09:53.218998634Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.219116846Z2026-02-21T03:09:43.150569+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-21T03:09:53.219124149Z2026-02-21T03:09:43.150798+00:00 Cloning git repository: /tmp/tmp1cnsu237/ci_repo
2026-02-21T03:09:53.219130356Z2026-02-21T03:09:43.197406+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.219134339Z2026-02-21T03:09:43.219795+00:00 Fetching PR ref: refs/pull/47/head
2026-02-21T03:09:53.219274481Z2026-02-21T03:09:43.234488+00:00 Fetching PR ref: refs/merge-requests/47/head
2026-02-21T03:09:53.219445326Z2026-02-21T03:09:43.248092+00:00 Fetching all remote refs...
2026-02-21T03:09:53.219455219Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.219732258Z2026-02-21T03:09:43.150430+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmp1cnsu237/ci_repo ref=[REDACTED]
2026-02-21T03:09:53.219752791Z2026-02-21T03:09:43.150727+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmp1cnsu237/ci_repo ref=[REDACTED] target=/job/ci
2026-02-21T03:09:53.220501105Z2026-02-21T03:09:43.283591+00:00 [ERROR] [runnerlib] Failed to prepare git source url=/tmp/tmp1cnsu237/ci_repo error=GitCommandError: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.220508728Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.221154904Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.221164588Z2026-02-21T03:09:43.283765+00:00 Failed to checkout repository: Cmd('git') failed due to: exit code(128)
2026-02-21T03:09:53.221169062Z cmdline: git checkout [REDACTED]
2026-02-21T03:09:53.221174225Z stderr: 'Could not checkout ref '[REDACTED]' after all fetch attempts'
2026-02-21T03:09:53.221178955Z______________ TestSourcePreparation.test_git_source_missing_url _______________
2026-02-21T03:09:53.221181575Z
2026-02-21T03:09:53.221187679Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc0913d8650>
2026-02-21T03:09:53.221189959Z
2026-02-21T03:09:53.221518964Z def test_git_source_missing_url(self):
2026-02-21T03:09:53.221524897Z """Test that git source without URL raises ValueError."""
2026-02-21T03:09:53.221528637Z config = get_config(
2026-02-21T03:09:53.221531987Z job_command="echo 'test'",
2026-02-21T03:09:53.221535834Z source_type="git"
2026-02-21T03:09:53.221539437Z # source_url not provided
2026-02-21T03:09:53.221542714Z )
2026-02-21T03:09:53.221545882Z
2026-02-21T03:09:53.222313745Z> with pytest.raises(ValueError, match="source_url is required"):
2026-02-21T03:09:53.222323329ZE Failed: DID NOT RAISE <class 'ValueError'>
2026-02-21T03:09:53.222327339Z
2026-02-21T03:09:53.222335089Z/workspace/runnerlib/tests/test_source_preparation.py:233: Failed
2026-02-21T03:09:53.222342292Z----------------------------- Captured stdout call -----------------------------
2026-02-21T03:09:53.222362419Z2026-02-21T03:09:43.361213+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-21T03:09:53.224923575Z2026-02-21T03:09:51.483428+00:00 Checking out ref: [REDACTED]
2026-02-21T03:09:53.224931316Z2026-02-21T03:09:52.003092+00:00 Repository checked out to: /job/src
2026-02-21T03:09:53.224936156Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.224944569Z2026-02-21T03:09:43.360976+00:00 [INFO] [runnerlib] Preparing source type=git url=[REDACTED] ref=[REDACTED]
2026-02-21T03:09:53.224956249Z2026-02-21T03:09:43.361138+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/job/src
2026-02-21T03:09:53.224961483Z2026-02-21T03:09:52.002950+00:00 [INFO] [runnerlib] Git source prepared successfully path=/job/src
2026-02-21T03:09:53.224966163Z______________ TestSourcePreparation.test_copy_source_missing_url ______________
2026-02-21T03:09:53.224969036Z
2026-02-21T03:09:53.224978399Zself = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7fc0913d8750>
2026-02-21T03:09:53.224980973Z
2026-02-21T03:09:53.224985516Z def test_copy_source_missing_url(self):
2026-02-21T03:09:53.224990189Z """Test that copy source without URL raises ValueError."""
2026-02-21T03:09:53.224993859Z config = get_config(
2026-02-21T03:09:53.224997446Z job_command="echo 'test'",
2026-02-21T03:09:53.225000583Z source_type="copy"
2026-02-21T03:09:53.225003826Z # source_url not provided
2026-02-21T03:09:53.225007483Z )
2026-02-21T03:09:53.225009973Z
2026-02-21T03:09:53.225014379Z with pytest.raises(ValueError, match="source_url is required"):
2026-02-21T03:09:53.225017689Z> prepare_source(config)
2026-02-21T03:09:53.225019679Z
2026-02-21T03:09:53.225023249Z/workspace/runnerlib/tests/test_source_preparation.py:245:
2026-02-21T03:09:53.225026779Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.225030133Z/workspace/runnerlib/src/source_prep.py:568: in prepare_source
2026-02-21T03:09:53.225033639Z return _prepare_copy_source(config.source_url, target_path)
2026-02-21T03:09:53.225036746Z_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
2026-02-21T03:09:53.225077954Z
2026-02-21T03:09:53.22509146Zsource_url = 'https://[REDACTED].com/[REDACTED].git'
2026-02-21T03:09:53.22509588Ztarget_path = PosixPath('/job/src')
2026-02-21T03:09:53.22509837Z
2026-02-21T03:09:53.225103124Z def _prepare_copy_source(source_url: str, target_path: Path) -> Path:
2026-02-21T03:09:53.22510792Z """Prepare source code by copying from a local directory.
2026-02-21T03:09:53.225279212Z
2026-02-21T03:09:53.225287909Z Args:
2026-02-21T03:09:53.225292626Z source_url: Path to source directory
2026-02-21T03:09:53.225297482Z target_path: Where to copy the directory
2026-02-21T03:09:53.225658352Z
2026-02-21T03:09:53.225665542Z Returns:
2026-02-21T03:09:53.225670112Z Path to the copied directory
2026-02-21T03:09:53.225674292Z """
2026-02-21T03:09:53.22567824Z source_path = Path(source_url).resolve()
2026-02-21T03:09:53.225681063Z
2026-02-21T03:09:53.225733116Z if not source_path.exists():
2026-02-21T03:09:53.225738336Z> raise FileNotFoundError(f"Source directory does not exist: {source_path}")
2026-02-21T03:09:53.22574675ZE FileNotFoundError: Source directory does not exist: /tmp/tmpws2vaj6l/https:/[REDACTED].com/[REDACTED].git
2026-02-21T03:09:53.226050916Z
2026-02-21T03:09:53.226059207Z/workspace/runnerlib/src/source_prep.py:433: FileNotFoundError
2026-02-21T03:09:53.226064047Z----------------------------- Captured stderr call -----------------------------
2026-02-21T03:09:53.226184045Z2026-02-21T03:09:52.022792+00:00 [INFO] [runnerlib] Preparing source type=copy url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T03:09:53.226401046Z_____ TestConfigValidator.test_validate_file_system_job_directory_missing ______
2026-02-21T03:09:53.226407922Z
2026-02-21T03:09:53.227061693Zself = <runnerlib.tests.test_validation.TestConfigValidator object at 0x7fc0919bec50>
2026-02-21T03:09:53.227065959Z
2026-02-21T03:09:53.227070786Z def test_validate_file_system_job_directory_missing(self):
2026-02-21T03:09:53.227075569Z """Test file system validation when job directory is missing."""
2026-02-21T03:09:53.227288887Z # Ensure ./job doesn't exist
2026-02-21T03:09:53.227293717Z job_path = Path("./job")
2026-02-21T03:09:53.227297184Z if job_path.exists():
2026-02-21T03:09:53.227985467Z shutil.rmtree(job_path)
2026-02-21T03:09:53.227989704Z
2026-02-21T03:09:53.227994381Z try:
2026-02-21T03:09:53.227999407Z errors, warnings = self.validator._validate_file_system(self.valid_config)
2026-02-21T03:09:53.228002757Z
2026-02-21T03:09:53.228006857Z # Should have warning about missing directory
2026-02-21T03:09:53.228010207Z> assert len(warnings) >= 1
2026-02-21T03:09:53.228013491ZE assert 0 >= 1
2026-02-21T03:09:53.228313364ZE + where 0 = len([])
2026-02-21T03:09:53.22834549Z
2026-02-21T03:09:53.228351807Z/workspace/runnerlib/tests/test_validation.py:304: AssertionError
2026-02-21T03:09:53.22836035Z=========================== short test summary info ============================
2026-02-21T03:09:53.228949226ZFAILED tests/test_container_isolation.py::TestContainerIsolation::test_work_directory_isolation_with_prepare
2026-02-21T03:09:53.22895735ZFAILED tests/test_directory_operations.py::TestDirectoryOperations::test_cleanup_removes_job_directory
2026-02-21T03:09:53.22896315ZFAILED tests/test_docker_execution.py::test_basic_docker_execution - Assertio...
2026-02-21T03:09:53.22896807ZFAILED tests/test_docker_execution.py::test_docker_with_environment_variables
2026-02-21T03:09:53.22897307ZFAILED tests/test_docker_execution.py::test_docker_with_python - AssertionErr...
2026-02-21T03:09:53.229237198ZFAILED tests/test_docker_execution.py::test_docker_failure_handling - Asserti...
2026-02-21T03:09:53.229242775ZFAILED tests/test_docker_execution.py::test_docker_available - FileNotFoundEr...
2026-02-21T03:09:53.229424096ZFAILED tests/test_docker_execution.py::test_container_with_working_directory
2026-02-21T03:09:53.229436647ZFAILED tests/test_docker_execution.py::test_dry_run_mode - AssertionError: Dr...
2026-02-21T03:09:53.229453714ZFAILED tests/test_docker_execution.py::test_node_container - AssertionError: ...
2026-02-21T03:09:53.231116789ZFAILED tests/test_docker_execution.py::test_container_with_multiple_env_vars
2026-02-21T03:09:53.231130955ZFAILED tests/test_docker_execution.py::test_selective_secret_masking - Assert...
2026-02-21T03:09:53.231136319ZFAILED tests/test_dynamic_secret_masking.py::test_value_printed_then_masked
2026-02-21T03:09:53.231142405ZFAILED tests/test_dynamic_secret_masking.py::test_multiple_values_masked_after_registration
2026-02-21T03:09:53.231159155ZFAILED tests/test_dynamic_secret_masking.py::test_immediate_masking_in_streaming_output
2026-02-21T03:09:53.231164689ZFAILED tests/test_dynamic_secrets.py::test_dynamic_secret_registration - Asse...
2026-02-21T03:09:53.231169255ZFAILED tests/test_dynamic_secrets.py::test_multiple_dynamic_secrets - Asserti...
2026-02-21T03:09:53.231173852ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_basic_eval_with_match - ...
2026-02-21T03:09:53.231178172ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_multiple_matches - ...
2026-02-21T03:09:53.231182402ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_branch_filter - Ass...
2026-02-21T03:09:53.231186023ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_full_event_context
2026-02-21T03:09:53.23119026ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_with_changed_files
2026-02-21T03:09:53.23119466ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_pr_uses_base_ref_for_diff
2026-02-21T03:09:53.231199593ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_push_uses_head_parent_for_diff
2026-02-21T03:09:53.231204817ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_no_git_dir_skips_changed_files
2026-02-21T03:09:53.231209017ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_env_vars - Assertio...
2026-02-21T03:09:53.231215937ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_job_priority_and_timeout
2026-02-21T03:09:53.23121976ZFAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_git_error_continues
2026-02-21T03:09:53.23122427ZFAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_ci_source_when_missing
2026-02-21T03:09:53.23122837ZFAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_source_when_missing
2026-02-21T03:09:53.231404035ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_pr_opened_triggers_test_not_deploy
2026-02-21T03:09:53.231411395ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_push_to_[REDACTED]_triggers_deploy
2026-02-21T03:09:53.231416522ZFAILED tests/test_eval_cli.py::TestEvalEndToEnd::test_tag_created_triggers_release
2026-02-21T03:09:53.231421972ZFAILED tests/test_git_operations.py::TestGitOperations::test_checkout_creates_job_directory
2026-02-21T03:09:53.231753928ZFAILED tests/test_integration.py::TestDirectoryManagementIntegration::test_directory_validation_with_real_filesystem
2026-02-21T03:09:53.231759698ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_work_dir_isolation
2026-02-21T03:09:53.231775258ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_concurrent_job_isolation
2026-02-21T03:09:53.231952007ZFAILED tests/test_job_isolation.py::TestJobIsolation::test_container_mount_isolation
2026-02-21T03:09:53.23195817ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_no_source_preparation_default
2026-02-21T03:09:53.231963973ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_preparation
2026-02-21T03:09:53.231968817ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_dual_source_preparation
2026-02-21T03:09:53.23227834ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_ci_source_only
2026-02-21T03:09:53.23228455ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_missing_url
2026-02-21T03:09:53.232502532ZFAILED tests/test_source_preparation.py::TestSourcePreparation::test_copy_source_missing_url
2026-02-21T03:09:53.232510468ZFAILED tests/test_validation.py::TestConfigValidator::test_validate_file_system_job_directory_missing
2026-02-21T03:09:53.232803647Z============ 45 failed, 351 passed, 1 skipped in 286.38s (0:04:46) =============