Add flake8 to our test suite and fix all errors (#7379)

* Add flake8 to our test suite and fix all errors

* Add some documentation
This commit is contained in:
skullydazed 2019-11-20 14:54:18 -08:00 committed by GitHub
parent d2115f7d1c
commit f7bdc54c69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 125 additions and 85 deletions

View File

@ -41,7 +41,7 @@ else:
os.environ['QMK_VERSION'] = 'nogit-' + strftime('%Y-%m-%d-%H:%M:%S') + '-dirty' os.environ['QMK_VERSION'] = 'nogit-' + strftime('%Y-%m-%d-%H:%M:%S') + '-dirty'
# Setup the CLI # Setup the CLI
import milc import milc # noqa
milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}'
@ -61,7 +61,7 @@ def main():
os.chdir(qmk_dir) os.chdir(qmk_dir)
# Import the subcommands # Import the subcommands
import qmk.cli import qmk.cli # noqa
# Execute # Execute
return_code = milc.cli() return_code = milc.cli()

View File

@ -173,3 +173,35 @@ You will only be able to access these arguments using `cli.args`. For example:
``` ```
cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output) cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output)
``` ```
# Testing, and Linting, and Formatting (oh my!)
We use nose2, flake8, and yapf to test, lint, and format code. You can use the `pytest` and `pyformat` subcommands to run these tests:
### Testing and Linting
qmk pytest
### Formatting
qmk pyformat
## Formatting Details
We use [yapf](https://github.com/google/yapf) to automatically format code. Our configuration is in the `[yapf]` section of `setup.cfg`.
?> Tip- Many editors can use yapf as a plugin to automatically format code as you type.
## Testing Details
Our tests can be found in `lib/python/qmk/tests/`. You will find both unit and integration tests in this directory. We hope you will write both unit and integration tests for your code, but if you do not please favor integration tests.
If your PR does not include a comprehensive set of tests please add comments like this to your code so that other people know where they can help:
# TODO(unassigned/<yourGithubUsername>): Write <unit|integration> tests
We use [nose2](https://nose2.readthedocs.io/en/latest/getting_started.html) to run our tests. You can refer to the nose2 documentation for more details on what you can do in your test functions.
## Linting Details
We use flake8 to lint our code. Your code should pass flake8 before you open a PR. This will be checked when you run `qmk pytest` and by CI when you submit a PR.

View File

@ -46,7 +46,7 @@ class KLE2xy(list):
if 'name' in properties: if 'name' in properties:
self.name = properties['name'] self.name = properties['name']
def parse_layout(self, layout): def parse_layout(self, layout): # noqa FIXME(skullydazed): flake8 says this has a complexity of 25, it should be refactored.
# Wrap this in a dictionary so hjson will parse KLE raw data # Wrap this in a dictionary so hjson will parse KLE raw data
layout = '{"layout": [' + layout + ']}' layout = '{"layout": [' + layout + ']}'
layout = hjson.loads(layout)['layout'] layout = hjson.loads(layout)['layout']

View File

@ -2,9 +2,6 @@
You can compile a keymap already in the repo or using a QMK Configurator export. You can compile a keymap already in the repo or using a QMK Configurator export.
""" """
import json
import os
import sys
import subprocess import subprocess
from argparse import FileType from argparse import FileType

View File

@ -1,8 +1,5 @@
"""Read and write configuration settings """Read and write configuration settings
""" """
import os
import subprocess
from milc import cli from milc import cli
@ -12,6 +9,54 @@ def print_config(section, key):
cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key]) cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key])
def show_config():
"""Print the current configuration to stdout.
"""
for section in cli.config:
for key in cli.config[section]:
print_config(section, key)
def parse_config_token(config_token):
"""Split a user-supplied configuration-token into its components.
"""
section = option = value = None
if '=' in config_token and '.' not in config_token:
cli.log.error('Invalid configuration token, the key must be of the form <section>.<option>: %s', config_token)
return section, option, value
# Separate the key (<section>.<option>) from the value
if '=' in config_token:
key, value = config_token.split('=')
else:
key = config_token
# Extract the section and option from the key
if '.' in key:
section, option = key.split('.', 1)
else:
section = key
return section, option, value
def set_config(section, option, value):
"""Set a config key in the running config.
"""
log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s'
if cli.args.read_only:
log_string += ' {fg_red}(change not written)'
cli.echo(log_string, section, option, cli.config[section][option], value)
if not cli.args.read_only:
if value == 'None':
del cli.config[section][option]
else:
cli.config[section][option] = value
@cli.argument('-ro', '--read-only', arg_only=True, action='store_true', help='Operate in read-only mode.') @cli.argument('-ro', '--read-only', arg_only=True, action='store_true', help='Operate in read-only mode.')
@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.') @cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.')
@cli.subcommand("Read and write configuration settings.") @cli.subcommand("Read and write configuration settings.")
@ -33,12 +78,7 @@ def config(cli):
No validation is done to ensure that the supplied section.key is actually used by qmk scripts. No validation is done to ensure that the supplied section.key is actually used by qmk scripts.
""" """
if not cli.args.configs: if not cli.args.configs:
# Walk the config tree return show_config()
for section in cli.config:
for key in cli.config[section]:
print_config(section, key)
return True
# Process config_tokens # Process config_tokens
save_config = False save_config = False
@ -46,43 +86,23 @@ def config(cli):
for argument in cli.args.configs: for argument in cli.args.configs:
# Split on space in case they quoted multiple config tokens # Split on space in case they quoted multiple config tokens
for config_token in argument.split(' '): for config_token in argument.split(' '):
# Extract the section, config_key, and value to write from the supplied config_token. section, option, value = parse_config_token(config_token)
if '=' in config_token:
key, value = config_token.split('=')
else:
key = config_token
value = None
if '.' in key:
section, config_key = key.split('.', 1)
else:
section = key
config_key = None
# Validation # Validation
if config_key and '.' in config_key: if option and '.' in option:
cli.log.error('Config keys may not have more than one period! "%s" is not valid.', key) cli.log.error('Config keys may not have more than one period! "%s" is not valid.', config_token)
return False return False
# Do what the user wants # Do what the user wants
if section and config_key and value: if section and option and value:
# Write a config key # Write a configuration option
log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s' set_config(section, option, value)
if cli.args.read_only:
log_string += ' {fg_red}(change not written)'
cli.echo(log_string, section, config_key, cli.config[section][config_key], value)
if not cli.args.read_only: if not cli.args.read_only:
if value == 'None':
del cli.config[section][config_key]
else:
cli.config[section][config_key] = value
save_config = True save_config = True
elif section and config_key: elif section and option:
# Display a single key # Display a single key
print_config(section, config_key) print_config(section, option)
elif section: elif section:
# Display an entire section # Display an entire section

