prepare_package_test.dart 38.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:convert';
import 'dart:io' hide Platform;
7
import 'dart:typed_data';
8 9

import 'package:path/path.dart' as path;
10
import 'package:platform/platform.dart' show FakePlatform, Platform;
11

12
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
13
import '../prepare_package.dart';
14
import 'common.dart';
15 16

void main() {
17
  const String testRef = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
18 19 20
  test('Throws on missing executable', () async {
    // Uses a *real* process manager, since we want to know what happens if
    // it can't find an executable.
21
    final ProcessRunner processRunner = ProcessRunner(subprocessOutput: false);
22 23 24 25
    expect(
        expectAsync1((List<String> commandLine) async {
          return processRunner.runProcess(commandLine);
        })(<String>['this_executable_better_not_exist_2857632534321']),
26
        throwsA(isA<PreparePackageException>()));
27 28 29 30 31 32

    await expectLater(
      () => processRunner.runProcess(<String>['this_executable_better_not_exist_2857632534321']),
      throwsA(isA<PreparePackageException>().having(
        (PreparePackageException error) => error.message,
        'message',
33
        contains('ProcessException: Failed to find "this_executable_better_not_exist_2857632534321" in the search path'),
34 35
      )),
    );
36
  });
37
  for (final String platformName in <String>[Platform.macOS, Platform.linux, Platform.windows]) {
38
    final FakePlatform platform = FakePlatform(
39
      operatingSystem: platformName,
40 41 42
      environment: <String, String>{
        'DEPOT_TOOLS': path.join('D:', 'depot_tools'),
      },
43
    );
44 45
    group('ProcessRunner for $platform', () {
      test('Returns stdout', () async {
46 47 48 49 50
        final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['echo', 'test',],
            stdout: 'output',
            stderr: 'error',
51
          ),
52
        ]);
53
        final ProcessRunner processRunner = ProcessRunner(
54 55
            subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
        final String output = await processRunner.runProcess(<String>['echo', 'test']);
56 57 58
        expect(output, equals('output'));
      });
      test('Throws on process failure', () async {
59 60 61 62 63 64
        final FakeProcessManager fakeProcessManager = FakeProcessManager.list(<FakeCommand>[
          const FakeCommand(
            command: <String>['echo', 'test',],
            stdout: 'output',
            stderr: 'error',
            exitCode: -1,
65
          ),
66
        ]);
67
        final ProcessRunner processRunner = ProcessRunner(
68
            subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
69 70 71 72
        expect(
            expectAsync1((List<String> commandLine) async {
              return processRunner.runProcess(commandLine);
            })(<String>['echo', 'test']),
73
            throwsA(isA<PreparePackageException>()));
74 75
      });
    });
76
    group('ArchiveCreator for $platformName', () {
77 78
      late ArchiveCreator creator;
      late Directory tempDir;
79
      Directory flutterDir;
80
      Directory cacheDir;
81
      late FakeProcessManager processManager;
82 83
      final List<List<String>> args = <List<String>>[];
      final List<Map<Symbol, dynamic>> namedArgs = <Map<Symbol, dynamic>>[];
84
      late String flutter;
85
      late String dart;
86

87
      Future<Uint8List> fakeHttpReader(Uri url, {Map<String, String>? headers}) {
88
        return Future<Uint8List>.value(Uint8List(0));
89 90
      }

91
      setUp(() async {
92
        processManager = FakeProcessManager.list(<FakeCommand>[]);
93 94
        args.clear();
        namedArgs.clear();
95
        tempDir = Directory.systemTemp.createTempSync('flutter_prepage_package_test.');
96
        flutterDir = Directory(path.join(tempDir.path, 'flutter'));
97
        flutterDir.createSync(recursive: true);
98 99
        cacheDir = Directory(path.join(flutterDir.path, 'bin', 'cache'));
        cacheDir.createSync(recursive: true);
100
        creator = ArchiveCreator(
101 102
          tempDir,
          tempDir,
103
          testRef,
104
          Branch.beta,
105 106 107
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
108
          httpReader: fakeHttpReader,
109
        );
110 111 112 113
        flutter = path.join(creator.flutterRoot.absolute.path,
          'bin', 'flutter');
        dart = path.join(creator.flutterRoot.absolute.path,
          'bin', 'cache', 'dart-sdk', 'bin', 'dart');
114 115 116
      });

      tearDown(() async {
117
        tryToDelete(tempDir);
118 119 120
      });

      test('sets PUB_CACHE properly', () async {
121
        final String createBase = path.join(tempDir.absolute.path, 'create_');
122
        final String archiveName = path.join(tempDir.absolute.path,
123
            'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
124

125
        processManager.addCommands(convertResults(<String, List<ProcessResult>?>{
126
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
127
          'git reset --hard $testRef': null,
128
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
129
          'git gc --prune=now --aggressive': null,
130
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
131 132 133 134
          '$flutter --version --machine': <ProcessResult>[
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
          ],
135
          '$dart --version': <ProcessResult>[
136
            ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
137
          ],
138
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
139 140 141 142 143 144 145
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
          '$flutter create --template=package ${createBase}package': null,
          '$flutter create --template=plugin ${createBase}plugin': null,
146
          '$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
147 148
          'git clean -f -x -- **/.packages': null,
          'git clean -f -x -- **/.dart_tool/': null,
149
          if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
150
          if (platform.isWindows) 'attrib -h .git': null,
151
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
152
          else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
153
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
154
        }));
155 156 157 158 159
        await creator.initializeRepo();
        await creator.createArchive();
      });

      test('calls the right commands for archive output', () async {
160
        final String createBase = path.join(tempDir.absolute.path, 'create_');
161
        final String archiveName = path.join(tempDir.absolute.path,
162
            'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
163
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
164
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
165
          'git reset --hard $testRef': null,
166
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
167
          'git gc --prune=now --aggressive': null,
168
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
169 170 171 172
          '$flutter --version --machine': <ProcessResult>[
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
          ],
173
          '$dart --version': <ProcessResult>[
174
            ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
175 176 177 178 179 180 181 182 183
          ],
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
          '$flutter create --template=package ${createBase}package': null,
          '$flutter create --template=plugin ${createBase}plugin': null,
184
          '$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
185 186 187 188 189 190 191 192 193 194 195 196 197
          'git clean -f -x -- **/.packages': null,
          'git clean -f -x -- **/.dart_tool/': null,
          if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
          if (platform.isWindows) 'attrib -h .git': null,
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
          else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
        processManager.addCommands(convertResults(calls));
        creator = ArchiveCreator(
          tempDir,
          tempDir,
          testRef,
198
          Branch.beta,
199 200 201 202 203 204 205 206 207 208 209 210
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
          httpReader: fakeHttpReader,
        );
        await creator.initializeRepo();
        await creator.createArchive();
      });

      test('adds the arch name to the archive for non-x64', () async {
        final String createBase = path.join(tempDir.absolute.path, 'create_');
        final String archiveName = path.join(tempDir.absolute.path,
211
            'flutter_${platformName}_arm64_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
212
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
213
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
214 215
          'git reset --hard $testRef': null,
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
216
          'git gc --prune=now --aggressive': null,
217 218 219 220 221 222
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
          '$flutter --version --machine': <ProcessResult>[
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
          ],
          '$dart --version': <ProcessResult>[
223
            ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_arm64"', ''),
224
          ],
225
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
226 227 228 229 230
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
231
          '$flutter create --template=package ${createBase}package': null,
232
          '$flutter create --template=plugin ${createBase}plugin': null,
233
          '$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
234 235
          'git clean -f -x -- **/.packages': null,
          'git clean -f -x -- **/.dart_tool/': null,
236
          if (platform.isMacOS) 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': null,
237
          if (platform.isWindows) 'attrib -h .git': null,
238
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
239
          else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
240 241
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
242
        processManager.addCommands(convertResults(calls));
243
        creator = ArchiveCreator(
244 245
          tempDir,
          tempDir,
246
          testRef,
247
          Branch.beta,
248 249 250
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
251
          httpReader: fakeHttpReader,
252 253 254 255 256 257 258
        );
        await creator.initializeRepo();
        await creator.createArchive();
      });

      test('throws when a command errors out', () async {
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
259
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter':
260 261
              <ProcessResult>[ProcessResult(0, 0, 'output1', '')],
          'git reset --hard $testRef': <ProcessResult>[ProcessResult(0, -1, 'output2', '')],
262
        };
263
        processManager.addCommands(convertResults(calls));
264
        expect(expectAsync0(creator.initializeRepo), throwsA(isA<PreparePackageException>()));
265 266 267 268
      });

      test('non-strict mode calls the right commands', () async {
        final String createBase = path.join(tempDir.absolute.path, 'create_');
269
        final String archiveName = path.join(tempDir.absolute.path,
270
            'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
271
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
272
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
273 274
          'git reset --hard $testRef': null,
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
275
          'git gc --prune=now --aggressive': null,
276
          'git describe --tags --abbrev=0 $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
277 278 279 280
          '$flutter --version --machine': <ProcessResult>[
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
          ],
281
          '$dart --version': <ProcessResult>[
282
            ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
283
          ],
284
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
285 286 287 288 289 290 291
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
          '$flutter create --template=package ${createBase}package': null,
          '$flutter create --template=plugin ${createBase}plugin': null,
292
          '$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
293 294
          'git clean -f -x -- **/.packages': null,
          'git clean -f -x -- **/.dart_tool/': null,
295
          if (platform.isWindows) 'attrib -h .git': null,
296
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
297
          else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
298 299
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
300
        processManager.addCommands(convertResults(calls));
301 302 303 304
        creator = ArchiveCreator(
          tempDir,
          tempDir,
          testRef,
305
          Branch.beta,
306 307 308 309 310 311 312 313
          strict: false,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
          httpReader: fakeHttpReader,
        );
        await creator.initializeRepo();
        await creator.createArchive();
314
      });
315 316 317 318

      test('fails if binary is not codesigned', () async {
        final String createBase = path.join(tempDir.absolute.path, 'create_');
        final String archiveName = path.join(tempDir.absolute.path,
319
            'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}');
320 321
        final ProcessResult codesignFailure = ProcessResult(1, 1, '', 'code object is not signed at all');
        final String binPath = path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
322
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
323
          'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null,
324 325
          'git reset --hard $testRef': null,
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
326
          'git gc --prune=now --aggressive': null,
327
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
328 329 330 331
          '$flutter --version --machine': <ProcessResult>[
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
            ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''),
          ],
332
          '$dart --version': <ProcessResult>[
333
            ProcessResult(0, 0, 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', ''),
334
          ],
335 336 337 338 339 340 341 342
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
          '$flutter create --template=package ${createBase}package': null,
          '$flutter create --template=plugin ${createBase}plugin': null,
343
          '$flutter pub cache list': <ProcessResult>[ProcessResult(0,0,'{"packages":{}}','')],
344 345 346 347 348 349 350 351 352 353 354 355 356
          'git clean -f -x -- **/.packages': null,
          'git clean -f -x -- **/.dart_tool/': null,
          if (platform.isMacOS) 'codesign -vvvv --check-notarization $binPath': <ProcessResult>[codesignFailure],
          if (platform.isWindows) 'attrib -h .git': null,
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
          else if (platform.isMacOS) 'zip -r -9 --symlinks $archiveName flutter': null
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
        processManager.addCommands(convertResults(calls));
        creator = ArchiveCreator(
          tempDir,
          tempDir,
          testRef,
357
          Branch.beta,
358 359 360 361 362 363 364
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
          httpReader: fakeHttpReader,
        );
        await creator.initializeRepo();

365 366 367 368 369 370 371 372
        await expectLater(
          () => creator.createArchive(),
          throwsA(isA<PreparePackageException>().having(
            (PreparePackageException exception) => exception.message,
            'message',
            contains('The binary $binPath was not codesigned!'),
          )),
        );
373
      }, skip: !platform.isMacOS); // [intended] codesign is only available on macOS
374 375
    });

376
    group('ArchivePublisher for $platformName', () {
377 378
      late FakeProcessManager processManager;
      late Directory tempDir;
379
      final String gsutilCall = platform.isWindows
380
          ? 'python3 ${path.join("D:", "depot_tools", "gsutil.py")}'
381 382 383 384
          : 'gsutil.py';
      final String releasesName = 'releases_$platformName.json';
      final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
      final String archiveMime = platform.isLinux ? 'application/x-gtar' : 'application/zip';
385
      final String gsArchivePath = 'gs://flutter_infra_release/releases/stable/$platformName/$archiveName';
386 387

      setUp(() async {
388
        processManager = FakeProcessManager.list(<FakeCommand>[]);
389
        tempDir = Directory.systemTemp.createTempSync('flutter_prepage_package_test.');
390 391 392
      });

      tearDown(() async {
393
        tryToDelete(tempDir);
394 395 396
      });

      test('calls the right processes', () async {
397
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
398
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
399
        final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
400 401
        final String releasesJson = '''
{
402
  "base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
403 404
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
405
    "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
406 407 408 409
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
410
      "channel": "beta",
411 412
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
413
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip",
414 415
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8",
      "dart_sdk_arch": "x64"
416
    },
417 418 419 420 421
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
422
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip",
423 424
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b",
      "dart_sdk_arch": "x64"
425 426 427
    },
    {
      "hash": "$testRef",
428
      "channel": "stable",
429 430
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
431
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip",
432 433
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd",
      "dart_sdk_arch": "x64"
434
    }
435
  ]
436
}
437
''';
438
        File(jsonPath).writeAsStringSync(releasesJson);
439
        File(archivePath).writeAsStringSync('archive contents');
440
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
441
          // This process fails because the file does NOT already exist
442
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
443
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
444 445 446
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
447
          '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
448
        };
449
        processManager.addCommands(convertResults(calls));
450
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
451
        outputFile.createSync();
452
        assert(tempDir.existsSync());
453
        final ArchivePublisher publisher = ArchivePublisher(
454 455
          tempDir,
          testRef,
456
          Branch.stable,
457 458 459 460 461
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'x64',
          },
462
          outputFile,
463
          false,
464 465 466 467 468
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        assert(tempDir.existsSync());
469
        await publisher.generateLocalMetadata();
470
        await publisher.publishArchive();
471

472
        final File releaseFile = File(jsonPath);
473 474 475
        expect(releaseFile.existsSync(), isTrue);
        final String contents = releaseFile.readAsStringSync();
        // Make sure new data is added.
476
        expect(contents, contains('"hash": "$testRef"'));
477 478
        expect(contents, contains('"channel": "stable"'));
        expect(contents, contains('"archive": "stable/$platformName/$archiveName"'));
479
        expect(contents, contains('"sha256": "f69f4865f861193a91d1c5544a894167a7137b788d10bac8edbf5d095f45cb4d"'));
480
        // Make sure existing entries are preserved.
481 482 483
        expect(contents, contains('"hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"'));
        expect(contents, contains('"hash": "b9bd51cc36b706215915711e580851901faebb40"'));
        expect(contents, contains('"channel": "beta"'));
484
        expect(contents, contains('"channel": "beta"'));
485 486
        // Make sure old matching entries are removed.
        expect(contents, isNot(contains('v0.0.0')));
487 488
        final Map<String, dynamic> jsonData = json.decode(contents) as Map<String, dynamic>;
        final List<dynamic> releases = jsonData['releases'] as List<dynamic>;
489 490 491 492
        expect(releases.length, equals(3));
        // Make sure the new entry is first (and hopefully it takes less than a
        // minute to go from publishArchive above to this line!).
        expect(
493
          DateTime.now().difference(DateTime.parse((releases[0] as Map<String, dynamic>)['release_date'] as String)),
494 495
          lessThan(const Duration(minutes: 1)),
        );
496
        const JsonEncoder encoder = JsonEncoder.withIndent('  ');
497 498
        expect(contents, equals(encoder.convert(jsonData)));
      });
499

500 501 502 503 504 505 506 507 508
      test('contains Dart SDK version info', () async {
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
        final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
        final String releasesJson = '''
{
  "base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
509
    "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
510 511 512 513
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
514
      "channel": "beta",
515 516
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
517
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip",
518 519 520 521 522 523 524
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8"
    },
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
525
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip",
526 527 528 529 530 531 532
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b"
    },
    {
      "hash": "$testRef",
      "channel": "stable",
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
533
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip",
534 535 536 537 538 539 540 541 542
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd"
    }
  ]
}
''';
        File(jsonPath).writeAsStringSync(releasesJson);
        File(archivePath).writeAsStringSync('archive contents');
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
          // This process fails because the file does NOT already exist
543
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
          '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
        };
        processManager.addCommands(convertResults(calls));
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
        outputFile.createSync();
        assert(tempDir.existsSync());
        final ArchivePublisher publisher = ArchivePublisher(
          tempDir,
          testRef,
          Branch.stable,
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'x64',
          },
          outputFile,
          false,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        assert(tempDir.existsSync());
570
        await publisher.generateLocalMetadata();
571 572 573 574 575 576 577 578 579
        await publisher.publishArchive();

        final File releaseFile = File(jsonPath);
        expect(releaseFile.existsSync(), isTrue);
        final String contents = releaseFile.readAsStringSync();
        expect(contents, contains('"dart_sdk_version": "3.2.1"'));
        expect(contents, contains('"dart_sdk_arch": "x64"'));
      });

580 581 582 583 584 585 586 587 588
      test('Supports multiple architectures', () async {
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
        final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
        final String releasesJson = '''
{
  "base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
589
    "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
590 591 592 593 594 595 596
  },
  "releases": [
    {
      "hash": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
      "channel": "stable",
      "version": "v1.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
597
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip",
598 599 600 601 602 603 604 605 606 607
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8",
      "dart_sdk_arch": "x64"
    }
  ]
}
''';
        File(jsonPath).writeAsStringSync(releasesJson);
        File(archivePath).writeAsStringSync('archive contents');
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
          // This process fails because the file does NOT already exist
608
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
          '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
        };
        processManager.addCommands(convertResults(calls));
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
        outputFile.createSync();
        assert(tempDir.existsSync());
        final ArchivePublisher publisher = ArchivePublisher(
          tempDir,
          testRef,
          Branch.stable,
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'arm64',
          },
          outputFile,
          false,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        assert(tempDir.existsSync());
635
        await publisher.generateLocalMetadata();
636 637 638 639 640 641 642 643 644 645
        await publisher.publishArchive();

        final File releaseFile = File(jsonPath);
        expect(releaseFile.existsSync(), isTrue);
        final String contents = releaseFile.readAsStringSync();
        final Map<String, dynamic> releases = jsonDecode(contents) as Map<String, dynamic>;
        expect((releases['releases'] as List<dynamic>).length, equals(2));
      });


646 647 648
      test('updates base_url from old bucket to new bucket', () async {
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
649
        final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
650 651
        final String releasesJson = '''
{
652
  "base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
653 654
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
655
    "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
656 657 658 659
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
660
      "channel": "beta",
661 662
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
663
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip",
664 665 666 667 668 669 670
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8"
    },
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
671
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip",
672 673 674 675 676 677 678
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b"
    },
    {
      "hash": "$testRef",
      "channel": "stable",
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
679
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip",
680 681 682 683 684 685 686
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd"
    }
  ]
}
''';
        File(jsonPath).writeAsStringSync(releasesJson);
        File(archivePath).writeAsStringSync('archive contents');
687
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
688
          // This process fails because the file does NOT already exist
689
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
690 691 692 693 694 695 696 697 698 699 700 701 702 703
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
          '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
        };
        processManager.addCommands(convertResults(calls));
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
        outputFile.createSync();
        assert(tempDir.existsSync());
        final ArchivePublisher publisher = ArchivePublisher(
          tempDir,
          testRef,
          Branch.stable,
704 705 706 707 708
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'x64',
          },
709 710 711 712 713 714 715
          outputFile,
          false,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        assert(tempDir.existsSync());
716
        await publisher.generateLocalMetadata();
717 718 719 720 721 722 723 724 725
        await publisher.publishArchive();

        final File releaseFile = File(jsonPath);
        expect(releaseFile.existsSync(), isTrue);
        final String contents = releaseFile.readAsStringSync();
        final Map<String, dynamic> jsonData = json.decode(contents) as Map<String, dynamic>;
        expect(jsonData['base_url'], 'https://storage.googleapis.com/flutter_infra_release/releases');
      });

726 727 728 729 730 731 732
      test('publishArchive throws if forceUpload is false and artifact already exists on cloud storage', () async {
        final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
        final ArchivePublisher publisher = ArchivePublisher(
          tempDir,
          testRef,
          Branch.stable,
733 734 735 736 737
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'x64',
          },
738
          outputFile,
739
          false,
740 741 742 743 744 745 746 747
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
          // This process returns 0 because file already exists
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 0, '', '')],
        };
748
        processManager.addCommands(convertResults(calls));
749
        expect(() async => publisher.publishArchive(), throwsException);
750 751 752 753 754 755 756 757 758
      });

      test('publishArchive does not throw if forceUpload is true and artifact already exists on cloud storage', () async {
        final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip';
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
        final ArchivePublisher publisher = ArchivePublisher(
          tempDir,
          testRef,
          Branch.stable,
759 760 761 762 763
          <String, String>{
            'frameworkVersionFromGit': 'v1.2.3',
            'dartSdkVersion': '3.2.1',
            'dartTargetArch': 'x64',
          },
764
          outputFile,
765
          false,
766 767 768 769 770 771
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
772
        final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName';
773 774
        final String releasesJson = '''
{
775
  "base_url": "https://storage.googleapis.com/flutter_infra_release/releases",
776 777
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
778
    "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
779 780 781 782
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
783
      "channel": "beta",
784 785
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
786
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip",
787 788 789 790 791 792 793
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8"
    },
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
794
      "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip",
795 796 797 798 799 800 801
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b"
    },
    {
      "hash": "$testRef",
      "channel": "stable",
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
802
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip",
803 804 805 806 807 808 809
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd"
    }
  ]
}
''';
        File(jsonPath).writeAsStringSync(releasesJson);
        File(archivePath).writeAsStringSync('archive contents');
810
        final Map<String, List<ProcessResult>?> calls = <String, List<ProcessResult>?>{
811
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
812 813 814
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
815
          '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': null,
816
        };
817
        processManager.addCommands(convertResults(calls));
818
        assert(tempDir.existsSync());
819
        await publisher.generateLocalMetadata();
820 821
        await publisher.publishArchive(true);
      });
822 823
    });
  }
824
}
825

826
List<FakeCommand> convertResults(Map<String, List<ProcessResult>?> results) {
827 828
  final List<FakeCommand> commands = <FakeCommand>[];
  for (final String key in results.keys) {
829
    final List<ProcessResult>? candidates = results[key];
830 831 832 833 834 835 836 837 838 839
    final List<String> args = key.split(' ');
    if (candidates == null) {
      commands.add(FakeCommand(
        command: args,
      ));
    } else {
      for (final ProcessResult result in candidates) {
        commands.add(FakeCommand(
          command: args,
          exitCode: result.exitCode,
840 841
          stderr: result.stderr.toString(),
          stdout: result.stdout.toString(),
842 843 844 845 846 847
        ));
      }
    }
  }
  return commands;
}