/* 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);
}
