Bug 1107545 - introduce a test manager to make storage tests scalable. r=cpearce.
authorJW Wang <jwwang@mozilla.com>
Thu, 04 Dec 2014 21:32:00 +0100
changeset 218454 1185ff1ada054ed7d3ebed7db849cfaffa7095bd
parent 218453 f25e20a0f238bcfacd88cd0afd5016532327d3fd
child 218455 769d12f35376248f58c3e72fa854bc4074742b01
push idunknown
push userunknown
push dateunknown
reviewerscpearce
bugs1107545
milestone37.0a1
Bug 1107545 - introduce a test manager to make storage tests scalable. r=cpearce.
dom/media/gmp-plugin/gmp-test-decryptor.cpp
--- a/dom/media/gmp-plugin/gmp-test-decryptor.cpp
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -8,36 +8,99 @@
 #include "gmp-test-output-protection.h"
 
 #include <string>
 #include <vector>
 #include <iostream>
 #include <istream>
 #include <iterator>
 #include <sstream>
+#include <set>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/NullPtr.h"
 
 using namespace std;
 
 FakeDecryptor* FakeDecryptor::sInstance = nullptr;
+extern GMPPlatformAPI* g_platform_api; // Defined in gmp-fake.cpp
 
-static bool sFinishedTruncateTest = false;
-static bool sFinishedReplaceTest = false;
-static bool sMultiClientTest = false;
+class GMPMutexAutoLock
+{
+public:
+  explicit GMPMutexAutoLock(GMPMutex* aMutex) : mMutex(aMutex) {
+    mMutex->Acquire();
+  }
+  ~GMPMutexAutoLock() {
+    mMutex->Release();
+  }
+private:
+  GMPMutex* const mMutex;
+};
+
+class TestManager {
+public:
+  TestManager() : mMutex(CreateMutex()) {}
+
+  // Register a test with the test manager.
+  void BeginTest(const string& aTestID) {
+    GMPMutexAutoLock lock(mMutex);
+    auto found = mTestIDs.find(aTestID);
+    if (found == mTestIDs.end()) {
+      mTestIDs.insert(aTestID);
+    } else {
+      Error("FAIL BeginTest test already existed: " + aTestID);
+    }
+  }
 
-void
-MaybeFinish()
-{
-  if (sFinishedTruncateTest && sFinishedReplaceTest && sMultiClientTest) {
+  // Notify the test manager that the test is finished. If all tests are done,
+  // test manager will send "test-storage complete" to notify the parent that
+  // all tests are finished and also delete itself.
+  void EndTest(const string& aTestID) {
+    bool isEmpty = false;
+    {
+      GMPMutexAutoLock lock(mMutex);
+      auto found = mTestIDs.find(aTestID);
+      if (found != mTestIDs.end()) {
+        mTestIDs.erase(aTestID);
+        isEmpty = mTestIDs.empty();
+      } else {
+        Error("FAIL EndTest test not existed: " + aTestID);
+        return;
+      }
+    }
+    if (isEmpty) {
+      Finish();
+      delete this;
+    }
+  }
+
+private:
+  ~TestManager() {
+    mMutex->Destroy();
+  }
+
+  static void Error(const string& msg) {
+    FakeDecryptor::Message(msg);
+  }
+
+  static void Finish() {
     FakeDecryptor::Message("test-storage complete");
   }
-}
+
+  static GMPMutex* CreateMutex() {
+    GMPMutex* mutex = nullptr;
+    g_platform_api->createmutex(&mutex);
+    return mutex;
+  }
+
+  GMPMutex* const mMutex;
+  set<string> mTestIDs;
+};
 
 FakeDecryptor::FakeDecryptor(GMPDecryptorHost* aHost)
   : mCallback(nullptr)
   , mHost(aHost)
 {
   MOZ_ASSERT(!sInstance);
   sInstance = this;
 }
@@ -82,178 +145,233 @@ public:
     delete this;
   }
   string mId;
   ReadContinuation* mThen;
 };
 
 class SendMessageTask : public GMPTask {
 public:
-  SendMessageTask(const string& aMessage)
-    : mMessage(aMessage)
-  {}
+  SendMessageTask(const string& aMessage,
+                  TestManager* aTestManager = nullptr,
+                  const string& aTestID = "")
+    : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {}
+
   void Run() MOZ_OVERRIDE {
     FakeDecryptor::Message(mMessage);
+    if (mTestmanager) {
+      mTestmanager->EndTest(mTestID);
+    }
   }
+
   void Destroy() MOZ_OVERRIDE {
     delete this;
   }
+
+private:
   string mMessage;
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 class TestEmptyContinuation : public ReadContinuation {
 public:
+  TestEmptyContinuation(TestManager* aTestManager, const string& aTestID)
+    : mTestmanager(aTestManager), mTestID(aTestID) {}
+
   void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
     if (aData != "") {
       FakeDecryptor::Message("FAIL TestEmptyContinuation record was not truncated");
     }
-    sFinishedTruncateTest = true;
-    MaybeFinish();
+    mTestmanager->EndTest(mTestID);
     delete this;
   }
+
+private:
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 class TruncateContinuation : public ReadContinuation {
 public:
+  TruncateContinuation(TestManager* aTestManager, const string& aTestID)
+    : mTestmanager(aTestManager), mTestID(aTestID) {}
+
   void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
     if (aData != TruncateRecordData) {
       FakeDecryptor::Message("FAIL TruncateContinuation read data doesn't match written data");
     }
+    auto cont = new TestEmptyContinuation(mTestmanager, mTestID);
+    auto msg = "FAIL in TruncateContinuation write.";
+    auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
     WriteRecord(TruncateRecordId, nullptr, 0,
-                new ReadThenTask(TruncateRecordId, new TestEmptyContinuation()),
-                new SendMessageTask("FAIL in TruncateContinuation write."));
+                new ReadThenTask(TruncateRecordId, cont),
+                failTask);
     delete this;
   }
