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

import 'dart:convert';

import 'package:file/file.dart';
8
import 'package:file/memory.dart';
9

10 11
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/file_system.dart';
12

13
import 'package:flutter_tools/src/globals.dart' as globals;
14

15 16
import '../src/common.dart';
import '../src/context.dart';
17 18

void main() {
19 20 21 22 23 24
  String fixPath(String path) {
    // The in-memory file system is strict about slashes on Windows being the
    // correct way so until https://github.com/google/file.dart/issues/112 is
    // fixed we fix them here.
    // TODO(dantup): Remove this function once the above issue is fixed and
    // rolls into Flutter.
25
    return path.replaceAll('/', globals.fs.path.separator);
26
  }
27
  void writePubspecFile(String path, String name, { List<String>? assets }) {
28 29 30 31
    String assetsSection;
    if (assets == null) {
      assetsSection = '';
    } else {
32
      final StringBuffer buffer = StringBuffer();
33 34 35 36 37
      buffer.write('''
flutter:
     assets:
''');

38
      for (final String asset in assets) {
39 40 41 42 43 44 45
        buffer.write('''
       - $asset
''');
      }
      assetsSection = buffer.toString();
    }

46
    globals.fs.file(fixPath(path))
47 48 49 50 51 52 53 54 55 56 57
      ..createSync(recursive: true)
      ..writeAsStringSync('''
name: $name
dependencies:
  flutter:
    sdk: flutter
$assetsSection
''');
  }

  void writePackagesFile(String packages) {
58
    globals.fs.file('.packages')
59 60 61 62
      ..createSync()
      ..writeAsStringSync(packages);
  }

63
  Future<void> buildAndVerifyAssets(
64 65
    List<String> assets,
    List<String> packages,
66
    String? expectedAssetManifest, {
67 68
    bool expectExists = true,
  }) async {
69
    final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
70
    await bundle.build(packagesPath: '.packages');
71

72 73
    for (final String packageName in packages) {
      for (final String asset in assets) {
74
        final String entryKey = Uri.encodeFull('packages/$packageName/$asset');
75 76 77 78
        expect(bundle.entries.containsKey(entryKey), expectExists,
          reason: 'Cannot find key on bundle: $entryKey');
        if (expectExists) {
          expect(
79
            utf8.decode(await bundle.entries[entryKey]!.contentsAsBytes()),
80 81 82
            asset,
          );
        }
83 84 85
      }
    }

86 87
    if (expectExists) {
      expect(
88
        utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()),
89 90 91
        expectedAssetManifest,
      );
    }
92 93 94
  }

  void writeAssets(String path, List<String> assets) {
95
    for (final String asset in assets) {
96
      final String fullPath = fixPath(globals.fs.path.join(path, asset));
97

98
      globals.fs.file(fullPath)
99 100 101 102 103
        ..createSync(recursive: true)
        ..writeAsStringSync(asset);
    }
  }

104
  late FileSystem testFileSystem;
105 106

  setUp(() async {
107
    testFileSystem = MemoryFileSystem(
108
      style: globals.platform.isWindows
109 110 111 112
        ? FileSystemStyle.windows
        : FileSystemStyle.posix,
    );
    testFileSystem.currentDirectory = testFileSystem.systemTempDirectory.createTempSync('flutter_asset_bundle_test.');
113 114
  });

115 116
  group('AssetBundle assets from packages', () {
    testUsingContext('No assets are bundled when the package has no assets', () async {
117 118 119 120
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');
      writePubspecFile('p/p/pubspec.yaml', 'test_package');

121
      final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
122
      await bundle.build(packagesPath: '.packages');
123
      expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
124
      const String expectedAssetManifest = '{}';
125
      expect(
126
        utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()),
127 128
        expectedAssetManifest,
      );
129
      expect(
130
        utf8.decode(await bundle.entries['FontManifest.json']!.contentsAsBytes()),
131 132
        '[]',
      );
133 134
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
135
      ProcessManager: () => FakeProcessManager.any(),
136
    });
137

138 139 140 141 142 143 144 145
    testUsingContext('No assets are bundled when the package has an asset that is not listed', () async {
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');
      writePubspecFile('p/p/pubspec.yaml', 'test_package');

      final List<String> assets = <String>['a/foo'];
      writeAssets('p/p/', assets);

146
      final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
147
      await bundle.build(packagesPath: '.packages');
148
      expect(bundle.entries.length, 3); // LICENSE, AssetManifest, FontManifest
149
      const String expectedAssetManifest = '{}';
150
      expect(
151
        utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()),
152 153
        expectedAssetManifest,
      );
154
      expect(
155
        utf8.decode(await bundle.entries['FontManifest.json']!.contentsAsBytes()),
156 157
        '[]',
      );
158 159
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
160
      ProcessManager: () => FakeProcessManager.any(),
161
    });
