| // Copyright (c) 2018, 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:io'; | |
| import 'package:compiler/src/common/elements.dart'; | |
| import 'package:compiler/src/elements/entities.dart'; | |
| import 'package:compiler/src/js_model/js_world.dart' show JClosedWorld; | |
| import 'package:expect/async_helper.dart'; | |
| import 'package:expect/expect.dart'; | |
| import 'package:compiler/src/util/memory_compiler.dart'; | |
| enum Kind { regular, native, jsInterop } | |
| main() { | |
| asyncTest(() async { | |
| await runTest( | |
| 'tests/web/jsinterop_test.dart', | |
| '', | |
| { | |
| 'Class': Kind.regular, | |
| 'JsInteropClass': Kind.jsInterop, | |
| 'flag': Kind.regular, | |
| 'topLevelField': Kind.regular, | |
| 'topLevelGetter': Kind.regular, | |
| 'topLevelSetter': Kind.regular, | |
| 'topLevelFunction': Kind.regular, | |
| 'externalTopLevelGetter': Kind.jsInterop, | |
| 'externalTopLevelSetter': Kind.jsInterop, | |
| 'externalTopLevelFunction': Kind.jsInterop, | |
| 'externalTopLevelJsInteropGetter': Kind.jsInterop, | |
| 'externalTopLevelJsInteropSetter': Kind.jsInterop, | |
| 'externalTopLevelJsInteropFunction': Kind.jsInterop, | |
| 'Class.generative': Kind.regular, | |
| 'Class.fact': Kind.regular, | |
| 'Class.instanceField': Kind.regular, | |
| 'Class.instanceGetter': Kind.regular, | |
| 'Class.instanceSetter': Kind.regular, | |
| 'Class.instanceMethod': Kind.regular, | |
| 'Class.staticField': Kind.regular, | |
| 'Class.staticGetter': Kind.regular, | |
| 'Class.staticSetter': Kind.regular, | |
| 'Class.staticMethod': Kind.regular, | |
| 'JsInteropClass.externalGenerative': Kind.jsInterop, | |
| 'JsInteropClass.externalFact': Kind.jsInterop, | |
| 'JsInteropClass.externalJsInteropGenerative': Kind.jsInterop, | |
| 'JsInteropClass.externalJsInteropFact': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropMethod': Kind.jsInterop, | |
| }, | |
| skipList: [ | |
| // TODO(34174): Js-interop fields should not be allowed. | |
| '01', | |
| '02', | |
| '03', | |
| '04', | |
| '38', | |
| '42', | |
| '46', | |
| '51', | |
| // TODO(33834): Non-external constructors should not be allowed. | |
| '35', | |
| '37', | |
| // TODO(34345): Non-external static members should not be allowed. | |
| '43', | |
| '44', | |
| '45', | |
| '52', | |
| '53', | |
| '54', | |
| ], | |
| ); | |
| await runTest( | |
| 'tests/web/non_jsinterop_test.dart', | |
| '', | |
| { | |
| 'Class': Kind.regular, | |
| 'JsInteropClass': Kind.jsInterop, | |
| 'flag': Kind.regular, | |
| 'topLevelField': Kind.regular, | |
| 'topLevelGetter': Kind.regular, | |
| 'topLevelSetter': Kind.regular, | |
| 'topLevelFunction': Kind.regular, | |
| 'externalTopLevelJsInteropGetter': Kind.jsInterop, | |
| 'externalTopLevelJsInteropSetter': Kind.jsInterop, | |
| 'externalTopLevelJsInteropFunction': Kind.jsInterop, | |
| 'Class.generative': Kind.regular, | |
| 'Class.fact': Kind.regular, | |
| 'Class.instanceField': Kind.regular, | |
| 'Class.instanceGetter': Kind.regular, | |
| 'Class.instanceSetter': Kind.regular, | |
| 'Class.instanceMethod': Kind.regular, | |
| 'Class.staticField': Kind.regular, | |
| 'Class.staticGetter': Kind.regular, | |
| 'Class.staticSetter': Kind.regular, | |
| 'Class.staticMethod': Kind.regular, | |
| 'JsInteropClass.externalGenerative': Kind.jsInterop, | |
| 'JsInteropClass.externalFact': Kind.jsInterop, | |
| 'JsInteropClass.externalJsInteropGenerative': Kind.jsInterop, | |
| 'JsInteropClass.externalJsInteropFact': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalInstanceJsInteropMethod': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropGetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropSetter': Kind.jsInterop, | |
| 'JsInteropClass.externalStaticJsInteropMethod': Kind.jsInterop, | |
| }, | |
| skipList: [ | |
| // TODO(34174): Js-interop fields should not be allowed. | |
| '01', | |
| '02', | |
| '03', | |
| '04', | |
| '38', | |
| '42', | |
| '46', | |
| '51', | |
| // TODO(33834): Non-external constructors should not be allowed. | |
| '35', | |
| '37', | |
| // TODO(34345): Non-external static members should not be allowed. | |
| '43', | |
| '44', | |
| '45', | |
| '52', | |
| '53', | |
| '54', | |
| ], | |
| ); | |
| await runTest( | |
| 'tests/web/native/native_test.dart', | |
| 'tests/web/native/', | |
| { | |
| 'Class': Kind.regular, | |
| 'NativeClass': Kind.native, | |
| 'topLevelField': Kind.regular, | |
| 'topLevelGetter': Kind.regular, | |
| 'topLevelSetter': Kind.regular, | |
| 'topLevelFunction': Kind.regular, | |
| 'nativeTopLevelGetter': Kind.native, | |
| 'nativeTopLevelSetter': Kind.native, | |
| 'nativeTopLevelFunction': Kind.native, | |
| 'Class.generative': Kind.regular, | |
| 'Class.fact': Kind.regular, | |
| 'Class.instanceField': Kind.regular, | |
| 'Class.instanceGetter': Kind.regular, | |
| 'Class.instanceSetter': Kind.regular, | |
| 'Class.instanceMethod': Kind.regular, | |
| 'Class.staticField': Kind.regular, | |
| 'Class.staticGetter': Kind.regular, | |
| 'Class.staticSetter': Kind.regular, | |
| 'Class.staticMethod': Kind.regular, | |
| 'Class.nativeInstanceGetter': Kind.native, | |
| 'Class.nativeInstanceSetter': Kind.native, | |
| 'Class.nativeInstanceMethod': Kind.native, | |
| 'NativeClass.generative': Kind.regular, | |
| 'NativeClass.fact': Kind.regular, | |
| 'NativeClass.nativeGenerative': Kind.native, | |
| 'NativeClass.nativeFact': Kind.native, | |
| 'NativeClass.instanceField': Kind.native, | |
| 'NativeClass.instanceNamedField': Kind.native, | |
| 'NativeClass.instanceGetter': Kind.regular, | |
| 'NativeClass.instanceSetter': Kind.regular, | |
| 'NativeClass.instanceMethod': Kind.regular, | |
| 'NativeClass.staticField': Kind.regular, | |
| 'NativeClass.staticGetter': Kind.regular, | |
| 'NativeClass.staticSetter': Kind.regular, | |
| 'NativeClass.staticMethod': Kind.regular, | |
| 'NativeClass.nativeInstanceGetter': Kind.native, | |
| 'NativeClass.nativeInstanceSetter': Kind.native, | |
| 'NativeClass.nativeInstanceMethod': Kind.native, | |
| 'NativeClass.nativeStaticGetter': Kind.native, | |
| 'NativeClass.nativeStaticSetter': Kind.native, | |
| 'NativeClass.nativeStaticMethod': Kind.native, | |
| }, | |
| skipList: [ | |
| // External constructors in non-native class | |
| //'08', | |
| //'09', | |
| // External instance members in non-native class | |
| //'22', | |
| //'23', | |
| //'24', | |
| // External static members in non-native class | |
| //'25', | |
| //'26', | |
| //'27', | |
| // External instance members in native class | |
| //'36', | |
| //'37', | |
| //'38', | |
| // External static members in native class | |
| //'39', | |
| //'40', | |
| //'41', | |
| ], | |
| ); | |
| }); | |
| } | |
| runTest( | |
| String fileName, | |
| String location, | |
| Map<String, Kind> expectations, { | |
| List<String> skipList = const <String>[], | |
| }) async { | |
| print('--------------------------------------------------------------------'); | |
| print('Testing $fileName'); | |
| print('--------------------------------------------------------------------'); | |
| String test = File(fileName).readAsStringSync(); | |
| List<String> commonLines = <String>[]; | |
| Map<String, SubTest> subTests = <String, SubTest>{}; | |
| int lineIndex = 0; | |
| for (String line in test.split('\n')) { | |
| int index = line.indexOf('//#'); | |
| if (index != -1) { | |
| String prefix = line.substring(0, index); | |
| String suffix = line.substring(index + 3); | |
| String name = suffix.substring(0, suffix.indexOf((':'))).trim(); | |
| SubTest subTest = subTests.putIfAbsent(name, () => SubTest()); | |
| subTest.lines[lineIndex] = line; | |
| int commentIndex = prefix.indexOf('// '); | |
| if (commentIndex != -1) { | |
| String combinedErrors = prefix.substring(commentIndex + 3); | |
| for (String error in combinedErrors.split(',')) { | |
| subTest.expectedErrors.add(error.trim()); | |
| } | |
| } | |
| commonLines.add(''); | |
| } else { | |
| commonLines.add(line); | |
| } | |
| lineIndex++; | |
| } | |
| String path = '${location}main.dart'; | |
| Uri entryPoint = Uri.parse('memory:$path'); | |
| await runPositiveTest(entryPoint, { | |
| path: commonLines.join('\n'), | |
| }, expectations); | |
| for (String name in subTests.keys) { | |
| if (!skipList.contains(name)) { | |
| SubTest subTest = subTests[name]!; | |
| await runNegativeTest(subTest, entryPoint, { | |
| path: subTest.generateCode(commonLines), | |
| }); | |
| } | |
| } | |
| } | |
| runPositiveTest( | |
| Uri entryPoint, | |
| Map<String, String> sources, | |
| Map<String, Kind> expectations, | |
| ) async { | |
| CompilationResult result = await runCompiler( | |
| entryPoint: entryPoint, | |
| memorySourceFiles: sources, | |
| ); | |
| Expect.isTrue(result.isSuccess); | |
| JClosedWorld closedWorld = result.compiler!.backendClosedWorldForTesting!; | |
| ElementEnvironment elementEnvironment = closedWorld.elementEnvironment; | |
| void checkClass( | |
| ClassEntity cls, { | |
| bool isNative = false, | |
| bool isJsInterop = false, | |
| }) { | |
| if (isJsInterop) { | |
| isNative = true; | |
| } | |
| Expect.equals( | |
| isJsInterop, | |
| closedWorld.nativeData.isJsInteropClass(cls), | |
| "Unexpected js interop class result for $cls.", | |
| ); | |
| Expect.equals( | |
| isNative, | |
| closedWorld.nativeData.isNativeClass(cls), | |
| "Unexpected native class result for $cls.", | |
| ); | |
| if (isJsInterop) { | |
| Expect.isTrue( | |
| closedWorld.nativeData.isJsInteropLibrary(cls.library), | |
| "Unexpected js interop library result for ${cls.library}.", | |
| ); | |
| } | |
| } | |
| void checkMember( | |
| MemberEntity member, { | |
| bool isNative = false, | |
| bool isJsInterop = false, | |
| }) { | |
| if (isJsInterop) { | |
| isNative = true; | |
| } | |
| Expect.equals( | |
| isJsInterop, | |
| closedWorld.nativeData.isJsInteropMember(member), | |
| "Unexpected js interop member result for $member.", | |
| ); | |
| Expect.equals( | |
| isNative, | |
| closedWorld.nativeData.isNativeMember(member), | |
| "Unexpected native member result for $member.", | |
| ); | |
| if (isJsInterop) { | |
| Expect.isTrue( | |
| closedWorld.nativeData.isJsInteropLibrary(member.library), | |
| "Unexpected js interop library result for ${member.library}.", | |
| ); | |
| } | |
| } | |
| elementEnvironment.forEachLibraryMember(elementEnvironment.mainLibrary!, ( | |
| MemberEntity member, | |
| ) { | |
| if (member == elementEnvironment.mainFunction) return; | |
| Kind? kind = expectations.remove(member.name); | |
| Expect.isNotNull(kind, "No expectations for $member"); | |
| checkMember( | |
| member, | |
| isNative: kind == Kind.native, | |
| isJsInterop: kind == Kind.jsInterop, | |
| ); | |
| }); | |
| elementEnvironment.forEachClass(elementEnvironment.mainLibrary!, ( | |
| ClassEntity cls, | |
| ) { | |
| Kind? kind = expectations.remove(cls.name); | |
| Expect.isNotNull(kind, "No expectations for $cls"); | |
| checkClass( | |
| cls, | |
| isNative: kind == Kind.native, | |
| isJsInterop: kind == Kind.jsInterop, | |
| ); | |
| checkClassMember(MemberEntity member) { | |
| Kind? kind = expectations.remove('${cls.name}.${member.name}'); | |
| Expect.isNotNull(kind, "No expectations for $member"); | |
| checkMember( | |
| member, | |
| isNative: kind == Kind.native, | |
| isJsInterop: kind == Kind.jsInterop, | |
| ); | |
| } | |
| elementEnvironment.forEachConstructor(cls, checkClassMember); | |
| elementEnvironment.forEachLocalClassMember(cls, checkClassMember); | |
| }); | |
| Expect.isTrue(expectations.isEmpty, "Untested expectations: $expectations"); | |
| } | |
| runNegativeTest( | |
| SubTest subTest, | |
| Uri entryPoint, | |
| Map<String, String> sources, | |
| ) async { | |
| DiagnosticCollector collector = DiagnosticCollector(); | |
| CompilationResult result = await runCompiler( | |
| entryPoint: entryPoint, | |
| memorySourceFiles: sources, | |
| diagnosticHandler: collector, | |
| ); | |
| Expect.isFalse( | |
| result.isSuccess, | |
| "Expected compile time error(s) for\n$subTest", | |
| ); | |
| List<String> expected = subTest.expectedErrors | |
| .map((error) => 'MessageKind.' + error) | |
| .toList(); | |
| List<String> actual = collector.errors | |
| .map((error) => error.messageKind.toString()) | |
| .toList(); | |
| expected.sort(); | |
| actual.sort(); | |
| Expect.listEquals( | |
| expected, | |
| actual, | |
| "Unexpected compile time error(s) for\n$subTest", | |
| ); | |
| } | |
| class SubTest { | |
| List<String> expectedErrors = []; | |
| final Map<int, String> lines = <int, String>{}; | |
| String generateCode(List<String> commonLines) { | |
| StringBuffer sb = StringBuffer(); | |
| int i = 0; | |
| while (i < commonLines.length) { | |
| if (lines.containsKey(i)) { | |
| sb.writeln(lines[i]); | |
| } else { | |
| sb.writeln(commonLines[i]); | |
| } | |
| i++; | |
| } | |
| return sb.toString(); | |
| } | |
| @override | |
| String toString() { | |
| return lines.values.join('\n'); | |
| } | |
| } |