From 32096df48c45e370f618d98cc4a3e26b87532ada Mon Sep 17 00:00:00 2001 From: Hannes Winkler Date: Thu, 18 Jul 2024 09:49:42 +0200 Subject: [PATCH] Less API traffic when looking for flutter-pi updates. Use `git ls-remote` first to list all release tags. If the latest release tag in that output matches the current one, don't check the GitHub API for the latest release. --- lib/src/cache.dart | 92 ++++++- lib/src/cli/flutterpi_command.dart | 6 + test/cache_test.dart | 413 +++++++++++++++++++++++++++++ 3 files changed, 505 insertions(+), 6 deletions(-) diff --git a/lib/src/cache.dart b/lib/src/cache.dart index c1dbe2e..af44bef 100644 --- a/lib/src/cache.dart +++ b/lib/src/cache.dart @@ -1,6 +1,7 @@ // ignore_for_file: implementation_imports import 'dart:async'; +import 'dart:convert'; import 'dart:io' as io; import 'package:file/file.dart'; @@ -16,6 +17,7 @@ import 'package:flutterpi_tool/src/fltool/common.dart'; import 'package:flutterpi_tool/src/fltool/globals.dart' as globals; import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as path; +import 'package:process/process.dart'; FlutterpiCache get flutterpiCache => globals.cache as FlutterpiCache; @@ -96,21 +98,68 @@ class FlutterpiBinaries extends ArtifactSet { required this.fs, required this.httpClient, required this.logger, + required this.processManager, }) : super(DevelopmentArtifact.universal); - final FlutterpiCache cache; + final Cache cache; final FileSystem fs; final http.Client httpClient; final Logger logger; + final ProcessManager processManager; final gh.RepositorySlug repo = gh.RepositorySlug('ardera', 'flutter-pi'); late final gh.GitHub github = gh.GitHub(client: httpClient); - Future _getLatestRelease() async { + Future _getLatestReleaseTag() async { + final allReleases = await processManager.run([ + 'git', + '-c', + 'gc.autoDetach=false', + '-c', + 'core.pager=cat', + '-c', + 'safe.bareRepository=all', + 'ls-remote', + '--tags', + '--sort=-v:refname:lstrip=3', + 'https://github.com/${repo.fullName}.git', + 'refs/tags/release/*', + ]); + + final lines = const LineSplitter().convert(allReleases.stdout as String); + for (final line in lines) { + const prefix = 'refs/tags/release/'; + + String tag; + try { + [_, tag] = line.split('\t'); + } on StateError { + continue; + } + + if (!tag.startsWith(prefix)) { + logger.printTrace( + 'Encountered non-release tag in `git ls-remote` output: $tag', + ); + continue; + } + + // remove the refs/tags/release/ prefix (and add release/ again) + tag = 'release/${tag.substring(prefix.length)}'; + + // we sorted the output in the git ls-remote invocation, so the first + // valid release tag is the latest version. + return tag; + } + + return null; + } + + Future _getLatestGitHubRelease() async { return await github.repositories.getLatestRelease(repo); } Future _getLatestVersion() async { - final release = await _getLatestRelease(); + final release = await _getLatestGitHubRelease(); return switch (release.tagName) { String tagName => tagName, null => @@ -118,6 +167,28 @@ class FlutterpiBinaries extends ArtifactSet { }; } + Future _isLatestVersion(String version) async { + final latestReleaseTag = await _getLatestReleaseTag(); + if (latestReleaseTag != version) { + logger.printTrace( + 'The latest flutter-pi release tag is $latestReleaseTag, but the ' + 'current version is $version, so there might be a new GitHub release. ' + 'Checking with GitHub API...', + ); + + final latestRelease = await _getLatestVersion(); + if (latestRelease != version) { + logger.printTrace( + 'There is a new flutter-pi release available: $latestRelease. ' + 'Current version: $version', + ); + return false; + } + } + + return true; + } + @override Future isUpToDate(FileSystem fileSystem, {bool offline = false}) async { if (!location.existsSync()) { @@ -126,8 +197,8 @@ class FlutterpiBinaries extends ArtifactSet { if (!offline) { try { - final version = await _getLatestVersion(); - if (version != cache.getStampFor(stampName)) { + final version = cache.getStampFor(stampName); + if (version == null || !await _isLatestVersion(version)) { return false; } } on gh.GitHubError catch (e) { @@ -135,6 +206,11 @@ class FlutterpiBinaries extends ArtifactSet { 'Failed to check for flutter-pi updates: ${e.message}', ); return true; + } on io.ProcessException catch (e) { + logger.printWarning( + 'Failed to run git to check for flutter-pi updates: ${e.message}', + ); + return true; } } @@ -175,7 +251,7 @@ class FlutterpiBinaries extends ArtifactSet { } } - final release = await _getLatestRelease(); + final release = await _getLatestGitHubRelease(); final artifacts = [ for (final triple in [ @@ -529,6 +605,7 @@ abstract class FlutterpiCache extends FlutterCache { required this.platform, required this.osUtils, required super.projectFactory, + required ProcessManager processManager, required ShutdownHooks hooks, io.HttpClient? httpClient, }) : httpClient = httpClient ?? io.HttpClient(), @@ -549,6 +626,7 @@ abstract class FlutterpiCache extends FlutterCache { fs: fileSystem, httpClient: pkgHttpHttpClient, logger: logger, + processManager: processManager, ), ); } @@ -760,6 +838,7 @@ class GithubRepoReleasesFlutterpiCache extends FlutterpiCache { required super.osUtils, required super.projectFactory, required super.hooks, + required super.processManager, super.httpClient, gh.RepositorySlug? repo, gh.Authentication? auth, @@ -794,6 +873,7 @@ class GithubWorkflowRunFlutterpiCache extends FlutterpiCache { required super.osUtils, required super.projectFactory, required super.hooks, + required super.processManager, gh.RepositorySlug? repo, gh.Authentication? auth, required String runId, diff --git a/lib/src/cli/flutterpi_command.dart b/lib/src/cli/flutterpi_command.dart index a3dd405..d536525 100644 --- a/lib/src/cli/flutterpi_command.dart +++ b/lib/src/cli/flutterpi_command.dart @@ -14,6 +14,7 @@ import 'package:flutterpi_tool/src/more_os_utils.dart'; import 'package:flutterpi_tool/src/devices/flutterpi_ssh/ssh_utils.dart'; import 'package:flutterpi_tool/src/shutdown_hooks.dart'; import 'package:github/github.dart' as gh; +import 'package:process/process.dart'; mixin FlutterpiCommandMixin on FlutterCommand { FlutterpiCache createCustomCache({ @@ -23,6 +24,7 @@ mixin FlutterpiCommandMixin on FlutterCommand { required Platform platform, required MoreOperatingSystemUtils os, required FlutterProjectFactory projectFactory, + required ProcessManager processManager, }) { final repo = stringArg('github-artifacts-repo'); final runId = stringArg('github-artifacts-runid'); @@ -37,6 +39,7 @@ mixin FlutterpiCommandMixin on FlutterCommand { platform: platform, osUtils: os, projectFactory: projectFactory, + processManager: processManager, repo: repo != null ? gh.RepositorySlug.full(repo) : null, runId: runId, auth: token != null ? gh.Authentication.bearerToken(token) : null, @@ -50,6 +53,7 @@ mixin FlutterpiCommandMixin on FlutterCommand { platform: platform, osUtils: os, projectFactory: projectFactory, + processManager: processManager, repo: repo != null ? gh.RepositorySlug.full(repo) : null, auth: token != null ? gh.Authentication.bearerToken(token) : null, ); @@ -202,6 +206,7 @@ mixin FlutterpiCommandMixin on FlutterCommand { platform: globals.platform, os: globals.os as MoreOperatingSystemUtils, projectFactory: globals.projectFactory, + processManager: globals.processManager, ), ); } @@ -380,6 +385,7 @@ mixin FlutterpiCommandMixin on FlutterCommand { platform: globals.platform, osUtils: globals.os as MoreOperatingSystemUtils, projectFactory: globals.projectFactory, + processManager: globals.processManager, ), Cache: () => globals.flutterpiCache, OperatingSystemUtils: () => MoreOperatingSystemUtils( diff --git a/test/cache_test.dart b/test/cache_test.dart index dbc9c28..95d349c 100644 --- a/test/cache_test.dart +++ b/test/cache_test.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:file/memory.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart' as http; import 'package:test/test.dart'; import 'package:flutterpi_tool/src/more_os_utils.dart'; @@ -10,6 +12,264 @@ import 'package:flutterpi_tool/src/fltool/common.dart'; import 'src/fake_process_manager.dart'; +const githubApiResponse = ''' +{ + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/159500529", + "assets_url": "https://api.github.com/repos/ardera/flutter-pi/releases/159500529/assets", + "upload_url": "https://uploads.github.com/repos/ardera/flutter-pi/releases/159500529/assets{?name,label}", + "html_url": "https://github.com/ardera/flutter-pi/releases/tag/release/1.0.0", + "id": 159500529, + "author": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "node_id": "RE_kwDOC1j4Fc4Jgcjx", + "tag_name": "release/1.0.0", + "target_commitish": "master", + "name": "release/1.0.0", + "draft": false, + "prerelease": false, + "created_at": "2024-06-08T08:44:57Z", + "published_at": "2024-06-08T08:49:27Z", + "assets": [ + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629658", + "id": 172629658, + "node_id": "RA_kwDOC1j4Fc4KSh6a", + "name": "flutterpi-aarch64-linux-gnu-debug.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 314756, + "download_count": 114, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-aarch64-linux-gnu-debug.tar.xz" + }, + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629656", + "id": 172629656, + "node_id": "RA_kwDOC1j4Fc4KSh6Y", + "name": "flutterpi-aarch64-linux-gnu-release.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 105720, + "download_count": 114, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-aarch64-linux-gnu-release.tar.xz" + }, + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629659", + "id": 172629659, + "node_id": "RA_kwDOC1j4Fc4KSh6b", + "name": "flutterpi-arm-linux-gnueabihf-debug.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 296388, + "download_count": 110, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-arm-linux-gnueabihf-debug.tar.xz" + }, + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629655", + "id": 172629655, + "node_id": "RA_kwDOC1j4Fc4KSh6X", + "name": "flutterpi-arm-linux-gnueabihf-release.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 104012, + "download_count": 118, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-arm-linux-gnueabihf-release.tar.xz" + }, + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629657", + "id": 172629657, + "node_id": "RA_kwDOC1j4Fc4KSh6Z", + "name": "flutterpi-x86_64-linux-gnu-debug.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 318924, + "download_count": 109, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-x86_64-linux-gnu-debug.tar.xz" + }, + { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/assets/172629660", + "id": 172629660, + "node_id": "RA_kwDOC1j4Fc4KSh6c", + "name": "flutterpi-x86_64-linux-gnu-release.tar.xz", + "label": "", + "uploader": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "site_admin": false + }, + "content_type": "application/x-xz", + "state": "uploaded", + "size": 119416, + "download_count": 108, + "created_at": "2024-06-08T08:49:27Z", + "updated_at": "2024-06-08T08:49:27Z", + "browser_download_url": "https://github.com/ardera/flutter-pi/releases/download/release/1.0.0/flutterpi-x86_64-linux-gnu-release.tar.xz" + } + ], + "tarball_url": "https://api.github.com/repos/ardera/flutter-pi/tarball/release/1.0.0", + "zipball_url": "https://api.github.com/repos/ardera/flutter-pi/zipball/release/1.0.0", + "body": "Initial Github release with prebuilt artifacts.", + "reactions": { + "url": "https://api.github.com/repos/ardera/flutter-pi/releases/159500529/reactions", + "total_count": 6, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 6, + "eyes": 0 + } +}'''; + Future> getArtifactKeysFor({ FlutterpiHostPlatform? host, Set targets = const {}, @@ -37,6 +297,7 @@ Future> getArtifactKeysFor({ logger: logger, ), hooks: hooks, + processManager: FakeProcessManager.any(), ); final result = cache @@ -248,4 +509,156 @@ void main() { ]), ); }); + + group('flutter-pi update checking', () { + late BufferLogger logger; + late MemoryFileSystem fs; + late FakePlatform platform; + late FakeProcessManager cacheProcessManager, binariesProcessManager; + late bool gitWasCalled; + late bool apiWasCalled; + late http.MockClient httpClient; + late FlutterpiBinaries binaries; + late Cache cache; + late List artifacts; + + setup({ + String? gitOutput, + String? apiOutput, + String? stamp, + bool createArtifactLocation = false, + }) { + logger = BufferLogger.test(); + fs = MemoryFileSystem.test(); + platform = FakePlatform(); + + cacheProcessManager = FakeProcessManager.list([ + FakeCommand(command: ['chmod', '755', 'cache/bin/cache']), + FakeCommand(command: ['chmod', '755', 'cache/bin/cache/artifacts']), + ]); + + binariesProcessManager = FakeProcessManager.list([ + FakeCommand( + command: [ + 'git', + '-c', + 'gc.autoDetach=false', + '-c', + 'core.pager=cat', + '-c', + 'safe.bareRepository=all', + 'ls-remote', + '--tags', + '--sort=-v:refname:lstrip=3', + 'https://github.com/ardera/flutter-pi.git', + 'refs/tags/release/*', + ], + onRun: () => gitWasCalled = true, + stdout: gitOutput ?? 'abcdef\trefs/tags/release/1.0.0', + ), + ]); + + gitWasCalled = false; + + apiWasCalled = false; + httpClient = http.MockClient((req) async { + expect( + req.url.toString(), + 'https://api.github.com/repos/ardera/flutter-pi/releases/latest', + ); + + apiWasCalled = true; + + return http.Response(githubApiResponse, 200); + }); + + artifacts = []; + + cache = Cache.test( + logger: logger, + fileSystem: fs, + platform: platform, + processManager: cacheProcessManager, + artifacts: artifacts, + ); + + binaries = FlutterpiBinaries( + cache: cache, + fs: fs, + httpClient: httpClient, + logger: logger, + processManager: binariesProcessManager, + ); + + artifacts.add(binaries); + + cache.getCacheDir('').createSync(recursive: true); + + if (stamp != null) cache.setStampFor(binaries.stampName, stamp); + if (createArtifactLocation) binaries.location.createSync(recursive: true); + } + + test('no stamp & artifact location present', () async { + setup(); + + await expectLater(binaries.isUpToDate(fs), completion(isFalse)); + expect(gitWasCalled, isFalse); + expect(apiWasCalled, isFalse); + }); + + test( + 'stamp present, artifact location not present', + () async { + setup(stamp: 'release/1.0.0'); + + expect(cache.getStampFor(binaries.stampName), 'release/1.0.0'); + + await expectLater(binaries.isUpToDate(fs), completion(isFalse)); + expect(gitWasCalled, isFalse); + expect(apiWasCalled, isFalse); + }, + ); + + test('stamp and artifact location present', () async { + setup(stamp: 'release/1.0.0', createArtifactLocation: true); + + expect(cache.getStampFor(binaries.stampName), 'release/1.0.0'); + + await expectLater(binaries.isUpToDate(fs), completion(isTrue)); + expect(gitWasCalled, isTrue); + expect(apiWasCalled, isFalse); + }); + + test('stamp & artifact location present, git has new version, API does not', + () async { + setup( + gitOutput: 'abcdef\trefs/tags/release/1.1.0\n' + 'abcdeg\trefs/tags/release/1.0.0', + stamp: 'release/1.0.0', + createArtifactLocation: true, + ); + + expect(cache.getStampFor(binaries.stampName), 'release/1.0.0'); + + await expectLater(binaries.isUpToDate(fs), completion(isTrue)); + expect(gitWasCalled, isTrue); + expect(apiWasCalled, isTrue); + }); + + test('stamp & artifact location present, git and API have new version', + () async { + setup( + gitOutput: 'abcdef\trefs/tags/release/1.0.0\n' + 'abcdeg\trefs/tags/release/0.9.0', + stamp: 'release/0.9.0', + createArtifactLocation: true, + ); + + expect(cache.getStampFor(binaries.stampName), 'release/0.9.0'); + + await expectLater(binaries.isUpToDate(fs), completion(isFalse)); + expect(gitWasCalled, isTrue); + expect(apiWasCalled, isTrue); + }); + }); }