| // Copyright (c) 2021, 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:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'package:dartdev/src/commands/create.dart'; | |
| import 'package:dartdev/src/templates.dart'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:test/test.dart'; | |
| import '../utils.dart'; | |
| void main() { | |
| ensureRunFromSdkBinDart(); | |
| group('create integration', defineCreateTests, timeout: longTimeout); | |
| } | |
| void defineCreateTests() { | |
| // Create tests for each template. | |
| for (String templateId in CreateCommand.legalTemplateIds( | |
| includeDeprecated: true, | |
| )) { | |
| test(templateId, () async { | |
| const projectName = 'template_project'; | |
| final p = project(); | |
| final templateGenerator = getGenerator(templateId)!; | |
| print('$templateId: creating template'); | |
| ProcessResult createResult = await p.run([ | |
| 'create', | |
| '--force', | |
| '--template', | |
| templateId, | |
| projectName, | |
| ]); | |
| printOnFailure(createResult.stdout); | |
| printOnFailure(createResult.stderr); | |
| expect(createResult.exitCode, 0, reason: createResult.stderr); | |
| // Validate that the project analyzes cleanly. | |
| print('$templateId: analyzing generated project'); | |
| ProcessResult analyzeResult = await p.runAnalyze([ | |
| '--fatal-infos', | |
| projectName, | |
| ], workingDir: p.dir.path); | |
| expect(analyzeResult.exitCode, 0, reason: analyzeResult.stdout); | |
| // Validate that the code is well formatted. | |
| print('$templateId: checking formatting'); | |
| ProcessResult formatResult = await p.run([ | |
| 'format', | |
| '--output', | |
| 'none', | |
| '--set-exit-if-changed', | |
| projectName, | |
| ]); | |
| expect(formatResult.exitCode, 0, reason: formatResult.stdout); | |
| // Process the execution instructions provided by the template. | |
| final runCommands = templateGenerator | |
| .getInstallInstructions(projectName, scriptPath: projectName) | |
| .split('\n') | |
| // Remove directory change instructions. | |
| .sublist(1) | |
| .map((command) => command.trim()) | |
| .map((command) { | |
| final commandParts = command.split(' '); | |
| if (command.startsWith('dart ')) { | |
| return commandParts.sublist(1); | |
| } | |
| return commandParts; | |
| }) | |
| .toList(); | |
| print('$templateId: running the following commands:'); | |
| for (final command in runCommands) { | |
| print(' $command'); | |
| } | |
| final isServerTemplate = templateGenerator.categories.contains('server'); | |
| final isWebTemplate = templateGenerator.categories.contains('web'); | |
| final workingDir = path.join(p.dirPath, projectName); | |
| // Execute the templates run instructions. | |
| for (int i = 0; i < runCommands.length; ++i) { | |
| // The last command is always the command to execute the code generated | |
| // by the template. | |
| final isLastCommand = i == runCommands.length - 1; | |
| final command = runCommands[i]; | |
| Process process; | |
| print('[${i + 1} / ${runCommands.length}] Running "$command"...'); | |
| if (isLastCommand && isWebTemplate) { | |
| // The web template uses `webdev` to execute, not `dart`, so don't | |
| // run the test through the project utility method. | |
| process = await Process.start( | |
| path.join( | |
| p.pubCacheBinPath, | |
| Platform.isWindows ? '${command.first}.bat' : command.first, | |
| ), | |
| [ | |
| ...command.sublist(1), | |
| 'web:0', // Allow for binding to a random available port. | |
| ], | |
| workingDirectory: workingDir, | |
| environment: { | |
| 'PUB_CACHE': p.pubCachePath, | |
| 'PATH': | |
| path.dirname(Platform.resolvedExecutable) + | |
| (Platform.isWindows ? ';' : ':') + | |
| Platform.environment['PATH']!, | |
| }, | |
| ); | |
| } else { | |
| process = await p.start(command, workingDir: workingDir); | |
| } | |
| if (isLastCommand && (isServerTemplate || isWebTemplate)) { | |
| final completer = Completer<void>(); | |
| late final StreamSubscription<String> stdoutSub; | |
| late final StreamSubscription<String> stderrSub; | |
| // Listen for well-known output from specific templates to determine | |
| // if they've executed correctly. These templates won't exit on their | |
| // own, so we'll need to terminate the process once we've verified it | |
| // runs correctly. | |
| var hasError = false; | |
| stdoutSub = process.stdout.transform(utf8.decoder).listen((e) { | |
| print('stdout: $e'); | |
| if (e.contains('[SEVERE]') || | |
| (isServerTemplate && e.contains('Server listening on port')) || | |
| (isWebTemplate && e.contains('Built with build_runner'))) { | |
| if (e.contains('[SEVERE]')) { | |
| hasError = true; | |
| } | |
| stderrSub.cancel(); | |
| stdoutSub.cancel(); | |
| process.kill(); | |
| completer.complete(); | |
| } | |
| }); | |
| stderrSub = process.stderr.transform(utf8.decoder).listen((e) { | |
| print('stderr: $e'); | |
| hasError = true; | |
| stderrSub.cancel(); | |
| stdoutSub.cancel(); | |
| process.kill(); | |
| completer.complete(); | |
| }); | |
| await completer.future; | |
| expect(hasError, isFalse, reason: 'Command $command failed.'); | |
| // Since we had to terminate the process manually, we aren't certain | |
| // as to what the exit code will be on all platforms (should be -15 | |
| // for POSIX systems), so we'll just wait for the process to exit | |
| // here. | |
| await process.exitCode; | |
| } else { | |
| final output = <String>[]; | |
| final errors = <String>[]; | |
| process.stdout.transform(utf8.decoder).listen(output.add); | |
| process.stderr.transform(utf8.decoder).listen(errors.add); | |
| // If the sample should exit on its own, it should always result in | |
| // an exit code of 0. | |
| final duration = const Duration(seconds: 60); | |
| final exitCode = await process.exitCode.timeout( | |
| duration, | |
| onTimeout: () { | |
| print('Command $command timed out'); | |
| return -1; | |
| }, | |
| ); | |
| if (exitCode != 0) { | |
| print('Command $command exited with code $exitCode'); | |
| print('Output: \n${output.join('\n')}'); | |
| print('Errors: \n${errors.join('\n')}'); | |
| } | |
| expect(exitCode, 0); | |
| } | |
| print('[${i + 1} / ${runCommands.length}] Done "$command".'); | |
| } | |
| }); | |
| } | |
| } |