visual_studio_test.dart 20.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// 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, ProcessResult;
8

9 10
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/windows/visual_studio.dart';
11
import 'package:flutter_tools/src/globals.dart' as globals;
12 13
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
14
import 'package:platform/platform.dart';
15

16 17
import '../../src/common.dart';
import '../../src/context.dart';
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

class MockPlatform extends Mock implements Platform {
  @override
  Map<String, String> environment = <String, String>{};
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcessResult extends Mock implements ProcessResult {}

void main() {
  const String programFilesPath = r'C:\Program Files (x86)';
  const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community';
  const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
  const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe';

  final MockPlatform windowsPlatform = MockPlatform()
      ..environment['PROGRAMFILES(X86)'] = r'C:\Program Files (x86)\';
  MockProcessManager mockProcessManager;
  final MemoryFileSystem memoryFilesystem = MemoryFileSystem(style: FileSystemStyle.windows);

37 38 39
  // A minimum version of a response where a VS installation was found.
  const Map<String, dynamic> _defaultResponse = <String, dynamic>{
    'installationPath': visualStudioPath,
40 41
    'displayName': 'Visual Studio Community 2019',
    'installationVersion': '16.2.29306.81',
42 43 44
    'isRebootRequired': false,
    'isComplete': true,
    'isLaunchable': true,
45 46
    'isPrerelease': false,
    'catalog': <String, dynamic>{
47
      'productDisplayVersion': '16.2.5',
48
    },
49 50
  };

51 52
  // A response for a VS installation that's too old.
  const Map<String, dynamic> _tooOldResponse = <String, dynamic>{
53 54 55
    'installationPath': visualStudioPath,
    'displayName': 'Visual Studio Community 2017',
    'installationVersion': '15.9.28307.665',
56 57 58 59
    'isRebootRequired': false,
    'isComplete': true,
    'isLaunchable': true,
    'isPrerelease': false,
60 61
    'catalog': <String, dynamic>{
      'productDisplayVersion': '15.9.12',
62
    },
63 64
  };

65 66 67 68 69 70 71 72 73 74 75
  // 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',
    },
  };

76 77 78 79 80 81 82
  // Arguments for a vswhere query to search for an installation with the required components.
  const List<String> _requiredComponents = <String>[
    'Microsoft.Component.MSBuild',
    'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
    'Microsoft.VisualStudio.Component.Windows10SDK.17763',
  ];

83 84 85
  // 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.
86 87 88 89
  void setMockVswhereResponse([
    List<String> requiredComponents,
    List<String> additionalArguments,
    Map<String, dynamic> response,
90
    String responseOverride,
91
  ]) {
92 93
    globals.fs.file(vswherePath).createSync(recursive: true);
    globals.fs.file(vcvarsPath).createSync(recursive: true);
94 95 96 97

    final MockProcessResult result = MockProcessResult();
    when(result.exitCode).thenReturn(0);

98
    final String finalResponse = responseOverride ??
99
        json.encode(<Map<String, dynamic>>[response]);
100 101
    when<String>(result.stdout as String).thenReturn(finalResponse);
    when<String>(result.stderr as String).thenReturn('');
102 103 104
    final List<String> requirementArguments = requiredComponents == null
        ? <String>[]
        : <String>['-requires', ...requiredComponents];
105 106 107
    when(mockProcessManager.runSync(
      <String>[
        vswherePath,
108 109 110 111 112 113
        '-format',
        'json',
        '-utf8',
        '-latest',
        ...?additionalArguments,
        ...?requirementArguments,
114 115 116 117
      ],
      workingDirectory: anyNamed('workingDirectory'),
      environment: anyNamed('environment'),
    )).thenAnswer((Invocation invocation) {
118 119 120 121
      return result;
    });
  }

122 123 124
  // Sets whether or not a vswhere query with the required components will
  // return an installation.
  void setMockCompatibleVisualStudioInstallation(Map<String, dynamic>response) {
125
    setMockVswhereResponse(_requiredComponents, <String>['-version', '16'], response);
126 127 128
  }

  // Sets whether or not a vswhere query with the required components will
129 130
  // return a pre-release installation.
  void setMockPrereleaseVisualStudioInstallation(Map<String, dynamic>response) {
131
    setMockVswhereResponse(_requiredComponents, <String>['-version', '16', '-prerelease'], response);
132 133 134 135
  }

  // Sets whether or not a vswhere query searching for 'all' and 'prerelease'
  // versions will return an installation.
