Add initial test suite

This commit is contained in:
zvecr
2026-06-08 11:15:02 +01:00
parent 8c6339493d
commit df7bd253d4
16 changed files with 824 additions and 119 deletions
+1 -1
View File
@@ -38,4 +38,4 @@ jobs:
run: ruff check --no-cache --output-format=github
- name: Run format
run: yapf -r --diff qmk_cli
run: yapf -r --diff qmk_cli test
+14 -5
View File
@@ -17,8 +17,17 @@ jobs:
- name: Install dependencies
run: apt-get update && apt-get install -y python3-venv
- name: Run unit test
shell: bash
run: |
python3 -m venv .ci_venv
source .ci_venv/bin/activate
python3 -m pip install -r requirements.txt
python3 -m pip install --group dev
pytest
- name: Run ci_tests
run: ./ci_tests -a
run: ./ci_tests
test_cli_linux_macos:
needs: test_cli_base_container
@@ -39,7 +48,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Run ci_tests
run: ./ci_tests -a
run: ./ci_tests
test_cli_win:
needs: test_cli_base_container
@@ -53,7 +62,7 @@ jobs:
uses: msys2/setup-msys2@v2
with:
update: true
install: git mingw-w64-x86_64-toolchain mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-build mingw-w64-x86_64-python-pillow mingw-w64-x86_64-rust mingw-w64-x86_64-python-halo mingw-w64-x86_64-python-nh3 mingw-w64-x86_64-python-rpds-py
install: git mingw-w64-x86_64-toolchain mingw-w64-x86_64-python-pip mingw-w64-x86_64-python-build mingw-w64-x86_64-python-pillow mingw-w64-x86_64-rust mingw-w64-x86_64-python-halo mingw-w64-x86_64-python-nh3 mingw-w64-x86_64-python-rpds-py mingw-w64-x86_64-python-ruff
# Upgrade pip due to msys packaging + pypa/build/pull/736 issues
- name: (MSYS2) Install Python dependencies
@@ -63,7 +72,7 @@ jobs:
- name: Run ci_tests
shell: msys2 {0}
run: ./ci_tests -a
run: ./ci_tests
test_docker_container:
needs: test_cli_base_container
@@ -106,7 +115,7 @@ jobs:
- name: Test
run: |
docker run --rm -v ${{ github.workspace }}:/qmk_cli -w /qmk_cli ${{ env.TEST_TAG }} /qmk_cli/ci_tests -a
docker run --rm -v ${{ github.workspace }}:/qmk_cli -w /qmk_cli ${{ env.TEST_TAG }} /qmk_cli/ci_tests
- name: Build All Containers
uses: docker/build-push-action@v7
-3
View File
@@ -30,9 +30,6 @@ jobs:
- uses: actions/checkout@v7
- name: Run ci_tests
run: ./ci_tests
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
+6 -44
View File
@@ -3,61 +3,23 @@
set -e
#set -x
ADVANCED=false
TMPDIR=$(mktemp -d)
QMK_HOME="$TMPDIR/qmk_firmware"
export QMK_HOME
for arg in $@; do
if [ "$arg" = "-a" ]; then
ADVANCED=true
fi
done
trap "rm -rf $TMPDIR" EXIT
if [ $ADVANCED = true ]; then
echo "*** Running in advanced mode with tmp files in $TMPDIR"
else
echo "*** Running in basic mode with tmp files in $TMPDIR"
fi
# Setup our virtualenv
if [ -e .ci_venv ]; then
rm -rf .ci_venv
fi
CI_VENV="$TMPDIR/.ci_venv"
# Avoid issues with building pillow as we install mingw-w64-x86_64-python-pillow
if [[ "$(uname -s)" =~ ^MINGW64_NT.* ]]; then
CI_VENV_ARGS="--system-site-packages"
fi
python3 -m venv $CI_VENV_ARGS .ci_venv
source .ci_venv/bin/activate
python3 -m venv $CI_VENV_ARGS $CI_VENV
source $CI_VENV/bin/activate
# Install dependencies
python3 -m pip install -U pip wheel
python3 -m pip install .
python3 -m pip install --group dev
# Ensure that qmk works
echo "*** Testing 'qmk clone -h'"
qmk clone -h
#echo "*** Testing 'qmk config -a'" # Test disabled as `milc` at least 1.6.8+ returns False and thus non-zero exit code
#qmk config -a
echo "*** Testing 'qmk setup -n'"
qmk setup -n
echo
echo "*** Basic tests completed successfully!"
# Run advanced test if requested
if [ $ADVANCED = true ]; then
echo
echo "*** Testing 'qmk setup -y'"
qmk setup -y
echo
echo "*** Advanced tests completed successfully!"
fi
# Cleanup
deactivate
rm -rf .ci_venv $TMPDIR
pytest -vv -m system --qmk=real
+12 -1
View File
@@ -40,6 +40,7 @@ dependencies = [
dev = [
"build",
"bumpversion",
"pytest",
"ruff",
"twine",
"yapf",
@@ -63,7 +64,10 @@ find = {namespaces = false}
[tool.ruff]
target-version = "py39"
include = ['qmk_cli/**/*.py']
include = [
"qmk_cli/**/*.py",
"test/**/*.py",
]
[tool.ruff.lint]
select = ["E", "F", "B", "N", "W", "COM", "C901", "PGH004"]
@@ -133,3 +137,10 @@ split_penalty_for_added_line_split = 30
split_penalty_import_names = 0
split_penalty_logical_operator = 300
use_tabs = false
[tool.pytest.ini_options]
markers = [
"integration",
"system",
]
addopts = "-m 'not (integration or system)'"
+58 -14
View File
@@ -1,24 +1,24 @@
"""Helpers for working with git.
"""
"""Helpers for working with git."""
import subprocess
from .helpers import rmtree
from milc import cli
default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo
default_branch = 'master'
DEFAULT_UPSTREAM = 'https://github.com/qmk/qmk_firmware'
def git_clone(url, destination, branch):
"""Add the qmk/qmk_firmware upstream to a qmk_firmware clone."""
git_clone = [
'git',
'clone',
'--recurse-submodules',
'--branch=' + branch,
f'--branch={branch}',
url,
str(destination),
]
cli.log.debug('Git clone command: %s', git_clone)
cli.log.debug(f'Git clone command: {git_clone}')
try:
with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
@@ -28,13 +28,57 @@ def git_clone(url, destination, branch):
except Exception as e:
git_cmd = ' '.join([s.replace(' ', r'\ ') for s in git_clone])
cli.log.error("Could not run '%s': %s: %s", git_cmd, e.__class__.__name__, e)
cli.log.error(f"Could not run '{git_cmd}': {e.__class__.__name__}:{e}")
return False
if p.returncode == 0:
cli.log.info('Successfully cloned %s to %s!', url, destination)
return True
else:
cli.log.error('git clone exited %d', p.returncode)
if p.returncode != 0:
cli.log.error(f'git clone exited {p.returncode}')
return False
cli.log.info(f'Successfully cloned {url} to {destination}!')
return True
def git_set_upstream(destination):
"""Add the qmk/qmk_firmware upstream to a qmk_firmware clone."""
git_remote = [
'git',
'-C',
destination,
'remote',
'add',
'upstream',
DEFAULT_UPSTREAM,
]
cli.log.debug(f'Git remote command: {git_remote}')
try:
with subprocess.Popen(git_remote, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
for line in p.stdout:
print(line, end='')
except Exception as e:
git_cmd = ' '.join([s.replace(' ', r'\ ') for s in git_remote])
cli.log.error(f"Could not run '{git_cmd}': {e.__class__.__name__}:{e}")
return False
if p.returncode != 0:
cli.log.error(f'git remote add exited {p.returncode}')
return False
cli.log.info(f'Added {DEFAULT_UPSTREAM} as remote upstream.')
return True
def git_clone_fork(destination, baseurl, fork, branch, force=False):
"""Clone fork and configure upstream."""
url = '/'.join((baseurl, fork))
if force:
rmtree(destination)
if not git_clone(url, destination, branch):
return False
return git_set_upstream(destination)
+1 -1
View File
@@ -23,6 +23,6 @@ def clone(cli):
# Exists (but not an empty dir)
if cli.args.destination.exists() and any(cli.args.destination.iterdir()):
cli.log.error('Destination already exists: %s', cli.args.destination)
exit(1)
return False
return git_clone(git_url, cli.args.destination, cli.args.branch)
+5
View File
@@ -1,6 +1,7 @@
"""Prints environment information.
"""
import os
import sys
from pathlib import Path
from milc import cli
@@ -26,6 +27,10 @@ def env(cli):
data[converted_key] = val
if cli.args.var:
if cli.args.var not in data:
print(f'Variable "{cli.args.var}" does not exist!', file=sys.stderr)
return False
# dump out requested arg
print(data[cli.args.var])
else:
+12 -50
View File
@@ -2,64 +2,26 @@
"""
import os
import shlex
import subprocess
import sys
from pathlib import Path
from milc import cli
from milc.questions import choice, question, yesno
from qmk_cli.git import git_clone
from qmk_cli.helpers import AbsPath, is_qmk_firmware, rmtree
from qmk_cli.git import git_clone_fork
from qmk_cli.helpers import AbsPath, is_qmk_firmware
default_base = 'https://github.com'
default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo
default_branch = 'master'
def git_upstream(destination):
"""Add the qmk/qmk_firmware upstream to a qmk_firmware clone.
"""
git_url = '/'.join((cli.args.baseurl, default_fork))
git_cmd = [
'git',
'-C',
destination,
'remote',
'add',
'upstream',
git_url,
]
with subprocess.Popen(git_cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
for line in p.stdout:
print(line, end='')
if p.returncode == 0:
cli.log.info('Added %s as remote upstream.', git_url)
return True
else:
cli.log.error('%s exited %d', ' '.join(git_cmd), p.returncode)
return False
def git_clone_fork(fork, branch, force=False):
if force:
rmtree(cli.args.home)
git_url = '/'.join((cli.args.baseurl, fork))
if git_clone(git_url, cli.args.home, branch):
git_upstream(cli.args.home)
else:
exit(1)
DEFAULT_BASE = 'https://github.com'
DEFAULT_REPO = 'qmk_firmware'
DEFAULT_FORK = 'qmk/' + DEFAULT_REPO
DEFAULT_BRANCH = 'master'
@cli.argument('-n', '--no', arg_only=True, action='store_true', help='Answer no to all questions')
@cli.argument('-y', '--yes', arg_only=True, action='store_true', help='Answer yes to all questions')
@cli.argument('--baseurl', arg_only=True, default=default_base, help='The URL all git operations start from. Default: %s' % default_base)
@cli.argument('-b', '--branch', arg_only=True, default=default_branch, help='The branch to clone. Default: %s' % default_branch)
@cli.argument('--baseurl', arg_only=True, default=DEFAULT_BASE, help='The URL all git operations start from. Default: %s' % DEFAULT_BASE)
@cli.argument('-b', '--branch', arg_only=True, default=DEFAULT_BRANCH, help='The branch to clone. Default: %s' % DEFAULT_BRANCH)
@cli.argument('-H', '--home', arg_only=True, default=Path(os.environ['QMK_HOME']), type=AbsPath, help='The location for QMK Firmware. Default: %s' % os.environ['QMK_HOME'])
@cli.argument('fork', arg_only=True, default=default_fork, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % default_fork)
@cli.argument('fork', arg_only=True, default=DEFAULT_FORK, nargs='?', help='The qmk_firmware fork to clone. Default: %s' % DEFAULT_FORK)
@cli.subcommand('Setup your computer for qmk_firmware.')
def setup(cli):
"""Guide the user through setting up their QMK environment.
@@ -90,7 +52,7 @@ def setup(cli):
if not yesno(delete_confirm, default=False):
exit(1)
git_clone_fork(cli.args.fork, cli.args.branch, force=True)
git_clone_fork(cli.args.home, cli.args.baseurl, cli.args.fork, cli.args.branch, force=True)
elif found_action == "Delete and clone a different fork":
fork_name = question("Enter the name of the fork:", default=cli.args.fork)
@@ -99,7 +61,7 @@ def setup(cli):
if not yesno(delete_confirm, default=False):
exit(1)
git_clone_fork(fork_name, branch_name, force=True)
git_clone_fork(cli.args.home, cli.args.baseurl, fork_name, branch_name, force=True)
# Exists (but not an empty dir)
elif cli.args.home.exists() and any(cli.args.home.iterdir()):
@@ -114,7 +76,7 @@ def setup(cli):
else:
cli.log.error('Could not find qmk_firmware!')
if yesno(clone_prompt):
git_clone_fork(cli.args.fork, cli.args.branch)
git_clone_fork(cli.args.home, cli.args.baseurl, cli.args.fork, cli.args.branch)
else:
cli.log.warning('Not cloning qmk_firmware due to user input or --no flag.')
+39
View File
@@ -0,0 +1,39 @@
import tempfile
import pytest
from pathlib import Path
from unittest.mock import create_autospec, PropertyMock, MagicMock
from milc import MILCInterface
from milc.configuration import Configuration
def pytest_addoption(parser):
parser.addoption(
'--qmk',
action='store',
default='wrapper',
help='options: ./qmk wrapper or real qmk from path',
choices=('wrapper', 'real'),
)
@pytest.fixture
def temp_directory():
"""Similar behavior to the default tmpdir fixture except the folder is cleaned up."""
with tempfile.TemporaryDirectory() as tmp_path:
yield Path(tmp_path)
@pytest.fixture
def mock_cli():
"""Mock cli to pass to cli subcommands,"""
ret = create_autospec(MILCInterface)
# default behavior
type(ret).config = Configuration()
ret.config.general.unicode = True
ret.config.general.color = True
type(ret).write_config_option = MagicMock()
type(ret.args).var = PropertyMock(return_value=None)
return ret
+40
View File
@@ -0,0 +1,40 @@
import pytest
from unittest.mock import patch, PropertyMock
@pytest.fixture
def subcommand(temp_directory):
tmp = temp_directory.as_posix()
with patch.dict('os.environ', {'ORIG_CWD': tmp, 'QMK_HOME': tmp}, clear=True):
import qmk_cli.subcommands.clone as clone
yield clone
@pytest.fixture
def git_clone(subcommand):
with patch.object(subcommand, 'git_clone') as git_clone:
yield git_clone
def test_clone(subcommand, mock_cli, git_clone):
type(mock_cli.args).baseurl = PropertyMock(return_value='asdf')
type(mock_cli.args).fork = PropertyMock(return_value='fork')
git_clone.return_value = True
ret = subcommand.clone(mock_cli)
git_clone.assert_called_once()
assert ret is True
def test_clone_not_empty(subcommand, mock_cli, temp_directory, git_clone):
type(mock_cli.args).baseurl = PropertyMock(return_value='asdf')
type(mock_cli.args).fork = PropertyMock(return_value='fork')
type(mock_cli.args).destination = PropertyMock(return_value=temp_directory)
(temp_directory / 'asdf').touch()
ret = subcommand.clone(mock_cli)
git_clone.assert_not_called()
assert ret is False
+51
View File
@@ -0,0 +1,51 @@
import pytest
from unittest.mock import patch, PropertyMock
@pytest.fixture
def subcommand(temp_directory):
tmp = temp_directory.as_posix()
with patch.dict('os.environ', {'ORIG_CWD': tmp, 'QMK_HOME': tmp}, clear=True):
import qmk_cli.subcommands.env as env
yield env
@pytest.fixture
def is_qmk_firmware(subcommand):
with patch.object(subcommand, 'is_qmk_firmware') as is_qmk_firmware:
is_qmk_firmware.return_value = True
yield is_qmk_firmware
def test_request_qmk_firmware_exists(subcommand, mock_cli, capsys, temp_directory, is_qmk_firmware):
subcommand.env(mock_cli)
captured = capsys.readouterr().out
assert f'QMK_HOME="{temp_directory}"' in captured
assert f'QMK_FIRMWARE="{temp_directory}"' in captured
def test_request_all_variables(subcommand, mock_cli, capsys, temp_directory):
subcommand.env(mock_cli)
captured = capsys.readouterr().out
assert f'QMK_HOME="{temp_directory}"' in captured
assert 'QMK_FIRMWARE=""' in captured
assert 'QMK_UNICODE="True"' in captured
def test_request_single_variable(subcommand, mock_cli, capsys, temp_directory):
type(mock_cli.args).var = PropertyMock(return_value='QMK_HOME')
subcommand.env(mock_cli)
captured = capsys.readouterr().out
assert captured == f'{temp_directory}\n'
def test_request_single_variable_no_exits(subcommand, mock_cli, capsys):
type(mock_cli.args).var = PropertyMock(return_value='ASDF')
ret = subcommand.env(mock_cli)
captured = capsys.readouterr().err
assert captured == 'Variable "ASDF" does not exist!\n'
assert ret is False
+176
View File
@@ -0,0 +1,176 @@
import pytest
from unittest.mock import patch, PropertyMock, ANY
@pytest.fixture
def subcommand(temp_directory):
tmp = temp_directory.as_posix()
with patch.dict('os.environ', {'ORIG_CWD': tmp, 'QMK_HOME': tmp}, clear=True):
import qmk_cli.subcommands.setup as setup
yield setup
@pytest.fixture
def is_qmk_firmware(subcommand):
with patch.object(subcommand, 'is_qmk_firmware') as is_qmk_firmware:
is_qmk_firmware.return_value = True
yield is_qmk_firmware
@pytest.fixture
def yesno(subcommand):
with patch.object(subcommand, 'yesno') as yesno:
yesno.return_value = True
yield yesno
@pytest.fixture
def choice(subcommand):
with patch.object(subcommand, 'choice') as choice:
yield choice
@pytest.fixture
def question(subcommand):
with patch.object(subcommand, 'question') as question:
yield question
@pytest.fixture
def git_clone_fork(subcommand):
with patch.object(subcommand, 'git_clone_fork') as git_clone_fork:
yield git_clone_fork
def test_setup_both_yes_no_invalid(subcommand, mock_cli):
type(mock_cli.args).yes = PropertyMock(return_value=True)
type(mock_cli.args).no = PropertyMock(return_value=True)
with pytest.raises(SystemExit) as e:
subcommand.setup(mock_cli)
assert e.value.code == 1
def test_setup_reclone(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, choice, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
type(mock_cli.args).fork = 'asdf'
choice.return_value = 'Delete and reclone asdf'
yesno.return_value = True
subcommand.setup(mock_cli)
yesno.assert_called_once()
assert 'This will delete your current qmk_firmware directory.' in yesno.call_args.args[0]
git_clone_fork.assert_called_once()
def test_setup_reclone_no(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, choice, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
type(mock_cli.args).fork = 'asdf'
choice.return_value = 'Delete and reclone asdf'
yesno.return_value = False
with pytest.raises(SystemExit) as e:
subcommand.setup(mock_cli)
assert e.value.code == 1
git_clone_fork.assert_not_called()
def test_setup_clone_diff_fork(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, choice, question, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
choice.return_value = 'Delete and clone a different fork'
yesno.return_value = True
subcommand.setup(mock_cli)
yesno.assert_called_once()
assert 'This will delete your current qmk_firmware directory.' in yesno.call_args.args[0]
git_clone_fork.assert_called_once()
def test_setup_clone_diff_fork_no(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, choice, question, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
choice.return_value = 'Delete and clone a different fork'
yesno.return_value = False
with pytest.raises(SystemExit) as e:
subcommand.setup(mock_cli)
assert e.value.code == 1
git_clone_fork.assert_not_called()
def test_setup_home_exists_not_empty(subcommand, mock_cli, is_qmk_firmware, temp_directory):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
is_qmk_firmware.return_value = False
(temp_directory / 'asdf').touch()
with pytest.raises(SystemExit) as e:
subcommand.setup(mock_cli)
assert e.value.code == 1
def test_setup_missing(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
is_qmk_firmware.return_value = False
yesno.return_value = True
subcommand.setup(mock_cli)
yesno.assert_called_once()
assert 'Would you like to clone' in yesno.call_args.args[0]
git_clone_fork.assert_called_once()
def test_setup_missing_no(subcommand, mock_cli, is_qmk_firmware, temp_directory, yesno, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=False)
type(mock_cli.args).home = PropertyMock(return_value=temp_directory)
is_qmk_firmware.return_value = False
yesno.return_value = False
subcommand.setup(mock_cli)
yesno.assert_called_once()
assert 'Would you like to clone' in yesno.call_args.args[0]
git_clone_fork.assert_not_called()
def test_setup_chains_args_to_doctor(subcommand, mock_cli, is_qmk_firmware, yesno, choice, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=True)
type(mock_cli.args).no = PropertyMock(return_value=False)
subcommand.setup(mock_cli)
mock_cli.run.assert_called_once()
assert mock_cli.run.call_args.args[0] == [ANY, '--color', '--unicode', 'doctor', '-y']
def test_setup_chains_args_to_doctor_no(subcommand, mock_cli, is_qmk_firmware, yesno, choice, git_clone_fork):
type(mock_cli.args).yes = PropertyMock(return_value=False)
type(mock_cli.args).no = PropertyMock(return_value=True)
mock_cli.config.general.color = False
mock_cli.config.general.unicode = False
subcommand.setup(mock_cli)
mock_cli.run.assert_called_once()
assert mock_cli.run.call_args.args[0] == [ANY, '--no-color', '--no-unicode', 'doctor', '-n']
+46
View File
@@ -0,0 +1,46 @@
import pytest
import subprocess
@pytest.fixture(autouse=True)
def temp_env(temp_directory, monkeypatch):
qmk_firmware = temp_directory / 'qmk_firmware'
monkeypatch.setenv("QMK_HOME", qmk_firmware.as_posix())
yield qmk_firmware
@pytest.fixture
def run_cli(request):
def run_cli_wrapper(args):
cmd = f'{request.config.rootpath}/qmk' if request.config.getoption("--qmk") == 'wrapper' else 'qmk'
return subprocess.run([cmd, *args], capture_output=True, text=True)
return run_cli_wrapper
@pytest.mark.system
def test_cli_help(run_cli):
completed_process = run_cli(['clone', '--help'])
assert 'usage:' in completed_process.stdout
assert completed_process.returncode == 0
@pytest.mark.system
def test_cli_setup_no(run_cli):
completed_process = run_cli(['setup', '-n'])
assert 'Could not find qmk_firmware!' in completed_process.stderr
assert 'Not cloning qmk_firmware due to user input or --no flag.' in completed_process.stderr
assert completed_process.returncode == 0
@pytest.mark.system
def test_cli_setup_yes(run_cli):
completed_process = run_cli(['setup', '-y'])
assert 'Successfully cloned https://github.com/qmk/qmk_firmware' in completed_process.stderr
assert 'Added https://github.com/qmk/qmk_firmware as remote upstream' in completed_process.stderr
assert 'QMK Doctor is checking your environment' in completed_process.stderr
assert completed_process.returncode == 0
+117
View File
@@ -0,0 +1,117 @@
import pytest
import subprocess
from unittest.mock import patch, PropertyMock
import qmk_cli.git
@patch("qmk_cli.git.subprocess.Popen", side_effect=Exception("foobar"))
@patch("qmk_cli.git.cli.log.error")
def test_git_clone_except(mock_log_error, mock_popen):
ret = qmk_cli.git.git_clone('https://github.com/qmk/qmk_firmware', '/tmp', 'master')
assert "Could not run" in str(mock_log_error.call_args_list)
assert "Exception:foobar" in str(mock_log_error.call_args_list)
assert ret is False
@patch("qmk_cli.git.subprocess.Popen")
def test_git_clone_logs_output(mock_popen, capsys):
type(mock_popen().__enter__()).returncode = PropertyMock(return_value=0)
type(mock_popen().__enter__()).stdout = PropertyMock(return_value=['asdf'])
ret = qmk_cli.git.git_clone('https://github.com/qmk/qmk_firmware', '/tmp', 'master')
captured = capsys.readouterr().out
assert 'asdf' in captured
assert ret is True
@patch("qmk_cli.git.subprocess.Popen")
def test_git_clone_captures_exit_code(mock_popen):
type(mock_popen().__enter__()).returncode = PropertyMock(return_value=1)
ret = qmk_cli.git.git_clone('https://github.com/qmk/qmk_firmware', '/tmp', 'master')
assert ret is False
@patch("qmk_cli.git.subprocess.Popen", side_effect=Exception("foobar"))
@patch("qmk_cli.git.cli.log.error")
def test_git_set_upstream_except(mock_log_error, mock_popen):
ret = qmk_cli.git.git_set_upstream('/tmp')
assert "Could not run" in str(mock_log_error.call_args_list)
assert "Exception:foobar" in str(mock_log_error.call_args_list)
assert ret is False
@patch("qmk_cli.git.subprocess.Popen")
def test_git_set_upstream_logs_output(mock_popen, capsys):
type(mock_popen().__enter__()).returncode = PropertyMock(return_value=0)
type(mock_popen().__enter__()).stdout = PropertyMock(return_value=['asdf'])
ret = qmk_cli.git.git_set_upstream('/tmp')
captured = capsys.readouterr().out
assert 'asdf' in captured
assert ret is True
@patch("qmk_cli.git.subprocess.Popen")
def test_git_set_upstream_captures_exit_code(mock_popen):
type(mock_popen().__enter__()).returncode = PropertyMock(return_value=1)
ret = qmk_cli.git.git_set_upstream('/tmp')
assert ret is False
@patch.object(qmk_cli.git, 'git_clone')
@patch.object(qmk_cli.git, 'git_set_upstream')
def test_git_clone_fork_fail(git_set_upstream, git_clone, temp_directory):
git_clone.return_value = False
git_set_upstream.return_value = False
ret = qmk_cli.git.git_clone_fork(temp_directory.as_posix(), 'https://github.com', 'qmk/qmk_firmware', 'master', False)
assert ret is False
@patch.object(qmk_cli.git, 'git_clone')
@patch.object(qmk_cli.git, 'git_set_upstream')
def test_git_clone_fork_force(git_set_upstream, git_clone, temp_directory):
(temp_directory / 'asdf').touch()
git_clone.return_value = True
git_set_upstream.return_value = True
ret = qmk_cli.git.git_clone_fork(temp_directory.as_posix(), 'https://github.com', 'qmk/qmk_firmware', 'master', True)
assert ret is True
assert (temp_directory / 'asdf').exists() is False
@pytest.mark.integration
def test_git_clone(temp_directory):
qmk_firmware = temp_directory / 'qmk_firmware'
ret = qmk_cli.git.git_clone('https://github.com/qmk/qmk_firmware', qmk_firmware.as_posix(), 'master')
from qmk_cli.helpers import is_qmk_firmware
assert is_qmk_firmware(qmk_firmware) is True
assert ret is True
@pytest.mark.integration
def test_git_clone_fork(temp_directory):
qmk_firmware = temp_directory / 'qmk_firmware'
qmk_firmware.mkdir()
(qmk_firmware / 'asdf').touch()
ret = qmk_cli.git.git_clone_fork(qmk_firmware.as_posix(), 'https://github.com', 'qmk/qmk_firmware', 'master', True)
from qmk_cli.helpers import is_qmk_firmware
assert is_qmk_firmware(qmk_firmware) is True
assert 'https://github.com/qmk/qmk_firmware' in subprocess.run(['git', '-C', qmk_firmware.as_posix(), 'remote', 'get-url', 'upstream'], capture_output=True, text=True).stdout
assert ret is True
+246
View File
@@ -0,0 +1,246 @@
import os
import stat
import pytest
from pathlib import Path
from unittest.mock import patch, PropertyMock
import qmk_cli.helpers
@pytest.fixture(autouse=True)
def reset_env():
cur_dir = os.getcwd()
yield cur_dir
os.chdir(cur_dir)
qmk_cli.helpers.find_qmk_firmware.cache_clear()
qmk_cli.helpers.in_qmk_firmware.cache_clear()
qmk_cli.helpers.find_qmk_userspace.cache_clear()
qmk_cli.helpers.in_qmk_userspace.cache_clear()
def test_abspath(temp_directory):
tmp = temp_directory.absolute()
ret = qmk_cli.helpers.AbsPath(tmp)
assert ret.samefile(tmp)
def test_abspath_str(temp_directory):
tmp = temp_directory.absolute().as_posix()
ret = qmk_cli.helpers.AbsPath(tmp)
assert ret.samefile(tmp)
def test_abspath_rel(temp_directory):
with patch.dict('os.environ', {'ORIG_CWD': temp_directory.as_posix()}, clear=True):
tmp = './asdf'
ret = qmk_cli.helpers.AbsPath(tmp)
assert ret.as_posix() == (temp_directory / 'asdf').as_posix()
def test_rmtree(temp_directory):
tmp = temp_directory / 'asdf.txt'
tmp.touch()
qmk_cli.helpers.rmtree(temp_directory)
assert tmp.exists() is False
@pytest.mark.skipif(os.name != "nt", reason="Only triggers issue on windows")
def test_rmtree_readonly(temp_directory):
tmp = temp_directory / 'asdf.txt'
tmp.touch()
os.chmod(tmp, stat.S_IREAD)
qmk_cli.helpers.rmtree(temp_directory)
assert tmp.exists() is False
def test_is_qmk_firmware(temp_directory):
(temp_directory / 'quantum').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'requirements.txt').touch()
(temp_directory / 'requirements-dev.txt').touch()
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
assert qmk_cli.helpers.is_qmk_firmware(temp_directory) is True
def test_is_qmk_firmware_partially_missing(temp_directory):
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
assert qmk_cli.helpers.is_qmk_firmware(temp_directory) is False
def test_find_qmk_firmware(temp_directory):
(temp_directory / 'quantum').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'requirements.txt').touch()
(temp_directory / 'requirements-dev.txt').touch()
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
os.chdir(temp_directory)
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_find_qmk_firmware_env(temp_directory):
with patch.dict('os.environ', {'QMK_HOME': temp_directory.as_posix()}, clear=True):
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_find_qmk_firmware_env_missing(temp_directory):
with patch.dict('os.environ', {'QMK_HOME': (temp_directory / 'asdf').as_posix()}, clear=True):
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == (temp_directory / 'asdf').as_posix()
def test_find_qmk_firmware_config(temp_directory):
with patch.object(qmk_cli.helpers, 'cli') as cli:
type(cli.config.user).qmk_home = PropertyMock(return_value=temp_directory.as_posix())
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_find_qmk_firmware_config_user():
with patch.object(qmk_cli.helpers, 'cli') as cli:
type(cli.config.user).qmk_home = PropertyMock(return_value='~/asdf')
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == (Path.home() / 'asdf').as_posix()
def test_find_qmk_firmware_default(temp_directory):
with patch('pathlib.Path.home') as home:
home.return_value = temp_directory
assert qmk_cli.helpers.find_qmk_firmware().as_posix() == (temp_directory / 'qmk_firmware').as_posix()
def test_in_qmk_firmware(temp_directory):
(temp_directory / 'quantum').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'requirements.txt').touch()
(temp_directory / 'requirements-dev.txt').touch()
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
os.chdir(temp_directory)
assert qmk_cli.helpers.in_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_in_qmk_firmware_sub(temp_directory):
(temp_directory / 'quantum').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'requirements.txt').touch()
(temp_directory / 'requirements-dev.txt').touch()
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
os.chdir(temp_directory / 'quantum')
assert qmk_cli.helpers.in_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_in_qmk_firmware_sub_sub(temp_directory):
(temp_directory / 'quantum').mkdir(parents=True)
(temp_directory / 'lib/python/qmk/cli').mkdir(parents=True)
(temp_directory / 'requirements.txt').touch()
(temp_directory / 'requirements-dev.txt').touch()
(temp_directory / 'lib/python/qmk/cli/__init__.py').touch()
os.chdir(temp_directory / 'lib' / 'python')
assert qmk_cli.helpers.in_qmk_firmware().as_posix() == temp_directory.as_posix()
def test_in_qmk_firmware_invalid(temp_directory):
os.chdir(temp_directory)
assert qmk_cli.helpers.in_qmk_firmware() is None
def test_is_qmk_userspace(temp_directory):
(temp_directory / 'qmk.json').write_text('{"userspace_version": 2}')
assert qmk_cli.helpers.is_qmk_userspace(temp_directory) is True
def test_is_qmk_userspace_missing(temp_directory):
assert qmk_cli.helpers.is_qmk_userspace(temp_directory) is False
def test_is_qmk_userspace_invalid(temp_directory):
(temp_directory / 'qmk.json').write_text('asdf')
assert qmk_cli.helpers.is_qmk_userspace(temp_directory) is False
def test_is_qmk_userspace_empty(temp_directory):
(temp_directory / 'qmk.json').touch()
assert qmk_cli.helpers.is_qmk_userspace(temp_directory) is False
def test_find_qmk_userspace(temp_directory):
(temp_directory / 'qmk.json').write_text('{"userspace_version": 2}')
os.chdir(temp_directory)
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_find_qmk_userspace_env(temp_directory):
with patch.dict('os.environ', {'QMK_USERSPACE': temp_directory.as_posix()}, clear=True):
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_find_qmk_userspace_env_missing(temp_directory):
with patch.dict('os.environ', {'QMK_USERSPACE': (temp_directory / 'asdf').as_posix()}, clear=True):
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == (temp_directory / 'asdf').as_posix()
def test_find_qmk_userspace_config(temp_directory):
with patch.object(qmk_cli.helpers, 'cli') as cli:
type(cli.config.user).overlay_dir = PropertyMock(return_value=temp_directory.as_posix())
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_find_qmk_userspace_config_user():
with patch.object(qmk_cli.helpers, 'cli') as cli:
type(cli.config.user).overlay_dir = PropertyMock(return_value='~/asdf')
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == (Path.home() / 'asdf').as_posix()
def test_find_qmk_userspace_default(temp_directory):
with patch('pathlib.Path.home') as home:
home.return_value = temp_directory
assert qmk_cli.helpers.find_qmk_userspace().as_posix() == (temp_directory / 'qmk_userspace').as_posix()
def test_in_qmk_userspace(temp_directory):
(temp_directory / 'qmk.json').write_text('{"userspace_version": 2}')
os.chdir(temp_directory)
assert qmk_cli.helpers.in_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_in_qmk_userspace_sub(temp_directory):
(temp_directory / 'qmk.json').write_text('{"userspace_version": 2}')
(temp_directory / 'keymaps' / 'test').mkdir(parents=True)
os.chdir(temp_directory / 'keymaps')
assert qmk_cli.helpers.in_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_in_qmk_userspace_sub_sub(temp_directory):
(temp_directory / 'qmk.json').write_text('{"userspace_version": 2}')
(temp_directory / 'keymaps' / 'test').mkdir(parents=True)
os.chdir(temp_directory / 'keymaps' / 'test')
assert qmk_cli.helpers.in_qmk_userspace().as_posix() == temp_directory.as_posix()
def test_in_qmk_userspace_invalid(temp_directory):
os.chdir(temp_directory)
assert qmk_cli.helpers.in_qmk_userspace() is None