+
+private:
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 class VerifyAndFinishContinuation : public ReadContinuation {
 public:
-  explicit VerifyAndFinishContinuation(string aValue)
-    : mValue(aValue)
-  {}
+  explicit VerifyAndFinishContinuation(string aValue,
+                                       TestManager* aTestManager,
+                                       const string& aTestID)
+  : mValue(aValue), mTestmanager(aTestManager), mTestID(aTestID) {}
+
   void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
     if (aData != mValue) {
       FakeDecryptor::Message("FAIL VerifyAndFinishContinuation read data doesn't match expected data");
     }
-    sFinishedReplaceTest = true;
-    MaybeFinish();
+    mTestmanager->EndTest(mTestID);
     delete this;
   }
+
+private:
   string mValue;
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 class VerifyAndOverwriteContinuation : public ReadContinuation {
 public:
-  VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite)
+  VerifyAndOverwriteContinuation(string aId, string aValue, string aOverwrite,
+                                 TestManager* aTestManager, const string& aTestID)
     : mId(aId)
     , mValue(aValue)
     , mOverwrite(aOverwrite)
+    , mTestmanager(aTestManager)
+    , mTestID(aTestID)
   {}
+
   void ReadComplete(GMPErr aErr, const std::string& aData) MOZ_OVERRIDE {
     if (aData != mValue) {
       FakeDecryptor::Message("FAIL VerifyAndOverwriteContinuation read data doesn't match expected data");
     }
-    WriteRecord(mId,
-                mOverwrite,
-                new ReadThenTask(mId, new VerifyAndFinishContinuation(mOverwrite)),
-                new SendMessageTask("FAIL in VerifyAndOverwriteContinuation write."));
+    auto cont = new VerifyAndFinishContinuation(mOverwrite, mTestmanager, mTestID);
+    auto msg = "FAIL in VerifyAndOverwriteContinuation write.";
+    auto failTask = new SendMessageTask(msg, mTestmanager, mTestID);
+    WriteRecord(mId, mOverwrite, new ReadThenTask(mId, cont), failTask);
     delete this;
   }
+
+private:
   string mId;
   string mValue;
   string mOverwrite;
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 static const string OpenAgainRecordId = "open-again-record-id";
 
 class OpenedSecondTimeContinuation : public OpenContinuation {
 public:
-  explicit OpenedSecondTimeContinuation(GMPRecord* aRecord)
-    : mRecord(aRecord)
-  {
-  }
+  explicit OpenedSecondTimeContinuation(GMPRecord* aRecord,
+                                        TestManager* aTestManager,
+                                        const string& aTestID)
+    : mRecord(aRecord), mTestmanager(aTestManager), mTestID(aTestID) {}
 
   virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) MOZ_OVERRIDE {
     if (GMP_SUCCEEDED(aStatus)) {
       FakeDecryptor::Message("FAIL OpenSecondTimeContinuation should not be able to re-open record.");
     }
 
     // Succeeded, open should have failed.
-    sMultiClientTest = true;
-    MaybeFinish();
-
+    mTestmanager->EndTest(mTestID);
     mRecord->Close();
-
     delete this;
   }
