[cloud functions] Fetch tryjob approvals for all commits in blamelist.

Change-Id: I2ee8e6a50f4e711d8f374cb3fb064074ee0074ea
Reviewed-on: https://dart-review.googlesource.com/c/dart_ci/+/127901
Reviewed-by: Alexander Thomas <athom@google.com>
diff --git a/functions/node/builder.dart b/functions/node/builder.dart
index d8bd9fa..89cc1d4 100644
--- a/functions/node/builder.dart
+++ b/functions/node/builder.dart
@@ -58,7 +58,7 @@
   int buildNumber;
   int startIndex;
   int endIndex;
-  Set<String> tryApprovals;
+  Set<String> tryApprovals = {};
   bool success = true; // Changed to false if any unapproved failure is seen.
   int countChanges = 0;
   int commitsFetched;
@@ -98,27 +98,34 @@
   /// Saves the commit indices of the start and end of the blamelist.
   Future<void> storeBuildCommitsInfo() async {
     // Get indices of change.  Range includes startIndex and endIndex.
-    var commit = await firestore.getCommit(commitHash);
-    if (commit == null) {
+    var endCommit = await firestore.getCommit(commitHash);
+    if (endCommit == null) {
       await getMissingCommits();
-      commit = await firestore.getCommit(commitHash);
-      if (commit == null) {
+      endCommit = await firestore.getCommit(commitHash);
+      if (endCommit == null) {
         error('Result received with unknown commit hash $commitHash');
       }
     }
-    endIndex = commit['index'];
-    if (commit.containsKey('review')) {
-      tryApprovals = {
-        for (final result in await firestore.tryApprovals(commit['review']))
-          testResult(result)
-      };
-    }
+    endIndex = endCommit['index'];
     // If this is a new builder, use the current commit as a trivial blamelist.
     if (firstResult['previous_commit_hash'] == null) {
       startIndex = endIndex;
     } else {
-      commit = await firestore.getCommit(firstResult['previous_commit_hash']);
-      startIndex = commit['index'] + 1;
+      final startCommit =
+          await firestore.getCommit(firstResult['previous_commit_hash']);
+      startIndex = startCommit['index'] + 1;
+    }
+    Future<void> fetchApprovals(Map<String, dynamic> commit) async {
+      if (commit.containsKey('review')) {
+        for (final result in await firestore.tryApprovals(commit['review'])) {
+          tryApprovals.add(testResult(result));
+        }
+      }
+    }
+
+    await fetchApprovals(endCommit);
+    for (var index = startIndex; index < endIndex; ++index) {
+      await fetchApprovals(await firestore.getCommitByIndex(index));
     }
   }
 
@@ -195,7 +202,7 @@
     var approved;
     String result = await firestore.findResult(change, startIndex, endIndex);
     if (result == null) {
-      approved = tryApprovals?.contains(testResult(change)) ?? false;
+      approved = tryApprovals.contains(testResult(change));
       await firestore.storeResult(change, startIndex, endIndex,
           approved: approved);
     } else {
diff --git a/functions/node/firestore.dart b/functions/node/firestore.dart
index 1bad425..9922606 100644
--- a/functions/node/firestore.dart
+++ b/functions/node/firestore.dart
@@ -11,6 +11,8 @@
 
   Future<Map<String, dynamic>> getCommit(String hash);
 
+  Future<Map<String, dynamic>> getCommitByIndex(int index);
+
   Future<Map<String, dynamic>> getLastCommit();
 
   Future<void> addCommit(String id, Map<String, dynamic> data);
diff --git a/functions/node/firestore_impl.dart b/functions/node/firestore_impl.dart
index 636cd85..b6809ae 100644
--- a/functions/node/firestore_impl.dart
+++ b/functions/node/firestore_impl.dart
@@ -36,6 +36,12 @@
       .get()
       .then((s) => s.exists ? s.data.toMap() : null);
 
+  Future<Map<String, dynamic>> getCommitByIndex(int index) => firestore
+      .collection('commits')
+      .where('index', isEqualTo: index)
+      .get()
+      .then((s) => s.documents.first.data.toMap());
+
   Future<Map<String, dynamic>> getLastCommit() async {
     QuerySnapshot lastCommit = await firestore
         .collection('commits')
diff --git a/functions/node/test/test.dart b/functions/node/test/test.dart
index bf6b27c..5dbb946 100644
--- a/functions/node/test/test.dart
+++ b/functions/node/test/test.dart
@@ -27,6 +27,13 @@
         .thenAnswer((_) => Future.value(existingCommit));
     when(firestore.getCommit(previousCommitHash))
         .thenAnswer((_) => Future.value(previousCommit));
+    when(firestore.getCommitByIndex(any))
+        .thenAnswer((_) => Future.value(commit53));
+    when(firestore.tryApprovals(77779)).thenAnswer((_) => Future(() =>
+        tryjobResults
+            .where((result) => result['review'] == 77779)
+            .where((result) => result['approved'] == true)
+            .toList()));
 
     final builder = Build(
         existingCommitHash, existingCommitChange, null, firestore, client);
@@ -35,7 +42,11 @@
     expect(builder.startIndex, previousCommit['index'] + 1);
     verifyInOrder([
       firestore.getCommit(existingCommitHash),
-      firestore.getCommit(previousCommitHash)
+      firestore.getCommit(previousCommitHash),
+      firestore.getCommitByIndex(50),
+      firestore.tryApprovals(77779),
+      firestore.getCommitByIndex(51),
+      firestore.tryApprovals(77779),
     ]);
     verifyNoMoreInteractions(firestore);
     verifyZeroInteractions(client);
@@ -49,6 +60,8 @@
     final landedResponses = [null, landedCommit];
     when(firestore.getCommit(landedCommitHash))
         .thenAnswer((_) => Future(() => landedResponses.removeAt(0)));
+    when(firestore.getCommitByIndex(53))
+        .thenAnswer((_) => Future.value(commit53));
     when(firestore.getLastCommit()).thenAnswer(
         (_) => Future(() => {...existingCommit, 'id': existingCommitHash}));
     when(firestore.tryApprovals(44445)).thenAnswer((_) => Future(() =>
@@ -56,6 +69,11 @@
             .where((result) => result['review'] == 44445)
             .where((result) => result['approved'] == true)
             .toList()));
+    when(firestore.tryApprovals(77779)).thenAnswer((_) => Future(() =>
+        tryjobResults
+            .where((result) => result['review'] == 77779)
+            .where((result) => result['approved'] == true)
+            .toList()));
     when(firestore.reviewIsLanded(any)).thenAnswer((_) => Future.value(true));
 
     when(client.get(any))
@@ -66,11 +84,12 @@
     await builder.storeBuildCommitsInfo();
     expect(builder.endIndex, landedCommit['index']);
     expect(builder.startIndex, existingCommit['index'] + 1);
-    expect(builder.tryApprovals, {testResult(tryjobResults[0])});
+    expect(builder.tryApprovals,
+        {testResult(tryjobResults[0]), testResult(tryjobResults[1])});
     verifyInOrder([
       verify(firestore.getCommit(landedCommitHash)).called(2),
       verify(firestore.getLastCommit()),
-      verify(firestore.addCommit(gitLogCommitHash, {
+      verify(firestore.addCommit(commit53Hash, {
         'author': 'user@example.com',
         'created': DateTime.parse('2019-11-28 12:07:55 +0000'),
         'index': 53,
@@ -88,7 +107,10 @@
       verify(firestore.reviewIsLanded(44445)),
       // We want to verify firestore.getCommit(landedCommitHash) here,
       // but it is already matched by the earlier check for the same call.
-      verify(firestore.getCommit(existingCommitHash))
+      verify(firestore.getCommit(existingCommitHash)),
+      verify(firestore.tryApprovals(44445)),
+      verify(firestore.getCommitByIndex(53)),
+      verify(firestore.tryApprovals(77779))
     ]);
   });
 
@@ -99,11 +121,18 @@
         .thenAnswer((_) => Future.value(existingCommit));
     when(firestore.getCommit(landedCommitHash))
         .thenAnswer((_) => Future.value(landedCommit));
+    when(firestore.getCommitByIndex(53))
+        .thenAnswer((_) => Future.value(commit53));
     when(firestore.tryApprovals(44445)).thenAnswer((_) => Future(() =>
         tryjobResults
             .where((result) => result['review'] == 44445)
             .where((result) => result['approved'] == true)
             .toList()));
+    when(firestore.tryApprovals(77779)).thenAnswer((_) => Future(() =>
+        tryjobResults
+            .where((result) => result['review'] == 77779)
+            .where((result) => result['approved'] == true)
+            .toList()));
 
     final builder =
         Build(landedCommitHash, landedCommitChange, null, firestore, client);
@@ -111,10 +140,10 @@
 
     verifyZeroInteractions(client);
     verifyInOrder([
-      verify(firestore.updateConfiguration(
-          "dart2js-new-rti-linux-x64-d8", "dart2js-rti-linux-x64-d8")),
-      verify(firestore.findResult(any, 53, 54)),
-      verify(firestore.storeResult(any, 53, 54, approved: true))
+      firestore.updateConfiguration(
+          "dart2js-new-rti-linux-x64-d8", "dart2js-rti-linux-x64-d8"),
+      firestore.findResult(any, 53, 54),
+      firestore.storeResult(any, 53, 54, approved: true)
     ]);
   });
 }
diff --git a/functions/node/test/test_data.dart b/functions/node/test/test_data.dart
index 7912b35..3dd6f2f 100644
--- a/functions/node/test/test_data.dart
+++ b/functions/node/test/test_data.dart
@@ -44,6 +44,15 @@
   'title': 'A commit used for testing, with index 49'
 };
 
+const String commit53Hash = 'commit 53 landing CL 77779 hash';
+Map<String, dynamic> commit53 = {
+  'author': 'user@example.com',
+  'created': DateTime.parse('2019-11-28 12:07:55 +0000'),
+  'index': 53,
+  'title': 'A commit on the git log',
+  'review': 77779
+};
+
 const String landedCommitHash = 'a commit landing a CL hash';
 Map<String, dynamic> landedCommit = {
   'author': 'gerrit_user@example.com',
@@ -91,6 +100,16 @@
     "previous_result": "Pass",
     "approved": true
   },
+  {
+    "review": 77779,
+    "configurations": ["test_configuration"],
+    "name": "test_suite/test_name",
+    "patchset": 5,
+    "result": "RuntimeError",
+    "expected": "CompileTimeError",
+    "previous_result": "CompileTimeError",
+    "approved": true
+  },
 ];
 
 const testBuilder = 'test_builder';
@@ -159,15 +178,13 @@
         "changed": true
       });
 
-String gitLogCommitHash = "a commit fetched from the git log";
-
 String gitilesLog = '''
 )]}'
 {
   "log": [
     {
       "commit": "$landedCommitHash",
-      "parents": ["$gitLogCommitHash"],
+      "parents": ["$commit53Hash"],
       "author": {
         "email": "gerrit_user@example.com"
       },
@@ -177,7 +194,7 @@
       "message": "A commit used for testing tryjob approvals, with index 54\\n\\nDescription of the landed commit\\nin multiple lines.\\n\\u003e Change-Id: I8dcc08b7571137e869a16ceea8cc73539eb02a5a\\n\\u003e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/33337\\n\\nChange-Id: I89b88c3d9f7c743fc340ee73a45c3f57059bcf30\\nReviewed-on: https://dart-review.googlesource.com/c/sdk/+/44445\\n\\n"
     },
     {
-      "commit": "$gitLogCommitHash",
+      "commit": "$commit53Hash",
       "parents": ["$existingCommitHash"],
       "author": {
         "email": "user@example.com"