diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e643dee09..2987969c0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: '.*\.(pcap|pcapng|dat)|(PacketExamples|PcapExamples|expected_output|pcap_examples).*\.txt' +exclude: '.*\.(pcap|pcapng|dat)|(PacketExamples|PcapExamples|expected_output|pcap_examples).*\.(txt|zst|zstd)' fail_fast: false repos: - repo: local diff --git a/Pcap++/CMakeLists.txt b/Pcap++/CMakeLists.txt index feb4bae47..2f0997792 100644 --- a/Pcap++/CMakeLists.txt +++ b/Pcap++/CMakeLists.txt @@ -102,6 +102,7 @@ target_link_libraries( ) if(LIGHT_PCAPNG_ZSTD) + target_compile_definitions(Pcap++ PRIVATE -DPCPP_PCAPNG_ZSTD_SUPPORT) target_link_libraries(Pcap++ PRIVATE light_pcapng) endif() diff --git a/Pcap++/header/PcapFileDevice.h b/Pcap++/header/PcapFileDevice.h index 61513d560..3160aa9af 100644 --- a/Pcap++/header/PcapFileDevice.h +++ b/Pcap++/header/PcapFileDevice.h @@ -124,7 +124,19 @@ namespace pcpp /// it returns an instance of PcapFileReaderDevice /// @param[in] fileName The file name to open /// @return An instance of the reader to read the file. Notice you should free this instance when done using it + /// @deprecated Prefer `createReader` due to selection of reader based on file content instead of extension. + PCPP_DEPRECATED("Prefer `createReader` due to selection of reader based on file content instead of extension.") static IFileReaderDevice* getReader(const std::string& fileName); + + /// @brief Creates an instance of the reader best fit to read the file. + /// + /// The factory function uses heuristics based on the file content to decide the reader. + /// If the file type is known at compile time, it is better to construct a concrete reader instance directly. + /// + /// @param[in] fileName The path to the file to open. + /// @return A unique pointer to a reader instance or nullptr if the file is not supported. + /// @throws std::runtime_error If the file could not be opened. + static std::unique_ptr createReader(const std::string& fileName); }; /// @class IFileWriterDevice @@ -313,6 +325,10 @@ namespace pcpp PcapNgFileReaderDevice& operator=(const PcapNgFileReaderDevice& other); public: + /// @brief A static method that checks if the device was built with zstd compression support + /// @return True if zstd compression is supported, false otherwise. + static bool isZstdSupported(); + /// A constructor for this class that gets the pcap-ng full path file name to open. Notice that after calling /// this constructor the file isn't opened yet, so reading packets will fail. For opening the file call open() /// @param[in] fileName The full path of the file to read @@ -397,6 +413,10 @@ namespace pcpp PcapNgFileWriterDevice& operator=(const PcapNgFileWriterDevice& other); public: + /// @brief A static method that checks if the device was built with zstd compression support. + /// @return True if zstd compression is supported, false otherwise. + static bool isZstdSupported(); + /// A constructor for this class that gets the pcap-ng full path file name to open for writing or create. Notice /// that after calling this constructor the file isn't opened yet, so writing packets will fail. For opening the /// file call open() diff --git a/Pcap++/src/PcapFileDevice.cpp b/Pcap++/src/PcapFileDevice.cpp index ee7cbd009..03f2d2dbe 100644 --- a/Pcap++/src/PcapFileDevice.cpp +++ b/Pcap++/src/PcapFileDevice.cpp @@ -1,7 +1,10 @@ +#include "PcapFileDevice.h" #define LOG_MODULE PcapLogModuleFileDevice #include #include +#include +#include #include "PcapFileDevice.h" #include "light_pcapng_ext.h" #include "Logger.h" @@ -14,6 +17,27 @@ namespace pcpp { namespace { + bool checkNanoSupport() + { +#ifdef PCAP_TSTAMP_PRECISION_NANO + return true; +#else + PCPP_LOG_DEBUG( + "PcapPlusPlus was compiled without nano precision support which requires libpcap > 1.5.1. Please " + "recompile PcapPlusPlus with nano precision support to use this feature. Using default microsecond precision"); + return false; +#endif + } + + bool checkZstdSupport() + { +#ifdef PCPP_PCAPNG_ZSTD_SUPPORT + return true; +#else + return false; +#endif + } + /// @brief Converts a light_pcapng_t* to an opaque LightPcapNgHandle*. /// @param pcapngHandle The light_pcapng_t* to convert. /// @return An pointer to the opaque handle. @@ -29,43 +53,195 @@ namespace pcpp { return reinterpret_cast(pcapngHandle); } - } // namespace - template constexpr size_t ARRAY_SIZE(T (&)[N]) - { - return N; - } + struct pcap_file_header + { + uint32_t magic; + uint16_t version_major; + uint16_t version_minor; + int32_t thiszone; + uint32_t sigfigs; + uint32_t snaplen; + uint32_t linktype; + }; - struct pcap_file_header - { - uint32_t magic; - uint16_t version_major; - uint16_t version_minor; - int32_t thiszone; - uint32_t sigfigs; - uint32_t snaplen; - uint32_t linktype; - }; + struct packet_header + { + uint32_t tv_sec; + uint32_t tv_usec; + uint32_t caplen; + uint32_t len; + }; - struct packet_header - { - uint32_t tv_sec; - uint32_t tv_usec; - uint32_t caplen; - uint32_t len; - }; + class StreamPositionCheckpoint + { + public: + explicit StreamPositionCheckpoint(std::istream& stream) + : m_Stream(stream), m_State(stream.rdstate()), m_Pos(stream.tellg()) + {} - static bool checkNanoSupport() - { -#if defined(PCAP_TSTAMP_PRECISION_NANO) - return true; -#else - PCPP_LOG_DEBUG( - "PcapPlusPlus was compiled without nano precision support which requires libpcap > 1.5.1. Please " - "recompile PcapPlusPlus with nano precision support to use this feature. Using default microsecond precision"); - return false; -#endif - } + ~StreamPositionCheckpoint() + { + m_Stream.seekg(m_Pos); + m_Stream.clear(m_State); + } + + private: + std::istream& m_Stream; + std::ios_base::iostate m_State; + std::streampos m_Pos; + }; + + enum class CaptureFileFormat + { + Unknown, + Pcap, + PcapNG, + Snoop, + }; + + /// @brief Heuristic file format detector that scans the magic number of the file format header. + class CaptureFileFormatDetector + { + public: + /// @brief Checks a content stream for the magic number and determines the type. + /// @param content A content stream that contains the file content. + /// @return A CaptureFileFormat value with the detected content type. + CaptureFileFormat detectFormat(std::istream& content) + { + // Check if the stream supports seeking. + if (!isStreamSeekable(content)) + { + throw std::runtime_error("Heuristic file format detection requires seekable stream"); + } + + if (isPcapFile(content)) + return CaptureFileFormat::Pcap; + + // PcapNG backend can support ZstdCompressed Pcap files, so we assume an archive is compressed PcapNG. + // If Zstd is not supported, we cannot read the file anyway, so we return Unknown. + if (isPcapNgFile(content) || (checkZstdSupport() && isZstdArchive(content))) + return CaptureFileFormat::PcapNG; + + if (isSnoopFile(content)) + return CaptureFileFormat::Snoop; + + return CaptureFileFormat::Unknown; + } + + private: + /// @brief Check if a stream is seekable. + /// @param stream The stream to check. + /// @return True if the stream supports seek operations, false otherwise. + bool isStreamSeekable(std::istream& stream) + { + auto pos = stream.tellg(); + if (stream.fail()) + { + stream.clear(); + return false; + } + + if (stream.seekg(pos).fail()) + { + stream.clear(); + return false; + } + + return true; + } + + bool isPcapFile(std::istream& content) + { + // Pcap magic numbers are taken from: https://github.com/the-tcpdump-group/libpcap/blob/master/sf-pcap.c + // There are some other reserved magic numbers but they are not supported by libpcap so we ignore them. + constexpr std::array pcapMagicNumbers = { + 0xa1'b2'c3'd4, // regular pcap, microsecond-precision + 0xd4'c3'b2'a1, // regular pcap, microsecond-precision (byte-swapped) + // Libpcap 1.5.0 and later support reading nanosecond-precision pcap files. + 0xa1'b2'3c'4d, // regular pcap, nanosecond-precision + 0x4d'3c'b2'a1, // regular pcap, nanosecond-precision (byte-swapped) + // Libpcap 0.9.1 and later support reading a modified pcap format that contains an extended header. + // Format reference: https://wiki.wireshark.org/Development/LibpcapFileFormat#modified-pcap + 0xa1'b2'cd'34, // Alexey Kuznetzov's modified libpcap format + 0x34'cd'b2'a1 // Alexey Kuznetzov's modified libpcap format (byte-swapped) + }; + + StreamPositionCheckpoint checkpoint(content); + + pcap_file_header header; + content.read(reinterpret_cast(&header), sizeof(header)); + if (content.gcount() != sizeof(header)) + { + return false; + } + + return std::find(pcapMagicNumbers.begin(), pcapMagicNumbers.end(), header.magic) != + pcapMagicNumbers.end(); + } + + bool isPcapNgFile(std::istream& content) + { + constexpr std::array pcapMagicNumbers = { + 0x0A'0D'0D'0A, // pcapng magic number (palindrome) + }; + + StreamPositionCheckpoint checkpoint(content); + + uint32_t magic = 0; + content.read(reinterpret_cast(&magic), sizeof(magic)); + if (content.gcount() != sizeof(magic)) + { + return false; + } + + return std::find(pcapMagicNumbers.begin(), pcapMagicNumbers.end(), magic) != pcapMagicNumbers.end(); + } + + bool isSnoopFile(std::istream& content) + { + constexpr std::array snoopMagicNumbers = { + 0x73'6E'6F'6F'70'00'00'00, // snoop magic number, "snoop" in ASCII + 0x00'00'00'70'6F'6F'6E'73 // snoop magic number, "snoop" in ASCII (byte-swapped) + }; + + StreamPositionCheckpoint checkpoint(content); + + uint64_t magic = 0; + content.read(reinterpret_cast(&magic), sizeof(magic)); + if (content.gcount() != sizeof(magic)) + { + return false; + } + + return std::find(snoopMagicNumbers.begin(), snoopMagicNumbers.end(), magic) != snoopMagicNumbers.end(); + } + + bool isZstdArchive(std::istream& content) + { + constexpr std::array zstdMagicNumbers = { + 0x28'B5'2F'FD, // zstd archive magic number + 0xFD'2F'B5'28, // zstd archive magic number (byte-swapped) + }; + + StreamPositionCheckpoint checkpoint(content); + + uint32_t magic = 0; + content.read(reinterpret_cast(&magic), sizeof(magic)); + if (content.gcount() != sizeof(magic)) + { + return false; + } + + return std::find(zstdMagicNumbers.begin(), zstdMagicNumbers.end(), magic) != zstdMagicNumbers.end(); + } + }; + + template constexpr size_t ARRAY_SIZE(T (&)[N]) + { + return N; + } + } // namespace // ~~~~~~~~~~~~~~~~~~~ // IFileDevice members @@ -131,6 +307,27 @@ namespace pcpp return new PcapFileReaderDevice(fileName); } + std::unique_ptr IFileReaderDevice::createReader(const std::string& fileName) + { + std::ifstream fileContent(fileName, std::ios_base::binary); + if (fileContent.fail()) + { + throw std::runtime_error("Could not open: " + fileName); + } + + switch (CaptureFileFormatDetector().detectFormat(fileContent)) + { + case CaptureFileFormat::Pcap: + return std::make_unique(fileName); + case CaptureFileFormat::PcapNG: + return std::make_unique(fileName); + case CaptureFileFormat::Snoop: + return std::make_unique(fileName); + default: + return nullptr; + } + } + uint64_t IFileReaderDevice::getFileSize() const { std::ifstream fileStream(m_FileName.c_str(), std::ifstream::ate | std::ifstream::binary); @@ -536,6 +733,11 @@ namespace pcpp // PcapNgFileReaderDevice members // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + bool PcapNgFileReaderDevice::isZstdSupported() + { + return checkZstdSupport(); + } + PcapNgFileReaderDevice::PcapNgFileReaderDevice(const std::string& fileName) : IFileReaderDevice(fileName) { m_LightPcapNg = nullptr; @@ -705,6 +907,11 @@ namespace pcpp // PcapNgFileWriterDevice members // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + bool PcapNgFileWriterDevice::isZstdSupported() + { + return checkZstdSupport(); + } + PcapNgFileWriterDevice::PcapNgFileWriterDevice(const std::string& fileName, int compressionLevel) : IFileWriterDevice(fileName) { diff --git a/Tests/Pcap++Test/CMakeLists.txt b/Tests/Pcap++Test/CMakeLists.txt index 1727a4d15..9d1174482 100644 --- a/Tests/Pcap++Test/CMakeLists.txt +++ b/Tests/Pcap++Test/CMakeLists.txt @@ -19,6 +19,14 @@ add_executable( Tests/XdpTests.cpp ) +# Added as source files for IDEs +target_sources(Pcap++Test PRIVATE + Common/GlobalTestArgs.h + Common/PcapFileNamesDef.h + Common/TestUtils.h + TestDefinition.h +) + target_link_libraries(Pcap++Test PUBLIC memplumber Pcap++ PcppTestFramework EndianPortable) if(MSVC) diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/bogus-content.txt b/Tests/Pcap++Test/PcapExamples/file_heuristics/bogus-content.txt new file mode 100644 index 000000000..15a8eef47 --- /dev/null +++ b/Tests/Pcap++Test/PcapExamples/file_heuristics/bogus-content.txt @@ -0,0 +1 @@ +4561d5a474d6as465d8as41d1as6531863d1as65d1a36d1 \ No newline at end of file diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/microsecs.pcap b/Tests/Pcap++Test/PcapExamples/file_heuristics/microsecs.pcap new file mode 100644 index 000000000..20cba6b85 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/microsecs.pcap differ diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/nanosecs.pcap b/Tests/Pcap++Test/PcapExamples/file_heuristics/nanosecs.pcap new file mode 100644 index 000000000..4353706a4 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/nanosecs.pcap differ diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/pcap-with-dat-ext.pcap.dat b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcap-with-dat-ext.pcap.dat new file mode 100644 index 000000000..20cba6b85 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcap-with-dat-ext.pcap.dat differ diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng new file mode 100644 index 000000000..e188daa44 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng differ diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng.zst b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng.zst new file mode 100644 index 000000000..76b195f97 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-example.pcapng.zst differ diff --git a/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-with-pcap-ext.pcapng.pcap b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-with-pcap-ext.pcapng.pcap new file mode 100644 index 000000000..e188daa44 Binary files /dev/null and b/Tests/Pcap++Test/PcapExamples/file_heuristics/pcapng-with-pcap-ext.pcapng.pcap differ diff --git a/Tests/Pcap++Test/TestDefinition.h b/Tests/Pcap++Test/TestDefinition.h index 8e9da6b79..38dbfdd24 100644 --- a/Tests/Pcap++Test/TestDefinition.h +++ b/Tests/Pcap++Test/TestDefinition.h @@ -20,6 +20,12 @@ PTF_TEST_CASE(TestLogger); PTF_TEST_CASE(TestLoggerMultiThread); // Implemented in FileTests.cpp +PTF_TEST_CASE(TestReaderFactory_Pcap_Micro); +PTF_TEST_CASE(TestReaderFactory_Pcap_Nano); +PTF_TEST_CASE(TestReaderFactory_PcapNG); +PTF_TEST_CASE(TestReaderFactory_PcapNG_ZST); +PTF_TEST_CASE(TestReaderFactory_PcapNG_ZST_Unsupported); +PTF_TEST_CASE(TestReaderFactory_InvalidFile); PTF_TEST_CASE(TestPcapFileReadWrite); PTF_TEST_CASE(TestPcapFileMicroPrecision); PTF_TEST_CASE(TestPcapFileNanoPrecision); diff --git a/Tests/Pcap++Test/Tests/FileTests.cpp b/Tests/Pcap++Test/Tests/FileTests.cpp index 2d631d3ef..44f98ed78 100644 --- a/Tests/Pcap++Test/Tests/FileTests.cpp +++ b/Tests/Pcap++Test/Tests/FileTests.cpp @@ -26,6 +26,100 @@ class FileReaderTeardown } }; +PTF_TEST_CASE(TestReaderFactory_Pcap_Micro) +{ + // Correct format + constexpr const char* PCAP_MICROSEC_FILE_PATH = "PcapExamples/file_heuristics/microsecs.pcap"; + // Correct format, wrong extension, microsecond precision + constexpr const char* PCAP_AS_DAT_FILE_PATH = "PcapExamples/file_heuristics/pcap-with-dat-ext.pcap.dat"; + + std::unique_ptr dev; + + for (const auto& filePath : { PCAP_MICROSEC_FILE_PATH, PCAP_AS_DAT_FILE_PATH }) + { + dev = pcpp::IFileReaderDevice::createReader(filePath); + PTF_ASSERT_NOT_NULL(dev); + PTF_ASSERT_NOT_NULL(dynamic_cast(dev.get())); + PTF_ASSERT_TRUE(dev->open()); + } +} + +PTF_TEST_CASE(TestReaderFactory_Pcap_Nano) +{ + if (!pcpp::PcapFileReaderDevice::isNanoSecondPrecisionSupported()) + { + PTF_SKIP_TEST("Nano-second precision is not supported in this platform/environment"); + } + + constexpr const char* PCAP_NANOSEC_FILE_PATH = "PcapExamples/file_heuristics/nanosecs.pcap"; + + auto dev = pcpp::IFileReaderDevice::createReader(PCAP_NANOSEC_FILE_PATH); + PTF_ASSERT_NOT_NULL(dev); + PTF_ASSERT_NOT_NULL(dynamic_cast(dev.get())); + PTF_ASSERT_TRUE(dev->open()); +} + +PTF_TEST_CASE(TestReaderFactory_PcapNG) +{ + // Correct format + constexpr const char* PCAPNG_FILE_PATH = "PcapExamples/file_heuristics/pcapng-example.pcapng"; + // Correct format, wrong extension + constexpr const char* PCAPNG_AS_PCAP_FILE_PATH = "PcapExamples/file_heuristics/pcapng-with-pcap-ext.pcapng.pcap"; + + std::unique_ptr dev; + + for (const auto& filePath : { PCAPNG_FILE_PATH }) + { + dev = pcpp::IFileReaderDevice::createReader(filePath); + PTF_ASSERT_NOT_NULL(dev); + PTF_ASSERT_NOT_NULL(dynamic_cast(dev.get())); + PTF_ASSERT_TRUE(dev->open()); + } + + // Test existent files with correct format but wrong extension + dev = pcpp::IFileReaderDevice::createReader(PCAPNG_AS_PCAP_FILE_PATH); + PTF_ASSERT_NOT_NULL(dev); + PTF_ASSERT_NOT_NULL(dynamic_cast(dev.get())); + PTF_ASSERT_TRUE(dev->open()); +} + +PTF_TEST_CASE(TestReaderFactory_PcapNG_ZST) +{ + if (!pcpp::PcapNgFileReaderDevice::isZstdSupported()) + { + PTF_SKIP_TEST("Zstandard compression is not supported in this platform/environment"); + } + + constexpr const char* PCAPNG_ZST_FILE_PATH = "PcapExamples/file_heuristics/pcapng-example.pcapng.zst"; + + auto dev = pcpp::IFileReaderDevice::createReader(PCAPNG_ZST_FILE_PATH); + PTF_ASSERT_NOT_NULL(dev); + PTF_ASSERT_NOT_NULL(dynamic_cast(dev.get())); + PTF_ASSERT_TRUE(dev->open()); +} + +PTF_TEST_CASE(TestReaderFactory_PcapNG_ZST_Unsupported) +{ + if (pcpp::PcapNgFileReaderDevice::isZstdSupported()) + { + PTF_SKIP_TEST("Zstandard compression is supported in this platform/environment"); + } + + constexpr const char* PCAPNG_ZST_FILE_PATH = "PcapExamples/file_heuristics/pcapng-example.pcapng.zst"; + auto dev = pcpp::IFileReaderDevice::createReader(PCAPNG_ZST_FILE_PATH); + PTF_ASSERT_NULL(dev); +} + +PTF_TEST_CASE(TestReaderFactory_InvalidFile) +{ + // Garbage data + constexpr const char* BOGUS_FILE_PATH = "PcapExamples/file_heuristics/bogus-content.txt"; + + // Test existent file with wrong extension and bogus content + auto dev = pcpp::IFileReaderDevice::createReader(BOGUS_FILE_PATH); + PTF_ASSERT_NULL(dev); +} + PTF_TEST_CASE(TestPcapFileReadWrite) { pcpp::PcapFileReaderDevice readerDev(EXAMPLE_PCAP_PATH); @@ -772,7 +866,7 @@ PTF_TEST_CASE(TestPcapNgFileReadWriteAdv) PTF_ASSERT_EQUAL(packetCount, 161); - // ------- + // ------- IFileReaderDevice::getReader() Factory // copy the .zstd file to a similar file with .zst extension std::ifstream zstdFile(EXAMPLE2_PCAPNG_ZSTD_WRITE_PATH, std::ios::binary); diff --git a/Tests/Pcap++Test/main.cpp b/Tests/Pcap++Test/main.cpp index 25ee1eba7..f1b09ff6f 100644 --- a/Tests/Pcap++Test/main.cpp +++ b/Tests/Pcap++Test/main.cpp @@ -215,6 +215,12 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(TestLogger, "no_network;logger"); PTF_RUN_TEST(TestLoggerMultiThread, "no_network;logger;skip_mem_leak_check"); + PTF_RUN_TEST(TestReaderFactory_Pcap_Micro, "no_network;pcap"); + PTF_RUN_TEST(TestReaderFactory_Pcap_Nano, "no_network;pcap"); + PTF_RUN_TEST(TestReaderFactory_PcapNG, "no_network;pcapng"); + PTF_RUN_TEST(TestReaderFactory_PcapNG_ZST, "no_network;pcapng"); + PTF_RUN_TEST(TestReaderFactory_PcapNG_ZST_Unsupported, "no_network;pcapng"); + PTF_RUN_TEST(TestReaderFactory_InvalidFile, "no_network;pcap"); PTF_RUN_TEST(TestPcapFileReadWrite, "no_network;pcap"); PTF_RUN_TEST(TestPcapFileMicroPrecision, "no_network;pcap"); PTF_RUN_TEST(TestPcapFileNanoPrecision, "no_network;pcap"); diff --git a/Tests/PcppTestFramework/PcppTestFramework.h b/Tests/PcppTestFramework/PcppTestFramework.h index 12defe217..6f62cdd20 100644 --- a/Tests/PcppTestFramework/PcppTestFramework.h +++ b/Tests/PcppTestFramework/PcppTestFramework.h @@ -34,8 +34,8 @@ std::cout << std::endl #define PTF_PRINT_ASSERTION(severity, op) \ - std::cout << std::left << std::setw(35) << __FUNCTION__ << ": " << severity << " (" << __FILE__ << ":" << __LINE__ \ - << "). " \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << __FUNCTION__ << ": " << severity << " (" << __FILE__ \ + << ":" << __LINE__ << "). " \ << "Assert " << op << " failed:" << std::endl #define PTF_PRINT_COMPARE_ASSERTION(severity, op, actualExp, actualVal, expectedExp, expectedVal, objType) \ @@ -274,7 +274,7 @@ #define PTF_PRINT_VERBOSE(data) \ if (printVerbose) \ { \ - std::cout << std::left << std::setw(35) << __FUNCTION__ << ": " \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << __FUNCTION__ << ": " \ << "[VERBOSE] " << data << std::endl; \ } @@ -282,7 +282,7 @@ { \ if (showSkipped) \ { \ - std::cout << std::left << std::setw(35) << __FUNCTION__ << ": " \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << __FUNCTION__ << ": " \ << "SKIPPED (" << why << ")" << std::endl; \ } \ ptfResult = PTF_RESULT_SKIPPED; \ diff --git a/Tests/PcppTestFramework/PcppTestFrameworkCommon.h b/Tests/PcppTestFramework/PcppTestFrameworkCommon.h index e0bad8494..8851aa6f2 100644 --- a/Tests/PcppTestFramework/PcppTestFrameworkCommon.h +++ b/Tests/PcppTestFramework/PcppTestFrameworkCommon.h @@ -3,3 +3,7 @@ #define PTF_RESULT_PASSED 1 #define PTF_RESULT_FAILED 0 #define PTF_RESULT_SKIPPED -1 + +#ifndef PTF_TESTNAME_WIDTH +# define PTF_TESTNAME_WIDTH 45 +#endif // !PTF_TESTNAME_WIDTH diff --git a/Tests/PcppTestFramework/PcppTestFrameworkRun.h b/Tests/PcppTestFramework/PcppTestFrameworkRun.h index bd5cc0d20..e3f5f8244 100644 --- a/Tests/PcppTestFramework/PcppTestFrameworkRun.h +++ b/Tests/PcppTestFramework/PcppTestFrameworkRun.h @@ -62,7 +62,8 @@ static bool __ptfCheckTags(const std::string& tagSet, const std::string& tagSetT { \ if (showSkippedTests) \ { \ - std::cout << std::left << std::setw(35) << #TestName << ": SKIPPED (tags don't match)" << std::endl; \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << #TestName << ": SKIPPED (tags don't match)" \ + << std::endl; \ } \ TestName##_result = PTF_RESULT_SKIPPED; \ } \ @@ -82,7 +83,8 @@ static bool __ptfCheckTags(const std::string& tagSet, const std::string& tagSetT catch (std::exception const& e) \ { \ TestName##_result = PTF_RESULT_FAILED; \ - std::cout << std::left << std::setw(35) << #TestName << ": FAILED. Unhandled exception occurred! " \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << #TestName \ + << ": FAILED. Unhandled exception occurred! " \ << "Exception: " << e.what() << std::endl; \ } \ if (runMemLeakCheck) \ @@ -100,14 +102,15 @@ static bool __ptfCheckTags(const std::string& tagSet, const std::string& tagSetT if (memLeakCount > 0 || memLeakSize > 0) \ { \ TestName##_result = PTF_RESULT_FAILED; \ - std::cout << std::left << std::setw(35) << #TestName << ": FAILED. Memory leak found! " \ - << memLeakCount << " objects and " << memLeakSize << "[bytes] leaked" << std::endl; \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << #TestName \ + << ": FAILED. Memory leak found! " << memLeakCount << " objects and " << memLeakSize \ + << "[bytes] leaked" << std::endl; \ } \ } \ } \ if (TestName##_result == PTF_RESULT_PASSED) \ { \ - std::cout << std::left << std::setw(35) << #TestName << ": PASSED" << std::endl; \ + std::cout << std::left << std::setw(PTF_TESTNAME_WIDTH) << #TestName << ": PASSED" << std::endl; \ } \ } \ if (TestName##_result == PTF_RESULT_PASSED) \