// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @Timeout(Duration(minutes: 2)) library; import 'dart:convert'; import 'package:dwds/src/debugging/metadata/module_metadata.dart'; import 'package:dwds/src/debugging/metadata/provider.dart'; import 'package:test/test.dart'; import 'fixtures/fakes.dart'; import 'fixtures/utilities.dart'; const _emptySourceMetadata = '{"version":"1.0.0","name":"web/main","closureName":"load__web__main",' '"sourceMapUri":"foo/web/main.ddc.js.map",' '"moduleUri":"foo/web/main.ddc.js",' '"libraries":[{"name":"main",' '"importUri":"org-dartlang-app:///web/main.dart",' '"fileUri":"org-dartlang-app:///web/main.dart","partUris":[]}]}\n' '// intentionally empty: package blah has no dart sources'; const _fileUriMetadata = '{"version":"1.0.0","name":"web/main","closureName":"load__web__main",' '"sourceMapUri":"foo/web/main.ddc.js.map",' '"moduleUri":"foo/web/main.ddc.js",' '"libraries":[{"name":"main",' '"importUri":"file:/Users/foo/blah/sample/lib/bar.dart",' '"fileUri":"org-dartlang-app:///web/main.dart","partUris":[]}]}\n' '// intentionally empty: package blah has no dart sources'; void main() { final toolConfiguration = TestToolConfiguration.withLoadStrategy( loadStrategy: FakeStrategy(FakeAssetReader()), ); setGlobalsForTesting(toolConfiguration: toolConfiguration); test('can parse metadata with empty sources', () async { final provider = MetadataProvider( 'foo.bootstrap.js', FakeAssetReader(metadata: _emptySourceMetadata), ); expect( await provider.libraries, contains('org-dartlang-app:///web/main.dart'), ); }); test('throws on metadata with absolute import uris', () async { final provider = MetadataProvider( 'foo.bootstrap.js', FakeAssetReader(metadata: _fileUriMetadata), ); await expectLater( provider.libraries, throwsA(const TypeMatcher()), ); }); test( 'module name exists if useModuleName and otherwise use module uri', () async { final provider = MetadataProvider( 'foo.bootstrap.js', FakeAssetReader(metadata: _emptySourceMetadata), ); final modulePath = 'foo/web/main.ddc.js'; final moduleName = 'web/main'; final module = moduleName; expect( await provider.scriptToModule, predicate>( (scriptToModule) => !scriptToModule.values.any((value) => value == modulePath), ), ); expect(await provider.moduleToSourceMap, { module: 'foo/web/main.ddc.js.map', }); expect(await provider.modulePathToModule, {modulePath: module}); expect(await provider.moduleToModulePath, {module: modulePath}); expect(await provider.modules, {module}); }, ); test('creates metadata from json', () async { const json = { 'version': '1.0.0', 'name': 'web/main', 'closureName': 'load__web__main', 'sourceMapUri': 'foo/web/main.ddc.js.map', 'moduleUri': 'foo/web/main.ddc.js', 'libraries': [ { 'name': 'main', 'importUri': 'org-dartlang-app:///web/main.dart', 'partUris': ['org-dartlang-app:///web/main.dart'], }, ], }; final metadata = ModuleMetadata.fromJson(json); expect(metadata.version, '1.0.0'); expect(metadata.name, 'web/main'); expect(metadata.closureName, 'load__web__main'); expect(metadata.sourceMapUri, 'foo/web/main.ddc.js.map'); expect(metadata.moduleUri, 'foo/web/main.ddc.js'); final libraries = metadata.libraries; expect(libraries.length, 1); for (final lib in libraries.values) { expect(lib.name, 'main'); expect(lib.importUri, 'org-dartlang-app:///web/main.dart'); final parts = lib.partUris; expect(parts.length, 1); expect(parts[0], 'org-dartlang-app:///web/main.dart'); } }); String createMetadataContents( Map> moduleToLibraries, Map> libraryToParts, ) { final contents = StringBuffer(); for (final MapEntry(key: module, value: libraries) in moduleToLibraries.entries) { final moduleMetadata = ModuleMetadata( module, 'load__web__$module', 'foo/web/$module.ddc.js.map', 'foo/web/$module.ddc.js', ); for (final library in libraries) { moduleMetadata.addLibrary( LibraryMetadata(library, library, libraryToParts[library] ?? []), ); } contents.writeln(json.encode(moduleMetadata.toJson())); } contents.write('// intentionally empty: ...'); return contents.toString(); } Future validateProvider( MetadataProvider provider, Map> moduleToLibraries, Map> libraryToParts, ) async { final expectedScriptToModule = {}; final expectedModuleToSourceMap = {}; final expectedModulePathToModule = {}; final expectedModules = {}; for (final MapEntry(key: module, value: libraries) in moduleToLibraries.entries) { for (final library in libraries) { expectedScriptToModule[library] = module; final parts = libraryToParts[library]; if (parts != null) { for (final part in parts) { expectedScriptToModule[part] = module; } } } expectedModuleToSourceMap[module] = 'foo/web/$module.ddc.js.map'; expectedModulePathToModule['foo/web/$module.ddc.js'] = module; expectedModules.add(module); } final scriptToModule = await provider.scriptToModule; for (final MapEntry(key: script, value: module) in expectedScriptToModule.entries) { expect(scriptToModule[script], module); } final moduleToSourceMap = await provider.moduleToSourceMap; for (final MapEntry(key: module, value: sourceMap) in expectedModuleToSourceMap.entries) { expect(moduleToSourceMap[module], sourceMap); } final modulePathToModule = await provider.modulePathToModule; for (final MapEntry(key: modulePath, value: module) in expectedModulePathToModule.entries) { expect(modulePathToModule[modulePath], module); } expect(await provider.modules, containsAll(expectedModules)); } test('reinitialize produces correct ModifiedModuleReport', () async { const moduleToLibraries = >{ 'm1': [ 'org-dartlang-app:///web/l1.dart', 'org-dartlang-app:///web/l2.dart', ], 'm2': [ 'org-dartlang-app:///web/l3.dart', 'org-dartlang-app:///web/l4.dart', ], 'm3': [ 'org-dartlang-app:///web/l5.dart', 'org-dartlang-app:///web/l6.dart', ], }; const libraryToParts = >{ 'org-dartlang-app:///web/l1.dart': ['org-dartlang-app:///web/l1_p1.dart'], 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p1.dart'], }; final assetReader = FakeAssetReader( metadata: createMetadataContents(moduleToLibraries, libraryToParts), ); final provider = MetadataProvider('foo.bootstrap.js', assetReader); await validateProvider(provider, moduleToLibraries, libraryToParts); const newModuleToLibraries = >{ 'm1': [ 'org-dartlang-app:///web/l1.dart', 'org-dartlang-app:///web/l2.dart', ], 'm3': ['org-dartlang-app:///web/l3.dart'], 'm4': [ 'org-dartlang-app:///web/l4.dart', 'org-dartlang-app:///web/l7.dart', ], }; const newLibraryToParts = >{ 'org-dartlang-app:///web/l2.dart': ['org-dartlang-app:///web/l1_p1.dart'], 'org-dartlang-app:///web/l3.dart': ['org-dartlang-app:///web/l3_p2.dart'], 'org-dartlang-app:///web/l7.dart': ['org-dartlang-app:///web/l7_p1.dart'], }; const reloadedModulesToLibraries = >{ 'm3': ['org-dartlang-app:///web/l3.dart'], 'm4': [ 'org-dartlang-app:///web/l4.dart', 'org-dartlang-app:///web/l7.dart', ], }; assetReader.metadata = createMetadataContents( newModuleToLibraries, newLibraryToParts, ); final modifiedModuleReport = await provider.reinitializeAfterHotReload( reloadedModulesToLibraries, ); expect(modifiedModuleReport.deletedModules, ['m2']); expect( modifiedModuleReport.deletedLibraries, unorderedEquals([ 'org-dartlang-app:///web/l5.dart', 'org-dartlang-app:///web/l6.dart', ]), ); expect(modifiedModuleReport.reloadedModules, ['m3', 'm4']); expect( modifiedModuleReport.reloadedLibraries, unorderedEquals([ 'org-dartlang-app:///web/l3.dart', 'org-dartlang-app:///web/l4.dart', 'org-dartlang-app:///web/l7.dart', ]), ); expect(modifiedModuleReport.modifiedModules, ['m2', 'm3', 'm4']); expect( modifiedModuleReport.modifiedLibraries, unorderedEquals([ 'org-dartlang-app:///web/l3.dart', 'org-dartlang-app:///web/l4.dart', 'org-dartlang-app:///web/l5.dart', 'org-dartlang-app:///web/l6.dart', 'org-dartlang-app:///web/l7.dart', ]), ); await validateProvider(provider, newModuleToLibraries, newLibraryToParts); }); }