| // 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:io'; | |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; | |
| import 'package:path/path.dart' as path; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import '../support/integration_tests.dart'; | |
| void main() { | |
| // Skip on Windows. | |
| if (Platform.isWindows) { | |
| return; | |
| } | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(BlazeChangesTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class BlazeChangesTest extends AbstractAnalysisServerIntegrationTest { | |
| var processedNotification = Completer<void>(); | |
| /// Path to the `command.log` file. | |
| /// | |
| /// Writing to it should trigger our change detection to run. | |
| late String commandLogPath; | |
| late String blazeRoot; | |
| late String tmpPath; | |
| late String workspacePath; | |
| late String blazeOutPath; | |
| late String blazeBinPath; | |
| late String blazeGenfilesPath; | |
| late Directory oldSourceDirectory; | |
| /// Temporary folders created by the test that should be deleted (recursively) | |
| /// during [tearDown]. | |
| final List<String> _temporaryFolders = []; | |
| String inTmpDir(String relative) => | |
| path.join(tmpPath, relative.replaceAll('/', path.separator)); | |
| String inWorkspace(String relative) => | |
| path.join(workspacePath, relative.replaceAll('/', path.separator)); | |
| @override | |
| Future<void> setUp() async { | |
| await super.setUp(); | |
| oldSourceDirectory = sourceDirectory; | |
| tmpPath = Directory( | |
| Directory.systemTemp | |
| .createTempSync('analysisServer_test_integration_blazeProject') | |
| .resolveSymbolicLinksSync(), | |
| ).path; | |
| _temporaryFolders.add(tmpPath); | |
| workspacePath = inTmpDir('workspace_root'); | |
| writeFile(inWorkspace(file_paths.blazeWorkspaceMarker), ''); | |
| sourceDirectory = Directory(inWorkspace('third_party/dart/project')); | |
| sourceDirectory.createSync(recursive: true); | |
| blazeRoot = inTmpDir('blaze_root'); | |
| Directory(blazeRoot).createSync(recursive: true); | |
| blazeOutPath = '$blazeRoot/execroot/blaze_workspace/blaze-out'; | |
| blazeBinPath = '$blazeRoot/execroot/blaze_workspace/blaze-out/bin'; | |
| blazeGenfilesPath = | |
| '$blazeRoot/execroot/blaze_workspace/blaze-out/genfiles'; | |
| Directory(inTmpDir(blazeOutPath)).createSync(recursive: true); | |
| Directory(inTmpDir(blazeBinPath)).createSync(recursive: true); | |
| Directory(inTmpDir(blazeGenfilesPath)).createSync(recursive: true); | |
| commandLogPath = inTmpDir('$blazeRoot/command.log'); | |
| } | |
| @override | |
| Future<void> tearDown() async { | |
| for (var temporaryFolder in _temporaryFolders) { | |
| Directory(temporaryFolder).deleteSync(recursive: true); | |
| } | |
| sourceDirectory = oldSourceDirectory; | |
| await super.tearDown(); | |
| } | |
| // Add a bit more time -- the isolate take a while to start when the test is | |
| // not run from a snapshot. | |
| @TestTimeout(Timeout.factor(2)) | |
| Future<void> test_blazeChanges() async { | |
| await testChangesImpl('blaze'); | |
| } | |
| Future<void> testChangesImpl(String prefix) async { | |
| var testFile = inWorkspace('${sourceDirectory.path}/lib/test.dart'); | |
| var errors = <AnalysisError>[]; | |
| onAnalysisErrors.listen((event) { | |
| if (event.file == testFile) { | |
| errors.addAll(event.errors); | |
| processedNotification.complete(); | |
| } | |
| }); | |
| resetCompleterAndErrors() async { | |
| // This is necessary because our polling uses modification timestamps | |
| // whose resolution seems to be too small for a test like this (i.e., we | |
| // write to the `command.log` file, but if the modification timestamp | |
| // doesn't change, we won't detect the change). | |
| await Future.delayed(Duration(seconds: 1)); | |
| errors.clear(); | |
| processedNotification = Completer(); | |
| } | |
| writeFile(testFile, r''' | |
| import 'generated.dart'; | |
| void f() { my_fun(); } | |
| '''); | |
| await standardAnalysisSetup(); | |
| await processedNotification.future; | |
| expect(errors, isNotEmpty); | |
| var error = errors.singleWhere( | |
| (e) => e.code == 'uri_does_not_exist', | |
| orElse: () => throw "'uri_does_not_exist' error was not found", | |
| ); | |
| expect(error.message, contains('generated.dart')); | |
| // This seems to be necessary (at least when running the test from source), | |
| // because it takes a while for the watcher isolate to start. | |
| await Future.delayed(Duration(seconds: 10)); | |
| await resetCompleterAndErrors(); | |
| var generatedFilePath = inWorkspace( | |
| '$blazeGenfilesPath/third_party/dart/project/lib/generated.dart', | |
| ); | |
| writeFile(generatedFilePath, 'my_fun() {}'); | |
| _createSymlinks(prefix); | |
| writeFile(commandLogPath, 'Build completed successfully'); | |
| await processedNotification.future; | |
| expect(errors, isEmpty); | |
| // Now let's write a file that does not define `my_fun` -- we should get an | |
| // error again. | |
| await resetCompleterAndErrors(); | |
| writeFile(generatedFilePath, 'different_fun() {}'); | |
| writeFile(commandLogPath, 'Build completed'); | |
| await processedNotification.future; | |
| expect(errors, isNotEmpty); | |
| // Now delete the file completely. | |
| await resetCompleterAndErrors(); | |
| File(generatedFilePath).deleteSync(); | |
| _deleteSymlinks(prefix); | |
| writeFile(commandLogPath, 'Build did NOT complete successfully'); | |
| await processedNotification.future; | |
| expect(errors, isNotEmpty); | |
| // And finally re-add the correct file -- errors should go away once again. | |
| await resetCompleterAndErrors(); | |
| writeFile(generatedFilePath, 'my_fun() {}'); | |
| _createSymlinks(prefix); | |
| writeFile(commandLogPath, 'Build completed successfully'); | |
| await processedNotification.future; | |
| expect(errors, isEmpty); | |
| } | |
| void _createSymlinks(String prefix) { | |
| Link(inWorkspace('$prefix-out')).createSync(blazeOutPath); | |
| Link(inWorkspace('$prefix-bin')).createSync(blazeBinPath); | |
| Link(inWorkspace('$prefix-genfiles')).createSync(blazeGenfilesPath); | |
| } | |
| void _deleteSymlinks(String prefix) { | |
| Link(inWorkspace('$prefix-out')).deleteSync(); | |
| Link(inWorkspace('$prefix-bin')).deleteSync(); | |
| Link(inWorkspace('$prefix-genfiles')).deleteSync(); | |
| } | |
| } |