prepare_package_test.dart 19.6 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 10

import 'package:mockito/mockito.dart';
import 'package:path/path.dart' as path;
11
import 'package:platform/platform.dart' show FakePlatform;
12 13

import '../prepare_package.dart';
14
import 'common.dart';
15
import 'fake_process_manager.dart';
16 17

void main() {
18
  const String testRef = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
19 20 21
  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.
22
    final ProcessRunner processRunner = ProcessRunner(subprocessOutput: false);
23 24 25 26
    expect(
        expectAsync1((List<String> commandLine) async {
          return processRunner.runProcess(commandLine);
        })(<String>['this_executable_better_not_exist_2857632534321']),
27
        throwsA(isA<PreparePackageException>()));
28 29
    try {
      await processRunner.runProcess(<String>['this_executable_better_not_exist_2857632534321']);
30
    } on PreparePackageException catch (e) {
31 32 33 34 35 36
      expect(
        e.message,
        contains('Invalid argument(s): Cannot find executable for this_executable_better_not_exist_2857632534321.'),
      );
    }
  });
37
  for (final String platformName in <String>['macos', 'linux', '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
        final FakeProcessManager fakeProcessManager = FakeProcessManager();
47
        fakeProcessManager.fakeResults = <String, List<ProcessResult>>{
48
          'echo test': <ProcessResult>[ProcessResult(0, 0, 'output', 'error')],
49
        };
50
        final ProcessRunner processRunner = ProcessRunner(
51 52
            subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
        final String output = await processRunner.runProcess(<String>['echo', 'test']);
53 54 55
        expect(output, equals('output'));
      });
      test('Throws on process failure', () async {
56
        final FakeProcessManager fakeProcessManager = FakeProcessManager();
57
        fakeProcessManager.fakeResults = <String, List<ProcessResult>>{
58
          'echo test': <ProcessResult>[ProcessResult(0, -1, 'output', 'error')],
59
        };
60
        final ProcessRunner processRunner = ProcessRunner(
61
            subprocessOutput: false, platform: platform, processManager: fakeProcessManager);
62 63 64 65
        expect(
            expectAsync1((List<String> commandLine) async {
              return processRunner.runProcess(commandLine);
            })(<String>['echo', 'test']),
66
            throwsA(isA<PreparePackageException>()));
67 68
      });
    });