162

163 164
    testUsingContext('One asset is bundled when the package has and lists one '
      'asset its pubspec', () async {
165 166 167 168 169 170 171 172 173 174 175 176
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assets = <String>['a/foo'];
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assets,
      );

      writeAssets('p/p/', assets);

177
      const String expectedAssetManifest = '{"packages/test_package/a/foo":'
178 179 180 181 182 183
          '["packages/test_package/a/foo"]}';
      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedAssetManifest,
      );
184 185
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
186
      ProcessManager: () => FakeProcessManager.any(),
187
    });
188

189 190
    testUsingContext('One asset is bundled when the package has one asset, '
      "listed in the app's pubspec", () async {
191 192 193 194 195 196 197 198 199 200 201 202
      final List<String> assetEntries = <String>['packages/test_package/a/foo'];
      writePubspecFile(
        'pubspec.yaml',
        'test',
        assets: assetEntries,
      );
      writePackagesFile('test_package:p/p/lib/');
      writePubspecFile('p/p/pubspec.yaml', 'test_package');

      final List<String> assets = <String>['a/foo'];
      writeAssets('p/p/lib/', assets);

203
      const String expectedAssetManifest = '{"packages/test_package/a/foo":'
204 205 206 207 208 209
          '["packages/test_package/a/foo"]}';
      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedAssetManifest,
      );
210 211
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
212
      ProcessManager: () => FakeProcessManager.any(),
213
    });
214

215 216
    testUsingContext('One asset and its variant are bundled when the package '
      'has an asset and a variant, and lists the asset in its pubspec', () async {
217 218 219 220 221 222 223 224 225 226 227
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: <String>['a/foo'],
      );

      final List<String> assets = <String>['a/foo', 'a/v/foo'];
      writeAssets('p/p/', assets);

228
      const String expectedManifest = '{"packages/test_package/a/foo":'
229 230 231 232 233 234 235
          '["packages/test_package/a/foo","packages/test_package/a/v/foo"]}';

      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedManifest,
      );
236 237
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
238
      ProcessManager: () => FakeProcessManager.any(),
239
    });
240

241 242
    testUsingContext('One asset and its variant are bundled when the package '
      'has an asset and a variant, and the app lists the asset in its pubspec', () async {
243 244 245 246 247 248 249 250 251 252 253 254 255 256
      writePubspecFile(
        'pubspec.yaml',
        'test',
        assets: <String>['packages/test_package/a/foo'],
      );
      writePackagesFile('test_package:p/p/lib/');
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
      );

      final List<String> assets = <String>['a/foo', 'a/v/foo'];
      writeAssets('p/p/lib/', assets);

257
      const String expectedManifest = '{"packages/test_package/a/foo":'
258 259 260 261 262 263 264
          '["packages/test_package/a/foo","packages/test_package/a/v/foo"]}';

      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedManifest,
      );
265 266
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
267
      ProcessManager: () => FakeProcessManager.any(),
268
    });
269

270 271
    testUsingContext('Two assets are bundled when the package has and lists '
      'two assets in its pubspec', () async {
272 273 274 275 276 277 278 279 280 281 282
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assets = <String>['a/foo', 'a/bar'];
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assets,
      );

      writeAssets('p/p/', assets);
283
      const String expectedAssetManifest =
284 285
          '{"packages/test_package/a/bar":["packages/test_package/a/bar"],'
          '"packages/test_package/a/foo":["packages/test_package/a/foo"]}';
286 287 288 289 290 291

      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedAssetManifest,
      );
292 293
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
294
      ProcessManager: () => FakeProcessManager.any(),
295
    });
296

297
    testUsingContext("Two assets are bundled when the package has two assets, listed in the app's pubspec", () async {
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
      final List<String> assetEntries = <String>[
        'packages/test_package/a/foo',
        'packages/test_package/a/bar',
      ];
      writePubspecFile(
        'pubspec.yaml',
        'test',
         assets: assetEntries,
      );
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assets = <String>['a/foo', 'a/bar'];
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
      );

      writeAssets('p/p/lib/', assets);
316
      const String expectedAssetManifest =
317 318
          '{"packages/test_package/a/bar":["packages/test_package/a/bar"],'
          '"packages/test_package/a/foo":["packages/test_package/a/foo"]}';
319 320 321 322 323 324

      await buildAndVerifyAssets(
        assets,
        <String>['test_package'],
        expectedAssetManifest,
      );
325 326
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
327
      ProcessManager: () => FakeProcessManager.any(),
328
    });
