artifact_updater_test.dart 19.3 KB
Newer Older
1 2 3 4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
9
import 'package:flutter_tools/src/base/common.dart';
10 11 12 13
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart';
14
import 'package:flutter_tools/src/base/platform.dart';
15
import 'package:flutter_tools/src/base/terminal.dart';
16
import 'package:flutter_tools/src/cache.dart';
17
import 'package:test/fake.dart';
18 19

import '../src/common.dart';
20
import '../src/fake_http_client.dart';
21
import '../src/fakes.dart';
22

23
final Platform testPlatform = FakePlatform(environment: const <String, String>{});
24

25 26
void main() {
  testWithoutContext('ArtifactUpdater can download a zip archive', () async {
27
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
28 29 30 31 32 33
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
34
      platform: testPlatform,
35
      httpClient: FakeHttpClient.any(),
36 37 38 39 40 41
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    await artifactUpdater.downloadZipArchive(
      'test message',
42
      Uri.parse('http://test.zip'),
43 44 45 46 47 48
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
  });

49
  testWithoutContext('ArtifactUpdater can download a zip archive and delete stale files', () async {
50
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
51 52 53 54 55 56 57
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
58
      httpClient: FakeHttpClient.any(),
59 60 61 62 63 64 65 66 67 68
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );
    // Unrelated file from another cache.
    fileSystem.file('out/bar').createSync(recursive: true);
    // Stale file from current cache.
    fileSystem.file('out/test/foo.txt').createSync(recursive: true);

    await artifactUpdater.downloadZipArchive(
      'test message',
69
      Uri.parse('http://test.zip'),
70 71 72 73 74 75 76 77
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
    expect(fileSystem.file('out/bar'), exists);
    expect(fileSystem.file('out/test/foo.txt'), isNot(exists));
  });

78 79
  testWithoutContext('ArtifactUpdater will not validate the md5 hash if the '
    'x-goog-hash header is present but missing an md5 entry', () async {
80
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
81 82 83 84 85 86 87 88
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();

    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
89
      httpClient: FakeHttpClient.list(<FakeRequest>[
90
        FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(
91 92 93 94 95
          headers: <String, List<String>>{
            'x-goog-hash': <String>[],
          }
        )),
      ]),
96 97 98 99 100 101
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    await artifactUpdater.downloadZipArchive(
      'test message',
102
      Uri.parse('http://test.zip'),
103 104 105 106 107 108 109 110
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
  });

  testWithoutContext('ArtifactUpdater will validate the md5 hash if the '
    'x-goog-hash header is present', () async {
111
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
112 113 114 115 116 117 118 119
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();

    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
120
      httpClient: FakeHttpClient.list(<FakeRequest>[
121
        FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(
122 123 124 125 126 127 128 129 130
          body: <int>[0],
          headers: <String, List<String>>{
            'x-goog-hash': <String>[
              'foo-bar-baz',
              'md5=k7iFrf4NoInN9jSQT9WfcQ=='
            ],
          }
        )),
      ]),
131 132 133 134 135 136
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    await artifactUpdater.downloadZipArchive(
      'test message',
137
      Uri.parse('http://test.zip'),
138 139 140 141 142 143 144 145
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
  });

  testWithoutContext('ArtifactUpdater will validate the md5 hash if the '
    'x-goog-hash header is present and throw if it does not match', () async {
146
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
147 148 149 150 151 152 153 154
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();

    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
155
      httpClient: FakeHttpClient.list(<FakeRequest>[
156
         FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(
157 158 159 160 161 162 163 164
           body: <int>[0],
           headers: <String, List<String>>{
             'x-goog-hash': <String>[
              'foo-bar-baz',
              'md5=k7iFrf4SQT9WfcQ=='
            ],
          }
        )),
165
       FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(
166 167 168 169 170 171 172 173
           headers: <String, List<String>>{
             'x-goog-hash': <String>[
              'foo-bar-baz',
              'md5=k7iFrf4SQT9WfcQ=='
            ],
          }
        )),
      ]),
174 175 176 177
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

178
    await expectLater(() async => artifactUpdater.downloadZipArchive(
179
      'test message',
180
      Uri.parse('http://test.zip'),
181 182 183 184
      fileSystem.currentDirectory.childDirectory('out'),
    ), throwsToolExit(message: 'k7iFrf4SQT9WfcQ==')); // validate that the hash mismatch message is included.
  });

