// 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; import 'dart:io'; import 'dart:math'; import 'package:args/args.dart'; import 'package:test_descriptor/test_descriptor.dart' as d; void main(List 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 = []; 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 json) => ' '_\$C${classCounter}FromJson(json);', ); content.writeln( ' Map 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';