visual_studio_test.dart 28.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// 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';
6 7 8
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';
9
import 'package:flutter_tools/src/base/platform.dart';
10 11 12
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/windows/visual_studio.dart';

13
import '../../src/common.dart';
14
import '../../src/fake_process_manager.dart';
15 16 17

const String programFilesPath = r'C:\Program Files (x86)';
const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community';
18
const String cmakePath = visualStudioPath + r'\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe';
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
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',
  },
};

42 43 44 45 46 47 48 49 50 51 52 53 54 55
// 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',
  },
};

56 57 58 59 60 61 62 63 64 65 66 67 68 69
// 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',
  },
};

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
// 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',
  },
};

95 96 97 98
// Arguments for a vswhere query to search for an installation with the
// requirements.
const List<String> _requirements = <String>[
  'Microsoft.VisualStudio.Workload.NativeDesktop',
99
  'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
100
  'Microsoft.VisualStudio.Component.VC.CMake.Project',
101 102
];

103 104 105 106 107 108 109 110
// 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',
];

111 112 113 114 115 116
// 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, [
117 118 119 120 121 122
  List<String>? requiredComponents,
  List<String>? additionalArguments,
  Map<String, dynamic>? response,
  String? responseOverride,
  int? exitCode,
  Exception? exception,
123 124
]) {
  fileSystem.file(vswherePath).createSync(recursive: true);
125
  fileSystem.file(cmakePath).createSync(recursive: true);
126
  final String finalResponse = responseOverride
127
    ?? (response != null ? json.encode(<Map<String, dynamic>>[response]) : '');
128 129 130 131 132 133 134 135 136
  final List<String> requirementArguments = requiredComponents == null
    ? <String>[]
    : <String>['-requires', ...requiredComponents];

  processManager.addCommand(FakeCommand(
    command: <String>[
      vswherePath,
      '-format',
      'json',
137 138
      '-products',
      '*',
139 140 141
      '-utf8',
      '-latest',
      ...?additionalArguments,
142
      ...requirementArguments,
143 144
    ],
    stdout: finalResponse,
145 146
    exception: exception,
    exitCode: exitCode ?? 0,
147 148
  ));
}
149

150 151 152
// Sets whether or not a vswhere query with the required components will
// return an installation.
void setMockCompatibleVisualStudioInstallation(
153
  Map<String, dynamic>? response,
154
  FileSystem fileSystem,
155
  FakeProcessManager processManager, [
156 157
  int? exitCode,
  Exception? exception,
158
]) {
159 160 161
  setMockVswhereResponse(
    fileSystem,
    processManager,
162
    _requirements,
163 164
    <String>['-version', '16'],
    response,
165 166 167
    null,
    exitCode,
    exception,
168
  );
169 170
}

171 172 173
// Sets whether or not a vswhere query with the required components will
// return a pre-release installation.
void setMockPrereleaseVisualStudioInstallation(
174
  Map<String, dynamic>? response,
175
  FileSystem fileSystem,
176
  FakeProcessManager processManager, [
177 178
  int? exitCode,
  Exception? exception,
179
]) {
180 181 182
  setMockVswhereResponse(
    fileSystem,
    processManager,
183
    _requirements,
184 185
    <String>['-version', '16', '-prerelease'],
    response,
186 187 188
    null,
    exitCode,
    exception,
189 190
  );
}
191

192 193 194
// Sets whether or not a vswhere query with the required components will
// return an Build Tools installation.
void setMockCompatibleVisualStudioBuildToolsInstallation(
195
  Map<String, dynamic>? response,
196
  FileSystem fileSystem,
197
  FakeProcessManager processManager, [
198 199
  int? exitCode,
  Exception? exception,
200
]) {
201 202 203 204 205 206
  setMockVswhereResponse(
    fileSystem,
    processManager,
    _requirementsBuildTools,
    <String>['-version', '16'],
    response,
207 208 209
    null,
    exitCode,
    exception,
210 211 212 213 214 215
  );
}

