From 4634de335bdf2c23659e84195ededc4ef4702ba4 Mon Sep 17 00:00:00 2001 From: Frank Winklmeier Date: Thu, 17 Feb 2022 13:26:55 +0100 Subject: cmCTestTestHandler: refactor CleanTestOutput method Refactor the code to skip over UTF-8 multi-bytes into its own lambda function so it can more easily be re-used. --- Source/CTest/cmCTestTestHandler.cxx | 39 ++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 5a3a8d0..958c51c 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -2101,24 +2101,31 @@ void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) return; } - // Truncate at given length but do not break in the middle of a multi-byte - // UTF-8 encoding. - char const* const begin = output.c_str(); - char const* const end = begin + output.size(); - char const* const truncate = begin + length; - char const* current = begin; - while (current < truncate) { - unsigned int ch; - if (const char* next = cm_utf8_decode_character(current, end, &ch)) { - if (next > truncate) { - break; + // Advance n bytes in string delimited by begin/end but do not break in the + // middle of a multi-byte UTF-8 encoding. + auto utf8_advance = [](char const* const begin, char const* const end, + size_t n) -> const char* { + char const* const stop = begin + n; + char const* current = begin; + while (current < stop) { + unsigned int ch; + if (const char* next = cm_utf8_decode_character(current, end, &ch)) { + if (next > stop) { + break; + } + current = next; + } else // Bad byte will be handled by cmXMLWriter. + { + ++current; } - current = next; - } else // Bad byte will be handled by cmXMLWriter. - { - ++current; } - } + return current; + }; + + // Truncate at given length respecting UTF-8 words + char const* const begin = output.c_str(); + char const* const end = begin + output.size(); + char const* current = utf8_advance(begin, end, length); output.erase(current - begin); // Append truncation message. -- cgit v0.12 From 359e5b17d8edd092a1e500698af7968f96fe1d8d Mon Sep 17 00:00:00 2001 From: Frank Winklmeier Date: Mon, 28 Feb 2022 10:13:55 +0100 Subject: presets: bump version to v5 Prepare for new test preset fields. --- Help/manual/cmake-presets.7.rst | 2 +- Help/manual/presets/example.json | 2 +- Help/manual/presets/schema.json | 50 +++++++++++++++++++++- Source/cmCMakePresetsGraphReadJSON.cxx | 2 +- .../CMakePresets/IncludeOutsideProjectInclude.json | 2 +- Tests/RunCMake/CMakePresetsBuild/Good.json.in | 2 +- Tests/RunCMake/CMakePresetsTest/Good.json.in | 2 +- 7 files changed, 54 insertions(+), 8 deletions(-) diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index 31bd9c0..23057b9 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -54,7 +54,7 @@ The root object recognizes the following fields: ``version`` A required integer representing the version of the JSON schema. - The supported versions are ``1``, ``2``, ``3``, and ``4``. + The supported versions are ``1``, ``2``, ``3``, ``4``, and ``5``. ``cmakeMinimumRequired`` diff --git a/Help/manual/presets/example.json b/Help/manual/presets/example.json index a7ec10e..9365c87 100644 --- a/Help/manual/presets/example.json +++ b/Help/manual/presets/example.json @@ -1,5 +1,5 @@ { - "version": 4, + "version": 5, "cmakeMinimumRequired": { "major": 3, "minor": 21, diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json index 12f8b5e..ac7b1dd 100644 --- a/Help/manual/presets/schema.json +++ b/Help/manual/presets/schema.json @@ -57,6 +57,21 @@ "include": { "$ref": "#/definitions/include"} }, "additionalProperties": false + }, + { + "properties": { + "version": { + "const": 5, + "description": "A required integer representing the version of the JSON schema." + }, + "cmakeMinimumRequired": { "$ref": "#/definitions/cmakeMinimumRequired"}, + "vendor": { "$ref": "#/definitions/vendor" }, + "configurePresets": { "$ref": "#/definitions/configurePresetsV3"}, + "buildPresets": { "$ref": "#/definitions/buildPresetsV4"}, + "testPresets": { "$ref": "#/definitions/testPresetsV5"}, + "include": { "$ref": "#/definitions/include"} + }, + "additionalProperties": false } ], "required": [ @@ -821,8 +836,7 @@ "type": "integer", "description": "An optional integer specifying the maximum width of a test name to output. Equivalent to passing --max-width on the command line." } - }, - "additionalProperties": false + } }, "filter": { "type": "object", @@ -998,6 +1012,38 @@ ] } }, + "testPresetsV5": { + "type": "array", + "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 5 and higher.", + "allOf": [ + { "$ref": "#/definitions/testPresetsItemsV2" }, + { "$ref": "#/definitions/testPresetsItemsV3" } + ], + "items": { + "type": "object", + "properties": { + "name": {}, + "hidden": {}, + "inherits": {}, + "configurePreset": {}, + "vendor": {}, + "displayName": {}, + "description": {}, + "inheritConfigureEnvironment": {}, + "environment": {}, + "configuration": {}, + "overwriteConfigurationFile": {}, + "output": {}, + "filter": {}, + "execution": {}, + "condition": {} + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, "testPresetsV3": { "type": "array", "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 3 and higher.", diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index 85cf5be..8a770b0 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -33,7 +33,7 @@ using TestPreset = cmCMakePresetsGraph::TestPreset; using ArchToolsetStrategy = cmCMakePresetsGraph::ArchToolsetStrategy; constexpr int MIN_VERSION = 1; -constexpr int MAX_VERSION = 4; +constexpr int MAX_VERSION = 5; struct CMakeVersion { diff --git a/Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json b/Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json index f13e55c..ebf106f 100644 --- a/Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json +++ b/Tests/RunCMake/CMakePresets/IncludeOutsideProjectInclude.json @@ -1,3 +1,3 @@ { - "version": 4 + "version": 5 } diff --git a/Tests/RunCMake/CMakePresetsBuild/Good.json.in b/Tests/RunCMake/CMakePresetsBuild/Good.json.in index 568907c..a953f48 100644 --- a/Tests/RunCMake/CMakePresetsBuild/Good.json.in +++ b/Tests/RunCMake/CMakePresetsBuild/Good.json.in @@ -1,5 +1,5 @@ { - "version": 4, + "version": 5, "configurePresets": [ { "name": "default", diff --git a/Tests/RunCMake/CMakePresetsTest/Good.json.in b/Tests/RunCMake/CMakePresetsTest/Good.json.in index 57be5a5..ea20d9d 100644 --- a/Tests/RunCMake/CMakePresetsTest/Good.json.in +++ b/Tests/RunCMake/CMakePresetsTest/Good.json.in @@ -1,5 +1,5 @@ { - "version": 2, + "version": 5, "configurePresets": [ { "name": "default", -- cgit v0.12 From 140704d443e73c2dc74ac8192a109ae0c21e834a Mon Sep 17 00:00:00 2001 From: Frank Winklmeier Date: Mon, 7 Mar 2022 09:28:55 +0100 Subject: ctest: add option for output truncation Add `--test-output-truncation` to `ctest`. This option can be used to customize which part of the test output is being truncated. Currently supported values are `tail`, `middle` and `head`. Also add equivalent `CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION` variable. Fixes: #23206 --- Auxiliary/vim/syntax/cmake.vim | 1 + Help/command/ctest_test.rst | 5 +- Help/manual/cmake-presets.7.rst | 6 +++ Help/manual/cmake-variables.7.rst | 1 + Help/manual/ctest.1.rst | 4 ++ Help/manual/presets/schema.json | 25 +++++++++- ...TEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE.rst | 3 +- ...TEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE.rst | 3 +- .../CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION.rst | 12 +++++ Source/CTest/cmCTestMemCheckHandler.cxx | 3 +- Source/CTest/cmCTestRunTest.cxx | 3 +- Source/CTest/cmCTestTestHandler.cxx | 57 +++++++++++++++++----- Source/CTest/cmCTestTestHandler.h | 11 ++++- Source/CTest/cmCTestTypes.h | 16 ++++++ Source/cmCMakePresetsGraph.cxx | 5 ++ Source/cmCMakePresetsGraph.h | 4 ++ Source/cmCMakePresetsGraphReadJSON.cxx | 5 ++ Source/cmCMakePresetsGraphReadJSONTestPresets.cxx | 39 +++++++++++++++ Source/cmCTest.cxx | 12 +++++ Source/ctest.cxx | 3 ++ Tests/RunCMake/CMakePresetsTest/Good.json.in | 3 +- Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake | 21 ++++++++ .../TestOutputTruncation-check.cmake | 12 +++++ .../TestOutputTruncation-stderr.txt | 1 + Tests/RunCMake/ctest_test/RunCMakeTest.cmake | 17 +++++++ .../ctest_test/TestOutputTruncation-check.cmake | 12 +++++ 26 files changed, 261 insertions(+), 23 deletions(-) create mode 100644 Help/variable/CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION.rst create mode 100644 Source/CTest/cmCTestTypes.h create mode 100644 Tests/RunCMake/CTestCommandLine/TestOutputTruncation-check.cmake create mode 100644 Tests/RunCMake/CTestCommandLine/TestOutputTruncation-stderr.txt create mode 100644 Tests/RunCMake/ctest_test/TestOutputTruncation-check.cmake diff --git a/Auxiliary/vim/syntax/cmake.vim b/Auxiliary/vim/syntax/cmake.vim index 381d97c..3762bed 100644 --- a/Auxiliary/vim/syntax/cmake.vim +++ b/Auxiliary/vim/syntax/cmake.vim @@ -1573,6 +1573,7 @@ syn keyword cmakeVariable contained \ CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS \ CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS \ CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE + \ CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION \ CTEST_CUSTOM_MEMCHECK_IGNORE \ CTEST_CUSTOM_POST_MEMCHECK \ CTEST_CUSTOM_POST_TEST diff --git a/Help/command/ctest_test.rst b/Help/command/ctest_test.rst index 6a9a6a0..11ebdbd 100644 --- a/Help/command/ctest_test.rst +++ b/Help/command/ctest_test.rst @@ -172,8 +172,9 @@ The options are: affected. Summary info detailing the percentage of passing tests is also unaffected by the ``QUIET`` option. -See also the :variable:`CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE` -and :variable:`CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE` variables. +See also the :variable:`CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE`, +:variable:`CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE` and +:variable:`CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION` variables. .. _`Additional Test Measurements`: diff --git a/Help/manual/cmake-presets.7.rst b/Help/manual/cmake-presets.7.rst index 23057b9..26b9dbc 100644 --- a/Help/manual/cmake-presets.7.rst +++ b/Help/manual/cmake-presets.7.rst @@ -694,6 +694,12 @@ that may contain the following fields: bytes. Equivalent to passing ``--test-output-size-failed`` on the command line. + ``testOutputTruncation`` + + An optional string specifying the test output truncation mode. Equivalent + to passing ``--test-output-truncation`` on the command line." + This is allowed in preset files specifying version ``5`` or above. + ``maxTestNameWidth`` An optional integer specifying the maximum width of a test name to diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 86e4d4c..956e918 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -636,6 +636,7 @@ Variables for CTest /variable/CTEST_CUSTOM_MAXIMUM_NUMBER_OF_ERRORS /variable/CTEST_CUSTOM_MAXIMUM_NUMBER_OF_WARNINGS /variable/CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE + /variable/CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION /variable/CTEST_CUSTOM_MEMCHECK_IGNORE /variable/CTEST_CUSTOM_POST_MEMCHECK /variable/CTEST_CUSTOM_POST_TEST diff --git a/Help/manual/ctest.1.rst b/Help/manual/ctest.1.rst index 1e7b077..82e27b8 100644 --- a/Help/manual/ctest.1.rst +++ b/Help/manual/ctest.1.rst @@ -362,6 +362,10 @@ Specify the directory in which to look for tests. ``--test-output-size-failed `` Limit the output for failed tests to ```` bytes. +``--test-output-truncation `` + Truncate 'tail' (default), 'middle' or 'head' of test output once maximum + output size is reached. + ``--overwrite`` Overwrite CTest configuration option. diff --git a/Help/manual/presets/schema.json b/Help/manual/presets/schema.json index ac7b1dd..c96405c 100644 --- a/Help/manual/presets/schema.json +++ b/Help/manual/presets/schema.json @@ -688,6 +688,28 @@ "additionalProperties": false } }, + "testPresetsItemsV5": { + "type": "array", + "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 5 and higher.", + "items": { + "type": "object", + "properties": { + "output": { + "type": "object", + "description": "An optional object specifying output options.", + "properties": { + "testOutputTruncation": { + "type": "string", + "description": "An optional string specifying the test output truncation mode. Equivalent to passing --test-output-truncation on the command line. Must be one of the following values: \"tail\", \"middle\", or \"head\".", + "enum": [ + "tail", "middle", "head" + ] + } + } + } + } + } + }, "testPresetsItemsV3": { "type": "array", "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 3 and higher.", @@ -1017,7 +1039,8 @@ "description": "An optional array of test preset objects. Used to specify arguments to ctest. Available in version 5 and higher.", "allOf": [ { "$ref": "#/definitions/testPresetsItemsV2" }, - { "$ref": "#/definitions/testPresetsItemsV3" } + { "$ref": "#/definitions/testPresetsItemsV3" }, + { "$ref": "#/definitions/testPresetsItemsV5" } ], "items": { "type": "object", diff --git a/Help/variable/CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE.rst b/Help/variable/CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE.rst index 7e7d431..007cfe0 100644 --- a/Help/variable/CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE.rst +++ b/Help/variable/CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE.rst @@ -3,7 +3,8 @@ CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE When saving a failing test's output, this is the maximum size, in bytes, that will be collected by the :command:`ctest_test` command. Defaults to 307200 -(300 KiB). +(300 KiB). See :variable:`CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION` for possible +truncation modes. If a test's output contains the literal string "CTEST_FULL_OUTPUT", the output will not be truncated and may exceed the maximum size. diff --git a/Help/variable/CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE.rst b/Help/variable/CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE.rst index 64367f9..8545fc4 100644 --- a/Help/variable/CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE.rst +++ b/Help/variable/CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE.rst @@ -3,7 +3,8 @@ CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE When saving a passing test's output, this is the maximum size, in bytes, that will be collected by the :command:`ctest_test` command. Defaults to 1024 -(1 KiB). +(1 KiB). See :variable:`CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION` for possible +truncation modes. If a test's output contains the literal string "CTEST_FULL_OUTPUT", the output will not be truncated and may exceed the maximum size. diff --git a/Help/variable/CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION.rst b/Help/variable/CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION.rst new file mode 100644 index 0000000..2d4219e --- /dev/null +++ b/Help/variable/CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION.rst @@ -0,0 +1,12 @@ +CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION +----------------------------------- + +.. versionadded:: 3.24 + +Set the test output truncation mode in case a maximum size is configured +via the :variable:`CTEST_CUSTOM_MAXIMUM_PASSED_TEST_OUTPUT_SIZE` or +:variable:`CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE` variables. +By default the ``tail`` of the output will be truncated. Other possible +values are ``middle`` and ``head``. + +.. include:: CTEST_CUSTOM_XXX.txt diff --git a/Source/CTest/cmCTestMemCheckHandler.cxx b/Source/CTest/cmCTestMemCheckHandler.cxx index 6bb8e79..2d8276a 100644 --- a/Source/CTest/cmCTestMemCheckHandler.cxx +++ b/Source/CTest/cmCTestMemCheckHandler.cxx @@ -371,7 +371,8 @@ void cmCTestMemCheckHandler::GenerateCTestXML(cmXMLWriter& xml) } this->CleanTestOutput( memcheckstr, - static_cast(this->CustomMaximumFailedTestOutputSize)); + static_cast(this->CustomMaximumFailedTestOutputSize), + this->TestOutputTruncation); this->WriteTestResultHeader(xml, result); xml.StartElement("Results"); int memoryErrors = 0; diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx index 6cd3b09..f594300 100644 --- a/Source/CTest/cmCTestRunTest.cxx +++ b/Source/CTest/cmCTestRunTest.cxx @@ -277,7 +277,8 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started) static_cast( this->TestResult.Status == cmCTestTestHandler::COMPLETED ? this->TestHandler->CustomMaximumPassedTestOutputSize - : this->TestHandler->CustomMaximumFailedTestOutputSize)); + : this->TestHandler->CustomMaximumFailedTestOutputSize), + this->TestHandler->TestOutputTruncation); } this->TestResult.Reason = reason; if (this->TestHandler->LogFile) { diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx index 958c51c..a794e3d 100644 --- a/Source/CTest/cmCTestTestHandler.cxx +++ b/Source/CTest/cmCTestTestHandler.cxx @@ -281,6 +281,7 @@ cmCTestTestHandler::cmCTestTestHandler() this->CustomMaximumPassedTestOutputSize = 1 * 1024; this->CustomMaximumFailedTestOutputSize = 300 * 1024; + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; this->MemCheck = false; @@ -325,6 +326,7 @@ void cmCTestTestHandler::Initialize() this->CustomPostTest.clear(); this->CustomMaximumPassedTestOutputSize = 1 * 1024; this->CustomMaximumFailedTestOutputSize = 300 * 1024; + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; this->TestsToRun.clear(); @@ -358,6 +360,11 @@ void cmCTestTestHandler::PopulateCustomVectors(cmMakefile* mf) this->CTest->PopulateCustomInteger( mf, "CTEST_CUSTOM_MAXIMUM_FAILED_TEST_OUTPUT_SIZE", this->CustomMaximumFailedTestOutputSize); + + cmValue dval = mf->GetDefinition("CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION"); + if (dval) { + this->SetTestOutputTruncation(dval); + } } int cmCTestTestHandler::PreProcessHandler() @@ -2076,6 +2083,20 @@ void cmCTestTestHandler::SetExcludeRegExp(const std::string& arg) this->ExcludeRegExp = arg; } +bool cmCTestTestHandler::SetTestOutputTruncation(const std::string& mode) +{ + if (mode == "tail") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Tail; + } else if (mode == "middle") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Middle; + } else if (mode == "head") { + this->TestOutputTruncation = cmCTestTypes::TruncationMode::Head; + } else { + return false; + } + return true; +} + void cmCTestTestHandler::SetTestsToRunInformation(cmValue in) { if (!in) { @@ -2094,7 +2115,8 @@ void cmCTestTestHandler::SetTestsToRunInformation(cmValue in) } } -void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) +void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length, + cmCTestTypes::TruncationMode truncate) { if (!length || length >= output.size() || output.find("CTEST_FULL_OUTPUT") != std::string::npos) { @@ -2122,20 +2144,29 @@ void cmCTestTestHandler::CleanTestOutput(std::string& output, size_t length) return current; }; - // Truncate at given length respecting UTF-8 words + // Truncation message. + const std::string msg = + "\n[This part of the test output was removed since it " + "exceeds the threshold of " + + std::to_string(length) + " bytes.]\n"; + char const* const begin = output.c_str(); char const* const end = begin + output.size(); - char const* current = utf8_advance(begin, end, length); - output.erase(current - begin); - - // Append truncation message. - std::ostringstream msg; - msg << "...\n" - "The rest of the test output was removed since it exceeds the " - "threshold " - "of " - << length << " bytes.\n"; - output += msg.str(); + + // Erase head, middle or tail of output. + if (truncate == cmCTestTypes::TruncationMode::Head) { + char const* current = utf8_advance(begin, end, output.size() - length); + output.erase(0, current - begin); + output.insert(0, msg + "..."); + } else if (truncate == cmCTestTypes::TruncationMode::Middle) { + char const* current = utf8_advance(begin, end, length / 2); + output.erase(current - begin, output.size() - length); + output.insert(current - begin, "..." + msg + "..."); + } else { // default or "tail" + char const* current = utf8_advance(begin, end, length); + output.erase(current - begin); + output += ("..." + msg); + } } bool cmCTestTestHandler::SetTestsProperties( diff --git a/Source/CTest/cmCTestTestHandler.h b/Source/CTest/cmCTestTestHandler.h index 135e764..d0049da 100644 --- a/Source/CTest/cmCTestTestHandler.h +++ b/Source/CTest/cmCTestTestHandler.h @@ -19,6 +19,7 @@ #include "cmCTest.h" #include "cmCTestGenericHandler.h" #include "cmCTestResourceSpec.h" +#include "cmCTestTypes.h" #include "cmDuration.h" #include "cmListFileCache.h" #include "cmValue.h" @@ -32,6 +33,7 @@ class cmXMLWriter; */ class cmCTestTestHandler : public cmCTestGenericHandler { + friend class cmCTest; friend class cmCTestRunTest; friend class cmCTestMultiProcessHandler; @@ -80,6 +82,9 @@ public: this->CustomMaximumFailedTestOutputSize = n; } + //! Set test output truncation mode. Return false if unknown mode. + bool SetTestOutputTruncation(const std::string& mode); + //! pass the -I argument down void SetTestsToRunInformation(cmValue); @@ -242,8 +247,9 @@ protected: void AttachFile(cmXMLWriter& xml, std::string const& file, std::string const& name); - //! Clean test output to specified length - void CleanTestOutput(std::string& output, size_t length); + //! Clean test output to specified length and truncation mode + void CleanTestOutput(std::string& output, size_t length, + cmCTestTypes::TruncationMode truncate); cmDuration ElapsedTestingTime; @@ -258,6 +264,7 @@ protected: bool MemCheck; int CustomMaximumPassedTestOutputSize; int CustomMaximumFailedTestOutputSize; + cmCTestTypes::TruncationMode TestOutputTruncation; int MaxIndex; public: diff --git a/Source/CTest/cmCTestTypes.h b/Source/CTest/cmCTestTypes.h new file mode 100644 index 0000000..843d27a --- /dev/null +++ b/Source/CTest/cmCTestTypes.h @@ -0,0 +1,16 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ + +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +namespace cmCTestTypes { + +enum class TruncationMode +{ // Test output truncation mode + Tail, + Middle, + Head +}; +} diff --git a/Source/cmCMakePresetsGraph.cxx b/Source/cmCMakePresetsGraph.cxx index 705e2b0..39bb326 100644 --- a/Source/cmCMakePresetsGraph.cxx +++ b/Source/cmCMakePresetsGraph.cxx @@ -773,6 +773,8 @@ cmCMakePresetsGraph::TestPreset::VisitPresetInherit( parentOutput.MaxPassedTestOutputSize); InheritOptionalValue(output.MaxFailedTestOutputSize, parentOutput.MaxFailedTestOutputSize); + InheritOptionalValue(output.TestOutputTruncation, + parentOutput.TestOutputTruncation); InheritOptionalValue(output.MaxTestNameWidth, parentOutput.MaxTestNameWidth); } else { @@ -1027,6 +1029,9 @@ const char* cmCMakePresetsGraph::ResultToString(ReadFileResult result) "support."; case ReadFileResult::CYCLIC_INCLUDE: return "Cyclic include among preset files"; + case ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED: + return "File version must be 5 or higher for testOutputTruncation " + "preset support."; } return "Unknown error"; diff --git a/Source/cmCMakePresetsGraph.h b/Source/cmCMakePresetsGraph.h index 9d6c61a..f1f8662 100644 --- a/Source/cmCMakePresetsGraph.h +++ b/Source/cmCMakePresetsGraph.h @@ -14,6 +14,8 @@ #include +#include "CTest/cmCTestTypes.h" + enum class PackageResolveMode; class cmCMakePresetsGraph @@ -47,6 +49,7 @@ public: CONDITION_UNSUPPORTED, TOOLCHAIN_FILE_UNSUPPORTED, CYCLIC_INCLUDE, + TEST_OUTPUT_TRUNCATION_UNSUPPORTED, }; enum class ArchToolsetStrategy @@ -226,6 +229,7 @@ public: cm::optional SubprojectSummary; cm::optional MaxPassedTestOutputSize; cm::optional MaxFailedTestOutputSize; + cm::optional TestOutputTruncation; cm::optional MaxTestNameWidth; }; diff --git a/Source/cmCMakePresetsGraphReadJSON.cxx b/Source/cmCMakePresetsGraphReadJSON.cxx index 8a770b0..e68f0fa 100644 --- a/Source/cmCMakePresetsGraphReadJSON.cxx +++ b/Source/cmCMakePresetsGraphReadJSON.cxx @@ -568,6 +568,11 @@ cmCMakePresetsGraph::ReadFileResult cmCMakePresetsGraph::ReadJSONFile( return ReadFileResult::CONDITION_UNSUPPORTED; } + // Support for TestOutputTruncation added in version 5. + if (v < 5 && preset.Output) { + return ReadFileResult::TEST_OUTPUT_TRUNCATION_UNSUPPORTED; + } + this->TestPresetOrder.push_back(preset.Name); } diff --git a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx index 4d6474a..43eccfe 100644 --- a/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx +++ b/Source/cmCMakePresetsGraphReadJSONTestPresets.cxx @@ -16,6 +16,8 @@ #include "cmCMakePresetsGraphInternal.h" #include "cmJSONHelpers.h" +#include "CTest/cmCTestTypes.h" + namespace { using ReadFileResult = cmCMakePresetsGraph::ReadFileResult; using TestPreset = cmCMakePresetsGraph::TestPreset; @@ -55,6 +57,40 @@ auto const TestPresetOptionalOutputVerbosityHelper = ReadFileResult>(ReadFileResult::READ_OK, TestPresetOutputVerbosityHelper); +ReadFileResult TestPresetOutputTruncationHelper( + cmCTestTypes::TruncationMode& out, const Json::Value* value) +{ + if (!value) { + out = cmCTestTypes::TruncationMode::Tail; + return ReadFileResult::READ_OK; + } + + if (!value->isString()) { + return ReadFileResult::INVALID_PRESET; + } + + if (value->asString() == "tail") { + out = cmCTestTypes::TruncationMode::Tail; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "middle") { + out = cmCTestTypes::TruncationMode::Middle; + return ReadFileResult::READ_OK; + } + + if (value->asString() == "head") { + out = cmCTestTypes::TruncationMode::Head; + return ReadFileResult::READ_OK; + } + + return ReadFileResult::INVALID_PRESET; +} + +auto const TestPresetOptionalTruncationHelper = + cmJSONOptionalHelper( + ReadFileResult::READ_OK, TestPresetOutputTruncationHelper); + auto const TestPresetOptionalOutputHelper = cmJSONOptionalHelper( ReadFileResult::READ_OK, @@ -83,6 +119,9 @@ auto const TestPresetOptionalOutputHelper = .Bind("maxFailedTestOutputSize"_s, &TestPreset::OutputOptions::MaxFailedTestOutputSize, cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false) + .Bind("testOutputTruncation"_s, + &TestPreset::OutputOptions::TestOutputTruncation, + TestPresetOptionalTruncationHelper, false) .Bind("maxTestNameWidth"_s, &TestPreset::OutputOptions::MaxTestNameWidth, cmCMakePresetsGraphInternal::PresetOptionalIntHelper, false)); diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx index a1e920e..710b4d7 100644 --- a/Source/cmCTest.cxx +++ b/Source/cmCTest.cxx @@ -2036,6 +2036,13 @@ bool cmCTest::HandleCommandLineArguments(size_t& i, "Invalid value for '--test-output-size-failed': " << args[i] << "\n"); } + } else if (this->CheckArgument(arg, "--test-output-truncation"_s) && + i < args.size() - 1) { + i++; + if (!this->Impl->TestHandler.SetTestOutputTruncation(args[i])) { + errormsg = "Invalid value for '--test-output-truncation': " + args[i]; + return false; + } } else if (this->CheckArgument(arg, "-N"_s, "--show-only")) { this->Impl->ShowOnly = true; } else if (cmHasLiteralPrefix(arg, "--show-only=")) { @@ -2464,6 +2471,11 @@ bool cmCTest::SetArgsFromPreset(const std::string& presetName, *expandedPreset->Output->MaxFailedTestOutputSize); } + if (expandedPreset->Output->TestOutputTruncation) { + this->Impl->TestHandler.TestOutputTruncation = + *expandedPreset->Output->TestOutputTruncation; + } + if (expandedPreset->Output->MaxTestNameWidth) { this->Impl->MaxTestNameWidth = *expandedPreset->Output->MaxTestNameWidth; } diff --git a/Source/ctest.cxx b/Source/ctest.cxx index cad27fa..363f473 100644 --- a/Source/ctest.cxx +++ b/Source/ctest.cxx @@ -44,6 +44,9 @@ static const char* cmDocumentationOptions[][2] = { { "--test-output-size-failed ", "Limit the output for failed tests " "to bytes" }, + { "--test-output-truncation ", + "Truncate 'tail' (default), 'middle' or 'head' of test output once " + "maximum output size is reached" }, { "-F", "Enable failover." }, { "-j , --parallel ", "Run the tests in parallel using the " diff --git a/Tests/RunCMake/CMakePresetsTest/Good.json.in b/Tests/RunCMake/CMakePresetsTest/Good.json.in index ea20d9d..d484a19 100644 --- a/Tests/RunCMake/CMakePresetsTest/Good.json.in +++ b/Tests/RunCMake/CMakePresetsTest/Good.json.in @@ -48,7 +48,8 @@ "quiet": false, "outputLogFile": "", "labelSummary": true, - "subprojectSummary": true + "subprojectSummary": true, + "testOutputTruncation": "tail" }, "filter": { "include": { diff --git a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake index 7da95a2..5b198bd 100644 --- a/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake +++ b/Tests/RunCMake/CTestCommandLine/RunCMakeTest.cmake @@ -274,6 +274,27 @@ function(run_TestOutputSize) endfunction() run_TestOutputSize() +# Test --test-output-truncation +function(run_TestOutputTruncation mode expected) + set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/TestOutputTruncation_${mode}) + set(RunCMake_TEST_NO_CLEAN 1) + set(TRUNCATED_OUTPUT ${expected}) # used in TestOutputTruncation-check.cmake + file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}") + file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}") + file(WRITE "${RunCMake_TEST_BINARY_DIR}/CTestTestfile.cmake" " + add_test(Truncation_${mode} \"${CMAKE_COMMAND}\" -E echo 123456789) +") + run_cmake_command(TestOutputTruncation + ${CMAKE_CTEST_COMMAND} -M Experimental -T Test + --no-compress-output + --test-output-size-passed 5 + --test-output-truncation ${mode} + ) +endfunction() +run_TestOutputTruncation("head" "\\.\\.\\.6789") +run_TestOutputTruncation("middle" "12\\.\\.\\..*\\.\\.\\.89") +run_TestOutputTruncation("tail" "12345\.\.\.") + # Test --stop-on-failure function(run_stop_on_failure) set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/stop-on-failure) diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-check.cmake b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-check.cmake new file mode 100644 index 0000000..5769c9f --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-check.cmake @@ -0,0 +1,12 @@ +file(GLOB test_xml_file "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(test_xml_file) + file(READ "${test_xml_file}" test_xml LIMIT 4096) + if("${test_xml}" MATCHES [[(.*)]]) + set(test_result "${CMAKE_MATCH_1}") + endif() + if(NOT "${test_result}" MATCHES ".*${TRUNCATED_OUTPUT}.*") + set(RunCMake_TEST_FAILED "Test output truncation failed:\n ${test_result}\nExpected: ${TRUNCATED_OUTPUT}") + endif() +else() + set(RunCMake_TEST_FAILED "Test.xml not found") +endif() diff --git a/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-stderr.txt b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-stderr.txt new file mode 100644 index 0000000..30b46ce --- /dev/null +++ b/Tests/RunCMake/CTestCommandLine/TestOutputTruncation-stderr.txt @@ -0,0 +1 @@ +^Cannot find file: .*/Tests/RunCMake/CTestCommandLine/TestOutputTruncation.*/DartConfiguration.tcl diff --git a/Tests/RunCMake/ctest_test/RunCMakeTest.cmake b/Tests/RunCMake/ctest_test/RunCMakeTest.cmake index de81049..b41c271 100644 --- a/Tests/RunCMake/ctest_test/RunCMakeTest.cmake +++ b/Tests/RunCMake/ctest_test/RunCMakeTest.cmake @@ -80,6 +80,23 @@ add_test(NAME FailingTest COMMAND ${CMAKE_COMMAND} -E no_such_command) endfunction() run_TestOutputSize() +# Test --test-output-truncation +function(run_TestOutputTruncation mode expected) + set(CASE_CTEST_TEST_ARGS EXCLUDE RunCMakeVersion) + set(TRUNCATED_OUTPUT ${expected}) # used in TestOutputTruncation-check.cmake + set(CASE_TEST_PREFIX_CODE [[ +set( CTEST_CUSTOM_TEST_OUTPUT_TRUNCATION${mode}) + ]]) + set(CASE_CMAKELISTS_SUFFIX_CODE [[ +add_test(NAME Truncation_${mode} COMMAND ${CMAKE_COMMAND} -E echo 123456789) + ]]) + + run_ctest(TestOutputTruncation) +endfunction() +run_TestOutputTruncation("head" "...6789") +run_TestOutputTruncation("middle" "12....*...89") +run_TestOutputTruncation("tail" "12345...") + run_ctest_test(TestRepeatBad1 REPEAT UNKNOWN:3) run_ctest_test(TestRepeatBad2 REPEAT UNTIL_FAIL:-1) diff --git a/Tests/RunCMake/ctest_test/TestOutputTruncation-check.cmake b/Tests/RunCMake/ctest_test/TestOutputTruncation-check.cmake new file mode 100644 index 0000000..5769c9f --- /dev/null +++ b/Tests/RunCMake/ctest_test/TestOutputTruncation-check.cmake @@ -0,0 +1,12 @@ +file(GLOB test_xml_file "${RunCMake_TEST_BINARY_DIR}/Testing/*/Test.xml") +if(test_xml_file) + file(READ "${test_xml_file}" test_xml LIMIT 4096) + if("${test_xml}" MATCHES [[(.*)]]) + set(test_result "${CMAKE_MATCH_1}") + endif() + if(NOT "${test_result}" MATCHES ".*${TRUNCATED_OUTPUT}.*") + set(RunCMake_TEST_FAILED "Test output truncation failed:\n ${test_result}\nExpected: ${TRUNCATED_OUTPUT}") + endif() +else() + set(RunCMake_TEST_FAILED "Test.xml not found") +endif() -- cgit v0.12