| // 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. |
| |
| import 'dart:io'; |
| |
| import 'package:io/io.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:pubspec_parse/pubspec_parse.dart'; |
| import 'package:test_common/utilities.dart'; |
| |
| enum IndexBaseMode { noBase, base } |
| |
| /// Project definitions from projects defined in webdev/fixtures. |
| class TestProject { |
| final String packageName; |
| final String packageDirectory; |
| final String webAssetsPath; |
| final String dartEntryFileName; |
| final String htmlEntryFileName; |
| |
| late Directory _fixturesCopy; |
| |
| /// The top level directory in which we run the test server, e.g. |
| /// "/tmp/_test". |
| String get absolutePackageDirectory => |
| p.join(_fixturesCopy.absolute.path, packageDirectory); |
| |
| /// The directory to build and serve, e.g. "example". |
| String get directoryToServe => p.split(webAssetsPath).first; |
| |
| /// The path to the HTML file to serve, relative to the [directoryToServe], |
| /// e.g. "hello_world/index.html". |
| String get filePathToServe { |
| final pathParts = p |
| .split(webAssetsPath) |
| .where((pathPart) => pathPart != directoryToServe); |
| return webCompatiblePath([...pathParts, htmlEntryFileName]); |
| } |
| |
| /// The path to the Dart entry file, e.g, |
| /// "/tmp/_test/example/hello_world/main.dart": |
| String get dartEntryFilePath => p.joinAll([ |
| _fixturesCopy.absolute.path, |
| packageDirectory, |
| webAssetsPath, |
| dartEntryFileName, |
| ]); |
| |
| /// The URI for the package_config.json is located in: |
| /// `<project directory>/.dart_tool/package_config` |
| Uri get packageConfigFile => p.toUri( |
| p.join(absolutePackageDirectory, '.dart_tool', 'package_config.json'), |
| ); |
| |
| /// The package URI of the Dart entry file, e.g, |
| /// "org-dartlang-app:example/hello_world/main.dart": |
| Uri get dartEntryFilePackageUri => Uri.parse( |
| 'org-dartlang-app:///${p.join(webAssetsPath, dartEntryFileName)}', |
| ); |
| |
| TestProject.testPackage({IndexBaseMode baseMode = IndexBaseMode.noBase}) |
| : this._( |
| packageName: '_test_package', |
| packageDirectory: '_test_package', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: |
| baseMode == IndexBaseMode.base ? 'base_index.html' : 'index.html', |
| ); |
| |
| static final testCircular1 = TestProject._( |
| packageName: '_test_circular1', |
| packageDirectory: '_test_circular1', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| TestProject.testCircular2({IndexBaseMode baseMode = IndexBaseMode.noBase}) |
| : this._( |
| packageName: '_test_circular2', |
| packageDirectory: '_test_circular2', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: |
| baseMode == IndexBaseMode.base ? 'base_index.html' : 'index.html', |
| ); |
| |
| static final test = TestProject._( |
| packageName: '_test', |
| packageDirectory: '_test', |
| webAssetsPath: 'example/hello_world', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testScopes = TestProject._( |
| packageName: '_test', |
| packageDirectory: '_test', |
| webAssetsPath: webCompatiblePath(['example', 'scopes']), |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'scopes.html', |
| ); |
| |
| static final testAppendBody = TestProject._( |
| packageName: '_test', |
| packageDirectory: '_test', |
| webAssetsPath: webCompatiblePath(['example', 'append_body']), |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testExperiment = TestProject._( |
| packageName: '_experiment', |
| packageDirectory: '_experiment', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testHotRestart1 = TestProject._( |
| packageName: '_test_hot_restart1', |
| packageDirectory: '_test_hot_restart1', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| /// This series of hot restart tests is divided across multiple packages in |
| /// order to test correctness when only a subset of libraries are updated. |
| static final testHotRestart2 = TestProject._( |
| packageName: '_test_hot_restart2', |
| packageDirectory: '_test_hot_restart2', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testHotRestartBreakpoints = TestProject._( |
| packageName: '_test_hot_restart_breakpoints', |
| packageDirectory: '_test_hot_restart_breakpoints', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testHotReload = TestProject._( |
| packageName: '_test_hot_reload', |
| packageDirectory: '_test_hot_reload', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| static final testHotReloadBreakpoints = TestProject._( |
| packageName: '_test_hot_reload_breakpoints', |
| packageDirectory: '_test_hot_reload_breakpoints', |
| webAssetsPath: 'web', |
| dartEntryFileName: 'main.dart', |
| htmlEntryFileName: 'index.html', |
| ); |
| |
| TestProject._({ |
| required this.packageName, |
| required this.packageDirectory, |
| required this.webAssetsPath, |
| required this.dartEntryFileName, |
| required this.htmlEntryFileName, |
| }); |
| |
| static Future<void> _copyPackageAndPathDependenciesIntoTempDirectory( |
| Directory tempDirectory, |
| String packageDirectory, |
| Set<String> copiedPackageDirectories, |
| ) async { |
| // There may be cycles in dependencies, so check that we already copied this |
| // package. |
| if (copiedPackageDirectories.contains(packageDirectory)) return; |
| final currentPath = absolutePath(pathFromFixtures: packageDirectory); |
| final newPath = p.join(tempDirectory.absolute.path, packageDirectory); |
| Directory(newPath).createSync(); |
| copyPathSync(currentPath, newPath); |
| copiedPackageDirectories.add(packageDirectory); |
| final pubspec = Pubspec.parse( |
| File(p.join(currentPath, 'pubspec.yaml')).readAsStringSync(), |
| ); |
| for (final dependency in pubspec.dependencies.values) { |
| if (dependency is PathDependency) { |
| final dependencyDirectory = Directory( |
| p.normalize(p.join(currentPath, dependency.path)), |
| ); |
| // It may be okay to do some more complicated copying here for path |
| // dependencies that aren't immediately under `fixtures`, but for now, |
| // only support those that are. |
| assert( |
| dependencyDirectory.parent.path == Directory(currentPath).parent.path, |
| 'Path dependency of $packageDirectory: ' |
| '${dependencyDirectory.path} is not an immediate directory in ' |
| '`fixtures`.', |
| ); |
| await _copyPackageAndPathDependenciesIntoTempDirectory( |
| tempDirectory, |
| p.basename(dependencyDirectory.path), |
| copiedPackageDirectories, |
| ); |
| } |
| } |
| |
| // Clean up the project. |
| // Called when we need to rebuild sdk and the app from previous test |
| // configurations. |
| await Process.run('dart', [ |
| 'run', |
| 'build_runner', |
| 'clean', |
| ], workingDirectory: newPath); |
| } |
| |
| Future<void> setUp() async { |
| // Verify that the web assets path has no starting slash. |
| assert(!webAssetsPath.startsWith('/')); |
| // Copy the package into a temporary directory to allow editing of files if |
| // needed. |
| final systemTempDir = Directory( |
| // Resolve symbolic links as build_daemon tests rely on paths matching |
| // between the client and the daemon. |
| Directory.systemTemp.resolveSymbolicLinksSync(), |
| ); |
| _fixturesCopy = systemTempDir.createTempSync(); |
| await _copyPackageAndPathDependenciesIntoTempDirectory( |
| _fixturesCopy, |
| packageDirectory, |
| {}, |
| ); |
| } |
| |
| /// Delete the copy of the project. |
| Future<void> tearDown() async { |
| try { |
| _fixturesCopy.deleteSync(recursive: true); |
| } on FileSystemException catch (_) { |
| // On Windows, the build daemon process might still be accessing the |
| // working directory, so try again with an exponential backoff. |
| var seconds = 1; |
| final maxAttempts = 3; |
| for (var attempt = 0; attempt < maxAttempts; attempt++) { |
| try { |
| _fixturesCopy.deleteSync(recursive: true); |
| break; |
| } on FileSystemException catch (_) { |
| await Future.delayed(Duration(seconds: seconds)); |
| seconds *= 2; |
| } |
| } |
| } |
| } |
| |
| /// The path to the Dart specified file in the 'lib' directory, e.g, |
| /// "/tmp/_test/lib/library.dart": |
| String dartLibFilePath(String dartLibFileName) => p.joinAll([ |
| _fixturesCopy.absolute.path, |
| packageDirectory, |
| 'lib', |
| dartLibFileName, |
| ]); |
| } |