136
  void setMockAnyVisualStudioInstallation(Map<String, dynamic> response) {
137
    setMockVswhereResponse(null, <String>['-prerelease', '-all'], response);
138 139
  }

140 141 142 143 144
  // Set a pre-encoded query result.
  void setMockEncodedAnyVisualStudioInstallation(String response) {
    setMockVswhereResponse(null, <String>['-prerelease', '-all'], null, response);
  }

145 146 147 148 149 150 151 152
  group('Visual Studio', () {
    VisualStudio visualStudio;

    setUp(() {
      mockProcessManager = MockProcessManager();
    });

    testUsingContext('isInstalled returns false when vswhere is missing', () {
153 154 155 156 157
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));
158 159 160 161 162 163

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
164
      Platform: () => windowsPlatform,
165 166 167
    });

    testUsingContext('vcvarsPath returns null when vswhere is missing', () {
168 169 170 171 172
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));
173 174 175 176 177 178

      visualStudio = VisualStudio();
      expect(visualStudio.vcvarsPath, isNull);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
179
      Platform: () => windowsPlatform,
180 181 182
    });

    testUsingContext('isInstalled returns false when vswhere returns non-zero', () {
183 184 185 186 187 188 189

      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));

190
      final MockProcessResult result = MockProcessResult();
191
      when(result.exitCode).thenReturn(1);
192 193 194 195 196
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenAnswer((Invocation invocation) {
197 198
        return result;
      });
199 200
      when<String>(result.stdout as String).thenReturn('');
      when<String>(result.stderr as String).thenReturn('');
201 202 203 204 205 206

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
207
      Platform: () => windowsPlatform,
208 209
    });

210 211 212 213 214 215 216
    testUsingContext('VisualStudio getters return the right values if no installation is found', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, false);
217
      expect(visualStudio.isAtLeastMinimumVersion, false);
218 219 220 221 222 223 224 225 226 227 228
      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);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
229
      Platform: () => windowsPlatform,
230 231
    });

232 233
    testUsingContext('necessaryComponentDescriptions suggest the right VS tools on major version 16', () {
      setMockCompatibleVisualStudioInstallation(_defaultResponse);
234 235

      visualStudio = VisualStudio();
236 237
      final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
      expect(toolsString.contains('v142'), true);
238 239 240
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
241
      Platform: () => windowsPlatform,
242 243
    });

244 245 246 247
    testUsingContext('necessaryComponentDescriptions suggest the right VS tools on an old version', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_tooOldResponse);
248 249

      visualStudio = VisualStudio();
250
      final String toolsString = visualStudio.necessaryComponentDescriptions()[1];
251 252 253 254
      expect(toolsString.contains('v142'), true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
255
      Platform: () => windowsPlatform,
256 257
    });

258 259 260
    testUsingContext('isInstalled returns true even with missing status information', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
261
      setMockAnyVisualStudioInstallation(_missingStatusResponse);
262 263 264 265 266 267

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
268
      Platform: () => windowsPlatform,
269 270
    });

271
    testUsingContext('isInstalled returns true when VS is present but missing components', () {
272 273 274 275 276 277 278 279 280
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
281
      Platform: () => windowsPlatform,
282 283
    });

284 285 286 287 288 289 290 291 292 293 294 295 296
    testUsingContext('isInstalled returns true when VS is present but too old', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_tooOldResponse);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
      Platform: () => windowsPlatform,
    });

297 298 299 300 301
    testUsingContext('isInstalled returns true when a prerelease version of VS is present', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);

      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
302
        ..['isPrerelease'] = true;
303 304
      setMockPrereleaseVisualStudioInstallation(response);

305 306
      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
307
      expect(visualStudio.isPrerelease, true);
308 309 310
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
311
      Platform: () => windowsPlatform,
312 313
    });

314 315 316 317 318 319 320 321 322 323 324 325 326 327
    testUsingContext('isAtLeastMinimumVersion returns false when the version found is too old', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_tooOldResponse);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isAtLeastMinimumVersion, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
      Platform: () => windowsPlatform,
    });

