mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:19:49 +00:00
Update worker mode to use the bazel protos directly instead of json
BUG= R=paulberry@google.com Review URL: https://codereview.chromium.org/1868663002 .
This commit is contained in:
parent
855bb4cdea
commit
92419d092b
3
DEPS
3
DEPS
|
@ -71,6 +71,7 @@ vars = {
|
|||
"ply_rev": "@604b32590ffad5cbb82e4afef1d305512d06ae93",
|
||||
"plugin_tag": "@0.1.0",
|
||||
"pool_tag": "@1.2.1",
|
||||
"protobuf_tag": "@0.5.0+1",
|
||||
"pub_rev": "@c1405b945c6d818c8cfe78334e8d4b11fd913103",
|
||||
"pub_cache_tag": "@v0.1.0",
|
||||
"pub_semver_tag": "@1.2.1",
|
||||
|
@ -225,6 +226,8 @@ deps = {
|
|||
(Var("github_mirror") % "plugin") + Var("plugin_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/pool":
|
||||
(Var("github_mirror") % "pool") + Var("pool_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/protobuf":
|
||||
(Var("github_dartlang") % "dart-protobuf") + Var("protobuf_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/pub_semver":
|
||||
(Var("github_mirror") % "pub_semver") + Var("pub_semver_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/pub":
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
library analyzer_cli.src.build_mode;
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:core' hide Resource;
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart' show CompilationUnit;
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/file_system/file_system.dart';
|
||||
|
@ -30,6 +31,9 @@ import 'package:analyzer_cli/src/driver.dart';
|
|||
import 'package:analyzer_cli/src/error_formatter.dart';
|
||||
import 'package:analyzer_cli/src/options.dart';
|
||||
|
||||
import 'message_grouper.dart';
|
||||
import 'worker_protocol.pb.dart';
|
||||
|
||||
/**
|
||||
* Analyzer used when the "--build-mode" option is supplied.
|
||||
*/
|
||||
|
@ -283,29 +287,19 @@ class BuildMode {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that every worker related data object has.
|
||||
*/
|
||||
abstract class WorkDataObject {
|
||||
/**
|
||||
* Translate the data in this class into a JSON map.
|
||||
*/
|
||||
Map<String, Object> toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection between a worker and input / output.
|
||||
*/
|
||||
abstract class WorkerConnection {
|
||||
/**
|
||||
* Read a new line. Block until a line is read. Return `null` if EOF.
|
||||
* Read a new [WorkRequest]. Returns [null] when there are no more requests.
|
||||
*/
|
||||
String readLineSync();
|
||||
WorkRequest readRequest();
|
||||
|
||||
/**
|
||||
* Write the given [json] as a new line to the output.
|
||||
* Write the given [response] as bytes to the output.
|
||||
*/
|
||||
void writeJson(Map<String, Object> json);
|
||||
void writeResponse(WorkResponse response);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,8 +316,11 @@ class WorkerLoop {
|
|||
|
||||
WorkerLoop(this.connection);
|
||||
|
||||
factory WorkerLoop.std() {
|
||||
WorkerConnection connection = new _StdWorkerConnection();
|
||||
factory WorkerLoop.std({io.Stdin stdinStream, io.Stdout stdoutStream}) {
|
||||
stdinStream ??= io.stdin;
|
||||
stdoutStream ??= io.stdout;
|
||||
WorkerConnection connection =
|
||||
new StdWorkerConnection(stdinStream, stdoutStream);
|
||||
return new WorkerLoop(connection);
|
||||
}
|
||||
|
||||
|
@ -339,7 +336,7 @@ class WorkerLoop {
|
|||
*/
|
||||
bool performSingle() {
|
||||
try {
|
||||
WorkRequest request = _readRequest();
|
||||
WorkRequest request = connection.readRequest();
|
||||
if (request == null) {
|
||||
return true;
|
||||
}
|
||||
|
@ -351,11 +348,15 @@ class WorkerLoop {
|
|||
// Analyze and respond.
|
||||
analyze(options);
|
||||
String msg = _getErrorOutputBuffersText();
|
||||
_writeResponse(new WorkResponse(EXIT_CODE_OK, msg));
|
||||
connection.writeResponse(new WorkResponse()
|
||||
..exitCode = EXIT_CODE_OK
|
||||
..output = msg);
|
||||
} catch (e, st) {
|
||||
String msg = _getErrorOutputBuffersText();
|
||||
msg += '$e \n $st';
|
||||
_writeResponse(new WorkResponse(EXIT_CODE_ERROR, msg));
|
||||
connection.writeResponse(new WorkResponse()
|
||||
..exitCode = EXIT_CODE_ERROR
|
||||
..output = msg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -389,182 +390,34 @@ class WorkerLoop {
|
|||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a new [WorkRequest]. Return `null` if EOF.
|
||||
* Throw [ArgumentError] if cannot be parsed.
|
||||
*/
|
||||
WorkRequest _readRequest() {
|
||||
String line = connection.readLineSync();
|
||||
if (line == null) {
|
||||
return null;
|
||||
}
|
||||
Object json = JSON.decode(line);
|
||||
if (json is Map) {
|
||||
return new WorkRequest.fromJson(json);
|
||||
} else {
|
||||
throw new ArgumentError('The request line is not a JSON object: $line');
|
||||
}
|
||||
}
|
||||
|
||||
void _writeResponse(WorkResponse response) {
|
||||
Map<String, Object> json = response.toJson();
|
||||
connection.writeJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input file.
|
||||
*/
|
||||
class WorkInput implements WorkDataObject {
|
||||
final String path;
|
||||
final List<int> digest;
|
||||
|
||||
WorkInput(this.path, this.digest);
|
||||
|
||||
factory WorkInput.fromJson(Map<String, Object> json) {
|
||||
// Parse path.
|
||||
Object path2 = json['path'];
|
||||
if (path2 == null) {
|
||||
throw new ArgumentError('The field "path" is missing.');
|
||||
}
|
||||
if (path2 is! String) {
|
||||
throw new ArgumentError('The field "path" must be a string.');
|
||||
}
|
||||
// Parse digest.
|
||||
List<int> digest = const <int>[];
|
||||
{
|
||||
Object digestJson = json['digest'];
|
||||
if (digestJson != null) {
|
||||
if (digestJson is List && digestJson.every((e) => e is int)) {
|
||||
digest = digestJson;
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
'The field "digest" should be a list of int.');
|
||||
}
|
||||
}
|
||||
}
|
||||
// OK
|
||||
return new WorkInput(path2, digest);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object> toJson() {
|
||||
Map<String, Object> json = <String, Object>{};
|
||||
if (path != null) {
|
||||
json['path'] = path;
|
||||
}
|
||||
if (digest != null) {
|
||||
json['digest'] = digest;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single work unit that Bazel sends to the worker.
|
||||
*/
|
||||
class WorkRequest implements WorkDataObject {
|
||||
/**
|
||||
* Command line arguments for this request.
|
||||
*/
|
||||
final List<String> arguments;
|
||||
|
||||
/**
|
||||
* Input files that the worker is allowed to read during execution of this
|
||||
* request.
|
||||
*/
|
||||
final List<WorkInput> inputs;
|
||||
|
||||
WorkRequest(this.arguments, this.inputs);
|
||||
|
||||
factory WorkRequest.fromJson(Map<String, Object> json) {
|
||||
// Parse arguments.
|
||||
List<String> arguments = const <String>[];
|
||||
{
|
||||
Object argumentsJson = json['arguments'];
|
||||
if (argumentsJson != null) {
|
||||
if (argumentsJson is List && argumentsJson.every((e) => e is String)) {
|
||||
arguments = argumentsJson;
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
'The field "arguments" should be a list of strings.');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse inputs.
|
||||
List<WorkInput> inputs = const <WorkInput>[];
|
||||
{
|
||||
Object inputsJson = json['inputs'];
|
||||
if (inputsJson != null) {
|
||||
if (inputsJson is List &&
|
||||
inputsJson.every((e) {
|
||||
return e is Map && e.keys.every((key) => key is String);
|
||||
})) {
|
||||
inputs = inputsJson
|
||||
.map((Map input) => new WorkInput.fromJson(input))
|
||||
.toList();
|
||||
} else {
|
||||
throw new ArgumentError(
|
||||
'The field "inputs" should be a list of objects.');
|
||||
}
|
||||
}
|
||||
}
|
||||
// No inputs.
|
||||
if (arguments.isEmpty && inputs.isEmpty) {
|
||||
throw new ArgumentError('Both "arguments" and "inputs" cannot be empty.');
|
||||
}
|
||||
// OK
|
||||
return new WorkRequest(arguments, inputs);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Object> toJson() {
|
||||
Map<String, Object> json = <String, Object>{};
|
||||
if (arguments != null) {
|
||||
json['arguments'] = arguments;
|
||||
}
|
||||
if (inputs != null) {
|
||||
json['inputs'] = inputs.map((input) => input.toJson()).toList();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Result that the worker sends back to Bazel when it finished its work on a
|
||||
* [WorkRequest] message.
|
||||
*/
|
||||
class WorkResponse implements WorkDataObject {
|
||||
final int exitCode;
|
||||
final String output;
|
||||
|
||||
WorkResponse(this.exitCode, this.output);
|
||||
|
||||
@override
|
||||
Map<String, Object> toJson() {
|
||||
Map<String, Object> json = <String, Object>{};
|
||||
if (exitCode != null) {
|
||||
json['exit_code'] = exitCode;
|
||||
}
|
||||
if (output != null) {
|
||||
json['output'] = output;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of [WorkerConnection] that works with stdio.
|
||||
*/
|
||||
class _StdWorkerConnection implements WorkerConnection {
|
||||
class StdWorkerConnection implements WorkerConnection {
|
||||
final MessageGrouper _messageGrouper;
|
||||
final io.Stdout _stdoutStream;
|
||||
|
||||
StdWorkerConnection(io.Stdin stdinStream, this._stdoutStream)
|
||||
: _messageGrouper = new MessageGrouper(stdinStream);
|
||||
|
||||
@override
|
||||
String readLineSync() {
|
||||
return io.stdin.readLineSync();
|
||||
WorkRequest readRequest() {
|
||||
var buffer = _messageGrouper.next;
|
||||
if (buffer == null) return null;
|
||||
|
||||
return new WorkRequest.fromBuffer(buffer);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeJson(Map<String, Object> json) {
|
||||
io.stdout.writeln(JSON.encode(json));
|
||||
void writeResponse(WorkResponse response) {
|
||||
var responseBuffer = response.writeToBuffer();
|
||||
|
||||
var writer = new CodedBufferWriter();
|
||||
writer.writeInt32NoTag(responseBuffer.length);
|
||||
writer.writeRawBytes(responseBuffer);
|
||||
|
||||
_stdoutStream.add(writer.toBuffer());
|
||||
}
|
||||
}
|
||||
|
|
112
pkg/analyzer_cli/lib/src/message_grouper.dart
Normal file
112
pkg/analyzer_cli/lib/src/message_grouper.dart
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2016, 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 'dart:typed_data';
|
||||
|
||||
/// Groups stdin input into messages by interpreting it as
|
||||
/// base-128 encoded lengths interleaved with raw data.
|
||||
///
|
||||
/// The base-128 encoding is in little-endian order, with the high bit set on
|
||||
/// all bytes but the last. This was chosen since it's the same as the
|
||||
/// base-128 encoding used by protobufs, so it allows a modest amount of code
|
||||
/// reuse at the other end of the protocol.
|
||||
///
|
||||
/// Possible future improvements to consider (should a debugging need arise):
|
||||
/// - Put a magic number at the beginning of the stream.
|
||||
/// - Use a guard byte between messages to sanity check that the encoder and
|
||||
/// decoder agree on the encoding of lengths.
|
||||
class MessageGrouper {
|
||||
final _state = new _MessageGrouperState();
|
||||
final Stdin _stdin;
|
||||
|
||||
MessageGrouper(this._stdin);
|
||||
|
||||
/// Blocks until the next full message is received, and then returns it.
|
||||
///
|
||||
/// Returns null at end of file.
|
||||
List<int> get next {
|
||||
var message;
|
||||
while (message == null) {
|
||||
var nextByte = _stdin.readByteSync();
|
||||
if (nextByte == -1) return null;
|
||||
message = _state.handleInput(nextByte);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/// State held by the [MessageGrouper] while waiting for additional data to
|
||||
/// arrive.
|
||||
class _MessageGrouperState {
|
||||
/// `true` means we are waiting to receive bytes of base-128 encoded length.
|
||||
/// Some bytes of length may have been received already.
|
||||
///
|
||||
/// `false` means we are waiting to receive more bytes of message data. Some
|
||||
/// bytes of message data may have been received already.
|
||||
bool waitingForLength = true;
|
||||
|
||||
/// If [waitingForLength] is `true`, the decoded value of the length bytes
|
||||
/// received so far (if any). If [waitingForLength] is `false`, the decoded
|
||||
/// length that was most recently received.
|
||||
int length = 0;
|
||||
|
||||
/// If [waitingForLength] is `true`, the amount by which the next received
|
||||
/// length byte must be left-shifted; otherwise undefined.
|
||||
int lengthShift = 0;
|
||||
|
||||
/// If [waitingForLength] is `false`, a [Uint8List] which is ready to receive
|
||||
/// message data. Otherwise null.
|
||||
Uint8List message;
|
||||
|
||||
/// If [waitingForLength] is `false`, the number of message bytes that have
|
||||
/// been received so far. Otherwise zero.
|
||||
int numMessageBytesReceived;
|
||||
|
||||
_MessageGrouperState() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/// Handle one byte at a time.
|
||||
///
|
||||
/// Returns a [List<int>] of message bytes if [byte] was the last byte in a
|
||||
/// message, otherwise returns [null].
|
||||
List<int> handleInput(int byte) {
|
||||
if (waitingForLength) {
|
||||
length |= (byte & 0x7f) << lengthShift;
|
||||
if ((byte & 0x80) == 0) {
|
||||
waitingForLength = false;
|
||||
message = new Uint8List(length);
|
||||
if (length == 0) {
|
||||
// There is no message data to wait for, so just go ahead and deliver the
|
||||
// empty message.
|
||||
var messageToReturn = message;
|
||||
reset();
|
||||
return messageToReturn;
|
||||
}
|
||||
} else {
|
||||
lengthShift += 7;
|
||||
}
|
||||
} else {
|
||||
message[numMessageBytesReceived] = byte;
|
||||
numMessageBytesReceived++;
|
||||
if (numMessageBytesReceived == length) {
|
||||
var messageToReturn = message;
|
||||
reset();
|
||||
return messageToReturn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Reset the state so that we are ready to receive the next base-128 encoded
|
||||
/// length.
|
||||
void reset() {
|
||||
waitingForLength = true;
|
||||
length = 0;
|
||||
lengthShift = 0;
|
||||
message = null;
|
||||
numMessageBytesReceived = 0;
|
||||
}
|
||||
}
|
|
@ -263,6 +263,12 @@ class CommandLineOptions {
|
|||
}
|
||||
|
||||
static CommandLineOptions _parse(List<String> args) {
|
||||
// Check if the args are in a file (bazel worker mode).
|
||||
if (args.last.startsWith('@')) {
|
||||
var argsFile = new File(args.last.substring(1));
|
||||
args = argsFile.readAsLinesSync();
|
||||
}
|
||||
|
||||
args = args.expand((String arg) => arg.split('=')).toList();
|
||||
var parser = new CommandLineParser()
|
||||
..addFlag('batch',
|
||||
|
|
133
pkg/analyzer_cli/lib/src/worker_protocol.pb.dart
Executable file
133
pkg/analyzer_cli/lib/src/worker_protocol.pb.dart
Executable file
|
@ -0,0 +1,133 @@
|
|||
///
|
||||
// Generated code. Do not modify.
|
||||
///
|
||||
library blaze.worker_worker_protocol;
|
||||
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
|
||||
class Input extends GeneratedMessage {
|
||||
static final BuilderInfo _i = new BuilderInfo('Input')
|
||||
..a(1, 'path', PbFieldType.OS)
|
||||
..a(2, 'digest', PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
Input() : super();
|
||||
Input.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r);
|
||||
Input.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r);
|
||||
Input clone() => new Input()..mergeFromMessage(this);
|
||||
BuilderInfo get info_ => _i;
|
||||
static Input create() => new Input();
|
||||
static PbList<Input> createRepeated() => new PbList<Input>();
|
||||
static Input getDefault() {
|
||||
if (_defaultInstance == null) _defaultInstance = new _ReadonlyInput();
|
||||
return _defaultInstance;
|
||||
}
|
||||
static Input _defaultInstance;
|
||||
static void $checkItem(Input v) {
|
||||
if (v is !Input) checkItemFailed(v, 'Input');
|
||||
}
|
||||
|
||||
String get path => $_get(0, 1, '');
|
||||
void set path(String v) { $_setString(0, 1, v); }
|
||||
bool hasPath() => $_has(0, 1);
|
||||
void clearPath() => clearField(1);
|
||||
|
||||
List<int> get digest => $_get(1, 2, null);
|
||||
void set digest(List<int> v) { $_setBytes(1, 2, v); }
|
||||
bool hasDigest() => $_has(1, 2);
|
||||
void clearDigest() => clearField(2);
|
||||
}
|
||||
|
||||
class _ReadonlyInput extends Input with ReadonlyMessageMixin {}
|
||||
|
||||
class WorkRequest extends GeneratedMessage {
|
||||
static final BuilderInfo _i = new BuilderInfo('WorkRequest')
|
||||
..p(1, 'arguments', PbFieldType.PS)
|
||||
..pp(2, 'inputs', PbFieldType.PM, Input.$checkItem, Input.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
WorkRequest() : super();
|
||||
WorkRequest.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r);
|
||||
WorkRequest.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r);
|
||||
WorkRequest clone() => new WorkRequest()..mergeFromMessage(this);
|
||||
BuilderInfo get info_ => _i;
|
||||
static WorkRequest create() => new WorkRequest();
|
||||
static PbList<WorkRequest> createRepeated() => new PbList<WorkRequest>();
|
||||
static WorkRequest getDefault() {
|
||||
if (_defaultInstance == null) _defaultInstance = new _ReadonlyWorkRequest();
|
||||
return _defaultInstance;
|
||||
}
|
||||
static WorkRequest _defaultInstance;
|
||||
static void $checkItem(WorkRequest v) {
|
||||
if (v is !WorkRequest) checkItemFailed(v, 'WorkRequest');
|
||||
}
|
||||
|
||||
List<String> get arguments => $_get(0, 1, null);
|
||||
|
||||
List<Input> get inputs => $_get(1, 2, null);
|
||||
}
|
||||
|
||||
class _ReadonlyWorkRequest extends WorkRequest with ReadonlyMessageMixin {}
|
||||
|
||||
class WorkResponse extends GeneratedMessage {
|
||||
static final BuilderInfo _i = new BuilderInfo('WorkResponse')
|
||||
..a(1, 'exitCode', PbFieldType.O3)
|
||||
..a(2, 'output', PbFieldType.OS)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
WorkResponse() : super();
|
||||
WorkResponse.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r);
|
||||
WorkResponse.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY]) : super.fromJson(i, r);
|
||||
WorkResponse clone() => new WorkResponse()..mergeFromMessage(this);
|
||||
BuilderInfo get info_ => _i;
|
||||
static WorkResponse create() => new WorkResponse();
|
||||
static PbList<WorkResponse> createRepeated() => new PbList<WorkResponse>();
|
||||
static WorkResponse getDefault() {
|
||||
if (_defaultInstance == null) _defaultInstance = new _ReadonlyWorkResponse();
|
||||
return _defaultInstance;
|
||||
}
|
||||
static WorkResponse _defaultInstance;
|
||||
static void $checkItem(WorkResponse v) {
|
||||
if (v is !WorkResponse) checkItemFailed(v, 'WorkResponse');
|
||||
}
|
||||
|
||||
int get exitCode => $_get(0, 1, 0);
|
||||
void set exitCode(int v) { $_setUnsignedInt32(0, 1, v); }
|
||||
bool hasExitCode() => $_has(0, 1);
|
||||
void clearExitCode() => clearField(1);
|
||||
|
||||
String get output => $_get(1, 2, '');
|
||||
void set output(String v) { $_setString(1, 2, v); }
|
||||
bool hasOutput() => $_has(1, 2);
|
||||
void clearOutput() => clearField(2);
|
||||
}
|
||||
|
||||
class _ReadonlyWorkResponse extends WorkResponse with ReadonlyMessageMixin {}
|
||||
|
||||
const Input$json = const {
|
||||
'1': 'Input',
|
||||
'2': const [
|
||||
const {'1': 'path', '3': 1, '4': 1, '5': 9},
|
||||
const {'1': 'digest', '3': 2, '4': 1, '5': 12},
|
||||
],
|
||||
};
|
||||
|
||||
const WorkRequest$json = const {
|
||||
'1': 'WorkRequest',
|
||||
'2': const [
|
||||
const {'1': 'arguments', '3': 1, '4': 3, '5': 9},
|
||||
const {'1': 'inputs', '3': 2, '4': 3, '5': 11, '6': '.blaze.worker.Input'},
|
||||
],
|
||||
};
|
||||
|
||||
const WorkResponse$json = const {
|
||||
'1': 'WorkResponse',
|
||||
'2': const [
|
||||
const {'1': 'exit_code', '3': 1, '4': 1, '5': 5},
|
||||
const {'1': 'output', '3': 2, '4': 1, '5': 9},
|
||||
],
|
||||
};
|
||||
|
|
@ -12,6 +12,7 @@ dependencies:
|
|||
linter: ^0.1.10
|
||||
package_config: ^0.1.1
|
||||
plugin: ^0.1.0
|
||||
protobuf: ^0.5.0
|
||||
yaml: ^2.1.2
|
||||
dev_dependencies:
|
||||
test_reflective_loader: '>=0.0.3 <0.1.0'
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'boot_loader_test.dart' as boot_loader;
|
|||
import 'build_mode_test.dart' as build_mode_test;
|
||||
import 'driver_test.dart' as driver;
|
||||
import 'error_test.dart' as error;
|
||||
import 'message_grouper_test.dart' as message_grouper;
|
||||
import 'options_test.dart' as options;
|
||||
import 'package_prefix_test.dart' as package_prefix;
|
||||
import 'perf_report_test.dart' as perf;
|
||||
|
@ -24,6 +25,7 @@ main() {
|
|||
//sdk_ext.main();
|
||||
//strong_mode.main();
|
||||
error.main();
|
||||
message_grouper.main();
|
||||
options.main();
|
||||
perf.main();
|
||||
plugin_manager.main();
|
||||
|
|
|
@ -4,38 +4,59 @@
|
|||
|
||||
library analyzer_cli.test.built_mode;
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:analyzer_cli/src/build_mode.dart';
|
||||
import 'package:analyzer_cli/src/driver.dart';
|
||||
import 'package:analyzer_cli/src/options.dart';
|
||||
import 'package:analyzer_cli/src/worker_protocol.pb.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
import 'package:typed_mock/typed_mock.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveTests(WorkerLoopTest);
|
||||
defineReflectiveTests(WorkInputTest);
|
||||
defineReflectiveTests(WorkRequestTest);
|
||||
}
|
||||
|
||||
typedef void _TestWorkerLoopAnalyze(CommandLineOptions options);
|
||||
|
||||
@reflectiveTest
|
||||
class WorkerLoopTest {
|
||||
final _TestWorkerConnection connection = new _TestWorkerConnection();
|
||||
final TestStdinStream stdinStream = new TestStdinStream();
|
||||
final TestStdoutStream stdoutStream = new TestStdoutStream();
|
||||
_TestWorkerConnection connection;
|
||||
|
||||
WorkerLoopTest() {
|
||||
connection = new _TestWorkerConnection(this.stdinStream, this.stdoutStream);
|
||||
}
|
||||
|
||||
void setUp() {}
|
||||
|
||||
List<int> _serializeProto(GeneratedMessage message) {
|
||||
var buffer = message.writeToBuffer();
|
||||
|
||||
var writer = new CodedBufferWriter();
|
||||
writer.writeInt32NoTag(buffer.length);
|
||||
writer.writeRawBytes(buffer);
|
||||
|
||||
return writer.toBuffer();
|
||||
}
|
||||
|
||||
test_run() {
|
||||
_setInputLine(JSON.encode({
|
||||
'arguments': [
|
||||
var request = new WorkRequest();
|
||||
request.arguments.addAll([
|
||||
'--build-summary-input=/tmp/1.sum',
|
||||
'--build-summary-input=/tmp/2.sum',
|
||||
'package:foo/foo.dart|/inputs/foo/lib/foo.dart',
|
||||
'package:foo/bar.dart|/inputs/foo/lib/bar.dart'
|
||||
],
|
||||
}));
|
||||
'package:foo/bar.dart|/inputs/foo/lib/bar.dart',
|
||||
]);
|
||||
stdinStream.addInputBytes(_serializeProto(request));
|
||||
|
||||
new _TestWorkerLoop(connection, (CommandLineOptions options) {
|
||||
expect(options.buildSummaryInputs,
|
||||
unorderedEquals(['/tmp/1.sum', '/tmp/2.sum']));
|
||||
|
@ -51,235 +72,71 @@ class WorkerLoopTest {
|
|||
errorSink.writeln('errorSink b');
|
||||
}).run();
|
||||
expect(connection.outputList, hasLength(1));
|
||||
expect(connection.outputList[0], {
|
||||
'exit_code': WorkerLoop.EXIT_CODE_OK,
|
||||
'output': allOf(contains('errorSink a'), contains('errorSink a'),
|
||||
contains('outSink a'), contains('outSink b'))
|
||||
});
|
||||
|
||||
var response = connection.outputList[0];
|
||||
expect(response.exitCode, WorkerLoop.EXIT_CODE_OK);
|
||||
expect(
|
||||
response.output,
|
||||
allOf(contains('errorSink a'), contains('errorSink a'),
|
||||
contains('outSink a'), contains('outSink b')));
|
||||
|
||||
// Check that a serialized version was written to std out.
|
||||
expect(stdoutStream.writes, hasLength(1));
|
||||
expect(stdoutStream.writes[0], _serializeProto(response));
|
||||
}
|
||||
|
||||
test_run_invalidOptions() {
|
||||
_setInputLine(JSON.encode({
|
||||
'arguments': ['--unknown-option', '/foo.dart', '/bar.dart',],
|
||||
}));
|
||||
var request = new WorkRequest();
|
||||
request.arguments.addAll(['--unknown-option', '/foo.dart', '/bar.dart']);
|
||||
stdinStream.addInputBytes(_serializeProto(request));
|
||||
new _TestWorkerLoop(connection).run();
|
||||
expect(connection.outputList, hasLength(1));
|
||||
expect(connection.outputList[0],
|
||||
{'exit_code': WorkerLoop.EXIT_CODE_ERROR, 'output': anything});
|
||||
|
||||
var response = connection.outputList[0];
|
||||
expect(response.exitCode, WorkerLoop.EXIT_CODE_ERROR);
|
||||
expect(response.output, anything);
|
||||
}
|
||||
|
||||
test_run_invalidRequest_noArgumentsInputs() {
|
||||
_setInputLine('{}');
|
||||
stdinStream.addInputBytes(_serializeProto(new WorkRequest()));
|
||||
|
||||
new _TestWorkerLoop(connection).run();
|
||||
expect(connection.outputList, hasLength(1));
|
||||
expect(connection.outputList[0],
|
||||
{'exit_code': WorkerLoop.EXIT_CODE_ERROR, 'output': anything});
|
||||
|
||||
var response = connection.outputList[0];
|
||||
expect(response.exitCode, WorkerLoop.EXIT_CODE_ERROR);
|
||||
expect(response.output, anything);
|
||||
}
|
||||
|
||||
test_run_invalidRequest_notJson() {
|
||||
_setInputLine('not a JSON string');
|
||||
test_run_invalidRequest_randomBytes() {
|
||||
stdinStream.addInputBytes([1, 2, 3]);
|
||||
new _TestWorkerLoop(connection).run();
|
||||
expect(connection.outputList, hasLength(1));
|
||||
expect(connection.outputList[0],
|
||||
{'exit_code': WorkerLoop.EXIT_CODE_ERROR, 'output': anything});
|
||||
|
||||
var response = connection.outputList[0];
|
||||
expect(response.exitCode, WorkerLoop.EXIT_CODE_ERROR);
|
||||
expect(response.output, anything);
|
||||
}
|
||||
|
||||
test_run_stopAtEOF() {
|
||||
when(connection.readLineSync()).thenReturnList([null]);
|
||||
stdinStream.addInputBytes([-1]);
|
||||
new _TestWorkerLoop(connection).run();
|
||||
}
|
||||
|
||||
void _setInputLine(String line) {
|
||||
when(connection.readLineSync()).thenReturnList([line, null]);
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class WorkInputTest {
|
||||
test_fromJson() {
|
||||
WorkInput input = new WorkInput.fromJson({
|
||||
'path': '/my/path',
|
||||
'digest': [1, 2, 3, 4, 5]
|
||||
});
|
||||
expect(input.path, '/my/path');
|
||||
expect(input.digest, <int>[1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
||||
test_fromJson_digest_isMissing() {
|
||||
WorkInput input = new WorkInput.fromJson({'path': '/my/path',});
|
||||
expect(input.path, '/my/path');
|
||||
expect(input.digest, <int>[]);
|
||||
}
|
||||
|
||||
test_fromJson_digest_isNotList() {
|
||||
expect(() {
|
||||
new WorkInput.fromJson({'path': '/my/path', 'digest': 0});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_digest_isNotListOfInt() {
|
||||
expect(() {
|
||||
new WorkInput.fromJson({
|
||||
'path': '/my/path',
|
||||
'digest': ['a', 'b', 'c']
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_path_isMissing() {
|
||||
expect(() {
|
||||
new WorkInput.fromJson({
|
||||
'digest': [1, 2, 3, 4, 5]
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_path_isNotString() {
|
||||
expect(() {
|
||||
new WorkInput.fromJson({
|
||||
'path': 0,
|
||||
'digest': [1, 2, 3, 4, 5]
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_toJson() {
|
||||
WorkInput input = new WorkInput('/my/path', <int>[1, 2, 3, 4, 5]);
|
||||
Map<String, Object> json = input.toJson();
|
||||
expect(json, {
|
||||
'path': '/my/path',
|
||||
'digest': [1, 2, 3, 4, 5]
|
||||
});
|
||||
}
|
||||
|
||||
test_toJson_withoutDigest() {
|
||||
WorkInput input = new WorkInput('/my/path', null);
|
||||
Map<String, Object> json = input.toJson();
|
||||
expect(json, {'path': '/my/path'});
|
||||
}
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class WorkRequestTest {
|
||||
test_fromJson() {
|
||||
WorkRequest request = new WorkRequest.fromJson({
|
||||
'arguments': ['--arg1', '--arg2', '--arg3'],
|
||||
'inputs': [
|
||||
{
|
||||
'path': '/my/path1',
|
||||
'digest': [11, 12, 13]
|
||||
},
|
||||
{
|
||||
'path': '/my/path2',
|
||||
'digest': [21, 22, 23]
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(request.arguments, ['--arg1', '--arg2', '--arg3']);
|
||||
expect(request.inputs, hasLength(2));
|
||||
expect(request.inputs[0].path, '/my/path1');
|
||||
expect(request.inputs[0].digest, <int>[11, 12, 13]);
|
||||
expect(request.inputs[1].path, '/my/path2');
|
||||
expect(request.inputs[1].digest, <int>[21, 22, 23]);
|
||||
}
|
||||
|
||||
test_fromJson_arguments_isMissing() {
|
||||
WorkRequest request = new WorkRequest.fromJson({
|
||||
'inputs': [
|
||||
{
|
||||
'path': '/my/path1',
|
||||
'digest': [11, 12, 13]
|
||||
},
|
||||
]
|
||||
});
|
||||
expect(request.arguments, isEmpty);
|
||||
expect(request.inputs, hasLength(1));
|
||||
expect(request.inputs[0].path, '/my/path1');
|
||||
expect(request.inputs[0].digest, <int>[11, 12, 13]);
|
||||
}
|
||||
|
||||
test_fromJson_arguments_isNotList() {
|
||||
expect(() {
|
||||
new WorkRequest.fromJson({'arguments': 0, 'inputs': []});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_arguments_isNotListOfString() {
|
||||
expect(() {
|
||||
new WorkRequest.fromJson({
|
||||
'arguments': [0, 1, 2],
|
||||
'inputs': []
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_inputs_isMissing() {
|
||||
WorkRequest request = new WorkRequest.fromJson({
|
||||
'arguments': ['--arg1', '--arg2', '--arg3'],
|
||||
});
|
||||
expect(request.arguments, ['--arg1', '--arg2', '--arg3']);
|
||||
expect(request.inputs, hasLength(0));
|
||||
}
|
||||
|
||||
test_fromJson_inputs_isNotList() {
|
||||
expect(() {
|
||||
new WorkRequest.fromJson({
|
||||
'arguments': ['--arg1', '--arg2', '--arg3'],
|
||||
'inputs': 0
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_inputs_isNotListOfObject() {
|
||||
expect(() {
|
||||
new WorkRequest.fromJson({
|
||||
'arguments': ['--arg1', '--arg2', '--arg3'],
|
||||
'inputs': [0, 1, 2]
|
||||
});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_fromJson_noArgumentsInputs() {
|
||||
expect(() {
|
||||
new WorkRequest.fromJson({});
|
||||
}, throwsArgumentError);
|
||||
}
|
||||
|
||||
test_toJson() {
|
||||
WorkRequest request = new WorkRequest(<String>[
|
||||
'--arg1',
|
||||
'--arg2',
|
||||
'--arg3'
|
||||
], <WorkInput>[
|
||||
new WorkInput('/my/path1', <int>[11, 12, 13]),
|
||||
new WorkInput('/my/path2', <int>[21, 22, 23])
|
||||
]);
|
||||
Map<String, Object> json = request.toJson();
|
||||
expect(json, {
|
||||
'arguments': ['--arg1', '--arg2', '--arg3'],
|
||||
'inputs': [
|
||||
{
|
||||
'path': '/my/path1',
|
||||
'digest': [11, 12, 13]
|
||||
},
|
||||
{
|
||||
'path': '/my/path2',
|
||||
'digest': [21, 22, 23]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [WorkerConnection] mock.
|
||||
* A [StdWorkerConnection] which records its responses.
|
||||
*/
|
||||
class _TestWorkerConnection extends TypedMock implements WorkerConnection {
|
||||
final outputList = <Map<String, Object>>[];
|
||||
class _TestWorkerConnection extends StdWorkerConnection {
|
||||
final outputList = <WorkResponse>[];
|
||||
|
||||
_TestWorkerConnection(Stdin stdinStream, Stdout stdoutStream)
|
||||
: super(stdinStream, stdoutStream);
|
||||
|
||||
@override
|
||||
void writeJson(Map<String, Object> json) {
|
||||
outputList.add(json);
|
||||
void writeResponse(WorkResponse response) {
|
||||
super.writeResponse(response);
|
||||
outputList.add(response);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
123
pkg/analyzer_cli/test/message_grouper_test.dart
Normal file
123
pkg/analyzer_cli/test/message_grouper_test.dart
Normal file
|
@ -0,0 +1,123 @@
|
|||
// Copyright (c) 2016, 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 'package:analyzer_cli/src/message_grouper.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'utils.dart';
|
||||
|
||||
main() {
|
||||
MessageGrouper messageGrouper;
|
||||
TestStdinStream stdinStream;
|
||||
|
||||
setUp(() {
|
||||
stdinStream = new TestStdinStream();
|
||||
messageGrouper = new MessageGrouper(stdinStream);
|
||||
});
|
||||
|
||||
group('message_grouper', () {
|
||||
/// Check that if the message grouper produces the [expectedOutput] in
|
||||
/// response to the corresponding [input].
|
||||
void check(List<int> input, List<List<int>> expectedOutput) {
|
||||
stdinStream.addInputBytes(input);
|
||||
for (var chunk in expectedOutput) {
|
||||
expect(messageGrouper.next, equals(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
/// Make a simple message having the given [length]
|
||||
List<int> makeMessage(int length) {
|
||||
var result = <int>[];
|
||||
for (int i = 0; i < length; i++) {
|
||||
result.add(i & 0xff);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
test('Empty message', () {
|
||||
check([0], [[]]);
|
||||
});
|
||||
|
||||
test('Short message', () {
|
||||
check([
|
||||
5,
|
||||
10,
|
||||
20,
|
||||
30,
|
||||
40,
|
||||
50
|
||||
], [
|
||||
[10, 20, 30, 40, 50]
|
||||
]);
|
||||
});
|
||||
|
||||
test('Message with 2-byte length', () {
|
||||
var len = 0x155;
|
||||
var msg = makeMessage(len);
|
||||
var encodedLen = [0xd5, 0x02];
|
||||
check([]..addAll(encodedLen)..addAll(msg), [msg]);
|
||||
});
|
||||
|
||||
test('Message with 3-byte length', () {
|
||||
var len = 0x4103;
|
||||
var msg = makeMessage(len);
|
||||
var encodedLen = [0x83, 0x82, 0x01];
|
||||
check([]..addAll(encodedLen)..addAll(msg), [msg]);
|
||||
});
|
||||
|
||||
test('Multiple messages', () {
|
||||
check([
|
||||
2,
|
||||
10,
|
||||
20,
|
||||
2,
|
||||
30,
|
||||
40
|
||||
], [
|
||||
[10, 20],
|
||||
[30, 40]
|
||||
]);
|
||||
});
|
||||
|
||||
test('Empty message at start', () {
|
||||
check([
|
||||
0,
|
||||
2,
|
||||
10,
|
||||
20
|
||||
], [
|
||||
[],
|
||||
[10, 20]
|
||||
]);
|
||||
});
|
||||
|
||||
test('Empty message at end', () {
|
||||
check([
|
||||
2,
|
||||
10,
|
||||
20,
|
||||
0
|
||||
], [
|
||||
[10, 20],
|
||||
[]
|
||||
]);
|
||||
});
|
||||
|
||||
test('Empty message in the middle', () {
|
||||
check([
|
||||
2,
|
||||
10,
|
||||
20,
|
||||
0,
|
||||
2,
|
||||
30,
|
||||
40
|
||||
], [
|
||||
[10, 20],
|
||||
[],
|
||||
[30, 40]
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
library analyzer_cli.test.utils;
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:mirrors';
|
||||
|
||||
|
@ -11,6 +12,7 @@ import 'package:analyzer/analyzer.dart';
|
|||
import 'package:analyzer/src/generated/java_io.dart';
|
||||
import 'package:path/path.dart' as pathos;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:typed_mock/typed_mock.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
/// Gets the test directory in a way that works with
|
||||
|
@ -63,3 +65,37 @@ dynamic withTempDir(fn(String path)) {
|
|||
}
|
||||
|
||||
class _TestUtils {}
|
||||
|
||||
/**
|
||||
* A [Stdin] mock.
|
||||
*/
|
||||
class TestStdinStream extends TypedMock implements Stdin {
|
||||
final pendingBytes = new Queue<int>();
|
||||
|
||||
// Adds all the input bytes to this stream.
|
||||
void addInputBytes(List<int> bytes) {
|
||||
pendingBytes.addAll(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
int readByteSync() {
|
||||
if (pendingBytes.isEmpty) {
|
||||
return -1;
|
||||
} else {
|
||||
return pendingBytes.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Stdout] mock.
|
||||
*/
|
||||
class TestStdoutStream extends TypedMock implements Stdout {
|
||||
final writes = <List<int>>[];
|
||||
|
||||
@override
|
||||
void add(List<int> bytes) {
|
||||
super.add(bytes);
|
||||
writes.add(bytes);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue