Flaky Test: storage/indexeddb/cursor-request-cycle.html
https://bugs.webkit.org/show_bug.cgi?id=209796
<rdar://problem/60171737>

Reviewed by Geoffrey Garen.

gc() does not guarantee all objects to be collected in our current implementation. So, instead of verifying all
cursor and request objects are released, we now only check if any of them is collected, which is enough to show
there is no ref cycle.

* storage/indexeddb/cursor-request-cycle-expected.txt:
* storage/indexeddb/cursor-request-cycle-private-expected.txt:
* storage/indexeddb/resources/cursor-request-cycle.js:
(checkCursor):
(isAnyCollected):
(onOpen.otherRequest.onsuccess.otherRequestSuccess.finalRequest.onsuccess):
(onOpen.otherRequest.onsuccess):
(onOpen):
(onOpen.cursorRequest.onsuccess): Deleted.
(onOpen.otherRequest.onsuccess.otherRequestSuccess.cursorRequest.onsuccess): Deleted.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@259462 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 22e8bce..e84723a 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,26 @@
+2020-04-03  Sihui Liu  <sihui_liu@apple.com>
+
+        Flaky Test: storage/indexeddb/cursor-request-cycle.html
+        https://bugs.webkit.org/show_bug.cgi?id=209796
+        <rdar://problem/60171737>
+
+        Reviewed by Geoffrey Garen.
+
+        gc() does not guarantee all objects to be collected in our current implementation. So, instead of verifying all
+        cursor and request objects are released, we now only check if any of them is collected, which is enough to show
+        there is no ref cycle.
+
+        * storage/indexeddb/cursor-request-cycle-expected.txt:
+        * storage/indexeddb/cursor-request-cycle-private-expected.txt:
+        * storage/indexeddb/resources/cursor-request-cycle.js:
+        (checkCursor):
+        (isAnyCollected):
+        (onOpen.otherRequest.onsuccess.otherRequestSuccess.finalRequest.onsuccess):
+        (onOpen.otherRequest.onsuccess):
+        (onOpen):
+        (onOpen.cursorRequest.onsuccess): Deleted.
+        (onOpen.otherRequest.onsuccess.otherRequestSuccess.cursorRequest.onsuccess): Deleted.
+
 2020-04-03  Zalan Bujtas  <zalan@apple.com>
 
         [MultiColumn] Infinite loop in RenderBlockFlow::pushToNextPageWithMinimumLogicalHeight
diff --git a/LayoutTests/storage/indexeddb/cursor-request-cycle-expected.txt b/LayoutTests/storage/indexeddb/cursor-request-cycle-expected.txt
index 6184653..4fd4dd2 100644
--- a/LayoutTests/storage/indexeddb/cursor-request-cycle-expected.txt
+++ b/LayoutTests/storage/indexeddb/cursor-request-cycle-expected.txt
@@ -14,49 +14,33 @@
 
 onOpen():
 db = event.target.result
-tx = db.transaction('store')
+tx = db.transaction('store', 'readonly')
 store = tx.objectStore('store')
-cursorRequest = store.openCursor()
+Create 1000 cursorRequests and check their results in otherRequestSuccess().
 otherRequest = store.get(0)
 
-openCursorRequestSuccess():
-Result will be checked later, to ensure that lazy access is safe
-
 otherRequestSuccess():
-Verify that the request's result can be accessed lazily:
+Verify that results of openCursor requests can be accessed lazily.
 gc()
-cursor = cursorRequest.result
-PASS cursor is non-null.
-PASS cursor.key is "key1"
-PASS cursor.value is "value1"
-cursorRequest.extra = 123
-cursor.extra = 456
-Ensure request is not released if cursor is still around.
-cursorRequest = null
+Ensure requests are not released if cursors are still around.
+cursorRequests = null
 gc()
-PASS cursorRequestObservation.wasCollected is false
-cursor.continue()
-cursor = null
+PASS isAnyCollected(cursorRequestObservers) is false
+Ensure requests are not released if they are pending.
+cursors = null
 gc()
-PASS cursorObservation.wasCollected is false
-PASS cursorRequestObservation.wasCollected is false
+PASS isAnyCollected(cursorObservers) is false
+PASS isAnyCollected(cursorRequestObservers) is false
 finalRequest = store.get(0)
 
-cursorContinueSuccess():
-cursor = event.target.result
-PASS cursor is non-null.
-PASS cursor.key is "key2"
-PASS cursor.value is "value2"
-PASS event.target.extra is 123
-PASS cursor.extra is 456
-
 finalRequestSuccess():
