test-python
failed- Job ID
019c7f02-cd6e-f352-77d9-630a130ae102- Created
- 2026-02-21 07:03:36 UTC
- Updated
- 2026-02-21 07:03:36 UTC
- Duration
- 4m 6s
- Source Ref
- a697a2c51c98ad174f1afd0f06ee7363f998a380
- 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
Cloning into '/workspace'...
=== Running Python Tests ===
Using CPython 3.13.12 interpreter at: /usr/local/bin/python3.13
Creating virtual environment at: .venv
Building runnerlib @ file:///workspace/runnerlib
Downloading pygments (1.2MiB)
Downloading cryptography (4.3MiB)
Downloaded cryptography
Downloaded pygments
Built runnerlib @ file:///workspace/runnerlib
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 22 packages in 112ms
============================= test session starts ==============================
platform linux -- Python 3.13.12, pytest-8.3.5, pluggy-1.6.0
rootdir: /workspace/runnerlib
configfile: pyproject.toml
plugins: cov-7.0.0
collected 381 items
tests/test_config.py .................... [ 5%]
tests/test_container_advanced.py ......... [ 7%]
tests/test_container_isolation.py .... [ 8%]
tests/test_container_validation.py ................... [ 13%]
tests/test_directory_operations.py ............ [ 16%]
tests/test_eval.py ..................................................... [ 30%]
............. [ 34%]
tests/test_eval_cli.py .........FFF....F.... [ 39%]
tests/test_git_operations.py .......... [ 42%]
tests/test_git_ops.py ....... [ 44%]
tests/test_integration.py ........... [ 46%]
tests/test_job_isolation.py ...F [ 48%]
tests/test_plugins.py ....................... [ 54%]
tests/test_register_secret.py ............ [ 57%]
tests/test_secrets.py ................... [ 62%]
tests/test_secrets_local.py ............................ [ 69%]
tests/test_secrets_resolver.py ............................. [ 77%]
tests/test_secrets_server.py ........ [ 79%]
tests/test_source_preparation.py .F...F.FF...... [ 83%]
tests/test_validation.py ........................... [ 90%]
tests/test_workflow.py ..................................... [100%]
=================================== FAILURES ===================================
_____________ TestEvalCommand.test_eval_pr_uses_base_ref_for_diff ______________
self = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f1f46206a80>
temp_dirs = (PosixPath('/tmp/tmphyunmizn/ci'), PosixPath('/tmp/tmphyunmizn/src'), PosixPath('/tmp/tmphyunmizn/ci/.reactorcide/jobs'), PosixPath('/tmp/tmphyunmizn/triggers.json'))
def test_eval_pr_uses_base_ref_for_diff(self, temp_dirs):
"""Test that PR events use pr_base_ref for changed files diff."""
ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
_write_yaml(jobs_dir / "test.yaml", {
"name": "test",
"triggers": {"events": ["pull_request_opened"]},
"job": {"image": "alpine:latest", "command": "make test"},
})
(src_dir / ".git").mkdir()
with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
result = runner.invoke(app, [
"eval",
"--ci-source-dir", str(ci_dir),
"--source-dir", str(src_dir),
"--event-type", "pull_request_opened",
"--branch", "feature/foo",
"--pr-base-ref", "[REDACTED]",
"--triggers-file", str(triggers_file),
])
# Verify it was called with origin/[REDACTED] as the from_ref
> mock_changed.assert_called_once_with(
"origin/[REDACTED]", "HEAD", str(src_dir)
)
tests/test_eval_cli.py:344:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='changed_files' id='139772302321584'>
args = ('origin/[REDACTED]', 'HEAD', '/tmp/tmphyunmizn/src'), kwargs = {}
expected = call('origin/[REDACTED]', 'HEAD', '/tmp/tmphyunmizn/src')
actual = call('[REDACTED]', 'HEAD', '/tmp/tmphyunmizn/src')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f1f453f5d00>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: changed_files('origin/[REDACTED]', 'HEAD', '/tmp/tmphyunmizn/src')
E Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmphyunmizn/src')
/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
___________ TestEvalCommand.test_eval_push_uses_head_parent_for_diff ___________
self = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f1f46206b70>
temp_dirs = (PosixPath('/tmp/tmpb_yxub0u/ci'), PosixPath('/tmp/tmpb_yxub0u/src'), PosixPath('/tmp/tmpb_yxub0u/ci/.reactorcide/jobs'), PosixPath('/tmp/tmpb_yxub0u/triggers.json'))
def test_eval_push_uses_head_parent_for_diff(self, temp_dirs):
"""Test that push events use HEAD^ for changed files diff."""
ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
_write_yaml(jobs_dir / "test.yaml", {
"name": "test",
"triggers": {"events": ["push"]},
"job": {"image": "alpine:latest", "command": "make test"},
})
(src_dir / ".git").mkdir()
with patch("src.workflow.changed_files", return_value=["file.py"]) as mock_changed:
result = runner.invoke(app, [
"eval",
"--ci-source-dir", str(ci_dir),
"--source-dir", str(src_dir),
"--event-type", "push",
"--branch", "[REDACTED]",
"--triggers-file", str(triggers_file),
])
> mock_changed.assert_called_once_with(
"HEAD^", "HEAD", str(src_dir)
)
tests/test_eval_cli.py:372:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.13/unittest/mock.py:991: in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='changed_files' id='139772302323600'>
args = ('HEAD^', 'HEAD', '/tmp/tmpb_yxub0u/src'), kwargs = {}
expected = call('HEAD^', 'HEAD', '/tmp/tmpb_yxub0u/src')
actual = call('[REDACTED]', 'HEAD', '/tmp/tmpb_yxub0u/src')
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x7f1f453f4860>
cause = None
def assert_called_with(self, /, *args, **kwargs):
"""assert that the last call was made with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
actual = 'not called.'
error_message = ('expected call not found.\nExpected: %s\n Actual: %s'
% (expected, actual))
raise AssertionError(error_message)
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher(_Call((args, kwargs), two=True))
actual = self._call_matcher(self.call_args)
if actual != expected:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: expected call not found.
E Expected: changed_files('HEAD^', 'HEAD', '/tmp/tmpb_yxub0u/src')
E Actual: changed_files('[REDACTED]', 'HEAD', '/tmp/tmpb_yxub0u/src')
/usr/local/lib/python3.13/unittest/mock.py:979: AssertionError
___________ TestEvalCommand.test_eval_no_git_dir_skips_changed_files ___________
self = <runnerlib.tests.test_eval_cli.TestEvalCommand object at 0x7f1f466318d0>
temp_dirs = (PosixPath('/tmp/tmp9szcunwp/ci'), PosixPath('/tmp/tmp9szcunwp/src'), PosixPath('/tmp/tmp9szcunwp/ci/.reactorcide/jobs'), PosixPath('/tmp/tmp9szcunwp/triggers.json'))
def test_eval_no_git_dir_skips_changed_files(self, temp_dirs):
"""Test that eval skips changed files detection when no .git dir exists."""
ci_dir, src_dir, jobs_dir, triggers_file = temp_dirs
_write_yaml(jobs_dir / "test.yaml", {
"name": "test",
"triggers": {"events": ["push"]},
"paths": {"include": ["src/**"]},
"job": {"image": "alpine:latest", "command": "make test"},
})
# No .git directory - should skip changed files and still match
# (path filtering is skipped when changed_files is None)
result = runner.invoke(app, [
"eval",
"--ci-source-dir", str(ci_dir),
"--source-dir", str(src_dir),
"--event-type", "push",
"--triggers-file", str(triggers_file),
])
assert result.exit_code == 0
> assert triggers_file.exists()
E AssertionError: assert False
E + where False = exists()
E + where exists = PosixPath('/tmp/tmp9szcunwp/triggers.json').exists
tests/test_eval_cli.py:400: AssertionError
________ TestEvalSourcePreparation.test_eval_clones_source_when_missing ________
self = <runnerlib.tests.test_eval_cli.TestEvalSourcePreparation object at 0x7f1f466ab4d0>
def test_eval_clones_source_when_missing(self):
"""Test that eval clones source repo when .git dir doesn't exist."""
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir)
ci_dir = base / "ci"
src_dir = base / "src"
src_dir.mkdir() # Exists but no .git (like worker creates)
jobs_dir = ci_dir / ".reactorcide" / "jobs"
jobs_dir.mkdir(parents=True)
triggers_file = base / "triggers.json"
# Create a fake "remote" source repo
remote_dir = base / "remote_src"
remote_dir.mkdir()
from git import Repo
remote_repo = Repo.init(remote_dir)
(remote_dir / "[REDACTED].py").write_text("print('hello')")
remote_repo.index.add(["[REDACTED].py"])
remote_repo.index.commit("Initial commit")
_write_yaml(jobs_dir / "test.yaml", {
"name": "test",
"triggers": {"events": ["push"]},
"job": {"image": "alpine:latest", "command": "make test"},
})
result = runner.invoke(app, [
"eval",
"--ci-source-dir", str(ci_dir),
"--source-dir", str(src_dir),
"--event-type", "push",
"--branch", "[REDACTED]",
"--source-url", str(remote_dir),
"--triggers-file", str(triggers_file),
])
> assert result.exit_code == 0
E AssertionError: assert 1 == 0
E + where 1 = <Result GitCommandError('git checkout [REDACTED]', 128)>.exit_code
tests/test_eval_cli.py:567: AssertionError
_______________ TestJobIsolation.test_container_mount_isolation ________________
self = <runnerlib.tests.test_job_isolation.TestJobIsolation object at 0x7f1f4620b820>
mock_popen = <MagicMock name='Popen' id='139772282208672'>
@patch('subprocess.Popen')
def test_container_mount_isolation(self, mock_popen):
"""Test that containers mount only their job's directory."""
# Mock the Popen object with proper behavior
mock_process = MagicMock()
mock_process.poll.side_effect = [None, None, 0] # Running, running, then finished
mock_process.returncode = 0
mock_process.stdout.readline.return_value = '' # No output (text mode)
mock_process.stderr.readline.return_value = '' # No errors
mock_process.communicate.return_value = ('', '') # Empty re[REDACTED]ing output
mock_popen.return_value = mock_process
with tempfile.TemporaryDirectory() as temp_dir:
# Save original cwd if possible
try:
original_cwd = os.getcwd()
except FileNotFoundError:
# If current dir doesn't exist, use temp dir as fallback
original_cwd = temp_dir
try:
os.chdir(temp_dir)
config = RunnerConfig(
code_dir="/job/src",
job_dir="/job/src",
job_command="echo test",
runner_image="alpine:latest"
)
# Prepare job directory
job_path = prepare_job_directory(config)
# Create a test file
test_file = job_path / "test.txt"
test_file.write_text("test data")
# Run container
> run_container(config)
/workspace/runnerlib/tests/test_job_isolation.py:257:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
config = 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)
additional_args = None
def run_container(
config: RunnerConfig,
additional_args: Optional[List[str]] = None
) -> int:
"""Run the job container using docker with full configuration support.
Args:
config: Runner configuration
additional_args: Additional arguments to pass to the job command
Returns:
Exit code of the container process
Raises:
ValueError: If configuration is invalid
FileNotFoundError: If docker is not available
"""
# Create plugin context for the execution
plugin_context = PluginContext(
config=config,
phase=PluginPhase.PRE_SOURCE_PREP,
metadata={}
)
try:
# Execute pre-source-prep plugins
plugin_manager.execute_phase(PluginPhase.PRE_SOURCE_PREP, plugin_context)
# Basic validation is handled by CLI layer
# Check if docker is available
if not shutil.which("docker"):
logger.error("Docker is not available in PATH")
> raise FileNotFoundError("docker is not available in PATH")
E FileNotFoundError: docker is not available in PATH
/workspace/runnerlib/src/container.py:114: FileNotFoundError
----------------------------- Captured stderr call -----------------------------
2026-02-21T07:07:03.701227+00:00 [ERROR] [runnerlib] Docker is not available in PATH
___________ TestSourcePreparation.test_no_source_preparation_default ___________
self = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f1f466491d0>
def test_no_source_preparation_default(self):
"""Test job with no source preparation (default - source_type not set)."""
# Configure without specifying source_type
config = get_config(job_command="echo 'hello'")
# Prepare source should return None
result = prepare_source(config)
> assert result is None
E AssertionError: assert PosixPath('/tmp/tmpy2338_yc/job/src') is None
/workspace/runnerlib/tests/test_source_preparation.py:89: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-02-21T07:07:15.012704+00:00 Cloning git repository: https://[REDACTED].com/[REDACTED].git
2026-02-21T07:07:25.910878+00:00 Checking out ref: [REDACTED]
2026-02-21T07:07:26.370166+00:00 Repository checked out to: /tmp/tmpy2338_yc/job/src
----------------------------- Captured stderr call -----------------------------
2026-02-21T07:07:15.012329+00:00 [INFO] [runnerlib] Preparing source type=git url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T07:07:15.012552+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/tmp/tmpy2338_yc/job/src
2026-02-21T07:07:26.369999+00:00 [INFO] [runnerlib] Git source prepared successfully path=/tmp/tmpy2338_yc/job/src
__________________ TestSourcePreparation.test_ci_source_only ___________________
self = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f1f4662d8c0>
def test_ci_source_only(self):
"""Test preparation of CI source without regular source."""
# Create CI repo
ci_repo_dir = Path(self.temp_dir) / "ci_repo"
ci_repo_dir.mkdir()
ci_repo = Repo.init(ci_repo_dir)
(ci_repo_dir / "deploy.sh").write_text("#!/bin/bash\necho deploying")
ci_repo.index.add(["deploy.sh"])
ci_repo.index.commit("CI commit")
# Configure with only CI source
config = get_config(
job_command="bash /job/ci/deploy.sh",
ci_source_type="git",
ci_source_url=str(ci_repo_dir),
ci_source_ref="[REDACTED]"
)
# Prepare CI source
ci_result = prepare_ci_source(config)
assert ci_result is not None
assert (ci_result / "deploy.sh").exists()
# Prepare regular source (should return None)
source_result = prepare_source(config)
> assert source_result is None
E AssertionError: assert PosixPath('/tmp/tmpvakaz0h4/job/src') is None
/workspace/runnerlib/tests/test_source_preparation.py:212: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-02-21T07:07:26.952394+00:00 🔐 Preparing trusted CI source (type: git)
2026-02-21T07:07:26.952597+00:00 Cloning git repository: /tmp/tmpvakaz0h4/ci_repo
2026-02-21T07:07:26.998284+00:00 Checking out ref: [REDACTED]
2026-02-21T07:07:27.014551+00:00 Repository checked out to: /tmp/tmpvakaz0h4/job/ci
2026-02-21T07:07:27.015318+00:00 Cloning git repository: [REDACTED]
2026-02-21T07:07:36.868778+00:00 Checking out ref: [REDACTED]
2026-02-21T07:07:37.337982+00:00 Repository checked out to: /tmp/tmpvakaz0h4/job/src
----------------------------- Captured stderr call -----------------------------
2026-02-21T07:07:26.952245+00:00 [INFO] [runnerlib] Preparing CI source type=git url=/tmp/tmpvakaz0h4/ci_repo ref=[REDACTED]
2026-02-21T07:07:26.952525+00:00 [INFO] [runnerlib] Preparing git source url=/tmp/tmpvakaz0h4/ci_repo ref=[REDACTED] target=/tmp/tmpvakaz0h4/job/ci
2026-02-21T07:07:27.014416+00:00 [INFO] [runnerlib] Git source prepared successfully path=/tmp/tmpvakaz0h4/job/ci
2026-02-21T07:07:27.015126+00:00 [INFO] [runnerlib] Preparing source type=git url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED]
2026-02-21T07:07:27.015248+00:00 [INFO] [runnerlib] Preparing git source url=[REDACTED] ref=[REDACTED] target=/tmp/tmpvakaz0h4/job/src
2026-02-21T07:07:37.337838+00:00 [INFO] [runnerlib] Git source prepared successfully path=/tmp/tmpvakaz0h4/job/src
______________ TestSourcePreparation.test_git_source_missing_url _______________
self = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f1f4675c150>
def test_git_source_missing_url(self):
"""Test that git source without URL raises ValueError."""
config = get_config(
job_command="echo 'test'",
source_type="git"
# source_url not provided
)
> with pytest.raises(ValueError, match="source_url is required"):
E Failed: DID NOT RAISE <class 'ValueError'>
/workspace/runnerlib/tests/test_source_preparation.py:233: Failed
----------------------------- Captured stdout call -----------------------------
2026-02-21T07:07:37.490767+00:00 Cloning git repository: [REDACTED]
2026-02-21T07:07:46.758055+00:00 Checking out ref: [REDACTED]
2026-02-21T07:07:47.634591+00:00 Repository checked out to: /tmp/tmp7p79op4a/job/src
----------------------------- Captured stderr call -----------------------------
2026-02-21T07:07:37.490495+00:00 [INFO] [runnerlib] Preparing source type=git url=[REDACTED] ref=[REDACTED]
2026-02-21T07:07:37.490686+00:00 [INFO] [runnerlib] Preparing git source url=https://[REDACTED].com/[REDACTED].git ref=[REDACTED] target=/tmp/tmp7p79op4a/job/src
2026-02-21T07:07:47.634424+00:00 [INFO] [runnerlib] Git source prepared successfully path=/tmp/tmp7p79op4a/job/src
______________ TestSourcePreparation.test_copy_source_missing_url ______________
self = <runnerlib.tests.test_source_preparation.TestSourcePreparation object at 0x7f1f4675c250>
def test_copy_source_missing_url(self):
"""Test that copy source without URL raises ValueError."""
config = get_config(
job_command="echo 'test'",
source_type="copy"
# source_url not provided
)
with pytest.raises(ValueError, match="source_url is required"):
> prepare_source(config)
/workspace/runnerlib/tests/test_source_preparation.py:245:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/workspace/runnerlib/src/source_prep.py:571: in prepare_source
return _prepare_copy_source(config.source_url, target_path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
source_url = 'https://[REDACTED].com/[REDACTED].git'
target_path = PosixPath('/tmp/tmpkko9dy8x/job/src')
def _prepare_copy_source(source_url: str, target_path: Path) -> Path:
"""Prepare source code by copying from a local directory.
Args:
source_url: Path to source directory
target_path: Where to copy the directory
Returns:
Path to the copied directory
"""
source_path = Path(source_url).resolve()
if not source_path.exists():
> raise FileNotFoundError(f"Source directory does not exist: {source_path}")
E FileNotFoundError: Source directory does not exist: /tmp/tmpkko9dy8x/https:/[REDACTED].com/[REDACTED].git
/workspace/runnerlib/src/source_prep.py:436: FileNotFoundError
----------------------------- Captured stderr call -----------------------------
2026-02-21T07:07:47.766525+00:00 [INFO] [runnerlib] Preparing source type=copy url=[REDACTED] ref=[REDACTED]
=========================== short test summary info ============================
FAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_pr_uses_base_ref_for_diff
FAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_push_uses_head_parent_for_diff
FAILED tests/test_eval_cli.py::TestEvalCommand::test_eval_no_git_dir_skips_changed_files
FAILED tests/test_eval_cli.py::TestEvalSourcePreparation::test_eval_clones_source_when_missing
FAILED tests/test_job_isolation.py::TestJobIsolation::test_container_mount_isolation
FAILED tests/test_source_preparation.py::TestSourcePreparation::test_no_source_preparation_default
FAILED tests/test_source_preparation.py::TestSourcePreparation::test_ci_source_only
FAILED tests/test_source_preparation.py::TestSourcePreparation::test_git_source_missing_url
FAILED tests/test_source_preparation.py::TestSourcePreparation::test_copy_source_missing_url
================== 9 failed, 372 passed in 156.80s (0:02:36) ===================