// 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 /// . /// /// 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().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? runIf; }