Move validation logic for .ci.yaml
to flutter/flutter
. (#161249)
Towards https://github.com/flutter/flutter/issues/160915 (future cleanup: remove the validation from `flutter/cocoon`). This PR effectively duplicates the logic in https://github.com/flutter/cocoon/pull/4137, in `flutter/flutter`. /cc @jtmcdole
This commit is contained in:
parent
f6b9f937da
commit
13ae4cd8d8
161
dev/bots/test/ci_yaml_validation_test.dart
Normal file
161
dev/bots/test/ci_yaml_validation_test.dart
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
@TestOn('vm')
|
||||
library;
|
||||
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import './common.dart';
|
||||
|
||||
void main() {
|
||||
final String flutterRoot = () {
|
||||
io.Directory current = io.Directory.current;
|
||||
while (!io.File(p.join(current.path, 'DEPS')).existsSync()) {
|
||||
if (current.path == current.parent.path) {
|
||||
fail(
|
||||
'Could not find flutter repository root (${io.Directory.current.path} -> ${current.path})',
|
||||
);
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return current.path;
|
||||
}();
|
||||
|
||||
group('framework', () {
|
||||
final List<_CiYamlTarget> targets = _CiYamlTarget.parseAll(p.join(flutterRoot, '.ci.yaml'));
|
||||
|
||||
for (final _CiYamlTarget target in targets) {
|
||||
if (target.runIf == null || target.runIf!.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
printOnFailure(target.span.message('One or more errors occurred validating'));
|
||||
});
|
||||
|
||||
group(target.name, () {
|
||||
test('must include .ci.yaml', () {
|
||||
expect(
|
||||
target.runIf,
|
||||
contains('.ci.yaml'),
|
||||
reason: '.ci.yaml inclusion means changes to the runIfs will trigger presubmit tests.',
|
||||
);
|
||||
});
|
||||
|
||||
test('must include DEPS', () {
|
||||
expect(
|
||||
target.runIf,
|
||||
contains('DEPS'),
|
||||
reason: 'DEPS updates (including the Dart SDK) mean presubmit tests must be run.',
|
||||
);
|
||||
});
|
||||
|
||||
test('must include the engine sources', () {
|
||||
expect(
|
||||
target.runIf,
|
||||
contains('engine/**'),
|
||||
reason: 'Engine updates means framework presubmit tests must be run.',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
group('engine', () {
|
||||
final List<_CiYamlTarget> targets = _CiYamlTarget.parseAll(
|
||||
p.join(flutterRoot, 'engine', 'src', 'flutter', '.ci.yaml'),
|
||||
);
|
||||
|
||||
for (final _CiYamlTarget target in targets) {
|
||||
if (target.runIf == null || target.runIf!.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
printOnFailure(target.span.message('One or more errors occurred validating'));
|
||||
});
|
||||
|
||||
group(target.name, () {
|
||||
test('must include .ci.yaml', () {
|
||||
expect(
|
||||
target.runIf,
|
||||
contains('engine/src/flutter/.ci.yaml'),
|
||||
reason: '.ci.yaml inclusion means changes to the runIfs will trigger presubmit tests.',
|
||||
);
|
||||
});
|
||||
|
||||
test('must include DEPS', () {
|
||||
expect(
|
||||
target.runIf,
|
||||
contains('DEPS'),
|
||||
reason: 'DEPS updates (including the Dart SDK) mean presubmit tests must be run.',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// A minimal representation of an ostensibly well-formatted `.ci.yaml` file.
|
||||
///
|
||||
/// Due to the repository setup, it's not possible to reuse the existing
|
||||
/// specifications of this file, and since the test case is only testing a
|
||||
/// subset of the encoding, this class exposes only that subset.
|
||||
///
|
||||
/// For a discussion leading to this design decision, see
|
||||
/// <https://github.com/flutter/flutter/issues/160915>.
|
||||
///
|
||||
/// See also:
|
||||
/// - [`scheduler.proto`][1], the schema definition of the file format.
|
||||
/// - [`CI_YAML.md`][2], a human-authored description of the file format.
|
||||
/// - [`ci_yaml.dart`][3], where validation is performed (in `flutter/cocoon`).
|
||||
///
|
||||
/// [1]: https://github.com/flutter/cocoon/blob/main/app_dart/lib/src/model/proto/internal/scheduler.proto
|
||||
/// [2]: https://github.com/flutter/cocoon/blob/main/CI_YAML.md
|
||||
/// [3]: https://github.com/flutter/cocoon/blob/main/app_dart/lib/src/model/ci_yaml/ci_yaml.dart
|
||||
final class _CiYamlTarget {
|
||||
_CiYamlTarget({required this.name, required this.span, required this.runIf});
|
||||
|
||||
factory _CiYamlTarget.fromYamlMap(YamlMap map) {
|
||||
return _CiYamlTarget(
|
||||
name: map['name'] as String,
|
||||
span: map.span,
|
||||
runIf: () {
|
||||
final YamlList? runIf = map['runIf'] as YamlList?;
|
||||
if (runIf == null) {
|
||||
return null;
|
||||
}
|
||||
return runIf.cast<String>().toList();
|
||||
}(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Parses a list of targets from the provided `.ci.yaml` file [path].
|
||||
static List<_CiYamlTarget> parseAll(String path) {
|
||||
final YamlDocument yamlDoc = loadYamlDocument(
|
||||
io.File(path).readAsStringSync(),
|
||||
sourceUrl: Uri.parse(path),
|
||||
);
|
||||
|
||||
final YamlMap root = yamlDoc.contents as YamlMap;
|
||||
final YamlList targets = root['targets'] as YamlList;
|
||||
return targets.nodes.map((YamlNode node) {
|
||||
return _CiYamlTarget.fromYamlMap(node as YamlMap);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Name of the target.
|
||||
final String name;
|
||||
|
||||
/// Where the target was parsed at in the `.ci.yaml` file.
|
||||
final SourceSpan span;
|
||||
|
||||
/// Which lines were present in a `runIf` block, if any.
|
||||
final List<String>? runIf;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user