Debug unit tests created with PlatformIO Unit Testing solution // Resolve #948

This commit is contained in:
Ivan Kravets
2021-03-19 20:25:30 +02:00
parent a366d1af2a
commit 34b4f8265a
10 changed files with 102 additions and 45 deletions

View File

@ -14,7 +14,8 @@ PlatformIO Core 5
* **PlatformIO Debugging** * **PlatformIO Debugging**
- Boosted `PlatformIO Debugging <https://docs.platformio.org/page/plus/debugging.html>`__ performance thanks to migrating the codebase to the pure Python 3 Asynchronous I/O stack - Boosted `PlatformIO Debugging <https://docs.platformio.org/page/plus/debugging.html>`__ 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 <https://github.com/platformio/platformio-core/issues/980>`_) - `Debug unit tests <https://docs.platformio.org/page/plus/debugging.html#debug-unit-tests>`__ created with `PlatformIO Unit Testing <https://docs.platformio.org/page/plus/unit-testing.html>`__ solution (`issue #948 <https://github.com/platformio/platformio-core/issues/948>`_)
- Debug native (desktop) applications on a host machine (`issue #980 <https://github.com/platformio/platformio-core/issues/980>`_)
- Support debugging on Windows using Windows CMD/CLI (`pio debug <https://docs.platformio.org/page/core/userguide/cmd_debug.html>`__) (`issue #3793 <https://github.com/platformio/platformio-core/issues/3793>`_) - Support debugging on Windows using Windows CMD/CLI (`pio debug <https://docs.platformio.org/page/core/userguide/cmd_debug.html>`__) (`issue #3793 <https://github.com/platformio/platformio-core/issues/3793>`_)
- Configure a custom pattern to determine when debugging server is started with a new `debug_server_ready_pattern <https://docs.platformio.org/page/projectconf/section_env_debug.html#debug-server-ready-pattern>`__ option - Configure a custom pattern to determine when debugging server is started with a new `debug_server_ready_pattern <https://docs.platformio.org/page/projectconf/section_env_debug.html#debug-server-ready-pattern>`__ option
- Fixed an issue with silent hanging when a custom debug server is not found (`issue #3756 <https://github.com/platformio/platformio-core/issues/3756>`_) - Fixed an issue with silent hanging when a custom debug server is not found (`issue #3756 <https://github.com/platformio/platformio-core/issues/3756>`_)

2
docs

Submodule docs updated: b76e3d53bb...b502c2a8dd

View File

@ -210,7 +210,7 @@ env.AddPreAction(
), ),
) )
AlwaysBuild(env.Alias("debug", DEFAULT_TARGETS)) AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS))
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
############################################################################## ##############################################################################

View File

@ -50,7 +50,7 @@ def GetBuildType(env):
return ( return (
"debug" "debug"
if ( if (
set(["debug", "sizedata"]) & set(COMMAND_LINE_TARGETS) set(["__debug", "sizedata"]) & set(COMMAND_LINE_TARGETS)
or env.GetProjectOption("build_type") == "debug" or env.GetProjectOption("build_type") == "debug"
) )
else "release" else "release"

View File

@ -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) env_name = environment or helpers.get_default_debug_env(project_config)
if not interface: 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) env_options = project_config.items(env=env_name, as_dict=True)
if "platform" not in env_options: if "platform" not in env_options:
@ -138,11 +140,15 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unpro
) )
stream = helpers.GDBMIConsoleStream() stream = helpers.GDBMIConsoleStream()
with proc.capture_std_streams(stream): 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() stream.close()
else: else:
click.echo("Preparing firmware for debugging...") 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 # save SHA sum of newly created prog
if load_mode == "modified": if load_mode == "modified":

View File