-PASS cursor.key is "key2"
-PASS cursor.value is "value2"
-cursor = null
+Ensure requests and cursors are released.
+PASS cursors is non-null.
+PASS cursors.length is 1000
+cursors = null
 gc()
-PASS cursorRequestObservation.wasCollected is true
-PASS cursorObservation.wasCollected is true
+PASS isAnyCollected(cursorObservers) is true
+PASS isAnyCollected(cursorRequestObservers) is true
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/LayoutTests/storage/indexeddb/cursor-request-cycle-private-expected.txt b/LayoutTests/storage/indexeddb/cursor-request-cycle-private-expected.txt
index 6184653..4fd4dd2 100644
--- a/LayoutTests/storage/indexeddb/cursor-request-cycle-private-expected.txt
+++ b/LayoutTests/storage/indexeddb/cursor-request-cycle-private-expected.txt
@@ -14,49 +14,33 @@
 
 onOpen():
 db = event.target.result
-tx = db.transaction('store')
+tx = db.transaction('store', 'readonly')
 store = tx.objectStore('store')
-cursorRequest = store.openCursor()
+Create 1000 cursorRequests and check their results in otherRequestSuccess().
 otherRequest = store.get(0)
 
-openCursorRequestSuccess():
-Result will be checked later, to ensure that lazy access is safe
-
 otherRequestSuccess():
-Verify that the request's result can be accessed lazily:
+Verify that results of openCursor requests can be accessed lazily.
 gc()
-cursor = cursorRequest.result
-PASS cursor is non-null.
-PASS cursor.key is "key1"
-PASS cursor.value is "value1"
-cursorRequest.extra = 123
-cursor.extra = 456
-Ensure request is not released if cursor is still around.
-cursorRequest = null
+Ensure requests are not released if cursors are still around.
+cursorRequests = null
 gc()
-PASS cursorRequestObservation.wasCollected is false
-cursor.continue()
-cursor = null
+PASS isAnyCollected(cursorRequestObservers) is false
+Ensure requests are not released if they are pending.
+cursors = null
 gc()
-PASS cursorObservation.wasCollected is false
-PASS cursorRequestObservation.wasCollected is false
+PASS isAnyCollected(cursorObservers) is false
+PASS isAnyCollected(cursorRequestObservers) is false
 finalRequest = store.get(0)
 
-cursorContinueSuccess():
-cursor = event.target.result
-PASS cursor is non-null.
-PASS cursor.key is "key2"
-PASS cursor.value is "value2"
-PASS event.target.extra is 123
-PASS cursor.extra is 456
-
 finalRequestSuccess():
-PASS cursor.key is "key2"
-PASS cursor.value is "value2"
-cursor = null
+Ensure requests and cursors are released.
+PASS cursors is non-null.
+PASS cursors.length is 1000
+cursors = null
 gc()
-PASS cursorRequestObservation.wasCollected is true
-PASS cursorObservation.wasCollected is true
+PASS isAnyCollected(cursorObservers) is true
+PASS isAnyCollected(cursorRequestObservers) is true
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/LayoutTests/storage/indexeddb/resources/cursor-request-cycle.js b/LayoutTests/storage/indexeddb/resources/cursor-request-cycle.js
index 646ce1e..e746aeb 100644
--- a/LayoutTests/storage/indexeddb/resources/cursor-request-cycle.js
+++ b/LayoutTests/storage/indexeddb/resources/cursor-request-cycle.js
@@ -16,69 +16,103 @@
     store.put("value2", "key2");
 }
 
