OLD | NEW |
---|---|
(Empty) | |
1 <!doctype html> | |
2 <meta charset="utf8"> | |
3 <title>IndexedDB: request result events are delivered in order</title> | |
4 <link rel="help" href="https://w3c.github.io/IndexedDB/#abort-transaction"> | |
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 'use strict'; | |
11 | |
12 // Should be large enough to trigger value wrapping in the IndexedDB engines | |
13 // that implement wrapping. | |
14 const wrapThreshold = 128 * 1024; | |
15 | |
16 // Returns an Uint8Array with pseudorandom data. | |
17 function largeValue(size, seed) { | |
jsbell
2017/05/15 23:37:37
Move this to support.js or support-promises.js ?
pwnall
2017/05/19 18:27:33
Done.
| |
18 const buffer = new Uint8Array(size); | |
19 | |
20 // 32-bit xorshift - the seed can't be zero | |
21 let state = 1000 + seed; | |
22 | |
23 for (let i = 0; i < size; ++i) { | |
24 state ^= state << 13; | |
25 state ^= state >> 17; | |
26 state ^= state << 5; | |
27 buffer[i] = state & 0xff; | |
28 } | |
29 | |
30 return buffer; | |
31 } | |
32 | |
33 function populateStore(store) { | |
34 store.put({id: 1, key: 'k1', value: largeValue(wrapThreshold, 1) }); | |
35 store.put({id: 2, key: 'k2', value: ['small-2'] }); | |
36 store.put({id: 3, key: 'k3', value: largeValue(wrapThreshold, 3) }); | |
37 store.put({id: 4, key: 'k4', value: ['small-4'] }); | |
38 } | |
39 | |
40 // Assigns cursor indexes for operations that require open cursors. | |
41 // | |
42 // Returns the number of open cursors required to perform all operations. | |
43 function assignCursors(operations) { | |
44 return cursorCount; | |
45 } | |
46 | |
47 // Opens index cursors for operations that require open cursors. | |
48 // | |
49 // onsuccess is called if all cursors are opened successfully. Otherwise, | |
50 // onerror will be called at least once. | |
51 function openCursors(testCase, index, operations, onerror, onsuccess) { | |
52 let pendingCursors = 0; | |
53 | |
54 nextOperation: | |
55 for (let operation of operations) { | |
56 const opcode = operation[0]; | |
57 const primaryKey = operation[1]; | |
58 let request; | |
59 switch (opcode) { | |
60 case 'continue': | |
61 request = index.openCursor( | |
62 IDBKeyRange.lowerBound(`k${primaryKey - 1}`)); | |
63 break; | |
64 case 'continue-empty': | |
65 // k4 is the last key in the data set, so calling continue() will get | |
66 // the cursor past the end of the store. | |
67 request = index.openCursor(IDBKeyRange.lowerBound('k4')); | |
68 break; | |
69 default: | |
70 continue nextOperation; | |
jsbell
2017/05/15 23:37:37
This label doesn't appear necessary
pwnall
2017/05/19 18:27:33
Done.
| |
71 } | |
72 | |
73 operation[2] = request; | |
74 ++pendingCursors; | |
75 | |
76 request.onsuccess = testCase.step_func(() => { | |
77 --pendingCursors; | |
78 if (!pendingCursors) | |
79 onsuccess(); | |
80 }); | |
81 request.onerror = testCase.step_func(onerror); | |
82 } | |
83 | |
84 if (!pendingCursors) | |
85 onsuccess(); | |
86 } | |
87 | |
88 function doOperation(testCase, store, index, operation, requestId, results) { | |
89 const opcode = operation[0]; | |
90 const primaryKey = operation[1]; | |
91 const cursor = operation[2]; | |
92 | |
93 return new Promise((resolve, reject) => { | |
94 let request; | |
95 switch (opcode) { | |
96 case 'put': // Tests returning a primary key. | |
97 request = store.put( | |
98 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); | |
99 break; | |
100 case 'get': // Tests returning a value. | |
101 case 'get-empty': // Tests returning undefined. | |
102 request = store.get(primaryKey); | |
103 break; | |
104 case 'error': // Tests returning an error. | |
105 request = store.put( | |
106 { key: `k${primaryKey}`, value: [`small-${primaryKey}`] }); | |
107 request.onerror = testCase.step_func(event => { | |
108 results.push([requestId, request.error]); | |
109 resolve(); | |
110 event.preventDefault(); | |
111 }); | |
112 request.onsuccess = testCase.step_func(() => { | |
113 reject(new Error('put with duplicate primary key succeded')); | |
114 }); | |
115 break; | |
116 case 'continue': // Tests returning a key, primary key, and value. | |
117 request = cursor; | |
118 cursor.result.continue(); | |
119 request.onsuccess = testCase.step_func(() => { | |
120 const result = request.result; | |
121 results.push( | |
122 [requestId, result.key, result.primaryKey, result.value]); | |
123 resolve(); | |
124 }); | |
125 request.onerror = null; | |
126 break; | |
127 case 'open': // Tests returning a cursor, key, primary key, and value. | |
128 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); | |
129 request.onsuccess = testCase.step_func(() => { | |
130 const result = request.result; | |
131 results.push( | |
132 [requestId, result.key, result.primaryKey, result.value]); | |
133 resolve(); | |
134 }); | |
135 break; | |
136 case 'continue-empty': // Tests returning a null result. | |
137 request = cursor; | |
138 cursor.result.continue(); | |
139 request.onsuccess = testCase.step_func(() => { | |
140 results.push([requestId, request.result]); | |
141 resolve(); | |
142 }); | |
143 request.onerror = null; | |
144 break; | |
145 case 'open-empty': // Tests returning a null cursor. | |
146 request = index.openCursor(IDBKeyRange.lowerBound(`k${primaryKey}`)); | |
147 request.onsuccess = testCase.step_func(() => { | |
148 const result = request.result; | |
149 results.push([requestId, request.result]); | |
150 resolve(); | |
151 }); | |
152 break; | |
153 case 'count': // Tests returning a numeric result. | |
154 request = index.count(); | |
155 request.onsuccess = testCase.step_func(() => { | |
156 results.push([requestId, request.result]); | |
157 resolve(); | |
158 }); | |
159 break; | |
160 }; | |
161 | |
162 if (!request.onsuccess) { | |
163 request.onsuccess = testCase.step_func(() => { | |
164 results.push([requestId, request.result]); | |
165 resolve(); | |
166 }); | |
167 } | |
168 if (!request.onerror) | |
169 request.onerror = testCase.step_func(event => { | |
170 reject(request.error); | |
171 event.preventDefault(); | |
172 }); | |
173 }); | |
174 } | |
175 | |
176 function checkOperationResult(operation, result, requestId) { | |
177 const opcode = operation[0]; | |
178 const primaryKey = operation[1]; | |
179 | |
180 const expectedValue = (primaryKey == 1 || primaryKey == 3) ? | |
181 largeValue(wrapThreshold, primaryKey) : [`small-${primaryKey}`]; | |
182 | |
183 const requestIndex = result[0]; | |
184 assert_equals( | |
185 requestIndex, requestId, 'result event order should match request order'); | |
186 switch (opcode) { | |
187 case 'put': | |
188 assert_equals( | |
189 result[1], primaryKey, | |
190 "put result should be the new object's primary key"); | |
191 break; | |
192 case 'get': | |
193 assert_equals( | |
194 result[1].id, primaryKey, | |
195 'get result should match put value (primary key)'); | |
196 assert_equals( | |
197 result[1].key, `k${primaryKey}`, | |
198 'get result should match put value (key)'); | |
199 assert_equals( | |
200 result[1].value.join(','), expectedValue.join(','), | |
201 'get result should match put value (nested value)'); | |
202 break; | |
203 case 'get-empty': | |
204 assert_equals( | |
205 result[1], undefined, 'get-empty result should be undefined'); | |
206 break; | |
207 case 'error': | |
208 assert_equals( | |
209 result[1].name, 'ConstraintError', | |
210 'incorrect error from put with duplicate primary key'); | |
211 break; | |
212 case 'continue': | |
213 case 'open': | |
214 assert_equals( | |
215 result[1], `k${primaryKey}`, | |
216 `${opcode} key should match the key in the put value`); | |
217 assert_equals( | |
218 result[2], primaryKey, | |
219 `${opcode} primary key should match the put value's primary key`); | |
220 assert_equals( | |
221 result[3].id, primaryKey, | |
222 `${opcode} value should match put value (primary key)`); | |
223 assert_equals( | |
224 result[3].key, `k${primaryKey}`, | |
225 `${opcode} value should match put value (key)`); | |
226 assert_equals( | |
227 result[3].value.join(','), expectedValue.join(','), | |
228 `${opcode} value should match put value (nested value)`); | |
229 break; | |
230 case 'continue-empty': | |
231 case 'open-empty': | |
232 assert_equals(result[1], null, `${opcode} result should be null`); | |
233 break; | |
234 } | |
235 } | |
236 | |
237 function eventsTest(label, operations) { | |
238 promise_test(testCase => { | |
239 return createDatabase(testCase, (database, transaction) => { | |
240 const store = database.createObjectStore( | |
241 'test-store', { autoIncrement: true, keyPath: 'id' }); | |
242 store.createIndex('test-index', 'key', { unique: true }); | |
243 populateStore(store); | |
244 }).then(database => { | |
245 const transaction = database.transaction(['test-store'], 'readwrite'); | |
246 const store = transaction.objectStore('test-store'); | |
247 const index = store.index('test-index'); | |
248 return new Promise((resolve, reject) => { | |
249 openCursors(testCase, index, operations, reject, () => { | |
250 const results = []; | |
251 const promises = []; | |
252 for (let i = 0; i < operations.length; ++i) { | |
253 const promise = doOperation( | |
254 testCase, store, index, operations[i], i, results); | |
255 promises.push(promise); | |
256 }; | |
257 resolve(Promise.all(promises).then(() => results)); | |
258 }); | |
259 }); | |
260 }).then(results => { | |
261 assert_equals( | |
262 results.length, operations.length, | |
263 'Promise.all should resolve after all sub-promises resolve'); | |
264 for (let i = 0; i < operations.length; ++i) | |
265 checkOperationResult(operations[i], results[i], i); | |
266 }); | |
267 }, label); | |
268 } | |
269 | |
270 eventsTest('small values', [ | |
271 ['get', 2], | |
272 ['count', 4], | |
273 ['continue-empty', null], | |
274 ['get-empty', 5], | |
275 ['put', 5], | |
276 ['open', 2], | |
277 ['continue', 2], | |
278 ['get', 4], | |
279 ['get-empty', 6], | |
280 ['count', 5], | |
281 ['put', 6], | |
282 ['error', 3], | |
283 ['continue', 4], | |
284 ['count', 6], | |
285 ['get-empty', 7], | |
286 ['open', 4], | |
287 ['open-empty', 7], | |
288 ]); | |
289 | |
290 eventsTest('large values', [ | |
291 ['open', 1], | |
292 ['get', 1], | |
293 ['get', 3], | |
294 ['continue', 3], | |
295 ['open', 3], | |
296 ]); | |
297 | |
298 eventsTest('large value followed by small values', [ | |
299 ['get', 1], | |
300 ['open', 2], | |
301 ['continue-empty', null], | |
302 ['get', 2], | |
303 ['get-empty', 5], | |
304 ['count', 4], | |
305 ['continue-empty', null], | |
306 ['open-empty', 5], | |
307 ['put', 5], | |
308 ['error', 1], | |
309 ['continue', 2], | |
310 ['get-empty', 6], | |
311 ]); | |
312 | |
313 eventsTest('large values mixed with small values', [ | |
314 ['get', 1], | |
315 ['get', 2], | |
316 ['get-empty', 5], | |
317 ['count', 4], | |
318 ['continue-empty', null], | |
319 ['open', 1], | |
320 ['continue', 2], | |
321 ['open-empty', 5], | |
322 ['open', 2], | |
323 ['continue-empty', null], | |
324 ['put', 5], | |
325 ['get', 3], | |
326 ['count', 5], | |
327 ['get-empty', 6], | |
328 ['continue', 3], | |
329 ['open-empty', 6], | |
330 ['put', 6], | |
331 ['error', 1], | |
332 ['continue', 2], | |
333 ['open', 4], | |
334 ['get-empty', 7], | |
335 ['continue', 3], | |
336 ['error', 3], | |
337 ['count', 6], | |
338 ]); | |
339 | |
340 </script> | |
OLD | NEW |