@ -14,9 +14,8 @@
# pylint: disable=too-many-arguments, too-many-locals, too-many-branches # pylint: disable=too-many-arguments, too-many-locals, too-many-branches
from fnmatch import fnmatch import fnmatch
from os import getcwd, listdir import os
from os.path import isdir, join
from time import time from time import time
import click import click
@ -24,6 +23,7 @@ from tabulate import tabulate
from platformio import app, exception, fs, util from platformio import app, exception, fs, util
from platformio.commands.test.embedded import EmbeddedTestProcessor 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.commands.test.native import NativeTestProcessor
from platformio.platform.factory import PlatformFactory from platformio.platform.factory import PlatformFactory
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
@ -50,7 +50,7 @@ from platformio.project.config import ProjectConfig
@click.option( @click.option(
"-d", "-d",
"--project-dir", "--project-dir",
default=getcwd, default=os.getcwd,
type=click.Path( type=click.Path(
exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True 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): with fs.cd(project_dir):
config = ProjectConfig.get_instance(project_conf) config = ProjectConfig.get_instance(project_conf)
config.validate(envs=environment) config.validate(envs=environment)
test_names = get_test_names(config)
test_dir = config.get_optional_dir("test")
if not isdir(test_dir):
raise exception.TestDirNotExists(test_dir)
test_names = get_test_names(test_dir)
if not verbose: if not verbose:
click.echo("Verbose mode can be enabled via `-v, --verbose` option") 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, not environment and default_envs and envname not in default_envs,
testname != "*" testname != "*"
and patterns["filter"] 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 != "*" 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): if any(skip_conditions):
results.append({"env": envname, "test": testname}) results.append({"env": envname, "test": testname})
@ -142,7 +140,10 @@ def cli( # pylint: disable=redefined-builtin
cls = ( cls = (
EmbeddedTestProcessor 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 else NativeTestProcessor
) )
tp = cls( tp = cls(
@ -185,22 +186,6 @@ def cli( # pylint: disable=redefined-builtin
raise exception.ReturnErrorCode(1) 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): def print_processing_header(test, env):
click.echo( click.echo(
"Processing %s in %s environment" "Processing %s in %s environment"

View File

@ -0,0 +1,30 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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

View File

@ -139,9 +139,9 @@ class TestProcessorBase(object):
cmd_run, cmd_run,
project_dir=self.options["project_dir"], project_dir=self.options["project_dir"],
project_conf=self.options["project_config"].path, project_conf=self.options["project_config"].path,
upload_port=self.options["upload_port"], upload_port=self.options.get("upload_port"),
verbose=self.options["verbose"], verbose=self.options["verbose"],
silent=self.options["silent"], silent=self.options.get("silent"),
environment=[self.env_name], environment=[self.env_name],
disable_auto_clean="nobuild" in target, disable_auto_clean="nobuild" in target,
target=target, target=target,

View File

@ -23,6 +23,9 @@ from os.path import isfile
from platformio import util from platformio import util
from platformio.commands import PlatformioCLI from platformio.commands import PlatformioCLI
from platformio.commands.run.command import cli as cmd_run 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.compat import IS_WINDOWS, is_bytes
from platformio.debug.exception import DebugInvalidOptionsError 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] return default_envs[0] if default_envs else all_envs[0]
def predebug_project(ctx, project_dir, env_name, preload, verbose): def predebug_project(
ctx.invoke( ctx, project_dir, project_config, env_name, preload, verbose
cmd_run, ): # pylint: disable=too-many-arguments
project_dir=project_dir, debug_testname = project_config.get("env:" + env_name, "debug_test")
environment=[env_name], if debug_testname:
target=["debug"] + (["upload"] if preload else []), test_names = get_test_names(project_config)
verbose=verbose, 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: if preload:
time.sleep(5) time.sleep(5)

View File

@ -703,6 +703,11 @@ ProjectOptions = OrderedDict(
"for an incoming connection" "for an incoming connection"
), ),
), ),
ConfigEnvOption(
group="debug",
name="debug_test",
description=("A name of a unit test to be debugged"),
),
# Advanced # Advanced
ConfigEnvOption( ConfigEnvOption(
group="advanced", group="advanced",