diff --git a/src/gcm/commands/__init__.py b/src/gcm/commands/__init__.py index 537c4aa..7522ace 100644 --- a/src/gcm/commands/__init__.py +++ b/src/gcm/commands/__init__.py @@ -1,5 +1,11 @@ +from .clear import Clear from .disable import Disable from .enable import Enable from .stats import Stats -COMMANDS = [Enable(), Disable(), Stats()] +COMMANDS = [ + Enable(), + Disable(), + Clear(), + Stats(), +] diff --git a/src/gcm/commands/clear.py b/src/gcm/commands/clear.py new file mode 100644 index 0000000..94d6ede --- /dev/null +++ b/src/gcm/commands/clear.py @@ -0,0 +1,58 @@ +import subprocess + +import click + +from ..utils import warn +from .base import PACKAGE_NAME, Command, ccache_dir_env +from .exit_codes import ( + CCACHE_BINARY_NOT_FOUND, + OK, + PERMISSION_DENIED, + UNKNOWN_ERROR, +) + + +def clear_stats(package: str, keep_stats: bool) -> int: + args = ['ccache', '-C'] + if not keep_stats: + args.append('-z') + + try: + result = subprocess.run( + args, + stderr=subprocess.PIPE, + env=ccache_dir_env(package), + text=True, + ) + except FileNotFoundError: + warn('Unable to clear ccache data due to missing ccache binary') + return CCACHE_BINARY_NOT_FOUND + + if result.returncode: + click.echo(f'\n{result.stderr}', nl=False, err=True) + if 'Permission denied' in result.stderr: + warn('Unable to clear ccache data due to a lack of permissions') + return PERMISSION_DENIED + + warn('Unknown error, please submit a bug report!') + return UNKNOWN_ERROR + + return OK + + +class Clear(Command): + """Clear ccache data for a package.""" + + INVOKE_MESSAGE = f'Clearing ccache data for {PACKAGE_NAME}' + + params = [ + click.Option( + ['-k', '--keep-stats'], + is_flag=True, + help='Keep statistics counters.', + ) + ] + + @staticmethod + def callback(package: str, keep_stats: bool) -> int: # type: ignore[override] + return clear_stats(package, keep_stats) diff --git a/src/gcm/commands/exit_codes.py b/src/gcm/commands/exit_codes.py index bae3021..672874f 100644 --- a/src/gcm/commands/exit_codes.py +++ b/src/gcm/commands/exit_codes.py @@ -1,4 +1,6 @@ +UNKNOWN_ERROR = -1 OK = 0 AMBIGUOUS_PACKAGE_NAME = 1 PACKAGE_NOT_FOUND = 2 CCACHE_BINARY_NOT_FOUND = 3 +PERMISSION_DENIED = 4 diff --git a/tests/commands/test_clear.py b/tests/commands/test_clear.py new file mode 100644 index 0000000..b9e9bfc --- /dev/null +++ b/tests/commands/test_clear.py @@ -0,0 +1,93 @@ +import subprocess +from unittest.mock import patch + +import pytest +from click.testing import CliRunner + +from gcm.commands.base import ccache_dir_env +from gcm.commands.clear import Clear +from gcm.commands.exit_codes import ( + CCACHE_BINARY_NOT_FOUND, + OK, + PERMISSION_DENIED, + UNKNOWN_ERROR, +) + +from ..base import ABORTED, DONE + +OUTPUT = 'Clearing ccache data for \x1b[32m\x1b[1mapp-misc/foo\x1b[0m\n{}{}\n' + + +@pytest.mark.parametrize( + 'args,run_args', + ( + (['foo'], ['ccache', '-C', '-z']), + (['foo', '-k'], ['ccache', '-C']), + (['foo', '--keep-stats'], ['ccache', '-C']), + ), +) +@patch('subprocess.run') +def test_clear_ok(run, args, run_args): + run.return_value.returncode = 0 + + result = CliRunner().invoke(Clear(), args, color=True) + + package = 'app-misc/foo' + run.assert_called_once_with( + run_args, + env=ccache_dir_env(package), + stderr=subprocess.PIPE, + text=True, + ) + assert result.exit_code == OK + assert result.output == OUTPUT.format('', DONE) + + +@patch('subprocess.run', side_effect=FileNotFoundError) +def test_clear_no_ccache(run): + result = CliRunner().invoke(Clear(), ['foo'], color=True) + + run.accert_called_once() + assert result.exit_code == CCACHE_BINARY_NOT_FOUND + assert result.output == OUTPUT.format( + '\x1b[33mUnable to clear ccache data ' + 'due to missing ccache binary\x1b[0m\n', + ABORTED, + ) + + +@patch('subprocess.run') +def test_clear_permission_denied(run): + stderr = 'ccache: error: Permission denied\n' + + run.return_value.returncode = 1 + run.return_value.stderr = stderr + + result = CliRunner().invoke(Clear(), ['foo'], color=True) + + run.accert_called_once() + assert result.exit_code == PERMISSION_DENIED + assert result.output == OUTPUT.format( + f'\n{stderr}' + '\x1b[33mUnable to clear ccache data ' + 'due to a lack of permissions\x1b[0m\n', + ABORTED, + ) + + +@patch('subprocess.run') +def test_clear_unknown_error(run): + stderr = 'ccache: error: Something went wrong\n' + + run.return_value.returncode = 1 + run.return_value.stderr = stderr + + result = CliRunner().invoke(Clear(), ['foo'], color=True) + + run.accert_called_once() + assert result.exit_code == UNKNOWN_ERROR + assert result.output == OUTPUT.format( + f'\n{stderr}' + '\x1b[33mUnknown error, please submit a bug report!\x1b[0m\n', + ABORTED, + )