skia2/tests/LListTest.cpp
mtklein 6f07665768 Simplify SkInstCnt
This code requires fewer macros to use it (just one), has less code in macro
definitions, and has simpler synchronization code (just atomic ints, no SkOnce,
no SkMutex, etc.)

A minor downside, we lose indentation and reverse-ordering in the final report:
  Leaked SkRefCntBase: 7
     Leaked SkFontMgr: 1
     Leaked SkWeakRefCnt: 1
         Leaked SkTypeface: 1
     Leaked SkFlattenable: 3
         Leaked SkXfermode: 3
     Leaked SkPathRef: 1
     Leaked SkPixelRef: 1
         Leaked SkMallocPixelRef: 1
becomes
  Leaked SkXfermode: 3
  Leaked SkMallocPixelRef: 1
  Leaked SkPixelRef: 1
  Leaked SkPathRef: 1
  Leaked SkFlattenable: 3
  Leaked SkTypeface: 1
  Leaked SkWeakRefCnt: 1
  Leaked SkFontMgr: 1
  Leaked SkRefCntBase: 7

This is motivated by wanting to land https://codereview.chromium.org/806473006/,
which makes sure all static use of SkOnce are in global scope.  The current
implementation of SkInstCnt uses them in function scope, which isn't safe.
BUG=skia:

No public API changes.
TBR=reed@google.com

Review URL: https://codereview.chromium.org/841263004
2015-01-13 08:22:44 -08:00

317 lines
12 KiB
C++

