From c9c541b23ccb0d1269667cf011b07a56722d661f Mon Sep 17 00:00:00 2001 From: Socialpranker <273312799+Socialpranker@users.noreply.github.com> Date: Thu, 23 Jul 2026 07:18:57 +0200 Subject: [PATCH] builder: include test sources in compiledb via __test target `pio run -t compiledb -t __test` used to fail with "Nothing to build" for the documented `test/test_*/` suite layout, making it impossible to get test sources (and test-framework headers such as unity.h) into compile_commands.json for clangd and other tools. Two changes: 1. `ConfigureTestTarget`: when no specific test suite is selected (PIOTEST_RUNNING_NAME is absent) and a compilation database is being generated, include every `test_*/` suite recursively in PIOTEST_SRC_FILTER and add each suite dir to CPPPATH. The default filter only matches sources directly inside the test dir, which the documented layout never has. 2. builder/main.py: when "compiledb" is among the command-line targets, don't alias "__test" to the default build targets. Compiledb generation never compiles or links, but the default alias would trigger a full build that tries to link every test suite (each with its own main()) into one program and fails. A plain `pio run -t compiledb` (without `-t __test`) and `pio test` keep their current behavior. Fixes #4934 --- platformio/builder/main.py | 10 +++- platformio/builder/tools/piotest.py | 19 +++++++ tests/commands/test_run_compiledb.py | 82 ++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tests/commands/test_run_compiledb.py diff --git a/platformio/builder/main.py b/platformio/builder/main.py index 880ca155ac..bfa36f538b 100644 --- a/platformio/builder/main.py +++ b/platformio/builder/main.py @@ -205,7 +205,15 @@ ) AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS)) -AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) +if "compiledb" in COMMAND_LINE_TARGETS: + # `pio run -t compiledb -t __test` (issue #4934): "__test" is passed only + # to include test sources in the compilation database. Don't alias it to + # the default build targets — a full build would try to link every test + # suite (each with its own `main()`) into one program and fail, while + # compiledb generation itself never compiles or links anything. + env.Alias("__test", []) +else: + AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) env.ProcessDelayedActions() diff --git a/platformio/builder/tools/piotest.py b/platformio/builder/tools/piotest.py index b3b324b1e1..4ffab1a3b9 100644 --- a/platformio/builder/tools/piotest.py +++ b/platformio/builder/tools/piotest.py @@ -14,6 +14,8 @@ import os +from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error + from platformio.builder.tools import piobuild from platformio.test.result import TestSuite from platformio.test.runners.factory import TestRunnerFactory @@ -26,6 +28,23 @@ def ConfigureTestTarget(env): ) env.Prepend(CPPPATH=["$PROJECT_TEST_DIR"]) + if "PIOTEST_RUNNING_NAME" not in env and "compiledb" in COMMAND_LINE_TARGETS: + # A compilation database is being generated without a specific test + # suite selected (`pio run -t compiledb -t __test`, issue #4934). + # The default filter above only matches sources directly in the test + # dir, so nested `test_*/` suites — the documented layout — would + # produce "Nothing to build". Include every test suite recursively: + # unlike a real test build, compiledb never links, so the multiple + # `main()` definitions across suites are not a problem. + env.Append(PIOTEST_SRC_FILTER=[f"+"]) + test_dir = env.subst("$PROJECT_TEST_DIR") + if os.path.isdir(test_dir): + for item in sorted(os.listdir(test_dir)): + if item.startswith("test_") and os.path.isdir( + os.path.join(test_dir, item) + ): + env.Prepend(CPPPATH=[os.path.join("$PROJECT_TEST_DIR", item)]) + if "PIOTEST_RUNNING_NAME" in env: test_name = env["PIOTEST_RUNNING_NAME"] while True: diff --git a/tests/commands/test_run_compiledb.py b/tests/commands/test_run_compiledb.py new file mode 100644 index 0000000000..dc0d963385 --- /dev/null +++ b/tests/commands/test_run_compiledb.py @@ -0,0 +1,82 @@ +# 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 json + +from platformio.run.cli import cli as cmd_run + + +def _make_project_with_tests(tmpdir, extra_ini=""): + tmpdir.join("platformio.ini").write(""" +[env:native] +platform = native +%s +""" % extra_ini) + tmpdir.mkdir("src").join("calc.c").write(""" +int add(int a, int b) { return a + b; } +""") + tmpdir.mkdir("test").mkdir("test_calc").join("test_add.c").write(""" +int add(int a, int b); +int main(void) { return add(1, 2) == 3 ? 0 : 1; } +""") + + +def _compiledb_files(tmpdir): + with open(str(tmpdir.join("compile_commands.json")), encoding="utf8") as fp: + return [entry["file"] for entry in json.load(fp)] + + +def test_compiledb_includes_test_sources(clirunner, validate_cliresult, tmpdir): + # Regression test for https://github.com/platformio/platformio-core/issues/4934 + # `pio run -t compiledb -t __test` used to fail with "Nothing to build" + # for the documented `test/test_*/` layout, leaving test sources (and + # test-framework headers like unity.h) out of the compilation database. + _make_project_with_tests(tmpdir) + result = clirunner.invoke( + cmd_run, + ["--project-dir", str(tmpdir), "-t", "compiledb", "-t", "__test"], + ) + validate_cliresult(result) + + files = _compiledb_files(tmpdir) + assert any(f.endswith("test_add.c") for f in files), files + + +def test_compiledb_with_tests_and_src(clirunner, validate_cliresult, tmpdir): + # With `test_build_src = yes`, both the test suites and the production + # sources should land in the compilation database. + _make_project_with_tests(tmpdir, extra_ini="test_build_src = yes") + result = clirunner.invoke( + cmd_run, + ["--project-dir", str(tmpdir), "-t", "compiledb", "-t", "__test"], + ) + validate_cliresult(result) + + files = _compiledb_files(tmpdir) + assert any(f.endswith("test_add.c") for f in files), files + assert any(f.endswith("calc.c") for f in files), files + + +def test_compiledb_without_test_target_unchanged(clirunner, validate_cliresult, tmpdir): + # Non-regression: a plain `pio run -t compiledb` must keep its current + # behavior — production sources only, no test sources. + _make_project_with_tests(tmpdir) + result = clirunner.invoke( + cmd_run, ["--project-dir", str(tmpdir), "-t", "compiledb"] + ) + validate_cliresult(result) + + files = _compiledb_files(tmpdir) + assert any(f.endswith("calc.c") for f in files), files + assert not any(f.endswith("test_add.c") for f in files), files