This is an automated email from the ASF dual-hosted git repository.

joscorbe pushed a commit to branch old-revisions-cleanup
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git

commit be618911d038785a180bc6960f25a03de5c22cac
Author: Jose Cordero <corde...@adobe.com>
AuthorDate: Fri Nov 3 15:33:01 2023 +0100

    More tests added (WIP)
---
 .../document/DocumentRevisionCleanupHelper.java    |  11 +-
 .../DocumentRevisionCleanupHelperTest.java         | 164 +++++++++++++++++----
 2 files changed, 145 insertions(+), 30 deletions(-)

diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelper.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelper.java
index 1cefd6f8f1..77c96ea465 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelper.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelper.java
@@ -93,8 +93,10 @@ public class DocumentRevisionCleanupHelper {
         // The first entry in "_deleted" has to be kept, as is when the 
document was created
         NavigableMap<Revision, String> deletedRevisions = 
((NavigableMap<Revision, String>) workingDocument.get("_deleted"));
         if (deletedRevisions != null && !deletedRevisions.isEmpty()) {
+            // TODO: This is just a check to ensure the order is the expected
+            assert(deletedRevisions.descendingMap().lastKey().getTimestamp() 
<= deletedRevisions.descendingMap().firstKey().getTimestamp());
+
             Revision createdRevision = 
deletedRevisions.descendingMap().lastKey();
-            addCandidateRevisionToClean(createdRevision);
             addBlockedRevisionToKeep(createdRevision);
         }
 
@@ -121,10 +123,11 @@ public class DocumentRevisionCleanupHelper {
      * @param unit the unit of time
      */
     protected void markRevisionsNewerThanThresholdToPreserve(long amount, 
ChronoUnit unit) {
-        long twentyFiveHoursAgoMillis = Instant.now().minus(amount, 
unit).toEpochMilli();
+        // TODO: Should we add a buffer here? Maybe 1 minute or even 1 hour?
+        long thresholdToPreserve = Instant.now().minus(amount, 
unit).toEpochMilli();
         for (TreeSet<Revision> revisionSet : 
candidateRevisionsToClean.values()) {
             for (Revision revision : revisionSet) {
-                if (revision.getTimestamp() > twentyFiveHoursAgoMillis) {
+                if (revision.getTimestamp() > thresholdToPreserve) {
                     addBlockedRevisionToKeep(revision);
                 }
             }
@@ -178,7 +181,7 @@ public class DocumentRevisionCleanupHelper {
      * Step 5:
      * Removes for each clusterId the revisions in the Map, that were blocked 
in the methods above.
      */
-    private void removeCandidatesInList(SortedMap<Integer, TreeSet<Revision>> 
revisions) {
+    protected void removeCandidatesInList(SortedMap<Integer, 
TreeSet<Revision>> revisions) {
         revisions.forEach((key, value) -> {
             if (candidateRevisionsToClean.containsKey(key)) {
                 candidateRevisionsToClean.get(key).removeAll(value);
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelperTest.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelperTest.java
index 01daf58358..add00b08f5 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelperTest.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentRevisionCleanupHelperTest.java
@@ -1,25 +1,29 @@
 package org.apache.jackrabbit.oak.plugins.document;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.collect.Maps;
-import org.mockito.Mockito;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.IOException;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
-import static org.apache.jackrabbit.oak.plugins.document.Collection.SETTINGS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -77,50 +81,158 @@ public class DocumentRevisionCleanupHelperTest {
 
         documentRevisionCleanupHelper.classifyAndMapRevisionsAndProperties();
         documentRevisionCleanupHelper.markCheckpointRevisionsToPreserve();
+        
documentRevisionCleanupHelper.removeCandidatesInList(documentRevisionCleanupHelper.getBlockedRevisionsToKeep());
 
         // The revisions blocked should be:
         //  - r105000000-0-1 (blocked by checkpoint r109000000-0-1)
         //  - r115000000-0-1 (blocked by checkpoint r119000000-0-1)
-
         assertEquals(Set.of(revisionB, revisionD), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1));
+        assertEquals(Set.of(revisionA, revisionC, revisionE, revisionF), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1));
+    }
+
+    @Test
+    public void testRecentRevisionsArePreserved() throws IOException {
+        StringBuilder jsonPropBuilder = new StringBuilder("'prop1': {");
+        StringBuilder jsonRevisionsBuilder = new StringBuilder("'_revisions': 
{");
+
+        Instant currentTime = Instant.now();
+        List<Revision> revisions = new ArrayList<>();
+        for (int i = 29; i >= 0; i--) {
+            long timestamp = currentTime.minus(i, 
ChronoUnit.HOURS).toEpochMilli();
+            Revision revision = new Revision(timestamp, 0, 1);
+            revisions.add(revision);
+
+            jsonPropBuilder.append("'").append(revision).append("': 
'value").append(i).append("', ");
+            jsonRevisionsBuilder.append("'").append(revision).append("': 'c', 
");
+        }
+
+        // 23 hours and 59 minutes ago -> Should be preserved
+        long timestamp = currentTime.minus(23, ChronoUnit.HOURS).minus(59, 
ChronoUnit.MINUTES).toEpochMilli();
+        Revision revision = new Revision(timestamp, 0, 1);
+        revisions.add(revision);  // revisions[30]
+        jsonPropBuilder.append("'").append(revision).append("': 
'value").append(30).append("', ");
+        jsonRevisionsBuilder.append("'").append(revision).append("': 'c', ");
+
+        // 24 hours and 1 minute ago -> Should be candidate to clean
+        timestamp = currentTime.minus(24, ChronoUnit.HOURS).minus(1, 
ChronoUnit.MINUTES).toEpochMilli();
+        revision = new Revision(timestamp, 0, 1);
+        revisions.add(revision);  // revisions[31]
+        jsonPropBuilder.append("'").append(revision).append("': 
'value").append(30).append("'");
+        jsonRevisionsBuilder.append("'").append(revision).append("': 'c'");
+
+        jsonPropBuilder.append("}");
+        jsonRevisionsBuilder.append("}");
+        StringBuilder jsonBuilder = new StringBuilder("{");
+        jsonBuilder.append(jsonPropBuilder).append(", 
").append(jsonRevisionsBuilder).append("}");
+
+        // revisions.forEach(rev -> 
System.out.println(rev.toReadableString()));
+
+        prepareDocumentMock(jsonBuilder.toString());
+
+        documentRevisionCleanupHelper.classifyAndMapRevisionsAndProperties();
+        
documentRevisionCleanupHelper.markRevisionsNewerThanThresholdToPreserve(24, 
ChronoUnit.HOURS);
+        
documentRevisionCleanupHelper.removeCandidatesInList(documentRevisionCleanupHelper.getBlockedRevisionsToKeep());
+
+        // The candidate revisions should be the 6 oldest ones (current time 
-29, -28, -27, -26, -25, -24)
+        // and the one created 24 hours and 1 minute ago
+        Set<Revision> expectedCandidateRevisions = Stream.concat(
+                revisions.subList(0, 6).stream(), Stream.of(revisions.get(31))
+        ).collect(Collectors.toSet());
 
+        // Blocked revisions are all the 24 revisions created in the last 24 
hours, and the one created 23 hours and 59 minutes ago
+        Set<Revision> expectedBlockedRevisions = revisions.subList(6, 
31).stream().collect(Collectors.toSet());
 
-        /*assertEquals(Set.of(revisionC), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1));
-        assertEquals(Set.of(revisionA, revisionB, revisionC), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1));*/
+        assertEquals(expectedCandidateRevisions, 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1));
+        assertEquals(expectedBlockedRevisions, 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1));
     }
 
     @Test
     public void testInitializeCleanupProcessMultipleClusters() throws 
IOException {
-        Revision revisionA = Revision.fromString("r100000000-0-1");
-        Revision revisionB = Revision.fromString("r105000000-0-2");
-        Revision revisionC = Revision.fromString("r110000000-0-3");
-        Revision revisionD = Revision.fromString("r115000000-0-1");
-        Revision revisionE = Revision.fromString("r120000000-0-2");
-        Revision revisionF = Revision.fromString("r125000000-0-3");
-
+        List<Revision> revs = new ArrayList<>();
+
+        // Some initial revisions (0-2)
+        revs.add(Revision.fromString("r100000000-0-1"));
+        revs.add(Revision.fromString("r100000000-0-2"));
+        revs.add(Revision.fromString("r100000000-0-3"));
+
+        // Blocked by first checkpoint r109000000 (3-5)
+        revs.add(Revision.fromString("r106000000-0-1"));
+        revs.add(Revision.fromString("r105000000-0-2"));
+        revs.add(Revision.fromString("r108000000-0-3"));
+
+        // Some unblocked revisions in middle (6-10)
+        revs.add(Revision.fromString("r110000000-0-3"));
+        revs.add(Revision.fromString("r110000000-1-3"));
+        revs.add(Revision.fromString("r114000000-0-2"));
+        revs.add(Revision.fromString("r115000000-0-1"));
+        revs.add(Revision.fromString("r118000000-0-1"));
+
+        // Blocked by second checkpoint r118000000 (11-13)
+        revs.add(Revision.fromString("r118000000-1-1"));
+        revs.add(Revision.fromString("r118000000-0-2"));
+        revs.add(Revision.fromString("r118000000-0-3"));
+
+        // Some more unblocked revisions (14-19)
+        revs.add(Revision.fromString("r120000000-0-1"));
+        revs.add(Revision.fromString("r122000000-0-2"));
+        revs.add(Revision.fromString("r122000000-1-2"));
+        revs.add(Revision.fromString("r123000000-0-2"));
+        revs.add(Revision.fromString("r125000000-0-3"));
+        revs.add(Revision.fromString("r130000000-0-1"));
+
+        // Last revision (20-22)
+        revs.add(Revision.fromString("r130000000-1-1"));
+        revs.add(Revision.fromString("r130000000-0-2"));
+        revs.add(Revision.fromString("r130000000-0-3"));
+
+        // Checkpoint revisions
         Revision checkpoint1 = Revision.fromString("r109000000-0-1");
         Revision checkpoint2 = Revision.fromString("r119000000-0-1");
 
-        String jsonProperties = "{" +
-                "'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 
'value2', '" + revisionC + "': 'value3', '" + revisionD + "': 'value4', '" + 
revisionE + "': 'value5', '" + revisionF + "': 'value6'}, " +
-                "'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 
'c', '" + revisionC + "': 'c', '" + revisionC + "': 'c', '" + revisionD + "': 
'c', '" + revisionE + "': 'c', '" + revisionF + "': 'c'}" +
-                "}";
+        StringBuilder jsonPropBuilder = new StringBuilder("'prop1': {");
+        StringBuilder jsonRevisionsBuilder = new StringBuilder("'_revisions': 
{");
+
+        for (int i = 0; i < revs.size(); i++) {
+            jsonPropBuilder.append("'").append(revs.get(i)).append("': 
'value").append(i).append("'");
+            jsonRevisionsBuilder.append("'").append(revs.get(i)).append("': 
'c'");
+            if (i < revs.size() - 1) {
+                jsonPropBuilder.append(", ");
+                jsonRevisionsBuilder.append(", ");
+            }
+        }
+
+        jsonPropBuilder.append("}");
+        jsonRevisionsBuilder.append("}");
+        StringBuilder jsonBuilder = new StringBuilder("{");
+        jsonBuilder.append(jsonPropBuilder).append(", 
").append(jsonRevisionsBuilder).append("}");
+
         String jsonCheckpoints = "{" +
-                "'" + checkpoint1 + "': 
{'expires':'200000000','rv':'r109000000-0-1,r109000000-0-1'}," +
-                "'" + checkpoint2 + "': 
{'expires':'200000000','rv':'r119000000-0-1,r109000000-0-1'}" +
+                "'" + checkpoint1 + "': 
{'expires':'200000000','rv':'r109000000-0-1,r109000000-0-2,r109000000-0-3'}," +
+                "'" + checkpoint2 + "': 
{'expires':'200000000','rv':'r118000000-1-1,r118000000-0-2,r118000000-0-3'}" +
                 "}";
 
 
-        prepareDocumentMock(jsonProperties);
+        prepareDocumentMock(jsonBuilder.toString());
         prepareCheckpointsMock(jsonCheckpoints);
 
         documentRevisionCleanupHelper.initializeCleanupProcess();
 
         // The revisions blocked should be:
-        //
-
-        assertEquals(Set.of(revisionC), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1));
-        assertEquals(Set.of(revisionA, revisionB, revisionC), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1));
+        //  - r106000000-0-1, r118000000-0-2, r108000000-0-3 (referenced by 
checkpoint 1)
+        //  - r118000000-1-1, r118000000-0-2, r118000000-0-3 (referenced by 
checkpoint 2)
+        //  - r130000000-1-1, r130000000-0-2, r130000000-0-3 (last revision)
+        assertEquals(Set.of(revs.get(3), revs.get(11), revs.get(20)), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1));
+        assertEquals(Set.of(revs.get(4), revs.get(12), revs.get(21)), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(2));
+        assertEquals(Set.of(revs.get(5), revs.get(13), revs.get(22)), 
documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(3));
+
+        // Rest of revisions are candidates to clean
+        assertEquals(Set.of(revs.get(0), revs.get(9), revs.get(10), 
revs.get(14), revs.get(19)), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1));
+        assertEquals(Set.of(revs.get(1), revs.get(8), revs.get(15), 
revs.get(16), revs.get(17)), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(2));
+        assertEquals(Set.of(revs.get(2), revs.get(6), revs.get(7), 
revs.get(18)), 
documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(3));
+
+        
assertTrue(Collections.disjoint(documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(1),
 documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(1)));
+        
assertTrue(Collections.disjoint(documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(2),
 documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(2)));
+        
assertTrue(Collections.disjoint(documentRevisionCleanupHelper.getBlockedRevisionsToKeep().get(3),
 documentRevisionCleanupHelper.getCandidateRevisionsToClean().get(3)));
     }
 
     @Test

Reply via email to