1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright 2016 The Chromium Authors. 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:meta/meta.dart';
import 'package:yaml/yaml.dart';
import 'utils.dart';
/// Loads manifest data from `manifest.yaml` file or from [yaml], if present.
Manifest loadTaskManifest([ String yaml ]) {
final dynamic manifestYaml = yaml == null
? loadYaml(file('manifest.yaml').readAsStringSync())
: loadYamlNode(yaml);
_checkType(manifestYaml is Map, manifestYaml, 'Manifest', 'dictionary');
return _validateAndParseManifest(manifestYaml);
}
/// Contains CI task information.
class Manifest {
Manifest._(this.tasks);
/// CI tasks.
final List<ManifestTask> tasks;
}
/// A CI task.
class ManifestTask {
ManifestTask._({
@required this.name,
@required this.description,
@required this.stage,
@required this.requiredAgentCapabilities,
@required this.isFlaky,
@required this.timeoutInMinutes,
}) {
final String taskName = 'task "$name"';
_checkIsNotBlank(name, 'Task name', taskName);
_checkIsNotBlank(description, 'Task description', taskName);
_checkIsNotBlank(stage, 'Task stage', taskName);
_checkIsNotBlank(requiredAgentCapabilities, 'requiredAgentCapabilities', taskName);
}
/// Task name as it appears on the dashboard.
final String name;
/// A human-readable description of the task.
final String description;
/// The stage this task should run in.
final String stage;
/// Capabilities required of the build agent to be able to perform this task.
final List<String> requiredAgentCapabilities;
/// Whether this test is flaky.
///
/// Flaky tests are not considered when deciding if the build is broken.
final bool isFlaky;
/// An optional custom timeout specified in minutes.
final int timeoutInMinutes;
}
/// Thrown when the manifest YAML is not valid.
class ManifestError extends Error {
ManifestError(this.message);
final String message;
@override
String toString() => '$ManifestError: $message';
}
// There's no good YAML validator, at least not for Dart, so we validate
// manually. It's not too much code and produces good error messages.
Manifest _validateAndParseManifest(Map<String, dynamic> manifestYaml) {
_checkKeys(manifestYaml, 'manifest', const <String>['tasks']);
return new Manifest._(_validateAndParseTasks(manifestYaml['tasks']));
}
List<ManifestTask> _validateAndParseTasks(dynamic tasksYaml) {
_checkType(tasksYaml is Map, tasksYaml, 'Value of "tasks"', 'dictionary');
final List<String> sortedKeys = tasksYaml.keys.toList()..sort();
return sortedKeys.map((dynamic taskName) => _validateAndParseTask(taskName, tasksYaml[taskName])).toList();
}
ManifestTask _validateAndParseTask(dynamic taskName, dynamic taskYaml) {
_checkType(taskName is String, taskName, 'Task name', 'string');
_checkType(taskYaml is Map, taskYaml, 'Value of task "$taskName"', 'dictionary');
_checkKeys(taskYaml, 'Value of task "$taskName"', const <String>[
'description',
'stage',
'required_agent_capabilities',
'flaky',
'timeout_in_minutes',
]);
final dynamic isFlaky = taskYaml['flaky'];
if (isFlaky != null) {
_checkType(isFlaky is bool, isFlaky, 'flaky', 'boolean');
}
final dynamic timeoutInMinutes = taskYaml['timeout_in_minutes'];
if (timeoutInMinutes != null) {
_checkType(timeoutInMinutes is int, timeoutInMinutes, 'timeout_in_minutes', 'integer');
}
final List<String> capabilities = _validateAndParseCapabilities(taskName, taskYaml['required_agent_capabilities']);
return new ManifestTask._(
name: taskName,
description: taskYaml['description'],
stage: taskYaml['stage'],
requiredAgentCapabilities: capabilities,
isFlaky: isFlaky ?? false,
timeoutInMinutes: timeoutInMinutes,
);
}
List<String> _validateAndParseCapabilities(String taskName, dynamic capabilitiesYaml) {
_checkType(capabilitiesYaml is List, capabilitiesYaml, 'required_agent_capabilities', 'list');
for (int i = 0; i < capabilitiesYaml.length; i++) {
final dynamic capability = capabilitiesYaml[i];
_checkType(capability is String, capability, 'required_agent_capabilities[$i]', 'string');
}
return capabilitiesYaml;
}
void _checkType(bool isValid, dynamic value, String variableName, String typeName) {
if (!isValid) {
throw new ManifestError(
'$variableName must be a $typeName but was ${value.runtimeType}: $value',
);
}
}
void _checkIsNotBlank(dynamic value, String variableName, String ownerName) {
if (value == null || value.isEmpty) {
throw new ManifestError('$variableName must not be empty in $ownerName.');
}
}
void _checkKeys(Map<String, dynamic> map, String variableName, List<String> allowedKeys) {
for (String key in map.keys) {
if (!allowedKeys.contains(key)) {
throw new ManifestError(
'Unrecognized property "$key" in $variableName. '
'Allowed properties: ${allowedKeys.join(', ')}');
}
}
}