69 70
    group('ArchiveCreator for $platformName', () {
      ArchiveCreator creator;
71
      Directory tempDir;
72 73 74 75 76 77
      Directory flutterDir;
      FakeProcessManager processManager;
      final List<List<String>> args = <List<String>>[];
      final List<Map<Symbol, dynamic>> namedArgs = <Map<Symbol, dynamic>>[];
      String flutter;

78
      Future<Uint8List> fakeHttpReader(Uri url, {Map<String, String> headers}) {
79
        return Future<Uint8List>.value(Uint8List(0));
80 81
      }

82
      setUp(() async {
83
        processManager = FakeProcessManager();
84 85
        args.clear();
        namedArgs.clear();
86
        tempDir = Directory.systemTemp.createTempSync('flutter_prepage_package_test.');
87
        flutterDir = Directory(path.join(tempDir.path, 'flutter'));
88
        flutterDir.createSync(recursive: true);
89
        creator = ArchiveCreator(
90 91
          tempDir,
          tempDir,
92 93 94 95 96
          testRef,
          Branch.dev,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
97
          httpReader: fakeHttpReader,
98 99 100 101 102
        );
        flutter = path.join(creator.flutterRoot.absolute.path, 'bin', 'flutter');
      });

      tearDown(() async {
103
        tryToDelete(tempDir);
104 105 106
      });

      test('sets PUB_CACHE properly', () async {
107
        final String createBase = path.join(tempDir.absolute.path, 'create_');
108 109 110
        final String archiveName = path.join(tempDir.absolute.path,
            'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
        processManager.fakeResults = <String, List<ProcessResult>>{
111
          'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
112
          'git reset --hard $testRef': null,
113
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
114
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
115
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
116 117 118 119 120 121 122 123
          '$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,
          'git clean -f -X **/.packages': null,
124
          'git clean -f -X **/.dart_tool': null,
125
          if (platform.isWindows) 'attrib -h .git': null,
126 127 128 129
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
          else if (platform.isMacOS) 'zip -r -9 $archiveName flutter': null
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
130 131 132 133
        await creator.initializeRepo();
        await creator.createArchive();
        expect(
          verify(processManager.start(
134 135 136
            captureAny,
            workingDirectory: captureAnyNamed('workingDirectory'),
            environment: captureAnyNamed('environment'),
137
          )).captured[2]['PUB_CACHE'],
138 139 140 141 142
          endsWith(path.join('flutter', '.pub-cache')),
        );
      });

      test('calls the right commands for archive output', () async {
143
        final String createBase = path.join(tempDir.absolute.path, 'create_');
144 145
        final String archiveName = path.join(tempDir.absolute.path,
            'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
146
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
147
          'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
148
          'git reset --hard $testRef': null,
149
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
150
          'git describe --tags --exact-match $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
151
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
152 153 154 155 156
          '$flutter doctor': null,
          '$flutter update-packages': null,
          '$flutter precache': null,
          '$flutter ide-config': null,
          '$flutter create --template=app ${createBase}app': null,
157
          '$flutter create --template=package ${createBase}package': null,
158 159
          '$flutter create --template=plugin ${createBase}plugin': null,
          'git clean -f -X **/.packages': null,
160
          'git clean -f -X **/.dart_tool': null,
161
          if (platform.isWindows) 'attrib -h .git': null,
162 163 164 165
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
          else if (platform.isMacOS) 'zip -r -9 $archiveName flutter': null
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
166
        processManager.fakeResults = calls;
167
        creator = ArchiveCreator(
168 169
          tempDir,
          tempDir,
170 171 172 173 174
          testRef,
          Branch.dev,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
175
          httpReader: fakeHttpReader,
176 177 178 179 180 181 182 183 184
        );
        await creator.initializeRepo();
        await creator.createArchive();
        processManager.verifyCalls(calls.keys.toList());
      });

      test('throws when a command errors out', () async {
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
          'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter':
185 186
              <ProcessResult>[ProcessResult(0, 0, 'output1', '')],
          'git reset --hard $testRef': <ProcessResult>[ProcessResult(0, -1, 'output2', '')],
187 188
        };
        processManager.fakeResults = calls;
189
        expect(expectAsync0(creator.initializeRepo), throwsA(isA<PreparePackageException>()));
190 191 192 193
      });

      test('non-strict mode calls the right commands', () async {
        final String createBase = path.join(tempDir.absolute.path, 'create_');
194 195
        final String archiveName = path.join(tempDir.absolute.path,
            'flutter_${platformName}_v1.2.3-dev${platform.isLinux ? '.tar.xz' : '.zip'}');
196 197 198 199 200
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
          'git clone -b dev https://chromium.googlesource.com/external/github.com/flutter/flutter': null,
          'git reset --hard $testRef': null,
          'git remote set-url origin https://github.com/flutter/flutter.git': null,
          'git describe --tags --abbrev=0 $testRef': <ProcessResult>[ProcessResult(0, 0, 'v1.2.3', '')],
201
          if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null,
202 203 204 205 206 207 208 209
          '$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,
          'git clean -f -X **/.packages': null,
210
          'git clean -f -X **/.dart_tool': null,
211
          if (platform.isWindows) 'attrib -h .git': null,
212 213 214 215
          if (platform.isWindows) '7za a -tzip -mx=9 $archiveName flutter': null
          else if (platform.isMacOS) 'zip -r -9 $archiveName flutter': null
          else if (platform.isLinux) 'tar cJf $archiveName flutter': null,
        };
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
        processManager.fakeResults = calls;
        creator = ArchiveCreator(
          tempDir,
          tempDir,
          testRef,
          Branch.dev,
          strict: false,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
          httpReader: fakeHttpReader,
        );
        await creator.initializeRepo();
        await creator.createArchive();
        processManager.verifyCalls(calls.keys.toList());
231
      });
232 233
    });

234 235 236
    group('ArchivePublisher for $platformName', () {
      FakeProcessManager processManager;
      Directory tempDir;
237 238 239 240 241 242 243
      final String gsutilCall = platform.isWindows
          ? 'python ${path.join("D:", "depot_tools", "gsutil.py")}'
          : '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';
      final String gsArchivePath = 'gs://flutter_infra/releases/stable/$platformName/$archiveName';
244 245

      setUp(() async {
246
        processManager = FakeProcessManager();
247
        tempDir = Directory.systemTemp.createTempSync('flutter_prepage_package_test.');
248 249 250
      });

      tearDown(() async {
251
        tryToDelete(tempDir);
252 253 254
      });

      test('calls the right processes', () async {
255
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
256 257
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
        final String gsJsonPath = 'gs://flutter_infra/releases/$releasesName';
258 259
        final String releasesJson = '''
{
260 261 262 263 264 265 266 267 268 269 270
  "base_url": "https://storage.googleapis.com/flutter_infra/releases",
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
    "dev": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
      "channel": "dev",
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
271 272
      "archive": "dev/$platformName/flutter_${platformName}_v0.2.3-dev.zip",
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8"
273
    },
274 275 276 277 278
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
279 280
      "archive": "dev/$platformName/flutter_${platformName}_v0.2.2-dev.zip",
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b"
281 282 283
    },
    {
      "hash": "$testRef",
284
      "channel": "stable",
285 286
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
287 288
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-dev.zip",
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd"
289
    }
290
  ]
291
}
292
''';
293
        File(jsonPath).writeAsStringSync(releasesJson);
294
        File(archivePath).writeAsStringSync('archive contents');
295
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
296 297
          // This process fails because the file does NOT already exist
          '$gsutilCall -- stat $gsArchivePath': <ProcessResult>[ProcessResult(0, 1, '', '')],
298 299 300 301 302
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
          '$gsutilCall -- -h Content-Type:application/json cp $jsonPath $gsJsonPath': null,
303 304
        };
        processManager.fakeResults = calls;
305
        final File outputFile = File(path.join(tempDir.absolute.path, archiveName));
306
        assert(tempDir.existsSync());
307
        final ArchivePublisher publisher = ArchivePublisher(
308 309
          tempDir,
          testRef,
310
          Branch.stable,
311
          'v1.2.3',
312 313 314 315 316 317 318 319
          outputFile,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        assert(tempDir.existsSync());
        await publisher.publishArchive();
        processManager.verifyCalls(calls.keys.toList());
320
        final File releaseFile = File(jsonPath);
321 322 323
        expect(releaseFile.existsSync(), isTrue);
        final String contents = releaseFile.readAsStringSync();
        // Make sure new data is added.
324
        expect(contents, contains('"hash": "$testRef"'));
325 326
        expect(contents, contains('"channel": "stable"'));
        expect(contents, contains('"archive": "stable/$platformName/$archiveName"'));
327
        expect(contents, contains('"sha256": "f69f4865f861193a91d1c5544a894167a7137b788d10bac8edbf5d095f45cb4d"'));
328
        // Make sure existing entries are preserved.
329 330 331 332 333 334
        expect(contents, contains('"hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"'));
        expect(contents, contains('"hash": "b9bd51cc36b706215915711e580851901faebb40"'));
        expect(contents, contains('"channel": "beta"'));
        expect(contents, contains('"channel": "dev"'));
        // Make sure old matching entries are removed.
        expect(contents, isNot(contains('v0.0.0')));
335 336
        final Map<String, dynamic> jsonData = json.decode(contents) as Map<String, dynamic>;
        final List<dynamic> releases = jsonData['releases'] as List<dynamic>;
337 338 339 340
        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(
341
          DateTime.now().difference(DateTime.parse(releases[0]['release_date'] as String)),
342 343
          lessThan(const Duration(minutes: 1)),
        );
344
        const JsonEncoder encoder = JsonEncoder.withIndent('  ');
345 346
        expect(contents, equals(encoder.convert(jsonData)));
      });
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434

      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,
          'v1.2.3',
          outputFile,
          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, '', '')],
        };
        processManager.fakeResults = calls;
        expect(() async => await publisher.publishArchive(false), throwsException);
        processManager.verifyCalls(calls.keys.toList());
      });

      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,
          'v1.2.3',
          outputFile,
          processManager: processManager,
          subprocessOutput: false,
          platform: platform,
        );
        final String archivePath = path.join(tempDir.absolute.path, archiveName);
        final String jsonPath = path.join(tempDir.absolute.path, releasesName);
        final String gsJsonPath = 'gs://flutter_infra/releases/$releasesName';
        final String releasesJson = '''
{
  "base_url": "https://storage.googleapis.com/flutter_infra/releases",
  "current_release": {
    "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2",
    "dev": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"
  },
  "releases": [
    {
      "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0",
      "channel": "dev",
      "version": "v0.2.3",
      "release_date": "2018-03-20T01:47:02.851729Z",
      "archive": "dev/$platformName/flutter_${platformName}_v0.2.3-dev.zip",
      "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8"
    },
    {
      "hash": "b9bd51cc36b706215915711e580851901faebb40",
      "channel": "beta",
      "version": "v0.2.2",
      "release_date": "2018-03-16T18:48:13.375013Z",
      "archive": "dev/$platformName/flutter_${platformName}_v0.2.2-dev.zip",
      "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b"
    },
    {
      "hash": "$testRef",
      "channel": "stable",
      "version": "v0.0.0",
      "release_date": "2018-03-20T01:47:02.851729Z",
      "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-dev.zip",
      "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd"
    }
  ]
}
''';
        File(jsonPath).writeAsStringSync(releasesJson);
        File(archivePath).writeAsStringSync('archive contents');
        final Map<String, List<ProcessResult>> calls = <String, List<ProcessResult>>{
          '$gsutilCall -- rm $gsArchivePath': null,
          '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null,
          '$gsutilCall -- cp $gsJsonPath $jsonPath': null,
          '$gsutilCall -- rm $gsJsonPath': null,
          '$gsutilCall -- -h Content-Type:application/json cp $jsonPath $gsJsonPath': null,
        };
        processManager.fakeResults = calls;
        assert(tempDir.existsSync());
        await publisher.publishArchive(true);
        processManager.verifyCalls(calls.keys.toList());
      });
435 436
    });
  }
437
}