Do 2 PRs per cycle, oldest first (by create date) (#640)

diff --git a/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests.dart b/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests.dart
index c262280..e755a49 100644
--- a/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests.dart
+++ b/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests.dart
@@ -18,6 +18,11 @@
 
 import 'check_for_waiting_pull_requests_queries.dart';
 
+/// Maximum number of pull requests to merge on each check.
+/// This should be kept reasonably low to avoid flooding infra when the tree
+/// goes green.
+const int _kMergeCountPerCycle = 2;
+
 @immutable
 class CheckForWaitingPullRequests extends ApiRequestHandler<Body> {
   const CheckForWaitingPullRequests(
@@ -46,7 +51,7 @@
     Logging log,
     GraphQLClient client,
   ) async {
-    bool hasMerged = false;
+    int mergeCount = 0;
     final Map<String, dynamic> data = await _queryGraphQL(
       owner,
       name,
@@ -54,13 +59,16 @@
       client,
     );
     for (_AutoMergeQueryResult queryResult in _parseQueryData(data)) {
-      if (!hasMerged && queryResult.shouldMerge) {
-        hasMerged = await _mergePullRequest(
+      if (mergeCount < _kMergeCountPerCycle && queryResult.shouldMerge) {
+        final bool merged = await _mergePullRequest(
           queryResult.graphQLId,
           queryResult.sha,
           log,
           client,
         );
+        if (merged) {
+          mergeCount++;
+        }
       } else if (queryResult.shouldRemoveLabel) {
         await _removeLabel(
           queryResult.graphQLId,
diff --git a/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests_queries.dart b/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests_queries.dart
index 6f17af4..915fc2b 100644
--- a/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests_queries.dart
+++ b/app_dart/lib/src/request_handlers/check_for_waiting_pull_requests_queries.dart
@@ -8,7 +8,7 @@
     labels(first: 1, query: $sLabelName) {
       nodes {
         id
-        pullRequests(first: 100, states: OPEN) {
+        pullRequests(first: 100, states: OPEN, orderBy: {direction: ASC, field: CREATED_AT}) {
           nodes {
             author {
               login
diff --git a/app_dart/test/request_handlers/check_for_waiting_pull_requests_test.dart b/app_dart/test/request_handlers/check_for_waiting_pull_requests_test.dart
index 38ed261..0ee3a40 100644
--- a/app_dart/test/request_handlers/check_for_waiting_pull_requests_test.dart
+++ b/app_dart/test/request_handlers/check_for_waiting_pull_requests_test.dart
@@ -206,7 +206,8 @@
       );
     });
 
-    test('Merges first PR in list, all successful', () async {
+    test('Merges first 2 PRs in list, all successful', () async {
+      flutterRepoPRs.add(PullRequestHelper());
       flutterRepoPRs.add(PullRequestHelper());
       flutterRepoPRs.add(PullRequestHelper()); // will be ignored.
       engineRepoPRs.add(PullRequestHelper());
@@ -220,7 +221,66 @@
           MutationOptions(
             document: mergePullRequestMutation,
             variables: <String, dynamic>{
-              'id': flutterRepoPRs.first.id,
+              'id': flutterRepoPRs[0].id,
+              'oid': oid,
+            },
+          ),
+          MutationOptions(
+            document: mergePullRequestMutation,
+            variables: <String, dynamic>{
+              'id': flutterRepoPRs[1].id,
+              'oid': oid,
+            },
+          ),
+          MutationOptions(
+            document: mergePullRequestMutation,
+            variables: <String, dynamic>{
+              'id': engineRepoPRs.first.id,
+              'oid': oid,
+            },
+          ),
+        ],
+      );
+    });
+
+    test('Merges 1st and 3rd PR, 2nd failed', () async {
+      flutterRepoPRs.add(PullRequestHelper());
+      flutterRepoPRs.add(PullRequestHelper(
+          lastCommitStatuses: const <StatusHelper>[
+            StatusHelper.cirrusFailure
+          ])); // not merged
+      flutterRepoPRs.add(PullRequestHelper());
+      engineRepoPRs.add(PullRequestHelper());
+
+      await tester.get(handler);
+
+      _verifyQueries();
+
+      githubGraphQLClient.verifyMutations(
+        <MutationOptions>[
+          MutationOptions(
+            document: mergePullRequestMutation,
+            variables: <String, dynamic>{
+              'id': flutterRepoPRs[0].id,
+              'oid': oid,
+            },
+          ),
+          MutationOptions(
+            document: removeLabelMutation,
+            variables: <String, dynamic>{
+              'id': flutterRepoPRs[1].id,
+              'labelId': base64LabelId,
+              'sBody': '''
+This pull request is not suitable for automatic merging in its current state.
+
+- The status or check suite Cirrus CI has failed. Please fix the issues identified (or deflake) before re-applying this label.
+''',
+            },
+          ),
+          MutationOptions(
+            document: mergePullRequestMutation,
+            variables: <String, dynamic>{
+              'id': flutterRepoPRs[2].id,
               'oid': oid,
             },
           ),