View File

@ -2,11 +2,9 @@
Check up for QMK environment. Check up for QMK environment.
""" """
import os
import platform import platform
import shutil import shutil
import subprocess import subprocess
from glob import glob
from milc import cli from milc import cli

View File

@ -3,17 +3,11 @@
You can compile a keymap already in the repo or using a QMK Configurator export. You can compile a keymap already in the repo or using a QMK Configurator export.
A bootloader must be specified. A bootloader must be specified.
""" """
import os
import sys
import subprocess import subprocess
from argparse import FileType
from milc import cli
from qmk.commands import create_make_command
from qmk.commands import parse_configurator_json
from qmk.commands import compile_configurator_json
import qmk.path import qmk.path
from milc import cli
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
def print_bootloader_help(): def print_bootloader_help():

View File

@ -2,7 +2,6 @@
""" """
import json import json
import os import os
import sys
from milc import cli from milc import cli

View File

@ -1,10 +1,8 @@
"""Convert raw KLE to JSON """Convert raw KLE to JSON
""" """
import json import json
import os import os
from pathlib import Path from pathlib import Path
from argparse import FileType
from decimal import Decimal from decimal import Decimal
from collections import OrderedDict from collections import OrderedDict
@ -23,7 +21,7 @@ class CustomJSONEncoder(json.JSONEncoder):
return float(obj) return float(obj)
except TypeError: except TypeError:
pass pass
return JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)
@cli.argument('filename', help='The KLE raw txt to convert') @cli.argument('filename', help='The KLE raw txt to convert')

