| // 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; |
| |
| String inTmpDir(String relative) => |
| path.join(tmpPath, relative.replaceAll('/', path.separator)); |
| |
| String inWorkspace(String relative) => |
| path.join(workspacePath, relative.replaceAll('/', path.separator)); |
| |
| @override |
| Future setUp() async { |
| await super.setUp(); |
| oldSourceDirectory = sourceDirectory; |
| |
| tmpPath = Directory(Directory.systemTemp |
| .createTempSync('analysisServer') |
| .resolveSymbolicLinksSync()) |
| .path; |
| 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 tearDown() async { |
| Directory(tmpPath).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(); |
| } |
| } |