+function checkCursor(cursor, target, message)
+{
+    if (!cursor)
+        testFailed(message + ": cursor is null");
+    if (cursor.key != target.key)
+        testFailed(message + ": cursor.key is " + cursor.key + ", should be " + target.key);
+    if (cursor.value != target.value)
+        testFailed(message + ": cursor.value is " + cursor.value + ", should be " + target.value);
+    if (cursor.extra != target.extra)
+        testFailed(message + ": cursor.extra is " + cursor.extra + ", should be " + target.extra);
+}
+
+function isAnyCollected(observers)
+{
+    for (let observer of observers) {
+        if (observer.wasCollected)
+            return true;
+    }
+    return false;
+}
+
 function onOpen(evt)
 {
     preamble(evt);
     evalAndLog("db = event.target.result");
-    evalAndLog("tx = db.transaction('store')");
+    evalAndLog("tx = db.transaction('store', 'readonly')");
     evalAndLog("store = tx.objectStore('store')");
 
-    evalAndLog("cursorRequest = store.openCursor()");
-    cursorRequest.onsuccess = function openCursorRequestSuccess(evt) {
-        preamble(evt);
-        debug("Result will be checked later, to ensure that lazy access is safe");
-    };
+    debug("Create 1000 cursorRequests and check their results in otherRequestSuccess().");
+    cursorRequests = [];
+    cursorRequestObservers = [];
+    for (let i = 0; i < 1000; ++i) {
+        cursorRequest = store.openCursor();
+        cursorRequests.push(cursorRequest);
+        cursorRequestObservers.push(internals.observeGC(cursorRequest));
+        cursorRequest = null;
+    }
 
     evalAndLog("otherRequest = store.get(0)");
+
     otherRequest.onsuccess = function otherRequestSuccess(evt) {
         preamble(evt);
 
-        debug("Verify that the request's result can be accessed lazily:");
+        debug("Verify that results of openCursor requests can be accessed lazily.");
         evalAndLog("gc()");
 
-        evalAndLog("cursor = cursorRequest.result");
-        shouldBeNonNull("cursor");
-        shouldBeEqualToString("cursor.key", "key1");
-        shouldBeEqualToString("cursor.value", "value1");
-        evalAndLog("cursorRequest.extra = 123");
-        evalAndLog("cursor.extra = 456");
+        cursors = [];
+        cursorObservers = [];
+        var target = { key:"key1", value:"value1" };
+        for (var i = 0; i < cursorRequests.length; i++) {
+            cursor = cursorRequests[i].result;
+            checkCursor(cursor, target, "Examine cursorRequests[" + i + "]");
+            cursorRequests[i].extra = "123";
+            cursor.extra = "456";
+            cursors.push(cursor);
+            cursorObservers.push(internals.observeGC(cursor));
+            cursor = null;
 
-        // Assign a new handler to inspect the request and cursor indirectly.
-        cursorRequest.onsuccess = function cursorContinueSuccess(evt) {
-            preamble(evt);
-            evalAndLog("cursor = event.target.result");
-            shouldBeNonNull("cursor");
-            shouldBeEqualToString("cursor.key", "key2");
-            shouldBeEqualToString("cursor.value", "value2");
-            shouldBe("event.target.extra", "123");
-            shouldBe("cursor.extra", "456");
-        };
+            // Assign a new handler to inspect the request and cursor indirectly.
+            cursorRequests[i].onsuccess = (event)=>{
+                cursor = event.target.result;
+                var target = { key: "key2", value:"value2", extra:"456" };
+                checkCursor(cursor, target, "Examine cursor after continue()");
+                if (event.target.extra != "123") {
+                    testFailed("Examine cursor after continue(): event.target.extra is " + event.target.extra + ", should be 123");
+                }
+                cursors.push(cursor);
+            };
+        }
 
-        debug("Ensure request is not released if cursor is still around.");
-        cursorRequestObservation = internals.observeGC(cursorRequest);
-        evalAndLog("cursorRequest = null");
+        debug("Ensure requests are not released if cursors are still around.");
+        evalAndLog("cursorRequests = null");
         evalAndLog("gc()");
-        shouldBeFalse("cursorRequestObservation.wasCollected");
+        shouldBeFalse("isAnyCollected(cursorRequestObservers)");
 
-        evalAndLog("cursor.continue()");
+        for (var i = 0; i < cursors.length; i++) {
+            cursors[i].continue();
+        }
 
-        cursorObservation = internals.observeGC(cursor);
-        evalAndLog("cursor = null");
+        debug("Ensure requests are not released if they are pending.");
+        evalAndLog("cursors = null"); 
         evalAndLog("gc()");
-        shouldBeFalse("cursorObservation.wasCollected");
-        shouldBeFalse("cursorRequestObservation.wasCollected");
+        shouldBeFalse("isAnyCollected(cursorObservers)");
+        shouldBeFalse("isAnyCollected(cursorRequestObservers)");
+        cursors = []; 
 
         evalAndLog("finalRequest = store.get(0)");
         finalRequest.onsuccess = function finalRequestSuccess(evt) {
             preamble(evt);
-            shouldBeEqualToString("cursor.key", "key2");
-            shouldBeEqualToString("cursor.value", "value2");
-
-            cursorObservation = internals.observeGC(cursor);
-            evalAndLog("cursor = null");
+            debug("Ensure requests and cursors are released.");
+            shouldBeNonNull("cursors");
+            shouldBe("cursors.length", "1000");
+            evalAndLog("cursors = null");
             evalAndLog("gc()");
-            shouldBeTrue("cursorRequestObservation.wasCollected");
-            shouldBeTrue("cursorObservation.wasCollected");
+
+            shouldBeTrue("isAnyCollected(cursorObservers)");
+            shouldBeTrue("isAnyCollected(cursorRequestObservers)");
         };
     };