185
  testWithoutContext('ArtifactUpdater will restart the status ticker if it needs to retry the download', () async {
186
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
187 188 189
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final Logger logger = StdoutLogger(
      terminal: Terminal.test(supportsColor: true),
190
      stdio: FakeStdio(),
191 192 193 194 195 196 197
      outputPreferences: OutputPreferences.test(),
    );
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
198
      httpClient: FakeHttpClient.list(<FakeRequest>[
199 200
        FakeRequest(Uri.parse('http://test.zip'), responseError: const HttpException('')),
        FakeRequest(Uri.parse('http://test.zip')),
201
      ]),
202 203 204 205 206 207
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    await artifactUpdater.downloadZipArchive(
      'test message',
208
      Uri.parse('http://test.zip'),
209 210 211 212 213 214
      fileSystem.currentDirectory.childDirectory('out'),
    );

    expect(fileSystem.file('out/test'), exists);
  });

215
  testWithoutContext('ArtifactUpdater will re-attempt on a non-200 response', () async {
216
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
217 218
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
219

220 221 222 223 224
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
225
      httpClient: FakeHttpClient.list(<FakeRequest>[
226 227
        FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(statusCode: HttpStatus.preconditionFailed)),
        FakeRequest(Uri.parse('http://test.zip'), response: const FakeResponse(statusCode: HttpStatus.preconditionFailed)),
228
      ]),
229 230 231 232
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

233
    await expectLater(() async => artifactUpdater.downloadZipArchive(
234
      'test message',
235
      Uri.parse('http://test.zip'),
236 237 238 239 240 241 242 243
      fileSystem.currentDirectory.childDirectory('out'),
    ), throwsToolExit());

    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), isNot(exists));
  });

  testWithoutContext('ArtifactUpdater will tool exit on an ArgumentError from http client with base url override', () async {
244
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
245 246 247 248 249 250 251 252 253 254 255
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: FakePlatform(
        environment: <String, String>{
          'FLUTTER_STORAGE_BASE_URL': 'foo-bar'
        },
      ),
256 257 258
      httpClient: FakeHttpClient.list(<FakeRequest>[
        FakeRequest(Uri.parse('http:///foo-bar/test.zip'), responseError: ArgumentError())
      ]),
259 260 261 262
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

263
    await expectLater(() async => artifactUpdater.downloadZipArchive(
264 265 266 267 268 269 270 271 272 273
      'test message',
      Uri.parse('http:///foo-bar/test.zip'),
      fileSystem.currentDirectory.childDirectory('out'),
    ), throwsToolExit());

    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), isNot(exists));
  });

  testWithoutContext('ArtifactUpdater will rethrow on an ArgumentError from http client without base url override', () async {
274
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
275 276 277 278 279 280 281
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
282
      httpClient: FakeHttpClient.list(<FakeRequest>[
283
        FakeRequest(Uri.parse('http://test.zip'), responseError: ArgumentError()),
284
      ]),
285 286 287 288
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

289
    await expectLater(() async => artifactUpdater.downloadZipArchive(
290
      'test message',
291
      Uri.parse('http://test.zip'),
292 293 294 295 296 297 298
      fileSystem.currentDirectory.childDirectory('out'),
    ), throwsA(isA<ArgumentError>()));

    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), isNot(exists));
  });

299
  testWithoutContext('ArtifactUpdater will re-download a file if unzipping fails', () async {
300
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
301 302 303 304 305 306
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
307
      platform: testPlatform,
308
      httpClient: FakeHttpClient.any(),
309 310 311 312 313 314 315
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );
    operatingSystemUtils.failures = 1;

    await artifactUpdater.downloadZipArchive(
      'test message',
316
      Uri.parse('http://test.zip'),
317 318 319 320 321 322
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
  });

323
  testWithoutContext('ArtifactUpdater will de-download a file if unzipping fails on windows', () async {
324
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(windows: true);
325 326 327 328 329 330 331
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
332
      httpClient: FakeHttpClient.any(),
333 334 335 336 337 338 339
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );
    operatingSystemUtils.failures = 1;

    await artifactUpdater.downloadZipArchive(
      'test message',
340
      Uri.parse('http://test.zip'),
341 342 343 344 345 346
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(logger.statusText, contains('test message'));
    expect(fileSystem.file('out/test'), exists);
  });

347
  testWithoutContext('ArtifactUpdater will bail with a tool exit if unzipping fails more than twice', () async {
348
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
349 350 351 352 353 354
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
355
      platform: testPlatform,
356
      httpClient: FakeHttpClient.any(),
357 358 359 360 361 362 363
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );
    operatingSystemUtils.failures = 2;

    expect(artifactUpdater.downloadZipArchive(
      'test message',
364
      Uri.parse('http://test.zip'),
365
      fileSystem.currentDirectory.childDirectory('out'),
366
    ), throwsA(isA<ToolExit>()));
367 368 369 370
    expect(fileSystem.file('te,[/test'), isNot(exists));
    expect(fileSystem.file('out/test'), isNot(exists));
  });

