From ea3563ea4d62f5c4acab91e3419ce38fdd3219ff Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 10 Mar 2020 16:49:23 -0700 Subject: [PATCH] [flutter_tools] update visual studio and validator to remove globals, update test cases (#51680) Updates VisualStudio and VisualStudioValidator to use constructors instead of global injection. Updates VisualStudio test cases to prefer FakeProcessManager Updates build_windows test to work without injected VisualStudio --- .../lib/src/commands/build_windows.dart | 13 +- .../flutter_tools/lib/src/context_runner.dart | 11 +- .../lib/src/windows/build_windows.dart | 11 +- .../lib/src/windows/visual_studio.dart | 31 +- .../src/windows/visual_studio_validator.dart | 66 +- .../hermetic/build_windows_test.dart | 81 +- .../windows/visual_studio_test.dart | 884 +++++++++++------- .../windows/visual_studio_validator_test.dart | 113 ++- 8 files changed, 750 insertions(+), 460 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/build_windows.dart b/packages/flutter_tools/lib/src/commands/build_windows.dart index 72ed2933289..6286cb25946 100644 --- a/packages/flutter_tools/lib/src/commands/build_windows.dart +++ b/packages/flutter_tools/lib/src/commands/build_windows.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../base/common.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -12,6 +14,7 @@ import '../globals.dart' as globals; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import '../windows/build_windows.dart'; +import '../windows/visual_studio.dart'; import 'build.dart'; /// A command to build a windows desktop target through a build shell script. @@ -36,6 +39,9 @@ class BuildWindowsCommand extends BuildSubCommand { @override String get description => 'build the desktop Windows target.'; + @visibleForTesting + VisualStudio visualStudioOverride; + @override Future runCommand() async { Cache.releaseLockEarly(); @@ -47,7 +53,12 @@ class BuildWindowsCommand extends BuildSubCommand { if (!globals.platform.isWindows) { throwToolExit('"build windows" only supported on Windows hosts.'); } - await buildWindows(flutterProject.windows, buildInfo, target: targetFile); + await buildWindows( + flutterProject.windows, + buildInfo, + target: targetFile, + visualStudioOverride: visualStudioOverride, + ); return FlutterCommandResult.success(); } } diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index dcf2e29c278..a081431de44 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -188,8 +188,15 @@ Future runInContext( runningOnBot: runningOnBot, ), UserMessages: () => UserMessages(), - VisualStudio: () => VisualStudio(), - VisualStudioValidator: () => const VisualStudioValidator(), + VisualStudioValidator: () => VisualStudioValidator( + userMessages: globals.userMessages, + visualStudio: VisualStudio( + fileSystem: globals.fs, + platform: globals.platform, + logger: globals.logger, + processManager: globals.processManager, + ) + ), WebWorkflow: () => const WebWorkflow(), WindowsWorkflow: () => const WindowsWorkflow(), Xcode: () => Xcode( diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index a0cef484686..85bb09a3281 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -16,7 +16,10 @@ import 'property_sheet.dart'; import 'visual_studio.dart'; /// Builds the Windows project using msbuild. -Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {String target}) async { +Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { + String target, + VisualStudio visualStudioOverride, +}) async { if (!windowsProject.solutionFile.existsSync()) { throwToolExit( 'No Windows desktop project configured. ' @@ -28,6 +31,12 @@ Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {S _writeGeneratedFlutterProperties(windowsProject, buildInfo, target); createPluginSymlinks(windowsProject.project); + final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio( + fileSystem: globals.fs, + platform: globals.platform, + logger: globals.logger, + processManager: globals.processManager, + ); final String vcvarsScript = visualStudio.vcvarsPath; if (vcvarsScript == null) { throwToolExit('Unable to find suitable Visual Studio toolchain. ' diff --git a/packages/flutter_tools/lib/src/windows/visual_studio.dart b/packages/flutter_tools/lib/src/windows/visual_studio.dart index 93e79c03dd4..13485aeb958 100644 --- a/packages/flutter_tools/lib/src/windows/visual_studio.dart +++ b/packages/flutter_tools/lib/src/windows/visual_studio.dart @@ -2,16 +2,31 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../base/context.dart'; +import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; + +import '../base/file_system.dart'; import '../base/io.dart'; +import '../base/logger.dart'; import '../base/process.dart'; import '../convert.dart'; -import '../globals.dart' as globals; - -VisualStudio get visualStudio => context.get(); /// Encapsulates information about the installed copy of Visual Studio, if any. class VisualStudio { + VisualStudio({ + @required FileSystem fileSystem, + @required ProcessManager processManager, + @required Platform platform, + @required Logger logger, + }) : _platform = platform, + _fileSystem = fileSystem, + _processUtils = ProcessUtils(processManager: processManager, logger: logger); + + final FileSystem _fileSystem; + final Platform _platform; + final ProcessUtils _processUtils; + /// True if Visual Studio installation was found. /// /// Versions older than 2017 Update 2 won't be detected, so error messages to @@ -107,7 +122,7 @@ class VisualStudio { if (details.isEmpty) { return null; } - return globals.fs.path.join( + return _fileSystem.path.join( _usableVisualStudioDetails[_installationPathKey] as String, 'VC', 'Auxiliary', @@ -125,8 +140,8 @@ class VisualStudio { /// present then there isn't a new enough installation of VS. This path is /// not user-controllable, unlike the install location of Visual Studio /// itself. - final String _vswherePath = globals.fs.path.join( - globals.platform.environment['PROGRAMFILES(X86)'], + String get _vswherePath => _fileSystem.path.join( + _platform.environment['PROGRAMFILES(X86)'], 'Microsoft Visual Studio', 'Installer', 'vswhere.exe', @@ -215,7 +230,7 @@ class VisualStudio { '-utf8', '-latest', ]; - final RunResult whereResult = processUtils.runSync([ + final RunResult whereResult = _processUtils.runSync([ _vswherePath, ...defaultArguments, ...?additionalArguments, diff --git a/packages/flutter_tools/lib/src/windows/visual_studio_validator.dart b/packages/flutter_tools/lib/src/windows/visual_studio_validator.dart index 81ccdaac01e..d1604bbdfcb 100644 --- a/packages/flutter_tools/lib/src/windows/visual_studio_validator.dart +++ b/packages/flutter_tools/lib/src/windows/visual_studio_validator.dart @@ -2,15 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:meta/meta.dart'; + import '../base/context.dart'; -import '../base/user_messages.dart'; +import '../base/user_messages.dart' hide userMessages; import '../doctor.dart'; import 'visual_studio.dart'; VisualStudioValidator get visualStudioValidator => context.get(); class VisualStudioValidator extends DoctorValidator { - const VisualStudioValidator() : super('Visual Studio - develop for Windows'); + const VisualStudioValidator({ + @required VisualStudio visualStudio, + @required UserMessages userMessages, + }) : _visualStudio = visualStudio, + _userMessages = userMessages, + super('Visual Studio - develop for Windows'); + + final VisualStudio _visualStudio; + final UserMessages _userMessages; @override Future validate() async { @@ -18,57 +28,57 @@ class VisualStudioValidator extends DoctorValidator { ValidationType status = ValidationType.missing; String versionInfo; - if (visualStudio.isInstalled) { + if (_visualStudio.isInstalled) { status = ValidationType.installed; messages.add(ValidationMessage( - userMessages.visualStudioLocation(visualStudio.installLocation) + _userMessages.visualStudioLocation(_visualStudio.installLocation) )); - messages.add(ValidationMessage(userMessages.visualStudioVersion( - visualStudio.displayName, - visualStudio.fullVersion, + messages.add(ValidationMessage(_userMessages.visualStudioVersion( + _visualStudio.displayName, + _visualStudio.fullVersion, ))); - if (visualStudio.isPrerelease) { - messages.add(ValidationMessage(userMessages.visualStudioIsPrerelease)); + if (_visualStudio.isPrerelease) { + messages.add(ValidationMessage(_userMessages.visualStudioIsPrerelease)); } // Messages for faulty installations. - if (!visualStudio.isAtLeastMinimumVersion) { + if (!_visualStudio.isAtLeastMinimumVersion) { status = ValidationType.partial; messages.add(ValidationMessage.error( - userMessages.visualStudioTooOld( - visualStudio.minimumVersionDescription, - visualStudio.workloadDescription, - visualStudio.necessaryComponentDescriptions(), + _userMessages.visualStudioTooOld( + _visualStudio.minimumVersionDescription, + _visualStudio.workloadDescription, + _visualStudio.necessaryComponentDescriptions(), ), )); - } else if (visualStudio.isRebootRequired) { + } else if (_visualStudio.isRebootRequired) { status = ValidationType.partial; - messages.add(ValidationMessage.error(userMessages.visualStudioRebootRequired)); - } else if (!visualStudio.isComplete) { + messages.add(ValidationMessage.error(_userMessages.visualStudioRebootRequired)); + } else if (!_visualStudio.isComplete) { status = ValidationType.partial; - messages.add(ValidationMessage.error(userMessages.visualStudioIsIncomplete)); - } else if (!visualStudio.isLaunchable) { + messages.add(ValidationMessage.error(_userMessages.visualStudioIsIncomplete)); + } else if (!_visualStudio.isLaunchable) { status = ValidationType.partial; - messages.add(ValidationMessage.error(userMessages.visualStudioNotLaunchable)); - } else if (!visualStudio.hasNecessaryComponents) { + messages.add(ValidationMessage.error(_userMessages.visualStudioNotLaunchable)); + } else if (!_visualStudio.hasNecessaryComponents) { status = ValidationType.partial; messages.add(ValidationMessage.error( - userMessages.visualStudioMissingComponents( - visualStudio.workloadDescription, - visualStudio.necessaryComponentDescriptions(), + _userMessages.visualStudioMissingComponents( + _visualStudio.workloadDescription, + _visualStudio.necessaryComponentDescriptions(), ), )); } - versionInfo = '${visualStudio.displayName} ${visualStudio.displayVersion}'; + versionInfo = '${_visualStudio.displayName} ${_visualStudio.displayVersion}'; } else { status = ValidationType.missing; messages.add(ValidationMessage.error( - userMessages.visualStudioMissing( - visualStudio.workloadDescription, - visualStudio.necessaryComponentDescriptions(), + _userMessages.visualStudioMissing( + _visualStudio.workloadDescription, + _visualStudio.necessaryComponentDescriptions(), ), )); } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index 0ec38b5c698..5a96954bab4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -8,7 +8,6 @@ import 'package:platform/platform.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build_windows.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/features.dart'; @@ -23,15 +22,28 @@ import '../../src/context.dart'; import '../../src/mocks.dart'; import '../../src/testbed.dart'; +const String solutionPath = r'C:\windows\Runner.sln'; +const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'; +const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; + +final Platform windowsPlatform = FakePlatform( + operatingSystem: 'windows', + environment: { + 'PROGRAMFILES(X86)': r'C:\Program Files (x86)\', + 'FLUTTER_ROOT': r'C:\', + } +); +final Platform notWindowsPlatform = FakePlatform( + operatingSystem: 'linux', + environment: { + 'FLUTTER_ROOT': r'C:\', + } +); + void main() { MockProcessManager mockProcessManager; MockProcess mockProcess; - MockPlatform windowsPlatform; - MockPlatform notWindowsPlatform; MockVisualStudio mockVisualStudio; - const String solutionPath = r'C:\windows\Runner.sln'; - const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'; - const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; setUpAll(() { Cache.disableLocking(); @@ -40,9 +52,6 @@ void main() { setUp(() { mockProcessManager = MockProcessManager(); mockProcess = MockProcess(); - windowsPlatform = MockPlatform() - ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\'; - notWindowsPlatform = MockPlatform(); mockVisualStudio = MockVisualStudio(); when(mockProcess.exitCode).thenAnswer((Invocation invocation) async { return 0; @@ -53,8 +62,6 @@ void main() { when(mockProcess.stdout).thenAnswer((Invocation invocation) { return Stream>.fromIterable(>[utf8.encode('STDOUT STUFF')]); }); - when(windowsPlatform.isWindows).thenReturn(true); - when(notWindowsPlatform.isWindows).thenReturn(false); }); // Creates the mock files necessary to look like a Flutter project. @@ -71,55 +78,58 @@ void main() { } testUsingContext('Windows build fails when there is no vcvars64.bat', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockProjectFilesForBuild(); + expect(createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ), throwsToolExit()); }, overrides: { Platform: () => windowsPlatform, FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => FakeProcessManager.any(), - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('Windows build fails when there is no windows project', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockCoreProjectFiles(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); + expect(createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ), throwsToolExit(message: 'No Windows desktop project configured')); }, overrides: { Platform: () => windowsPlatform, FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => FakeProcessManager.any(), - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('Windows build fails on non windows platform', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); expect(createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ), throwsToolExit()); }, overrides: { Platform: () => notWindowsPlatform, FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => FakeProcessManager.any(), - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('Windows build does not spew stdout to status logger', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); @@ -134,7 +144,7 @@ void main() { }); await createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ); expect(testLogger.statusText, isNot(contains('STDOUT STUFF'))); expect(testLogger.traceText, contains('STDOUT STUFF')); @@ -142,12 +152,12 @@ void main() { FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => mockProcessManager, Platform: () => windowsPlatform, - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('Windows build invokes msbuild and writes generated files', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); @@ -162,7 +172,7 @@ void main() { }); await createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ); // Spot-check important elements from the properties file. @@ -176,12 +186,12 @@ void main() { FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => mockProcessManager, Platform: () => windowsPlatform, - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('Release build prints an under-construction warning', () async { - final BuildCommand command = BuildCommand(); + final BuildWindowsCommand command = BuildWindowsCommand() + ..visualStudioOverride = mockVisualStudio; applyMocksToCommand(command); setUpMockProjectFilesForBuild(); when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); @@ -196,7 +206,7 @@ void main() { }); await createTestCommandRunner(command).run( - const ['build', 'windows'] + const ['windows'] ); expect(testLogger.statusText, contains('🚧')); @@ -204,35 +214,24 @@ void main() { FileSystem: () => MemoryFileSystem(style: FileSystemStyle.windows), ProcessManager: () => mockProcessManager, Platform: () => windowsPlatform, - VisualStudio: () => mockVisualStudio, FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('hidden when not enabled on Windows host', () { - when(globals.platform.isWindows).thenReturn(true); - expect(BuildWindowsCommand().hidden, true); }, overrides: { FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false), - Platform: () => MockPlatform(), + Platform: () => windowsPlatform, }); testUsingContext('Not hidden when enabled and on Windows host', () { - when(globals.platform.isWindows).thenReturn(true); - expect(BuildWindowsCommand().hidden, false); }, overrides: { FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), - Platform: () => MockPlatform(), + Platform: () => windowsPlatform, }); } class MockProcessManager extends Mock implements ProcessManager {} class MockProcess extends Mock implements Process {} -class MockPlatform extends Mock implements Platform { - @override - Map environment = { - 'FLUTTER_ROOT': r'C:\', - }; -} class MockVisualStudio extends Mock implements VisualStudio {} diff --git a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart index e7f33dc23cb..2d7edc7b106 100644 --- a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart @@ -3,216 +3,265 @@ // found in the LICENSE file. import 'package:file/memory.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult; +import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart' show ProcessException; +import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; -import 'package:platform/platform.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/mocks.dart'; -class MockPlatform extends Mock implements Platform { - @override - Map environment = {}; +const String programFilesPath = r'C:\Program Files (x86)'; +const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community'; +const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; +const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe'; + +final Platform windowsPlatform = FakePlatform( + operatingSystem: 'windows', + environment: { + 'PROGRAMFILES(X86)': r'C:\Program Files (x86)\', + }, +); + +// A minimum version of a response where a VS installation was found. +const Map _defaultResponse = { + 'installationPath': visualStudioPath, + 'displayName': 'Visual Studio Community 2019', + 'installationVersion': '16.2.29306.81', + 'isRebootRequired': false, + 'isComplete': true, + 'isLaunchable': true, + 'isPrerelease': false, + 'catalog': { + 'productDisplayVersion': '16.2.5', + }, +}; + +// A response for a VS installation that's too old. +const Map _tooOldResponse = { + 'installationPath': visualStudioPath, + 'displayName': 'Visual Studio Community 2017', + 'installationVersion': '15.9.28307.665', + 'isRebootRequired': false, + 'isComplete': true, + 'isLaunchable': true, + 'isPrerelease': false, + 'catalog': { + 'productDisplayVersion': '15.9.12', + }, +}; + +// A version of a response that doesn't include certain installation status +// information that might be missing in older vswhere. +const Map _missingStatusResponse = { + 'installationPath': visualStudioPath, + 'displayName': 'Visual Studio Community 2017', + 'installationVersion': '16.4.29609.76', + 'catalog': { + 'productDisplayVersion': '16.4.1', + }, +}; + +// Arguments for a vswhere query to search for an installation with the required components. +const List _requiredComponents = [ + 'Microsoft.Component.MSBuild', + 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + 'Microsoft.VisualStudio.Component.Windows10SDK.17763', +]; + +// Sets up the mock environment so that searching for Visual Studio with +// exactly the given required components will provide a result. By default it +// return a preset installation, but the response can be overridden. +void setMockVswhereResponse( + FileSystem fileSystem, + FakeProcessManager processManager, [ + List requiredComponents, + List additionalArguments, + Map response, + String responseOverride, +]) { + fileSystem.file(vswherePath).createSync(recursive: true); + fileSystem.file(vcvarsPath).createSync(recursive: true); + final String finalResponse = responseOverride + ?? json.encode(>[response]); + final List requirementArguments = requiredComponents == null + ? [] + : ['-requires', ...requiredComponents]; + + processManager.addCommand(FakeCommand( + command: [ + vswherePath, + '-format', + 'json', + '-utf8', + '-latest', + ...?additionalArguments, + ...?requirementArguments, + ], + stdout: finalResponse, + )); +} + +// Sets whether or not a vswhere query with the required components will +// return an installation. +void setMockCompatibleVisualStudioInstallation( + Map response, + FileSystem fileSystem, + FakeProcessManager processManager, +) { + setMockVswhereResponse( + fileSystem, + processManager, + _requiredComponents, + ['-version', '16'], + response, + ); +} + +// Sets whether or not a vswhere query with the required components will +// return a pre-release installation. +void setMockPrereleaseVisualStudioInstallation( + Map response, + FileSystem fileSystem, + FakeProcessManager processManager, +) { + setMockVswhereResponse( + fileSystem, + processManager, + _requiredComponents, + ['-version', '16', '-prerelease'], + response, + ); +} + +// Sets whether or not a vswhere query searching for 'all' and 'prerelease' +// versions will return an installation. +void setMockAnyVisualStudioInstallation( + Map response, + FileSystem fileSystem, + FakeProcessManager processManager, +) { + setMockVswhereResponse( + fileSystem, + processManager, + null, + ['-prerelease', '-all'], + response, + ); +} + +// Set a pre-encoded query result. +void setMockEncodedAnyVisualStudioInstallation( + String response, + FileSystem fileSystem, + FakeProcessManager processManager, +) { + setMockVswhereResponse( + fileSystem, + processManager, + null, + ['-prerelease', '-all'], + null, + response, + ); +} + +// Create a visual studio instance with a FakeProcessManager. +VisualStudioFixture setUpVisualStudio() { + final FakeProcessManager processManager = FakeProcessManager.list([]); + final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + final BufferLogger logger = BufferLogger.test(); + final VisualStudio visualStudio = VisualStudio( + fileSystem: fileSystem, + platform: windowsPlatform, + logger: logger, + processManager: processManager, + ); + return VisualStudioFixture(visualStudio, fileSystem, processManager); } -class MockProcessManager extends Mock implements ProcessManager {} -class MockProcessResult extends Mock implements ProcessResult {} void main() { - const String programFilesPath = r'C:\Program Files (x86)'; - const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community'; - const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; - const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe'; - - final MockPlatform windowsPlatform = MockPlatform() - ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\'; - MockProcessManager mockProcessManager; - final MemoryFileSystem memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows); - - // A minimum version of a response where a VS installation was found. - const Map _defaultResponse = { - 'installationPath': visualStudioPath, - 'displayName': 'Visual Studio Community 2019', - 'installationVersion': '16.2.29306.81', - 'isRebootRequired': false, - 'isComplete': true, - 'isLaunchable': true, - 'isPrerelease': false, - 'catalog': { - 'productDisplayVersion': '16.2.5', - }, - }; - - // A response for a VS installation that's too old. - const Map _tooOldResponse = { - 'installationPath': visualStudioPath, - 'displayName': 'Visual Studio Community 2017', - 'installationVersion': '15.9.28307.665', - 'isRebootRequired': false, - 'isComplete': true, - 'isLaunchable': true, - 'isPrerelease': false, - 'catalog': { - 'productDisplayVersion': '15.9.12', - }, - }; - - // A version of a response that doesn't include certain installation status - // information that might be missing in older vswhere. - const Map _missingStatusResponse = { - 'installationPath': visualStudioPath, - 'displayName': 'Visual Studio Community 2017', - 'installationVersion': '16.4.29609.76', - 'catalog': { - 'productDisplayVersion': '16.4.1', - }, - }; - - // Arguments for a vswhere query to search for an installation with the required components. - const List _requiredComponents = [ - 'Microsoft.Component.MSBuild', - 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', - 'Microsoft.VisualStudio.Component.Windows10SDK.17763', - ]; - - // Sets up the mock environment so that searching for Visual Studio with - // exactly the given required components will provide a result. By default it - // return a preset installation, but the response can be overridden. - void setMockVswhereResponse([ - List requiredComponents, - List additionalArguments, - Map response, - String responseOverride, - ]) { - globals.fs.file(vswherePath).createSync(recursive: true); - globals.fs.file(vcvarsPath).createSync(recursive: true); - - final MockProcessResult result = MockProcessResult(); - when(result.exitCode).thenReturn(0); - - final String finalResponse = responseOverride ?? - json.encode(>[response]); - when(result.stdout as String).thenReturn(finalResponse); - when(result.stderr as String).thenReturn(''); - final List requirementArguments = requiredComponents == null - ? [] - : ['-requires', ...requiredComponents]; - when(mockProcessManager.runSync( - [ - vswherePath, - '-format', - 'json', - '-utf8', - '-latest', - ...?additionalArguments, - ...?requirementArguments, - ], - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'), - )).thenAnswer((Invocation invocation) { - return result; - }); - } - - // Sets whether or not a vswhere query with the required components will - // return an installation. - void setMockCompatibleVisualStudioInstallation(Mapresponse) { - setMockVswhereResponse(_requiredComponents, ['-version', '16'], response); - } - - // Sets whether or not a vswhere query with the required components will - // return a pre-release installation. - void setMockPrereleaseVisualStudioInstallation(Mapresponse) { - setMockVswhereResponse(_requiredComponents, ['-version', '16', '-prerelease'], response); - } - - // Sets whether or not a vswhere query searching for 'all' and 'prerelease' - // versions will return an installation. - void setMockAnyVisualStudioInstallation(Map response) { - setMockVswhereResponse(null, ['-prerelease', '-all'], response); - } - - // Set a pre-encoded query result. - void setMockEncodedAnyVisualStudioInstallation(String response) { - setMockVswhereResponse(null, ['-prerelease', '-all'], null, response); - } - group('Visual Studio', () { - VisualStudio visualStudio; - - setUp(() { - mockProcessManager = MockProcessManager(); - }); - - testUsingContext('isInstalled returns false when vswhere is missing', () { + testWithoutContext('isInstalled returns false when vswhere is missing', () { + final MockProcessManager mockProcessManager = MockProcessManager(); when(mockProcessManager.runSync( any, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenThrow(const ProcessException('vswhere', [])); + final VisualStudio visualStudio = VisualStudio( + logger: BufferLogger.test(), + fileSystem: MemoryFileSystem.test(style: FileSystemStyle.windows), + platform: windowsPlatform, + processManager: mockProcessManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('vcvarsPath returns null when vswhere is missing', () { + testWithoutContext('vcvarsPath returns null when vswhere is missing', () { + final MockProcessManager mockProcessManager = MockProcessManager(); when(mockProcessManager.runSync( any, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenThrow(const ProcessException('vswhere', [])); + final VisualStudio visualStudio = VisualStudio( + logger: BufferLogger.test(), + fileSystem: MemoryFileSystem.test(style: FileSystemStyle.windows), + platform: windowsPlatform, + processManager: mockProcessManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.vcvarsPath, isNull); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isInstalled returns false when vswhere returns non-zero', () { - - when(mockProcessManager.runSync( - any, - workingDirectory: anyNamed('workingDirectory'), - environment: anyNamed('environment'), - )).thenThrow(const ProcessException('vswhere', [])); - - final MockProcessResult result = MockProcessResult(); - when(result.exitCode).thenReturn(1); + testWithoutContext( + 'isInstalled returns false when vswhere returns non-zero', () { + final MockProcessManager mockProcessManager = MockProcessManager(); when(mockProcessManager.runSync( any, workingDirectory: anyNamed('workingDirectory'), environment: anyNamed('environment'), )).thenAnswer((Invocation invocation) { - return result; + return FakeProcessResult(exitCode: 1, stderr: '', stdout: ''); }); - when(result.stdout as String).thenReturn(''); - when(result.stderr as String).thenReturn(''); + final VisualStudio visualStudio = VisualStudio( + logger: BufferLogger.test(), + fileSystem: MemoryFileSystem.test(style: FileSystemStyle.windows), + platform: windowsPlatform, + processManager: mockProcessManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, + expect(visualStudio.isInstalled, false); }); - testUsingContext('VisualStudio getters return the right values if no installation is found', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(null); + testWithoutContext('VisualStudio getters return the right values if no installation is found', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, false); expect(visualStudio.isAtLeastMinimumVersion, false); expect(visualStudio.hasNecessaryComponents, false); @@ -223,260 +272,414 @@ void main() { expect(visualStudio.displayVersion, null); expect(visualStudio.installLocation, null); expect(visualStudio.fullVersion, null); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('necessaryComponentDescriptions suggest the right VS tools on major version 16', () { - setMockCompatibleVisualStudioInstallation(_defaultResponse); + testWithoutContext('necessaryComponentDescriptions suggest the right VS tools on major version 16', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); final String toolsString = visualStudio.necessaryComponentDescriptions()[1]; + expect(toolsString.contains('v142'), true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('necessaryComponentDescriptions suggest the right VS tools on an old version', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_tooOldResponse); + testWithoutContext('necessaryComponentDescriptions suggest the right VS tools on an old version', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _tooOldResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); final String toolsString = visualStudio.necessaryComponentDescriptions()[1]; + expect(toolsString.contains('v142'), true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isInstalled returns true even with missing status information', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_missingStatusResponse); + testWithoutContext('isInstalled returns true even with missing status information', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _missingStatusResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isInstalled returns true when VS is present but missing components', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_defaultResponse); + testWithoutContext('isInstalled returns true when VS is present but missing components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isInstalled returns true when VS is present but too old', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_tooOldResponse); + testWithoutContext('isInstalled returns true when VS is present but too old', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _tooOldResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isInstalled returns true when a prerelease version of VS is present', () { - setMockCompatibleVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(null); + testWithoutContext('isInstalled returns true when a prerelease version of VS is present', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; final Map response = Map.from(_defaultResponse) ..['isPrerelease'] = true; - setMockPrereleaseVisualStudioInstallation(response); + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isPrerelease, true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isAtLeastMinimumVersion returns false when the version found is too old', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_tooOldResponse); + testWithoutContext('isAtLeastMinimumVersion returns false when the version found is too old', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _tooOldResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isAtLeastMinimumVersion, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isComplete returns false when an incomplete installation is found', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); + testWithoutContext('isComplete returns false when an incomplete installation is found', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); final Map response = Map.from(_defaultResponse) ..['isComplete'] = false; - setMockAnyVisualStudioInstallation(response); + setMockAnyVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isComplete, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext("isLaunchable returns false if the installation can't be launched", () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); + testWithoutContext( + "isLaunchable returns false if the installation can't be launched", () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); final Map response = Map.from(_defaultResponse) ..['isLaunchable'] = false; - setMockAnyVisualStudioInstallation(response); + setMockAnyVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isLaunchable, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('isRebootRequired returns true if the installation needs a reboot', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); + testWithoutContext('isRebootRequired returns true if the installation needs a reboot', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); final Map response = Map.from(_defaultResponse) ..['isRebootRequired'] = true; - setMockAnyVisualStudioInstallation(response); + setMockAnyVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isRebootRequired, true); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); + testWithoutContext('hasNecessaryComponents returns false when VS is present but missing components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; - testUsingContext('hasNecessaryComponents returns false when VS is present but missing components', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_defaultResponse); + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.hasNecessaryComponents, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('vcvarsPath returns null when VS is present but missing components', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_defaultResponse); + testWithoutContext('vcvarsPath returns null when VS is present but missing components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.vcvarsPath, isNull); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('vcvarsPath returns null when VS is present but with require components but installation is faulty', () { + testWithoutContext('vcvarsPath returns null when VS is present but with require components but installation is faulty', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + final Map response = Map.from(_defaultResponse) ..['isRebootRequired'] = true; - setMockCompatibleVisualStudioInstallation(response); - setMockPrereleaseVisualStudioInstallation(null); + setMockCompatibleVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.vcvarsPath, isNull); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () { + testWithoutContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + final Map response = Map.from(_defaultResponse) ..['isRebootRequired'] = true; - setMockCompatibleVisualStudioInstallation(response); - setMockPrereleaseVisualStudioInstallation(null); + setMockCompatibleVisualStudioInstallation( + response, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.hasNecessaryComponents, false); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('VS metadata is available when VS is present, even if missing components', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(_defaultResponse); + testWithoutContext('VS metadata is available when VS is present, even if missing components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.displayName, equals('Visual Studio Community 2019')); expect(visualStudio.displayVersion, equals('16.2.5')); expect(visualStudio.installLocation, equals(visualStudioPath)); expect(visualStudio.fullVersion, equals('16.2.29306.81')); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('vcvarsPath returns null when VS is present but when vswhere returns invalid JSON', () { - setMockCompatibleVisualStudioInstallation(null); - setMockPrereleaseVisualStudioInstallation(null); - setMockEncodedAnyVisualStudioInstallation('{'); + testWithoutContext('vcvarsPath returns null when VS is present but when vswhere returns invalid JSON', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockEncodedAnyVisualStudioInstallation( + '{', + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.vcvarsPath, isNull); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('Everything returns good values when VS is present with all components', () { - setMockCompatibleVisualStudioInstallation(_defaultResponse); - setMockPrereleaseVisualStudioInstallation(null); - setMockAnyVisualStudioInstallation(null); + testWithoutContext('Everything returns good values when VS is present with all components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + + setMockCompatibleVisualStudioInstallation( + _defaultResponse, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); + setMockAnyVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.isInstalled, true); expect(visualStudio.isAtLeastMinimumVersion, true); expect(visualStudio.hasNecessaryComponents, true); expect(visualStudio.vcvarsPath, equals(vcvarsPath)); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); - testUsingContext('Metadata is for compatible version when latest is missing components', () { + testWithoutContext('Metadata is for compatible version when latest is missing components', () { + final VisualStudioFixture fixture = setUpVisualStudio(); + final VisualStudio visualStudio = fixture.visualStudio; + // Return a different version for queries without the required packages. final Map olderButCompleteVersionResponse = { 'installationPath': visualStudioPath, @@ -487,8 +690,16 @@ void main() { }, }; - setMockCompatibleVisualStudioInstallation(olderButCompleteVersionResponse); - setMockPrereleaseVisualStudioInstallation(null); + setMockCompatibleVisualStudioInstallation( + olderButCompleteVersionResponse, + fixture.fileSystem, + fixture.processManager, + ); + setMockPrereleaseVisualStudioInstallation( + null, + fixture.fileSystem, + fixture.processManager, + ); // Return a different version for queries without the required packages. final Map incompleteVersionResponse = { 'installationPath': visualStudioPath, @@ -498,15 +709,24 @@ void main() { 'productDisplayVersion': '16.1', }, }; - setMockAnyVisualStudioInstallation(incompleteVersionResponse); + setMockAnyVisualStudioInstallation( + incompleteVersionResponse, + fixture.fileSystem, + fixture.processManager, + ); - visualStudio = VisualStudio(); expect(visualStudio.displayName, equals('Visual Studio Community 2017')); expect(visualStudio.displayVersion, equals('15.9.12')); - }, overrides: { - FileSystem: () => memoryFilesystem, - ProcessManager: () => mockProcessManager, - Platform: () => windowsPlatform, }); }); } + +class VisualStudioFixture { + VisualStudioFixture(this.visualStudio, this.fileSystem, this.processManager); + + final VisualStudio visualStudio; + final FileSystem fileSystem; + final FakeProcessManager processManager; +} + +class MockProcessManager extends Mock implements ProcessManager {} diff --git a/packages/flutter_tools/test/general.shard/windows/visual_studio_validator_test.dart b/packages/flutter_tools/test/general.shard/windows/visual_studio_validator_test.dart index d8ee9d0a12a..b118e0d9cef 100644 --- a/packages/flutter_tools/test/general.shard/windows/visual_studio_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/visual_studio_validator_test.dart @@ -2,16 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_tools/src/base/user_messages.dart'; +import 'package:flutter_tools/src/base/user_messages.dart' hide userMessages; import 'package:flutter_tools/src/doctor.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart'; import 'package:flutter_tools/src/windows/visual_studio_validator.dart'; import 'package:mockito/mockito.dart'; import '../../src/common.dart'; -import '../../src/context.dart'; -class MockVisualStudio extends Mock implements VisualStudio {} +final UserMessages userMessages = UserMessages(); void main() { group('Visual Studio validation', () { @@ -62,111 +61,131 @@ void main() { when(mockVisualStudio.hasNecessaryComponents).thenReturn(false); } - testUsingContext('Emits a message when Visual Studio is a pre-release version', () async { + testWithoutContext('Emits a message when Visual Studio is a pre-release version', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - when(visualStudio.isPrerelease).thenReturn(true); + when(mockVisualStudio.isPrerelease).thenReturn(true); - const VisualStudioValidator validator = VisualStudioValidator(); final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage(userMessages.visualStudioIsPrerelease); + expect(result.messages.contains(expectedMessage), true); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits a partial status when Visual Studio installation is incomplete', () async { + testWithoutContext('Emits a partial status when Visual Studio installation is incomplete', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - when(visualStudio.isComplete).thenReturn(false); + when(mockVisualStudio.isComplete).thenReturn(false); - const VisualStudioValidator validator = VisualStudioValidator(); final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioIsIncomplete); + expect(result.messages.contains(expectedMessage), true); expect(result.type, ValidationType.partial); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits a partial status when Visual Studio installation needs rebooting', () async { + testWithoutContext('Emits a partial status when Visual Studio installation needs rebooting', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - when(visualStudio.isRebootRequired).thenReturn(true); + when(mockVisualStudio.isRebootRequired).thenReturn(true); - const VisualStudioValidator validator = VisualStudioValidator(); final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioRebootRequired); + expect(result.messages.contains(expectedMessage), true); expect(result.type, ValidationType.partial); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits a partial status when Visual Studio installation is not launchable', () async { + testWithoutContext('Emits a partial status when Visual Studio installation is not launchable', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - when(visualStudio.isLaunchable).thenReturn(false); + when(mockVisualStudio.isLaunchable).thenReturn(false); - const VisualStudioValidator validator = VisualStudioValidator(); final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage.error(userMessages.visualStudioNotLaunchable); + expect(result.messages.contains(expectedMessage), true); expect(result.type, ValidationType.partial); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits partial status when Visual Studio is installed but too old', () async { + testWithoutContext('Emits partial status when Visual Studio is installed but too old', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsTooOld(); - const VisualStudioValidator validator = VisualStudioValidator(); + final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage.error( userMessages.visualStudioTooOld( - visualStudio.minimumVersionDescription, - visualStudio.workloadDescription, - visualStudio.necessaryComponentDescriptions(), + mockVisualStudio.minimumVersionDescription, + mockVisualStudio.workloadDescription, + mockVisualStudio.necessaryComponentDescriptions(), ), ); + expect(result.messages.contains(expectedMessage), true); expect(result.type, ValidationType.partial); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - - testUsingContext('Emits partial status when Visual Studio is installed without necessary components', () async { + testWithoutContext('Emits partial status when Visual Studio is installed without necessary components', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - when(visualStudio.hasNecessaryComponents).thenReturn(false); - const VisualStudioValidator validator = VisualStudioValidator(); + when(mockVisualStudio.hasNecessaryComponents).thenReturn(false); final ValidationResult result = await validator.validate(); + expect(result.type, ValidationType.partial); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits installed status when Visual Studio is installed with necessary components', () async { + testWithoutContext('Emits installed status when Visual Studio is installed with necessary components', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsInstalled(); - const VisualStudioValidator validator = VisualStudioValidator(); + final ValidationResult result = await validator.validate(); final ValidationMessage expectedDisplayNameMessage = ValidationMessage( - userMessages.visualStudioVersion(visualStudio.displayName, visualStudio.fullVersion)); + userMessages.visualStudioVersion(mockVisualStudio.displayName, mockVisualStudio.fullVersion)); + expect(result.messages.contains(expectedDisplayNameMessage), true); expect(result.type, ValidationType.installed); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); - testUsingContext('Emits missing status when Visual Studio is not installed', () async { + testWithoutContext('Emits missing status when Visual Studio is not installed', () async { + final VisualStudioValidator validator = VisualStudioValidator( + userMessages: userMessages, + visualStudio: mockVisualStudio, + ); _configureMockVisualStudioAsNotInstalled(); - const VisualStudioValidator validator = VisualStudioValidator(); + final ValidationResult result = await validator.validate(); final ValidationMessage expectedMessage = ValidationMessage.error( userMessages.visualStudioMissing( - visualStudio.workloadDescription, - visualStudio.necessaryComponentDescriptions(), + mockVisualStudio.workloadDescription, + mockVisualStudio.necessaryComponentDescriptions(), ), ); + expect(result.messages.contains(expectedMessage), true); expect(result.type, ValidationType.missing); - }, overrides: { - VisualStudio: () => mockVisualStudio, }); }); } + +class MockVisualStudio extends Mock implements VisualStudio {}