| // 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:convert'; | |
| import 'dart:io' hide Platform; | |
| import 'package:collection/collection.dart'; | |
| import 'package:file/file.dart' as fs; | |
| import 'package:file/memory.dart'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:process/process.dart'; | |
| import '../suite_runners/run_flutter_packages_tests.dart'; | |
| import '../utils.dart'; | |
| import 'common.dart'; | |
| /// Fails a test if the exit code of `result` is not the expected value. This | |
| /// is favored over `expect(result.exitCode, expectedExitCode)` because this | |
| /// will include the process result's stdio in the failure message. | |
| void expectExitCode(ProcessResult result, int expectedExitCode) { | |
| if (result.exitCode != expectedExitCode) { | |
| fail( | |
| 'Process ${result.pid} exited with the wrong exit code.\n' | |
| '\n' | |
| 'EXPECTED: exit code $expectedExitCode\n' | |
| 'ACTUAL: exit code ${result.exitCode}\n' | |
| '\n' | |
| 'STDOUT:\n' | |
| '${result.stdout}\n' | |
| 'STDERR:\n' | |
| '${result.stderr}', | |
| ); | |
| } | |
| } | |
| void main() { | |
| group('verifyVersion()', () { | |
| late MemoryFileSystem fileSystem; | |
| setUp(() { | |
| fileSystem = MemoryFileSystem.test(); | |
| }); | |
| test('passes for valid version strings', () async { | |
| const valid_versions = <String>[ | |
| '1.2.3', | |
| '12.34.56', | |
| '1.2.3.pre.1', | |
| '1.2.3-4.5.pre', | |
| '1.2.3-5.0.pre.12', | |
| ]; | |
| for (final version in valid_versions) { | |
| final fs.File file = fileSystem.file('version'); | |
| file.writeAsStringSync(jsonEncode(<String, String>{'flutterVersion': version})); | |
| await expectLater( | |
| Version.resolveFile(file), | |
| completion(isA<VersionOk>()), | |
| reason: '$version is valid but verifyVersionFile said it was bad', | |
| ); | |
| } | |
| }); | |
| test('fails for invalid version strings', () async { | |
| const invalid_versions = <String>[ | |
| '1.2.3.4', | |
| '1.2.3.', | |
| '1.2.pre.1', | |
| '1.2.3-pre.1', | |
| '1.2.3-pre.1+hotfix.1', | |
| ' 1.2.3', | |
| '1.2.3-hotfix.1', | |
| ]; | |
| for (final version in invalid_versions) { | |
| final fs.File file = fileSystem.file('version'); | |
| file.writeAsStringSync(jsonEncode(<String, String>{'flutterVersion': version})); | |
| await expectLater( | |
| Version.resolveFile(file), | |
| completion( | |
| isA<VersionError>().having( | |
| (VersionError e) => e.error, | |
| 'error', | |
| 'The version logic generated an invalid version string: "$version".', | |
| ), | |
| ), | |
| reason: '$version is invalid but verifyVersionFile said it was fine', | |
| ); | |
| } | |
| }); | |
| }); | |
| group('flutter/packages version', () { | |
| final memoryFileSystem = MemoryFileSystem(); | |
| final fs.File packagesVersionFile = memoryFileSystem.file( | |
| path.join('bin', 'internal', 'flutter_packages.version'), | |
| ); | |
| const kSampleHash = '592b5b27431689336fa4c721a099eedf787aeb56'; | |
| setUpAll(() { | |
| packagesVersionFile.createSync(recursive: true); | |
| }); | |
| test('commit hash', () async { | |
| packagesVersionFile.writeAsStringSync(kSampleHash); | |
| final String actualHash = await getFlutterPackagesVersion( | |
| flutterRoot: flutterRoot, | |
| fileSystem: memoryFileSystem, | |
| packagesVersionFile: packagesVersionFile.path, | |
| ); | |
| expect(actualHash, kSampleHash); | |
| }); | |
| test('commit hash with newlines', () async { | |
| packagesVersionFile.writeAsStringSync('\n$kSampleHash\n'); | |
| final String actualHash = await getFlutterPackagesVersion( | |
| flutterRoot: flutterRoot, | |
| fileSystem: memoryFileSystem, | |
| packagesVersionFile: packagesVersionFile.path, | |
| ); | |
| expect(actualHash, kSampleHash); | |
| }); | |
| }); | |
| group('test.dart script', () { | |
| const ProcessManager processManager = LocalProcessManager(); | |
| Future<ProcessResult> runScript([ | |
| Map<String, String>? environment, | |
| List<String> otherArgs = const <String>[], | |
| ]) async { | |
| final String dart = path.absolute( | |
| path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', 'dart'), | |
| ); | |
| final ProcessResult scriptProcess = processManager.runSync(<String>[ | |
| dart, | |
| 'test.dart', | |
| ...otherArgs, | |
| ], environment: environment); | |
| return scriptProcess; | |
| } | |
| test('subshards tests correctly', () async { | |
| // When updating this test, try to pick shard numbers that ensure we're checking | |
| // that unequal test distributions don't miss tests. | |
| ProcessResult result = await runScript(<String, String>{ | |
| 'SHARD': kTestHarnessShardName, | |
| 'SUBSHARD': '1_3', | |
| }); | |
| expectExitCode(result, 0); | |
| expect(result.stdout, contains('Selecting subshard 1 of 3 (tests 1-3 of 9)')); | |
| result = await runScript(<String, String>{'SHARD': kTestHarnessShardName, 'SUBSHARD': '3_3'}); | |
| expectExitCode(result, 0); | |
| expect(result.stdout, contains('Selecting subshard 3 of 3 (tests 7-9 of 9)')); | |
| }); | |
| test('exits with code 1 when SUBSHARD index greater than total', () async { | |
| final ProcessResult result = await runScript(<String, String>{ | |
| 'SHARD': kTestHarnessShardName, | |
| 'SUBSHARD': '100_99', | |
| }); | |
| expectExitCode(result, 1); | |
| expect(result.stdout, contains('Invalid subshard name')); | |
| }); | |
| test('exits with code 255 when invalid SUBSHARD name', () async { | |
| final ProcessResult result = await runScript(<String, String>{ | |
| 'SHARD': kTestHarnessShardName, | |
| 'SUBSHARD': 'invalid_name', | |
| }); | |
| expectExitCode(result, 255); | |
| expect(result.stdout, contains('Invalid subshard name')); | |
| }); | |
| test('--dry-run prints every test that would run', () async { | |
| final ProcessResult result = await runScript(<String, String>{}, <String>['--dry-run']); | |
| expectExitCode(result, 0); | |
| expect(result.stdout, contains('|> bin/flutter')); | |
| }, testOn: 'posix'); | |
| }); | |
| test('selectTestsForSubShard distributes tests amongst subshards correctly', () async { | |
| List<int> makeTests(int count) => List<int>.generate(count, (int index) => index); | |
| void testSubsharding(int testCount, int subshardCount) { | |
| String failureReason(String reason) { | |
| return 'Subsharding test failed for testCount=$testCount, subshardCount=$subshardCount.\n' | |
| '$reason'; | |
| } | |
| final List<int> tests = makeTests(testCount); | |
| final subshards = List<List<int>>.generate(subshardCount, (int index) { | |
| final int subShardIndex = index + 1; | |
| final (int start, int end) = selectTestsForSubShard( | |
| testCount: tests.length, | |
| subShardIndex: subShardIndex, | |
| subShardCount: subshardCount, | |
| ); | |
| return tests.sublist(start, end); | |
| }); | |
| final List<int> testedTests = subshards.flattened.toList(); | |
| final deduped = Set<int>.from(subshards.flattened); | |
| expect( | |
| testedTests, | |
| hasLength(deduped.length), | |
| reason: failureReason('Subshards may have had duplicate tests.'), | |
| ); | |
| expect( | |
| testedTests, | |
| unorderedEquals(tests), | |
| reason: failureReason('One or more tests were not assigned to a subshard.'), | |
| ); | |
| final int minimumTestsPerShard = (testCount / subshardCount).floor(); | |
| for (var i = 0; i < subshards.length; i++) { | |
| final int extraTestsInThisShard = subshards[i].length - minimumTestsPerShard; | |
| expect( | |
| extraTestsInThisShard, | |
| isNonNegative, | |
| reason: failureReason( | |
| 'Subsharding uneven. Subshard ${i + 1} had too few tests: ${subshards[i].length}', | |
| ), | |
| ); | |
| expect( | |
| extraTestsInThisShard, | |
| lessThanOrEqualTo(1), | |
| reason: failureReason( | |
| 'Subsharding uneven. Subshard ${i + 1} had too many tests: ${subshards[i].length}', | |
| ), | |
| ); | |
| } | |
| } | |
| testSubsharding(9, 3); | |
| testSubsharding(25, 8); | |
| testSubsharding(30, 15); | |
| }); | |
| } |