| // Copyright (c) 2023, 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. |
| |
| /// This script generates a test package with a specified number of libraries, |
| /// classes, methods, and doc comment references, in order to test analyzer's |
| /// performance, scalability, and stability characteristics. |
| /// |
| /// Call with `--help` to see all of the args. |
| library generate_testing_package; |
| |
| import 'dart:io'; |
| import 'dart:math'; |
| |
| import 'package:args/args.dart'; |
| import 'package:test_descriptor/test_descriptor.dart' as d; |
| |
| void main(List<String> args) async { |
| // TODO(srawlins): Support multiple packages which depend on each other, in a |
| // DAG similar to the import graph. |
| var argParser = ArgParser() |
| ..addOption( |
| 'library-count', |
| defaultsTo: '1', |
| help: 'the number of libraries', |
| ) |
| ..addOption( |
| 'class-count', |
| defaultsTo: '1', |
| help: 'the number of classes per library', |
| ) |
| ..addOption( |
| 'method-count', |
| defaultsTo: '1', |
| help: 'the number of methods per class', |
| ) |
| ..addOption( |
| 'parameter-count', |
| defaultsTo: '1', |
| help: 'the number of parameters per method', |
| ) |
| ..addFlag('use-barrel-file', help: 'Whether to add a barrel import') |
| ..addFlag('use-json-serializable', |
| help: 'Whether to declare @JsonSerializable classes'); |
| var argResults = argParser.parse(args); |
| var libraryCount = int.parse(argResults['library-count'] as String); |
| var classCount = int.parse(argResults['class-count'] as String); |
| var methodCount = int.parse(argResults['method-count'] as String); |
| var parameterCount = int.parse(argResults['parameter-count'] as String); |
| var useBarrelFile = argResults['use-barrel-file'] as bool; |
| var useJsonSerializable = argResults['use-json-serializable'] as bool; |
| var testDataDir = Directory('test_data')..createSync(); |
| var libFiles = <d.Descriptor>[]; |
| var classCounter = 1; |
| var methodCounter = 1; |
| var middleImportIndex = libraryCount ~/ 2; |
| // We need a global index for the names of top-level variables, to avoid |
| // ambiguous elements from imports. |
| var topLevelVariableIndex = 1; |
| for (var lIndex = 1; lIndex <= libraryCount; lIndex++) { |
| var libraryName = 'lib$lIndex'.padLeft(3, '0'); |
| // Each library has an index, starting at 1. The libraries depend on each |
| // other in tiers. The "tier" of a library is the base-2 logarithm of its |
| // index. Libraries in higher tiers depend on libraries in lower tiers. |
| // Libraries in higher tiers depend on more libraries than those in lower |
| // tiers. |
| // |
| // In a package with 10 libraries, we have the following tiers and |
| // dependencies: |
| // * T0: lib1 - no imports |
| // * T1: lib2 - imports lib1 |
| // * T2: lib3, lib4 - each imports two libraries from T0, T1 |
| // * T3: lib5, lib6, lib7, lib8 - each imports three libraries from T0, T1, |
| // and T2 |
| // * T4: lib9, lib10 - each imports four libraries from T0, T1, T2, T3 |
| // |
| // In a package with 1000 libraries, there are 11 tiers, and libraries in |
| // the last have 10 imports each. |
| // TODO(srawlins): Make the "connectedness" of the import graph |
| // configurable. |
| var importGraphTier = (log(lIndex) / ln2).ceil(); |
| var importIndexStep = |
| importGraphTier == 0 ? -1 : (lIndex - 1) ~/ importGraphTier; |
| var content = StringBuffer(); |
| if (useJsonSerializable) { |
| content.writeln("import 'package:json_annotation/json_annotation.dart';"); |
| } |
| // Add imports in a library above tier 0. |
| if (importGraphTier > 0) { |
| if (useBarrelFile && lIndex > middleImportIndex) { |
| content.writeln(import(testPackageLibUri('barrel.dart'))); |
| } |
| for (var tierIndex = 1; tierIndex <= importGraphTier; tierIndex++) { |
| var importIndex = tierIndex * importIndexStep; |
| if (useBarrelFile) { |
| if (lIndex <= middleImportIndex || importIndex > middleImportIndex) { |
| content.writeln(import(testPackageLibUri('lib$importIndex.dart'))); |
| } |
| } else { |
| content.writeln(import(testPackageLibUri('lib$importIndex.dart'))); |
| } |
| } |
| content.writeln(); |
| } |
| |
| if (useJsonSerializable) { |
| content.writeln("part '$libraryName.g.dart';"); |
| content.writeln(); |
| } |
| |
| // Add top-level variables above tier 0. |
| if (importGraphTier > 0) { |
| for (var tierIndex = 1; tierIndex <= importGraphTier; tierIndex++) { |
| var importIndex = tierIndex * importIndexStep; |
| // We instantiate a class which is guaranteed to be found in |
| // `lib$importIndex.dart`. |
| var classReferenceIndex = classCount * importIndex; |
| content |
| .writeln('var x$topLevelVariableIndex = C$classReferenceIndex();'); |
| topLevelVariableIndex++; |
| } |
| content.writeln(); |
| } |
| |
| for (var cIndex = 1; cIndex <= classCount; cIndex++) { |
| content.writeln('/// Doc comment.'); |
| if (useJsonSerializable) { |
| content.writeln('@JsonSerializable()'); |
| } |
| content.writeln('class C$classCounter {'); |
| if (useJsonSerializable) { |
| content.writeln(' C$classCounter();'); |
| content.writeln( |
| ' factory C$classCounter.fromJson(Map<String, dynamic> json) => ' |
| '_\$C${classCounter}FromJson(json);'); |
| content.writeln(' Map<String, dynamic> toJson() => ' |
| '_\$C${classCounter}ToJson(this);'); |
| } |
| for (var mIndex = 1; mIndex <= methodCount; mIndex++) { |
| content.write(' void m$methodCounter('); |
| content.write(List.generate(parameterCount, (pIndex) => 'int p$pIndex') |
| .join(', ')); |
| content.writeln(') {}'); |
| methodCounter++; |
| } |
| content.writeln('}'); |
| classCounter++; |
| } |
| libFiles.add(d.file('$libraryName.dart', content.toString())); |
| } |
| |
| if (useBarrelFile) { |
| // Write the barrel file, which exports the first half of the libraries. |
| var content = StringBuffer(); |
| for (var j = 1; j <= middleImportIndex; j++) { |
| content.writeln(export(testPackageLibUri('lib$j.dart'))); |
| } |
| libFiles.add(d.file('barrel.dart', content.toString())); |
| } |
| |
| var testPackageDir = d.dir('test_package', [ |
| d.file('pubspec.yaml', pubspec(useJsonSerializable: useJsonSerializable)), |
| d.dir('lib', libFiles), |
| ]); |
| await testPackageDir.create(testDataDir.path); |
| } |
| |
| String export(String uri) => "export '$uri';"; |
| |
| String import(String uri) => "import '$uri';"; |
| |
| /// Returns the text of a 'pubspec.yaml' file. |
| /// |
| /// With [useJsonSerializable], several packages are added to the dependencies |
| /// in order to test using build_runner. |
| String pubspec({required bool useJsonSerializable}) { |
| var dependencies = useJsonSerializable |
| ? ''' |
| dependencies: |
| json_annotation: any |
| json_serializable: any |
| ''' |
| : ''; |
| var devDependencies = useJsonSerializable |
| ? ''' |
| dev_dependencies: |
| build_runner: any |
| ''' |
| : ''; |
| return ''' |
| name: test_package |
| version: 0.0.1 |
| environment: |
| sdk: '>=2.12.0 <3.0.0' |
| $dependencies |
| $devDependencies |
| '''; |
| } |
| |
| String testPackageLibUri(String path) => 'package:test_package/$path'; |