/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkRandom.h"
#include "SkTInternalLList.h"
#include "SkTLList.h"
#include "Test.h"
class ListElement {
public:
ListElement(int id) : fID(id) {
}
bool operator== (const ListElement& other) { return fID == other.fID; }
#if SK_ENABLE_INST_COUNT
// Make the instance count available publicly.
static int InstanceCount() { return GetInstanceCount(); }
#endif
int fID;
private:
SK_DECLARE_INST_COUNT(ListElement);
SK_DECLARE_INTERNAL_LLIST_INTERFACE(ListElement);
};
static void check_list(const SkTInternalLList<ListElement>& list,
skiatest::Reporter* reporter,
bool empty,
int numElements,
bool in0, bool in1, bool in2, bool in3,
ListElement elements[4]) {
REPORTER_ASSERT(reporter, empty == list.isEmpty());
#ifdef SK_DEBUG
list.validate();
REPORTER_ASSERT(reporter, numElements == list.countEntries());
REPORTER_ASSERT(reporter, in0 == list.isInList(&elements[0]));
REPORTER_ASSERT(reporter, in1 == list.isInList(&elements[1]));
REPORTER_ASSERT(reporter, in2 == list.isInList(&elements[2]));
REPORTER_ASSERT(reporter, in3 == list.isInList(&elements[3]));
#endif
}
static void TestTInternalLList(skiatest::Reporter* reporter) {
SkTInternalLList<ListElement> list;
ListElement elements[4] = {
ListElement(0),
ListElement(1),
ListElement(2),
ListElement(3),
};
// list should be empty to start with
check_list(list, reporter, true, 0, false, false, false, false, elements);
list.addToHead(&elements[0]);
check_list(list, reporter, false, 1, true, false, false, false, elements);
list.addToHead(&elements[1]);
list.addToHead(&elements[2]);
list.addToHead(&elements[3]);
check_list(list, reporter, false, 4, true, true, true, true, elements);
// test out iterators
typedef SkTInternalLList<ListElement>::Iter Iter;
Iter iter;
ListElement* cur = iter.init(list, Iter::kHead_IterStart);
for (int i = 0; cur; ++i, cur = iter.next()) {
REPORTER_ASSERT(reporter, cur->fID == 3-i);
}
cur = iter.init(list, Iter::kTail_IterStart);
for (int i = 0; cur; ++i, cur = iter.prev()) {
REPORTER_ASSERT(reporter, cur->fID == i);
}
// remove middle, frontmost then backmost
list.remove(&elements[1]);
list.remove(&elements[3]);
list.remove(&elements[0]);
check_list(list, reporter, false, 1, false, false, true, false, elements);
// remove last element
list.remove(&elements[2]);
// list should be empty again
check_list(list, reporter, true, 0, false, false, false, false, elements);
// test out methods that add to the middle of the list.
list.addAfter(&elements[1], NULL);
check_list(list, reporter, false, 1, false, true, false, false, elements);
list.remove(&elements[1]);
list.addBefore(&elements[1], NULL);
check_list(list, reporter, false, 1, false, true, false, false, elements);
list.addBefore(&elements[0], &elements[1]);
check_list(list, reporter, false, 2, true, true, false, false, elements);
list.addAfter(&elements[3], &elements[1]);
check_list(list, reporter, false, 3, true, true, false, true, elements);
list.addBefore(&elements[2], &elements[3]);
check_list(list, reporter, false, 4, true, true, true, true, elements);
cur = iter.init(list, Iter::kHead_IterStart);
for (int i = 0; cur; ++i, cur = iter.next()) {
REPORTER_ASSERT(reporter, cur->fID == i);
}
}
static void TestTLList(skiatest::Reporter* reporter) {
typedef SkTLList<ListElement> ElList;
typedef ElList::Iter Iter;
SkRandom random;
for (int i = 1; i <= 16; i *= 2) {
ElList list1(i);
ElList list2(i);
Iter iter1;
Iter iter2;
Iter iter3;
Iter iter4;
#if SK_ENABLE_INST_COUNT
SkASSERT(0 == ListElement::InstanceCount());
#endif
REPORTER_ASSERT(reporter, list1.isEmpty());
REPORTER_ASSERT(reporter, NULL == iter1.init(list1, Iter::kHead_IterStart));
REPORTER_ASSERT(reporter, NULL == iter1.init(list1, Iter::kTail_IterStart));
// Try popping an empty list
list1.popHead();
list1.popTail();
REPORTER_ASSERT(reporter, list1.isEmpty());
REPORTER_ASSERT(reporter, list1 == list2);
// Create two identical lists, one by appending to head and the other to the tail.
list1.addToHead(ListElement(1));
list2.addToTail(ListElement(1));
#if SK_ENABLE_INST_COUNT
SkASSERT(2 == ListElement::InstanceCount());
#endif
iter1.init(list1, Iter::kHead_IterStart);
iter2.init(list1, Iter::kTail_IterStart);
REPORTER_ASSERT(reporter, iter1.get()->fID == iter2.get()->fID);
iter3.init(list2, Iter::kHead_IterStart);
iter4.init(list2, Iter::kTail_IterStart);
REPORTER_ASSERT(reporter, iter3.get()->fID == iter1.get()->fID);
REPORTER_ASSERT(reporter, iter4.get()->fID == iter1.get()->fID);
REPORTER_ASSERT(reporter, list1 == list2);
list2.reset();
// use both before/after in-place construction on an empty list
SkNEW_INSERT_IN_LLIST_BEFORE(&list2, list2.headIter(), ListElement, (1));
REPORTER_ASSERT(reporter, list2 == list1);
list2.reset();
SkNEW_INSERT_IN_LLIST_AFTER(&list2, list2.tailIter(), ListElement, (1));
REPORTER_ASSERT(reporter, list2 == list1);
// add an element to the second list, check that iters are still valid
iter3.init(list2, Iter::kHead_IterStart);
iter4.init(list2, Iter::kTail_IterStart);
list2.addToHead(ListElement(2));
#if SK_ENABLE_INST_COUNT
SkASSERT(3 == ListElement::InstanceCount());
#endif
REPORTER_ASSERT(reporter, iter3.get()->fID == iter1.get()->fID);
REPORTER_ASSERT(reporter, iter4.get()->fID == iter1.get()->fID);
REPORTER_ASSERT(reporter, 1 == Iter(list2, Iter::kTail_IterStart).get()->fID);
REPORTER_ASSERT(reporter, 2 == Iter(list2, Iter::kHead_IterStart).get()->fID);
REPORTER_ASSERT(reporter, list1 != list2);
list1.addToHead(ListElement(2));
REPORTER_ASSERT(reporter, list1 == list2);
#if SK_ENABLE_INST_COUNT
SkASSERT(4 == ListElement::InstanceCount());
#endif
REPORTER_ASSERT(reporter, !list1.isEmpty());
list1.reset();
list2.reset();
#if SK_ENABLE_INST_COUNT
SkASSERT(0 == ListElement::InstanceCount());
#endif
REPORTER_ASSERT(reporter, list1.isEmpty() && list2.isEmpty());
// randomly perform insertions and deletions on a list and perform tests
int count = 0;
for (int j = 0; j < 100; ++j) {
if (list1.isEmpty() || random.nextBiasedBool(3 * SK_Scalar1 / 4)) {
int id = j;
// Choose one of three ways to insert a new element: at the head, at the tail,
// before a random element, after a random element
int numValidMethods = 0 == count ? 2 : 4;
int insertionMethod = random.nextULessThan(numValidMethods);
switch (insertionMethod) {
case 0:
list1.addToHead(ListElement(id));
break;
case 1:
list1.addToTail(ListElement(id));
break;
case 2: // fallthru to share code that picks random element.
case 3: {
int n = random.nextULessThan(list1.count());
Iter iter = list1.headIter();
// remember the elements before/after the insertion point.
while (n--) {
iter.next();
}
Iter prev(iter);
Iter next(iter);
next.next();
prev.prev();
SkASSERT(iter.get());
// insert either before or after the iterator, then check that the
// surrounding sequence is correct.
if (2 == insertionMethod) {
SkNEW_INSERT_IN_LLIST_BEFORE(&list1, iter, ListElement, (id));
Iter newItem(iter);
newItem.prev();
REPORTER_ASSERT(reporter, newItem.get()->fID == id);
if (next.get()) {
REPORTER_ASSERT(reporter, next.prev()->fID == iter.get()->fID);
}
if (prev.get()) {
REPORTER_ASSERT(reporter, prev.next()->fID == id);
}
} else {
SkNEW_INSERT_IN_LLIST_AFTER(&list1, iter, ListElement, (id));
Iter newItem(iter);
newItem.next();
REPORTER_ASSERT(reporter, newItem.get()->fID == id);
if (next.get()) {
REPORTER_ASSERT(reporter, next.prev()->fID == id);
}
if (prev.get()) {
REPORTER_ASSERT(reporter, prev.next()->fID == iter.get()->fID);
}
}
}
}
++count;
} else {
// walk to a random place either forward or backwards and remove.
int n = random.nextULessThan(list1.count());
Iter::IterStart start;
ListElement* (Iter::*incrFunc)();
if (random.nextBool()) {
start = Iter::kHead_IterStart;
incrFunc = &Iter::next;
} else {
start = Iter::kTail_IterStart;
incrFunc = &Iter::prev;
}
// find the element
Iter iter(list1, start);
while (n--) {
REPORTER_ASSERT(reporter, iter.get());
(iter.*incrFunc)();
}
REPORTER_ASSERT(reporter, iter.get());
// remember the prev and next elements from the element to be removed
Iter prev = iter;
Iter next = iter;
prev.prev();
next.next();
list1.remove(iter.get());
// make sure the remembered next/prev iters still work
Iter pn = prev; pn.next();
Iter np = next; np.prev();
// pn should match next unless the target node was the head, in which case prev
// walked off the list.
REPORTER_ASSERT(reporter, pn.get() == next.get() || NULL == prev.get());
// Similarly, np should match prev unless next originally walked off the tail.
REPORTER_ASSERT(reporter, np.get() == prev.get() || NULL == next.get());
--count;
}
REPORTER_ASSERT(reporter, count == list1.count());
#if SK_ENABLE_INST_COUNT
SkASSERT(count == ListElement::InstanceCount());
#endif
}
list1.reset();
#if SK_ENABLE_INST_COUNT
SkASSERT(0 == ListElement::InstanceCount());
#endif
}
}
DEF_TEST(LList, reporter) {
TestTInternalLList(reporter);
TestTLList(reporter);
}