|  | // Copyright (c) 2023, 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. | 
|  |  | 
|  | import 'dart:convert'; | 
|  | import 'dart:io'; | 
|  |  | 
|  | import 'package:collection/collection.dart'; | 
|  | import 'package:front_end/src/api_unstable/vm.dart'; | 
|  | import 'package:kernel/const_finder.dart'; | 
|  | import 'package:path/path.dart' as path; | 
|  |  | 
|  | void expect<T>(T value, T expected) { | 
|  | if (value != expected) { | 
|  | stderr.writeln('Expected: $expected'); | 
|  | stderr.writeln('Actual:   $value'); | 
|  | exitCode = -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | void expectInstances(dynamic value, dynamic expected, Compiler compiler) { | 
|  | // To ensure we ignore insertion order into maps as well as lists we use | 
|  | // DeepCollectionEquality as well as sort the lists. | 
|  |  | 
|  | int compareByStringValue(dynamic a, dynamic b) { | 
|  | return a['stringValue'].compareTo(b['stringValue']) as int; | 
|  | } | 
|  |  | 
|  | value['constantInstances'].sort(compareByStringValue); | 
|  | expected['constantInstances'].sort(compareByStringValue); | 
|  |  | 
|  | final Equality<Object?> equality; | 
|  | if (compiler == Compiler.dart2js) { | 
|  | equality = const Dart2JSDeepCollectionEquality(); | 
|  | } else { | 
|  | equality = const DeepCollectionEquality(); | 
|  | } | 
|  | if (!equality.equals(value, expected)) { | 
|  | stderr.writeln('Expected: ${jsonEncode(expected)}'); | 
|  | stderr.writeln('Actual:   ${jsonEncode(value)}'); | 
|  | exitCode = -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | // This test is assuming the `dart` used to invoke the tests is compatible | 
|  | // with the version of package:kernel in //third-party/dart/pkg/kernel | 
|  | final String dart = Platform.resolvedExecutable; | 
|  | final String bat = Platform.isWindows ? '.bat' : ''; | 
|  |  | 
|  | void _checkRecursion(String dillPath, Compiler compiler) { | 
|  | stdout.writeln('Checking recursive calls.'); | 
|  | final ConstFinder finder = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/box.dart', | 
|  | className: 'Box', | 
|  | ); | 
|  | // Will timeout if we did things wrong. | 
|  | jsonEncode(finder.findInstances()); | 
|  | } | 
|  |  | 
|  | void _checkConsts(String dillPath, Compiler compiler) { | 
|  | stdout.writeln('Checking for expected constants.'); | 
|  | final ConstFinder finder = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/target.dart', | 
|  | className: 'Target', | 
|  | ); | 
|  | final Map<String, Object?> expectation = <String, dynamic>{ | 
|  | 'constantInstances': <Map<String, dynamic>>[ | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '100', | 
|  | 'intValue': 100, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '102', | 
|  | 'intValue': 102, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '101', 'intValue': 101}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '103', | 
|  | 'intValue': 103, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '105', | 
|  | 'intValue': 105, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '104', 'intValue': 104}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '106', | 
|  | 'intValue': 106, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '108', | 
|  | 'intValue': 108, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '107', 'intValue': 107}, | 
|  | <String, dynamic>{'stringValue': '1', 'intValue': 1, 'targetValue': null}, | 
|  | <String, dynamic>{'stringValue': '4', 'intValue': 4, 'targetValue': null}, | 
|  | <String, dynamic>{'stringValue': '2', 'intValue': 2}, | 
|  | <String, dynamic>{'stringValue': '6', 'intValue': 6, 'targetValue': null}, | 
|  | <String, dynamic>{'stringValue': '8', 'intValue': 8, 'targetValue': null}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '10', | 
|  | 'intValue': 10, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '9', 'intValue': 9}, | 
|  | <String, dynamic>{'stringValue': '7', 'intValue': 7, 'targetValue': null}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '11', | 
|  | 'intValue': 11, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '12', | 
|  | 'intValue': 12, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': 'package', | 
|  | 'intValue': -1, | 
|  | 'targetValue': null | 
|  | }, | 
|  | ], | 
|  | 'nonConstantLocations': <dynamic>[], | 
|  | }; | 
|  | if (compiler == Compiler.aot) { | 
|  | expectation['nonConstantLocations'] = <Object?>[]; | 
|  | } else { | 
|  | final String fixturesUrl = Platform.isWindows | 
|  | ? '/$fixtures'.replaceAll(Platform.pathSeparator, '/') | 
|  | : fixtures; | 
|  |  | 
|  | // Without true tree-shaking, there is a non-const reference in a | 
|  | // never-invoked function that will be present in the dill. | 
|  | expectation['nonConstantLocations'] = <Object?>[ | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/pkg/package.dart', | 
|  | 'line': 17, | 
|  | 'column': 25, | 
|  | }, | 
|  | ]; | 
|  | } | 
|  | expectInstances( | 
|  | finder.findInstances(), | 
|  | expectation, | 
|  | compiler, | 
|  | ); | 
|  |  | 
|  | final ConstFinder finder2 = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/target.dart', | 
|  | className: 'MixedInTarget', | 
|  | ); | 
|  | expectInstances( | 
|  | finder2.findInstances(), | 
|  | <String, dynamic>{ | 
|  | 'constantInstances': <Map<String, dynamic>>[ | 
|  | <String, dynamic>{'val': '13'}, | 
|  | ], | 
|  | 'nonConstantLocations': <dynamic>[], | 
|  | }, | 
|  | compiler, | 
|  | ); | 
|  | } | 
|  |  | 
|  | void _checkAnnotation(String dillPath, Compiler compiler) { | 
|  | stdout.writeln( | 
|  | 'Checking constant instances in a class annotated with instance of ' | 
|  | 'StaticIconProvider are ignored with $compiler'); | 
|  | final ConstFinder finder = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/target.dart', | 
|  | className: 'Target', | 
|  | annotationClassName: 'StaticIconProvider', | 
|  | annotationClassLibraryUri: | 
|  | 'package:const_finder_fixtures/static_icon_provider.dart', | 
|  | ); | 
|  | final Map<String, dynamic> instances = finder.findInstances(); | 
|  | expectInstances( | 
|  | instances, | 
|  | <String, dynamic>{ | 
|  | 'constantInstances': <Map<String, Object?>>[ | 
|  | <String, Object?>{ | 
|  | 'stringValue': 'used1', | 
|  | 'intValue': 1, | 
|  | 'targetValue': null, | 
|  | }, | 
|  | <String, Object?>{ | 
|  | 'stringValue': 'used2', | 
|  | 'intValue': 2, | 
|  | 'targetValue': null, | 
|  | }, | 
|  | ], | 
|  | // TODO(fujino): This should have non-constant locations from the use of | 
|  | // a tear-off, see https://github.com/flutter/flutter/issues/116797 | 
|  | 'nonConstantLocations': <Object?>[], | 
|  | }, | 
|  | compiler, | 
|  | ); | 
|  | } | 
|  |  | 
|  | void _checkNonConstsFrontend(String dillPath, Compiler compiler) { | 
|  | stdout.writeln('Checking for non-constant instances with $compiler'); | 
|  | final ConstFinder finder = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/target.dart', | 
|  | className: 'Target', | 
|  | ); | 
|  | final String fixturesUrl = Platform.isWindows | 
|  | ? '/$fixtures'.replaceAll(Platform.pathSeparator, '/') | 
|  | : fixtures; | 
|  |  | 
|  | expectInstances( | 
|  | finder.findInstances(), | 
|  | <String, dynamic>{ | 
|  | 'constantInstances': <dynamic>[ | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '1', | 
|  | 'intValue': 1, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '4', | 
|  | 'intValue': 4, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '6', | 
|  | 'intValue': 6, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '8', | 
|  | 'intValue': 8, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '10', | 
|  | 'intValue': 10, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '9', 'intValue': 9}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '7', | 
|  | 'intValue': 7, | 
|  | 'targetValue': null | 
|  | }, | 
|  | ], | 
|  | 'nonConstantLocations': <dynamic>[ | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 17, | 
|  | 'column': 26, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 20, | 
|  | 'column': 7, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 20, | 
|  | 'column': 22, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 21, | 
|  | 'column': 26, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/pkg/package.dart', | 
|  | 'line': 17, | 
|  | 'column': 25, | 
|  | } | 
|  | ] | 
|  | }, | 
|  | compiler, | 
|  | ); | 
|  | } | 
|  |  | 
|  | // Note, since web dills don't have tree shaking, we aren't able to eliminate | 
|  | // an additional const versus _checkNonConstFrontend. | 
|  | void _checkNonConstsWeb(String dillPath, Compiler compiler) { | 
|  | assert(compiler == Compiler.dart2js); | 
|  | stdout.writeln('Checking for non-constant instances with $compiler'); | 
|  | final ConstFinder finder = new ConstFinder( | 
|  | kernelFilePath: dillPath, | 
|  | classLibraryUri: 'package:const_finder_fixtures/target.dart', | 
|  | className: 'Target', | 
|  | ); | 
|  |  | 
|  | final String fixturesUrl = Platform.isWindows | 
|  | ? '/$fixtures'.replaceAll(Platform.pathSeparator, '/') | 
|  | : fixtures; | 
|  | expectInstances( | 
|  | finder.findInstances(), | 
|  | <String, dynamic>{ | 
|  | 'constantInstances': <dynamic>[ | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '1', | 
|  | 'intValue': 1, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '4', | 
|  | 'intValue': 4, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '6', | 
|  | 'intValue': 6, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '8', | 
|  | 'intValue': 8, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '10', | 
|  | 'intValue': 10, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{'stringValue': '9', 'intValue': 9}, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': '7', | 
|  | 'intValue': 7, | 
|  | 'targetValue': null | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'stringValue': 'package', | 
|  | 'intValue': -1, | 
|  | 'targetValue': null | 
|  | }, | 
|  | ], | 
|  | 'nonConstantLocations': <dynamic>[ | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 17, | 
|  | 'column': 26, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 20, | 
|  | 'column': 7, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 20, | 
|  | 'column': 22, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/lib/consts_and_non.dart', | 
|  | 'line': 21, | 
|  | 'column': 26, | 
|  | }, | 
|  | <String, dynamic>{ | 
|  | 'file': 'file://$fixturesUrl/pkg/package.dart', | 
|  | 'line': 17, | 
|  | 'column': 25, | 
|  | } | 
|  | ], | 
|  | }, | 
|  | compiler, | 
|  | ); | 
|  | } | 
|  |  | 
|  | void checkProcessResult(ProcessResult result) { | 
|  | if (result.exitCode != 0) { | 
|  | stdout.writeln(result.stdout); | 
|  | stderr.writeln(result.stderr); | 
|  | } | 
|  | expect(result.exitCode, 0); | 
|  | } | 
|  |  | 
|  | final String basePath = path | 
|  | .canonicalize(path.join(path.dirname(Platform.script.toFilePath()), '..')); | 
|  | final String fixtures = path.join(basePath, 'test', 'fixtures'); | 
|  | final String packageConfig = | 
|  | path.join(fixtures, '.dart_tool', 'package_config.json'); | 
|  |  | 
|  | // TODO(johnniwinther): Add unittests to package:kernel. | 
|  | // TODO(johnniwinther): Avoid emitting .deps files during testing. | 
|  | Future<void> main() async { | 
|  | final String frontendServer = | 
|  | 'pkg/frontend_server/bin/frontend_server_starter.dart'; | 
|  | final String sdkRoot = computePlatformBinariesLocation().toFilePath(); | 
|  | final String librariesSpec = 'sdk/lib/libraries.json'; | 
|  |  | 
|  | final List<_Test> tests = <_Test>[ | 
|  | new _Test( | 
|  | name: 'box_frontend', | 
|  | dartSource: path.join(fixtures, 'lib', 'box.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkRecursion, | 
|  | compiler: Compiler.aot, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'box_web', | 
|  | dartSource: path.join(fixtures, 'lib', 'box.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkRecursion, | 
|  | compiler: Compiler.dart2js, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'consts_frontend', | 
|  | dartSource: path.join(fixtures, 'lib', 'consts.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkConsts, | 
|  | compiler: Compiler.aot, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'consts_web', | 
|  | dartSource: path.join(fixtures, 'lib', 'consts.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkConsts, | 
|  | compiler: Compiler.dart2js, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'consts_and_non_frontend', | 
|  | dartSource: path.join(fixtures, 'lib', 'consts_and_non.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkNonConstsFrontend, | 
|  | compiler: Compiler.aot, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'consts_and_non_web', | 
|  | dartSource: path.join(fixtures, 'lib', 'consts_and_non.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkNonConstsWeb, | 
|  | compiler: Compiler.dart2js, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'static_icon_provider_frontend', | 
|  | dartSource: path.join(fixtures, 'lib', 'static_icon_provider.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkAnnotation, | 
|  | compiler: Compiler.aot, | 
|  | ), | 
|  | new _Test( | 
|  | name: 'static_icon_provider_web', | 
|  | dartSource: path.join(fixtures, 'lib', 'static_icon_provider.dart'), | 
|  | frontendServer: frontendServer, | 
|  | sdkRoot: sdkRoot, | 
|  | librariesSpec: librariesSpec, | 
|  | verify: _checkAnnotation, | 
|  | compiler: Compiler.dart2js, | 
|  | ), | 
|  | ]; | 
|  | try { | 
|  | stdout.writeln('Generating kernel fixtures...'); | 
|  |  | 
|  | for (final _Test test in tests) { | 
|  | test.run(); | 
|  | } | 
|  | } finally { | 
|  | try { | 
|  | for (final _Test test in tests) { | 
|  | test.dispose(); | 
|  | } | 
|  | } finally { | 
|  | stdout.writeln('Tests ${exitCode == 0 ? 'succeeded' : 'failed'} ' | 
|  | '- exit code: $exitCode'); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | enum Compiler { | 
|  | // Uses TFA tree-shaking. | 
|  | aot, | 
|  | // Does not have TFA tree-shaking. | 
|  | dart2js, | 
|  | } | 
|  |  | 
|  | class _Test { | 
|  | _Test({ | 
|  | required this.name, | 
|  | required this.dartSource, | 
|  | required this.sdkRoot, | 
|  | required this.verify, | 
|  | required this.frontendServer, | 
|  | required this.librariesSpec, | 
|  | required this.compiler, | 
|  | }) : dillPath = path.join(fixtures, '$name.dill'); | 
|  |  | 
|  | final String name; | 
|  | final String dartSource; | 
|  | final String sdkRoot; | 
|  | final String frontendServer; | 
|  | final String librariesSpec; | 
|  | final String dillPath; | 
|  | void Function(String, Compiler) verify; | 
|  | final Compiler compiler; | 
|  |  | 
|  | final List<String> resourcesToDispose = <String>[]; | 
|  |  | 
|  | void run() { | 
|  | stdout.writeln('Compiling $dartSource to $dillPath with $compiler'); | 
|  |  | 
|  | if (compiler == Compiler.aot) { | 
|  | _compileAOTDill(); | 
|  | } else { | 
|  | _compileWebDill(); | 
|  | } | 
|  |  | 
|  | stdout.writeln('Testing $dillPath'); | 
|  |  | 
|  | verify(dillPath, compiler); | 
|  | } | 
|  |  | 
|  | void dispose() { | 
|  | for (final String resource in resourcesToDispose) { | 
|  | stdout.writeln('Deleting $resource'); | 
|  | new File(resource).deleteSync(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _compileAOTDill() { | 
|  | checkProcessResult(Process.runSync(dart, <String>[ | 
|  | frontendServer, | 
|  | '--sdk-root=$sdkRoot', | 
|  | '--platform=vm_platform.dill', | 
|  | '--target=vm', | 
|  | '--aot', | 
|  | '--tfa', | 
|  | '--packages=$packageConfig', | 
|  | '--output-dill=$dillPath', | 
|  | dartSource, | 
|  | ])); | 
|  |  | 
|  | resourcesToDispose.add(dillPath); | 
|  | } | 
|  |  | 
|  | void _compileWebDill() { | 
|  | final ProcessResult result = Process.runSync(dart, <String>[ | 
|  | 'compile', | 
|  | 'js', | 
|  | '--libraries-spec=$librariesSpec', | 
|  | '-Ddart.vm.product=true', | 
|  | '-o', | 
|  | dillPath, | 
|  | '--packages=$packageConfig', | 
|  | '--cfe-only', | 
|  | dartSource, | 
|  | ]); | 
|  | checkProcessResult(result); | 
|  |  | 
|  | resourcesToDispose.add(dillPath); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Equality that casts all [num]'s to [double] before comparing. | 
|  | class Dart2JSDeepCollectionEquality extends DeepCollectionEquality { | 
|  | const Dart2JSDeepCollectionEquality(); | 
|  |  | 
|  | @override | 
|  | bool equals(Object? e1, Object? e2) { | 
|  | if (e1 is num && e2 is num) { | 
|  | return e1.toDouble() == e2.toDouble(); | 
|  | } | 
|  | return super.equals(e1, e2); | 
|  | } | 
|  | } |