329

330
    testUsingContext('Two assets are bundled when two packages each have and list an asset their pubspec', () async {
331 332 333 334
      writePubspecFile(
        'pubspec.yaml',
        'test',
      );
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
      writePackagesFile('test_package:p/p/lib/\ntest_package2:p2/p/lib/');
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: <String>['a/foo'],
      );
      writePubspecFile(
        'p2/p/pubspec.yaml',
        'test_package2',
        assets: <String>['a/foo'],
      );

      final List<String> assets = <String>['a/foo', 'a/v/foo'];
      writeAssets('p/p/', assets);
      writeAssets('p2/p/', assets);

351
      const String expectedAssetManifest =
352 353 354 355 356 357 358 359 360 361
          '{"packages/test_package/a/foo":'
          '["packages/test_package/a/foo","packages/test_package/a/v/foo"],'
          '"packages/test_package2/a/foo":'
          '["packages/test_package2/a/foo","packages/test_package2/a/v/foo"]}';

      await buildAndVerifyAssets(
        assets,
        <String>['test_package', 'test_package2'],
        expectedAssetManifest,
      );
362 363
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
364
      ProcessManager: () => FakeProcessManager.any(),
365
    });
366

367
    testUsingContext("Two assets are bundled when two packages each have an asset, listed in the app's pubspec", () async {
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
      final List<String> assetEntries = <String>[
        'packages/test_package/a/foo',
        'packages/test_package2/a/foo',
      ];
      writePubspecFile(
        'pubspec.yaml',
        'test',
        assets: assetEntries,
      );
      writePackagesFile('test_package:p/p/lib/\ntest_package2:p2/p/lib/');
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
      );
      writePubspecFile(
        'p2/p/pubspec.yaml',
        'test_package2',
      );

      final List<String> assets = <String>['a/foo', 'a/v/foo'];
      writeAssets('p/p/lib/', assets);
      writeAssets('p2/p/lib/', assets);

391
      const String expectedAssetManifest =
392 393 394 395 396 397 398 399 400 401
          '{"packages/test_package/a/foo":'
          '["packages/test_package/a/foo","packages/test_package/a/v/foo"],'
          '"packages/test_package2/a/foo":'
          '["packages/test_package2/a/foo","packages/test_package2/a/v/foo"]}';

      await buildAndVerifyAssets(
        assets,
        <String>['test_package', 'test_package2'],
        expectedAssetManifest,
      );
402 403
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
404
      ProcessManager: () => FakeProcessManager.any(),
405
    });
406

407 408
    testUsingContext('One asset is bundled when the app depends on a package, '
      'listing in its pubspec an asset from another package', () async {
409 410 411 412 413 414 415 416
      writePubspecFile(
        'pubspec.yaml',
        'test',
      );
      writePackagesFile('test_package:p/p/lib/\ntest_package2:p2/p/lib/');
      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
417
        assets: <String>['packages/test_package2/a/foo'],
418 419 420 421 422 423 424 425 426
      );
      writePubspecFile(
        'p2/p/pubspec.yaml',
        'test_package2',
      );

      final List<String> assets = <String>['a/foo', 'a/v/foo'];
      writeAssets('p2/p/lib/', assets);

427
      const String expectedAssetManifest =
428 429 430 431 432 433 434 435
          '{"packages/test_package2/a/foo":'
          '["packages/test_package2/a/foo","packages/test_package2/a/v/foo"]}';

      await buildAndVerifyAssets(
        assets,
        <String>['test_package2'],
        expectedAssetManifest,
      );
436 437
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
438
      ProcessManager: () => FakeProcessManager.any(),
439
    });
440
  });
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462

  testUsingContext('Asset paths can contain URL reserved characters', () async {
    writePubspecFile('pubspec.yaml', 'test');
    writePackagesFile('test_package:p/p/lib/');

    final List<String> assets = <String>['a/foo', 'a/foo[x]'];
    writePubspecFile(
      'p/p/pubspec.yaml',
      'test_package',
      assets: assets,
    );

    writeAssets('p/p/', assets);
    const String expectedAssetManifest =
        '{"packages/test_package/a/foo":["packages/test_package/a/foo"],'
        '"packages/test_package/a/foo%5Bx%5D":["packages/test_package/a/foo%5Bx%5D"]}';

    await buildAndVerifyAssets(
      assets,
      <String>['test_package'],
      expectedAssetManifest,
    );
463 464
  }, overrides: <Type, Generator>{
    FileSystem: () => testFileSystem,
465
      ProcessManager: () => FakeProcessManager.any(),
466
  });
