From b69567718a3e2b345609ca960ff768637a9bdf0d Mon Sep 17 00:00:00 2001 From: Thorsten de Buhr Date: Thu, 3 Sep 2026 13:42:21 +0200 Subject: [PATCH] fix(ctrace): report trace-run reference diagnostics --- .../ctrace/src/control/TraceDirectoryJob.cpp | 9 +-- .../test/integration/src/CtraceIntegTests.cpp | 38 +++++++++++- .../src/control/TraceDirectoryJobTests.cpp | 58 ++++++++++++++++++- .../tracerun/TraceRunConfigReaderTests.cpp | 5 +- 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/tools/ctrace/src/control/TraceDirectoryJob.cpp b/tools/ctrace/src/control/TraceDirectoryJob.cpp index f9b721ee5..d5f007e57 100644 --- a/tools/ctrace/src/control/TraceDirectoryJob.cpp +++ b/tools/ctrace/src/control/TraceDirectoryJob.cpp @@ -41,13 +41,10 @@ static std::vector> referenceContext(const T return context; } -/** @brief Reports informational, warning, and error annotations from trace-run references. */ -static void reportTraceRunDiagnostics(const TraceRunConfig& config, DiagnosticSink& diagnostics) +/** @brief Reports annotations from every reference retained by the trace-run reader. */ +static void reportConsumedReferenceDiagnostics(const TraceRunConfig& config, DiagnosticSink& diagnostics) { for (const auto& reference : config.references) { - if (TraceRunSchema::isItmChannelZero(reference)) { - continue; - } const auto report = [&](DiagnosticSink::Severity severity, const std::optional& message) { if (!message.has_value() || message->empty()) { return; @@ -117,7 +114,7 @@ void TraceDirectoryJob::run() {"setups", std::to_string(config.setups.size())}, }, }); - reportTraceRunDiagnostics(config, m_diagnostics); + reportConsumedReferenceDiagnostics(config, m_diagnostics); const auto ctraceRunMeta = CtraceRunMeta::fromConfig(config); reportTraceRunWarnings(ctraceRunMeta, m_diagnostics); const auto rawInputs = TraceRunDiscovery::rawInputs(configFile); diff --git a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp index c5b4a8862..dbea017ed 100644 --- a/tools/ctrace/test/integration/src/CtraceIntegTests.cpp +++ b/tools/ctrace/test/integration/src/CtraceIntegTests.cpp @@ -186,8 +186,44 @@ TEST_F(CtraceIntegTests, AppliesTraceRunConfiguration) EXPECT_EQ(0, result.exitCode) << result.stderrText; expectContains(result.stderrText, "aligned DWT range"); expectContains(result.stderrText, "configured DWT data trace"); + expectContains(result.stderrText, "configured ITM channel"); expectContains(result.stderrText, "applied ctrace-run meta"); - expectNotContains(result.stderrText, "configured ITM channel"); +} + +TEST_F(CtraceIntegTests, ReportsDiagnosticsFromConsumedTraceRunReferences) +{ + writeFile(workDirectory() / "Diagnostics.ctrace-run.yml", R"yml(ctrace-run: + ctrace-setup: + - pname: core + timestamps: + clock: 400000000 + ctrace-refs: + - ctrace-ref: core/itm + type: itm + pname: core + stream: 1 + source: 0 + info: configured ITM channel zero + warning: ITM channel zero uses fallback routing + error: target could not enable ITM channel zero + - ctrace-ref: core/exceptions + type: exception + error: ignored reference diagnostic +)yml"); + writeFile(workDirectory() / "Diagnostics.SWO.raw"); + + const auto result = run({"ctrace", workDirectory().string(), "--target", "Diagnostics", "--all"}); + EXPECT_EQ(0, result.exitCode) << result.stderrText; + expectContains(result.stderrText, "[info] configured ITM channel zero:"); + expectContains(result.stderrText, "[warning] ITM channel zero uses fallback routing:"); + expectContains(result.stderrText, "[error] target could not enable ITM channel zero:"); + expectContains(result.stderrText, "ctraceRef=core/itm, type=itm, pname=core"); + expectNotContains(result.stderrText, "ignored reference diagnostic"); + + expectNonEmptyFile(workDirectory() / "Diagnostics.SWO.csv"); + expectNonEmptyFile(workDirectory() / "Diagnostics.ctf" / "metadata"); + expectNonEmptyFile(workDirectory() / "Diagnostics.ctf" / "stream_0"); + expectNonEmptyFile(workDirectory() / "Diagnostics.SWO.traceanalysis.xml"); } TEST_F(CtraceIntegTests, GeneratesRequestedOutputsAfterDecoderError) diff --git a/tools/ctrace/test/unit/src/control/TraceDirectoryJobTests.cpp b/tools/ctrace/test/unit/src/control/TraceDirectoryJobTests.cpp index b87479925..93dc21d2d 100644 --- a/tools/ctrace/test/unit/src/control/TraceDirectoryJobTests.cpp +++ b/tools/ctrace/test/unit/src/control/TraceDirectoryJobTests.cpp @@ -83,6 +83,15 @@ static void writeTraceInputs(const std::filesystem::path& traceDirectory, } } +/** @brief Finds one diagnostic with an exact message. */ +static const DiagnosticSink::Event* findDiagnostic(const CollectingDiagnosticSink& diagnostics, + const std::string_view message) +{ + const auto found = std::find_if(diagnostics.events().begin(), diagnostics.events().end(), + [&](const auto& event) { return event.message == message; }); + return found == diagnostics.events().end() ? nullptr : &*found; +} + TEST(CtraceUnitTests, testTraceDirectoryTargetAndOutputNames) { const TemporaryTestPath temporaryPath("ctrace-trace-directory-job-test"); @@ -170,7 +179,7 @@ TEST(CtraceUnitTests, testTraceDirectoryReportsGenerationDiagnosticsAndMissingSw emptyError.warning = ""; emptyError.error = ""; auto channelZero = TraceRunTestSupport::makeReference("itm", std::nullopt, std::nullopt, {0U}, "core/itm"); - channelZero.error = "ignored channel zero"; + channelZero.error = "channel zero diagnostic"; TraceRunReference noStream = reported; noStream.ctraceRef = "core/no-stream"; noStream.stream.reset(); @@ -189,9 +198,56 @@ TEST(CtraceUnitTests, testTraceDirectoryReportsGenerationDiagnosticsAndMissingSw EXPECT_TRUE(diagnostics.containsMessage("producer note")); EXPECT_TRUE(diagnostics.containsMessage("producer warning")); EXPECT_TRUE(diagnostics.containsMessage("producer error")); + EXPECT_TRUE(diagnostics.containsMessage("channel zero diagnostic")); + EXPECT_TRUE(diagnostics.containsMessage("trace generation setup failed without a diagnostic message")); EXPECT_TRUE(diagnostics.containsMessage("does not match ctrace-setup pname")); EXPECT_TRUE(diagnostics.containsMessage("skipping raw trace channel")); EXPECT_TRUE(diagnostics.containsMessage("no supported .SWO.raw input found")); + + const auto* info = findDiagnostic(diagnostics, "producer note"); + const auto* warning = findDiagnostic(diagnostics, "producer warning"); + const auto* error = findDiagnostic(diagnostics, "producer error"); + const auto* channelZeroError = findDiagnostic(diagnostics, "channel zero diagnostic"); + ASSERT_NE(info, nullptr); + ASSERT_NE(warning, nullptr); + ASSERT_NE(error, nullptr); + ASSERT_NE(channelZeroError, nullptr); + EXPECT_EQ(info->severity, DiagnosticSink::Severity::Info); + EXPECT_EQ(warning->severity, DiagnosticSink::Severity::Warning); + EXPECT_EQ(error->severity, DiagnosticSink::Severity::Error); + EXPECT_EQ(channelZeroError->severity, DiagnosticSink::Severity::Error); + EXPECT_EQ(error->impact, DiagnosticSink::Impact::NonFailing); + EXPECT_EQ(channelZeroError->impact, DiagnosticSink::Impact::NonFailing); +} + +TEST(CtraceUnitTests, testTraceDirectoryChecksOutputRequirementsAfterReferenceError) +{ + const TemporaryTestPath temporaryPath("ctrace-trace-directory-reference-error-test"); + const auto traceDir = temporaryPath.path() / ".trace"; + writeTraceInputs(traceDir, {"MissingClock"}); + + TraceRunConfig config; + auto reference = TraceRunTestSupport::makeReference("event", "core", 3U, {}, "core/event"); + reference.error = "producer could not configure event trace"; + config.references.push_back(std::move(reference)); + + CliOptions options; + options.traceDir = traceDir.string(); + options.targetName = "MissingClock"; + options.outputFormat = OutputFormat::All; + + CollectingDiagnosticSink diagnostics; + TestTraceRunConfigReader reader(config); + TraceDirectoryJob(options, diagnostics, reader).run(); + + const auto* referenceError = findDiagnostic(diagnostics, "producer could not configure event trace"); + ASSERT_NE(referenceError, nullptr); + EXPECT_EQ(referenceError->severity, DiagnosticSink::Severity::Error); + EXPECT_EQ(referenceError->impact, DiagnosticSink::Impact::NonFailing); + EXPECT_TRUE(diagnostics.containsMessage("CTF output requires timestamps.clock")); + EXPECT_GT(diagnostics.failureCount(), 0U); + EXPECT_TRUE(std::filesystem::is_regular_file(traceDir / "MissingClock.SWO.csv")); + EXPECT_FALSE(std::filesystem::exists(traceDir / "MissingClock.ctf")); } TEST(CtraceUnitTests, testTraceDirectoryReportsConfigFailureAndRequiresDirectory) diff --git a/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp b/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp index af983de88..3c1d5b6e7 100644 --- a/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp +++ b/tools/ctrace/test/unit/src/tracerun/TraceRunConfigReaderTests.cpp @@ -319,8 +319,11 @@ TEST(CtraceUnitTests, TraceRunReaderPreservesDiagnosticReferences) - { type: dwt, ctrace-ref: core/notdata#2, error: unrouted } - { type: itm, ctrace-ref: core/itm0, source: 0, error: disabled } - { type: itm, ctrace-ref: core/itm1, source: 1, error: usable, label: null } + - { type: exception, ctrace-ref: core/exceptions, error: ignored } + - { type: global_ts, ctrace-ref: core/timesync, warning: ignored } + - { type: overflow, ctrace-ref: core/overflow, info: ignored } )yml"); - ASSERT_EQ(config.references.size(), 8U); + ASSERT_EQ(config.references.size(), 8U) << "reader must ignore diagnostic annotations on unconsumed reference types"; EXPECT_EQ(config.references[0].processorName, std::optional("core0")); EXPECT_EQ(config.references[1].processorName, std::optional("core1")); EXPECT_FALSE(config.references[2].processorName.has_value());