From 708338979db0ee23cc4a5ed14205fc4938841ecc Mon Sep 17 00:00:00 2001 From: "Dmitry Grand (dmgr)" Date: Thu, 17 Sep 2026 14:42:40 -0700 Subject: [PATCH 1/4] 176989 ucr flag removal preserving legacy flow for merge queue For presubmit removed support of legacy check run flow if `unifiedCheckRunFlow` flag is not enabled. For merge queue preserved legacy check run flow. `unifiedCheckRunFlow` flag itself will be removed in a separate PR fix: https://github.com/flutter/flutter/issues/192812 --- app_dart/lib/cocoon_service.dart | 1 - .../common/presubmit_completed_check.dart | 11 +- .../presubmit_subscription.dart | 14 +- .../service/firestore/unified_check_run.dart | 8 +- .../lib/src/service/luci_build_service.dart | 8 +- app_dart/lib/src/service/scheduler.dart | 262 +--- .../presubmit_completed_check_test.dart | 37 - .../github/webhook_subscription_test.dart | 22 +- .../presubmit_luci_subscription_test.dart | 433 +++---- .../presubmit_ordered_subscription_test.dart | 8 - .../mapping_http_client_test.mocks.dart | 4 +- .../firestore/unified_check_run_test.dart | 38 +- .../schedule_try_builds_test.dart | 90 +- app_dart/test/service/scheduler_test.dart | 1133 +---------------- .../lib/src/fakes/fake_firestore_service.dart | 5 +- .../lib/src/utilities/mocks.mocks.dart | 280 ++-- .../test/fake_firestore_service_test.dart | 4 +- 17 files changed, 492 insertions(+), 1866 deletions(-) diff --git a/app_dart/lib/cocoon_service.dart b/app_dart/lib/cocoon_service.dart index 2fd0263f54..e54286cac0 100644 --- a/app_dart/lib/cocoon_service.dart +++ b/app_dart/lib/cocoon_service.dart @@ -60,7 +60,6 @@ export 'src/service/config.dart'; export 'src/service/firestore.dart'; export 'src/service/flags/dynamic_config.dart'; export 'src/service/flags/ordered_presubmit_flags.dart'; -export 'src/service/flags/unified_check_run_flow_flags.dart'; export 'src/service/gerrit_service.dart'; export 'src/service/github_checks_service.dart'; export 'src/service/issue_service.dart'; diff --git a/app_dart/lib/src/model/common/presubmit_completed_check.dart b/app_dart/lib/src/model/common/presubmit_completed_check.dart index d7a3139bc6..8bafb12740 100644 --- a/app_dart/lib/src/model/common/presubmit_completed_check.dart +++ b/app_dart/lib/src/model/common/presubmit_completed_check.dart @@ -34,7 +34,6 @@ class PresubmitCompletedJob { final int checkRunId; final int? checkSuiteId; final String? headBranch; - final bool isUnifiedCheckRun; final CiStage? stage; final int? prNum; final int attempt; @@ -53,7 +52,6 @@ class PresubmitCompletedJob { required this.checkRunId, required this.checkSuiteId, required this.headBranch, - required this.isUnifiedCheckRun, this.stage, this.prNum, this.attempt = 1, @@ -80,7 +78,6 @@ class PresubmitCompletedJob { checkRunId: userData.guardCheckRunId ?? userData.checkRunId!, checkSuiteId: userData.checkSuiteId, headBranch: userData.commit.branch, - isUnifiedCheckRun: userData.guardCheckRunId != null, stage: userData.stage, prNum: userData.pullRequestNumber, attempt: _getAttempt(build), @@ -103,7 +100,7 @@ class PresubmitCompletedJob { cocoon_checks.CheckRun get checkRun { return cocoon_checks.CheckRun( id: checkRunId, - name: isUnifiedCheckRun ? Config.kDashboardCheckName : name, + name: Config.kDashboardCheckName, headSha: sha, conclusion: status.toConclusion(), checkSuite: CheckSuite( @@ -121,7 +118,11 @@ class PresubmitCompletedJob { slug: slug, prNum: prNum ?? 0, checkRunId: checkRunId, - stage: stage ?? CiStage.fusionTests, + stage: + stage ?? + (slug == Config.flutterSlug + ? CiStage.fusionTests + : CiStage.genericTests), ); } diff --git a/app_dart/lib/src/request_handlers/presubmit_subscription.dart b/app_dart/lib/src/request_handlers/presubmit_subscription.dart index be629c233a..7bb90a7a9d 100644 --- a/app_dart/lib/src/request_handlers/presubmit_subscription.dart +++ b/app_dart/lib/src/request_handlers/presubmit_subscription.dart @@ -143,10 +143,12 @@ base class PresubmitSubscription extends SubscriptionHandler { tagSet ??= BuildTags.fromStringPairs(build.tags); final builderName = build.builder.builder; var rescheduled = false; - final isUnifiedCheckRun = userData.guardCheckRunId != null; - log.info('Unified Check Run ${isUnifiedCheckRun ? 'Enabled' : 'Disabled'}'); + final isMergeQueue = tagSet.containsType(); + log.info( + 'Processing Build for ${isMergeQueue ? 'Merge Queue' : 'Presubmit'}', + ); if (build.status.isTaskFailed()) { - if (isUnifiedCheckRun) { + if (!isMergeQueue) { // If failed we need summaryMarkdown. For github check run flow this // called in [GithubChecksService.updateCheckStatus(...)] build = await _luciBuildService.getBuildById( @@ -165,7 +167,7 @@ base class PresubmitSubscription extends SubscriptionHandler { if (tagSet.currentAttempt < maxAttempt) { rescheduled = true; log.info('Rerunning failed task: $builderName'); - if (isUnifiedCheckRun) { + if (!isMergeQueue) { await UnifiedCheckRun.reInitializeInProgressJob( firestoreService: _firestore, completedJob: PresubmitCompletedJob.fromBuild( @@ -199,9 +201,9 @@ base class PresubmitSubscription extends SubscriptionHandler { '### ⚠️ Test failed but marked as suppressed on dashboard'; } } - if (!isUnifiedCheckRun) { + if (isMergeQueue) { if (userData.checkRunId == null) { - log.error('checkRunId is null for non-unified check run'); + log.error('checkRunId is required for merge queue builds.'); return; } await _githubChecksService.updateCheckStatus( diff --git a/app_dart/lib/src/service/firestore/unified_check_run.dart b/app_dart/lib/src/service/firestore/unified_check_run.dart index b763671efc..b9b272f88a 100644 --- a/app_dart/lib/src/service/firestore/unified_check_run.dart +++ b/app_dart/lib/src/service/firestore/unified_check_run.dart @@ -36,14 +36,10 @@ final class UnifiedCheckRun { CheckRun? mergeQueueGuard, @visibleForTesting DateTime Function() utcNow = DateTime.timestamp, }) async { - if (dashboardChecks != null && - pullRequest != null && - config.flags.isUnifiedCheckRunFlowEnabledForUser( - pullRequest.user!.login!, - )) { + if (dashboardChecks != null && pullRequest != null) { // Create the presubmit_guard and associated presubmit_job documents. log.info( - 'Storing UnifiedCheckRun data for ${slug.fullName}#${pullRequest.number} as it enabled for user ${pullRequest.user!.login}.', + 'Storing UnifiedCheckRun data for ${slug.fullName}#${pullRequest.number}.', ); // We store the creation time of the guard since there might be several // guards for the same PR created and each new one created after previous diff --git a/app_dart/lib/src/service/luci_build_service.dart b/app_dart/lib/src/service/luci_build_service.dart index 6cbc88e13d..2d81c1804a 100644 --- a/app_dart/lib/src/service/luci_build_service.dart +++ b/app_dart/lib/src/service/luci_build_service.dart @@ -293,8 +293,6 @@ class LuciBuildService { final slug = pullRequest.base!.repo!.slug(); final commitBranch = pullRequest.base!.ref!.replaceAll('refs/heads/', ''); final isFusion = slug == Config.flutterSlug; - final isUnifiedCheckRunFlow = _config.flags - .isUnifiedCheckRunFlowEnabledForUser(pullRequest.user!.login!); final isOrderedPresubmit = _config.flags.isOrderedPresubmitEnabledForUser( pullRequest.user!.login!, ); @@ -307,7 +305,7 @@ class LuciBuildService { late PresubmitUserData userData; // If the unified check run flow is enabled, do not create individual // check runs for each target but use the guard check run instead. - if (isUnifiedCheckRunFlow && dashboardChecks != null) { + if (dashboardChecks != null) { userData = PresubmitUserData( commit: CommitRef(slug: slug, sha: commitSha, branch: commitBranch), guardCheckRunId: dashboardChecks.id!, @@ -326,7 +324,7 @@ class LuciBuildService { for (final MapEntry(key: target, value: attemptNumber) in targets.entries) { // If the unified check run flow is disabled create individual check runs // for each target. - if (!isUnifiedCheckRunFlow || dashboardChecks == null) { + if (dashboardChecks == null) { final checkRun = await _githubChecksUtil.createCheckRun( _config, target.slug, @@ -396,7 +394,7 @@ class LuciBuildService { userData: userData, properties: properties, // if unified check run flow is enabled, use guard check run othervise check run id. - tags: isUnifiedCheckRunFlow && dashboardChecks != null + tags: dashboardChecks != null ? BuildTags([ GuardCheckRunIdBuildTag( guardCheckRunId: dashboardChecks.id!, diff --git a/app_dart/lib/src/service/scheduler.dart b/app_dart/lib/src/service/scheduler.dart index 6914c2c8dc..cd8304dcc5 100644 --- a/app_dart/lib/src/service/scheduler.dart +++ b/app_dart/lib/src/service/scheduler.dart @@ -19,12 +19,10 @@ import '../foundation/utils.dart'; import '../model/ci_yaml/ci_yaml.dart'; import '../model/ci_yaml/target.dart'; import '../model/commit_ref.dart'; -import '../model/common/checks_extension.dart'; import '../model/common/presubmit_completed_check.dart'; import '../model/common/presubmit_guard_conclusion.dart'; import '../model/common/presubmit_job_state.dart'; import '../model/firestore/base.dart'; -import '../model/firestore/ci_staging.dart'; import '../model/firestore/commit.dart' as fs; import '../model/firestore/pr_check_runs.dart'; import '../model/firestore/presubmit_guard.dart'; @@ -349,10 +347,6 @@ class Scheduler { return; } - final isUnifiedCheckRun = _config.flags.isUnifiedCheckRunFlowEnabledForUser( - pullRequest.user!.login!, - ); - // Always cancel running builds so we don't ever schedule duplicates. log.info( 'Attempting to cancel existing presubmit targets for ${pullRequest.number}', @@ -367,12 +361,9 @@ class Scheduler { final lockResult = await lockMergeGroupChecks( slug, sha, - // Override details url of merge queue guard check for users with unified - // check run flow enabled - detailsUrl: isUnifiedCheckRun - ? 'https://flutter-dashboard.appspot.com/#/presubmit?repo=${slug.name}&sha=$sha' - : null, - isUnifiedCheckRun: isUnifiedCheckRun, + detailsUrl: + 'https://flutter-dashboard.appspot.com/#/presubmit?repo=${slug.name}&sha=$sha', + isPresubmit: true, ); final dashboardChecks = lockResult.dashboardChecks; final mergeQueueGuard = lockResult.mergeQueueGuard; @@ -385,11 +376,12 @@ class Scheduler { log.info('Creating presubmit targets for ${pullRequest.number}'); Object? exception; - final isFusion = slug == Config.flutterSlug; - final isPackages = slug == Config.packagesSlug; + final isFlutterRepo = slug == Config.flutterSlug; + final isPackagesRepo = slug == Config.packagesSlug; do { try { - if (!isFusion && !(isPackages && isUnifiedCheckRun)) { + // If it's not flutter or packages, unlock the merge group lock. + if (!isFlutterRepo && !isPackagesRepo) { unlockMergeGroup = true; } @@ -433,7 +425,7 @@ class Scheduler { ); break; } - final presubmitTargets = isFusion + final presubmitTargets = isFlutterRepo ? await _getTestsForStage(pullRequest, CiStage.fusionEngineBuild) : await getPresubmitTargets(pullRequest); final presubmitTriggerTargets = filterTargets( @@ -441,59 +433,45 @@ class Scheduler { builderTriggerList, ); + final stage = isFlutterRepo + ? CiStage.fusionEngineBuild + : CiStage.genericTests; + // When running presubmits for a fusion PR; create a new staging document to track tasks needed // to complete before we can schedule more tests (i.e. build engine artifacts before testing against them). - final EngineArtifacts engineArtifacts; - if (isFusion) { - await UnifiedCheckRun.initializeCiStagingDocument( - firestoreService: _firestore, - slug: slug, - sha: sha, - stage: CiStage.fusionEngineBuild, - tasks: [...presubmitTriggerTargets.map((t) => t.name)], - pullRequest: pullRequest, - config: _config, - dashboardChecks: dashboardChecks, - mergeQueueGuard: mergeQueueGuard, - ); + await UnifiedCheckRun.initializeCiStagingDocument( + firestoreService: _firestore, + slug: slug, + sha: sha, + stage: stage, + tasks: [...presubmitTriggerTargets.map((t) => t.name)], + pullRequest: pullRequest, + config: _config, + dashboardChecks: dashboardChecks, + mergeQueueGuard: mergeQueueGuard, + ); + // Even though this appears to be an engine build, it could be a + // release candidate build, where the engine artifacts are built + // via the dart-internal builder. + // + // In either case, providing FLUTTER_PREBUILT_ENGINE_VERSION has no + // consequences for engine builds, as it just won't be used (it is + // only understood by the Flutter CLI). + // + // See https://github.com/flutter/flutter/issues/165810. + final engineArtifacts = isFlutterRepo + ? EngineArtifacts.usingExistingEngine(commitSha: sha) + : const EngineArtifacts.noFrameworkTests( + reason: 'This is not the flutter/flutter repository', + ); - // Even though this appears to be an engine build, it could be a - // release candidate build, where the engine artifacts are built - // via the dart-internal builder. - // - // In either case, providing FLUTTER_PREBUILT_ENGINE_VERSION has no - // consequences for engine builds, as it just won't be used (it is - // only understood by the Flutter CLI). - // - // See https://github.com/flutter/flutter/issues/165810. - engineArtifacts = EngineArtifacts.usingExistingEngine(commitSha: sha); - } else { - // For non-flutter repos, if unified check run flow is enabled, create - // a presubmit_guard document to track presubmit tests. - if (isUnifiedCheckRun) { - await UnifiedCheckRun.initializeCiStagingDocument( - firestoreService: _firestore, - slug: slug, - sha: sha, - stage: CiStage.genericTests, - tasks: [...presubmitTriggerTargets.map((t) => t.name)], - pullRequest: pullRequest, - config: _config, - dashboardChecks: dashboardChecks, - mergeQueueGuard: mergeQueueGuard, - ); - } - engineArtifacts = const EngineArtifacts.noFrameworkTests( - reason: 'This is not the flutter/flutter repository', - ); - } await _luciBuildService.scheduleTryBuilds( targets: presubmitTriggerTargets, pullRequest: pullRequest, engineArtifacts: engineArtifacts, dashboardChecks: dashboardChecks, mergeQueueGuard: mergeQueueGuard, - stage: isFusion ? CiStage.fusionEngineBuild : CiStage.genericTests, + stage: stage, ); } on FormatException catch (e, s) { log.warn( @@ -529,13 +507,9 @@ class Scheduler { // there are situations (see code above) when it needs to be unlocked // immediately. if (unlockMergeGroup) { - if (isUnifiedCheckRun) { - await unlockMergeQueueGuard(slug, sha, dashboardChecks); - if (mergeQueueGuard != null) { - await unlockMergeQueueGuard(slug, sha, mergeQueueGuard); - } - } else if (mergeQueueGuard != null) { - await unlockMergeQueueGuard(slug, sha, mergeQueueGuard); + await unlockCheckRun(slug, sha, dashboardChecks); + if (mergeQueueGuard != null) { + await unlockCheckRun(slug, sha, mergeQueueGuard); } } log.info( @@ -658,7 +632,7 @@ class Scheduler { final lockResult = await lockMergeGroupChecks( slug, headSha, - isUnifiedCheckRun: false, + isPresubmit: false, ); final dashboardChecks = lockResult.dashboardChecks; final mergeQueueGuard = lockResult.mergeQueueGuard!; @@ -666,7 +640,7 @@ class Scheduler { // If the repo is not fusion, it doesn't run anything in the MQ, so just // close the merge group guard. if (!isFusion) { - await unlockMergeQueueGuard(slug, headSha, mergeQueueGuard); + await unlockCheckRun(slug, headSha, mergeQueueGuard); return; } @@ -871,7 +845,7 @@ $s RepositorySlug slug, String headSha, { String? detailsUrl, - required bool isUnifiedCheckRun, + required bool isPresubmit, }) async { final mergeQueueGuard = await _githubChecksService.githubChecksUtil .createCheckRun( @@ -883,7 +857,7 @@ $s title: Config.kMergeQueueLockName, summary: kMergeQueueLockDescription, ), - detailsUrl: isUnifiedCheckRun ? null : detailsUrl, + detailsUrl: isPresubmit ? null : detailsUrl, ); final dashboardChecks = await _githubChecksService.githubChecksUtil @@ -896,10 +870,10 @@ $s title: Config.kDashboardCheckName, summary: kDashboardChecksDescription, ), - detailsUrl: isUnifiedCheckRun ? detailsUrl : null, + detailsUrl: isPresubmit ? detailsUrl : null, ); - if (!isUnifiedCheckRun) { + if (!isPresubmit) { // Skip Dashboard Checks await _githubChecksService.githubChecksUtil.updateCheckRun( _config, @@ -974,7 +948,7 @@ $s /// /// If the guard is guarding a pull request, this immediately makes the pull /// request eligible for enqueuing into the merge queue. - Future unlockMergeQueueGuard( + Future unlockCheckRun( RepositorySlug slug, String headSha, CheckRun lock, @@ -1129,56 +1103,20 @@ detailsUrl: $detailsUrl if (kCheckRunsToIgnore.contains(check.name)) { return true; } - final flow = check.isUnifiedCheckRun ? 'unified' : 'github'; final requestor = check.isMergeGroup ? 'merge group' : 'pull request'; final logCrumb = - 'checkCompleted(${check.name}, $flow, $requestor, ${check.slug}, ${check.sha}, ${check.status})'; - - final isFusion = check.slug == Config.flutterSlug; - if (!isFusion && !check.isUnifiedCheckRun) { - return true; - } - - late CiStage stage; - late PresubmitGuardConclusion stagingConclusion; - - if (check.isUnifiedCheckRun) { - stage = check.stage!; - stagingConclusion = await _markUnifiedCheckRunConclusion( - guardId: check.guardId, - state: check.state, - ); - } else { - // for github flow check runs are processed only if the build succeeded or - // some kind of failure occurred. - if (!check.status.isComplete) { - return true; - } - // Check runs are fired at every stage. However, at this point it is unknown - // if this check run belongs in the engine build stage or in the test stage. - // So first look for it in the engine stage, and if it's missing, look for - // it in the test stage. - stage = CiStage.fusionEngineBuild; - stagingConclusion = await _recordCurrentCiStage( - slug: check.slug, - sha: check.sha, - stage: stage, - name: check.name, - conclusion: check.status.toTaskConclusion(), - ); + 'checkCompleted(${check.name}, $requestor, ${check.slug}, ${check.sha}, ${check.status})'; + + final stage = + check.stage ?? + (check.slug == Config.flutterSlug + ? CiStage.fusionTests + : CiStage.genericTests); + final stagingConclusion = await _markUnifiedCheckRunConclusion( + guardId: check.guardId, + state: check.state, + ); - if (stagingConclusion.result == PresubmitGuardConclusionResult.missing) { - // Check run not found in the engine stage. Look for it in the test stage. - stage = CiStage.fusionTests; - stagingConclusion = await _recordCurrentCiStage( - slug: check.slug, - sha: check.sha, - stage: stage, - name: check.name, - conclusion: check.status.toTaskConclusion(), - ); - } - } // First; check if we even recorded anything. This can occur if we've already passed the check_run and // have moved on to running more tests (which wouldn't be present in our document). if (!stagingConclusion.isOk) { @@ -1234,7 +1172,7 @@ detailsUrl: $detailsUrl summary: stagingConclusion.summary, details: stagingConclusion.details, ); - } else if (check.isUnifiedCheckRun) { + } else { final guard = checkRunFromString(stagingConclusion.dashboardChecks!); final detailsUrl = 'https://flutter-dashboard.appspot.com/#/presubmit?repo=${check.slug.name}&sha=${check.sha}'; @@ -1283,30 +1221,16 @@ detailsUrl: $detailsUrl logCrumb: logCrumb, ); } + break; case CiStage.fusionTests: + case CiStage.genericTests: await _closeSuccessfulTestStage( dashboardChecks: stagingConclusion.dashboardChecks, mergeQueueGuard: stagingConclusion.mergeQueueGuard, slug: check.slug, sha: check.sha, logCrumb: logCrumb, - isUnifiedCheckRun: check.isUnifiedCheckRun, ); - case CiStage.genericTests: - if (check.isUnifiedCheckRun) { - await _closeSuccessfulTestStage( - dashboardChecks: stagingConclusion.dashboardChecks, - mergeQueueGuard: stagingConclusion.mergeQueueGuard, - slug: check.slug, - sha: check.sha, - logCrumb: logCrumb, - isUnifiedCheckRun: check.isUnifiedCheckRun, - ); - } else { - // generic tests do not have a staging document nor are associated - // with a merge group - they are only used to collect commit stats. - log.warn('$logCrumb: generic tests have no merge queue guard.'); - } break; } return true; @@ -1364,32 +1288,13 @@ detailsUrl: $detailsUrl required RepositorySlug slug, required String sha, required String logCrumb, - required bool isUnifiedCheckRun, }) async { log.info('$logCrumb: Test stage completed'); - if (isUnifiedCheckRun) { - if (dashboardChecks != null) { - await unlockMergeQueueGuard( - slug, - sha, - checkRunFromString(dashboardChecks), - ); - } - if (mergeQueueGuard != null) { - await unlockMergeQueueGuard( - slug, - sha, - checkRunFromString(mergeQueueGuard), - ); - } - } else { - if (mergeQueueGuard != null) { - await unlockMergeQueueGuard( - slug, - sha, - checkRunFromString(mergeQueueGuard), - ); - } + if (dashboardChecks != null) { + await unlockCheckRun(slug, sha, checkRunFromString(dashboardChecks)); + } + if (mergeQueueGuard != null) { + await unlockCheckRun(slug, sha, checkRunFromString(mergeQueueGuard)); } } @@ -1428,7 +1333,7 @@ detailsUrl: $detailsUrl // Unlock the guarding check_run. final checkRunGuard = checkRunFromString(mergeQueueGuard); - await unlockMergeQueueGuard(slug, sha, checkRunGuard); + await unlockCheckRun(slug, sha, checkRunGuard); } /// Schedules post-engine build tests (i.e. engine tests, and framework tests). @@ -1595,37 +1500,6 @@ $stacktrace } } - Future _recordCurrentCiStage({ - required RepositorySlug slug, - required String sha, - required CiStage stage, - required String name, - required TaskConclusion conclusion, - }) async { - final logCrumb = 'checkCompleted($name, $slug, $sha, $conclusion)'; - final documentName = CiStaging.documentNameFor( - slug: slug, - sha: sha, - stage: stage, - ); - log.info('$logCrumb: $documentName'); - - // We're doing a transactional update, which could fail if multiple tasks are running at the same time; so retry - // a sane amount of times before giving up. - const r = RetryOptions(maxAttempts: 3, delayFactor: Duration(seconds: 2)); - - return r.retry(() { - return CiStaging.markConclusion( - firestoreService: _firestore, - slug: slug, - sha: sha, - stage: stage, - checkRun: name, - conclusion: conclusion, - ); - }); - } - Future _markUnifiedCheckRunConclusion({ required PresubmitGuardId guardId, required PresubmitJobState state, diff --git a/app_dart/test/model/common/presubmit_completed_check_test.dart b/app_dart/test/model/common/presubmit_completed_check_test.dart index a2b9de29da..c89ba0b4d7 100644 --- a/app_dart/test/model/common/presubmit_completed_check_test.dart +++ b/app_dart/test/model/common/presubmit_completed_check_test.dart @@ -50,48 +50,11 @@ void main() { expect(check.checkRunId, 123); expect(check.checkSuiteId, 456); expect(check.headBranch, 'gh-readonly-queue/master/pr-123-abc'); - expect(check.isUnifiedCheckRun, true); expect(check.checkRun.name, Config.kDashboardCheckName); expect(check.buildNumber, 0); expect(check.buildId, Int64.MAX_VALUE); }); - test('fromBuild creates correct legacy check', () { - final build = Build( - id: Int64.MAX_VALUE, - builder: BuilderID(builder: 'test_builder'), - status: Status.SUCCESS, - number: 1234, - ); - - final userData = PresubmitUserData( - commit: CommitRef( - slug: slug, - sha: sha, - branch: 'gh-readonly-queue/master/pr-123-abc', - ), - stage: CiStage.fusionEngineBuild, - pullRequestNumber: 1, - checkRunId: 123, - checkSuiteId: 456, - ); - - final check = PresubmitCompletedJob.fromBuild(build, userData); - - expect(check.name, 'test_builder'); - expect(check.sha, sha); - expect(check.slug, slug); - expect(check.status, TaskStatus.succeeded); - expect(check.isMergeGroup, true); - expect(check.checkRunId, 123); - expect(check.checkSuiteId, 456); - expect(check.headBranch, 'gh-readonly-queue/master/pr-123-abc'); - expect(check.isUnifiedCheckRun, false); - expect(check.checkRun.name, 'test_builder'); - expect(check.buildNumber, 1234); - expect(check.buildId, Int64.MAX_VALUE); - }); - test('fromBuild handles custom status and summaryPrepend', () { final build = Build( id: Int64.MAX_VALUE, diff --git a/app_dart/test/request_handlers/github/webhook_subscription_test.dart b/app_dart/test/request_handlers/github/webhook_subscription_test.dart index 85f01d46f0..ef462c1f51 100644 --- a/app_dart/test/request_handlers/github/webhook_subscription_test.dart +++ b/app_dart/test/request_handlers/github/webhook_subscription_test.dart @@ -12,11 +12,11 @@ import 'package:cocoon_server/logging.dart'; import 'package:cocoon_server_test/mocks.dart'; import 'package:cocoon_server_test/test_logging.dart'; import 'package:cocoon_service/cocoon_service.dart'; -import 'package:cocoon_service/src/model/firestore/ci_staging.dart'; import 'package:cocoon_service/src/model/firestore/commit.dart' as fs; import 'package:cocoon_service/src/model/github/checks.dart' hide CheckRun; import 'package:cocoon_service/src/request_handling/exceptions.dart'; import 'package:cocoon_service/src/service/big_query.dart'; +import 'package:cocoon_service/src/service/firestore/unified_check_run.dart'; import 'package:cocoon_service/src/service/github_service.dart'; import 'package:fixnum/fixnum.dart'; import 'package:github/github.dart' hide Branch; @@ -96,9 +96,7 @@ void main() { wrongBaseBranchPullRequestMessageValue: '{{target_branch}} -> {{default_branch}}', ); - config.dynamicConfig = DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ); + config.dynamicConfig = DynamicConfig(); issuesService = MockIssuesService(); when( // ignore: discarded_futures @@ -149,6 +147,8 @@ void main() { any, any, output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), ), ).thenAnswer((_) async { return CheckRun.fromJson(const { @@ -2792,13 +2792,23 @@ void foo() { }); test('Tries to schedule tests for a duplicate SHA warns', () async { - await CiStaging.initializeDocument( + final pr = generatePullRequest( + number: 1, + headSha: '66d6bd9a3f79a36fe4f5178ccefbc781488a596c', + ); + final checkRunGuard = generateCheckRun( + 1, + name: Config.kDashboardCheckName, + ); + await UnifiedCheckRun.initializeCiStagingDocument( firestoreService: firestore, slug: Config.flutterSlug, sha: '66d6bd9a3f79a36fe4f5178ccefbc781488a596c', stage: CiStage.fusionEngineBuild, tasks: [], - checkRunGuard: '', + config: config, + pullRequest: pr, + dashboardChecks: checkRunGuard, ); config.maxFilesChangedForSkippingEnginePhaseValue = 1; await testActions( diff --git a/app_dart/test/request_handlers/presubmit_luci_subscription_test.dart b/app_dart/test/request_handlers/presubmit_luci_subscription_test.dart index 058cc17668..dd8d6f6a17 100644 --- a/app_dart/test/request_handlers/presubmit_luci_subscription_test.dart +++ b/app_dart/test/request_handlers/presubmit_luci_subscription_test.dart @@ -77,7 +77,7 @@ void main() { config.githubClient = mockGitHubClient; }); - test('Requests with repo_owner and repo_name update checks', () async { + test('In merge queue update check status', () async { when( mockGithubChecksService.updateCheckStatus( build: anyNamed('build'), @@ -98,6 +98,7 @@ void main() { Int64(1), status: bbv2.Status.SUCCESS, builder: 'Linux Host Engine', + extraTags: [InMergeQueueBuildTag().toStringPair()], userData: PresubmitUserData( commit: CommitRef( sha: 'abc', @@ -122,105 +123,50 @@ void main() { verify(mockScheduler.processCheckRunCompleted(any)).called(1); }); - test('Requests when task failed but no need to reschedule', () async { - when( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - ), - ).thenAnswer((_) async => true); - - when( - mockGithubChecksService.conclusionForResult(any), - ).thenAnswer((_) => github.CheckRunConclusion.empty); - when( - mockScheduler.processCheckRunCompleted(any), - ).thenAnswer((_) async => true); - - final userData = PresubmitUserData( - commit: CommitRef( - sha: 'abc', - branch: 'master', - slug: RepositorySlug('flutter', 'flutter'), - ), - checkRunId: 1, - checkSuiteId: 2, - ); - tester.message = createPushMessage( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux A', - userData: userData, - ); - - final buildsPubSub = createBuild( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux A', - ); - - await tester.post(handler); - verifyNever( - mockLuciBuildService.reschedulePresubmitBuild( - build: buildsPubSub.build, - builderName: 'Linux Coverage', - nextAttempt: 0, - userData: userData, - ), - ); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - ), - ).called(1); - verify(mockScheduler.processCheckRunCompleted(any)).called(1); - }); - - test('Requests when task failed but need to reschedule', () async { - when( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: true, - ), - ).thenAnswer((_) async => true); + test( + 'In merge queue, update check status when task failed but need to reschedule', + () async { + when( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: true, + ), + ).thenAnswer((_) async => true); - tester.message = createPushMessage( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux presubmit_max_attempts=2', - userData: PresubmitUserData( - commit: CommitRef( - sha: 'abc', - branch: 'master', - slug: RepositorySlug('flutter', 'flutter'), + tester.message = createPushMessage( + Int64(1), + status: bbv2.Status.FAILURE, + builder: 'Linux presubmit_max_attempts=2', + extraTags: [InMergeQueueBuildTag().toStringPair()], + userData: PresubmitUserData( + commit: CommitRef( + sha: 'abc', + branch: 'master', + slug: RepositorySlug('flutter', 'flutter'), + ), + checkRunId: 1, + checkSuiteId: 2, ), - checkRunId: 1, - checkSuiteId: 2, - ), - ); - await tester.post(handler); + ); + await tester.post(handler); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: true, - ), - ).called(1); - verifyNever(mockScheduler.processCheckRunCompleted(any)); - }); + verify( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: true, + ), + ).called(1); + verifyNever(mockScheduler.processCheckRunCompleted(any)); + }, + ); - test('Build rescheduled when in merge queue', () async { + test('In merge queue build rescheduled', () async { when( mockGithubChecksService.updateCheckStatus( build: anyNamed('build'), @@ -302,129 +248,137 @@ void main() { verifyNever(mockScheduler.processCheckRunCompleted(any)); }); - test('Build not rescheduled if not found in ciYaml list.', () async { - when( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: false, - ), - ).thenAnswer((_) async => true); - - when( - mockGithubChecksService.conclusionForResult(any), - ).thenAnswer((_) => github.CheckRunConclusion.empty); - when( - mockScheduler.processCheckRunCompleted(any), - ).thenAnswer((_) async => true); - - final userData = PresubmitUserData( - commit: CommitRef( - sha: 'abc', - branch: 'master', - slug: RepositorySlug('flutter', 'flutter'), - ), - checkRunId: 1, - checkSuiteId: 2, - ); + test( + 'In merge queue, build not rescheduled if not found in ciYaml list.', + () async { + when( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: false, + ), + ).thenAnswer((_) async => true); - tester.message = createPushMessage( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux C', - userData: userData, - ); + when( + mockGithubChecksService.conclusionForResult(any), + ).thenAnswer((_) => github.CheckRunConclusion.empty); + when( + mockScheduler.processCheckRunCompleted(any), + ).thenAnswer((_) async => true); - final buildsPubSub = createBuild( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux C', - ); + final userData = PresubmitUserData( + commit: CommitRef( + sha: 'abc', + branch: 'master', + slug: RepositorySlug('flutter', 'flutter'), + ), + checkRunId: 1, + checkSuiteId: 2, + ); - await tester.post(handler); - verifyNever( - mockLuciBuildService.reschedulePresubmitBuild( - build: buildsPubSub.build, - builderName: 'Linux C', + tester.message = createPushMessage( + Int64(1), + status: bbv2.Status.FAILURE, + builder: 'Linux C', + extraTags: [InMergeQueueBuildTag().toStringPair()], userData: userData, - nextAttempt: 1, - ), - ); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: false, - ), - ).called(1); + ); - verify(mockScheduler.processCheckRunCompleted(any)).called(1); - }); + final buildsPubSub = createBuild( + Int64(1), + status: bbv2.Status.FAILURE, + builder: 'Linux C', + ); - test('Build not rescheduled if ci.yaml fails validation.', () async { - when( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: false, - ), - ).thenAnswer((_) async => true); + await tester.post(handler); + verifyNever( + mockLuciBuildService.reschedulePresubmitBuild( + build: buildsPubSub.build, + builderName: 'Linux C', + userData: userData, + nextAttempt: 1, + ), + ); + verify( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: false, + ), + ).called(1); - when( - mockGithubChecksService.conclusionForResult(any), - ).thenAnswer((_) => github.CheckRunConclusion.empty); - when( - mockScheduler.processCheckRunCompleted(any), - ).thenAnswer((_) async => true); + verify(mockScheduler.processCheckRunCompleted(any)).called(1); + }, + ); - final userData = PresubmitUserData( - checkRunId: 1, - checkSuiteId: 2, - commit: CommitRef( - sha: 'abc', - branch: 'master', - slug: RepositorySlug('flutter', 'flutter'), - ), - ); - tester.message = createPushMessage( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux C', - userData: userData, - ); + test( + 'In merge queue, build not rescheduled if ci.yaml fails validation.', + () async { + when( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: false, + ), + ).thenAnswer((_) async => true); - final buildsPubSub = createBuild( - Int64(1), - status: bbv2.Status.FAILURE, - builder: 'Linux C', - ); + when( + mockGithubChecksService.conclusionForResult(any), + ).thenAnswer((_) => github.CheckRunConclusion.empty); + when( + mockScheduler.processCheckRunCompleted(any), + ).thenAnswer((_) async => true); - await tester.post(handler); - verifyNever( - mockLuciBuildService.reschedulePresubmitBuild( - build: buildsPubSub.build, - builderName: 'Linux C', + final userData = PresubmitUserData( + checkRunId: 1, + checkSuiteId: 2, + commit: CommitRef( + sha: 'abc', + branch: 'master', + slug: RepositorySlug('flutter', 'flutter'), + ), + ); + tester.message = createPushMessage( + Int64(1), + status: bbv2.Status.FAILURE, + builder: 'Linux C', + extraTags: [InMergeQueueBuildTag().toStringPair()], userData: userData, - nextAttempt: 1, - ), - ); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - rescheduled: false, - ), - ).called(1); - verify(mockScheduler.processCheckRunCompleted(any)).called(1); - }); + ); + + final buildsPubSub = createBuild( + Int64(1), + status: bbv2.Status.FAILURE, + builder: 'Linux C', + ); + + await tester.post(handler); + verifyNever( + mockLuciBuildService.reschedulePresubmitBuild( + build: buildsPubSub.build, + builderName: 'Linux C', + userData: userData, + nextAttempt: 1, + ), + ); + verify( + mockGithubChecksService.updateCheckStatus( + build: anyNamed('build'), + checkRunId: anyNamed('checkRunId'), + luciBuildService: anyNamed('luciBuildService'), + slug: anyNamed('slug'), + rescheduled: false, + ), + ).called(1); + verify(mockScheduler.processCheckRunCompleted(any)).called(1); + }, + ); test('Pubsub rejected if branch is not enabled.', () async { final userData = PresubmitUserData( @@ -455,7 +409,7 @@ void main() { ); }); - test('Build contains data from build_large_fields', () async { + test('In merge queue, build contains data from build_large_fields', () async { when( mockGithubChecksService.updateCheckStatus( build: anyNamed('build'), @@ -473,6 +427,7 @@ void main() { Int64(1), status: bbv2.Status.FAILURE, builder: 'Linux presubmit_max_attempts=2', + extraTags: [InMergeQueueBuildTag().toStringPair()], userData: PresubmitUserData( checkRunId: 1, checkSuiteId: 2, @@ -584,7 +539,7 @@ void main() { ); }); - test('Requests when task failed and is suppressed', () async { + test('In merge queue, requests when task failed and is suppressed', () async { final userData = PresubmitUserData( commit: CommitRef( sha: 'abc', @@ -632,6 +587,10 @@ void main() { Int64(1), status: bbv2.Status.FAILURE, builder: 'Linux A', + extraTags: [ + InMergeQueueBuildTag().toStringPair(), + CurrentAttemptBuildTag(attemptNumber: 4).toStringPair(), + ], userData: userData, ); @@ -807,11 +766,12 @@ void main() { }, ); - test('Suppression check skipped when rescheduled', () async { + test('In Merge Queue suppression check skipped when rescheduled', () async { tester.message = createPushMessage( Int64(1), status: bbv2.Status.FAILURE, builder: 'Linux presubmit_max_attempts=2', + extraTags: [InMergeQueueBuildTag().toStringPair()], userData: PresubmitUserData( commit: CommitRef( sha: 'abc', @@ -931,7 +891,7 @@ void main() { }); test( - 'publishes build message to ordered-presubmit topic with orderingKey when ordering_key flag exists', + 'Publishes build message to ordered-presubmit topic with orderingKey when ordering_key flag exists', () async { tester.message = createPushMessage( Int64(1), @@ -967,53 +927,4 @@ void main() { ); }, ); - - test( - 'does not publish to ordered-presubmit topic when orderingKey is empty', - () async { - when( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - ), - ).thenAnswer((_) async => true); - when( - mockGithubChecksService.conclusionForResult(any), - ).thenAnswer((_) => github.CheckRunConclusion.empty); - when( - mockScheduler.processCheckRunCompleted(any), - ).thenAnswer((_) async => true); - - tester.message = createPushMessage( - Int64(1), - status: bbv2.Status.SUCCESS, - builder: 'Linux Host Engine', - userData: PresubmitUserData( - commit: CommitRef( - sha: 'abc', - branch: 'master', - slug: RepositorySlug('flutter', 'cocoon'), - ), - checkRunId: 1, - checkSuiteId: 2, - ), - extraTags: [OrderingKeyTag(orderingKey: '').toStringPair()], - ); - - final response = await tester.post(handler); - - expect(response, Response.emptyOk); - expect(pubSub.topics, isEmpty); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - ), - ).called(1); - }, - ); } diff --git a/app_dart/test/request_handlers/presubmit_ordered_subscription_test.dart b/app_dart/test/request_handlers/presubmit_ordered_subscription_test.dart index b0eee7f8c4..13efb12e0d 100644 --- a/app_dart/test/request_handlers/presubmit_ordered_subscription_test.dart +++ b/app_dart/test/request_handlers/presubmit_ordered_subscription_test.dart @@ -105,14 +105,6 @@ void main() { final response = await tester.post(handler); expect(response, Response.emptyOk); - verify( - mockGithubChecksService.updateCheckStatus( - build: anyNamed('build'), - checkRunId: anyNamed('checkRunId'), - luciBuildService: anyNamed('luciBuildService'), - slug: anyNamed('slug'), - ), - ).called(1); verify(mockScheduler.processCheckRunCompleted(any)).called(1); }, ); diff --git a/app_dart/test/request_handling/mapping_http_client_test.mocks.dart b/app_dart/test/request_handling/mapping_http_client_test.mocks.dart index 3f451c2d2d..32412138da 100644 --- a/app_dart/test/request_handling/mapping_http_client_test.mocks.dart +++ b/app_dart/test/request_handling/mapping_http_client_test.mocks.dart @@ -1,8 +1,9 @@ -// Mocks generated by Mockito 5.4.6 from annotations +// Mocks generated by Mockito from annotations // in cocoon_service/test/request_handling/mapping_http_client_test.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes + import 'dart:async' as _i3; import 'dart:convert' as _i4; import 'dart:typed_data' as _i6; @@ -17,6 +18,7 @@ import 'package:mockito/src/dummies.dart' as _i5; // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: experimental_member_use // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable diff --git a/app_dart/test/service/firestore/unified_check_run_test.dart b/app_dart/test/service/firestore/unified_check_run_test.dart index f6b486ace7..2a8ad462de 100644 --- a/app_dart/test/service/firestore/unified_check_run_test.dart +++ b/app_dart/test/service/firestore/unified_check_run_test.dart @@ -48,11 +48,7 @@ void main() { group('initializeCiStagingDocument', () { test('creates PresubmitGuard and Checks when enabled for user', () async { - config.dynamicConfig = DynamicConfig.fromJson({ - 'unifiedCheckRunFlow': { - 'useForUsers': ['dash'], - }, - }); + config.dynamicConfig = DynamicConfig(); await UnifiedCheckRun.initializeCiStagingDocument( firestoreService: firestoreService, @@ -87,37 +83,6 @@ void main() { ); expect(checkDoc.name, endsWith(checkId.documentId)); }); - - test('initializes CiStagingDocument when NOT enabled for user', () async { - config.dynamicConfig = DynamicConfig.fromJson({ - 'unifiedCheckRunFlow': {'useForUsers': []}, - }); - - await UnifiedCheckRun.initializeCiStagingDocument( - firestoreService: firestoreService, - slug: slug, - sha: sha, - stage: CiStage.fusionEngineBuild, - tasks: ['linux', 'mac'], - config: config, - pullRequest: pullRequest, - mergeQueueGuard: checkRun, - ); - - // Verify PresubmitGuard is NOT created - final guardId = PresubmitGuard.documentIdFor( - slug: slug, - prNum: 1, - checkRunId: 123, - stage: CiStage.fusionEngineBuild, - ); - expect( - () => firestoreService.getDocument( - 'projects/flutter-dashboard/databases/cocoon/documents/presubmit_guards/${guardId.documentId}', - ), - throwsA(isA()), - ); - }); }); group('markConclusion', () { @@ -724,7 +689,6 @@ void main() { checkRunId: 123, checkSuiteId: 234, headBranch: 'master', - isUnifiedCheckRun: true, prNum: 567, attempt: 1, endTime: 2000, diff --git a/app_dart/test/service/luci_build_service/schedule_try_builds_test.dart b/app_dart/test/service/luci_build_service/schedule_try_builds_test.dart index d4e4514529..7762693c18 100644 --- a/app_dart/test/service/luci_build_service/schedule_try_builds_test.dart +++ b/app_dart/test/service/luci_build_service/schedule_try_builds_test.dart @@ -16,7 +16,6 @@ import 'package:cocoon_service/src/service/cache_service.dart'; import 'package:cocoon_service/src/service/firestore.dart'; import 'package:cocoon_service/src/service/flags/dynamic_config.dart'; import 'package:cocoon_service/src/service/flags/ordered_presubmit_flags.dart'; -import 'package:cocoon_service/src/service/flags/unified_check_run_flow_flags.dart'; import 'package:cocoon_service/src/service/luci_build_service.dart'; import 'package:cocoon_service/src/service/luci_build_service/build_tags.dart'; import 'package:cocoon_service/src/service/luci_build_service/engine_artifacts.dart'; @@ -420,11 +419,7 @@ void main() { // Enable Unified Check Run Flow luci = LuciBuildService( - config: FakeConfig( - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ), - ), + config: FakeConfig(dynamicConfig: DynamicConfig()), cache: CacheService.inMemory(), buildBucketClient: mockBuildBucketClient, githubChecksUtil: mockGithubChecksUtil, @@ -492,11 +487,7 @@ void main() { // Enable Unified Check Run Flow but provide NO guard luci = LuciBuildService( - config: FakeConfig( - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ), - ), + config: FakeConfig(dynamicConfig: DynamicConfig()), cache: CacheService.inMemory(), buildBucketClient: mockBuildBucketClient, githubChecksUtil: mockGithubChecksUtil, @@ -554,79 +545,6 @@ void main() { expect(userData.checkRunId, 456); expect(userData.guardCheckRunId, isNull); }); - - test( - 'does not update dashboard checks when unified flow is disabled', - () async { - final pullRequest = generatePullRequest( - id: 1, - repo: 'flutter', - headSha: 'headsha123', - ); - - final buildTarget = generateTarget( - 1, - properties: {'os': 'abc'}, - slug: RepositorySlug.full('flutter/flutter'), - name: 'Linux foo', - ); - - // Disable Unified Check Run Flow but provide a guard (unexpected but should be handled) - luci = LuciBuildService( - config: FakeConfig( - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ), - ), - cache: CacheService.inMemory(), - buildBucketClient: mockBuildBucketClient, - githubChecksUtil: mockGithubChecksUtil, - pubsub: pubSub, - gerritService: gerritService, - firestore: firestore, - ); - - final checkRunGuard = generateCheckRun(1234, name: 'Guard'); - - when( - mockGithubChecksUtil.createCheckRun(any, any, any, any), - ).thenAnswer((_) async => generateCheckRun(456, name: 'Linux foo')); - - await expectLater( - luci.scheduleTryBuilds( - pullRequest: pullRequest, - targets: [buildTarget], - engineArtifacts: EngineArtifacts.builtFromSource( - commitSha: pullRequest.head!.sha!, - ), - dashboardChecks: checkRunGuard, // Pass guard even though disabled - ), - completion([isTarget.hasName('Linux foo')]), - ); - - // Should NOT update dashboard checks - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - ), - ); - - // Should create individual check run because unified flow is disabled - verify( - mockGithubChecksUtil.createCheckRun( - any, - RepositorySlug.full('flutter/flutter'), - 'headsha123', - 'Linux foo', - ), - ).called(1); - }, - ); - test( 'does not reRequest check run for re-run failed checks when failedJobs > 0', () async { @@ -653,9 +571,7 @@ void main() { luci = LuciBuildService( config: FakeConfig( githubClient: mockGithubClient, - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ), + dynamicConfig: DynamicConfig(), ), cache: CacheService.inMemory(), buildBucketClient: mockBuildBucketClient, diff --git a/app_dart/test/service/scheduler_test.dart b/app_dart/test/service/scheduler_test.dart index bc785af7ed..6ae5cf024c 100644 --- a/app_dart/test/service/scheduler_test.dart +++ b/app_dart/test/service/scheduler_test.dart @@ -76,10 +76,9 @@ void main() { Config.flutterSlug, Config.packagesSlug, }, + maxFilesChangedForSkippingEnginePhaseValue: 0, ); - config.dynamicConfig = DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ); + config.dynamicConfig = DynamicConfig(); fakeContentAwareHash = FakeContentAwareHashService(config: config); @@ -93,6 +92,8 @@ void main() { any, any, output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), ), ).thenAnswer((Invocation invocation) async { return generateCheckRun( @@ -673,9 +674,7 @@ void main() { final mockGithubClient = MockGitHub(); config = FakeConfig( githubService: mockGithubService, - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ), + dynamicConfig: DynamicConfig(), ); scheduler = Scheduler( githubService: config.githubService ?? FakeGithubService(), @@ -728,6 +727,7 @@ void main() { any, any, output: anyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).thenAnswer((_) async { return CheckRun.fromJson(const { @@ -757,9 +757,16 @@ void main() { output: anyNamed('output'), ), ); - // Verfies Linux A was created + // Verifies Dashboard Checks was created verify( - mockGithubChecksUtil.createCheckRun(any, any, any, any), + mockGithubChecksUtil.createCheckRun( + any, + any, + any, + Config.kDashboardCheckName, + output: anyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), + ), ).called(1); }); @@ -1605,15 +1612,6 @@ targets: test( 'ignores default check runs that have no side effects', () async { - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'abc123', - stage: CiStage.fusionTests, - tasks: ['foo', 'bar'], - checkRunGuard: '{}', - ); - for (final ignored in Scheduler.kCheckRunsToIgnore) { expect( await scheduler.processCheckRunCompleted( @@ -1625,584 +1623,14 @@ targets: isMergeGroup: false, checkRunId: 1, checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - } - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasCheckRuns({ - 'foo': TaskConclusion.scheduled, - 'bar': TaskConclusion.scheduled, - }), - ]), - ); - }, - ); - - test('ignores invalid conclusions', () async { - final document = await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'abc123', - stage: CiStage.fusionTests, - tasks: ['Bar bar'], - checkRunGuard: '{}', - ); - - firestore.failOnWriteDocument(document); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'abc123', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isFalse, - ); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasCheckRuns({'Bar bar': TaskConclusion.scheduled}), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - }); - - test('does not complete with remaining tests', () async { - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'abc123', - stage: CiStage.fusionEngineBuild, - tasks: ['Foo foo', 'Bar bar'], - checkRunGuard: '{}', - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'abc123', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isFalse, - ); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasCheckRuns({ - 'Foo foo': TaskConclusion.scheduled, - 'Bar bar': TaskConclusion.success, - }), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - }); - - // The merge guard is not closed until both engine build and tests - // complete and are successful. - // This behavior is explained here: - // https://github.com/flutter/flutter/issues/159898#issuecomment-2597209435 - test( - 'failed tests neither unlock merge queue guard nor schedule test stage', - () async { - await PrCheckRuns.initializeDocument( - firestoreService: firestore, - pullRequest: pullRequest, - checks: [createGithubCheckRun(name: 'Bar bar')], - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'abc123', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'abc123', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasCheckRuns({'Bar bar': TaskConclusion.success}), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - }, - ); - - test('schedules tests after engine stage', () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - when(githubService.github).thenReturn(githubClient); - when( - githubService.searchIssuesAndPRs( - any, - any, - sort: anyNamed('sort'), - pages: anyNamed('pages'), - ), - ).thenAnswer((_) async => [generateIssue(42)]); - - final pullRequest = generatePullRequest(); - when( - githubService.getPullRequest(any, any), - ).thenAnswer((_) async => pullRequest); - getFilesChanged.cannedFiles = ['abc/def']; - when( - mockGithubChecksUtil.listCheckSuitesForRef( - any, - any, - ref: anyNamed('ref'), - ), - ).thenAnswer( - (_) async => [ - // From check_run.check_suite.id in [checkRunString]. - generateCheckSuite(668083231), - ], - ); - - ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionCiYaml); - final luci = MockLuciBuildService(); - when( - luci.scheduleTryBuilds( - targets: anyNamed('targets'), - pullRequest: anyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ).thenAnswer((inv) async { - return []; - }); - - final gitHubChecksService = MockGithubChecksService(); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - when( - gitHubChecksService.findMatchingPullRequest(any, any, any), - ).thenAnswer((inv) async { - return pullRequest; - }); - - // Cocoon creates a Firestore document to track the tasks in the - // test stage. - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - verify( - gitHubChecksService.findMatchingPullRequest( - Config.flutterSlug, - 'testSha', - 668083231, - ), - ).called(1); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasStage(CiStage.fusionEngineBuild).hasCheckRuns({ - 'Bar bar': TaskConclusion.success, - }), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Linux A': TaskConclusion.scheduled, - 'Linux Z': TaskConclusion.scheduled, - 'Linux engine_presubmit': TaskConclusion.scheduled, - }), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - - final result = verify( - luci.scheduleTryBuilds( - targets: captureAnyNamed('targets'), - pullRequest: captureAnyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ); - expect(result.callCount, 1); - final captured = result.captured; - expect(captured[0], hasLength(3)); - // see the blend of fusionCiYaml and singleCiYaml - expect(captured[0][0].name, 'Linux A'); - expect(captured[0][1].name, 'Linux Z'); - expect(captured[0][2].name, 'Linux engine_presubmit'); - expect(captured[1], pullRequest); - }); - - test( - 'processCheckRunCompleted not failed when check suite id is 0', - () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - when(githubService.github).thenReturn(githubClient); - when( - githubService.searchIssuesAndPRs( - any, - any, - sort: anyNamed('sort'), - pages: anyNamed('pages'), - ), - ).thenAnswer((_) async => [generateIssue(42)]); - - final pullRequest = generatePullRequest(); - when( - githubService.getPullRequest(any, any), - ).thenAnswer((_) async => pullRequest); - getFilesChanged.cannedFiles = ['abc/def']; - when( - mockGithubChecksUtil.listCheckSuitesForRef( - any, - any, - ref: anyNamed('ref'), - ), - ).thenAnswer( - (_) async => [ - // From check_run.check_suite.id in [checkRunString]. - generateCheckSuite(668083231), - ], - ); - - ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionCiYaml); - final luci = MockLuciBuildService(); - when( - luci.scheduleTryBuilds( - targets: anyNamed('targets'), - pullRequest: anyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ).thenAnswer((inv) async { - return []; - }); - - final gitHubChecksService = MockGithubChecksService(); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - when( - gitHubChecksService.findMatchingPullRequest(any, any, any), - ).thenAnswer((inv) async { - return pullRequest; - }); - - // Cocoon creates a Firestore document to track the tasks in the - // test stage. - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 0, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - verify( - gitHubChecksService.findMatchingPullRequest( - Config.flutterSlug, - 'testSha', - 0, - ), - ).called(1); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasStage(CiStage.fusionEngineBuild).hasCheckRuns({ - 'Bar bar': TaskConclusion.success, - }), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Linux A': TaskConclusion.scheduled, - 'Linux Z': TaskConclusion.scheduled, - 'Linux engine_presubmit': TaskConclusion.scheduled, - }), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - - final result = verify( - luci.scheduleTryBuilds( - targets: captureAnyNamed('targets'), - pullRequest: captureAnyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ); - expect(result.callCount, 1); - final captured = result.captured; - expect(captured[0], hasLength(3)); - // see the blend of fusionCiYaml and singleCiYaml - expect(captured[0][0].name, 'Linux A'); - expect(captured[0][1].name, 'Linux Z'); - expect(captured[0][2].name, 'Linux engine_presubmit'); - expect(captured[1], pullRequest); - }, - ); - - test('tracks test check runs in firestore', () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - final luci = MockLuciBuildService(); - final gitHubChecksService = MockGithubChecksService(); - - when(githubService.github).thenReturn(githubClient); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: [], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionTests, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - // The first invocation looks in the fusionEngineBuild stage, which - // returns "missing" result. - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging - .hasStage(CiStage.fusionEngineBuild) - .hasCheckRuns(isEmpty), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Bar bar': TaskConclusion.success, - }), - ]), - ); - - // Because tests completed, and completed successfully, the guard is - // unlocked, allowing the PR to land. - verify( - mockGithubChecksUtil.updateCheckRun( - any, - argThat(equals(RepositorySlug('flutter', 'flutter'))), - argThat( - predicate((arg) { - expect(arg.name, 'GUARD TEST'); - return true; - }), - ), - status: argThat( - equals(CheckRunStatus.completed), - named: 'status', - ), - conclusion: argThat( - equals(CheckRunConclusion.success), - named: 'conclusion', - ), - output: anyNamed('output'), - ), - ).called(1); - }); + headBranch: 'master', + ), + ), + isTrue, + ); + } + }, + ); test( 'writes failure comment if moving to next phase fails', @@ -2376,421 +1804,6 @@ targets: ); }); - test( - 'does not fail the merge queue guard when a test check run fails (presubmit)', - () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - final luci = MockLuciBuildService(); - final gitHubChecksService = MockGithubChecksService(); - - when(githubService.github).thenReturn(githubClient); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: [], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionTests, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.failed, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - // The first invocation looks in the fusionEngineBuild stage, which - // returns "missing" result. - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging - .hasStage(CiStage.fusionEngineBuild) - .hasCheckRuns(isEmpty), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Bar bar': TaskConclusion.failure, - }), - ]), - ); - - // The test stage completed, but with failures. The merge queue - // guard should stay open to prevent the pull request from landing. - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - }, - ); - - test( - 'fails the merge queue guard when a test check run fails (merge group)', - () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - final luci = MockLuciBuildService(); - final gitHubChecksService = MockGithubChecksService(); - - when(githubService.github).thenReturn(githubClient); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - const headBranch = - 'gh-readonly-queue/master/pr-15-c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor( - name: 'GUARD TEST', - headBranch: headBranch, - ), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.failed, - isMergeGroup: true, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: headBranch, - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - // The first invocation looks in the fusionEngineBuild stage, which - // returns "missing" result. - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasStage(CiStage.fusionEngineBuild).hasCheckRuns({ - 'Bar bar': TaskConclusion.failure, - }), - ]), - ); - - // The test stage completed, but with failures. The merge queue - // guard should stay open to prevent the pull request from landing. - verify( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: CheckRunConclusion.failure, - output: anyNamed('output'), - ), - ).called(1); - - expect(fakeContentAwareHash.completedShas, [ - (commitSha: 'testSha', successful: false), - ]); - }, - ); - - test('closes merge queue guard in merge group success', () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - final luci = MockLuciBuildService(); - final gitHubChecksService = MockGithubChecksService(); - - when(githubService.github).thenReturn(githubClient); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - getFilesChanged: getFilesChanged, - githubChecksService: gitHubChecksService, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - const headBranch = - 'gh-readonly-queue/master/pr-15-c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor( - name: 'GUARD TEST', - headBranch: headBranch, - ), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: true, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: headBranch, - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - // The first invocation looks in the fusionEngineBuild stage, which - // returns "missing" result. - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasStage(CiStage.fusionEngineBuild).hasCheckRuns({ - 'Bar bar': TaskConclusion.success, - }), - ]), - ); - - // The test stage completed, but with failures. The merge queue - // guard should stay open to prevent the pull request from landing. - verify( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: CheckRunConclusion.success, - output: anyNamed('output'), - ), - ).called(1); - - expect(fakeContentAwareHash.completedShas, [ - (commitSha: 'testSha', successful: true), - ]); - }); - - test( - 'schedules tests after engine stage - with pr caching', - () async { - final githubService = config.githubService = MockGithubService(); - final githubClient = MockGitHub(); - when(githubService.github).thenReturn(githubClient); - when( - githubService.searchIssuesAndPRs( - any, - any, - sort: anyNamed('sort'), - pages: anyNamed('pages'), - ), - ).thenAnswer((_) async => [generateIssue(42)]); - - final pullRequest = generatePullRequest(); - when( - githubService.getPullRequest(any, any), - ).thenAnswer((_) async => pullRequest); - getFilesChanged.cannedFiles = ['abc/def']; - when( - mockGithubChecksUtil.listCheckSuitesForRef( - any, - any, - ref: anyNamed('ref'), - ), - ).thenAnswer( - (_) async => [ - // From check_run.check_suite.id in [checkRunString]. - generateCheckSuite(668083231), - ], - ); - - await PrCheckRuns.initializeDocument( - firestoreService: firestore, - checks: [generateCheckRun(1, name: 'Bar bar')], - pullRequest: pullRequest, - ); - - ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionCiYaml); - final luci = MockLuciBuildService(); - when( - luci.scheduleTryBuilds( - targets: anyNamed('targets'), - pullRequest: anyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ).thenAnswer((inv) async { - return []; - }); - - final gitHubChecksService = MockGithubChecksService(); - when( - gitHubChecksService.githubChecksUtil, - ).thenReturn(mockGithubChecksUtil); - - scheduler = Scheduler( - githubService: config.githubService ?? FakeGithubService(), - cache: cache, - config: config, - githubChecksService: gitHubChecksService, - getFilesChanged: getFilesChanged, - ciYamlFetcher: ciYamlFetcher, - luciBuildService: luci, - contentAwareHash: fakeContentAwareHash, - firestore: firestore, - bigQuery: bigQuery, - ); - - await CiStaging.initializeDocument( - firestoreService: firestore, - slug: Config.flutterSlug, - sha: 'testSha', - stage: CiStage.fusionEngineBuild, - tasks: ['Bar bar'], - checkRunGuard: checkRunFor(name: 'GUARD TEST'), - ); - - expect( - await scheduler.processCheckRunCompleted( - PresubmitCompletedJob( - name: 'Bar bar', - sha: 'testSha', - slug: createGithubRepository().slug(), - status: TaskStatus.succeeded, - isMergeGroup: false, - checkRunId: 1, - checkSuiteId: 668083231, - headBranch: 'master', - isUnifiedCheckRun: false, - ), - ), - isTrue, - ); - - verifyNever( - gitHubChecksService.findMatchingPullRequest(any, any, any), - ); - - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging.hasStage(CiStage.fusionEngineBuild).hasCheckRuns({ - 'Bar bar': TaskConclusion.success, - }), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Linux A': TaskConclusion.scheduled, - 'Linux Z': TaskConclusion.scheduled, - 'Linux engine_presubmit': TaskConclusion.scheduled, - }), - ]), - ); - - verifyNever( - mockGithubChecksUtil.updateCheckRun( - any, - any, - any, - status: anyNamed('status'), - conclusion: anyNamed('conclusion'), - output: anyNamed('output'), - ), - ); - - final result = verify( - luci.scheduleTryBuilds( - targets: captureAnyNamed('targets'), - pullRequest: captureAnyNamed('pullRequest'), - engineArtifacts: anyNamed('engineArtifacts'), - dashboardChecks: anyNamed('dashboardChecks'), - mergeQueueGuard: anyNamed('mergeQueueGuard'), - stage: anyNamed('stage'), - ), - ); - expect(result.callCount, 1); - final captured = result.captured; - expect(captured[0], hasLength(3)); - // see the blend of fusionCiYaml and singleCiYaml - expect(captured[0][0].name, 'Linux A'); - expect(captured[0][1].name, 'Linux Z'); - expect(captured[0][2].name, 'Linux engine_presubmit'); - expect( - captured[1], - isA().having( - (p) => p.number, - 'number', - pullRequest.number, - ), - ); - }, - ); // end of group }); }); @@ -2884,6 +1897,7 @@ targets: any, captureAny, output: captureAnyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).captured, [ @@ -2903,9 +1917,6 @@ targets: summary: 'If this check is stuck pending, push an empty commit to retrigger the checks', ), - 'Linux A', - null, - // Linux runIf is not run as this is for tip of tree and the files weren't affected ], ); }); @@ -2914,9 +1925,7 @@ targets: 'creates presubmit_guard document for flutter/packages when unified check run flow is enabled', () async { getFilesChanged.cannedFiles = ['README.md']; - config.dynamicConfig = DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ); + config.dynamicConfig = DynamicConfig(); when( mockGithubChecksUtil.createCheckRun( @@ -2975,9 +1984,7 @@ targets: 'unlocks merge group for cocoon when unified check run flow is enabled', () async { getFilesChanged.cannedFiles = ['README.md']; - config.dynamicConfig = DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ); + config.dynamicConfig = DynamicConfig(); when( mockGithubChecksUtil.createCheckRun( @@ -3044,7 +2051,7 @@ targets: final lockResult = await scheduler.lockMergeGroupChecks( Config.flutterSlug, 'sha123', - isUnifiedCheckRun: true, + isPresubmit: true, ); expect(lockResult.dashboardChecks.name, Config.kDashboardCheckName); @@ -3138,9 +2145,7 @@ targets: final fakeConfig = FakeConfig( githubService: mockGithubService, githubClient: MockGitHub(), - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ), + dynamicConfig: DynamicConfig(), ); scheduler = Scheduler( githubService: fakeConfig.githubService ?? FakeGithubService(), @@ -3175,6 +2180,7 @@ targets: any, captureAny, output: captureAnyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).captured, [ @@ -3194,13 +2200,12 @@ targets: summary: 'If this check is stuck pending, push an empty commit to retrigger the checks', ), - 'Linux A', - null, - // runIf requires a diff in dev, so an error will cause it to be triggered - 'Linux runIf', - null, ], ); + final guards = await firestore.query(PresubmitGuard.collectionId, {}); + expect(guards, isNotEmpty); + final guard = PresubmitGuard.fromDocument(guards.first); + expect(guard.jobs.keys, containsAll(['Linux A', 'Linux runIf'])); }, ); @@ -3221,6 +2226,7 @@ targets: any, captureAny, output: captureAnyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).captured, [ @@ -3264,14 +2270,7 @@ targets: output: anyNamed('output'), ), ).captured, - [ - CheckRunStatus.completed, - CheckRunConclusion.success, - CheckRunStatus.completed, - CheckRunConclusion.success, - CheckRunStatus.completed, - CheckRunConclusion.success, - ], + [CheckRunStatus.completed, CheckRunConclusion.success], ); }); @@ -3302,11 +2301,6 @@ targets: await scheduler.triggerPresubmitTargets(pullRequest: pullRequest); expect(capturedUpdates, <(String, CheckRunStatus, CheckRunConclusion)>[ - ( - Config.kDashboardCheckName, - CheckRunStatus.completed, - CheckRunConclusion.success, - ), ( 'ci.yaml validation', CheckRunStatus.completed, @@ -3329,12 +2323,7 @@ targets: output: anyNamed('output'), ), ).captured, - [ - CheckRunStatus.completed, - CheckRunConclusion.success, - CheckRunStatus.completed, - CheckRunConclusion.failure, - ], + [CheckRunStatus.completed, CheckRunConclusion.failure], ); }); @@ -3432,6 +2421,7 @@ targets: any, any, output: anyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).thenAnswer((inv) async { final slug = inv.positionalArguments[1] as RepositorySlug; @@ -3455,9 +2445,7 @@ targets: githubService: mockGithubService, githubClient: MockGitHub(), maxFilesChangedForSkippingEnginePhaseValue: 0, - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ), + dynamicConfig: DynamicConfig(), ); scheduler = Scheduler( githubService: fakeConfig.githubService ?? FakeGithubService(), @@ -3482,6 +2470,7 @@ targets: any, captureAny, output: captureAnyNamed('output'), + detailsUrl: anyNamed('detailsUrl'), ), ).captured; stdout.writeAll(results); @@ -3507,7 +2496,7 @@ targets: mockGithubChecksUtil.updateCheckRun( any, Config.flutterSlug, - checkRuns[1], + checkRuns[2], status: argThat(equals(CheckRunStatus.completed), named: 'status'), conclusion: argThat( equals(CheckRunConclusion.success), @@ -3527,6 +2516,16 @@ targets: output: anyNamed('output'), ), ); + verifyNever( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + checkRuns[1], + status: anyNamed('status'), + conclusion: anyNamed('conclusion'), + output: anyNamed('output'), + ), + ); }); }); @@ -4114,9 +3113,7 @@ targets: githubService: mockGithubService, githubClient: MockGitHub(), maxFilesChangedForSkippingEnginePhaseValue: 29, - dynamicConfig: DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: false), - ), + dynamicConfig: DynamicConfig(), ); scheduler = Scheduler( githubService: fakeConfig.githubService ?? FakeGithubService(), @@ -4198,18 +3195,18 @@ targets: 'Linux analyze', ], reason: 'Should skip Linux engine_build'); - expect( - firestore, - existsInStorage(CiStaging.metadata, [ - isCiStaging - .hasStage(CiStage.fusionEngineBuild) - .hasCheckRuns(isEmpty), - isCiStaging.hasStage(CiStage.fusionTests).hasCheckRuns({ - 'Linux A': TaskConclusion.scheduled, - 'Linux analyze': TaskConclusion.scheduled, - }), - ]), - ); + final guards = await firestore.query(PresubmitGuard.collectionId, {}); + final engineGuard = guards + .map(PresubmitGuard.fromDocument) + .firstWhere((g) => g.stage == CiStage.fusionEngineBuild); + expect(engineGuard.jobs, isEmpty); + final testsGuard = guards + .map(PresubmitGuard.fromDocument) + .firstWhere((g) => g.stage == CiStage.fusionTests); + expect(testsGuard.jobs, { + 'Linux A': TaskStatus.waitingForBackfill, + 'Linux analyze': TaskStatus.waitingForBackfill, + }); }); // Regression test for https://github.com/flutter/flutter/issues/167124. @@ -4294,9 +3291,7 @@ targets: // Enable fusion ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionCiYaml); - config.dynamicConfig = DynamicConfig( - unifiedCheckRunFlow: UnifiedCheckRunFlow(useForAll: true), - ); + config.dynamicConfig = DynamicConfig(); final userData = PresubmitUserData( commit: CommitRef( diff --git a/packages/cocoon_integration_test/lib/src/fakes/fake_firestore_service.dart b/packages/cocoon_integration_test/lib/src/fakes/fake_firestore_service.dart index 9b6601b284..fc81f5acee 100644 --- a/packages/cocoon_integration_test/lib/src/fakes/fake_firestore_service.dart +++ b/packages/cocoon_integration_test/lib/src/fakes/fake_firestore_service.dart @@ -469,8 +469,11 @@ abstract base class _FakeInMemoryFirestoreService 'simulate a backend failure.', ); } + final statusCode = result.any((r) => r.code == 9) + ? HttpStatus.conflict + : 500; throw DetailedApiRequestError( - 500, + statusCode, 'The transaction was aborted:\n' '${result.where((r) => r.code != 0).map((r) => r.message).join('\n')}', ); diff --git a/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart b/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart index 8d4568ae7c..8c03100ea3 100644 --- a/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart +++ b/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart @@ -1,17 +1,18 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in cocoon_integration_test/src/utilities/mocks.dart. +// Mocks generated by Mockito from annotations +// in cocoon_integration_test/lib/src/utilities/mocks.dart. // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes + import 'dart:async' as _i13; import 'dart:convert' as _i12; import 'dart:io' as _i11; -import 'dart:typed_data' as _i26; +import 'dart:typed_data' as _i25; import 'package:buildbucket/buildbucket_pb.dart' as _i6; -import 'package:cocoon_common/rpc_model.dart' as _i19; -import 'package:cocoon_integration_test/src/fakes/fake_entry.dart' as _i25; -import 'package:cocoon_service/cocoon_service.dart' as _i16; +import 'package:cocoon_common/rpc_model.dart' as _i18; +import 'package:cocoon_integration_test/src/fakes/fake_entry.dart' as _i24; +import 'package:cocoon_service/cocoon_service.dart' as _i2; import 'package:cocoon_service/src/foundation/github_checks_util.dart' as _i10; import 'package:cocoon_service/src/model/ci_yaml/ci_yaml.dart' as _i37; import 'package:cocoon_service/src/model/ci_yaml/target.dart' as _i27; @@ -20,15 +21,14 @@ import 'package:cocoon_service/src/model/common/presubmit_completed_check.dart' as _i38; import 'package:cocoon_service/src/model/github/checks.dart' as _i30; import 'package:cocoon_service/src/model/github/workflow_job.dart' as _i36; -import 'package:cocoon_service/src/service/big_query.dart' as _i18; -import 'package:cocoon_service/src/service/commit_service.dart' as _i21; -import 'package:cocoon_service/src/service/config.dart' as _i2; -import 'package:cocoon_service/src/service/discord_service.dart' as _i24; +import 'package:cocoon_service/src/service/big_query.dart' as _i17; +import 'package:cocoon_service/src/service/commit_service.dart' as _i20; +import 'package:cocoon_service/src/service/discord_service.dart' as _i23; import 'package:cocoon_service/src/service/github_service.dart' as _i9; import 'package:cocoon_service/src/service/luci_build_service/build_tags.dart' as _i34; import 'package:cocoon_service/src/service/luci_build_service/cipd_version.dart' - as _i23; + as _i22; import 'package:cocoon_service/src/service/luci_build_service/engine_artifacts.dart' as _i28; import 'package:cocoon_service/src/service/luci_build_service/pending_task.dart' @@ -39,16 +39,17 @@ import 'package:cocoon_service/src/service/scheduler/process_check_run_result.da as _i39; import 'package:fixnum/fixnum.dart' as _i32; import 'package:github/github.dart' as _i7; -import 'package:github/hooks.dart' as _i22; +import 'package:github/hooks.dart' as _i21; import 'package:googleapis/bigquery/v2.dart' as _i4; import 'package:googleapis/firestore/v1.dart' as _i14; import 'package:googleapis/shared.dart' as _i15; import 'package:googleapis_auth/auth_io.dart' as _i3; +import 'package:gql_exec/gql_exec.dart' as _i26; import 'package:graphql/client.dart' as _i8; import 'package:http/http.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i20; -import 'package:neat_cache/neat_cache.dart' as _i17; +import 'package:mockito/src/dummies.dart' as _i19; +import 'package:neat_cache/neat_cache.dart' as _i16; import 'package:process/src/interface/process_manager.dart' as _i35; // ignore_for_file: type=lint @@ -57,6 +58,7 @@ import 'package:process/src/interface/process_manager.dart' as _i35; // ignore_for_file: comment_references // ignore_for_file: deprecated_member_use // ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: experimental_member_use // ignore_for_file: implementation_imports // ignore_for_file: invalid_use_of_visible_for_testing_member // ignore_for_file: must_be_immutable @@ -378,17 +380,17 @@ class _FakeRepositorySlug_57 extends _i1.SmartFake } class _FakeCheckRunLockResult_58 extends _i1.SmartFake - implements _i16.CheckRunLockResult { + implements _i2.CheckRunLockResult { _FakeCheckRunLockResult_58(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeEntry_59 extends _i1.SmartFake implements _i17.Entry { +class _FakeEntry_59 extends _i1.SmartFake implements _i16.Entry { _FakeEntry_59(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } -class _FakeCache_60 extends _i1.SmartFake implements _i17.Cache { +class _FakeCache_60 extends _i1.SmartFake implements _i16.Cache { _FakeCache_60(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } @@ -397,7 +399,7 @@ class _FakeCache_60 extends _i1.SmartFake implements _i17.Cache { /// /// See the documentation for Mockito's code generation for more information. class MockAccessTokenService extends _i1.Mock - implements _i16.AccessTokenService { + implements _i2.AccessTokenService { MockAccessTokenService() { _i1.throwOnMissingStub(this); } @@ -427,7 +429,7 @@ class MockAccessTokenService extends _i1.Mock /// A class which mocks [BigQueryService]. /// /// See the documentation for Mockito's code generation for more information. -class MockBigQueryService extends _i1.Mock implements _i18.BigQueryService { +class MockBigQueryService extends _i1.Mock implements _i17.BigQueryService { MockBigQueryService() { _i1.throwOnMissingStub(this); } @@ -444,7 +446,7 @@ class MockBigQueryService extends _i1.Mock implements _i18.BigQueryService { as _i4.TabledataResource); @override - _i13.Future> listBuilderStatistic( + _i13.Future> listBuilderStatistic( String? projectId, { int? limit = 100, String? bucket = 'prod', @@ -455,14 +457,14 @@ class MockBigQueryService extends _i1.Mock implements _i18.BigQueryService { [projectId], {#limit: limit, #bucket: bucket}, ), - returnValue: _i13.Future>.value( - <_i18.BuilderStatistic>[], + returnValue: _i13.Future>.value( + <_i17.BuilderStatistic>[], ), ) - as _i13.Future>); + as _i13.Future>); @override - _i13.Future> listRecentBuildRecordsForBuilder( + _i13.Future> listRecentBuildRecordsForBuilder( String? projectId, { String? builder, int? limit, @@ -473,17 +475,17 @@ class MockBigQueryService extends _i1.Mock implements _i18.BigQueryService { [projectId], {#builder: builder, #limit: limit}, ), - returnValue: _i13.Future>.value( - <_i18.BuilderRecord>[], + returnValue: _i13.Future>.value( + <_i17.BuilderRecord>[], ), ) - as _i13.Future>); + as _i13.Future>); } /// A class which mocks [BranchService]. /// /// See the documentation for Mockito's code generation for more information. -class MockBranchService extends _i1.Mock implements _i16.BranchService { +class MockBranchService extends _i1.Mock implements _i2.BranchService { MockBranchService() { _i1.throwOnMissingStub(this); } @@ -498,21 +500,20 @@ class MockBranchService extends _i1.Mock implements _i16.BranchService { as _i13.Future); @override - _i13.Future> getReleaseBranches({ + _i13.Future> getReleaseBranches({ required _i7.RepositorySlug? slug, }) => (super.noSuchMethod( Invocation.method(#getReleaseBranches, [], {#slug: slug}), - returnValue: _i13.Future>.value(<_i19.Branch>[]), + returnValue: _i13.Future>.value(<_i18.Branch>[]), ) - as _i13.Future>); + as _i13.Future>); } /// A class which mocks [BuildBucketClient]. /// /// See the documentation for Mockito's code generation for more information. -// ignore: must_be_immutable -class MockBuildBucketClient extends _i1.Mock implements _i16.BuildBucketClient { +class MockBuildBucketClient extends _i1.Mock implements _i2.BuildBucketClient { MockBuildBucketClient() { _i1.throwOnMissingStub(this); } @@ -521,7 +522,7 @@ class MockBuildBucketClient extends _i1.Mock implements _i16.BuildBucketClient { String get buildBucketBuildUri => (super.noSuchMethod( Invocation.getter(#buildBucketBuildUri), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#buildBucketBuildUri), ), @@ -532,7 +533,7 @@ class MockBuildBucketClient extends _i1.Mock implements _i16.BuildBucketClient { String get buildBucketBuilderUri => (super.noSuchMethod( Invocation.getter(#buildBucketBuilderUri), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#buildBucketBuilderUri), ), @@ -707,13 +708,13 @@ class MockBuildBucketClient extends _i1.Mock implements _i16.BuildBucketClient { /// A class which mocks [CommitService]. /// /// See the documentation for Mockito's code generation for more information. -class MockCommitService extends _i1.Mock implements _i21.CommitService { +class MockCommitService extends _i1.Mock implements _i20.CommitService { MockCommitService() { _i1.throwOnMissingStub(this); } @override - _i13.Future handleCreateGithubRequest(_i22.CreateEvent? createEvent) => + _i13.Future handleCreateGithubRequest(_i21.CreateEvent? createEvent) => (super.noSuchMethod( Invocation.method(#handleCreateGithubRequest, [createEvent]), returnValue: _i13.Future.value(), @@ -768,7 +769,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#githubPrivateKey), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.getter(#githubPrivateKey), ), @@ -781,7 +782,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#githubPublicKey), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.getter(#githubPublicKey), ), @@ -794,7 +795,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#githubAppId), returnValue: _i13.Future.value( - _i20.dummyValue(this, Invocation.getter(#githubAppId)), + _i19.dummyValue(this, Invocation.getter(#githubAppId)), ), ) as _i13.Future); @@ -810,15 +811,15 @@ class MockConfig extends _i1.Mock implements _i2.Config { as _i13.Future>); @override - _i23.CipdVersion get defaultRecipeBundleRef => + _i22.CipdVersion get defaultRecipeBundleRef => (super.noSuchMethod( Invocation.getter(#defaultRecipeBundleRef), - returnValue: _i20.dummyValue<_i23.CipdVersion>( + returnValue: _i19.dummyValue<_i22.CipdVersion>( this, Invocation.getter(#defaultRecipeBundleRef), ), ) - as _i23.CipdVersion); + as _i22.CipdVersion); @override List get releaseBranches => @@ -832,7 +833,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get releaseCandidateBranchPath => (super.noSuchMethod( Invocation.getter(#releaseCandidateBranchPath), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#releaseCandidateBranchPath), ), @@ -878,7 +879,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#oauthClientId), returnValue: _i13.Future.value( - _i20.dummyValue(this, Invocation.getter(#oauthClientId)), + _i19.dummyValue(this, Invocation.getter(#oauthClientId)), ), ) as _i13.Future); @@ -888,7 +889,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#frobWebhookKey), returnValue: _i13.Future.value( - _i20.dummyValue(this, Invocation.getter(#frobWebhookKey)), + _i19.dummyValue(this, Invocation.getter(#frobWebhookKey)), ), ) as _i13.Future); @@ -898,7 +899,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#githubOAuthToken), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.getter(#githubOAuthToken), ), @@ -911,7 +912,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#discordTreeStatusWebhookUrl), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.getter(#discordTreeStatusWebhookUrl), ), @@ -924,7 +925,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#geminiLogAnalyzerKey), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.getter(#geminiLogAnalyzerKey), ), @@ -936,7 +937,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get wrongBaseBranchPullRequestMessage => (super.noSuchMethod( Invocation.getter(#wrongBaseBranchPullRequestMessage), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#wrongBaseBranchPullRequestMessage), ), @@ -947,7 +948,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get releaseBranchPullRequestMessage => (super.noSuchMethod( Invocation.getter(#releaseBranchPullRequestMessage), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#releaseBranchPullRequestMessage), ), @@ -959,7 +960,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.getter(#webhookKey), returnValue: _i13.Future.value( - _i20.dummyValue(this, Invocation.getter(#webhookKey)), + _i19.dummyValue(this, Invocation.getter(#webhookKey)), ), ) as _i13.Future); @@ -968,7 +969,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get mergeConflictPullRequestMessage => (super.noSuchMethod( Invocation.getter(#mergeConflictPullRequestMessage), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#mergeConflictPullRequestMessage), ), @@ -979,7 +980,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get missingTestsPullRequestMessage => (super.noSuchMethod( Invocation.getter(#missingTestsPullRequestMessage), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#missingTestsPullRequestMessage), ), @@ -990,7 +991,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterGoldPending => (super.noSuchMethod( Invocation.getter(#flutterGoldPending), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterGoldPending), ), @@ -1001,7 +1002,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterGoldSuccess => (super.noSuchMethod( Invocation.getter(#flutterGoldSuccess), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterGoldSuccess), ), @@ -1012,7 +1013,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterGoldChanges => (super.noSuchMethod( Invocation.getter(#flutterGoldChanges), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterGoldChanges), ), @@ -1023,7 +1024,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterGoldStalePR => (super.noSuchMethod( Invocation.getter(#flutterGoldStalePR), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterGoldStalePR), ), @@ -1034,7 +1035,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterGoldDraftChange => (super.noSuchMethod( Invocation.getter(#flutterGoldDraftChange), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterGoldDraftChange), ), @@ -1085,7 +1086,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterTreeStatusRed => (super.noSuchMethod( Invocation.getter(#flutterTreeStatusRed), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterTreeStatusRed), ), @@ -1096,7 +1097,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String get flutterTreeStatusEmergency => (super.noSuchMethod( Invocation.getter(#flutterTreeStatusEmergency), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#flutterTreeStatusEmergency), ), @@ -1112,21 +1113,21 @@ class MockConfig extends _i1.Mock implements _i2.Config { as Set); @override - _i16.DynamicConfig get flags => + _i2.DynamicConfig get flags => (super.noSuchMethod( Invocation.getter(#flags), - returnValue: _i20.dummyValue<_i16.DynamicConfig>( + returnValue: _i19.dummyValue<_i2.DynamicConfig>( this, Invocation.getter(#flags), ), ) - as _i16.DynamicConfig); + as _i2.DynamicConfig); @override String wrongHeadBranchPullRequestMessage(String? branch) => (super.noSuchMethod( Invocation.method(#wrongHeadBranchPullRequestMessage, [branch]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#wrongHeadBranchPullRequestMessage, [branch]), ), @@ -1137,7 +1138,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String flutterGoldInitialAlert(String? url) => (super.noSuchMethod( Invocation.method(#flutterGoldInitialAlert, [url]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#flutterGoldInitialAlert, [url]), ), @@ -1148,7 +1149,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String flutterGoldFollowUpAlert(String? url) => (super.noSuchMethod( Invocation.method(#flutterGoldFollowUpAlert, [url]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#flutterGoldFollowUpAlert, [url]), ), @@ -1159,7 +1160,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String flutterGoldAlertConstant(_i7.RepositorySlug? slug) => (super.noSuchMethod( Invocation.method(#flutterGoldAlertConstant, [slug]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#flutterGoldAlertConstant, [slug]), ), @@ -1170,7 +1171,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { String flutterGoldCommentID(_i7.PullRequest? pr) => (super.noSuchMethod( Invocation.method(#flutterGoldCommentID, [pr]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#flutterGoldCommentID, [pr]), ), @@ -1182,7 +1183,7 @@ class MockConfig extends _i1.Mock implements _i2.Config { (super.noSuchMethod( Invocation.method(#generateGithubToken, [slug]), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.method(#generateGithubToken, [slug]), ), @@ -1279,54 +1280,54 @@ class MockConfig extends _i1.Mock implements _i2.Config { /// A class which mocks [DiscordService]. /// /// See the documentation for Mockito's code generation for more information. -class MockDiscordService extends _i1.Mock implements _i24.DiscordService { +class MockDiscordService extends _i1.Mock implements _i23.DiscordService { MockDiscordService() { _i1.throwOnMissingStub(this); } @override - _i13.Future<_i24.DiscordStatus> postTreeStatusMessage(String? message) => + _i13.Future<_i23.DiscordStatus> postTreeStatusMessage(String? message) => (super.noSuchMethod( Invocation.method(#postTreeStatusMessage, [message]), - returnValue: _i13.Future<_i24.DiscordStatus>.value( - _i24.DiscordStatus.ok, + returnValue: _i13.Future<_i23.DiscordStatus>.value( + _i23.DiscordStatus.ok, ), ) - as _i13.Future<_i24.DiscordStatus>); + as _i13.Future<_i23.DiscordStatus>); } /// A class which mocks [FakeEntry]. /// /// See the documentation for Mockito's code generation for more information. -class MockFakeEntry extends _i1.Mock implements _i25.FakeEntry { +class MockFakeEntry extends _i1.Mock implements _i24.FakeEntry { MockFakeEntry() { _i1.throwOnMissingStub(this); } @override - _i26.Uint8List get value => + _i25.Uint8List get value => (super.noSuchMethod( Invocation.getter(#value), - returnValue: _i26.Uint8List(0), + returnValue: _i25.Uint8List(0), ) - as _i26.Uint8List); + as _i25.Uint8List); @override - set value(_i26.Uint8List? value) => super.noSuchMethod( + set value(_i25.Uint8List? value) => super.noSuchMethod( Invocation.setter(#value, value), returnValueForMissingStub: null, ); @override - _i13.Future<_i26.Uint8List> get([ - _i13.Future<_i26.Uint8List?> Function()? create, + _i13.Future<_i25.Uint8List> get([ + _i13.Future<_i25.Uint8List?> Function()? create, Duration? ttl, ]) => (super.noSuchMethod( Invocation.method(#get, [create, ttl]), - returnValue: _i13.Future<_i26.Uint8List>.value(_i26.Uint8List(0)), + returnValue: _i13.Future<_i25.Uint8List>.value(_i25.Uint8List(0)), ) - as _i13.Future<_i26.Uint8List>); + as _i13.Future<_i25.Uint8List>); @override _i13.Future purge({int? retries = 0}) => @@ -1338,12 +1339,12 @@ class MockFakeEntry extends _i1.Mock implements _i25.FakeEntry { as _i13.Future); @override - _i13.Future<_i26.Uint8List?> set(_i26.Uint8List? value, [Duration? ttl]) => + _i13.Future<_i25.Uint8List?> set(_i25.Uint8List? value, [Duration? ttl]) => (super.noSuchMethod( Invocation.method(#set, [value, ttl]), - returnValue: _i13.Future<_i26.Uint8List?>.value(), + returnValue: _i13.Future<_i25.Uint8List?>.value(), ) - as _i13.Future<_i26.Uint8List?>); + as _i13.Future<_i25.Uint8List?>); } /// A class which mocks [IssuesService]. @@ -1852,7 +1853,7 @@ class MockIssuesService extends _i1.Mock implements _i7.IssuesService { /// /// See the documentation for Mockito's code generation for more information. class MockGithubChecksService extends _i1.Mock - implements _i16.GithubChecksService { + implements _i2.GithubChecksService { MockGithubChecksService() { _i1.throwOnMissingStub(this); } @@ -1891,7 +1892,7 @@ class MockGithubChecksService extends _i1.Mock @override _i13.Future updateCheckStatus({ required _i6.Build? build, - required _i16.LuciBuildService? luciBuildService, + required _i2.LuciBuildService? luciBuildService, required _i7.RepositorySlug? slug, required int? checkRunId, bool? rescheduled = false, @@ -1916,7 +1917,7 @@ class MockGithubChecksService extends _i1.Mock String getGithubSummary(String? summary) => (super.noSuchMethod( Invocation.method(#getGithubSummary, [summary]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#getGithubSummary, [summary]), ), @@ -1927,7 +1928,7 @@ class MockGithubChecksService extends _i1.Mock String getGithubSummaryWithHeader(String? header, String? summary) => (super.noSuchMethod( Invocation.method(#getGithubSummaryWithHeader, [header, summary]), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.method(#getGithubSummaryWithHeader, [header, summary]), ), @@ -1984,7 +1985,7 @@ class MockGithubChecksUtil extends _i1.Mock implements _i10.GithubChecksUtil { @override _i13.Future> allCheckRuns( _i7.GitHub? gitHubClient, - _i22.CheckSuiteEvent? checkSuiteEvent, + _i21.CheckSuiteEvent? checkSuiteEvent, ) => (super.noSuchMethod( Invocation.method(#allCheckRuns, [gitHubClient, checkSuiteEvent]), @@ -2514,7 +2515,7 @@ class MockGithubService extends _i1.Mock implements _i9.GithubService { (super.noSuchMethod( Invocation.method(#getFileContent, [slug, path], {#ref: ref}), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.method(#getFileContent, [slug, path], {#ref: ref}), ), @@ -3023,7 +3024,7 @@ class MockGraphQLClient extends _i1.Mock implements _i8.GraphQLClient { @override Map? readQuery( - _i8.Request? request, { + _i26.Request? request, { bool? optimistic = true, }) => (super.noSuchMethod( @@ -3047,7 +3048,7 @@ class MockGraphQLClient extends _i1.Mock implements _i8.GraphQLClient { @override void writeQuery( - _i8.Request? request, { + _i26.Request? request, { required Map? data, bool? broadcast = true, }) => super.noSuchMethod( @@ -3455,7 +3456,7 @@ class MockHttpClientRequest extends _i1.Mock implements _i11.HttpClientRequest { String get method => (super.noSuchMethod( Invocation.getter(#method), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#method), ), @@ -3638,7 +3639,7 @@ class MockHttpClientResponse extends _i1.Mock String get reasonPhrase => (super.noSuchMethod( Invocation.getter(#reasonPhrase), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#reasonPhrase), ), @@ -3897,8 +3898,8 @@ class MockHttpClientResponse extends _i1.Mock (super.noSuchMethod( Invocation.method(#fold, [initialValue, combine]), returnValue: - _i20.ifNotNull( - _i20.dummyValueOrNull( + _i19.ifNotNull( + _i19.dummyValueOrNull( this, Invocation.method(#fold, [initialValue, combine]), ), @@ -3916,7 +3917,7 @@ class MockHttpClientResponse extends _i1.Mock (super.noSuchMethod( Invocation.method(#join, [separator]), returnValue: _i13.Future.value( - _i20.dummyValue( + _i19.dummyValue( this, Invocation.method(#join, [separator]), ), @@ -3986,8 +3987,8 @@ class MockHttpClientResponse extends _i1.Mock (super.noSuchMethod( Invocation.method(#drain, [futureValue]), returnValue: - _i20.ifNotNull( - _i20.dummyValueOrNull( + _i19.ifNotNull( + _i19.dummyValueOrNull( this, Invocation.method(#drain, [futureValue]), ), @@ -4098,7 +4099,7 @@ class MockHttpClientResponse extends _i1.Mock /// A class which mocks [LuciBuildService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { +class MockLuciBuildService extends _i1.Mock implements _i2.LuciBuildService { MockLuciBuildService() { _i1.throwOnMissingStub(this); } @@ -4150,7 +4151,7 @@ class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { required _i28.EngineArtifacts? engineArtifacts, _i7.CheckRun? dashboardChecks, _i7.CheckRun? mergeQueueGuard, - _i16.CiStage? stage, + _i2.CiStage? stage, }) => (super.noSuchMethod( Invocation.method(#scheduleTryBuilds, [], { @@ -4172,7 +4173,7 @@ class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { required _i28.EngineArtifacts? engineArtifacts, _i7.CheckRun? dashboardChecks, _i7.CheckRun? mergeQueueGuard, - required _i16.CiStage? stage, + required _i2.CiStage? stage, }) => (super.noSuchMethod( Invocation.method(#reScheduleTryBuilds, [], { @@ -4250,7 +4251,7 @@ class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { _i30.CheckRunEvent? checkRunEvent, { required _i31.CommitRef? commit, required _i27.Target? target, - required _i16.Task? task, + required _i2.Task? task, }) => (super.noSuchMethod( Invocation.method( @@ -4370,7 +4371,7 @@ class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { _i13.Future rerunBuilder({ required _i31.CommitRef? commit, required _i27.Target? target, - required _i16.Task? task, + required _i2.Task? task, Iterable<_i34.BuildTag>? tags = const [], }) => (super.noSuchMethod( @@ -4387,7 +4388,7 @@ class MockLuciBuildService extends _i1.Mock implements _i16.LuciBuildService { @override _i13.Future rerunDartInternalReleaseBuilder({ required _i31.CommitRef? commit, - required _i16.Task? task, + required _i2.Task? task, }) => (super.noSuchMethod( Invocation.method(#rerunDartInternalReleaseBuilder, [], { @@ -4471,7 +4472,7 @@ class MockProcessManager extends _i1.Mock implements _i35.ProcessManager { }, ), returnValue: _i13.Future<_i11.ProcessResult>.value( - _i20.dummyValue<_i11.ProcessResult>( + _i19.dummyValue<_i11.ProcessResult>( this, Invocation.method( #run, @@ -4513,7 +4514,7 @@ class MockProcessManager extends _i1.Mock implements _i35.ProcessManager { #stderrEncoding: stderrEncoding, }, ), - returnValue: _i20.dummyValue<_i11.ProcessResult>( + returnValue: _i19.dummyValue<_i11.ProcessResult>( this, Invocation.method( #runSync, @@ -5473,7 +5474,7 @@ class MockBeginTransactionResponse extends _i1.Mock /// /// See the documentation for Mockito's code generation for more information. class MockPullRequestLabelProcessor extends _i1.Mock - implements _i16.PullRequestLabelProcessor { + implements _i2.PullRequestLabelProcessor { MockPullRequestLabelProcessor() { _i1.throwOnMissingStub(this); } @@ -5524,7 +5525,7 @@ class MockPullRequestLabelProcessor extends _i1.Mock String get logCrumb => (super.noSuchMethod( Invocation.getter(#logCrumb), - returnValue: _i20.dummyValue( + returnValue: _i19.dummyValue( this, Invocation.getter(#logCrumb), ), @@ -5561,13 +5562,13 @@ class MockPullRequestLabelProcessor extends _i1.Mock /// A class which mocks [Scheduler]. /// /// See the documentation for Mockito's code generation for more information. -class MockScheduler extends _i1.Mock implements _i16.Scheduler { +class MockScheduler extends _i1.Mock implements _i2.Scheduler { MockScheduler() { _i1.throwOnMissingStub(this); } @override - _i13.Future addCommits(List<_i16.Commit>? commits) => + _i13.Future addCommits(List<_i2.Commit>? commits) => (super.noSuchMethod( Invocation.method(#addCommits, [commits]), returnValue: _i13.Future.value(), @@ -5683,7 +5684,7 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { String? baseRef, _i7.RepositorySlug? slug, String? headSha, - _i16.CiStage? stage, + _i2.CiStage? stage, ) => (super.noSuchMethod( Invocation.method(#getMergeGroupTargetsForStage, [ @@ -5727,33 +5728,30 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { as _i13.Future); @override - _i13.Future<_i16.CheckRunLockResult> lockMergeGroupChecks( + _i13.Future<_i2.CheckRunLockResult> lockMergeGroupChecks( _i7.RepositorySlug? slug, String? headSha, { String? detailsUrl, - required bool? isUnifiedCheckRun, + required bool? isPresubmit, }) => (super.noSuchMethod( Invocation.method( #lockMergeGroupChecks, [slug, headSha], - {#detailsUrl: detailsUrl, #isUnifiedCheckRun: isUnifiedCheckRun}, + {#detailsUrl: detailsUrl, #isPresubmit: isPresubmit}, ), - returnValue: _i13.Future<_i16.CheckRunLockResult>.value( + returnValue: _i13.Future<_i2.CheckRunLockResult>.value( _FakeCheckRunLockResult_58( this, Invocation.method( #lockMergeGroupChecks, [slug, headSha], - { - #detailsUrl: detailsUrl, - #isUnifiedCheckRun: isUnifiedCheckRun, - }, + {#detailsUrl: detailsUrl, #isPresubmit: isPresubmit}, ), ), ), ) - as _i13.Future<_i16.CheckRunLockResult>); + as _i13.Future<_i2.CheckRunLockResult>); @override _i13.Future<_i7.CheckRun?> createAwaitingCicdLabelCheckRun( @@ -5785,13 +5783,13 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { as _i13.Future); @override - _i13.Future unlockMergeQueueGuard( + _i13.Future unlockCheckRun( _i7.RepositorySlug? slug, String? headSha, _i7.CheckRun? lock, ) => (super.noSuchMethod( - Invocation.method(#unlockMergeQueueGuard, [slug, headSha, lock]), + Invocation.method(#unlockCheckRun, [slug, headSha, lock]), returnValue: _i13.Future.value(), returnValueForMissingStub: _i13.Future.value(), ) @@ -5897,7 +5895,7 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { (super.noSuchMethod( Invocation.method(#processCheckRun, [checkRunEvent]), returnValue: _i13.Future<_i39.ProcessCheckRunResult>.value( - _i20.dummyValue<_i39.ProcessCheckRunResult>( + _i19.dummyValue<_i39.ProcessCheckRunResult>( this, Invocation.method(#processCheckRun, [checkRunEvent]), ), @@ -5914,7 +5912,7 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { (super.noSuchMethod( Invocation.method(#reRunTargets, [slug, pullRequest, names]), returnValue: _i13.Future<_i39.ProcessCheckRunResult>.value( - _i20.dummyValue<_i39.ProcessCheckRunResult>( + _i19.dummyValue<_i39.ProcessCheckRunResult>( this, Invocation.method(#reRunTargets, [slug, pullRequest, names]), ), @@ -5956,7 +5954,7 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { returnValue: _i13.Future<(List<_i27.Target>, _i28.EngineArtifacts)>.value(( <_i27.Target>[], - _i20.dummyValue<_i28.EngineArtifacts>( + _i19.dummyValue<_i28.EngineArtifacts>( this, Invocation.method(#getAllTargetsForPullRequest, [ slug, @@ -5982,35 +5980,35 @@ class MockScheduler extends _i1.Mock implements _i16.Scheduler { /// A class which mocks [Cache]. /// /// See the documentation for Mockito's code generation for more information. -class MockCache extends _i1.Mock implements _i17.Cache<_i26.Uint8List> { +class MockCache extends _i1.Mock implements _i16.Cache<_i25.Uint8List> { MockCache() { _i1.throwOnMissingStub(this); } @override - _i17.Entry<_i26.Uint8List> operator [](String? key) => + _i16.Entry<_i25.Uint8List> operator [](String? key) => (super.noSuchMethod( Invocation.method(#[], [key]), - returnValue: _FakeEntry_59<_i26.Uint8List>( + returnValue: _FakeEntry_59<_i25.Uint8List>( this, Invocation.method(#[], [key]), ), ) - as _i17.Entry<_i26.Uint8List>); + as _i16.Entry<_i25.Uint8List>); @override - _i17.Cache<_i26.Uint8List> withPrefix(String? prefix) => + _i16.Cache<_i25.Uint8List> withPrefix(String? prefix) => (super.noSuchMethod( Invocation.method(#withPrefix, [prefix]), - returnValue: _FakeCache_60<_i26.Uint8List>( + returnValue: _FakeCache_60<_i25.Uint8List>( this, Invocation.method(#withPrefix, [prefix]), ), ) - as _i17.Cache<_i26.Uint8List>); + as _i16.Cache<_i25.Uint8List>); @override - _i17.Cache withCodec(_i12.Codec? codec) => + _i16.Cache withCodec(_i12.Codec? codec) => (super.noSuchMethod( Invocation.method(#withCodec, [codec]), returnValue: _FakeCache_60( @@ -6018,16 +6016,16 @@ class MockCache extends _i1.Mock implements _i17.Cache<_i26.Uint8List> { Invocation.method(#withCodec, [codec]), ), ) - as _i17.Cache); + as _i16.Cache); @override - _i17.Cache<_i26.Uint8List> withTTL(Duration? ttl) => + _i16.Cache<_i25.Uint8List> withTTL(Duration? ttl) => (super.noSuchMethod( Invocation.method(#withTTL, [ttl]), - returnValue: _FakeCache_60<_i26.Uint8List>( + returnValue: _FakeCache_60<_i25.Uint8List>( this, Invocation.method(#withTTL, [ttl]), ), ) - as _i17.Cache<_i26.Uint8List>); + as _i16.Cache<_i25.Uint8List>); } diff --git a/packages/cocoon_integration_test/test/fake_firestore_service_test.dart b/packages/cocoon_integration_test/test/fake_firestore_service_test.dart index e0d149ee67..043f3d22b1 100644 --- a/packages/cocoon_integration_test/test/fake_firestore_service_test.dart +++ b/packages/cocoon_integration_test/test/fake_firestore_service_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:cocoon_integration_test/testing.dart'; import 'package:cocoon_server_test/test_logging.dart'; import 'package:cocoon_service/src/service/firestore.dart'; @@ -524,7 +526,7 @@ void main() { isA().having( (e) => e.status, 'status', - 500, + HttpStatus.conflict, ), ), ); From 28f1fd9a7580d28d75e70052a722c403c4c90f86 Mon Sep 17 00:00:00 2001 From: "Dmitry Grand (dmgr)" Date: Fri, 18 Sep 2026 18:05:02 -0700 Subject: [PATCH 2/4] process merge queue using CiStaging --- app_dart/lib/src/service/scheduler.dart | 139 ++++++++++++++---- app_dart/test/service/scheduler_test.dart | 105 ++++++------- .../lib/src/utilities/mocks.mocks.dart | 2 +- 3 files changed, 163 insertions(+), 83 deletions(-) diff --git a/app_dart/lib/src/service/scheduler.dart b/app_dart/lib/src/service/scheduler.dart index cd8304dcc5..4f24c2cd0d 100644 --- a/app_dart/lib/src/service/scheduler.dart +++ b/app_dart/lib/src/service/scheduler.dart @@ -19,10 +19,12 @@ import '../foundation/utils.dart'; import '../model/ci_yaml/ci_yaml.dart'; import '../model/ci_yaml/target.dart'; import '../model/commit_ref.dart'; +import '../model/common/checks_extension.dart'; import '../model/common/presubmit_completed_check.dart'; import '../model/common/presubmit_guard_conclusion.dart'; import '../model/common/presubmit_job_state.dart'; import '../model/firestore/base.dart'; +import '../model/firestore/ci_staging.dart'; import '../model/firestore/commit.dart' as fs; import '../model/firestore/pr_check_runs.dart'; import '../model/firestore/presubmit_guard.dart'; @@ -507,9 +509,9 @@ class Scheduler { // there are situations (see code above) when it needs to be unlocked // immediately. if (unlockMergeGroup) { - await unlockCheckRun(slug, sha, dashboardChecks); + await unlockMergeQueueGuard(slug, sha, dashboardChecks); if (mergeQueueGuard != null) { - await unlockCheckRun(slug, sha, mergeQueueGuard); + await unlockMergeQueueGuard(slug, sha, mergeQueueGuard); } } log.info( @@ -640,7 +642,7 @@ class Scheduler { // If the repo is not fusion, it doesn't run anything in the MQ, so just // close the merge group guard. if (!isFusion) { - await unlockCheckRun(slug, headSha, mergeQueueGuard); + await unlockMergeQueueGuard(slug, headSha, mergeQueueGuard); return; } @@ -948,7 +950,7 @@ $s /// /// If the guard is guarding a pull request, this immediately makes the pull /// request eligible for enqueuing into the merge queue. - Future unlockCheckRun( + Future unlockMergeQueueGuard( RepositorySlug slug, String headSha, CheckRun lock, @@ -1107,15 +1109,51 @@ detailsUrl: $detailsUrl final logCrumb = 'checkCompleted(${check.name}, $requestor, ${check.slug}, ${check.sha}, ${check.status})'; - final stage = - check.stage ?? - (check.slug == Config.flutterSlug - ? CiStage.fusionTests - : CiStage.genericTests); - final stagingConclusion = await _markUnifiedCheckRunConclusion( - guardId: check.guardId, - state: check.state, - ); + final isFusion = check.slug == Config.flutterSlug; + if (!isFusion && check.isMergeGroup) { + return true; + } + + late CiStage stage; + late PresubmitGuardConclusion stagingConclusion; + + if (check.isMergeGroup) { + // for github flow check runs are processed only if the build succeeded or + // some kind of failure occurred. + if (!check.status.isComplete) { + return true; + } + // Check runs are fired at every stage. However, at this point it is unknown + // if this check run belongs in the engine build stage or in the test stage. + // So first look for it in the engine stage, and if it's missing, look for + // it in the test stage. + stage = CiStage.fusionEngineBuild; + stagingConclusion = await _recordCurrentCiStage( + slug: check.slug, + sha: check.sha, + stage: stage, + name: check.name, + conclusion: check.status.toTaskConclusion(), + ); + + if (stagingConclusion.result == PresubmitGuardConclusionResult.missing) { + // Check run not found in the engine stage. Look for it in the test stage. + stage = CiStage.fusionTests; + stagingConclusion = await _recordCurrentCiStage( + slug: check.slug, + sha: check.sha, + stage: stage, + name: check.name, + conclusion: check.status.toTaskConclusion(), + ); + } + } else { + stage = check.stage!; + stagingConclusion = await _markUnifiedCheckRunConclusion( + guardId: check.guardId, + state: check.state, + ); + } // First; check if we even recorded anything. This can occur if we've already passed the check_run and // have moved on to running more tests (which wouldn't be present in our document). @@ -1225,11 +1263,10 @@ detailsUrl: $detailsUrl case CiStage.fusionTests: case CiStage.genericTests: await _closeSuccessfulTestStage( + check: check, + logCrumb: logCrumb, dashboardChecks: stagingConclusion.dashboardChecks, mergeQueueGuard: stagingConclusion.mergeQueueGuard, - slug: check.slug, - sha: check.sha, - logCrumb: logCrumb, ); break; } @@ -1283,18 +1320,39 @@ detailsUrl: $detailsUrl } Future _closeSuccessfulTestStage({ + required PresubmitCompletedJob check, + required String logCrumb, String? dashboardChecks, String? mergeQueueGuard, - required RepositorySlug slug, - required String sha, - required String logCrumb, }) async { log.info('$logCrumb: Test stage completed'); - if (dashboardChecks != null) { - await unlockCheckRun(slug, sha, checkRunFromString(dashboardChecks)); - } - if (mergeQueueGuard != null) { - await unlockCheckRun(slug, sha, checkRunFromString(mergeQueueGuard)); + if (check.isMergeGroup) { + if (check.stage != CiStage.fusionTests) { + // generic tests do not have a staging document nor are associated + // with a merge group - they are only used to collect commit stats. + log.warn('$logCrumb: generic tests have no merge queue guard.'); + } else if (mergeQueueGuard != null) { + await unlockMergeQueueGuard( + check.slug, + check.sha, + checkRunFromString(mergeQueueGuard), + ); + } + } else { + if (dashboardChecks != null) { + await unlockMergeQueueGuard( + check.slug, + check.sha, + checkRunFromString(dashboardChecks), + ); + } + if (mergeQueueGuard != null) { + await unlockMergeQueueGuard( + check.slug, + check.sha, + checkRunFromString(mergeQueueGuard), + ); + } } } @@ -1333,7 +1391,7 @@ detailsUrl: $detailsUrl // Unlock the guarding check_run. final checkRunGuard = checkRunFromString(mergeQueueGuard); - await unlockCheckRun(slug, sha, checkRunGuard); + await unlockMergeQueueGuard(slug, sha, checkRunGuard); } /// Schedules post-engine build tests (i.e. engine tests, and framework tests). @@ -1500,6 +1558,37 @@ $stacktrace } } + Future _recordCurrentCiStage({ + required RepositorySlug slug, + required String sha, + required CiStage stage, + required String name, + required TaskConclusion conclusion, + }) async { + final logCrumb = 'checkCompleted($name, $slug, $sha, $conclusion)'; + final documentName = CiStaging.documentNameFor( + slug: slug, + sha: sha, + stage: stage, + ); + log.info('$logCrumb: $documentName'); + + // We're doing a transactional update, which could fail if multiple tasks are running at the same time; so retry + // a sane amount of times before giving up. + const r = RetryOptions(maxAttempts: 3, delayFactor: Duration(seconds: 2)); + + return r.retry(() { + return CiStaging.markConclusion( + firestoreService: _firestore, + slug: slug, + sha: sha, + stage: stage, + checkRun: name, + conclusion: conclusion, + ); + }); + } + Future _markUnifiedCheckRunConclusion({ required PresubmitGuardId guardId, required PresubmitJobState state, diff --git a/app_dart/test/service/scheduler_test.dart b/app_dart/test/service/scheduler_test.dart index 6ae5cf024c..0b1d31050e 100644 --- a/app_dart/test/service/scheduler_test.dart +++ b/app_dart/test/service/scheduler_test.dart @@ -3354,34 +3354,22 @@ targets: pullRequest: pullRequest, ); - // Make it look like a merge group - // checkRunGuard.checkSuite!.headBranch = 'gh-readonly-queue/master/pr-123-abc'; - - // Initialize presubmit guard for tests stage - firestore.putDocument( - PresubmitGuard( - checkRun: dashboardChecks, - checkRunGuard: mergeQueueGuard, - headSha: pullRequest.head!.sha!, - slug: pullRequest.base!.repo!.slug(), - prNum: pullRequest.number!, - stage: CiStage.fusionTests, - author: pullRequest.user!.login!, - creationTime: DateTime.now().millisecondsSinceEpoch, - jobs: {'Linux test': TaskStatus.waitingForBackfill}, - remainingJobs: 1, - failedJobs: 0, - ), + await CiStaging.initializeDocument( + firestoreService: firestore, + slug: pullRequest.base!.repo!.slug(), + sha: pullRequest.head!.sha!, + stage: CiStage.fusionEngineBuild, + tasks: [], + checkRunGuard: '$mergeQueueGuard', ); - // Initialize check run for the task - firestore.putDocument( - PresubmitJob.init( - slug: pullRequest.base!.repo!.slug(), - jobName: 'Linux test', - checkRunId: dashboardChecks.id!, - creationTime: DateTime.now().millisecondsSinceEpoch, - ), + await CiStaging.initializeDocument( + firestoreService: firestore, + slug: pullRequest.base!.repo!.slug(), + sha: pullRequest.head!.sha!, + stage: CiStage.fusionTests, + tasks: ['Linux test'], + checkRunGuard: '$mergeQueueGuard', ); final userData = PresubmitUserData( @@ -3441,9 +3429,16 @@ targets: ), ); - final guards = await firestore.query(PresubmitGuard.collectionId, {}); - final guard = PresubmitGuard.fromDocument(guards.single); - expect(guard.failedJobs, 1); + expect( + firestore, + existsInStorage(CiStaging.metadata, [ + isCiStaging.hasStage(CiStage.fusionEngineBuild), + isCiStaging + .hasStage(CiStage.fusionTests) + .hasFailed(1) + .hasCheckRuns({'Linux test': TaskConclusion.failure}), + ]), + ); }, ); @@ -3542,33 +3537,22 @@ targets: pullRequest: pullRequest, ); - // Make it look like a merge group - // checkRunGuard.checkSuite!.headBranch = 'gh-readonly-queue/master/pr-123-abc'; - - // Initialize presubmit guard for tests stage - firestore.putDocument( - PresubmitGuard( - checkRun: checkRunGuard, - headSha: pullRequest.head!.sha!, - slug: pullRequest.base!.repo!.slug(), - prNum: pullRequest.number!, - stage: CiStage.fusionTests, - author: pullRequest.user!.login!, - creationTime: DateTime.now().millisecondsSinceEpoch, - jobs: {'Linux test': TaskStatus.waitingForBackfill}, - remainingJobs: 1, - failedJobs: 0, - ), + await CiStaging.initializeDocument( + firestoreService: firestore, + slug: pullRequest.base!.repo!.slug(), + sha: pullRequest.head!.sha!, + stage: CiStage.fusionEngineBuild, + tasks: [], + checkRunGuard: '$checkRunGuard', ); - // Initialize check run for the task - firestore.putDocument( - PresubmitJob.init( - slug: pullRequest.base!.repo!.slug(), - jobName: 'Linux test', - checkRunId: checkRunGuard.id!, - creationTime: DateTime.now().millisecondsSinceEpoch, - ), + await CiStaging.initializeDocument( + firestoreService: firestore, + slug: pullRequest.base!.repo!.slug(), + sha: pullRequest.head!.sha!, + stage: CiStage.fusionTests, + tasks: ['Linux test'], + checkRunGuard: '$checkRunGuard', ); final userData = PresubmitUserData( @@ -3611,9 +3595,16 @@ targets: ), ).called(1); - final guards = await firestore.query(PresubmitGuard.collectionId, {}); - final guard = PresubmitGuard.fromDocument(guards.single); - expect(guard.remainingJobs, 0); + expect( + firestore, + existsInStorage(CiStaging.metadata, [ + isCiStaging.hasStage(CiStage.fusionEngineBuild), + isCiStaging + .hasStage(CiStage.fusionTests) + .hasRemaining(0) + .hasCheckRuns({'Linux test': TaskConclusion.success}), + ]), + ); }); test( diff --git a/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart b/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart index 8c03100ea3..844434e46f 100644 --- a/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart +++ b/packages/cocoon_integration_test/lib/src/utilities/mocks.mocks.dart @@ -5783,7 +5783,7 @@ class MockScheduler extends _i1.Mock implements _i2.Scheduler { as _i13.Future); @override - _i13.Future unlockCheckRun( + _i13.Future unlockMergeQueueGuard( _i7.RepositorySlug? slug, String? headSha, _i7.CheckRun? lock, From c202dae3d7c65a7c722cdec7df78ab8617c2d548 Mon Sep 17 00:00:00 2001 From: "Dmitry Grand (dmgr)" Date: Mon, 21 Sep 2026 15:17:58 -0700 Subject: [PATCH 3/4] added unit tests for merge queue flow --- app_dart/test/service/scheduler_test.dart | 793 ++++++++++++++++++++++ 1 file changed, 793 insertions(+) diff --git a/app_dart/test/service/scheduler_test.dart b/app_dart/test/service/scheduler_test.dart index 0b1d31050e..821e9e1a0b 100644 --- a/app_dart/test/service/scheduler_test.dart +++ b/app_dart/test/service/scheduler_test.dart @@ -2640,6 +2640,8 @@ targets: }), ]), ); + expect(await firestore.query(PresubmitGuard.collectionId, {}), isEmpty); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); verify( luci.getAvailableBuilderSet( @@ -2787,6 +2789,8 @@ targets: ]), ), ); + expect(await firestore.query(PresubmitGuard.collectionId, {}), isEmpty); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); verifyNever( luci.getAvailableBuilderSet( @@ -2908,6 +2912,8 @@ targets: .hasCheckRuns(contains('Mac engine_build')), ]), ); + expect(await firestore.query(PresubmitGuard.collectionId, {}), isEmpty); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); verify( mockGithubChecksUtil.createCheckRun( @@ -3037,6 +3043,8 @@ targets: ), ]), ); + expect(await firestore.query(PresubmitGuard.collectionId, {}), isEmpty); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); verify( luci.getAvailableBuilderSet( @@ -3086,6 +3094,791 @@ targets: [CheckRunStatus.completed, CheckRunConclusion.failure], ); }); + + test('creates separate check runs in GitHub for each build target', () async { + ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionDualCiYaml); + final pubsub = FakePubSub(); + final buildBucketClient = FakeBuildBucketClient( + listBuildersResponse: Future.value( + bbv2.ListBuildersResponse( + builders: [ + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Linux engine_build', + ), + ), + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Mac engine_build', + ), + ), + ], + ), + ), + ); + + final checkRuns = []; + when( + mockGithubChecksUtil.createCheckRun( + any, + any, + any, + any, + output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), + ), + ).thenAnswer((inv) async { + final slug = inv.positionalArguments[1] as RepositorySlug; + final sha = inv.positionalArguments[2] as String; + final name = inv.positionalArguments[3] as String?; + checkRuns.add( + createGithubCheckRun( + id: checkRuns.length + 1, + owner: slug.owner, + repo: slug.name, + sha: sha, + name: name, + ), + ); + return checkRuns.last; + }); + + final testConfig = FakeConfig( + githubService: MockGithubService(), + githubClient: MockGitHub(), + postsubmitSupportedReposValue: {Config.flutterSlug}, + dynamicConfig: DynamicConfig.fromJson({ + 'contentAwareHashing': {'waitOnContentHash': false}, + }), + ); + final luci = FakeLuciBuildService( + config: testConfig, + firestore: firestore, + buildBucketClient: buildBucketClient, + githubChecksUtil: mockGithubChecksUtil, + pubsub: pubsub, + ); + + scheduler = Scheduler( + githubService: testConfig.githubService ?? FakeGithubService(), + cache: cache, + config: testConfig, + githubChecksService: GithubChecksService( + testConfig, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + final mergeGroupEvent = cocoon_checks.MergeGroupEvent.fromJson( + json.decode( + generateMergeGroupEventString( + repository: 'flutter/flutter', + action: 'checks_requested', + message: 'Implement an amazing feature', + ), + ) + as Map, + ); + + await scheduler.handleMergeGroupEvent(mergeGroupEvent: mergeGroupEvent); + + // Separate check runs are created in GitHub for each build target + // in addition to Merge Queue Guard and Dashboard Checks (legacy flow). + expect(checkRuns.map((c) => c.name), [ + Config.kMergeQueueLockName, + Config.kDashboardCheckName, + 'Linux engine_build', + 'Mac engine_build', + ]); + + // Dashboard Checks is immediately completed for merge groups, while Merge Queue Guard stays open. + final dashboardChecks = checkRuns.firstWhere( + (c) => c.name == Config.kDashboardCheckName, + ); + final mergeQueueGuard = checkRuns.firstWhere( + (c) => c.name == Config.kMergeQueueLockName, + ); + verify( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + dashboardChecks, + status: CheckRunStatus.completed, + conclusion: CheckRunConclusion.success, + ), + ).called(1); + verifyNever( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + mergeQueueGuard, + status: anyNamed('status'), + conclusion: anyNamed('conclusion'), + output: anyNamed('output'), + ), + ); + + // Verify scheduled BuildBucket requests carry individual checkRunId and null guardCheckRunId. + expect(pubsub.messages, hasLength(1)); + final batchRequest = bbv2.BatchRequest() + ..mergeFromProto3Json(pubsub.messages.single); + expect(batchRequest.requests, hasLength(2)); + for (final req in batchRequest.requests) { + final userData = PresubmitUserData.fromBytes( + req.scheduleBuild.notify.userData, + ); + expect(userData.checkRunId, isNotNull); + expect(userData.guardCheckRunId, isNull); + } + }); + + test('stores state in ciStaging', () async { + ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionDualCiYaml); + final pubsub = FakePubSub(); + final buildBucketClient = FakeBuildBucketClient( + listBuildersResponse: Future.value( + bbv2.ListBuildersResponse( + builders: [ + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Linux engine_build', + ), + ), + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Mac engine_build', + ), + ), + ], + ), + ), + ); + + final testConfig = FakeConfig( + githubService: MockGithubService(), + githubClient: MockGitHub(), + postsubmitSupportedReposValue: {Config.flutterSlug}, + dynamicConfig: DynamicConfig.fromJson({ + 'contentAwareHashing': {'waitOnContentHash': false}, + }), + ); + final luci = FakeLuciBuildService( + config: testConfig, + firestore: firestore, + buildBucketClient: buildBucketClient, + githubChecksUtil: mockGithubChecksUtil, + pubsub: pubsub, + ); + + scheduler = Scheduler( + githubService: testConfig.githubService ?? FakeGithubService(), + cache: cache, + config: testConfig, + githubChecksService: GithubChecksService( + testConfig, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + final mergeGroupEvent = cocoon_checks.MergeGroupEvent.fromJson( + json.decode( + generateMergeGroupEventString( + repository: 'flutter/flutter', + action: 'checks_requested', + message: 'Implement an amazing feature', + ), + ) + as Map, + ); + + await scheduler.handleMergeGroupEvent(mergeGroupEvent: mergeGroupEvent); + + // Records are stored in ciStaging document, NOT in presubmit_guards or presubmit_jobs. + expect( + firestore, + existsInStorage(CiStaging.metadata, [ + isCiStaging + .hasSha('c9affbbb12aa40cb3afbe94b9ea6b119a256bebf') + .hasStage(CiStage.fusionEngineBuild) + .hasCheckRuns({ + 'Linux engine_build': TaskConclusion.scheduled, + 'Mac engine_build': TaskConclusion.scheduled, + }), + ]), + ); + expect(await firestore.query(PresubmitGuard.collectionId, {}), isEmpty); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); + }); + + test( + 'completes merge queue guard when all engine builds succeed in ciStaging without using presubmit_guards or presubmit_jobs', + () async { + ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionDualCiYaml); + final pubsub = FakePubSub(); + final buildBucketClient = FakeBuildBucketClient( + listBuildersResponse: Future.value( + bbv2.ListBuildersResponse( + builders: [ + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Linux engine_build', + ), + ), + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Mac engine_build', + ), + ), + ], + ), + ), + ); + + final checkRuns = []; + when( + mockGithubChecksUtil.createCheckRun( + any, + any, + any, + any, + output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), + ), + ).thenAnswer((inv) async { + final slug = inv.positionalArguments[1] as RepositorySlug; + final sha = inv.positionalArguments[2] as String; + final name = inv.positionalArguments[3] as String?; + checkRuns.add( + createGithubCheckRun( + id: checkRuns.length + 1, + owner: slug.owner, + repo: slug.name, + sha: sha, + name: name, + ), + ); + return checkRuns.last; + }); + + final testConfig = FakeConfig( + githubService: MockGithubService(), + githubClient: MockGitHub(), + postsubmitSupportedReposValue: {Config.flutterSlug}, + dynamicConfig: DynamicConfig.fromJson({ + 'contentAwareHashing': {'waitOnContentHash': false}, + }), + ); + final luci = FakeLuciBuildService( + config: testConfig, + firestore: firestore, + buildBucketClient: buildBucketClient, + githubChecksUtil: mockGithubChecksUtil, + pubsub: pubsub, + ); + + scheduler = Scheduler( + githubService: testConfig.githubService ?? FakeGithubService(), + cache: cache, + config: testConfig, + githubChecksService: GithubChecksService( + testConfig, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + const sha = 'c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; + const branch = + 'gh-readonly-queue/main/pr-15-c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; + + final mergeGroupEvent = cocoon_checks.MergeGroupEvent.fromJson( + json.decode( + generateMergeGroupEventString( + repository: 'flutter/flutter', + action: 'checks_requested', + message: 'Implement an amazing feature', + ), + ) + as Map, + ); + + await scheduler.handleMergeGroupEvent( + mergeGroupEvent: mergeGroupEvent, + ); + + final mergeQueueGuard = checkRuns.firstWhere( + (c) => c.name == Config.kMergeQueueLockName, + ); + final linuxCheckRun = checkRuns.firstWhere( + (c) => c.name == 'Linux engine_build', + ); + final macCheckRun = checkRuns.firstWhere( + (c) => c.name == 'Mac engine_build', + ); + + // 1. First engine build completes: ciStaging has 1 remaining, Merge Queue Guard stays open. + final linuxCompleted = PresubmitCompletedJob.fromBuild( + generateBbv2Build( + Int64(101), + name: 'Linux engine_build', + status: bbv2.Status.SUCCESS, + tags: [bbv2.StringPair(key: 'current_attempt', value: '1')], + ), + PresubmitUserData( + commit: CommitRef( + slug: Config.flutterSlug, + sha: sha, + branch: branch, + ), + checkRunId: linuxCheckRun.id, + checkSuiteId: linuxCheckRun.checkSuiteId, + ), + ); + + expect( + await scheduler.processCheckRunCompleted(linuxCompleted), + isFalse, + ); + verifyNever( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + mergeQueueGuard, + status: anyNamed('status'), + conclusion: anyNamed('conclusion'), + output: anyNamed('output'), + ), + ); + + // 2. Second engine build completes: ciStaging reaches 0 remaining and Merge Queue Guard is unlocked. + final macCompleted = PresubmitCompletedJob.fromBuild( + generateBbv2Build( + Int64(102), + name: 'Mac engine_build', + status: bbv2.Status.SUCCESS, + tags: [bbv2.StringPair(key: 'current_attempt', value: '1')], + ), + PresubmitUserData( + commit: CommitRef( + slug: Config.flutterSlug, + sha: sha, + branch: branch, + ), + checkRunId: macCheckRun.id, + checkSuiteId: macCheckRun.checkSuiteId, + ), + ); + + expect( + await scheduler.processCheckRunCompleted(macCompleted), + isTrue, + ); + verify( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + argThat( + isA().having( + (c) => c.name, + 'name', + Config.kMergeQueueLockName, + ), + ), + status: CheckRunStatus.completed, + conclusion: CheckRunConclusion.success, + output: anyNamed('output'), + ), + ).called(1); + + expect( + firestore, + existsInStorage(CiStaging.metadata, [ + isCiStaging + .hasSha(sha) + .hasStage(CiStage.fusionEngineBuild) + .hasRemaining(0) + .hasFailed(0) + .hasCheckRuns({ + 'Linux engine_build': TaskConclusion.success, + 'Mac engine_build': TaskConclusion.success, + }), + ]), + ); + expect( + await firestore.query(PresubmitGuard.collectionId, {}), + isEmpty, + ); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); + }, + ); + + test( + 'fails merge queue guard when an engine build fails in ciStaging without using presubmit_guards or presubmit_jobs', + () async { + ciYamlFetcher.setCiYamlFrom(singleCiYaml, engine: fusionDualCiYaml); + final pubsub = FakePubSub(); + final buildBucketClient = FakeBuildBucketClient( + listBuildersResponse: Future.value( + bbv2.ListBuildersResponse( + builders: [ + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Linux engine_build', + ), + ), + bbv2.BuilderItem( + id: bbv2.BuilderID( + bucket: 'prod', + project: 'flutter', + builder: 'Mac engine_build', + ), + ), + ], + ), + ), + ); + + final checkRuns = []; + when( + mockGithubChecksUtil.createCheckRun( + any, + any, + any, + any, + output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), + ), + ).thenAnswer((inv) async { + final slug = inv.positionalArguments[1] as RepositorySlug; + final sha = inv.positionalArguments[2] as String; + final name = inv.positionalArguments[3] as String?; + checkRuns.add( + createGithubCheckRun( + id: checkRuns.length + 1, + owner: slug.owner, + repo: slug.name, + sha: sha, + name: name, + ), + ); + return checkRuns.last; + }); + + final testConfig = FakeConfig( + githubService: MockGithubService(), + githubClient: MockGitHub(), + postsubmitSupportedReposValue: {Config.flutterSlug}, + dynamicConfig: DynamicConfig.fromJson({ + 'contentAwareHashing': {'waitOnContentHash': false}, + }), + ); + final luci = FakeLuciBuildService( + config: testConfig, + firestore: firestore, + buildBucketClient: buildBucketClient, + githubChecksUtil: mockGithubChecksUtil, + pubsub: pubsub, + ); + + scheduler = Scheduler( + githubService: testConfig.githubService ?? FakeGithubService(), + cache: cache, + config: testConfig, + githubChecksService: GithubChecksService( + testConfig, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + const sha = 'c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; + const branch = + 'gh-readonly-queue/main/pr-15-c9affbbb12aa40cb3afbe94b9ea6b119a256bebf'; + + final mergeGroupEvent = cocoon_checks.MergeGroupEvent.fromJson( + json.decode( + generateMergeGroupEventString( + repository: 'flutter/flutter', + action: 'checks_requested', + message: 'Implement an amazing feature', + ), + ) + as Map, + ); + + await scheduler.handleMergeGroupEvent( + mergeGroupEvent: mergeGroupEvent, + ); + + final linuxCheckRun = checkRuns.firstWhere( + (c) => c.name == 'Linux engine_build', + ); + final macCheckRun = checkRuns.firstWhere( + (c) => c.name == 'Mac engine_build', + ); + + // 1. Linux engine_build fails + final linuxFailed = PresubmitCompletedJob.fromBuild( + generateBbv2Build( + Int64(101), + name: 'Linux engine_build', + status: bbv2.Status.FAILURE, + tags: [bbv2.StringPair(key: 'current_attempt', value: '1')], + ), + PresubmitUserData( + commit: CommitRef( + slug: Config.flutterSlug, + sha: sha, + branch: branch, + ), + checkRunId: linuxCheckRun.id, + checkSuiteId: linuxCheckRun.checkSuiteId, + ), + ); + expect( + await scheduler.processCheckRunCompleted(linuxFailed), + isFalse, + ); + + // 2. Mac engine_build succeeds -> stage finishes with 1 failed check, failing Merge Queue Guard + final macSucceeded = PresubmitCompletedJob.fromBuild( + generateBbv2Build( + Int64(102), + name: 'Mac engine_build', + status: bbv2.Status.SUCCESS, + tags: [bbv2.StringPair(key: 'current_attempt', value: '1')], + ), + PresubmitUserData( + commit: CommitRef( + slug: Config.flutterSlug, + sha: sha, + branch: branch, + ), + checkRunId: macCheckRun.id, + checkSuiteId: macCheckRun.checkSuiteId, + ), + ); + expect( + await scheduler.processCheckRunCompleted(macSucceeded), + isTrue, + ); + + verify( + mockGithubChecksUtil.updateCheckRun( + any, + Config.flutterSlug, + argThat( + isA().having( + (c) => c.name, + 'name', + Config.kMergeQueueLockName, + ), + ), + status: CheckRunStatus.completed, + conclusion: CheckRunConclusion.failure, + output: anyNamed('output'), + ), + ).called(1); + + expect( + firestore, + existsInStorage(CiStaging.metadata, [ + isCiStaging + .hasSha(sha) + .hasStage(CiStage.fusionEngineBuild) + .hasRemaining(0) + .hasFailed(1) + .hasCheckRuns({ + 'Linux engine_build': TaskConclusion.failure, + 'Mac engine_build': TaskConclusion.success, + }), + ]), + ); + expect( + await firestore.query(PresubmitGuard.collectionId, {}), + isEmpty, + ); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); + }, + ); + + test( + 'unlocks merge queue guard immediately for non-fusion repos without creating ciStaging or presubmit_guards/presubmit_jobs', + () async { + final luci = MockLuciBuildService(); + final checkRuns = []; + when( + mockGithubChecksUtil.createCheckRun( + any, + any, + any, + any, + output: anyNamed('output'), + conclusion: anyNamed('conclusion'), + detailsUrl: anyNamed('detailsUrl'), + ), + ).thenAnswer((inv) async { + final slug = inv.positionalArguments[1] as RepositorySlug; + final sha = inv.positionalArguments[2] as String; + final name = inv.positionalArguments[3] as String?; + checkRuns.add( + createGithubCheckRun( + id: checkRuns.length + 1, + owner: slug.owner, + repo: slug.name, + sha: sha, + name: name, + ), + ); + return checkRuns.last; + }); + + scheduler = Scheduler( + githubService: config.githubService ?? FakeGithubService(), + cache: cache, + config: FakeConfig( + githubService: MockGithubService(), + githubClient: MockGitHub(), + dynamicConfig: DynamicConfig.fromJson({ + 'contentAwareHashing': {'waitOnContentHash': false}, + }), + ), + githubChecksService: GithubChecksService( + config, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + final mergeGroupEvent = cocoon_checks.MergeGroupEvent.fromJson( + json.decode( + generateMergeGroupEventString( + repository: 'flutter/packages', + action: 'checks_requested', + message: 'Implement an amazing feature', + ), + ) + as Map, + ); + + await scheduler.handleMergeGroupEvent( + mergeGroupEvent: mergeGroupEvent, + ); + + expect(checkRuns.map((c) => c.name), [ + Config.kMergeQueueLockName, + Config.kDashboardCheckName, + ]); + final mergeQueueGuard = checkRuns.firstWhere( + (c) => c.name == Config.kMergeQueueLockName, + ); + verify( + mockGithubChecksUtil.updateCheckRun( + any, + Config.packagesSlug, + mergeQueueGuard, + status: CheckRunStatus.completed, + conclusion: CheckRunConclusion.success, + output: anyNamed('output'), + ), + ).called(1); + verifyNever( + luci.scheduleMergeGroupBuilds( + targets: anyNamed('targets'), + commit: anyNamed('commit'), + ), + ); + expect(firestore, existsInStorage(CiStaging.metadata, isEmpty)); + expect( + await firestore.query(PresubmitGuard.collectionId, {}), + isEmpty, + ); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); + }, + ); + + test( + 'cancels builds when merge group is destroyed without creating ciStaging or presubmit_guards/presubmit_jobs', + () async { + final luci = MockLuciBuildService(); + scheduler = Scheduler( + githubService: config.githubService ?? FakeGithubService(), + cache: cache, + config: config, + githubChecksService: GithubChecksService( + config, + githubChecksUtil: mockGithubChecksUtil, + ), + getFilesChanged: getFilesChanged, + ciYamlFetcher: ciYamlFetcher, + luciBuildService: luci, + contentAwareHash: fakeContentAwareHash, + firestore: firestore, + bigQuery: bigQuery, + ); + + await scheduler.cancelDestroyedMergeGroupTargets( + headSha: 'c9affbbb12aa40cb3afbe94b9ea6b119a256bebf', + ); + + verify( + luci.cancelBuildsBySha( + sha: 'c9affbbb12aa40cb3afbe94b9ea6b119a256bebf', + reason: anyNamed('reason'), + ), + ).called(1); + expect(firestore, existsInStorage(CiStaging.metadata, isEmpty)); + expect( + await firestore.query(PresubmitGuard.collectionId, {}), + isEmpty, + ); + expect(await firestore.query(PresubmitJob.collectionId, {}), isEmpty); + }, + ); }); group('framework-only PR optimization', () { From 3eea823e3906b8107e5147c058c42227611479f8 Mon Sep 17 00:00:00 2001 From: "Dmitry Grand (dmgr)" Date: Mon, 21 Sep 2026 16:04:31 -0700 Subject: [PATCH 4/4] unit test for unlocking merge queue guard --- app_dart/test/service/scheduler_test.dart | 61 ++++++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/app_dart/test/service/scheduler_test.dart b/app_dart/test/service/scheduler_test.dart index 821e9e1a0b..7a71fc4237 100644 --- a/app_dart/test/service/scheduler_test.dart +++ b/app_dart/test/service/scheduler_test.dart @@ -4344,7 +4344,7 @@ targets: slug: pullRequest.base!.repo!.slug(), sha: pullRequest.head!.sha!, stage: CiStage.fusionTests, - tasks: ['Linux test'], + tasks: ['Linux test', 'Mac test'], checkRunGuard: '$checkRunGuard', ); @@ -4360,7 +4360,7 @@ targets: pullRequestNumber: pullRequest.number, ); - final build = generateBbv2Build( + final linuxBuild = generateBbv2Build( Int64(1), name: 'Linux test', status: bbv2.Status.SUCCESS, @@ -4373,16 +4373,57 @@ targets: ], ); - final check = PresubmitCompletedJob.fromBuild(build, userData); - - expect(await scheduler.processCheckRunCompleted(check), isTrue); - - verify( + // First test succeeds: merge queue guard remains locked while 'Mac test' is still pending. + expect( + await scheduler.processCheckRunCompleted( + PresubmitCompletedJob.fromBuild(linuxBuild, userData), + ), + isFalse, + ); + verifyNever( mockGithubChecksUtil.updateCheckRun( any, any, any, status: anyNamed('status'), + conclusion: anyNamed('conclusion'), + output: anyNamed('output'), + ), + ); + + final macBuild = generateBbv2Build( + Int64(2), + name: 'Mac test', + status: bbv2.Status.SUCCESS, + tags: [ + bbv2.StringPair(key: 'current_attempt', value: '1'), + bbv2.StringPair( + key: 'buildset', + value: 'sha/git/${pullRequest.head!.sha!}', + ), + ], + ); + + // Second (final) test succeeds: all tests succeeded, so merge queue guard is unlocked. + expect( + await scheduler.processCheckRunCompleted( + PresubmitCompletedJob.fromBuild(macBuild, userData), + ), + isTrue, + ); + + verify( + mockGithubChecksUtil.updateCheckRun( + any, + pullRequest.base!.repo!.slug(), + argThat( + isA().having( + (c) => c.name, + 'name', + Config.kMergeQueueLockName, + ), + ), + status: CheckRunStatus.completed, conclusion: CheckRunConclusion.success, // Merge queue success output: anyNamed('output'), ), @@ -4395,7 +4436,11 @@ targets: isCiStaging .hasStage(CiStage.fusionTests) .hasRemaining(0) - .hasCheckRuns({'Linux test': TaskConclusion.success}), + .hasFailed(0) + .hasCheckRuns({ + 'Linux test': TaskConclusion.success, + 'Mac test': TaskConclusion.success, + }), ]), ); });