467 468

  group('AssetBundle assets from scanned paths', () {
469
    testUsingContext('Two assets are bundled when scanning their directory', () async {
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetsOnDisk = <String>['a/foo', 'a/bar'];
      final List<String> assetsOnManifest = <String>['a/'];

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetsOnManifest,
      );

      writeAssets('p/p/', assetsOnDisk);
      const String expectedAssetManifest =
          '{"packages/test_package/a/bar":["packages/test_package/a/bar"],'
          '"packages/test_package/a/foo":["packages/test_package/a/foo"]}';

      await buildAndVerifyAssets(
        assetsOnDisk,
        <String>['test_package'],
        expectedAssetManifest,
      );
492 493
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
494
      ProcessManager: () => FakeProcessManager.any(),
495 496
    });

497
    testUsingContext('Two assets are bundled when listing one and scanning second directory', () async {
498 499 500 501 502 503 504 505 506 507 508 509 510 511
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetsOnDisk = <String>['a/foo', 'abc/bar'];
      final List<String> assetOnManifest = <String>['a/foo', 'abc/'];

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetOnManifest,
      );

      writeAssets('p/p/', assetsOnDisk);
      const String expectedAssetManifest =
512 513
          '{"packages/test_package/a/foo":["packages/test_package/a/foo"],'
          '"packages/test_package/abc/bar":["packages/test_package/abc/bar"]}';
514 515 516 517 518 519

      await buildAndVerifyAssets(
        assetsOnDisk,
        <String>['test_package'],
        expectedAssetManifest,
      );
520 521
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
522
      ProcessManager: () => FakeProcessManager.any(),
523 524
    });

525
    testUsingContext('One asset is bundled with variant, scanning wrong directory', () async {
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetsOnDisk = <String>['a/foo','a/b/foo','a/bar'];
      final List<String> assetOnManifest = <String>['a','a/bar']; // can't list 'a' as asset, should be 'a/'

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetOnManifest,
      );

      writeAssets('p/p/', assetsOnDisk);

      final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
541
      await bundle.build(packagesPath: '.packages');
542 543 544

      expect(bundle.entries['AssetManifest.json'], isNull,
        reason: 'Invalid pubspec.yaml should not generate AssetManifest.json'  );
545 546
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
547
      ProcessManager: () => FakeProcessManager.any(),
548 549 550 551
    });
  });

  group('AssetBundle assets from scanned paths with MemoryFileSystem', () {
552
    testUsingContext('One asset is bundled with variant, scanning directory', () async {
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetsOnDisk = <String>['a/foo','a/b/foo'];
      final List<String> assetOnManifest = <String>['a/',];

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetOnManifest,
      );

      writeAssets('p/p/', assetsOnDisk);
      const String expectedAssetManifest =
          '{"packages/test_package/a/foo":["packages/test_package/a/foo","packages/test_package/a/b/foo"]}';

      await buildAndVerifyAssets(
        assetsOnDisk,
        <String>['test_package'],
        expectedAssetManifest,
      );
574 575
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
576
      ProcessManager: () => FakeProcessManager.any(),
577 578
    });

579
    testUsingContext('No asset is bundled with variant, no assets or directories are listed', () async {
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetsOnDisk = <String>['a/foo', 'a/b/foo'];
      final List<String> assetOnManifest = <String>[];

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetOnManifest,
      );

      writeAssets('p/p/', assetsOnDisk);
      const String expectedAssetManifest = '{}';

      await buildAndVerifyAssets(
        assetOnManifest,
        <String>['test_package'],
        expectedAssetManifest,
      );
600 601
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
602
      ProcessManager: () => FakeProcessManager.any(),
603 604
    });

605
    testUsingContext('Expect error generating manifest, wrong non-existing directory is listed', () async {
606 607 608 609 610 611 612 613 614 615 616
      writePubspecFile('pubspec.yaml', 'test');
      writePackagesFile('test_package:p/p/lib/');

      final List<String> assetOnManifest = <String>['c/'];

      writePubspecFile(
        'p/p/pubspec.yaml',
        'test_package',
        assets: assetOnManifest,
      );

617 618 619 620 621 622
      await buildAndVerifyAssets(
        assetOnManifest,
        <String>['test_package'],
        null,
        expectExists: false,
      );
623 624
    }, overrides: <Type, Generator>{
      FileSystem: () => testFileSystem,
625
      ProcessManager: () => FakeProcessManager.any(),
626 627
    });
  });
628
}