customer_test.dart 6.45 KB
Newer Older
1 2 3 4 5 6
// 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.

import 'dart:io';

7
import 'package:collection/collection.dart';
8 9 10 11 12 13 14 15
import 'package:meta/meta.dart';

@immutable
class CustomerTest {
  factory CustomerTest(File testFile) {
    final String errorPrefix = 'Could not parse: ${testFile.path}\n';
    final List<String> contacts = <String>[];
    final List<String> fetch = <String>[];
16
    final List<String> setup = <String>[];
17 18
    final List<Directory> update = <Directory>[];
    final List<String> test = <String>[];
19
    int? iterations;
20 21
    bool hasTests = false;
    for (final String line in testFile.readAsLinesSync().map((String line) => line.trim())) {
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
      if (line.isEmpty || line.startsWith('#')) {
        // Blank line or comment.
        continue;
      }

      final bool isUnknownDirective = _TestDirective.values.firstWhereOrNull((_TestDirective d) => line.startsWith(d.name)) == null;
      if (isUnknownDirective) {
        throw FormatException('${errorPrefix}Unexpected directive:\n$line');
      }

      _maybeAddTestConfig(line, directive: _TestDirective.contact, directiveValues: contacts);
      _maybeAddTestConfig(line, directive: _TestDirective.fetch, directiveValues: fetch);
      _maybeAddTestConfig(line, directive: _TestDirective.setup, directiveValues: setup, platformAgnostic: false);

      final String updatePrefix = _directive(_TestDirective.update);
      if (line.startsWith(updatePrefix)) {
        update.add(Directory(line.substring(updatePrefix.length)));
      }

      final String iterationsPrefix = _directive(_TestDirective.iterations);
      if (line.startsWith(iterationsPrefix)) {
43
        if (iterations != null) {
44
          throw FormatException('Cannot specify "${_TestDirective.iterations.name}" directive multiple times.');
45
        }
46
        iterations = int.parse(line.substring(iterationsPrefix.length));
47
        if (iterations < 1) {
48
          throw FormatException('The "${_TestDirective.iterations.name}" directive must have a positive integer value.');
49
        }
50 51 52
      }

      if (line.startsWith(_directive(_TestDirective.test)) || line.startsWith('${_TestDirective.test.name}.')) {
53 54
        hasTests = true;
      }
55
      _maybeAddTestConfig(line, directive: _TestDirective.test, directiveValues: test, platformAgnostic: false);
56
    }
57

58
    if (contacts.isEmpty) {
59
      throw FormatException('${errorPrefix}No "${_TestDirective.contact.name}" directives specified. At least one contact e-mail address must be specified.');
60
    }
61
    for (final String email in contacts) {
62
      if (!email.contains(_email) || email.endsWith('@example.com')) {
63
        throw FormatException('${errorPrefix}The following e-mail address appears to be an invalid e-mail address: $email');
64
      }
65
    }
66
    if (fetch.isEmpty) {
67
      throw FormatException('${errorPrefix}No "${_TestDirective.fetch.name}" directives specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".');
68 69
    }
    if (fetch.length < 2) {
70
      throw FormatException('${errorPrefix}Only one "${_TestDirective.fetch.name}" directive specified. Two lines are expected: "git clone https://github.com/USERNAME/REPOSITORY.git tests" and "git -C tests checkout HASH".');
71 72
    }
    if (!fetch[0].contains(_fetch1)) {
73
      throw FormatException('${errorPrefix}First "${_TestDirective.fetch.name}" directive does not match expected pattern (expected "git clone https://github.com/USERNAME/REPOSITORY.git tests").');
74 75
    }
    if (!fetch[1].contains(_fetch2)) {
76
      throw FormatException('${errorPrefix}Second "${_TestDirective.fetch.name}" directive does not match expected pattern (expected "git -C tests checkout HASH").');
77 78
    }
    if (update.isEmpty) {
79
      throw FormatException('${errorPrefix}No "${_TestDirective.update.name}" directives specified. At least one directory must be specified. (It can be "." to just upgrade the root of the repository.)');
80 81
    }
    if (!hasTests) {
82
      throw FormatException('${errorPrefix}No "${_TestDirective.test.name}" directives specified. At least one command must be specified to run tests.');
83
    }
84 85 86
    return CustomerTest._(
      List<String>.unmodifiable(contacts),
      List<String>.unmodifiable(fetch),
87
      List<String>.unmodifiable(setup),
88 89
      List<Directory>.unmodifiable(update),
      List<String>.unmodifiable(test),
90
      iterations,
91 92 93
    );
  }

94 95 96 97 98 99 100 101
  const CustomerTest._(
    this.contacts,
    this.fetch,
    this.setup,
    this.update,
    this.tests,
    this.iterations,
  );
102 103 104 105 106 107 108 109

  // (e-mail regexp from HTML standard)
  static final RegExp _email = RegExp(r"^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
  static final RegExp _fetch1 = RegExp(r'^git(?: -c core.longPaths=true)? clone https://github.com/[-a-zA-Z0-9]+/[-_a-zA-Z0-9]+.git tests$');
  static final RegExp _fetch2 = RegExp(r'^git(?: -c core.longPaths=true)? -C tests checkout [0-9a-f]+$');

  final List<String> contacts;
  final List<String> fetch;
110
  final List<String> setup;
111 112
  final List<Directory> update;
  final List<String> tests;
113
  final int? iterations;
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 153 154 155 156 157 158 159 160 161 162 163 164 165

  static void _maybeAddTestConfig(
    String line, {
    required _TestDirective directive,
    required List<String> directiveValues,
    bool platformAgnostic = true,
  }) {
    final List<_PlatformType> platforms = platformAgnostic
        ? <_PlatformType>[_PlatformType.all]
        : _PlatformType.values;
    for (final _PlatformType platform in platforms) {
      final String directiveName = _directive(directive, platform: platform);
      if (line.startsWith(directiveName) && platform.conditionMet) {
        directiveValues.add(line.substring(directiveName.length));
      }
    }
  }

  static String _directive(
    _TestDirective directive, {
    _PlatformType platform = _PlatformType.all,
  }) {
    return switch (platform) {
      _PlatformType.all => '${directive.name}=',
      _ => '${directive.name}.${platform.name}=',
    };
  }
}

enum _PlatformType {
  all,
  windows,
  macos,
  linux,
  posix;

  bool get conditionMet => switch (this) {
        _PlatformType.all => true,
        _PlatformType.windows => Platform.isWindows,
        _PlatformType.macos => Platform.isMacOS,
        _PlatformType.linux => Platform.isLinux,
        _PlatformType.posix => Platform.isLinux || Platform.isMacOS,
      };
}

enum _TestDirective {
  contact,
  fetch,
  setup,
  update,
  test,
  iterations,
166
}