View File

@ -1,27 +1,27 @@
"""List the keyboards currently defined within QMK """List the keyboards currently defined within QMK
""" """
import os import os
import re
import glob import glob
from milc import cli from milc import cli
BASE_PATH = os.path.join(os.getcwd(), "keyboards") + os.path.sep
KB_WILDCARD = os.path.join(BASE_PATH, "**", "rules.mk")
def find_name(path):
"""Determine the keyboard name by stripping off the base_path and rules.mk.
"""
return path.replace(BASE_PATH, "").replace(os.path.sep + "rules.mk", "")
@cli.subcommand("List the keyboards currently defined within QMK") @cli.subcommand("List the keyboards currently defined within QMK")
def list_keyboards(cli): def list_keyboards(cli):
"""List the keyboards currently defined within QMK """List the keyboards currently defined within QMK
""" """
base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
kb_path_wildcard = os.path.join(base_path, "**", "rules.mk")
# find everywhere we have rules.mk where keymaps isn't in the path # find everywhere we have rules.mk where keymaps isn't in the path
paths = [path for path in glob.iglob(kb_path_wildcard, recursive=True) if 'keymaps' not in path] paths = [path for path in glob.iglob(KB_WILDCARD, recursive=True) if 'keymaps' not in path]
# strip the keyboard directory path prefix and rules.mk suffix and alphabetize # Extract the keyboard name from the path and print it
find_name = lambda path: path.replace(base_path, "").replace(os.path.sep + "rules.mk", "") for keyboard_name in sorted(map(find_name, paths)):
names = sorted(map(find_name, paths)) print(keyboard_name)
for name in names:
# We echo instead of cli.log.info to allow easier piping of this output
cli.echo(name)

View File

@ -2,19 +2,15 @@
QMK script to run unit and integration tests against our python code. QMK script to run unit and integration tests against our python code.
""" """
import sys import subprocess
from milc import cli from milc import cli
@cli.subcommand('QMK Python Unit Tests') @cli.subcommand('QMK Python Unit Tests')
def pytest(cli): def pytest(cli):
"""Use nose2 to run unittests """Run several linting/testing commands.
""" """
try: flake8 = subprocess.run(['flake8', 'lib/python', 'bin/qmk'])
import nose2 nose2 = subprocess.run(['nose2', '-v'])
return flake8.returncode | nose2.returncode
except ImportError:
cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2')
return False
nose2.discover(argv=['nose2', '-v'])

View File

@ -1,12 +1,8 @@
"""Functions that help you work with QMK keymaps. """Functions that help you work with QMK keymaps.
""" """
import json
import logging
import os import os
from traceback import format_exc
import qmk.path import qmk.path
from qmk.errors import NoSuchKeyboardError
# The `keymap.c` template to use when a keyboard doesn't have its own # The `keymap.c` template to use when a keyboard doesn't have its own
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H

View File

@ -2,7 +2,6 @@
""" """
import logging import logging
import os import os
from pkgutil import walk_packages
from qmk.errors import NoSuchKeyboardError from qmk.errors import NoSuchKeyboardError

View File

@ -4,3 +4,5 @@ appdirs
argcomplete argcomplete
colorama colorama
hjson hjson
nose2
flake8

View File

@ -1,4 +1,13 @@
# Python settings for QMK # Python settings for QMK
[flake8]
ignore =
# QMK is ok with long lines.
E501
per_file_ignores =
**/__init__.py:F401
# Let's slowly crank this down
max_complexity=16
[yapf] [yapf]
# Align closing bracket with visual indentation. # Align closing bracket with visual indentation.