// Sets whether or not a vswhere query with the required components will
// return a pre-release Build Tools installation.
void setMockPrereleaseVisualStudioBuildToolsInstallation(
216
  Map<String, dynamic>? response,
217
  FileSystem fileSystem,
218
  FakeProcessManager processManager, [
219 220
  int? exitCode,
  Exception? exception,
221
]) {
222 223 224 225 226 227
  setMockVswhereResponse(
    fileSystem,
    processManager,
    _requirementsBuildTools,
    <String>['-version', '16', '-prerelease'],
    response,
228 229 230
    null,
    exitCode,
    exception,
231 232 233
  );
}

234 235 236
// Sets whether or not a vswhere query searching for 'all' and 'prerelease'
// versions will return an installation.
void setMockAnyVisualStudioInstallation(
237
  Map<String, dynamic>? response,
238
  FileSystem fileSystem,
239
  FakeProcessManager processManager, [
240 241
  int? exitCode,
  Exception? exception,
242
]) {
243 244 245 246 247 248
  setMockVswhereResponse(
    fileSystem,
    processManager,
    null,
    <String>['-prerelease', '-all'],
    response,
249 250 251
    null,
    exitCode,
    exception,
252 253
  );
}
254

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
// Set a pre-encoded query result.
void setMockEncodedAnyVisualStudioInstallation(
  String response,
  FileSystem fileSystem,
  FakeProcessManager processManager,
) {
  setMockVswhereResponse(
    fileSystem,
    processManager,
    null,
    <String>['-prerelease', '-all'],
    null,
    response,
  );
}
270

271 272 273
// Sets up the mock environment for a Windows 10 SDK query.
//
// registryPresent controls whether or not the registry key is found.
274
// filesPresent controls where or not there are any SDK folders at the location
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
// 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,
  ));
}

315 316
// Create a visual studio instance with a FakeProcessManager.
VisualStudioFixture setUpVisualStudio() {
317
  final FakeProcessManager processManager = FakeProcessManager.empty();
318 319 320 321 322 323 324 325 326 327 328
  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);
}

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
// 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,
  );
}

355 356
void main() {
  group('Visual Studio', () {
357 358 359 360 361 362 363 364 365 366 367 368
    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'));
    });

369 370 371
    testWithoutContext('isInstalled and cmakePath correct when vswhere is missing', () {
      final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
      const Exception exception = ProcessException('vswhere', <String>[]);
372
      final FakeProcessManager fakeProcessManager = FakeProcessManager.empty();
373

374 375 376 377 378
      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);
379

380 381
      final VisualStudio visualStudio = VisualStudio(
        logger: BufferLogger.test(),
382
        fileSystem: fileSystem,
383
        platform: windowsPlatform,
384
        processManager: fakeProcessManager,
385
      );
386

387
      expect(visualStudio.isInstalled, false);
388
      expect(visualStudio.cmakePath, isNull);
389 390
    });

391 392
    testWithoutContext(
        'isInstalled returns false when vswhere returns non-zero', () {
393
      final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
394
      final FakeProcessManager fakeProcessManager = FakeProcessManager.empty();
395 396 397 398 399 400 401

      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);

402 403
      final VisualStudio visualStudio = VisualStudio(
        logger: BufferLogger.test(),
404
        fileSystem: fileSystem,
405
        platform: windowsPlatform,
406
        processManager: fakeProcessManager,
407
      );
408 409

      expect(visualStudio.isInstalled, false);
410
      expect(visualStudio.cmakePath, isNull);
411 412
    });

413 414 415 416
    testWithoutContext('VisualStudio getters return the right values if no installation is found', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

417 418
      setNoViableToolchainInstallation(fixture);

419 420 421 422 423
      setMockAnyVisualStudioInstallation(
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
424 425

      expect(visualStudio.isInstalled, false);
426
      expect(visualStudio.isAtLeastMinimumVersion, false);
427 428 429 430 431 432 433 434 435 436
      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);
    });

437 438 439 440 441 442 443 444 445
    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,
      );
446

447
      final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
448

449
      expect(toolsString.contains('v142'), true);
450 451
    });

452 453 454 455
    testWithoutContext('necessaryComponentDescriptions suggest the right VS tools on an old version', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

456 457
      setNoViableToolchainInstallation(fixture);

458 459 460 461 462
      setMockAnyVisualStudioInstallation(
        _tooOldResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
463

464
      final String toolsString = visualStudio.necessaryComponentDescriptions()[0];
465

466 467 468
      expect(toolsString.contains('v142'), true);
    });