328 329 330 331 332 333 334 335 336 337 338 339 340 341
    testUsingContext('isComplete returns false when an incomplete installation is found', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);

      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
        ..['isComplete'] = false;
      setMockAnyVisualStudioInstallation(response);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isComplete, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
342
      Platform: () => windowsPlatform,
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    });

    testUsingContext('isLaunchable returns false if the installation can\'t be launched', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);

      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
        ..['isLaunchable'] = false;
      setMockAnyVisualStudioInstallation(response);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isLaunchable, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
359
      Platform: () => windowsPlatform,
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
    });

    testUsingContext('isRebootRequired returns true if the installation needs a reboot', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);

      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
        ..['isRebootRequired'] = true;
      setMockAnyVisualStudioInstallation(response);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.isRebootRequired, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
376
      Platform: () => windowsPlatform,
377 378 379
    });


380
    testUsingContext('hasNecessaryComponents returns false when VS is present but missing components', () {
381 382 383
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
384 385 386 387 388 389

      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
390
      Platform: () => windowsPlatform,
391 392 393
    });

    testUsingContext('vcvarsPath returns null when VS is present but missing components', () {
394 395 396
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
397 398 399 400 401 402

      visualStudio = VisualStudio();
      expect(visualStudio.vcvarsPath, isNull);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
403
      Platform: () => windowsPlatform,
404 405
    });

406 407 408 409 410
    testUsingContext('vcvarsPath returns null when VS is present but with require components but installation is faulty', () {
      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
        ..['isRebootRequired'] = true;
      setMockCompatibleVisualStudioInstallation(response);
      setMockPrereleaseVisualStudioInstallation(null);
411 412

      visualStudio = VisualStudio();
413
      expect(visualStudio.vcvarsPath, isNull);
414 415 416
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
417
      Platform: () => windowsPlatform,
418 419
    });

420 421 422 423 424
    testUsingContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () {
      final Map<String, dynamic> response = Map<String, dynamic>.from(_defaultResponse)
        ..['isRebootRequired'] = true;
      setMockCompatibleVisualStudioInstallation(response);
      setMockPrereleaseVisualStudioInstallation(null);
425

426 427 428 429 430
      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
431
      Platform: () => windowsPlatform,
432 433 434 435 436 437
    });

    testUsingContext('VS metadata is available when VS is present, even if missing components', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
438 439

      visualStudio = VisualStudio();
440 441
      expect(visualStudio.displayName, equals('Visual Studio Community 2019'));
      expect(visualStudio.displayVersion, equals('16.2.5'));
442
      expect(visualStudio.installLocation, equals(visualStudioPath));
443
      expect(visualStudio.fullVersion, equals('16.2.29306.81'));
444 445 446
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
447
      Platform: () => windowsPlatform,
448 449
    });

450 451 452 453 454 455 456 457 458 459 460 461 462
    testUsingContext('vcvarsPath returns null when VS is present but when vswhere returns invalid JSON', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockEncodedAnyVisualStudioInstallation('{');

      visualStudio = VisualStudio();
      expect(visualStudio.vcvarsPath, isNull);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
      Platform: () => windowsPlatform,
    });

463
    testUsingContext('Everything returns good values when VS is present with all components', () {
464 465 466
      setMockCompatibleVisualStudioInstallation(_defaultResponse);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);
467 468 469

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
470
      expect(visualStudio.isAtLeastMinimumVersion, true);
471 472 473 474 475
      expect(visualStudio.hasNecessaryComponents, true);
      expect(visualStudio.vcvarsPath, equals(vcvarsPath));
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
476
      Platform: () => windowsPlatform,
477 478 479
    });

    testUsingContext('Metadata is for compatible version when latest is missing components', () {
480 481 482 483 484 485 486
      // 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',
487
        },
488 489 490
      };

      setMockCompatibleVisualStudioInstallation(olderButCompleteVersionResponse);
491
      setMockPrereleaseVisualStudioInstallation(null);
492
      // Return a different version for queries without the required packages.
493 494 495 496 497 498 499 500 501
      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);
502 503 504 505 506 507 508

      visualStudio = VisualStudio();
      expect(visualStudio.displayName, equals('Visual Studio Community 2017'));
      expect(visualStudio.displayVersion, equals('15.9.12'));
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
509
      Platform: () => windowsPlatform,
510 511 512
    });
  });
}