visual_studio_test.dart 18.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 8 9 10 11 12 13
// 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;
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 'package:mockito/mockito.dart';
import 'package:process/process.dart';

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

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

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

  // A version of a response that doesn't include certain installation status
  // information that might be missing in older Visual Studio versions.
51
  const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{
52 53 54
    'installationPath': visualStudioPath,
    'displayName': 'Visual Studio Community 2017',
    'installationVersion': '15.9.28307.665',
55 56
    'catalog': <String, dynamic>{
      'productDisplayVersion': '15.9.12',
57
    },
58 59 60 61 62 63 64 65 66
  };

  // 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',
  ];

67 68 69
  // 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.
70 71 72 73
  void setMockVswhereResponse([
    List<String> requiredComponents,
    List<String> additionalArguments,
    Map<String, dynamic> response,
74
    String responseOverride,
75
  ]) {
76 77 78 79 80 81
    fs.file(vswherePath).createSync(recursive: true);
    fs.file(vcvarsPath).createSync(recursive: true);

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

82
    final String finalResponse = responseOverride ??
83
        json.encode(<Map<String, dynamic>>[response]);
84 85
    when<String>(result.stdout as String).thenReturn(finalResponse);
    when<String>(result.stderr as String).thenReturn('');
86 87 88
    final List<String> requirementArguments = requiredComponents == null
        ? <String>[]
        : <String>['-requires', ...requiredComponents];
89 90 91
    when(mockProcessManager.runSync(
      <String>[
        vswherePath,
92 93 94 95 96 97
        '-format',
        'json',
        '-utf8',
        '-latest',
        ...?additionalArguments,
        ...?requirementArguments,
98 99 100 101
      ],
      workingDirectory: anyNamed('workingDirectory'),
      environment: anyNamed('environment'),
    )).thenAnswer((Invocation invocation) {
102 103 104 105
      return result;
    });
  }

106 107 108 109
  // Sets whether or not a vswhere query with the required components will
  // return an installation.
  void setMockCompatibleVisualStudioInstallation(Map<String, dynamic>response) {
    setMockVswhereResponse(_requiredComponents, null, response);
110 111 112
  }

  // Sets whether or not a vswhere query with the required components will
113 114 115 116 117 118 119
  // return a pre-release installation.
  void setMockPrereleaseVisualStudioInstallation(Map<String, dynamic>response) {
    setMockVswhereResponse(_requiredComponents, <String>['-prerelease'], response);
  }

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

124 125 126 127 128
  // Set a pre-encoded query result.
  void setMockEncodedAnyVisualStudioInstallation(String response) {
    setMockVswhereResponse(null, <String>['-prerelease', '-all'], null, response);
  }

129 130 131 132 133 134 135 136
  group('Visual Studio', () {
    VisualStudio visualStudio;

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

    testUsingContext('isInstalled returns false when vswhere is missing', () {
137 138 139 140 141
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));
142 143 144 145 146 147

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

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

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

    testUsingContext('isInstalled returns false when vswhere returns non-zero', () {
167 168 169 170 171 172 173

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

174
      final MockProcessResult result = MockProcessResult();
175
      when(result.exitCode).thenReturn(1);
176 177 178 179 180
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenAnswer((Invocation invocation) {
181 182
        return result;
      });
183 184
      when<String>(result.stdout as String).thenReturn('');
      when<String>(result.stderr as String).thenReturn('');
185 186 187 188 189 190

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

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    testUsingContext('VisualStudio getters return the right values if no installation is found', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, 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);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
212
      Platform: () => windowsPlatform,
213 214
    });

215 216 217 218 219 220 221 222
    testUsingContext('necessaryComponentDescriptions suggest the right VS tools on major version 15', () {

      visualStudio = VisualStudio();
      final String toolsString = visualStudio.necessaryComponentDescriptions(15)[1];
      expect(toolsString.contains('v141'), true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
223
      Platform: () => windowsPlatform,
224 225 226 227 228 229 230 231 232 233
    });

    testUsingContext('necessaryComponentDescriptions suggest the right VS tools on major version != 15', () {

      visualStudio = VisualStudio();
      final String toolsString = visualStudio.necessaryComponentDescriptions(16)[1];
      expect(toolsString.contains('v142'), true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
234
      Platform: () => windowsPlatform,
235 236
    });

237 238 239
    testUsingContext('isInstalled returns true even with missing status information', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
240
      setMockAnyVisualStudioInstallation(_missingStatusResponse);
241 242 243 244 245 246

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

250
    testUsingContext('isInstalled returns true when VS is present but missing components', () {
251 252 253 254 255 256 257 258 259
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
260
      Platform: () => windowsPlatform,
261 262 263 264 265 266 267
    });

    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)
268
        ..['isPrerelease'] = true;
269 270
      setMockPrereleaseVisualStudioInstallation(response);

271 272
      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
273
      expect(visualStudio.isPrerelease, true);
274 275 276
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
277
      Platform: () => windowsPlatform,
278 279
    });

280 281 282 283 284 285 286 287 288 289 290 291 292 293
    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,
294
      Platform: () => windowsPlatform,
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    });

    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,
311
      Platform: () => windowsPlatform,
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
    });

    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,
328
      Platform: () => windowsPlatform,
329 330 331
    });


332
    testUsingContext('hasNecessaryComponents returns false when VS is present but missing components', () {
333 334 335
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
336 337 338 339 340 341

      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
342
      Platform: () => windowsPlatform,
343 344 345
    });

    testUsingContext('vcvarsPath returns null when VS is present but missing components', () {
346 347 348
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
349 350 351 352 353 354

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

358 359 360 361 362
    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);
363 364

      visualStudio = VisualStudio();
365
      expect(visualStudio.vcvarsPath, isNull);
366 367 368
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
369
      Platform: () => windowsPlatform,
370 371
    });

372 373 374 375 376
    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);
377

378 379 380 381 382
      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
383
      Platform: () => windowsPlatform,
384 385 386 387 388 389
    });

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

      visualStudio = VisualStudio();
392 393
      expect(visualStudio.displayName, equals('Visual Studio Community 2019'));
      expect(visualStudio.displayVersion, equals('16.2.5'));
394
      expect(visualStudio.installLocation, equals(visualStudioPath));
395
      expect(visualStudio.fullVersion, equals('16.2.29306.81'));
396 397 398
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
399
      Platform: () => windowsPlatform,
400 401
    });

402 403 404 405 406 407 408 409 410 411 412 413 414
    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,
    });

415
    testUsingContext('Everything returns good values when VS is present with all components', () {
416 417 418
      setMockCompatibleVisualStudioInstallation(_defaultResponse);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);
419 420 421 422 423 424 425 426

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.hasNecessaryComponents, true);
      expect(visualStudio.vcvarsPath, equals(vcvarsPath));
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
427
      Platform: () => windowsPlatform,
428 429 430
    });

    testUsingContext('Metadata is for compatible version when latest is missing components', () {
431 432 433 434 435 436 437
      // 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',
438
        },
439 440 441
      };

      setMockCompatibleVisualStudioInstallation(olderButCompleteVersionResponse);
442
      setMockPrereleaseVisualStudioInstallation(null);
443
      // Return a different version for queries without the required packages.
444 445 446 447 448 449 450 451 452
      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);
453 454 455 456 457 458 459

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