469 470 471 472
    testWithoutContext('isInstalled returns true even with missing status information', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

473 474
      setNoViableToolchainInstallation(fixture);

475 476 477 478 479
      setMockAnyVisualStudioInstallation(
        _missingStatusResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
480 481 482 483

      expect(visualStudio.isInstalled, true);
    });

484 485 486 487
    testWithoutContext('isInstalled returns true when VS is present but missing components', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

488 489
      setNoViableToolchainInstallation(fixture);

490 491 492 493 494
      setMockAnyVisualStudioInstallation(
        _defaultResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
495 496 497 498

      expect(visualStudio.isInstalled, true);
    });

499 500 501 502
    testWithoutContext('isInstalled returns true when VS is present but too old', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

503 504
      setNoViableToolchainInstallation(fixture);

505 506 507 508 509
      setMockAnyVisualStudioInstallation(
        _tooOldResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
510 511 512 513

      expect(visualStudio.isInstalled, true);
    });

514 515 516
    testWithoutContext('isInstalled returns true when a prerelease version of VS is present', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;
517

518
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
519
        ..['isPrerelease'] = true;
520 521 522 523 524
      setMockCompatibleVisualStudioInstallation(
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
525 526
      setMockCompatibleVisualStudioBuildToolsInstallation(
        null,
527 528 529
        fixture.fileSystem,
        fixture.processManager,
      );
530 531
      setMockPrereleaseVisualStudioInstallation(
        response,
532 533 534
        fixture.fileSystem,
        fixture.processManager,
      );
535

536
      expect(visualStudio.isInstalled, true);
537
      expect(visualStudio.isPrerelease, true);
538 539
    });

540
    testWithoutContext('isInstalled returns true when a prerelease version of Build Tools is present', () {
541 542 543
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

544 545
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultBuildToolsResponse)
        ..['isPrerelease'] = true;
546 547 548 549 550
      setMockCompatibleVisualStudioInstallation(
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
551 552 553 554 555
      setMockCompatibleVisualStudioBuildToolsInstallation(
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
556 557 558 559 560
      setMockPrereleaseVisualStudioInstallation(
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
      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);

577 578 579 580 581
      setMockAnyVisualStudioInstallation(
        _tooOldResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
582 583 584 585 586

      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isAtLeastMinimumVersion, false);
    });

587 588 589 590
    testWithoutContext('isComplete returns false when an incomplete installation is found', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

591
      setNoViableToolchainInstallation(fixture);
592

593
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
594
        ..['isComplete'] = false;
595 596 597 598 599
      setMockAnyVisualStudioInstallation(
        response,
        fixture.fileSystem,
        fixture.processManager,
      );
600 601 602 603 604

      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isComplete, false);
    });

605 606 607 608 609
    testWithoutContext(
        "isLaunchable returns false if the installation can't be launched", () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

610
      setNoViableToolchainInstallation(fixture);
611

612
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
613
        ..['isLaunchable'] = false;
614 615 616 617 618
      setMockAnyVisualStudioInstallation(
        response,
        fixture.fileSystem,
        fixture.processManager,
      );
619 620 621 622 623

      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isLaunchable, false);
    });

624 625 626 627
    testWithoutContext('isRebootRequired returns true if the installation needs a reboot', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

628
      setNoViableToolchainInstallation(fixture);
629

630
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
631
        ..['isRebootRequired'] = true;
632 633 634 635 636
      setMockAnyVisualStudioInstallation(
        response,
        fixture.fileSystem,
        fixture.processManager,
      );
637 638 639 640 641

      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isRebootRequired, true);
    });

642 643 644 645
    testWithoutContext('hasNecessaryComponents returns false when VS is present but missing components', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

646 647
      setNoViableToolchainInstallation(fixture);

648 649 650 651 652
      setMockAnyVisualStudioInstallation(
        _defaultResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
653

654 655 656
      expect(visualStudio.hasNecessaryComponents, false);
    });

