| // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| |
| final thisDirectory = path.join('runtime', 'tests', 'concurrency'); |
| final stressTestListJson = path.join(thisDirectory, 'stress_test_list.json'); |
| final generatedTest = path.join(thisDirectory, 'generated_stress_test.dart'); |
| final generatedNnbdTest = |
| path.join(path.join(thisDirectory, 'generated_stress_test_nnbd.dart')); |
| |
| final Map testMap = json.decode(File(stressTestListJson).readAsStringSync()); |
| final testFiles = testMap['non-nnbd'].cast<String>(); |
| final testFilesNnbd = testMap['nnbd'].cast<String>(); |
| final dartfmt = 'tools/sdks/dart-sdk/bin/dartfmt'; |
| |
| main(List<String> args) async { |
| File(generatedNnbdTest) |
| .writeAsStringSync(await generateStressTest(testFilesNnbd)); |
| File(generatedTest).writeAsStringSync(await generateStressTest(testFiles)); |
| } |
| |
| Future<String> generateStressTest(List<String> testFiles) async { |
| testFiles = testFiles |
| .map((String file) => path.absolute(path.join(thisDirectory, file))) |
| .toList(); |
| |
| final sb = StringBuffer(); |
| sb.writeln(r''' |
| import 'dart:async'; |
| import 'dart:isolate'; |
| import 'dart:io'; |
| |
| '''); |
| for (int i = 0; i < testFiles.length; ++i) { |
| final testFile = testFiles[i]; |
| sb.writeln('import "$testFile" as test$i;'); |
| } |
| for (int i = 0; i < testFiles.length; ++i) { |
| final testFile = testFiles[i]; |
| sb.writeln(''' |
| wrapper$i(dynamic _) { |
| print('[$testFile] starting ...'); |
| runZoned(() { |
| test$i.main(); |
| }, zoneSpecification: ZoneSpecification( |
| handleUncaughtError: (_, ZoneDelegate parent, Zone zone, error, stack) { |
| parent.print(zone, 'Error($testFile): \$error \\nstack:\$stack'); |
| parent.handleUncaughtError(zone, error, stack); |
| }, |
| print: (_, _2, _3, String line) {})); |
| } |
| '''); |
| } |
| sb.writeln(''); |
| sb.writeln(r''' |
| class Test { |
| final String name; |
| final dynamic Function(dynamic) fun; |
| Test(this.name, this.fun); |
| } |
| '''); |
| sb.writeln('final List<Test> tests = ['); |
| for (int i = 0; i < testFiles.length; ++i) { |
| final testFile = testFiles[i]; |
| sb.writeln(' Test("$testFile", wrapper$i),'); |
| } |
| sb.writeln('];'); |
| sb.writeln(''); |
| |
| sb.writeln(''' |
| class Runner { |
| static const progressEvery = 100; |
| |
| final List<Test> tests; |
| late final ReceivePort onExit; |
| late final List<ReceivePort> onExits; |
| late final List<ReceivePort> onErrors; |
| final List<String> errorLog = []; |
| |
| Runner(this.tests) { |
| onExit = ReceivePort(); |
| onExits = List<ReceivePort>.generate(tests.length, (int i) { |
| return ReceivePort()..listen((_) { |
| print('[\${tests[i].name}] finished'); |
| onExit.sendPort.send(null); |
| }); |
| }); |
| onErrors = List<ReceivePort>.generate(tests.length, (int i) { |
| return ReceivePort()..listen((error) { |
| errorLog.add('[\${tests[i].name}] error: \$error'); |
| }); |
| }); |
| } |
| |
| Future runWithUnlimitedParallelism() async { |
| for (int i = 0; i < tests.length; ++i) { |
| await Isolate.spawn( |
| tests[i].fun, |
| null, |
| onExit: onExits[i].sendPort, |
| onError: onErrors[i].sendPort); |
| } |
| await waitUntilDone(); |
| } |
| |
| Future runWithParallelism(int parallelism) async { |
| int _current = 0; |
| |
| Future run() async { |
| final int current = _current++; |
| await Isolate.spawn( |
| tests[current].fun, |
| null, |
| onExit: onExits[current].sendPort, |
| onError: onErrors[current].sendPort); |
| } |
| void scheduleNext() { |
| if (_current == tests.length) return; |
| run().whenComplete(scheduleNext); |
| } |
| |
| for (int i = 0; i < parallelism; ++i) { |
| scheduleNext(); |
| } |
| await waitUntilDone(); |
| } |
| |
| Future waitUntilDone() async { |
| final exitSi = StreamIterator(onExit); |
| for (int i = 0; i < tests.length; ++i) { |
| await exitSi.moveNext(); |
| } |
| await exitSi.cancel(); |
| onExit.close(); |
| onExits.forEach((rp) => rp.close()); |
| onErrors.forEach((rp) => rp.close()); |
| |
| if (!errorLog.isEmpty) { |
| print('Spawning tests in isolates resulted in the following errors:'); |
| print('------------------------------------------------------------'); |
| errorLog.forEach(print); |
| print('------------------------------------------------------------'); |
| print('-> Setting exitCode to 255'); |
| exitCode = 255; |
| } |
| } |
| |
| } |
| |
| main() async { |
| final shards = int.fromEnvironment( |
| 'shards', defaultValue: 1); |
| final shard = int.fromEnvironment( |
| 'shard', defaultValue: 0); |
| |
| final parallelism = int.fromEnvironment( |
| 'parallelism', defaultValue: 0); |
| |
| final filteredTests = <Test>[]; |
| for (int i = 0; i < tests.length; ++i) { |
| if ((i % shards) == shard) { |
| filteredTests.add(tests[i]); |
| } |
| } |
| |
| final runner = Runner(filteredTests); |
| if (parallelism <= 0) { |
| await runner.runWithUnlimitedParallelism(); |
| } else { |
| await runner.runWithParallelism(parallelism); |
| } |
| } |
| '''); |
| return format(sb.toString()); |
| } |
| |
| Future<String> format(String generatedSource) async { |
| try { |
| final result = await Process.start(dartfmt, []); |
| result.stdin.writeln(generatedSource); |
| |
| final results = await Future.wait([ |
| result.stdin.close(), |
| result.stdout.transform(utf8.decoder).join(''), |
| result.stderr.transform(utf8.decoder).join(''), |
| result.exitCode |
| ]); |
| |
| final exitCode = results[3] as int; |
| if (exitCode != 0) { |
| print('Note: Failed to format source code. Dartfmt exited non-0.'); |
| return generatedSource; |
| } |
| final stdout = results[1] as String; |
| final stderr = results[2] as String; |
| if (stderr.trim().length != 0) { |
| print('Note: Failed to format source code. Dartfmt had stderr: $stderr'); |
| return generatedSource; |
| } |
| return stdout; |
| } catch (e) { |
| print('Note: Failed to format source code: $e'); |
| return generatedSource; |
| } |
| } |