visual_studio_test.dart 17.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright 2019 The Chromium 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, 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 74
  void setMockVswhereResponse([
    List<String> requiredComponents,
    List<String> additionalArguments,
    Map<String, dynamic> response,
  ]) {
75 76 77 78 79 80
    fs.file(vswherePath).createSync(recursive: true);
    fs.file(vcvarsPath).createSync(recursive: true);

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

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

105 106 107 108
  // 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);
109 110 111
  }

  // Sets whether or not a vswhere query with the required components will
112 113 114 115 116 117 118 119 120
  // 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.
  void setMockAnyVisualStudioInstallation(Map<String, dynamic>response) {
    setMockVswhereResponse(null, <String>['-prerelease', '-all'], response);
121 122 123 124 125 126 127 128 129 130
  }

  group('Visual Studio', () {
    VisualStudio visualStudio;

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

    testUsingContext('isInstalled returns false when vswhere is missing', () {
131 132 133 134 135
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));
136 137 138 139 140 141

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

    testUsingContext('vcvarsPath returns null when vswhere is missing', () {
146 147 148 149 150
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenThrow(const ProcessException('vswhere', <String>[]));
151 152 153 154 155 156

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

    testUsingContext('isInstalled returns false when vswhere returns non-zero', () {
161 162 163 164 165 166 167

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

168
      final MockProcessResult result = MockProcessResult();
169
      when(result.exitCode).thenReturn(1);
170 171 172 173 174
      when(mockProcessManager.runSync(
        any,
        workingDirectory: anyNamed('workingDirectory'),
        environment: anyNamed('environment'),
      )).thenAnswer((Invocation invocation) {
175 176
        return result;
      });
177 178
      when<String>(result.stdout).thenReturn('');
      when<String>(result.stderr).thenReturn('');
179 180 181 182 183 184

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

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    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,
206
      Platform: () => windowsPlatform,
207 208
    });

209 210 211 212 213 214 215 216
    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,
217
      Platform: () => windowsPlatform,
218 219 220 221 222 223 224 225 226 227
    });

    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,
228
      Platform: () => windowsPlatform,
229 230
    });

231 232 233
    testUsingContext('isInstalled returns true even with missing status information', () {
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
234
      setMockAnyVisualStudioInstallation(_missingStatusResponse);
235 236 237 238 239 240

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

244
    testUsingContext('isInstalled returns true when VS is present but missing components', () {
245 246 247 248 249 250 251 252 253
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
254
      Platform: () => windowsPlatform,
255 256 257 258 259 260 261
    });

    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)
262
        ..['isPrerelease'] = true;
263 264
      setMockPrereleaseVisualStudioInstallation(response);

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

274 275 276 277 278 279 280 281 282 283 284 285 286 287
    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,
288
      Platform: () => windowsPlatform,
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    });

    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,
305
      Platform: () => windowsPlatform,
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
    });

    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,
322
      Platform: () => windowsPlatform,
323 324 325
    });


326
    testUsingContext('hasNecessaryComponents returns false when VS is present but missing components', () {
327 328 329
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
330 331 332 333 334 335

      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
336
      Platform: () => windowsPlatform,
337 338 339
    });

    testUsingContext('vcvarsPath returns null when VS is present but missing components', () {
340 341 342
      setMockCompatibleVisualStudioInstallation(null);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(_defaultResponse);
343 344 345 346 347 348

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

352 353 354 355 356
    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);
357 358

      visualStudio = VisualStudio();
359
      expect(visualStudio.vcvarsPath, isNull);
360 361 362
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
363
      Platform: () => windowsPlatform,
364 365
    });

366 367 368 369 370
    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);
371

372 373 374 375 376
      visualStudio = VisualStudio();
      expect(visualStudio.hasNecessaryComponents, false);
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
377
      Platform: () => windowsPlatform,
378 379 380 381 382 383
    });

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

      visualStudio = VisualStudio();
386 387
      expect(visualStudio.displayName, equals('Visual Studio Community 2019'));
      expect(visualStudio.displayVersion, equals('16.2.5'));
388
      expect(visualStudio.installLocation, equals(visualStudioPath));
389
      expect(visualStudio.fullVersion, equals('16.2.29306.81'));
390 391 392
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
393
      Platform: () => windowsPlatform,
394 395 396
    });

    testUsingContext('Everything returns good values when VS is present with all components', () {
397 398 399
      setMockCompatibleVisualStudioInstallation(_defaultResponse);
      setMockPrereleaseVisualStudioInstallation(null);
      setMockAnyVisualStudioInstallation(null);
400 401 402 403 404 405 406 407

      visualStudio = VisualStudio();
      expect(visualStudio.isInstalled, true);
      expect(visualStudio.hasNecessaryComponents, true);
      expect(visualStudio.vcvarsPath, equals(vcvarsPath));
    }, overrides: <Type, Generator>{
      FileSystem: () => memoryFilesystem,
      ProcessManager: () => mockProcessManager,
408
      Platform: () => windowsPlatform,
409 410 411
    });

    testUsingContext('Metadata is for compatible version when latest is missing components', () {
412 413 414 415 416 417 418
      // 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',
419
        },
420 421 422
      };

      setMockCompatibleVisualStudioInstallation(olderButCompleteVersionResponse);
423
      setMockPrereleaseVisualStudioInstallation(null);
424
      // Return a different version for queries without the required packages.
425 426 427 428 429 430 431 432 433
      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);
434 435 436 437 438 439 440

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