| // 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. |
| |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| import 'package:path/path.dart' as p; |
| import 'package:test/test.dart'; |
| import 'package:test_process/test_process.dart'; |
| |
| import '../descriptor.dart' as d; |
| import '../golden_file.dart'; |
| import '../test_pub.dart'; |
| |
| const _commandRunner = 'tool/test-bin/pub_command_runner.dart'; |
| |
| late String snapshot; |
| |
| final logFile = p.join(d.sandbox, cachePath, 'log', 'pub_log.txt'); |
| |
| /// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and appends the output to [buffer]. |
| Future<void> runEmbeddingToBuffer( |
| List<String> args, |
| StringBuffer buffer, { |
| String? workingDirectory, |
| Map<String, String>? environment, |
| dynamic exitCode = 0, |
| }) async { |
| final process = await TestProcess.start( |
| Platform.resolvedExecutable, |
| ['--enable-asserts', snapshot, ...args], |
| environment: { |
| ...getPubTestEnvironment(), |
| ...?environment, |
| }, |
| workingDirectory: workingDirectory, |
| ); |
| await process.shouldExit(exitCode); |
| |
| buffer.writeln([ |
| '\$ $_commandRunner ${args.join(' ')}', |
| ...await process.stdout.rest.map(_filter).toList(), |
| ...await process.stderr.rest.map((e) => '[E] ${_filter(e)}').toList(), |
| ].join('\n')); |
| buffer.write('\n'); |
| } |
| |
| extension on GoldenTestContext { |
| /// Runs `dart tool/test-bin/pub_command_runner.dart [args]` and compare to |
| /// next section in golden file. |
| Future<void> runEmbedding( |
| List<String> args, { |
| String? workingDirectory, |
| Map<String, String>? environment, |
| dynamic exitCode = 0, |
| }) async { |
| final buffer = StringBuffer(); |
| await runEmbeddingToBuffer( |
| args, |
| buffer, |
| workingDirectory: workingDirectory, |
| environment: environment, |
| exitCode: exitCode, |
| ); |
| |
| expectNextSection(buffer.toString()); |
| } |
| } |
| |
| Future<void> main() async { |
| setUpAll(() async { |
| final tempDir = Directory.systemTemp.createTempSync(); |
| snapshot = path.join(tempDir.path, 'command_runner.dart.snapshot'); |
| final r = Process.runSync( |
| Platform.resolvedExecutable, ['--snapshot=$snapshot', _commandRunner]); |
| expect(r.exitCode, 0, reason: r.stderr); |
| }); |
| |
| tearDownAll(() { |
| File(snapshot).parent.deleteSync(recursive: true); |
| }); |
| |
| testWithGolden('run works, though hidden', (ctx) async { |
| await servePackages(); |
| await d.dir(appPath, [ |
| d.pubspec({ |
| 'name': 'myapp', |
| 'environment': { |
| 'sdk': '0.1.2+3', |
| }, |
| }), |
| d.dir('bin', [ |
| d.file('main.dart', ''' |
| import 'dart:io'; |
| main() { |
| print('Hi'); |
| exit(123); |
| } |
| ''') |
| ]), |
| ]).create(); |
| await ctx.runEmbedding( |
| ['pub', 'get'], |
| workingDirectory: d.path(appPath), |
| ); |
| await ctx.runEmbedding( |
| ['pub', 'run', 'bin/main.dart'], |
| exitCode: 123, |
| workingDirectory: d.path(appPath), |
| ); |
| }); |
| |
| testWithGolden( |
| 'logfile is written with --verbose and on unexpected exceptions', |
| (context) async { |
| final server = await servePackages(); |
| server.serve('foo', '1.0.0'); |
| await d.appDir({'foo': 'any'}).create(); |
| |
| // TODO(sigurdm) This logs the entire verbose trace to a golden file. |
| // |
| // This is fragile, and can break for all sorts of small reasons. We think |
| // this might be worth while having to have at least minimal testing of the |
| // verbose stack trace. |
| // |
| // But if you, future contributor, think this test is annoying: feel free to |
| // remove it, or rewrite it to filter out the stack-trace itself, only |
| // testing for creation of the file. |
| // |
| // It is a fragile test, and we acknowledge that it's usefulness can be |
| // debated... |
| await context.runEmbedding( |
| ['pub', '--verbose', 'get'], |
| workingDirectory: d.path(appPath), |
| ); |
| context.expectNextSection( |
| _filter( |
| File(logFile).readAsStringSync(), |
| ), |
| ); |
| await d.dir('empty').create(); |
| await context.runEmbedding( |
| ['pub', 'fail'], |
| workingDirectory: d.path('empty'), |
| exitCode: 1, |
| ); |
| context.expectNextSection( |
| _filter( |
| File(logFile).readAsStringSync(), |
| ), |
| ); |
| }); |
| |
| test('analytics', () async { |
| await servePackages() |
| ..serve('foo', '1.0.0', deps: {'bar': 'any'}) |
| ..serve('bar', '1.0.0'); |
| await d.dir('dep', [ |
| d.pubspec({ |
| 'name': 'dep', |
| 'environment': {'sdk': '>=0.0.0 <3.0.0'} |
| }) |
| ]).create(); |
| final app = d.dir(appPath, [ |
| d.appPubspec({ |
| 'foo': '1.0.0', |
| // The path dependency should not go to analytics. |
| 'dep': {'path': '../dep'} |
| }) |
| ]); |
| await app.create(); |
| |
| final buffer = StringBuffer(); |
| |
| await runEmbeddingToBuffer( |
| ['pub', 'get'], |
| buffer, |
| workingDirectory: app.io.path, |
| environment: {...getPubTestEnvironment(), '_PUB_LOG_ANALYTICS': 'true'}, |
| ); |
| final analytics = buffer |
| .toString() |
| .split('\n') |
| .where((line) => line.startsWith('[E] [analytics]: ')) |
| .map((line) => json.decode(line.substring('[E] [analytics]: '.length))); |
| expect(analytics, { |
| { |
| 'hitType': 'event', |
| 'message': { |
| 'category': 'pub-get', |
| 'action': 'foo', |
| 'label': '1.0.0', |
| 'value': 1, |
| 'cd1': 'direct', |
| 'ni': '1', |
| } |
| }, |
| { |
| 'hitType': 'event', |
| 'message': { |
| 'category': 'pub-get', |
| 'action': 'bar', |
| 'label': '1.0.0', |
| 'value': 1, |
| 'cd1': 'transitive', |
| 'ni': '1', |
| } |
| }, |
| { |
| 'hitType': 'timing', |
| 'message': { |
| 'variableName': 'resolution', |
| 'time': isA<int>(), |
| 'category': 'pub-get', |
| 'label': null |
| } |
| }, |
| }); |
| // Don't write the logs to file on a normal run. |
| expect(File(logFile).existsSync(), isFalse); |
| }); |
| |
| test('`embedding --verbose pub` is verbose', () async { |
| await servePackages(); |
| final buffer = StringBuffer(); |
| await runEmbeddingToBuffer(['--verbose', 'pub', 'logout'], buffer); |
| expect(buffer.toString(), contains('FINE: Pub 0.1.2+3')); |
| }); |
| } |
| |
| String _filter(String input) { |
| return input |
| .replaceAll(p.toUri(d.sandbox).toString(), r'file://$SANDBOX') |
| .replaceAll(d.sandbox, r'$SANDBOX') |
| .replaceAll(Platform.pathSeparator, '/') |
| .replaceAll(Platform.operatingSystem, r'$OS') |
| .replaceAll(globalServer.port.toString(), r'$PORT') |
| .replaceAll( |
| RegExp(r'^Created:(.*)$', multiLine: true), |
| r'Created: $TIME', |
| ) |
| .replaceAll( |
| RegExp(r'Generated by pub on (.*)$', multiLine: true), |
| r'Generated by pub on $TIME', |
| ) |
| .replaceAll( |
| RegExp(r'X-Pub-Session-ID(.*)$', multiLine: true), |
| r'X-Pub-Session-ID: $ID', |
| ) |
| .replaceAll( |
| RegExp(r'took (.*)$', multiLine: true), |
| r'took: $TIME', |
| ) |
| .replaceAll( |
| RegExp(r'date: (.*)$', multiLine: true), |
| r'date: $TIME', |
| ) |
| .replaceAll( |
| RegExp(r'Creating (.*) from stream\.$', multiLine: true), |
| r'Creating $FILE from stream', |
| ) |
| .replaceAll( |
| RegExp(r'Created (.*) from stream\.$', multiLine: true), |
| r'Created $FILE from stream', |
| ) |
| .replaceAll( |
| RegExp(r'Renaming directory $SANDBOX/cache/_temp/(.*?) to', |
| multiLine: true), |
| r'Renaming directory $SANDBOX/cache/_temp/', |
| ) |
| .replaceAll( |
| RegExp(r'Extracting .tar.gz stream to (.*?)$', multiLine: true), |
| r'Extracting .tar.gz stream to $DIR', |
| ) |
| .replaceAll( |
| RegExp(r'Extracted .tar.gz to (.*?)$', multiLine: true), |
| r'Extracted .tar.gz to $DIR', |
| ) |
| .replaceAll( |
| RegExp(r'Reading binary file (.*?)$', multiLine: true), |
| r'Reading binary file $FILE.', |
| ) |
| .replaceAll( |
| RegExp(r'Deleting directory (.*)$', multiLine: true), |
| r'Deleting directory $DIR', |
| ) |
| .replaceAll( |
| RegExp(r'Deleting directory (.*)$', multiLine: true), |
| r'Deleting directory $DIR', |
| ) |
| .replaceAll( |
| RegExp(r'Resolving dependencies finished (.*)$', multiLine: true), |
| r'Resolving dependencies finished ($TIME)', |
| ) |
| .replaceAll( |
| RegExp(r'Created temp directory (.*)$', multiLine: true), |
| r'Created temp directory $DIR', |
| ) |
| .replaceAll( |
| RegExp(r'Renaming directory (.*)$', multiLine: true), |
| r'Renaming directory $A to $B', |
| ) |
| .replaceAll( |
| RegExp(r'"_fetchedAt":"(.*)"}$', multiLine: true), |
| r'"_fetchedAt": "$TIME"}', |
| ) |
| .replaceAll( |
| RegExp(r'"generated": "(.*)",$', multiLine: true), |
| r'"generated": "$TIME",', |
| ) |
| .replaceAll( |
| RegExp(r'( |^)(/|[A-Z]:)(.*)/tool/test-bin/pub_command_runner.dart', |
| multiLine: true), |
| r' tool/test-bin/pub_command_runner.dart', |
| ) |
| .replaceAll( |
| RegExp(r'[ ]{4,}', multiLine: true), |
| r' ', |
| ) |
| .replaceAll( |
| RegExp(r' [\d]+:[\d]+ ', multiLine: true), |
| r' $LINE:$COL ', |
| ) |
| .replaceAll( |
| RegExp(r'Writing \d+ characters', multiLine: true), |
| r'Writing $N characters', |
| ); |
| } |