From 34b4f8265ae2c54d6d6b258eb8a53dc40ba01cba Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Fri, 19 Mar 2021 20:25:30 +0200 Subject: [PATCH] Debug unit tests created with PlatformIO Unit Testing solution // Resolve #948 --- HISTORY.rst | 3 +- docs | 2 +- platformio/builder/main.py | 2 +- platformio/builder/tools/platformio.py | 2 +- platformio/commands/debug.py | 12 +++++-- platformio/commands/test/command.py | 41 ++++++++--------------- platformio/commands/test/helpers.py | 30 +++++++++++++++++ platformio/commands/test/processor.py | 4 +-- platformio/debug/helpers.py | 46 +++++++++++++++++++++----- platformio/project/options.py | 5 +++ 10 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 platformio/commands/test/helpers.py diff --git a/HISTORY.rst b/HISTORY.rst index e626c5ca..3ea37b58 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,7 +14,8 @@ PlatformIO Core 5 * **PlatformIO Debugging** - Boosted `PlatformIO Debugging `__ performance thanks to migrating the codebase to the pure Python 3 Asynchronous I/O stack - - Debug native (desktop) application on a host machine (`issue #980 `_) + - `Debug unit tests `__ created with `PlatformIO Unit Testing `__ solution (`issue #948 `_) + - Debug native (desktop) applications on a host machine (`issue #980 `_) - Support debugging on Windows using Windows CMD/CLI (`pio debug `__) (`issue #3793 `_) - Configure a custom pattern to determine when debugging server is started with a new `debug_server_ready_pattern `__ option - Fixed an issue with silent hanging when a custom debug server is not found (`issue #3756 `_) diff --git a/docs b/docs index b76e3d53..b502c2a8 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit b76e3d53bbe10c945e1dbe302fe7f0b07005447a +Subproject commit b502c2a8dd6667e669f4c36a73007a89edf94af2 diff --git a/platformio/builder/main.py b/platformio/builder/main.py index 2fbd0b37..dcbd4800 100644 --- a/platformio/builder/main.py +++ b/platformio/builder/main.py @@ -210,7 +210,7 @@ env.AddPreAction( ), ) -AlwaysBuild(env.Alias("debug", DEFAULT_TARGETS)) +AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS)) AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) ############################################################################## diff --git a/platformio/builder/tools/platformio.py b/platformio/builder/tools/platformio.py index c412012e..544e1de8 100644 --- a/platformio/builder/tools/platformio.py +++ b/platformio/builder/tools/platformio.py @@ -50,7 +50,7 @@ def GetBuildType(env): return ( "debug" if ( - set(["debug", "sizedata"]) & set(COMMAND_LINE_TARGETS) + set(["__debug", "sizedata"]) & set(COMMAND_LINE_TARGETS) or env.GetProjectOption("build_type") == "debug" ) else "release" diff --git a/platformio/commands/debug.py b/platformio/commands/debug.py index c30fdc09..661ad7e1 100644 --- a/platformio/commands/debug.py +++ b/platformio/commands/debug.py @@ -76,7 +76,9 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro env_name = environment or helpers.get_default_debug_env(project_config) if not interface: - return helpers.predebug_project(ctx, project_dir, env_name, False, verbose) + return helpers.predebug_project( + ctx, project_dir, project_config, env_name, False, verbose + ) env_options = project_config.items(env=env_name, as_dict=True) if "platform" not in env_options: @@ -138,11 +140,15 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro ) stream = helpers.GDBMIConsoleStream() with proc.capture_std_streams(stream): - helpers.predebug_project(ctx, project_dir, env_name, preload, verbose) + helpers.predebug_project( + ctx, project_dir, project_config, env_name, preload, verbose + ) stream.close() else: click.echo("Preparing firmware for debugging...") - helpers.predebug_project(ctx, project_dir, env_name, preload, verbose) + helpers.predebug_project( + ctx, project_dir, project_config, env_name, preload, verbose + ) # save SHA sum of newly created prog if load_mode == "modified": diff --git a/platformio/commands/test/command.py b/platformio/commands/test/command.py index 07f95226..40780ab4 100644 --- a/platformio/commands/test/command.py +++ b/platformio/commands/test/command.py @@ -14,9 +14,8 @@ # pylint: disable=too-many-arguments, too-many-locals, too-many-branches -from fnmatch import fnmatch -from os import getcwd, listdir -from os.path import isdir, join +import fnmatch +import os from time import time import click @@ -24,6 +23,7 @@ from tabulate import tabulate from platformio import app, exception, fs, util from platformio.commands.test.embedded import EmbeddedTestProcessor +from platformio.commands.test.helpers import get_test_names from platformio.commands.test.native import NativeTestProcessor from platformio.platform.factory import PlatformFactory from platformio.project.config import ProjectConfig @@ -50,7 +50,7 @@ from platformio.project.config import ProjectConfig @click.option( "-d", "--project-dir", - default=getcwd, + default=os.getcwd, type=click.Path( exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True ), @@ -102,11 +102,7 @@ def cli( # pylint: disable=redefined-builtin with fs.cd(project_dir): config = ProjectConfig.get_instance(project_conf) config.validate(envs=environment) - - test_dir = config.get_optional_dir("test") - if not isdir(test_dir): - raise exception.TestDirNotExists(test_dir) - test_names = get_test_names(test_dir) + test_names = get_test_names(config) if not verbose: click.echo("Verbose mode can be enabled via `-v, --verbose` option") @@ -129,9 +125,11 @@ def cli( # pylint: disable=redefined-builtin not environment and default_envs and envname not in default_envs, testname != "*" and patterns["filter"] - and not any(fnmatch(testname, p) for p in patterns["filter"]), + and not any( + fnmatch.fnmatch(testname, p) for p in patterns["filter"] + ), testname != "*" - and any(fnmatch(testname, p) for p in patterns["ignore"]), + and any(fnmatch.fnmatch(testname, p) for p in patterns["ignore"]), ] if any(skip_conditions): results.append({"env": envname, "test": testname}) @@ -142,7 +140,10 @@ def cli( # pylint: disable=redefined-builtin cls = ( EmbeddedTestProcessor - if is_embedded_platform(config.get(section, "platform")) + if config.get(section, "platform") + and PlatformFactory.new( + config.get(section, "platform") + ).is_embedded() else NativeTestProcessor ) tp = cls( @@ -185,22 +186,6 @@ def cli( # pylint: disable=redefined-builtin raise exception.ReturnErrorCode(1) -def get_test_names(test_dir): - names = [] - for item in sorted(listdir(test_dir)): - if isdir(join(test_dir, item)): - names.append(item) - if not names: - names = ["*"] - return names - - -def is_embedded_platform(name): - if not name: - return False - return PlatformFactory.new(name).is_embedded() - - def print_processing_header(test, env): click.echo( "Processing %s in %s environment" diff --git a/platformio/commands/test/helpers.py b/platformio/commands/test/helpers.py new file mode 100644 index 00000000..ce72360f --- /dev/null +++ b/platformio/commands/test/helpers.py @@ -0,0 +1,30 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from platformio import exception + + +def get_test_names(config): + test_dir = config.get_optional_dir("test") + if not os.path.isdir(test_dir): + raise exception.TestDirNotExists(test_dir) + names = [] + for item in sorted(os.listdir(test_dir)): + if os.path.isdir(os.path.join(test_dir, item)): + names.append(item) + if not names: + names = ["*"] + return names diff --git a/platformio/commands/test/processor.py b/platformio/commands/test/processor.py index de09b5f9..ea975e62 100644 --- a/platformio/commands/test/processor.py +++ b/platformio/commands/test/processor.py @@ -139,9 +139,9 @@ class TestProcessorBase(object): cmd_run, project_dir=self.options["project_dir"], project_conf=self.options["project_config"].path, - upload_port=self.options["upload_port"], + upload_port=self.options.get("upload_port"), verbose=self.options["verbose"], - silent=self.options["silent"], + silent=self.options.get("silent"), environment=[self.env_name], disable_auto_clean="nobuild" in target, target=target, diff --git a/platformio/debug/helpers.py b/platformio/debug/helpers.py index fc6e0116..d2d2716c 100644 --- a/platformio/debug/helpers.py +++ b/platformio/debug/helpers.py @@ -23,6 +23,9 @@ from os.path import isfile from platformio import util from platformio.commands import PlatformioCLI from platformio.commands.run.command import cli as cmd_run +from platformio.commands.run.command import print_processing_header +from platformio.commands.test.helpers import get_test_names +from platformio.commands.test.processor import TestProcessorBase from platformio.compat import IS_WINDOWS, is_bytes from platformio.debug.exception import DebugInvalidOptionsError @@ -72,14 +75,41 @@ def get_default_debug_env(config): return default_envs[0] if default_envs else all_envs[0] -def predebug_project(ctx, project_dir, env_name, preload, verbose): - ctx.invoke( - cmd_run, - project_dir=project_dir, - environment=[env_name], - target=["debug"] + (["upload"] if preload else []), - verbose=verbose, - ) +def predebug_project( + ctx, project_dir, project_config, env_name, preload, verbose +): # pylint: disable=too-many-arguments + debug_testname = project_config.get("env:" + env_name, "debug_test") + if debug_testname: + test_names = get_test_names(project_config) + if debug_testname not in test_names: + raise DebugInvalidOptionsError( + "Unknown test name `%s`. Valid names are `%s`" + % (debug_testname, ", ".join(test_names)) + ) + print_processing_header(env_name, project_config, verbose) + tp = TestProcessorBase( + ctx, + debug_testname, + env_name, + dict( + project_config=project_config, + project_dir=project_dir, + without_building=False, + without_uploading=True, + without_testing=True, + verbose=False, + ), + ) + tp.build_or_upload(["__debug", "__test"] + (["upload"] if preload else [])) + else: + ctx.invoke( + cmd_run, + project_dir=project_dir, + environment=[env_name], + target=["__debug"] + (["upload"] if preload else []), + verbose=verbose, + ) + if preload: time.sleep(5) diff --git a/platformio/project/options.py b/platformio/project/options.py index 480ab4ac..3c9db96c 100644 --- a/platformio/project/options.py +++ b/platformio/project/options.py @@ -703,6 +703,11 @@ ProjectOptions = OrderedDict( "for an incoming connection" ), ), + ConfigEnvOption( + group="debug", + name="debug_test", + description=("A name of a unit test to be debugged"), + ), # Advanced ConfigEnvOption( group="advanced",