Add SkCachedData and use it for SkMipMap

This reverts commit 37c5a815d8ea33247968212ef4cc83394ceee1bc.

TBR=mtklein

Review URL: https://codereview.chromium.org/635333002
diff --git a/tests/CachedDataTest.cpp b/tests/CachedDataTest.cpp
new file mode 100644
index 0000000..f65a46b
--- /dev/null
+++ b/tests/CachedDataTest.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCachedData.h"
+#include "SkDiscardableMemoryPool.h"
+#include "Test.h"
+
+enum LockedState {
+    kUnlocked,
+    kLocked,
+};
+
+enum CachedState {
+    kNotInCache,
+    kInCache,
+};
+
+static void check_data(skiatest::Reporter* reporter, SkCachedData* data,
+                       int refcnt, CachedState cacheState, LockedState lockedState) {
+    REPORTER_ASSERT(reporter, data->testing_only_getRefCnt() == refcnt);
+    REPORTER_ASSERT(reporter, data->testing_only_isInCache() == (kInCache == cacheState));
+    REPORTER_ASSERT(reporter, data->testing_only_isLocked() == (lockedState == kLocked));
+}
+
+static SkCachedData* make_data(size_t size, SkDiscardableMemoryPool* pool) {
+    if (pool) {
+        SkDiscardableMemory* dm = pool->create(size);
+        // the pool "can" return null, but it shouldn't in these controlled conditions
+        SK_ALWAYSBREAK(dm);
+        return SkNEW_ARGS(SkCachedData, (size, dm));
+    } else {
+        return SkNEW_ARGS(SkCachedData, (sk_malloc_throw(size), size));
+    }
+}
+
+// returns with the data locked by client and cache
+static SkCachedData* test_locking(skiatest::Reporter* reporter,
+                                  size_t size, SkDiscardableMemoryPool* pool) {
+    SkCachedData* data = make_data(size, pool);
+    
+    memset(data->writable_data(), 0x80, size);  // just to use writable_data()
+
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    
+    data->ref();
+    check_data(reporter, data, 2, kNotInCache, kLocked);
+    data->unref();
+    check_data(reporter, data, 1, kNotInCache, kLocked);
+    
+    data->attachToCacheAndRef();
+    check_data(reporter, data, 2, kInCache, kLocked);
+    
+    data->unref();
+    check_data(reporter, data, 1, kInCache, kUnlocked);
+    
+    data->ref();
+    check_data(reporter, data, 2, kInCache, kLocked);
+
+    return data;
+}
+
+/*
+ *  SkCachedData behaves differently (regarding its locked/unlocked state) depending on
+ *  when it is in the cache or not. Being in the cache is signaled by calling attachToCacheAndRef()
+ *  instead of ref(). (and balanced by detachFromCacheAndUnref).
+ *
+ *  Thus, among other things, we test the end-of-life behavior when the client is the last owner
+ *  and when the cache is.
+ */
+DEF_TEST(CachedData, reporter) {
+    SkAutoTUnref<SkDiscardableMemoryPool> pool(SkDiscardableMemoryPool::Create(1000));
+
+    for (int useDiscardable = 0; useDiscardable <= 1; ++useDiscardable) {
+        const size_t size = 100;
+
+        // test with client as last owner
+        SkCachedData* data = test_locking(reporter, size, useDiscardable ? pool.get() : NULL);
+        check_data(reporter, data, 2, kInCache, kLocked);
+        data->detachFromCacheAndUnref();
+        check_data(reporter, data, 1, kNotInCache, kLocked);
+        data->unref();
+
+        // test with cache as last owner
+        data = test_locking(reporter, size, useDiscardable ? pool.get() : NULL);
+        check_data(reporter, data, 2, kInCache, kLocked);
+        data->unref();
+        check_data(reporter, data, 1, kInCache, kUnlocked);
+        data->detachFromCacheAndUnref();
+    }
+}
+