Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(374)

Side by Side Diff: third_party/WebKit/LayoutTests/external/wpt/IndexedDB/interleaved-cursors-support.js

Issue 2781623008: More thorough overlapping cursor tests. (Closed)
Patch Set: Addressed feedback. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 'use strict';
2
3 // Size of large objects. This should exceed the size of a block in the storage
4 // method underlying the browser's IndexedDB implementation. For example, this
5 // needs to exceed the LevelDB block size on Chrome, and the SQLite block size
6 // on Firefox.
7 const largeObjectSize = 48 * 1024;
8
9 function largeObjectValue(cursorIndex, itemIndex) {
10 // We use a typed array (as opposed to a string) because IndexedDB
11 // implementations may serialize strings using UTF-8 or UTF-16, yielding
12 // larger IndexedDB entries than we'd expect. It's very unlikely that an
13 // IndexedDB implementation would use anything other than the raw buffer to
14 // serialize a typed array.
15 const buffer = new Uint8Array(largeObjectSize);
16
17 // Some IndexedDB implementations, like LevelDB, compress their data blocks
18 // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
19 // should be sufficient to foil any fast generic-purpose compression scheme.
20
21 // 32-bit xorshift - the seed can't be zero
22 let state = 1000 + (cursorIndex * itemCount + itemIndex);
23
24 for (let i = 0; i < largeObjectSize; ++i) {
25 state ^= state << 13;
26 state ^= state >> 17;
27 state ^= state << 5;
28 buffer[i] = state & 0xff;
29 }
30
31 return buffer;
32 }
33
34 // Writes the objects to be read by one cursor. Returns a promise that resolves
35 // when the write completes.
36 //
37 // We want to avoid creating a large transaction, because that is outside the
38 // test's scope, and it's a bad practice. So we break up the writes across
39 // multiple transactions. For simplicity, each transaction writes all the
40 // objects that will be read by a cursor.
41 function writeCursorObjects(database, cursorIndex) {
42 return new Promise((resolve, reject) => {
43 const transaction = database.transaction('cache', 'readwrite');
44 transaction.onabort = () => { reject(transaction.error); };
45
46 const store = transaction.objectStore('cache');
47 for (let i = 0; i < itemCount; ++i) {
48 store.put({
49 key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
50 }
51 transaction.oncomplete = resolve;
52 });
53 }
54
55 // Returns a promise that resolves when the store has been populated.
56 function populateTestStore(testCase, database, cursorCount) {
57 let promiseChain = Promise.resolve();
58
59 for (let i = 0; i < cursorCount; ++i)
60 promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
61
62 return promiseChain;
63 }
64
65 // A bank of cursors that can be used in an interleaved or parallel manner.
66 class CursorBank {
67 constructor(testCase, store, cursorCount) {
68 this.testCase = testCase;
69 this.store = store;
70 this.itemCount = itemCount;
71
72 // The cursors used for iteration are stored here so each cursor's onsuccess
73 // handler can call continue() on the next cursor.
74 this.cursors = [];
75
76 // The results of IDBObjectStore.openCursor() calls are stored here so we
77 // we can change the requests' onsuccess handler after every
78 // IDBCursor.continue() call.
79 this.requests = [];
80 }
81
82 // Asserts that a cursor's key and value match the expectation.
83 checkCursorState(cursorIndex, itemIndex) {
84 this.testCase.step(() => {
85 const cursor = this.cursors[cursorIndex];
86
87 if (itemIndex < this.itemCount) {
88 assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
89 assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
90 assert_equals(
91 cursor.value.value.join('-'),
92 objectValue(cursorIndex, itemIndex).join('-'));
93 } else {
94 assert_equals(cursor, null);
95 }
96 });
97 }
98
99 // Opens a cursor. The callback is called when the cursor open succeeds.
100 openCursor(cursorIndex, callback) {
101 this.testCase.step(() => {
102 const request = this.store.openCursor(IDBKeyRange.bound(
103 objectKey(cursorIndex, 0), objectKey(cursorIndex, this.itemCount)));
104 this.requests[cursorIndex] = request;
105
106 request.onsuccess = this.testCase.step_func(() => {
107 const cursor = request.result;
108 this.cursors[cursorIndex] = cursor;
109 this.checkCursorState(cursorIndex, 0);
110 callback();
111 });
112 request.onerror = () => {
113 this.testCase.unreached_func(
114 `IDBObjectStore.openCursor failed: ${request.error}`);
115 };
116 });
117 }
118
119 // Reads the next item available in the cursor. The callback is called when
120 // the read suceeds.
121 continueCursor(cursorIndex, itemIndex, callback) {
122 this.testCase.step(() => {
123 const request = this.requests[cursorIndex];
124 request.onsuccess = this.testCase.step_func(() => {
125 const cursor = request.result;
126 this.cursors[cursorIndex] = cursor;
127 this.checkCursorState(cursorIndex, itemIndex);
128 callback();
129 });
130 request.onerror = this.testCase.unreached_func(
131 `IDBCursor.continue() failed: ${request.error}`);
132 request.onerror = () => {
133 this.testCase.unreached_func(
134 `IDBCursor.continue() failed: ${request.error}`);
135 };
136
137 const cursor = this.cursors[cursorIndex];
138 cursor.continue();
139 });
140 }
141 }
142
143 // Reads cursors in an interleaved fashion, as shown below. Returns a promise
144 // that resolves when the reading is done.
145 //
146 // Given N cursors, each of which points to the beginning of a K-item sequence,
147 // the following accesses will be made.
148 //
149 // OC(i) = open cursor i
150 // RD(i, j) = read result of cursor i, which should be at item j
151 // REND(i) = read result of cursor i, which should be at the end of items
152 // CC(i) = continue cursor i
153 // | = wait for onsuccess on the previous OC or CC
154 //
155 // OC(1) | RD(1, 1) OC(2) | RD(2, 1) OC(3) | ... | RD(n-1, 1) CC(n) |
156 // RD(n, 1) CC(1) | RD(1, 2) CC(2) | RD(2, 2) CC(3) | ... | RD(n-1, 2) CC(n) |
157 // RD(n, 2) CC(1) | RD(1, 3) CC(2) | RD(2, 3) CC(3) | ... | RD(n-1, 3) CC(n) |
158 // ...
159 // RD(n, k-1) CC(1) | RD(1, k) CC(2) | RD(2, k) CC(3) | ... | RD(n-1, k) CC(n) |
160 // RD(n) CC(1) | REND(1) CC(2) | REND(2) CC(3) | ... | REND(n-1) CC(n) |
161 // REND(n) done
162 function interleaveCursors(testCase, store, cursorCount, itemCount) {
163 return new Promise((resolve, reject) => {
164 const cursors = new CursorBank(testCase, store, itemCount);
165
166 // We open all the cursors one at a time, then cycle through the cursors and
167 // call continue() on each of them. This access pattern causes maximal
168 // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
169 // have to evict some cursors, and this access pattern verifies that the
170 // cache correctly restores the state of evicted cursors.
171 const steps = [];
172 for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
173 steps.push(cursors.openCursor.bind(cursors, cursorIndex));
174 for (let itemIndex = 1; itemIndex <= itemCount; ++itemIndex) {
175 for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex) {
176 steps.push(
177 cursors.continueCursor.bind(cursors, cursorIndex, itemIndex));
178 }
179 }
180
181 const runStep = (stepIndex) => {
182 if (stepIndex === steps.length) {
183 resolve();
184 return;
185 }
186 steps[stepIndex](testCase.step_func(() => { runStep(stepIndex + 1); }));
187 };
188 runStep(0);
189 });
190 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698