diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /image/test/gtest/TestStreamingLexer.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream/115.8.0esr.tar.xz firefox-esr-upstream/115.8.0esr.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/test/gtest/TestStreamingLexer.cpp')
-rw-r--r-- | image/test/gtest/TestStreamingLexer.cpp | 935 |
1 files changed, 935 insertions, 0 deletions
diff --git a/image/test/gtest/TestStreamingLexer.cpp b/image/test/gtest/TestStreamingLexer.cpp new file mode 100644 index 0000000000..c83569a7b9 --- /dev/null +++ b/image/test/gtest/TestStreamingLexer.cpp @@ -0,0 +1,935 @@ +/* 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 "Common.h" +#include "mozilla/Vector.h" +#include "StreamingLexer.h" + +using namespace mozilla; +using namespace mozilla::image; + +enum class TestState { + ONE, + TWO, + THREE, + UNBUFFERED, + TRUNCATED_SUCCESS, + TRUNCATED_FAILURE +}; + +void CheckLexedData(const char* aData, size_t aLength, size_t aOffset, + size_t aExpectedLength) { + EXPECT_TRUE(aLength == aExpectedLength); + + for (size_t i = 0; i < aLength; ++i) { + EXPECT_EQ(aData[i], char(aOffset + i + 1)); + } +} + +LexerTransition<TestState> DoLex(TestState aState, const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::To(TestState::TWO, 3); + case TestState::TWO: + CheckLexedData(aData, aLength, 3, 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::TRUNCATED_SUCCESS: + return Transition::TerminateSuccess(); + case TestState::TRUNCATED_FAILURE: + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithUnbuffered( + TestState aState, const char* aData, size_t aLength, + Vector<char>& aUnbufferedVector) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::TWO: + CheckLexedData(aUnbufferedVector.begin(), aUnbufferedVector.length(), 3, + 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + EXPECT_TRUE(aLength <= 3); + EXPECT_TRUE(aUnbufferedVector.append(aData, aLength)); + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithUnbufferedTerminate(TestState aState, + const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3); + case TestState::UNBUFFERED: + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithYield(TestState aState, const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + CheckLexedData(aData, aLength, 0, 3); + return Transition::To(TestState::THREE, 6); + case TestState::THREE: + CheckLexedData(aData, aLength, 3, 6); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithTerminateAfterYield(TestState aState, + const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithZeroLengthStates(TestState aState, + const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::TWO, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithZeroLengthStatesAtEnd(TestState aState, + const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::To(TestState::TWO, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 0); + case TestState::THREE: + EXPECT_TRUE(aLength == 0); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithZeroLengthYield(TestState aState, + const char* aData, + size_t aLength) { + switch (aState) { + case TestState::ONE: + EXPECT_EQ(0u, aLength); + return Transition::ToAfterYield(TestState::TWO); + case TestState::TWO: + EXPECT_EQ(0u, aLength); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithZeroLengthStatesUnbuffered( + TestState aState, const char* aData, size_t aLength) { + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 0); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 9); + case TestState::THREE: + CheckLexedData(aData, aLength, 0, 9); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + ADD_FAILURE() << "Should not enter zero-length unbuffered state"; + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +LexerTransition<TestState> DoLexWithZeroLengthStatesAfterUnbuffered( + TestState aState, const char* aData, size_t aLength) { + switch (aState) { + case TestState::ONE: + EXPECT_TRUE(aLength == 0); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 9); + case TestState::TWO: + EXPECT_TRUE(aLength == 0); + return Transition::To(TestState::THREE, 0); + case TestState::THREE: + EXPECT_TRUE(aLength == 0); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + CheckLexedData(aData, aLength, 0, 9); + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } +} + +class ImageStreamingLexer : public ::testing::Test { + public: + // Note that mLexer is configured to enter TerminalState::FAILURE immediately + // if the input data is truncated. We don't expect that to happen in most + // tests, so we want to detect that issue. If a test needs a different + // behavior, we create a special StreamingLexer just for that test. + ImageStreamingLexer() + : mLexer(Transition::To(TestState::ONE, 3), + Transition::TerminateFailure()), + mSourceBuffer(new SourceBuffer), + mIterator(mSourceBuffer->Iterator()), + mExpectNoResume(new ExpectNoResume), + mCountResumes(new CountResumes) {} + + protected: + void CheckTruncatedState(StreamingLexer<TestState>& aLexer, + TerminalState aExpectedTerminalState, + nsresult aCompletionStatus = NS_OK) { + for (unsigned i = 0; i < 9; ++i) { + if (i < 2) { + mSourceBuffer->Append(mData + i, 1); + } else if (i == 2) { + mSourceBuffer->Complete(aCompletionStatus); + } + + LexerResult result = aLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i >= 2) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(aExpectedTerminalState, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + } + + AutoInitializeImageLib mInit; + const char mData[9]{1, 2, 3, 4, 5, 6, 7, 8, 9}; + StreamingLexer<TestState> mLexer; + RefPtr<SourceBuffer> mSourceBuffer; + SourceBufferIterator mIterator; + RefPtr<ExpectNoResume> mExpectNoResume; + RefPtr<CountResumes> mCountResumes; +}; + +TEST_F(ImageStreamingLexer, ZeroLengthData) { + // Test a zero-length input. + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthDataUnbuffered) { + // Test a zero-length input. + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be unbuffered. + StreamingLexer<TestState> lexer( + Transition::ToUnbuffered(TestState::ONE, TestState::UNBUFFERED, + sizeof(mData)), + Transition::TerminateFailure()); + + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, StartWithTerminal) { + // Create a special StreamingLexer for this test because we want the first + // state to be a terminal state. This doesn't really make sense, but we should + // handle it. + StreamingLexer<TestState> lexer(Transition::TerminateSuccess(), + Transition::TerminateFailure()); + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, DoLex); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, SingleChunk) { + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, SingleChunkWithUnbuffered) { + Vector<char> unbufferedVector; + + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex( + mIterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, SingleChunkWithYield) { + // Test delivering all the data at once. + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + + result = mLexer.Lex(mIterator, mExpectNoResume, DoLexWithYield); + ASSERT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ChunkPerState) { + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i == 2) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbuffered) { + Vector<char> unbufferedVector; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = mLexer.Lex( + mIterator, mCountResumes, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 2) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithYield) { + // Test delivering in perfectly-sized chunks, one per state. + mSourceBuffer->Append(mData, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + + mSourceBuffer->Append(mData + 3, 6); + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + + EXPECT_EQ(1u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ChunkPerStateWithUnbufferedYield) { + size_t unbufferedCallCount = 0; + Vector<char> unbufferedVector; + auto lexerFunc = [&](TestState aState, const char* aData, + size_t aLength) -> LexerTransition<TestState> { + switch (aState) { + case TestState::ONE: + CheckLexedData(aData, aLength, 0, 3); + return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, + 3); + case TestState::TWO: + CheckLexedData(unbufferedVector.begin(), unbufferedVector.length(), 3, + 3); + return Transition::To(TestState::THREE, 3); + case TestState::THREE: + CheckLexedData(aData, aLength, 6, 3); + return Transition::TerminateSuccess(); + case TestState::UNBUFFERED: + switch (unbufferedCallCount) { + case 0: + CheckLexedData(aData, aLength, 3, 3); + EXPECT_TRUE(unbufferedVector.append(aData, 2)); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 2 bytes. + return Transition::ContinueUnbufferedAfterYield( + TestState::UNBUFFERED, 2); + + case 1: + CheckLexedData(aData, aLength, 5, 1); + EXPECT_TRUE(unbufferedVector.append(aData, 1)); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 1 byte. + // We should end up in the TWO state. + return Transition::ContinueUnbuffered(TestState::UNBUFFERED); + } + ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED"; + return Transition::TerminateFailure(); + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } + }; + + // Test delivering in perfectly-sized chunks, one per state. + for (unsigned i = 0; i < 3; ++i) { + mSourceBuffer->Append(mData + 3 * i, 3); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + + switch (i) { + case 0: + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + EXPECT_EQ(0u, unbufferedCallCount); + break; + + case 1: + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + EXPECT_EQ(1u, unbufferedCallCount); + + result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + EXPECT_EQ(2u, unbufferedCallCount); + break; + + case 2: + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + break; + } + } + + EXPECT_EQ(2u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mCountResumes, lexerFunc); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, OneByteChunks) { + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLex); + + if (i == 8) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, OneByteChunksWithUnbuffered) { + Vector<char> unbufferedVector; + + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex( + mIterator, mCountResumes, + [&](TestState aState, const char* aData, size_t aLength) { + return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector); + }); + + if (i == 8) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, OneByteChunksWithYield) { + // Test delivering in one byte chunks. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + + switch (i) { + case 2: + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + + result = mLexer.Lex(mIterator, mCountResumes, DoLexWithYield); + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + break; + + case 8: + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + break; + + default: + EXPECT_TRUE(i < 9); + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + EXPECT_EQ(8u, mCountResumes->Count()); + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, ZeroLengthState) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStates); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStatesAtEnd) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to consume the full input. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 9), + Transition::TerminateFailure()); + + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthStatesAtEnd); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithYield) { + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + mSourceBuffer->Append(mData, 3); + LexerResult result = + lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + + result = lexer.Lex(mIterator, mCountResumes, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + + mSourceBuffer->Append(mData + 3, sizeof(mData) - 3); + mSourceBuffer->Complete(NS_OK); + result = lexer.Lex(mIterator, mExpectNoResume, DoLexWithZeroLengthYield); + ASSERT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + EXPECT_EQ(1u, mCountResumes->Count()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbuffered) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be both zero length and unbuffered. + StreamingLexer<TestState> lexer( + Transition::ToUnbuffered(TestState::ONE, TestState::UNBUFFERED, 0), + Transition::TerminateFailure()); + + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, + DoLexWithZeroLengthStatesUnbuffered); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateAfterUnbuffered) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Create a special StreamingLexer for this test because we want the first + // state to be zero length. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 0), + Transition::TerminateFailure()); + + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, + DoLexWithZeroLengthStatesAfterUnbuffered); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, ZeroLengthStateWithUnbufferedYield) { + size_t unbufferedCallCount = 0; + auto lexerFunc = [&](TestState aState, const char* aData, + size_t aLength) -> LexerTransition<TestState> { + switch (aState) { + case TestState::ONE: + EXPECT_EQ(0u, aLength); + return Transition::TerminateSuccess(); + + case TestState::UNBUFFERED: + switch (unbufferedCallCount) { + case 0: + CheckLexedData(aData, aLength, 0, 3); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 0 bytes. + return Transition::ContinueUnbufferedAfterYield( + TestState::UNBUFFERED, 0); + + case 1: + CheckLexedData(aData, aLength, 0, 3); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 2 bytes. + return Transition::ContinueUnbufferedAfterYield( + TestState::UNBUFFERED, 2); + + case 2: + EXPECT_EQ(1u, aLength); + CheckLexedData(aData, aLength, 2, 1); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 1 bytes. + return Transition::ContinueUnbufferedAfterYield( + TestState::UNBUFFERED, 1); + + case 3: + CheckLexedData(aData, aLength, 3, 6); + unbufferedCallCount++; + + // Continue after yield, telling StreamingLexer we consumed 6 bytes. + // We should transition to TestState::ONE when we return from the + // yield. + return Transition::ContinueUnbufferedAfterYield( + TestState::UNBUFFERED, 6); + } + + ADD_FAILURE() << "Too many invocations of TestState::UNBUFFERED"; + return Transition::TerminateFailure(); + + default: + MOZ_CRASH("Unexpected or unhandled TestState"); + } + }; + + // Create a special StreamingLexer for this test because we want the first + // state to be unbuffered. + StreamingLexer<TestState> lexer( + Transition::ToUnbuffered(TestState::ONE, TestState::UNBUFFERED, + sizeof(mData)), + Transition::TerminateFailure()); + + mSourceBuffer->Append(mData, 3); + LexerResult result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + EXPECT_EQ(1u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + EXPECT_EQ(2u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + EXPECT_EQ(3u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mCountResumes, lexerFunc); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + EXPECT_EQ(3u, unbufferedCallCount); + + mSourceBuffer->Append(mData + 3, 6); + mSourceBuffer->Complete(NS_OK); + EXPECT_EQ(1u, mCountResumes->Count()); + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + EXPECT_EQ(4u, unbufferedCallCount); + + result = lexer.Lex(mIterator, mExpectNoResume, lexerFunc); + ASSERT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, TerminateSuccess) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Test that Terminate is "sticky". + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + LexerResult result = + mLexer.Lex(iterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::TerminateSuccess(); + }); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + + SourceBufferIterator iterator2 = mSourceBuffer->Iterator(); + result = mLexer.Lex(iterator2, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, TerminateFailure) { + mSourceBuffer->Append(mData, sizeof(mData)); + mSourceBuffer->Complete(NS_OK); + + // Test that Terminate is "sticky". + SourceBufferIterator iterator = mSourceBuffer->Iterator(); + LexerResult result = + mLexer.Lex(iterator, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(aState == TestState::ONE); + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>()); + + SourceBufferIterator iterator2 = mSourceBuffer->Iterator(); + result = mLexer.Lex(iterator2, mExpectNoResume, + [&](TestState aState, const char* aData, size_t aLength) { + EXPECT_TRUE(false); // Shouldn't get here. + return Transition::TerminateFailure(); + }); + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, TerminateUnbuffered) { + // Test that Terminate works during an unbuffered read. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, DoLexWithUnbufferedTerminate); + + if (i > 2) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + // We expect 3 resumes because TestState::ONE consumes 3 bytes and then + // transitions to TestState::UNBUFFERED, which calls TerminateSuccess() as + // soon as it receives a single byte. That's four bytes total, which are + // delivered one at a time, requiring 3 resumes. + EXPECT_EQ(3u, mCountResumes->Count()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, TerminateAfterYield) { + // Test that Terminate works after yielding. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = + mLexer.Lex(mIterator, mCountResumes, DoLexWithTerminateAfterYield); + + if (i > 2) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else if (i == 2) { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::OUTPUT_AVAILABLE, result.as<Yield>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + // We expect 2 resumes because TestState::ONE consumes 3 bytes and then + // yields. When the lexer resumes at TestState::TWO, which receives the same 3 + // bytes, TerminateSuccess() gets called immediately. That's three bytes + // total, which are delivered one at a time, requiring 2 resumes. + EXPECT_EQ(2u, mCountResumes->Count()); + + mSourceBuffer->Complete(NS_OK); +} + +TEST_F(ImageStreamingLexer, SourceBufferImmediateComplete) { + // Test calling SourceBuffer::Complete() without appending any data. This + // causes the SourceBuffer to automatically have a failing completion status, + // no matter what you pass, so we expect TerminalState::FAILURE below. + mSourceBuffer->Complete(NS_OK); + + LexerResult result = mLexer.Lex(mIterator, mExpectNoResume, DoLex); + + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::FAILURE, result.as<TerminalState>()); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateSuccess) { + // Test that using a terminal state (in this case TerminalState::SUCCESS) as a + // truncated state works. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateSuccess()); + + CheckTruncatedState(lexer, TerminalState::SUCCESS); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedTerminalStateFailure) { + // Test that using a terminal state (in this case TerminalState::FAILURE) as a + // truncated state works. + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateFailure()); + + CheckTruncatedState(lexer, TerminalState::FAILURE); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningSuccess) { + // Test that a truncated state that returns TerminalState::SUCCESS works. When + // |lexer| discovers that the data is truncated, it invokes the + // TRUNCATED_SUCCESS state, which returns TerminalState::SUCCESS. + // CheckTruncatedState() verifies that this happens. + StreamingLexer<TestState> lexer( + Transition::To(TestState::ONE, 3), + Transition::To(TestState::TRUNCATED_SUCCESS, 0)); + + CheckTruncatedState(lexer, TerminalState::SUCCESS); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedStateReturningFailure) { + // Test that a truncated state that returns TerminalState::FAILURE works. When + // |lexer| discovers that the data is truncated, it invokes the + // TRUNCATED_FAILURE state, which returns TerminalState::FAILURE. + // CheckTruncatedState() verifies that this happens. + StreamingLexer<TestState> lexer( + Transition::To(TestState::ONE, 3), + Transition::To(TestState::TRUNCATED_FAILURE, 0)); + + CheckTruncatedState(lexer, TerminalState::FAILURE); +} + +TEST_F(ImageStreamingLexer, SourceBufferTruncatedFailingCompleteStatus) { + // Test that calling SourceBuffer::Complete() with a failing status results in + // an immediate TerminalState::FAILURE result. (Note that |lexer|'s truncated + // state is TerminalState::SUCCESS, so if we ignore the failing status, the + // test will fail.) + StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3), + Transition::TerminateSuccess()); + + CheckTruncatedState(lexer, TerminalState::FAILURE, NS_ERROR_FAILURE); +} + +TEST_F(ImageStreamingLexer, NoSourceBufferResumable) { + // Test delivering in one byte chunks with no IResumable. + for (unsigned i = 0; i < 9; ++i) { + mSourceBuffer->Append(mData + i, 1); + LexerResult result = mLexer.Lex(mIterator, nullptr, DoLex); + + if (i == 8) { + EXPECT_TRUE(result.is<TerminalState>()); + EXPECT_EQ(TerminalState::SUCCESS, result.as<TerminalState>()); + } else { + EXPECT_TRUE(result.is<Yield>()); + EXPECT_EQ(Yield::NEED_MORE_DATA, result.as<Yield>()); + } + } + + mSourceBuffer->Complete(NS_OK); +} |