mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

The `flutter doctor` command uses `vswhere.exe` to verify the Visual Studio installation. This `vswhere.exe` is known to encode its output incorrectly. This is problematic as the `description` property is localized, and in certain languages this results in invalid JSON due to the incorrect encoding. This change introduces a fallback to our `vswhere.exe` output parsing logic: if parsing JSON fails, remove the `description` property and retry parsing the JSON. This fix was also tested on the outputs provided here: https://github.com/flutter/flutter/issues/106601#issuecomment-1170138123 Addresses https://github.com/flutter/flutter/issues/106601
1096 lines
37 KiB
Dart
1096 lines
37 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// 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;
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/convert.dart';
|
|
import 'package:flutter_tools/src/windows/visual_studio.dart';
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/fake_process_manager.dart';
|
|
|
|
const String programFilesPath = r'C:\Program Files (x86)';
|
|
const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community';
|
|
const String cmakePath = visualStudioPath + r'\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe';
|
|
const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe';
|
|
|
|
final Platform windowsPlatform = FakePlatform(
|
|
operatingSystem: 'windows',
|
|
environment: <String, String>{
|
|
'PROGRAMFILES(X86)': r'C:\Program Files (x86)\',
|
|
},
|
|
);
|
|
|
|
// A minimum version of a response where a VS installation was found.
|
|
const Map<String, dynamic> _defaultResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2019',
|
|
'installationVersion': '16.2.29306.81',
|
|
'isRebootRequired': false,
|
|
'isComplete': true,
|
|
'isLaunchable': true,
|
|
'isPrerelease': false,
|
|
'catalog': <String, dynamic>{
|
|
'productDisplayVersion': '16.2.5',
|
|
},
|
|
};
|
|
|
|
// A minimum version of a response where a VS 2022 installation was found.
|
|
const Map<String, dynamic> _vs2022Response = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2022',
|
|
'installationVersion': '17.0.31903.59',
|
|
'isRebootRequired': false,
|
|
'isComplete': true,
|
|
'isLaunchable': true,
|
|
'isPrerelease': false,
|
|
'catalog': <String, dynamic>{
|
|
'productDisplayVersion': '17.0.0',
|
|
},
|
|
};
|
|
|
|
// A minimum version of a response where a Build Tools installation was found.
|
|
const Map<String, dynamic> _defaultBuildToolsResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Build Tools 2019',
|
|
'installationVersion': '16.7.30413.136',
|
|
'isRebootRequired': false,
|
|
'isComplete': true,
|
|
'isLaunchable': true,
|
|
'isPrerelease': false,
|
|
'catalog': <String, dynamic>{
|
|
'productDisplayVersion': '16.7.2',
|
|
},
|
|
};
|
|
|
|
// A response for a VS installation that's too old.
|
|
const Map<String, dynamic> _tooOldResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2017',
|
|
'installationVersion': '15.9.28307.665',
|
|
'isRebootRequired': false,
|
|
'isComplete': true,
|
|
'isLaunchable': true,
|
|
'isPrerelease': false,
|
|
'catalog': <String, dynamic>{
|
|
'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<String, dynamic> _missingStatusResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2017',
|
|
'installationVersion': '16.4.29609.76',
|
|
'catalog': <String, dynamic>{
|
|
'productDisplayVersion': '16.4.1',
|
|
},
|
|
};
|
|
|
|
const String _malformedDescriptionResponse = r'''
|
|
[
|
|
{
|
|
"installationPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community",
|
|
"displayName": "Visual Studio Community 2019",
|
|
"description": "This description has too many "quotes",
|
|
"installationVersion": "16.2.29306.81",
|
|
"isRebootRequired": false,
|
|
"isComplete": true,
|
|
"isPrerelease": false,
|
|
"catalog": {
|
|
"productDisplayVersion": "16.2.5"
|
|
}
|
|
}
|
|
]
|
|
''';
|
|
|
|
// Arguments for a vswhere query to search for an installation with the
|
|
// requirements.
|
|
const List<String> _requirements = <String>[
|
|
'Microsoft.VisualStudio.Workload.NativeDesktop',
|
|
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
|
'Microsoft.VisualStudio.Component.VC.CMake.Project',
|
|
];
|
|
|
|
// Arguments for a vswhere query to search for a Build Tools installation with the
|
|
// requirements.
|
|
const List<String> _requirementsBuildTools = <String>[
|
|
'Microsoft.VisualStudio.Workload.VCTools',
|
|
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
|
'Microsoft.VisualStudio.Component.VC.CMake.Project',
|
|
];
|
|
|
|
// 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<String>? requiredComponents,
|
|
List<String>? additionalArguments,
|
|
Map<String, dynamic>? response,
|
|
String? responseOverride,
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
fileSystem.file(vswherePath).createSync(recursive: true);
|
|
fileSystem.file(cmakePath).createSync(recursive: true);
|
|
final String finalResponse = responseOverride
|
|
?? (response != null ? json.encode(<Map<String, dynamic>>[response]) : '[]');
|
|
final List<String> requirementArguments = requiredComponents == null
|
|
? <String>[]
|
|
: <String>['-requires', ...requiredComponents];
|
|
|
|
processManager.addCommand(FakeCommand(
|
|
command: <String>[
|
|
vswherePath,
|
|
'-format',
|
|
'json',
|
|
'-products',
|
|
'*',
|
|
'-utf8',
|
|
'-latest',
|
|
...?additionalArguments,
|
|
...requirementArguments,
|
|
],
|
|
stdout: finalResponse,
|
|
exception: exception,
|
|
exitCode: exitCode ?? 0,
|
|
));
|
|
}
|
|
|
|
// Sets whether or not a vswhere query with the required components will
|
|
// return an installation.
|
|
void setMockCompatibleVisualStudioInstallation(
|
|
Map<String, dynamic>? response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, [
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
_requirements,
|
|
<String>['-version', '16'],
|
|
response,
|
|
null,
|
|
exitCode,
|
|
exception,
|
|
);
|
|
}
|
|
|
|
// Sets whether or not a vswhere query with the required components will
|
|
// return a pre-release installation.
|
|
void setMockPrereleaseVisualStudioInstallation(
|
|
Map<String, dynamic>? response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, [
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
_requirements,
|
|
<String>['-version', '16', '-prerelease'],
|
|
response,
|
|
null,
|
|
exitCode,
|
|
exception,
|
|
);
|
|
}
|
|
|
|
// Sets whether or not a vswhere query with the required components will
|
|
// return an Build Tools installation.
|
|
void setMockCompatibleVisualStudioBuildToolsInstallation(
|
|
Map<String, dynamic>? response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, [
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
_requirementsBuildTools,
|
|
<String>['-version', '16'],
|
|
response,
|
|
null,
|
|
exitCode,
|
|
exception,
|
|
);
|
|
}
|
|
|
|
// Sets whether or not a vswhere query with the required components will
|
|
// return a pre-release Build Tools installation.
|
|
void setMockPrereleaseVisualStudioBuildToolsInstallation(
|
|
Map<String, dynamic>? response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, [
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
_requirementsBuildTools,
|
|
<String>['-version', '16', '-prerelease'],
|
|
response,
|
|
null,
|
|
exitCode,
|
|
exception,
|
|
);
|
|
}
|
|
|
|
// Sets whether or not a vswhere query searching for 'all' and 'prerelease'
|
|
// versions will return an installation.
|
|
void setMockAnyVisualStudioInstallation(
|
|
Map<String, dynamic>? response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, [
|
|
int? exitCode,
|
|
Exception? exception,
|
|
]) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
null,
|
|
<String>['-prerelease', '-all'],
|
|
response,
|
|
null,
|
|
exitCode,
|
|
exception,
|
|
);
|
|
}
|
|
|
|
// Set a pre-encoded query result.
|
|
void setMockEncodedAnyVisualStudioInstallation(
|
|
String response,
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager,
|
|
) {
|
|
setMockVswhereResponse(
|
|
fileSystem,
|
|
processManager,
|
|
null,
|
|
<String>['-prerelease', '-all'],
|
|
null,
|
|
response,
|
|
);
|
|
}
|
|
|
|
// Sets up the mock environment for a Windows 10 SDK query.
|
|
//
|
|
// registryPresent controls whether or not the registry key is found.
|
|
// filesPresent controls where or not there are any SDK folders at the location
|
|
// returned by the registry query.
|
|
void setMockSdkRegResponse(
|
|
FileSystem fileSystem,
|
|
FakeProcessManager processManager, {
|
|
bool registryPresent = true,
|
|
bool filesPresent = true,
|
|
}) {
|
|
const String registryPath = r'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0';
|
|
const String registryKey = r'InstallationFolder';
|
|
const String installationPath = r'C:\Program Files (x86)\Windows Kits\10\';
|
|
final String stdout = registryPresent
|
|
? '''
|
|
$registryPath
|
|
$registryKey REG_SZ $installationPath
|
|
'''
|
|
: '''
|
|
|
|
ERROR: The system was unable to find the specified registry key or value.
|
|
''';
|
|
|
|
if (filesPresent) {
|
|
final Directory includeDirectory = fileSystem.directory(installationPath).childDirectory('Include');
|
|
includeDirectory.childDirectory('10.0.17763.0').createSync(recursive: true);
|
|
includeDirectory.childDirectory('10.0.18362.0').createSync(recursive: true);
|
|
// Not an actual version; added to ensure that version comparison is number, not string-based.
|
|
includeDirectory.childDirectory('10.0.184.0').createSync(recursive: true);
|
|
}
|
|
|
|
processManager.addCommand(FakeCommand(
|
|
command: const <String>[
|
|
'reg',
|
|
'query',
|
|
registryPath,
|
|
'/v',
|
|
registryKey,
|
|
],
|
|
stdout: stdout,
|
|
));
|
|
}
|
|
|
|
// Create a visual studio instance with a FakeProcessManager.
|
|
VisualStudioFixture setUpVisualStudio() {
|
|
final FakeProcessManager processManager = FakeProcessManager.empty();
|
|
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, logger);
|
|
}
|
|
|
|
// Set all vswhere query with the required components return null.
|
|
void setNoViableToolchainInstallation(
|
|
VisualStudioFixture fixture,
|
|
) {
|
|
setMockCompatibleVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockPrereleaseVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockPrereleaseVisualStudioBuildToolsInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('Visual Studio', () {
|
|
testWithoutContext('isInstalled throws when PROGRAMFILES(X86) env not set', () {
|
|
final VisualStudio visualStudio = VisualStudio(
|
|
logger: BufferLogger.test(),
|
|
fileSystem: MemoryFileSystem.test(style: FileSystemStyle.windows),
|
|
platform: FakePlatform(operatingSystem: 'windows'),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
|
|
expect(() => visualStudio.isInstalled,
|
|
throwsToolExit(message: '%PROGRAMFILES(X86)% environment variable not found'));
|
|
});
|
|
|
|
testWithoutContext('isInstalled and cmakePath correct when vswhere is missing', () {
|
|
final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
|
const Exception exception = ProcessException('vswhere', <String>[]);
|
|
final FakeProcessManager fakeProcessManager = FakeProcessManager.empty();
|
|
|
|
setMockCompatibleVisualStudioInstallation(null, fileSystem, fakeProcessManager, null, exception);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(null, fileSystem, fakeProcessManager, null, exception);
|
|
setMockPrereleaseVisualStudioInstallation(null, fileSystem, fakeProcessManager, null, exception);
|
|
setMockPrereleaseVisualStudioBuildToolsInstallation(null, fileSystem, fakeProcessManager, null, exception);
|
|
setMockAnyVisualStudioInstallation(null, fileSystem, fakeProcessManager, null, exception);
|
|
|
|
final VisualStudio visualStudio = VisualStudio(
|
|
logger: BufferLogger.test(),
|
|
fileSystem: fileSystem,
|
|
platform: windowsPlatform,
|
|
processManager: fakeProcessManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, false);
|
|
expect(visualStudio.cmakePath, isNull);
|
|
});
|
|
|
|
testWithoutContext(
|
|
'isInstalled returns false when vswhere returns non-zero', () {
|
|
final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
|
final FakeProcessManager fakeProcessManager = FakeProcessManager.empty();
|
|
|
|
setMockCompatibleVisualStudioInstallation(null, fileSystem, fakeProcessManager, 1);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(null, fileSystem, fakeProcessManager, 1);
|
|
setMockPrereleaseVisualStudioInstallation(null, fileSystem, fakeProcessManager, 1);
|
|
setMockPrereleaseVisualStudioBuildToolsInstallation(null, fileSystem, fakeProcessManager, 1);
|
|
setMockAnyVisualStudioInstallation(null, fileSystem, fakeProcessManager, 1);
|
|
|
|
final VisualStudio visualStudio = VisualStudio(
|
|
logger: BufferLogger.test(),
|
|
fileSystem: fileSystem,
|
|
platform: windowsPlatform,
|
|
processManager: fakeProcessManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, false);
|
|
expect(visualStudio.cmakePath, isNull);
|
|
});
|
|
|
|
testWithoutContext('VisualStudio getters return the right values if no installation is found', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, false);
|
|
expect(visualStudio.isAtLeastMinimumVersion, false);
|
|
expect(visualStudio.hasNecessaryComponents, false);
|
|
expect(visualStudio.isComplete, false);
|
|
expect(visualStudio.isRebootRequired, false);
|
|
expect(visualStudio.isLaunchable, false);
|
|
expect(visualStudio.displayName, null);
|
|
expect(visualStudio.displayVersion, null);
|
|
expect(visualStudio.installLocation, null);
|
|
expect(visualStudio.fullVersion, null);
|
|
});
|
|
|
|
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,
|
|
);
|
|
|
|
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
|
|
|
|
expect(toolsString.contains('v142'), true);
|
|
});
|
|
|
|
testWithoutContext('necessaryComponentDescriptions suggest the right VS tools on an old version', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_tooOldResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
|
|
|
|
expect(toolsString.contains('v142'), true);
|
|
});
|
|
|
|
testWithoutContext('isInstalled returns true even with missing status information', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_missingStatusResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
});
|
|
|
|
testWithoutContext('isInstalled returns true when VS is present but missing components', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_defaultResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
});
|
|
|
|
testWithoutContext('isInstalled returns true when VS is present but too old', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_tooOldResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
});
|
|
|
|
testWithoutContext('isInstalled returns true when a prerelease version of VS is present', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isPrerelease'] = true;
|
|
setMockCompatibleVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockPrereleaseVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isPrerelease, true);
|
|
});
|
|
|
|
testWithoutContext('isInstalled returns true when a prerelease version of Build Tools is present', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultBuildToolsResponse)
|
|
..['isPrerelease'] = true;
|
|
setMockCompatibleVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockPrereleaseVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockPrereleaseVisualStudioBuildToolsInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isPrerelease, true);
|
|
});
|
|
|
|
testWithoutContext('isAtLeastMinimumVersion returns false when the version found is too old', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_tooOldResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, false);
|
|
});
|
|
|
|
testWithoutContext('isComplete returns false when an incomplete installation is found', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isComplete'] = false;
|
|
setMockAnyVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isComplete, false);
|
|
});
|
|
|
|
testWithoutContext(
|
|
"isLaunchable returns false if the installation can't be launched", () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isLaunchable'] = false;
|
|
setMockAnyVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isLaunchable, false);
|
|
});
|
|
|
|
testWithoutContext('isRebootRequired returns true if the installation needs a reboot', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isRebootRequired'] = true;
|
|
setMockAnyVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isRebootRequired, true);
|
|
});
|
|
|
|
testWithoutContext('hasNecessaryComponents returns false when VS is present but missing components', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_defaultResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.hasNecessaryComponents, false);
|
|
});
|
|
|
|
testWithoutContext('cmakePath returns null when VS is present but missing components', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_defaultResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.cmakePath, isNull);
|
|
});
|
|
|
|
testWithoutContext('cmakePath 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<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isRebootRequired'] = true;
|
|
setMockCompatibleVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.cmakePath, isNull);
|
|
});
|
|
|
|
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<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isRebootRequired'] = true;
|
|
setMockCompatibleVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.hasNecessaryComponents, false);
|
|
});
|
|
|
|
testWithoutContext('VS metadata is available when VS is present, even if missing components', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockAnyVisualStudioInstallation(
|
|
_defaultResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
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'));
|
|
});
|
|
|
|
testWithoutContext('Warns and returns no installation when VS is present but vswhere returns invalid JSON', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setNoViableToolchainInstallation(fixture);
|
|
|
|
setMockEncodedAnyVisualStudioInstallation(
|
|
'{',
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, isFalse);
|
|
expect(visualStudio.isComplete, isFalse);
|
|
expect(visualStudio.isLaunchable, isFalse);
|
|
expect(visualStudio.isPrerelease, isFalse);
|
|
expect(visualStudio.isRebootRequired, isFalse);
|
|
expect(visualStudio.hasNecessaryComponents, isFalse);
|
|
expect(visualStudio.displayName, isNull);
|
|
expect(visualStudio.displayVersion, isNull);
|
|
expect(visualStudio.installLocation, isNull);
|
|
expect(visualStudio.fullVersion, isNull);
|
|
expect(visualStudio.cmakePath, isNull);
|
|
|
|
expect(fixture.logger.warningText, contains('Warning: Unexpected vswhere.exe JSON output'));
|
|
});
|
|
|
|
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,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
|
});
|
|
|
|
testWithoutContext('Everything returns good values when Build Tools is present with all components', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setMockCompatibleVisualStudioInstallation(
|
|
null,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
setMockCompatibleVisualStudioBuildToolsInstallation(
|
|
_defaultBuildToolsResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
});
|
|
|
|
testWithoutContext('properties return the right value for Visual Studio 2022', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setMockCompatibleVisualStudioInstallation(
|
|
_vs2022Response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 17 2022'));
|
|
});
|
|
|
|
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<String, dynamic> olderButCompleteVersionResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2017',
|
|
'installationVersion': '15.9.28307.665',
|
|
'catalog': <String, dynamic>{
|
|
'productDisplayVersion': '15.9.12',
|
|
},
|
|
};
|
|
|
|
setMockCompatibleVisualStudioInstallation(
|
|
olderButCompleteVersionResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
// Return a different version for queries without the required packages.
|
|
final Map<String, dynamic> incompleteVersionResponse = <String, dynamic>{
|
|
'installationPath': visualStudioPath,
|
|
'displayName': 'Visual Studio Community 2019',
|
|
'installationVersion': '16.1.1.1',
|
|
'catalog': <String, String>{
|
|
'productDisplayVersion': '16.1',
|
|
},
|
|
};
|
|
setMockAnyVisualStudioInstallation(
|
|
incompleteVersionResponse,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
|
|
expect(visualStudio.displayVersion, equals('15.9.12'));
|
|
});
|
|
|
|
testWithoutContext('SDK version returns the latest version when present', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setMockSdkRegResponse(
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.getWindows10SDKVersion(), '10.0.18362.0');
|
|
});
|
|
|
|
testWithoutContext('SDK version returns null when the registry key is not present', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setMockSdkRegResponse(
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
registryPresent: false,
|
|
);
|
|
|
|
expect(visualStudio.getWindows10SDKVersion(), null);
|
|
});
|
|
|
|
testWithoutContext('SDK version returns null when there are no SDK files present', () {
|
|
final VisualStudioFixture fixture = setUpVisualStudio();
|
|
final VisualStudio visualStudio = fixture.visualStudio;
|
|
|
|
setMockSdkRegResponse(
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
filesPresent: false,
|
|
);
|
|
|
|
expect(visualStudio.getWindows10SDKVersion(), null);
|
|
});
|
|
});
|
|
|
|
// The output of vswhere.exe is known to contain bad UTF8.
|
|
// See: https://github.com/flutter/flutter/issues/102451
|
|
group('Correctly handles bad UTF-8 from vswhere.exe output', () {
|
|
late VisualStudioFixture fixture;
|
|
late VisualStudio visualStudio;
|
|
|
|
setUp(() {
|
|
fixture = setUpVisualStudio();
|
|
visualStudio = fixture.visualStudio;
|
|
});
|
|
|
|
testWithoutContext('Ignores unicode replacement char in unused properties', () {
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['unused'] = 'Bad UTF8 \u{FFFD}';
|
|
|
|
setMockCompatibleVisualStudioInstallation(
|
|
response,
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
|
});
|
|
|
|
testWithoutContext('Throws ToolExit on bad UTF-8 in installationPath', () {
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['installationPath'] = '\u{FFFD}';
|
|
|
|
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
|
|
|
expect(() => visualStudio.isInstalled,
|
|
throwsToolExit(message: 'Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found in string'));
|
|
});
|
|
|
|
testWithoutContext('Throws ToolExit on bad UTF-8 in installationVersion', () {
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['installationVersion'] = '\u{FFFD}';
|
|
|
|
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
|
|
|
expect(() => visualStudio.isInstalled,
|
|
throwsToolExit(message: 'Bad UTF-8 encoding (U+FFFD; REPLACEMENT CHARACTER) found in string'));
|
|
});
|
|
|
|
testWithoutContext('Ignores bad UTF-8 in displayName', () {
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['displayName'] = '\u{FFFD}';
|
|
|
|
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
|
expect(visualStudio.displayName, equals('\u{FFFD}'));
|
|
});
|
|
|
|
testWithoutContext("Ignores bad UTF-8 in catalog's productDisplayVersion", () {
|
|
final Map<String, dynamic> catalog = Map<String, dynamic>.of(_defaultResponse['catalog'] as Map<String, dynamic>)
|
|
..['productDisplayVersion'] = '\u{FFFD}';
|
|
final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
|
|
..['catalog'] = catalog;
|
|
|
|
setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
|
expect(visualStudio.displayVersion, equals('\u{FFFD}'));
|
|
});
|
|
|
|
testWithoutContext('Ignores malformed JSON in description property', () {
|
|
setMockVswhereResponse(
|
|
fixture.fileSystem,
|
|
fixture.processManager,
|
|
_requirements,
|
|
<String>['-version', '16'],
|
|
null,
|
|
_malformedDescriptionResponse,
|
|
);
|
|
|
|
expect(visualStudio.isInstalled, true);
|
|
expect(visualStudio.isAtLeastMinimumVersion, true);
|
|
expect(visualStudio.hasNecessaryComponents, true);
|
|
expect(visualStudio.cmakePath, equals(cmakePath));
|
|
expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
|
|
expect(visualStudio.displayVersion, equals('16.2.5'));
|
|
|
|
expect(fixture.logger.warningText, isEmpty);
|
|
});
|
|
});
|
|
|
|
group(VswhereDetails, () {
|
|
test('Accepts empty JSON', () {
|
|
const bool meetsRequirements = true;
|
|
final Map<String, dynamic> json = <String, dynamic>{};
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json);
|
|
|
|
expect(result.installationPath, null);
|
|
expect(result.displayName, null);
|
|
expect(result.fullVersion, null);
|
|
expect(result.isComplete, null);
|
|
expect(result.isLaunchable, null);
|
|
expect(result.isRebootRequired, null);
|
|
expect(result.isPrerelease, null);
|
|
expect(result.catalogDisplayVersion, null);
|
|
expect(result.isUsable, isTrue);
|
|
});
|
|
|
|
test('Ignores unknown JSON properties', () {
|
|
const bool meetsRequirements = true;
|
|
final Map<String, dynamic> json = <String, dynamic>{
|
|
'hello': 'world',
|
|
};
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json);
|
|
|
|
expect(result.installationPath, null);
|
|
expect(result.displayName, null);
|
|
expect(result.fullVersion, null);
|
|
expect(result.isComplete, null);
|
|
expect(result.isLaunchable, null);
|
|
expect(result.isRebootRequired, null);
|
|
expect(result.isPrerelease, null);
|
|
expect(result.catalogDisplayVersion, null);
|
|
expect(result.isUsable, isTrue);
|
|
});
|
|
|
|
test('Accepts JSON', () {
|
|
const bool meetsRequirements = true;
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse);
|
|
|
|
expect(result.installationPath, visualStudioPath);
|
|
expect(result.displayName, 'Visual Studio Community 2019');
|
|
expect(result.fullVersion, '16.2.29306.81');
|
|
expect(result.isComplete, true);
|
|
expect(result.isLaunchable, true);
|
|
expect(result.isRebootRequired, false);
|
|
expect(result.isPrerelease, false);
|
|
expect(result.catalogDisplayVersion, '16.2.5');
|
|
expect(result.isUsable, isTrue);
|
|
});
|
|
|
|
test('Installation that does not satisfy requirements is not usable', () {
|
|
const bool meetsRequirements = false;
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse);
|
|
|
|
expect(result.isUsable, isFalse);
|
|
});
|
|
|
|
test('Incomplete installation is not usable', () {
|
|
const bool meetsRequirements = true;
|
|
final Map<String, dynamic> json = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isComplete'] = false;
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json);
|
|
|
|
expect(result.isUsable, isFalse);
|
|
});
|
|
|
|
test('Unlaunchable installation is not usable', () {
|
|
const bool meetsRequirements = true;
|
|
final Map<String, dynamic> json = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isLaunchable'] = false;
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json);
|
|
|
|
expect(result.isUsable, isFalse);
|
|
});
|
|
|
|
test('Installation that requires reboot is not usable', () {
|
|
const bool meetsRequirements = true;
|
|
final Map<String, dynamic> json = Map<String, dynamic>.of(_defaultResponse)
|
|
..['isRebootRequired'] = true;
|
|
|
|
final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json);
|
|
|
|
expect(result.isUsable, isFalse);
|
|
});
|
|
});
|
|
}
|
|
|
|
class VisualStudioFixture {
|
|
VisualStudioFixture(this.visualStudio, this.fileSystem, this.processManager, this.logger);
|
|
|
|
final VisualStudio visualStudio;
|
|
final FileSystem fileSystem;
|
|
final FakeProcessManager processManager;
|
|
final BufferLogger logger;
|
|
}
|