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

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

Issue 2763813002: Ensure correctness for interleaved IndexedDB cursor iteration. (Closed)
Patch Set: Relax quota restriction for the test to pass. Created 3 years, 9 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 <!doctype html>
2 <meta charset="utf-8">
3 <meta name="timeout" content="long">
4 <title>IndexedDB: Interleaved iteration of multiple cursors</title>
5 <link rel="author" href="pwnall@chromium.org" title="Victor Costan">
6 <script src="/resources/testharness.js"></script>
7 <script src="/resources/testharnessreport.js"></script>
8 <script src="support-promises.js"></script>
9 <script>
10 // Number of objects that each iterator goes over.
11 const itemCount = 10;
12
13 // Ratio of small objects to large objects.
14 const largeObjectRatio = 5;
15
16 // Size of large objects. This should exceed the size of a block in the storage
17 // method underlying the browser's IndexedDB implementation. For example, this
18 // needs to exceed the LevelDB block size on Chrome, and the SQLite block size
19 // on Firefox.
20 const largeObjectSize = 48 * 1024;
21
22 function objectKey(cursorIndex, itemIndex) {
23 return `${cursorIndex}-key-${itemIndex}`;
24 }
25
26 function objectValue(cursorIndex, itemIndex) {
27 if ((cursorIndex * itemCount + itemIndex) % largeObjectRatio === 0) {
28 // We use a typed array (as opposed to a string) because IndexedDB
29 // implementations may serialize strings using UTF-8 or UTF-16, yielding
30 // larger IndexedDB entries than we'd expect. It's very unlikely that an
31 // IndexedDB implementation would use anything other than the raw buffer to
32 // serialize a typed array.
33 const buffer = new Uint8Array(largeObjectSize);
34
35 // Some IndexedDB implementations, like LevelDB, compress their data blocks
36 // before storing them to disk. We use a simple 32-bit xorshift PRNG, which
37 // should be sufficient to foil any fast generic-purpose compression scheme.
38
39 // 32-bit xorshift - the seed can't be zero
40 let state = 1000 + (cursorIndex * itemCount + itemIndex);
41
42 for (let i = 0; i < largeObjectSize; ++i) {
43 state ^= state << 13;
44 state ^= state >> 17;
45 state ^= state << 5;
46 buffer[i] = state & 0xff;
47 }
48
49 return buffer;
50 }
51 return [cursorIndex, 'small', itemIndex];
52 }
53
54
55 // Writes the objects to be read by one cursor. Returns a promise that resolves
56 // when the write completes.
57 //
58 // We want to avoid creating a large transaction, because that is outside the
59 // test's scope, and it's a bad practice. So we break up the writes across
60 // multiple transactions. For simplicity, each transaction writes all the
61 // objects that will be read by a cursor.
62 function writeCursorObjects(database, cursorIndex) {
63 return new Promise((resolve, reject) => {
64 const transaction = database.transaction('cache', 'readwrite');
65 const store = transaction.objectStore('cache');
66 for (let i = 0; i < itemCount; ++i) {
67 store.put({
68 key: objectKey(cursorIndex, i), value: objectValue(cursorIndex, i)});
69 }
70 transaction.oncomplete = resolve;
71 transaction.onerror = () => { reject(transaction.error); };
jsbell 2017/03/23 21:20:01 transaction.error won't be set to anything here si
pwnall 2017/03/23 22:08:27 Done.
72 transaction.onabort = () => { reject(transaction.error); };
73 });
74 }
75
76 // Returns a promise that resolves when the store has been populated.
77 function populateTestStore(testCase, database, cursorCount) {
78 let promiseChain = Promise.resolve();
79
80 for (let i = 0; i < cursorCount; ++i)
81 promiseChain = promiseChain.then(() => writeCursorObjects(database, i));
82
83 return promiseChain;
84 }
85
86 function interleaveCursors(testCase, store, cursorCount) {
87 return new Promise((resolve, reject) => {
88 const cursors = [];
89 const requests = [];
dmurph 2017/03/23 21:10:52 Can you add a comment like the following: // Reque
pwnall 2017/03/23 22:08:27 Done.
90
91 const checkCursorState = (cursorIndex, itemIndex) => {
92 const cursor = cursors[cursorIndex];
93 assert_equals(cursor.key, objectKey(cursorIndex, itemIndex));
94 assert_equals(cursor.value.key, objectKey(cursorIndex, itemIndex));
95 assert_equals(
96 cursor.value.value.join('-'),
97 objectValue(cursorIndex, itemIndex).join('-'));
98 };
99
100 const openCursor = (cursorIndex, callback) => {
101 const request = store.openCursor(
102 IDBKeyRange.lowerBound(objectKey(cursorIndex, 0)));
103 requests[cursorIndex] = request;
104
105 request.onsuccess = testCase.step_func(() => {
106 const cursor = request.result;
107 cursors[cursorIndex] = cursor;
108 checkCursorState(cursorIndex, 0);
109 callback();
110 });
111 request.onerror = event => reject(request.error);
112 };
113
114 const readItemFromCursor = (cursorIndex, itemIndex, callback) => {
115 const request = requests[cursorIndex];
116 request.onsuccess = testCase.step_func(() => {
117 const cursor = request.result;
118 cursors[cursorIndex] = cursor;
119 checkCursorState(cursorIndex, itemIndex);
120 callback();
121 });
122
123 const cursor = cursors[cursorIndex];
124 cursor.continue();
125 };
126
127 // We open all the cursors one at a time, then cycle through the cursors and
128 // call continue() on each of them. This access pattern causes maximal
129 // trashing to an LRU cursor cache. Eviction scheme aside, any cache will
130 // have to evict some cursors, and this access pattern verifies that the
131 // cache correctly restores the state of evicted cursors.
132 const steps = [];
133 for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
134 steps.push(openCursor.bind(null, cursorIndex));
135 for (let itemIndex = 1; itemIndex < itemCount; ++itemIndex) {
136 for (let cursorIndex = 0; cursorIndex < cursorCount; ++cursorIndex)
137 steps.push(readItemFromCursor.bind(null, cursorIndex, itemIndex));
138 }
139
140 const runStep = (stepIndex) => {
141 if (stepIndex === steps.length) {
142 resolve();
143 return;
144 }
145 steps[stepIndex](() => { runStep(stepIndex + 1); });
146 };
147 runStep(0);
148 });
149 }
150
151 for (let cursorCount of [1, 10, 100, 500]) {
152 promise_test(testCase => {
153 return createDatabase(testCase, (database, transaction) => {
154 const store = database.createObjectStore('cache',
155 { keyPath: 'key', autoIncrement: true });
156 }).then(database => {
157 return populateTestStore(testCase, database, cursorCount).then(
158 () => database);
159 }).then(database => {
160 database.close();
161 }).then(() => {
162 return openDatabase(testCase);
163 }).then(database => {
164 const transaction = database.transaction('cache', 'readonly');
165 const store = transaction.objectStore('cache');
166 return interleaveCursors(testCase, store, cursorCount).then(
jsbell 2017/03/23 21:20:01 This return means the transaction.onXXX below won'
pwnall 2017/03/23 22:08:27 Done. Durr! Thanks for catching that!
167 () => database);
168
169 transaction.onerror = () => { reject(transaction.error); };
jsbell 2017/03/23 21:20:01 Same as above - don't bother with transaction.oner
pwnall 2017/03/23 22:08:27 Done.
170 transaction.onabort = () => { reject(transaction.error); };
171 }).then(database => {
172 database.close();
173 indexedDB.deleteDatabase(database.name);
174 });
175 }, `${cursorCount} cursors`);
176 }
177 </script>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698