Bug 1196065 - Add sanity tests for image decoders. r=tn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Wed, 19 Aug 2015 14:04:01 -0700
changeset 292853 8551bc98b4be9b4e518be87035fe90aa744eed67
parent 292852 0f5421c28b14b9ee5cc62ed66e3994b34205b2d4
child 292854 312447440b3d1b42dcd980d2badaa6dfdcc7d3fa
push id962
push userjlund@mozilla.com
push dateFri, 04 Dec 2015 23:28:54 +0000
treeherdermozilla-release@23a2d286e80f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1196065
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1196065 - Add sanity tests for image decoders. r=tn
image/Decoder.cpp
image/Decoder.h
image/SourceBuffer.cpp
image/test/gtest/TestDecoders.cpp
image/test/gtest/moz.build
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -85,25 +85,28 @@ Decoder::Init()
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
 nsresult
-Decoder::Decode()
+Decoder::Decode(IResumable* aOnResume)
 {
   MOZ_ASSERT(mInitialized, "Should be initialized here");
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
+  // If no IResumable was provided, default to |this|.
+  IResumable* onResume = aOnResume ? aOnResume : this;
+
   // We keep decoding chunks until the decode completes or there are no more
   // chunks available.
   while (!GetDecodeDone() && !HasError()) {
-    auto newState = mIterator->AdvanceOrScheduleResume(this);
+    auto newState = mIterator->AdvanceOrScheduleResume(onResume);
 
     if (newState == SourceBufferIterator::WAITING) {
       // We can't continue because the rest of the data hasn't arrived from the
       // network yet. We don't have to do anything special; the
       // SourceBufferIterator will ensure that Decode() gets called again on a
       // DecodePool thread when more data is available.
       return NS_OK;
     }
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -31,23 +31,26 @@ public:
   explicit Decoder(RasterImage* aImage);
 
   /**
    * Initialize an image decoder. Decoders may not be re-initialized.
    */
   void Init();
 
   /**
-   * Decodes, reading all data currently available in the SourceBuffer. If more
-   * data is needed, Decode() automatically ensures that it will be called again
-   * on a DecodePool thread when the data becomes available.
+   * Decodes, reading all data currently available in the SourceBuffer.
+   *
+   * If more data is needed, Decode() will schedule @aOnResume to be called when
+   * more data is available. If @aOnResume is null or unspecified, the default
+   * implementation resumes decoding on a DecodePool thread. Most callers should
+   * use the default implementation.
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    */
-  nsresult Decode();
+  nsresult Decode(IResumable* aOnResume = nullptr);
 
   /**
    * Given a maximum number of bytes we're willing to decode, @aByteLimit,
    * returns true if we should attempt to run this decoder synchronously.
    */
   bool ShouldSyncDecode(size_t aByteLimit);
 
   /**
--- a/image/SourceBuffer.cpp
+++ b/image/SourceBuffer.cpp
@@ -208,20 +208,16 @@ SourceBuffer::FibonacciCapacityWithMinim
 
 void
 SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
 {
   mMutex.AssertCurrentThreadOwns();
 
   MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
 
-  if (MOZ_UNLIKELY(NS_IsMainThread())) {
-    NS_WARNING("SourceBuffer consumer on the main thread needed to wait");
-  }
-
   mWaitingConsumers.AppendElement(aConsumer);
 }
 
 void
 SourceBuffer::ResumeWaitingConsumers()
 {
   mMutex.AssertCurrentThreadOwns();
 
new file mode 100644
--- /dev/null
+++ b/image/test/gtest/TestDecoders.cpp
@@ -0,0 +1,249 @@
+/* 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 "Decoder.h"
+#include "DecoderFactory.h"
+#include "decoders/nsBMPDecoder.h"
+#include "imgIContainer.h"
+#include "imgITools.h"
+#include "ImageFactory.h"
+#include "mozilla/gfx/2D.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIRunnable.h"
+#include "nsIThread.h"
+#include "mozilla/nsRefPtr.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "ProgressTracker.h"
+#include "SourceBuffer.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+TEST(ImageDecoders, ImageModuleAvailable)
+{
+  // We can run into problems if XPCOM modules get initialized in the wrong
+  // order. It's important that this test run first, both as a sanity check and
+  // to ensure we get the module initialization order we want.
+  nsCOMPtr<imgITools> imgTools =
+    do_CreateInstance("@mozilla.org/image/tools;1");
+  EXPECT_TRUE(imgTools != nullptr);
+}
+
+static void
+CheckDecoderResults(const ImageTestCase& aTestCase, Decoder* aDecoder)
+{
+  EXPECT_TRUE(aDecoder->GetDecodeDone());
+  EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
+            aDecoder->HasError());
+  EXPECT_TRUE(!aDecoder->WasAborted());
+
+  // Verify that the decoder made the expected progress.
+  Progress progress = aDecoder->TakeProgress();
+  EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_HAS_ERROR),
+            bool(progress & FLAG_HAS_ERROR));
+
+  if (aTestCase.mFlags & TEST_CASE_HAS_ERROR) {
+    return;  // That's all we can check for bad images.
+  }
+
+  EXPECT_TRUE(bool(progress & FLAG_SIZE_AVAILABLE));
+  EXPECT_TRUE(bool(progress & FLAG_DECODE_COMPLETE));
+  EXPECT_TRUE(bool(progress & FLAG_FRAME_COMPLETE));
+  EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_TRANSPARENT),
+            bool(progress & FLAG_HAS_TRANSPARENCY));
+  EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
+            bool(progress & FLAG_IS_ANIMATED));
+
+  // The decoder should get the correct size.
+  IntSize size = aDecoder->GetSize();
+  EXPECT_EQ(aTestCase.mSize.width, size.width);
+  EXPECT_EQ(aTestCase.mSize.height, size.height);
+
+  // Get the current frame, which is always the first frame of the image
+  // because CreateAnonymousDecoder() forces a first-frame-only decode.
+  RawAccessFrameRef currentFrame = aDecoder->GetCurrentFrameRef();
+  nsRefPtr<SourceSurface> surface = currentFrame->GetSurface();
+
+  // Verify that the resulting surfaces matches our expectations.
+  EXPECT_EQ(SurfaceType::DATA, surface->GetType());
+  EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 ||
+              surface->GetFormat() == SurfaceFormat::B8G8R8A8);
+  EXPECT_EQ(aTestCase.mSize, surface->GetSize());
+  EXPECT_TRUE(IsSolidColor(surface, BGRAColor::Green(),
+                           aTestCase.mFlags & TEST_CASE_IS_FUZZY));
+}
+
+static void
+CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
+{
+  nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
+  ASSERT_TRUE(inputStream != nullptr);
+
+  // Figure out how much data we have.
+  uint64_t length;
+  nsresult rv = inputStream->Available(&length);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Write the data into a SourceBuffer.
+  nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  sourceBuffer->ExpectLength(length);
+  rv = sourceBuffer->AppendFromInputStream(inputStream, length);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+  sourceBuffer->Complete(NS_OK);
+
+  // Create a decoder.
+  DecoderType decoderType =
+    DecoderFactory::GetDecoderType(aTestCase.mMimeType);
+  nsRefPtr<Decoder> decoder =
+    DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
+                                           DefaultSurfaceFlags());
+  ASSERT_TRUE(decoder != nullptr);
+
+  // Run the full decoder synchronously.
+  decoder->Decode();
+  
+  CheckDecoderResults(aTestCase, decoder);
+}
+
+class NoResume : public IResumable
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(NoResume, override)
+  virtual void Resume() override { }
+
+private:
+  ~NoResume() { }
+};
+
+static void
+CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
+{
+  nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
+  ASSERT_TRUE(inputStream != nullptr);
+
+  // Figure out how much data we have.
+  uint64_t length;
+  nsresult rv = inputStream->Available(&length);
+  ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+  // Create a SourceBuffer and a decoder.
+  nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
+  sourceBuffer->ExpectLength(length);
+  DecoderType decoderType =
+    DecoderFactory::GetDecoderType(aTestCase.mMimeType);
+  nsRefPtr<Decoder> decoder =
+    DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer,
+                                           DefaultSurfaceFlags());
+  ASSERT_TRUE(decoder != nullptr);
+
+  // Decode synchronously, using a |NoResume| IResumable so the Decoder doesn't
+  // attempt to schedule itself on a nonexistent DecodePool when we write more
+  // data into the SourceBuffer.
+  nsRefPtr<NoResume> noResume = new NoResume();
+  for (uint64_t read = 0; read < length ; ++read) {
+    uint64_t available = 0;
+    rv = inputStream->Available(&available);
+    ASSERT_TRUE(available > 0);
+    ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+    rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
+    ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+    decoder->Decode(noResume);
+  }
+
+  sourceBuffer->Complete(NS_OK);
+  decoder->Decode(noResume);
+  
+  CheckDecoderResults(aTestCase, decoder);
+}
+
+TEST(ImageDecoders, PNGSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenPNGTestCase());
+}
+
+TEST(ImageDecoders, PNGMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenPNGTestCase());
+}
+
+TEST(ImageDecoders, GIFSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenGIFTestCase());
+}
+
+TEST(ImageDecoders, GIFMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenGIFTestCase());
+}
+
+TEST(ImageDecoders, JPGSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenJPGTestCase());
+}
+
+TEST(ImageDecoders, JPGMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenJPGTestCase());
+}
+
+TEST(ImageDecoders, BMPSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenBMPTestCase());
+}
+
+TEST(ImageDecoders, BMPMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenBMPTestCase());
+}
+
+TEST(ImageDecoders, ICOSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenICOTestCase());
+}
+
+// XXX(seth): Disabled. We'll fix this in bug 1196066.
+TEST(ImageDecoders, DISABLED_ICOMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenICOTestCase());
+}
+
+TEST(ImageDecoders, AnimatedGIFSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenFirstFrameAnimatedGIFTestCase());
+}
+
+TEST(ImageDecoders, AnimatedGIFMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenFirstFrameAnimatedGIFTestCase());
+}
+
+TEST(ImageDecoders, AnimatedPNGSingleChunk)
+{
+  CheckDecoderSingleChunk(GreenFirstFrameAnimatedPNGTestCase());
+}
+
+TEST(ImageDecoders, AnimatedPNGMultiChunk)
+{
+  CheckDecoderMultiChunk(GreenFirstFrameAnimatedPNGTestCase());
+}
+
+TEST(ImageDecoders, CorruptSingleChunk)
+{
+  CheckDecoderSingleChunk(CorruptTestCase());
+}
+
+TEST(ImageDecoders, CorruptMultiChunk)
+{
+  CheckDecoderMultiChunk(CorruptTestCase());
+}
--- a/image/test/gtest/moz.build
+++ b/image/test/gtest/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 Library('imagetest')
 
 FAIL_ON_WARNINGS = True
 
 UNIFIED_SOURCES = [
     'Common.cpp',
+    'TestDecoders.cpp',
     'TestDecodeToSurface.cpp',
     'TestMetadata.cpp',
 ]
 
 TEST_HARNESS_FILES.gtest += [
     'corrupt.jpg',
     'first-frame-green.gif',
     'first-frame-green.png',