diff --git a/java/google/registry/tools/VerifyOteCommand.java b/java/google/registry/tools/VerifyOteCommand.java
index 1129c2e7e..5dadfec63 100644
--- a/java/google/registry/tools/VerifyOteCommand.java
+++ b/java/google/registry/tools/VerifyOteCommand.java
@@ -36,7 +36,12 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
-/** Command to verify that a registrar has passed OT&E. */
+/**
+ * Command to verify that a registrar has passed OT&E.
+ *
+ * Outputted stats may be truncated at the point where all tests passed to avoid unnecessarily
+ * loading lots of data.
+ */
@Parameters(
separators = " =",
commandDescription = "Verify passage of OT&E for specified (or all) registrars")
diff --git a/java/google/registry/tools/server/VerifyOteAction.java b/java/google/registry/tools/server/VerifyOteAction.java
index a1eb47aa7..4654b4e9e 100644
--- a/java/google/registry/tools/server/VerifyOteAction.java
+++ b/java/google/registry/tools/server/VerifyOteAction.java
@@ -28,6 +28,8 @@ import com.google.common.base.Predicates;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
+import com.googlecode.objectify.Key;
+import com.googlecode.objectify.cmd.Query;
import google.registry.flows.EppException;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
@@ -44,12 +46,14 @@ import google.registry.request.JsonActionRunner;
import google.registry.request.JsonActionRunner.JsonAction;
import google.registry.request.auth.Auth;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.inject.Inject;
@@ -242,18 +246,30 @@ public class VerifyOteAction implements Runnable, JsonAction {
/**
* Records data in the passed historyEntryStats object on what actions have been performed by
* the four numbered OT&E variants of the registrar name.
+ *
+ *
Stops when it notices that all tests have passed.
*/
HistoryEntryStats recordRegistrarHistory(String registrarName) {
- ImmutableList.Builder clientIds = new ImmutableList.Builder<>();
- for (int i = 1; i <= 4; i++) {
- clientIds.add(String.format("%s-%d", registrarName, i));
- }
- for (HistoryEntry historyEntry :
- ofy().load().type(HistoryEntry.class).filter("clientId in", clientIds.build()).list()) {
+ ImmutableList clientIds =
+ IntStream.rangeClosed(1, 4)
+ .mapToObj(i -> String.format("%s-%d", registrarName, i))
+ .collect(toImmutableList());
+
+ Query query =
+ ofy()
+ .load()
+ .type(HistoryEntry.class)
+ .filter("clientId in", clientIds)
+ .order("modificationTime");
+ for (HistoryEntry historyEntry : query) {
try {
record(historyEntry);
} catch (EppException e) {
- throw new RuntimeException(e);
+ throw new RuntimeException("Couldn't parse history entry " + Key.create(historyEntry), e);
+ }
+ // Break out early if all tests were passed.
+ if (wereAllTestsPassed()) {
+ break;
}
}
return this;
@@ -264,18 +280,19 @@ public class VerifyOteAction implements Runnable, JsonAction {
byte[] xmlBytes = historyEntry.getXmlBytes();
// xmlBytes can be null on contact create and update for safe-harbor compliance.
final Optional eppInput =
- (xmlBytes == null)
- ? Optional.empty()
- : Optional.of(unmarshal(EppInput.class, xmlBytes));
+ (xmlBytes == null) ? Optional.empty() : Optional.of(unmarshal(EppInput.class, xmlBytes));
if (!statCounts.addAll(
- EnumSet.allOf(StatType.class)
- .stream()
+ EnumSet.allOf(StatType.class).stream()
.filter(statType -> statType.matches(historyEntry.getType(), eppInput))
.collect(toImmutableList()))) {
statCounts.add(StatType.UNCLASSIFIED_FLOWS);
}
}
+ boolean wereAllTestsPassed() {
+ return Arrays.stream(StatType.values()).allMatch(s -> statCounts.count(s) >= s.requirement);
+ }
+
/**
* Returns a list of failure messages describing any cases where the passed stats fail to meet
* the required thresholds, or the empty list if all requirements are met.
diff --git a/javatests/google/registry/tools/server/VerifyOteActionTest.java b/javatests/google/registry/tools/server/VerifyOteActionTest.java
index 99e59a216..ab905bd39 100644
--- a/javatests/google/registry/tools/server/VerifyOteActionTest.java
+++ b/javatests/google/registry/tools/server/VerifyOteActionTest.java
@@ -17,9 +17,11 @@ package google.registry.tools.server;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.deleteResource;
import static google.registry.testing.DatastoreHelper.persistResource;
+import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.Type;
import google.registry.testing.AppEngineRule;
@@ -40,9 +42,9 @@ public class VerifyOteActionTest {
private final VerifyOteAction action = new VerifyOteAction();
- HistoryEntry hostDeleteHistoryEntry;
- HistoryEntry domainCreateHistoryEntry;
- HistoryEntry domainRestoreHistoryEntry;
+ private HistoryEntry hostDeleteHistoryEntry;
+ private HistoryEntry domainCreateHistoryEntry;
+ private HistoryEntry domainRestoreHistoryEntry;
@Before
public void init() throws Exception {
@@ -137,12 +139,19 @@ public class VerifyOteActionTest {
.setType(Type.HOST_DELETE)
.setXmlBytes(ToolsTestData.loadBytes("host_delete.xml").read())
.build());
- persistResource(
- new HistoryEntry.Builder()
- .setClientId("blobio-1")
- .setType(Type.HOST_UPDATE)
- .setXmlBytes(ToolsTestData.loadBytes("host_update.xml").read())
- .build());
+ // Persist 10 host updates for a total of 25 history entries. Since these also sort last by
+ // modification time, when these cause all tests to pass, only the first will be recorded and
+ // the rest will be skipped.
+ for (int i = 0; i < 10; i++) {
+ persistResource(
+ new HistoryEntry.Builder()
+ .setClientId("blobio-1")
+ .setType(Type.HOST_UPDATE)
+ .setXmlBytes(ToolsTestData.loadBytes("host_update.xml").read())
+ .setTrid(Trid.create(null, String.format("blahtrid-%d", i)))
+ .setModificationTime(END_OF_TIME)
+ .build());
+ }
}
@Test
@@ -165,7 +174,7 @@ public class VerifyOteActionTest {
ImmutableMap.of("summarize", "true", "registrars", ImmutableList.of("blobio")));
assertThat(response)
.containsExactly(
- "blobio", "# actions: 26 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL");
+ "blobio", "# actions: 35 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL");
}
@Test
@@ -235,7 +244,7 @@ public class VerifyOteActionTest {
+ ".*"
+ "host creates subordinate: 1\n"
+ "host deletes: 0\n"
- + "host updates: 1\n"
+ + "host updates: 10\n"
+ ".*"
+ "Requirements passed: 15/16\n"
+ "Overall OT&E status: FAIL\n";