// Copyright (c) 2015, 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. /// Tests code generation. /// Runs Dart Dev Compiler on all input in the `codegen` directory and checks /// that the output is what we expected. library dev_compiler.test.codegen_test; import 'dart:io'; import 'package:analyzer/src/generated/engine.dart' show AnalysisContext, AnalysisEngine, Logger; import 'package:analyzer/src/generated/java_engine.dart' show CaughtException; import 'package:args/args.dart'; import 'package:logging/logging.dart' show Level; import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'package:dev_compiler/devc.dart'; import 'package:dev_compiler/src/compiler.dart' show defaultRuntimeFiles; import 'package:dev_compiler/src/options.dart'; import 'testing.dart' show realSdkContext, testDirectory; import 'multitest.dart'; final ArgParser argParser = new ArgParser() ..addOption('dart-sdk', help: 'Dart SDK Path', defaultsTo: null); final inputDir = path.join(testDirectory, 'codegen'); Iterable _findTests(String dir, RegExp filePattern) { var files = new Directory(dir) .listSync() .where((f) => f is File) .map((f) => f.path) .where((p) => p.endsWith('.dart') && filePattern.hasMatch(p)); if (dir != inputDir) { files = files .where((p) => p.endsWith('_test.dart') || p.endsWith('_multi.dart')); } return files; } main(arguments) { if (arguments == null) arguments = []; ArgResults args = argParser.parse(arguments); var filePattern = new RegExp(args.rest.length > 0 ? args.rest[0] : '.'); var compilerMessages = new StringBuffer(); var loggerSub; bool codeCoverage = Platform.environment.containsKey('COVERALLS_TOKEN'); setUp(() { compilerMessages.clear(); loggerSub = setupLogger(Level.CONFIG, compilerMessages.writeln); }); tearDown(() { if (loggerSub != null) { loggerSub.cancel(); loggerSub = null; } }); var expectDir = path.join(inputDir, 'expect'); BatchCompiler createCompiler(AnalysisContext context, {bool checkSdk: false, bool sourceMaps: false, bool closure: false}) { // TODO(jmesserly): add a way to specify flags in the test file, so // they're more self-contained. var runtimeDir = path.join(path.dirname(testDirectory), 'lib', 'runtime'); var options = new CompilerOptions( codegenOptions: new CodegenOptions( outputDir: expectDir, emitSourceMaps: sourceMaps, closure: closure, forceCompile: checkSdk), useColors: false, checkSdk: checkSdk, runtimeDir: runtimeDir, inputBaseDir: inputDir); var reporter = createErrorReporter(context, options); return new BatchCompiler(context, options, reporter: reporter); } bool compile(BatchCompiler compiler, String filePath) { compiler.compileFromUriString(filePath, (String url) { // Write compiler messages to disk. var messagePath = '${path.withoutExtension(url)}.txt'; var file = new File(messagePath); var message = ''' // Messages from compiling ${path.basenameWithoutExtension(url)}.dart $compilerMessages'''; var dir = file.parent; if (!dir.existsSync()) dir.createSync(recursive: true); file.writeAsStringSync(message); compilerMessages.clear(); }); return !compiler.failure; } var multitests = new Set(); { // Expand wacky multitests into a bunch of test files. // We'll compile each one as if it was an input. var languageDir = path.join(inputDir, 'language'); var testFiles = _findTests(languageDir, filePattern); for (var filePath in testFiles) { if (filePath.endsWith('_multi.dart')) continue; var contents = new File(filePath).readAsStringSync(); if (isMultiTest(contents)) { multitests.add(filePath); var tests = new Map(); var outcomes = new Map>(); extractTestsFromMultitest(filePath, contents, tests, outcomes); var filename = path.basenameWithoutExtension(filePath); tests.forEach((name, contents) { new File(path.join(languageDir, '${filename}_${name}_multi.dart')) .writeAsStringSync(contents); }); } } } var batchCompiler = createCompiler(realSdkContext); for (var dir in [null, 'language']) { if (codeCoverage && dir == 'language') continue; group('dartdevc ' + path.join('test', 'codegen', dir), () { var outDir = new Directory(path.join(expectDir, dir)); if (!outDir.existsSync()) outDir.createSync(recursive: true); var testFiles = _findTests(path.join(inputDir, dir), filePattern); for (var filePath in testFiles) { if (multitests.contains(filePath)) continue; var filename = path.basenameWithoutExtension(filePath); test('$filename.dart', () { // TODO(jmesserly): this was added to get some coverage of source maps // and closure annotations. // We need a more comprehensive strategy to test them. var sourceMaps = filename == 'map_keys'; var closure = filename == 'closure'; var success; // TODO(vsm): Is it okay to reuse the same context here? If there is // overlap between test files, we may need separate ones for each // compiler. var compiler = (sourceMaps || closure) ? createCompiler(realSdkContext, sourceMaps: sourceMaps, closure: closure) : batchCompiler; success = compile(compiler, filePath); var outFile = new File(path.join(outDir.path, '$filename.js')); expect(!success || outFile.existsSync(), true, reason: '${outFile.path} was created if compilation succeeds'); }); } }); } if (codeCoverage) { group('sdk', () { // The analyzer does not bubble exception messages for certain internal // dart:* library failures, such as failing to find // "_internal/libraries.dart". Instead it produces an opaque "failed to // instantiate dart:core" message. To remedy this we hook up an analysis // logger that prints these messages. var savedLogger; setUp(() { savedLogger = AnalysisEngine.instance.logger; AnalysisEngine.instance.logger = new PrintLogger(); }); tearDown(() { AnalysisEngine.instance.logger = savedLogger; }); test('devc dart:core', () { var testSdkContext = createAnalysisContextWithSources( new SourceResolverOptions( dartSdkPath: path.join(testDirectory, '..', 'tool', 'generated_sdk'))); // Get the test SDK. We use a checked in copy so test expectations can // be generated against a specific SDK version. var compiler = createCompiler(testSdkContext, checkSdk: true); compile(compiler, 'dart:core'); var outFile = new File(path.join(expectDir, 'dart/core.js')); expect(outFile.existsSync(), true, reason: '${outFile.path} was created for dart:core'); }); }); } var expectedRuntime = defaultRuntimeFiles.map((f) => 'dev_compiler/runtime/$f'); test('devc jscodegen sunflower.html', () { var filePath = path.join(inputDir, 'sunflower', 'sunflower.html'); var success = compile(batchCompiler, filePath); var expectedFiles = ['sunflower.html', 'sunflower.js',]; for (var filepath in expectedFiles) { var outFile = new File(path.join(expectDir, 'sunflower', filepath)); expect(outFile.existsSync(), success, reason: '${outFile.path} was created iff compilation succeeds'); } }); test('devc jscodegen html_input.html', () { var filePath = path.join(inputDir, 'html_input.html'); var success = compile(batchCompiler, filePath); var expectedFiles = [ 'html_input.html', 'dir/html_input_a.js', 'dir/html_input_b.js', 'dir/html_input_c.js', 'dir/html_input_d.js', 'dir/html_input_e.js' ]..addAll(expectedRuntime); for (var filepath in expectedFiles) { var outFile = new File(path.join(expectDir, filepath)); expect(outFile.existsSync(), success, reason: '${outFile.path} was created iff compilation succeeds'); } }); } /// An implementation of analysis engine's [Logger] that prints. class PrintLogger implements Logger { @override void logError(String message, [CaughtException exception]) { print('[AnalysisEngine] error $message $exception'); } @override void logError2(String message, Object exception) { print('[AnalysisEngine] error $message $exception'); } void logInformation(String message, [CaughtException exception]) {} void logInformation2(String message, Object exception) {} }