diff --git a/pkg/analysis_server/pubspec.yaml b/pkg/analysis_server/pubspec.yaml index ea99a326dff..5bd9899e695 100644 --- a/pkg/analysis_server/pubspec.yaml +++ b/pkg/analysis_server/pubspec.yaml @@ -35,4 +35,5 @@ dev_dependencies: lints: any logging: any matcher: any + test_descriptor: any test_reflective_loader: any diff --git a/pkg/analysis_server/tool/generate_testing_package.dart b/pkg/analysis_server/tool/generate_testing_package.dart new file mode 100644 index 00000000000..b86d3666929 --- /dev/null +++ b/pkg/analysis_server/tool/generate_testing_package.dart @@ -0,0 +1,203 @@ +// 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 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', + defaultsTo: false, help: 'Whether to add a barrel import') + ..addFlag('use-json-serializable', + defaultsTo: false, + 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(); + // Add imports in a library above tier 0. + if (importGraphTier > 0) { + if (useJsonSerializable) { + content + .writeln("import 'package:json_annotation/json_annotation.dart';"); + } + 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';