blob: 48f3e6aa36996a401bb6ee4807c9868a252a9148 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(https://fxbug.dev/84961): Fix null safety and remove this language version.
// @dart=2.9
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:mockito/mockito.dart';
import 'package:status/status.dart';
import 'package:test/test.dart';
// Mock this because it reads a real life file on disk
class MockDeviceFilenameReader extends Mock implements DeviceFilenameReader {}
// Mock this because it talks to .git
class MockGitStatusChecker extends Mock implements GitStatusChecker {}
// Mock this because it calls `fx gn ...`
class MockGNStatusChecker extends Mock implements GNStatusChecker {}
// Mock this because it checks environment variables
class MockEnvReader extends Mock implements EnvReader {}
void main() {
group('EnvironmentCollector', () {
test('returns default when deviceName file is empty', () async {
// Not setting a return value for `getDeviceName()` means it returns None,
// simulating the Collector not finding a device name file
MockDeviceFilenameReader filenameReader = MockDeviceFilenameReader();
EnvironmentCollector env = EnvironmentCollector();
List<Item> results = await env.collect(filenameReader: filenameReader);
expect(results.length, 1);
});
test('returns specific result when deviceName file is found', () async {
MockDeviceFilenameReader filenameReader = MockDeviceFilenameReader();
EnvironmentCollector env = EnvironmentCollector();
// Setting this return value mimics finding a device name file
when(filenameReader.getDeviceName(envReader: EnvReader.shared))
.thenReturn('asdf');
List<Item> results = await env.collect(filenameReader: filenameReader);
expect(results.length, 2);
expect(results[1].key, 'device_name');
expect(results[1].title, 'Device name');
expect(results[1].value, 'asdf');
expect(results[1].notes, 'set by `fx set-device`');
});
test('returns env result even when deviceName file is present', () async {
MockEnvReader envReader = MockEnvReader();
when(envReader.getEnv('FUCHSIA_DEVICE_NAME'))
.thenReturn('Really good device');
EnvironmentCollector env = EnvironmentCollector();
List<Item> results = await env.collect(envReader: envReader);
expect(results.length, 2);
expect(results[1].key, 'device_name');
expect(results[1].title, 'Device name');
expect(results[1].value, 'Really good device');
expect(results[1].notes, 'set by `fx -d`');
});
});
group('GitCollector', () {
test('returns True when git hashes are the same', () async {
MockGitStatusChecker gitChecker = MockGitStatusChecker();
when(gitChecker.checkStatus()).thenAnswer(
(_) => Future.value(ProcessResult(123, 0, 'asdf\nasdf', '')));
GitCollector gitCollector = GitCollector();
List<Item> results =
await gitCollector.collect(statusChecker: gitChecker);
expect(results.length, 1);
expect(results[0].value, true);
});
test('returns False when git hashes are different', () async {
MockGitStatusChecker gitChecker = MockGitStatusChecker();
when(gitChecker.checkStatus()).thenAnswer(
(_) => Future.value(ProcessResult(123, 0, 'asdf\nnot-asdf', '')));
GitCollector gitCollector = GitCollector();
List<Item> results =
await gitCollector.collect(statusChecker: gitChecker);
expect(results.length, 1);
expect(results[0].value, false);
});
test('returns Null when the git process has a non-zero exit code',
() async {
MockGitStatusChecker gitChecker = MockGitStatusChecker();
when(gitChecker.checkStatus())
.thenAnswer((_) => Future.value(ProcessResult(123, 1, '', '')));
GitCollector gitCollector = GitCollector();
List<Item> results =
await gitCollector.collect(statusChecker: gitChecker);
expect(results, null);
});
});
/*
Helper function to wrap up the process and callback binding tedium
*/
Future<Map> runFxGn(argsGn) {
var args = ['gn', 'format', '--dump-tree=json', '--stdin'];
return Process.start('fx', args).then((Process pr) {
pr.exitCode.then((exitCode) {
if (exitCode != 0) {
throw Exception('Unexpected error running fx gn format:\n'
'exit code $exitCode\n'
'command: fx $args\n'
'input:\n'
'$argsGn');
}
});
// With the `--stdin` option, `gn format --dump-tree` echos the formatted
// GN right after the JSON data. We need to truncate this extra output so
// that the JSON parser doesn't complain.
var data = pr.stdout
.transform(utf8.decoder)
.transform(LineSplitter())
.takeWhile((s) => s != '}')
.join('\n');
pr.stdin
..writeln(argsGn)
..close();
return data.then((d) => jsonDecode('$d}'));
});
}
/*
Helper function to wrap up parsing the json data
*/
Future<BasicGnParser> parseFxGn(argsGn) {
return runFxGn(argsGn).then((data) => BasicGnParser(data['child']));
}
group('GNStatusParser', () {
GNStatusParser parser = GNStatusParser();
test('throws an error on non-zero exit codes', () async {
expect(
() => parser.parseGn(processResult: ProcessResult(123, 1, '', 'asdf')),
throwsA(
'Unexpected error running fx gn: exit code 1\n---- stderr output:\nasdf\n------'),
);
});
test('parses a json output', () async {
var jsonText = '''{"child": [
{"type": "IDENTIFIER", "value": "use_goma"},
{"type": "LITERAL", "value": "true"}
]}
''';
ProcessResult pr = ProcessResult(123, 0, jsonText, '');
List<Item> items = parser.parseGn(processResult: pr);
expect(items.length, 2);
expect(items[0].key, 'goma');
expect(items[1].key, 'release');
});
test('handles variable assignments', () async {
BasicGnParser parser =
await parseFxGn('use_goma = true\nis_debug = false');
expect(parser.assignedVariables['use_goma'], 'true');
expect(parser.assignedVariables['is_debug'], 'false');
});
test('handles import statements', () async {
BasicGnParser parser = await parseFxGn(
'import("//products/core.gni")\nimport("//vendor/google/boards/x64.gni")');
expect(parser.imports[0], '//products/core.gni');
expect(parser.imports[1], '//vendor/google/boards/x64.gni');
});
test('correctly parses direct items', () async {
BasicGnParser parser =
await parseFxGn('universe_package_labels += [ "//bundles:tests" ]');
var items = GNStatusParser()
.collectFromTreeParser(parser)
.where((item) => item.key == 'universe_package_labels');
expect(items.length, 1);
expect(items.first.key, 'universe_package_labels');
expect(items.first.title, 'Universe packages');
expect(items.first.value.length, 1);
expect(items.first.value[0], '//bundles:tests');
});
test('correctly parses calculated variables', () async {
BasicGnParser parser =
await parseFxGn('use_goma = true\nis_debug = false');
var items = GNStatusParser().collectFromTreeParser(parser);
expect(items.length, 2);
expect(items[0].key, 'goma');
expect(items[0].title, 'Goma');
expect(items[0].value, 'enabled');
expect(items[1].key, 'release');
expect(items[1].title, 'Is release?');
expect(items[1].value, 'true');
expect(items[1].notes, '--release argument of `fx set`');
});
});
group('fx gn format', () {
test('parses partial files (useful for other tests)', () async {
var data = await runFxGn('use_goma = true');
expect(data, {
'begin_token': '',
'child': [
{
'child': [
{
'location': {
'begin_column': 1,
'begin_line': 1,
'end_column': 9,
'end_line': 1
},
'type': 'IDENTIFIER',
'value': 'use_goma'
},
{
'location': {
'begin_column': 12,
'begin_line': 1,
'end_column': 16,
'end_line': 1
},
'type': 'LITERAL',
'value': 'true'
}
],
'location': {
'begin_column': 1,
'begin_line': 1,
'end_column': 16,
'end_line': 1
},
'type': 'BINARY',
'value': '='
}
],
'location': {
'begin_column': 1,
'begin_line': 1,
'end_column': 16,
'end_line': 1
},
'result_mode': 'discards_result',
'type': 'BLOCK'
});
});
test('parses well-formed gni file from stdin', () async {
var data = await runFxGn('''import("//products/core.gni")
use_goma = true
goma_dir = "/Users/ldap/goma"
# See: fx args --list=base_package_labels
base_package_labels += []
# See: fx args --list=universe_package_labels
universe_package_labels += [
"//third_party/dart-pkg/pub/io/",
]''');
expect(data, {
'begin_token': '',
'child': [
{
'child': [
{
'begin_token': '(',
'child': [
{
'location': {
'begin_column': 8,
'begin_line': 1,
'end_column': 29,
'end_line': 1
},
'type': 'LITERAL',
'value': '"//products/core.gni"'
}
],
'end': {
'location': {
'begin_column': 29,
'begin_line': 1,
'end_column': 30,
'end_line': 1
},
'type': 'END',
'value': ')'
},
'location': {
'begin_column': 7,
'begin_line': 1,
'end_column': 29,
'end_line': 1
},
'type': 'LIST'
}
],
'location': {
'begin_column': 1,
'begin_line': 1,
'end_column': 29,
'end_line': 1
},
'type': 'FUNCTION',
'value': 'import'
},
{
'child': [
{
'location': {
'begin_column': 1,
'begin_line': 2,
'end_column': 9,
'end_line': 2
},
'type': 'IDENTIFIER',
'value': 'use_goma'
},
{
'location': {
'begin_column': 12,
'begin_line': 2,
'end_column': 16,
'end_line': 2
},
'type': 'LITERAL',
'value': 'true'
}
],
'location': {
'begin_column': 1,
'begin_line': 2,
'end_column': 16,
'end_line': 2
},
'type': 'BINARY',
'value': '='
},
{
'child': [
{
'location': {
'begin_column': 1,
'begin_line': 3,
'end_column': 9,
'end_line': 3
},
'type': 'IDENTIFIER',
'value': 'goma_dir'
},
{
'location': {
'begin_column': 12,
'begin_line': 3,
'end_column': 30,
'end_line': 3
},
'type': 'LITERAL',
'value': '"/Users/ldap/goma"'
}
],
'location': {
'begin_column': 1,
'begin_line': 3,
'end_column': 30,
'end_line': 3
},
'type': 'BINARY',
'value': '='
},
{
'before_comment': ['# See: fx args --list=base_package_labels'],
'child': [
{
'location': {
'begin_column': 1,
'begin_line': 6,
'end_column': 20,
'end_line': 6
},
'type': 'IDENTIFIER',
'value': 'base_package_labels'
},
{
'begin_token': '[',
'child': [],
'end': {
'location': {
'begin_column': 25,
'begin_line': 6,
'end_column': 26,
'end_line': 6
},
'type': 'END',
'value': ']'
},
'location': {
'begin_column': 24,
'begin_line': 6,
'end_column': 25,
'end_line': 6
},
'type': 'LIST'
}
],
'location': {
'begin_column': 1,
'begin_line': 6,
'end_column': 25,
'end_line': 6
},
'type': 'BINARY',
'value': '+='
},
{
'before_comment': ['# See: fx args --list=universe_package_labels'],
'child': [
{
'location': {
'begin_column': 1,
'begin_line': 9,
'end_column': 24,
'end_line': 9
},
'type': 'IDENTIFIER',
'value': 'universe_package_labels'
},
{
'begin_token': '[',
'child': [
{
'location': {
'begin_column': 3,
'begin_line': 10,
'end_column': 35,
'end_line': 10
},
'type': 'LITERAL',
'value': '"//third_party/dart-pkg/pub/io/"'
}
],
'end': {
'location': {
'begin_column': 1,
'begin_line': 11,
'end_column': 2,
'end_line': 11
},
'type': 'END',
'value': ']'
},
'location': {
'begin_column': 28,
'begin_line': 9,
'end_column': 1,
'end_line': 11
},
'type': 'LIST'
}
],
'location': {
'begin_column': 1,
'begin_line': 9,
'end_column': 1,
'end_line': 11
},
'type': 'BINARY',
'value': '+='
}
],
'location': {
'begin_column': 1,
'begin_line': 1,
'end_column': 1,
'end_line': 11
},
'result_mode': 'discards_result',
'type': 'BLOCK'
});
});
});
}