657
    testWithoutContext('cmakePath returns null when VS is present but missing components', () {
658 659 660
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

661 662
      setNoViableToolchainInstallation(fixture);

663 664 665 666 667
      setMockAnyVisualStudioInstallation(
        _defaultResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
668

669
      expect(visualStudio.cmakePath, isNull);
670 671
    });

672
    testWithoutContext('cmakePath returns null when VS is present but with require components but installation is faulty', () {
673 674 675
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

676
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
677
        ..['isRebootRequired'] = true;
678 679 680 681 682
      setMockCompatibleVisualStudioInstallation(
        response,
        fixture.fileSystem,
        fixture.processManager,
      );
683

684
      expect(visualStudio.cmakePath, isNull);
685 686
    });

687 688 689 690
    testWithoutContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

691
      final Map<String, dynamic> response = Map<String, dynamic>.of(_defaultResponse)
692
        ..['isRebootRequired'] = true;
693 694 695 696 697
      setMockCompatibleVisualStudioInstallation(
        response,
        fixture.fileSystem,
        fixture.processManager,
      );
698

699 700 701
      expect(visualStudio.hasNecessaryComponents, false);
    });

702 703 704 705
    testWithoutContext('VS metadata is available when VS is present, even if missing components', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

706 707
      setNoViableToolchainInstallation(fixture);

708 709 710 711 712
      setMockAnyVisualStudioInstallation(
        _defaultResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
713

714 715
      expect(visualStudio.displayName, equals('Visual Studio Community 2019'));
      expect(visualStudio.displayVersion, equals('16.2.5'));
716
      expect(visualStudio.installLocation, equals(visualStudioPath));
717
      expect(visualStudio.fullVersion, equals('16.2.29306.81'));
718 719
    });

720
    testWithoutContext('cmakePath returns null when VS is present but when vswhere returns invalid JSON', () {
721 722 723
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

724 725
      setNoViableToolchainInstallation(fixture);

726 727 728 729 730
      setMockEncodedAnyVisualStudioInstallation(
        '{',
        fixture.fileSystem,
        fixture.processManager,
      );
731

732
      expect(visualStudio.cmakePath, isNull);
733 734
    });

735 736 737 738 739 740 741 742 743
    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,
      );
744 745 746 747 748

      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isAtLeastMinimumVersion, true);
      expect(visualStudio.hasNecessaryComponents, true);
      expect(visualStudio.cmakePath, equals(cmakePath));
749
      expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019'));
750 751 752 753 754 755 756
    });

    testWithoutContext('Everything returns good values when Build Tools is present with all components', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

      setMockCompatibleVisualStudioInstallation(
757 758 759 760
        null,
        fixture.fileSystem,
        fixture.processManager,
      );
761 762
      setMockCompatibleVisualStudioBuildToolsInstallation(
        _defaultBuildToolsResponse,
763 764 765
        fixture.fileSystem,
        fixture.processManager,
      );
766 767

      expect(visualStudio.isInstalled, true);
768
      expect(visualStudio.isAtLeastMinimumVersion, true);
769
      expect(visualStudio.hasNecessaryComponents, true);
770
      expect(visualStudio.cmakePath, equals(cmakePath));
771 772
    });

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789
    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'));
    });

790 791 792 793
    testWithoutContext('Metadata is for compatible version when latest is missing components', () {
      final VisualStudioFixture fixture = setUpVisualStudio();
      final VisualStudio visualStudio = fixture.visualStudio;

794 795 796 797 798 799 800
      // 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',
801
        },
802 803
      };

804 805 806 807 808
      setMockCompatibleVisualStudioInstallation(
        olderButCompleteVersionResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
809
      // Return a different version for queries without the required packages.
810 811 812 813 814 815 816 817
      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',
        },
      };
818 819 820 821 822
      setMockAnyVisualStudioInstallation(
        incompleteVersionResponse,
        fixture.fileSystem,
        fixture.processManager,
      );
823 824 825 826

      expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
      expect(visualStudio.displayVersion, equals('15.9.12'));
    });
827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864

    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);
    });
865 866
  });
}
867 868 869 870 871 872 873 874

class VisualStudioFixture {
  VisualStudioFixture(this.visualStudio, this.fileSystem, this.processManager);

  final VisualStudio visualStudio;
  final FileSystem fileSystem;
  final FakeProcessManager processManager;
}