blob: 6f2aa45411a32910840352b814cd246d5cdf2737 [file] [log] [blame]
// 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;
}