diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/test/gtest | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/base/test/gtest')
-rw-r--r-- | comm/mailnews/base/test/gtest/TestHeaderReader.cpp | 205 | ||||
-rw-r--r-- | comm/mailnews/base/test/gtest/TestLineReader.cpp | 200 | ||||
-rw-r--r-- | comm/mailnews/base/test/gtest/moz.build | 15 |
3 files changed, 420 insertions, 0 deletions
diff --git a/comm/mailnews/base/test/gtest/TestHeaderReader.cpp b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp new file mode 100644 index 0000000000..326279655a --- /dev/null +++ b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp @@ -0,0 +1,205 @@ +#include "gtest/gtest.h" +#include "nsString.h" +#include "HeaderReader.h" +#include "nsTArray.h" +#include "mozilla/ArrayUtils.h" + +// Invocation: +// $ ./mach gtest "TestHeaderReader.*" + +TEST(TestHeaderReader, Basic) +{ + struct hdr { + nsCString name; + nsCString rawValue; + }; + + struct { + nsCString raw; + nsTArray<hdr> expected; + bool isComplete; // expect to find complete header block? + nsCString leftOver; // Data which should be left unprocessed. + } testMsgs[] = { + // Simple case works? + { + "Message-ID: one\r\n" + "To: alice@example.com\r\n" + "\r\n" + "Body here. HeaderReader should have stopped by now.\r\n" + "Note: this line looks like a header, but it isn't!\r\n"_ns, + { + {"Message-ID"_ns, "one"_ns}, + {"To"_ns, "alice@example.com"_ns}, + }, + true, + "Body here. HeaderReader should have stopped by now.\r\n" + "Note: this line looks like a header, but it isn't!\r\n"_ns, + }, + // Handle folded header values correctly? + { + "To: bob@example.com\r\n" + "Subject: Do\r\n" + " we\r\n" + " handle\r\n" + "\tfolded\r\n" // Can fold with tabs too. + " fields OK?\r\n" + "Message-ID: two\r\n" + "\r\n" + "...message body here...\r\n"_ns, + { + {"To"_ns, "bob@example.com"_ns}, + {"Subject"_ns, + "Do\r\n we\r\n handle\r\n\tfolded\r\n fields OK?"_ns}, + {"Message-ID"_ns, "two"_ns}, + }, + true, + "...message body here...\r\n"_ns, + }, + // Handle no whitespace after colon? + { + "Foo:bar\r\n" + "\r\n"_ns, + { + {"Foo"_ns, "bar"_ns}, + }, + true, + ""_ns, + }, + // Folding with no text on first line? + // (I _think_ this is legal...) + { + "Foo: \r\n" + " bar\r\n" + "\r\n"_ns, + { + {"Foo"_ns, "\r\n bar"_ns}, + }, + true, + ""_ns, + }, + // Folded line with no end of header block. + // Input could be truncated. So we don't want this to output any headers + // (The missing next line could be folded or not - we just don't know). + { + "Foo:\r\n" + " bar\r\n" + " wibble\r\n"_ns, + {}, + false, + "Foo:\r\n bar\r\n wibble\r\n"_ns, + }, + // Ignore incomplete lines as expected?. + { + "Foo: bar\r\n" + "Wibble: this is a part"_ns, // ... "ial line". + { + {"Foo"_ns, "bar"_ns}, + }, + false, + "Wibble: this is a part"_ns, + }, + // Ignore incomplete folded lines? + { + "Foo: bar\r\n" + "Wibble: this\r\n" + " value is not co"_ns, // ... "mplete". + { + {"Foo"_ns, "bar"_ns}, + }, + false, + "Wibble: this\r\n value is not co"_ns, + }, + // Handle empty input without crashing? + { + ""_ns, + {}, + false, + ""_ns, + }}; + + for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) { + auto const& t = testMsgs[i]; + // Collect all the headers. + nsTArray<HeaderReader::Hdr> gotHeaders; + HeaderReader rdr; + auto handler = [&](HeaderReader::Hdr const& hdr) { + gotHeaders.AppendElement(hdr); + return true; // Keep going. + }; + + // Simulate multiple passes over a growing buffer - we'll add a quarter + // of the data each pass. + for (uint32_t i = 1; i < 4; ++i) { + // Encourage each pass to use different memory. + nsCString fudge(t.raw.BeginReading(), i * t.raw.Length() / 4); + rdr.Parse(fudge, handler); + } + + // Last pass - give the reader the entire input. + auto leftOver = rdr.Parse(t.raw, handler); + + // Did we get all the headers we expected? + ASSERT_EQ(gotHeaders.Length(), t.expected.Length()); + for (size_t i = 0; i < t.expected.Length(); ++i) { + auto const& expect = t.expected[i]; + auto const& got = gotHeaders[i]; + ASSERT_EQ(expect.name, nsCString(got.Name(t.raw))); + ASSERT_EQ(expect.rawValue, nsCString(got.RawValue(t.raw))); + } + + // Correctly detected the end of the header block? + ASSERT_EQ(t.isComplete, rdr.IsComplete()); + + // Make sure processing stopped where expected. + ASSERT_EQ(t.leftOver, nsCString(leftOver)); + } +} + +// Check that callback can halt processing, and that it can be correctly +// resumed. +TEST(TestHeaderReader, Stop) +{ + // We'll stop each time we find a header name containing "STOP". + struct { + nsCString raw; + int expectedCount; // Number of headers we expect to find. + int expectedPasses; // Number of passes we expect to run. + } testMsgs[] = { + {"foo: bar\r\n\r\n"_ns, 1, 1}, + {"Message-ID: one\r\n" + "STOP: pause here please\r\n" + "foo-One: nothing to see here...\r\n" + "foo-Two: still nothing...\r\n" + "\r\n"_ns, + 4, 2}, + {"A: eh\r\n" + "B: bee\r\n" + "STOP_1: pause here.\r\n" + "C: sea\r\n" + "STOP_2: another pause.\r\n" + "E: eee\r\n" + "STOP_3: last one.\r\n" + "\r\n"_ns, + 7, 3}, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) { + auto const& t = testMsgs[i]; + HeaderReader rdr; + int gotCount = 0; + auto handler = [&](HeaderReader::Hdr const& hdr) { + ++gotCount; + return nsCString(hdr.Name(t.raw)).Find("STOP") == kNotFound; + }; + + int passes = 0; + // All our examples have a complete header block. + while (!rdr.IsComplete()) { + rdr.Parse(t.raw, handler); + ++passes; + } + ASSERT_EQ(gotCount, t.expectedCount); + ASSERT_EQ(passes, t.expectedPasses); + ASSERT_TRUE(rdr.IsComplete()); + } +} diff --git a/comm/mailnews/base/test/gtest/TestLineReader.cpp b/comm/mailnews/base/test/gtest/TestLineReader.cpp new file mode 100644 index 0000000000..e84274fcb0 --- /dev/null +++ b/comm/mailnews/base/test/gtest/TestLineReader.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "nsString.h" +#include "LineReader.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Span.h" + +// Invocation: +// $ ./mach gtest "TestLineReader.*" + +TEST(TestLineReader, Basic) +{ + struct { + nsTArray<nsCString> chunks; + int expectedLines; + } testCases[] = { + // Split lines as expected? + { + {"Line one\r\nLine two\r\n"_ns}, + 2, + }, + + // Plain LFs should be accepted. + { + {"Line one\nLine two\n"_ns}, + 2, + }, + + // Empty lines appear as expected? + { + {"\r\n\r\n\r\n"_ns}, + 3, + }, + + // Empty case handled? + { + {""_ns}, + 0, + }, + + // Split in mid-CRLF. + { + { + "EOL split across\r"_ns, + "\nchunks.\r\n"_ns, + }, + 2, + }, + + // Chunks join up correctly? + { + {"This single line is "_ns, "fed in as "_ns, + "multiple chunks\r\n"_ns}, + 1, + }, + // Handle an empty chunk OK? + { + {"foo"_ns, ""_ns, + "bar\n"_ns + "wibble\n"_ns}, + 2, + }, + // Handle lines without EOL? + { + {"This line has no EOL and relies on Flush()."_ns}, + 1, + }, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) { + auto const& t = testCases[i]; + + // Join chunks into one string - we expect the output to be + // this, byte-for-byte. + nsCString expectedText; + for (auto chunk : t.chunks) { + expectedText.Append(chunk); + } + + // Callback to collect the lines and count them. + int gotLineCount = 0; + nsCString gotText; + auto callback = [&](mozilla::Span<const char> line) { + ++gotLineCount; + gotText.Append(line); + return true; + }; + // Parse the chunks. + LineReader chopper; + for (auto chunk : t.chunks) { + chopper.Feed(chunk, callback); + } + chopper.Flush(callback); + + ASSERT_EQ(t.expectedLines, gotLineCount); + ASSERT_EQ(expectedText, gotText); + } +} + +// Check that processing is aborted when callback returns false. +TEST(TestLineReader, Stop) +{ + struct { + nsTArray<nsCString> chunks; + int expectedLines; + } testCases[] = { + // Stop at 2 lines. + { + {"Line one\nSTOP\nThis line is never seen.\n"_ns}, + 2, + }, + + // Line split up over multiple chunks. + { + {"one\r\nST"_ns, "OP\r\nblah blah\r\n"_ns}, + 2, + }, + + // Empty string -> no lines. + { + {""_ns}, + 0, + }, + + // No EOL, relies on Flush(). + { + { + "STOP"_ns, + }, + 1, + }, + }; + + for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) { + auto const& t = testCases[i]; + + // Callback to collect the lines and count them, stopping when + // we find a line containing "STOP". + int gotLineCount = 0; + auto callback = [&](mozilla::Span<const char> line) -> bool { + ++gotLineCount; + return nsCString(line).Find("STOP"_ns) == kNotFound; + }; + // Parse. + LineReader chopper; + for (auto chunk : t.chunks) { + chopper.Feed(chunk, callback); + } + chopper.Flush(callback); + ASSERT_EQ(t.expectedLines, gotLineCount); + } +} + +// Test the SplitLines() fn. +TEST(TestLineReader, SplitLines) +{ + struct { + nsCString input; + nsTArray<nsCString> expect; // The lines we expect to see. + nsCString expectLeftover; // The unconsumed data we expect at the end. + } testCases[] = { + // Empty string -> no lines. + {""_ns, {}, ""_ns}, + // Incomplete line + {"foo"_ns, {}, "foo"_ns}, + // Blank lines split as expected? + {"\r\n\r\n\r\n"_ns, {"\r\n"_ns, "\r\n"_ns, "\r\n"_ns}, ""_ns}, + // A couple of normal-looking lines. + {"one\r\ntwo\r\n"_ns, {"one\r\n"_ns, "two\r\n"_ns}, ""_ns}, + // Handles bare LFs? + {"one\ntwo\n"_ns, {"one\n"_ns, "two\n"_ns}, ""_ns}, + // Ignores bare CRs? + {"one\rtwo\r"_ns, {}, "one\rtwo\r"_ns}, + + // Early-out works? + {"one\r\nSTOP\r\n3\r\n4\r\n"_ns, + {"one\r\n"_ns, "STOP\r\n"_ns}, + "3\r\n4\r\n"_ns}, + }; + + for (auto const& t : testCases) { + nsTArray<nsCString> got; + auto fn = [&](mozilla::Span<const char> line) -> bool { + got.AppendElement(line); + // Finish early if line contains "STOP". + return nsCString(line).Find("STOP"_ns) == kNotFound; + }; + mozilla::Span<const char> leftover = SplitLines(t.input, fn); + + ASSERT_EQ(t.expect.Length(), got.Length()); + for (size_t i = 0; i < t.expect.Length(); ++i) { + ASSERT_EQ(t.expect[i], got[i]); + } + ASSERT_EQ(t.expectLeftover, nsCString(leftover)); + } +} diff --git a/comm/mailnews/base/test/gtest/moz.build b/comm/mailnews/base/test/gtest/moz.build new file mode 100644 index 0000000000..a0b6129efd --- /dev/null +++ b/comm/mailnews/base/test/gtest/moz.build @@ -0,0 +1,15 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "xul-gtest" + +UNIFIED_SOURCES += [ + "TestHeaderReader.cpp", + "TestLineReader.cpp", +] + +# LOCAL_INCLUDES += [ +# "../../src", +# ] |