From 609481dfbd50b993b3daf7e34b2f28f0dbfaa314 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:52:05 +0000 Subject: [PATCH 1/6] Initial plan From d89612dc2a0d2493c786ff073ac661209f27a3dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:56:36 +0000 Subject: [PATCH 2/6] Fix tox allowlist_externals and coverage-ci robustness - Remove invalid ' *' suffix from allowlist_externals patterns in [testenv] and [testenv:py38]; the old patterns '/usr/bin/git *' had a literal space+asterisk making them never match - Add missing 'rm' to allowlist so /bin/rm commands are permitted - Change hardcoded /usr/bin/git, /bin/rm, /usr/bin/cp to bare names for portability - Replace coverage-ci multi-command block with a single Python one-liner that skips combine/xml/report gracefully when no .coverage.* files exist (prevents 'No data to combine' error when upstream test envs haven't produced coverage data) Agent-Logs-Url: https://github.com/mitre/atomic/sessions/bad89eda-e616-45ad-a73c-9a874ab39e0e Co-authored-by: deacon-mp <61169193+deacon-mp@users.noreply.github.com> --- tox.ini | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/tox.ini b/tox.ini index ce5c3be..af1a6c3 100644 --- a/tox.ini +++ b/tox.ini @@ -28,15 +28,15 @@ deps = codecov changedir = {homedir}/tmp commands = - /usr/bin/git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp - /bin/rm -rf {homedir}/tmp/plugins/atomic + git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp + rm -rf {homedir}/tmp/plugins/atomic python -m pip install -r {homedir}/tmp/requirements.txt - /usr/bin/cp -R {toxinidir} {homedir}/tmp/plugins/atomic + cp -R {toxinidir} {homedir}/tmp/plugins/atomic coverage run -p -m pytest --tb=short --rootdir={homedir}/tmp -Werror plugins/atomic/tests allowlist_externals = - /usr/bin/sudo * - /usr/bin/git * - /usr/bin/cp * + git + rm + cp [testenv:py38] description = run tests @@ -49,15 +49,15 @@ deps = codecov changedir = {homedir}/tmp commands = - /usr/bin/git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp - /bin/rm -rf {homedir}/tmp/plugins/atomic + git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp + rm -rf {homedir}/tmp/plugins/atomic python -m pip install -r {homedir}/tmp/requirements.txt - /usr/bin/cp -R {toxinidir} {homedir}/tmp/plugins/atomic + cp -R {toxinidir} {homedir}/tmp/plugins/atomic coverage run -p -m pytest --tb=short --rootdir={homedir}/tmp {homedir}/tmp/plugins/atomic/tests allowlist_externals = - /usr/bin/sudo * - /usr/bin/git * - /usr/bin/cp * + git + rm + cp [testenv:coverage] deps = @@ -76,6 +76,4 @@ deps = skip_install = true changedir = {homedir}/tmp commands = - coverage combine - coverage xml - coverage report + python -c "import glob,subprocess,sys; files=glob.glob('.coverage.*'); [subprocess.run(c,check=True) for c in [['coverage','combine'],['coverage','xml'],['coverage','report']]] if files else print('No coverage data files found, skipping combine')" From 93d958d75f2b963bea1a54f5eb41475c5504e816 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:57:50 +0000 Subject: [PATCH 3/6] Refactor coverage-ci one-liner to a readable helper script Move the inline Python one-liner in tox.ini [testenv:coverage-ci] to a proper, documented script at scripts/check_coverage.py. The script checks for .coverage.* files and runs coverage combine/xml/report only when data exists, exiting 0 gracefully when no coverage data is present. Agent-Logs-Url: https://github.com/mitre/atomic/sessions/bad89eda-e616-45ad-a73c-9a874ab39e0e Co-authored-by: deacon-mp <61169193+deacon-mp@users.noreply.github.com> --- scripts/check_coverage.py | 25 +++++++++++++++++++++++++ tox.ini | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 scripts/check_coverage.py diff --git a/scripts/check_coverage.py b/scripts/check_coverage.py new file mode 100644 index 0000000..30b0c78 --- /dev/null +++ b/scripts/check_coverage.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Run coverage combine/xml/report only when coverage data files exist. + +Used by the coverage-ci tox environment to avoid a "No data to combine" +failure when upstream test environments did not produce any coverage data. +""" +import glob +import subprocess +import sys + + +def main(): + files = glob.glob('.coverage.*') + if not files: + print('No coverage data files found, skipping combine') + return + + for cmd in [['coverage', 'combine'], ['coverage', 'xml'], ['coverage', 'report']]: + result = subprocess.run(cmd) + if result.returncode != 0: + sys.exit(result.returncode) + + +if __name__ == '__main__': + main() diff --git a/tox.ini b/tox.ini index af1a6c3..1b4f0ba 100644 --- a/tox.ini +++ b/tox.ini @@ -76,4 +76,4 @@ deps = skip_install = true changedir = {homedir}/tmp commands = - python -c "import glob,subprocess,sys; files=glob.glob('.coverage.*'); [subprocess.run(c,check=True) for c in [['coverage','combine'],['coverage','xml'],['coverage','report']]] if files else print('No coverage data files found, skipping combine')" + python {toxinidir}/scripts/check_coverage.py From e4cf2bc1469193040ec1388e87c1871c9bb730ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:58:47 +0000 Subject: [PATCH 4/6] Improve error diagnostics in check_coverage.py Add stderr message identifying which coverage command failed and its exit code, making CI log debugging easier when coverage operations encounter real errors. Agent-Logs-Url: https://github.com/mitre/atomic/sessions/bad89eda-e616-45ad-a73c-9a874ab39e0e Co-authored-by: deacon-mp <61169193+deacon-mp@users.noreply.github.com> --- scripts/check_coverage.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/check_coverage.py b/scripts/check_coverage.py index 30b0c78..58cca7c 100644 --- a/scripts/check_coverage.py +++ b/scripts/check_coverage.py @@ -18,6 +18,10 @@ def main(): for cmd in [['coverage', 'combine'], ['coverage', 'xml'], ['coverage', 'report']]: result = subprocess.run(cmd) if result.returncode != 0: + print( + f'Command {" ".join(cmd)!r} failed with exit code {result.returncode}', + file=sys.stderr, + ) sys.exit(result.returncode) From b3084ffcba3cdad1042c7ad988de4973b7a46cdf Mon Sep 17 00:00:00 2001 From: deacon-mp Date: Thu, 30 Apr 2026 12:58:33 -0400 Subject: [PATCH 5/6] Fix tox 4 substitution: {homedir} -> {env:HOME} tox 4 dropped the {homedir} substitution that tox 3 supported. The previous CI run got past the allowlist_externals fix but then failed in the cp step with: /usr/bin/cp: cannot copy a directory, '/home/runner/work/atomic/atomic', into itself, '{homedir}/tmp/plugins/atomic' because {homedir} was being passed verbatim. The earlier git clone happily created a literal directory named "{homedir}" under cwd, and the cp destination then resolved inside the source tree. Replacing {homedir} with {env:HOME} (the tox 4 spelling) restores the intended ~/tmp working directory. HOME is in tox 4's default pass-through set, so no passenv change is required. --- tox.ini | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tox.ini b/tox.ini index 1b4f0ba..38257e9 100644 --- a/tox.ini +++ b/tox.ini @@ -26,13 +26,13 @@ deps = pytest-aiohttp coverage codecov -changedir = {homedir}/tmp +changedir = {env:HOME}/tmp commands = - git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp - rm -rf {homedir}/tmp/plugins/atomic - python -m pip install -r {homedir}/tmp/requirements.txt - cp -R {toxinidir} {homedir}/tmp/plugins/atomic - coverage run -p -m pytest --tb=short --rootdir={homedir}/tmp -Werror plugins/atomic/tests + git clone https://github.com/mitre/caldera.git --recursive {env:HOME}/tmp + rm -rf {env:HOME}/tmp/plugins/atomic + python -m pip install -r {env:HOME}/tmp/requirements.txt + cp -R {toxinidir} {env:HOME}/tmp/plugins/atomic + coverage run -p -m pytest --tb=short --rootdir={env:HOME}/tmp -Werror plugins/atomic/tests allowlist_externals = git rm @@ -47,13 +47,13 @@ deps = pytest-aiohttp coverage codecov -changedir = {homedir}/tmp +changedir = {env:HOME}/tmp commands = - git clone https://github.com/mitre/caldera.git --recursive {homedir}/tmp - rm -rf {homedir}/tmp/plugins/atomic - python -m pip install -r {homedir}/tmp/requirements.txt - cp -R {toxinidir} {homedir}/tmp/plugins/atomic - coverage run -p -m pytest --tb=short --rootdir={homedir}/tmp {homedir}/tmp/plugins/atomic/tests + git clone https://github.com/mitre/caldera.git --recursive {env:HOME}/tmp + rm -rf {env:HOME}/tmp/plugins/atomic + python -m pip install -r {env:HOME}/tmp/requirements.txt + cp -R {toxinidir} {env:HOME}/tmp/plugins/atomic + coverage run -p -m pytest --tb=short --rootdir={env:HOME}/tmp {env:HOME}/tmp/plugins/atomic/tests allowlist_externals = git rm @@ -63,7 +63,7 @@ allowlist_externals = deps = coverage skip_install = true -changedir = {homedir}/tmp +changedir = {env:HOME}/tmp commands = coverage combine coverage html @@ -74,6 +74,6 @@ deps = coveralls coverage skip_install = true -changedir = {homedir}/tmp +changedir = {env:HOME}/tmp commands = python {toxinidir}/scripts/check_coverage.py From 93c6481e089285f6e967aeabb22c2453a30a9ea0 Mon Sep 17 00:00:00 2001 From: deacon-mp Date: Thu, 30 Apr 2026 13:03:59 -0400 Subject: [PATCH 6/6] Remove dead app_svc.application access in enable() and its test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The line `app = services.get('app_svc').application` in hook.enable() was assigned but never used downstream. Its companion test test_enable_accesses_app verified that the access happened, using a PropertyMock attached to the MagicMock class — a fragile pattern that silently failed to register the call across all four matrix Python versions (1 failed / 143 passed in CI). Drop the dead access from hook.py and the test that covered it. No behavioral change to enable() — atomic_gui is still instantiated and the abilities-dir branch still runs as before. --- hook.py | 1 - tests/test_hook.py | 22 +--------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/hook.py b/hook.py index 105e34f..48d273f 100644 --- a/hook.py +++ b/hook.py @@ -13,7 +13,6 @@ async def enable(services): atomic_gui = AtomicGUI(services, name, description) - app = services.get('app_svc').application # we only ingest data once, and save new abilities in the data/ folder of the plugin if "abilities" not in os.listdir(data_dir): diff --git a/tests/test_hook.py b/tests/test_hook.py index f23c090..34d6c79 100644 --- a/tests/test_hook.py +++ b/tests/test_hook.py @@ -1,6 +1,6 @@ import os import pytest -from unittest.mock import MagicMock, AsyncMock, patch, PropertyMock +from unittest.mock import MagicMock, AsyncMock, patch class TestHookModuleAttributes: @@ -103,23 +103,3 @@ async def test_enable_skips_ingest_when_abilities_exist(self): # AtomicService should NOT be instantiated when abilities dir exists mock_svc_cls.assert_not_called() - @pytest.mark.asyncio - async def test_enable_accesses_app(self): - """enable() should access services['app_svc'].application.""" - import hook - - mock_app = MagicMock() - mock_app_svc = MagicMock() - type(mock_app_svc).application = PropertyMock(return_value=mock_app) - - services = { - 'auth_svc': MagicMock(), - 'data_svc': MagicMock(), - 'app_svc': mock_app_svc, - } - - with patch.object(hook, 'data_dir', '/tmp/atomic_test_hook_data'), \ - patch('os.listdir', return_value=['abilities']), \ - patch('hook.AtomicGUI'): - await hook.enable(services) - type(mock_app_svc).application.assert_called()