371
  testWithoutContext('ArtifactUpdater will bail if unzipping fails more than twice on Windows', () async {
372
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(windows: true);
373 374 375 376 377 378 379
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: testPlatform,
380
      httpClient: FakeHttpClient.any(),
381 382 383 384 385 386 387
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );
    operatingSystemUtils.failures = 2;

    expect(artifactUpdater.downloadZipArchive(
      'test message',
388
      Uri.parse('http://test.zip'),
389
      fileSystem.currentDirectory.childDirectory('out'),
390
    ), throwsA(isA<ToolExit>()));
391 392 393 394
    expect(fileSystem.file('te,[/test'), isNot(exists));
    expect(fileSystem.file('out/test'), isNot(exists));
  });

395
  testWithoutContext('ArtifactUpdater can download a tar archive', () async {
396
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
397 398 399 400 401 402
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
403
      platform: testPlatform,
404
      httpClient: FakeHttpClient.any(),
405 406 407 408 409 410
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    await artifactUpdater.downloadZippedTarball(
      'test message',
411
      Uri.parse('http://test.zip'),
412 413 414 415 416 417
      fileSystem.currentDirectory.childDirectory('out'),
    );
    expect(fileSystem.file('out/test'), exists);
  });

  testWithoutContext('ArtifactUpdater will delete downloaded files if they exist.', () async {
418
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
419 420 421 422 423 424
    final MemoryFileSystem fileSystem = MemoryFileSystem.test();
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
425
      platform: testPlatform,
426
      httpClient: FakeHttpClient.any(),
427 428 429 430 431 432 433 434 435 436 437 438 439 440
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    artifactUpdater.downloadedFiles.addAll(<File>[
      fileSystem.file('a/b/c/d')..createSync(recursive: true),
      fileSystem.file('d/e/f'),
    ]);

    artifactUpdater.removeDownloadedFiles();

    expect(fileSystem.file('a/b/c/d'), isNot(exists));
    expect(logger.errorText, isEmpty);
  });
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463

  testWithoutContext('ArtifactUpdater will tool exit if deleting the existing artifacts fails with 32 on windows', () async {
    const int kSharingViolation = 32;
    final FileExceptionHandler handler = FileExceptionHandler();
    final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils();
    final MemoryFileSystem fileSystem = MemoryFileSystem.test(opHandle: handler.opHandle);
    final BufferLogger logger = BufferLogger.test();
    final ArtifactUpdater artifactUpdater = ArtifactUpdater(
      fileSystem: fileSystem,
      logger: logger,
      operatingSystemUtils: operatingSystemUtils,
      platform: FakePlatform(operatingSystem: 'windows'),
      httpClient: FakeHttpClient.any(),
      tempStorage: fileSystem.currentDirectory.childDirectory('temp')
        ..createSync(),
    );

    final Directory errorDirectory = fileSystem.currentDirectory
      .childDirectory('out')
      .childDirectory('test')
      ..createSync(recursive: true);
    handler.addError(errorDirectory, FileSystemOp.delete, const FileSystemException('', '', OSError('', kSharingViolation)));

464
    await expectLater(() async => artifactUpdater.downloadZippedTarball(
465 466 467 468 469 470 471 472
      'test message',
      Uri.parse('http://test.zip'),
      fileSystem.currentDirectory.childDirectory('out'),
    ), throwsToolExit(
      message: 'Failed to delete /out/test because the local file/directory is in use by another process'
    ));
    expect(fileSystem.file('out/test'), isNot(exists));
  });
473 474
}

475 476
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
  FakeOperatingSystemUtils({this.windows = false});
477

478
  int failures = 0;
479
  final bool windows;
480 481 482 483 484

  @override
  void unzip(File file, Directory targetDirectory) {
    if (failures > 0) {
      failures -= 1;
485
      throw Exception();
486 487 488 489 490 491 492 493 494
    }
    targetDirectory.childFile(file.fileSystem.path.basenameWithoutExtension(file.path))
      .createSync();
  }

  @override
  void unpack(File gzippedTarFile, Directory targetDirectory) {
    if (failures > 0) {
      failures -= 1;
495
      throw Exception();
496 497 498 499 500
    }
    targetDirectory.childFile(gzippedTarFile.fileSystem.path.basenameWithoutExtension(gzippedTarFile.path))
      .createSync();
  }
}