+
+private:
   GMPRecord* mRecord;
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 class OpenedFirstTimeContinuation : public OpenContinuation {
 public:
+  OpenedFirstTimeContinuation(TestManager* aTestManager, const string& aTestID)
+    : mTestmanager(aTestManager), mTestID(aTestID) {}
+
   virtual void OpenComplete(GMPErr aStatus, GMPRecord* aRecord) MOZ_OVERRIDE {
     if (GMP_FAILED(aStatus)) {
       FakeDecryptor::Message("FAIL OpenAgainContinuation to open record initially.");
-      sMultiClientTest = true;
-      MaybeFinish();
+      mTestmanager->EndTest(mTestID);
       return;
     }
 
-    GMPOpenRecord(OpenAgainRecordId, new OpenedSecondTimeContinuation(aRecord));
-
+    auto cont = new OpenedSecondTimeContinuation(aRecord, mTestmanager, mTestID);
+    GMPOpenRecord(OpenAgainRecordId, cont);
     delete this;
   }
+
+private:
+  TestManager* const mTestmanager;
+  const string mTestID;
 };
 
 void
 FakeDecryptor::TestStorage()
 {
+  TestManager* testManager = new TestManager();
+
   // Basic I/O tests. We run three cases concurrently. The tests, like
   // GMPStorage run asynchronously. When they've all passed, we send
   // a message back to the parent process, or a failure message if not.
 
   // Test 1: Basic I/O test, and test that writing 0 bytes in a record
   // deletes record.
   //
   // Write data to truncate record, then
   // read data, verify that we read what we wrote, then
   // write 0 bytes to truncate record, then
   // read data, verify that 0 bytes was read
-  // set sFinishedTruncateTest=true and MaybeFinish().
+  const string testID1 = "write-test-1";
+  testManager->BeginTest(testID1);
+  auto cont1 = new TruncateContinuation(testManager, testID1);
+  auto msg1 = "FAIL in TestStorage writing TruncateRecord.";
+  auto failTask1 = new SendMessageTask(msg1, testManager, testID1);
   WriteRecord(TruncateRecordId,
               TruncateRecordData,
-              new ReadThenTask(TruncateRecordId, new TruncateContinuation()),
-              new SendMessageTask("FAIL in TestStorage writing TruncateRecord."));
+              new ReadThenTask(TruncateRecordId, cont1),
+              failTask1);
 
   // Test 2: Test that overwriting a record with a shorter record truncates
   // the record to the shorter record.
   //
   // Write record, then
   // read and verify record, then
   // write a shorter record to same record.
   // read and verify
-  // set sFinishedReplaceTest=true and MaybeFinish().
   string id = "record1";
   string record1 = "This is the first write to a record.";
   string overwrite = "A shorter record";
-  WriteRecord(id,
-              record1,
-              new ReadThenTask(id, new VerifyAndOverwriteContinuation(id, record1, overwrite)),
-              new SendMessageTask("FAIL in TestStorage writing record1."));
+  const string testID2 = "write-test-2";
+  testManager->BeginTest(testID2);
+  auto task2 = new VerifyAndOverwriteContinuation(id, record1, overwrite,
+                                                  testManager, testID2);
+  auto msg2 = "FAIL in TestStorage writing record1.";
+  auto failTask2 = new SendMessageTask(msg2, testManager, testID2);
+  WriteRecord(id, record1, new ReadThenTask(id, task2), failTask2);
 
   // Test 3: Test that opening a record while it's already open fails.
   //
   // Open record1, then
   // open record1, should fail.
-  // close record1,
-  // set sMultiClientTest=true and MaybeFinish().
+  // close record1
+  const string testID3 = "open-test-1";
+  testManager->BeginTest(testID3);
+  auto task3 = new OpenedFirstTimeContinuation(testManager, testID3);
+  GMPOpenRecord(OpenAgainRecordId, task3);
 
-  GMPOpenRecord(OpenAgainRecordId, new OpenedFirstTimeContinuation());
-
-  // Note: Once all tests finish, dispatch "test-pass" message,
+  // Note: Once all tests finish, TestManager will dispatch "test-pass" message,
   // which ends the test for the parent.
 }
 
 class ReportWritten : public GMPTask {
 public:
   ReportWritten(const string& aRecordId, const string& aValue)
     : mRecordId(aRecordId)
     , mValue(aValue)