UNPKG

gulp-msbuild

Version:

msbuild plugin for gulp. Inspired by grunt-msbuild.

622 lines (487 loc) 22.9 kB
import { MSBuildFinder } from '../src/msbuild/msbuild-finder'; import { MSBuildOptions } from '../src/msbuild/msbuild-options'; import { ArchitectureService } from '../src/utilities/ArchitectureService'; import * as fs from 'fs'; import { execSync } from 'child_process'; // Mock modules jest.mock('fs'); jest.mock('child_process'); jest.mock('../src/utilities/ArchitectureService'); describe('MSBuildFinder', () => { let mockOptions: MSBuildOptions; const mockFs = fs as jest.Mocked<typeof fs>; const mockExecSync = execSync as jest.MockedFunction<typeof execSync>; beforeEach(() => { jest.clearAllMocks(); mockOptions = new MSBuildOptions({ toolsVersion: '15.0', windir: 'C:\\Windows' as any as any }); // Default mock for Architecture Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); }); describe('constructor', () => { it('should initialize with x64 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); const finder = new MSBuildFinder(mockOptions); expect(finder).toBeDefined(); }); it('should initialize with x86 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x86'), configurable: true }); const finder = new MSBuildFinder(mockOptions); expect(finder).toBeDefined(); }); it('should use default windir when not provided', () => { const options = new MSBuildOptions({ toolsVersion: '15.0' }); const finder = new MSBuildFinder(options); expect(finder).toBeDefined(); }); it('should default toolsVersion to auto when undefined', () => { const options = new MSBuildOptions({}); options.toolsVersion = undefined; mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(options); const result = finder.findVersion(); // When auto mode and nothing found, should return null expect(result).toBeNull(); }); it('should use default windir C:\\Windows when undefined', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); const options = new MSBuildOptions({ toolsVersion: '4.0' }); options.windir = undefined; const finder = new MSBuildFinder(options); const result = finder.concatPreV12('4.0'); // The default in the constructor is 'C:\Windows' (with single backslash in the string literal) expect(result).toContain('Windows'); expect(result).toContain('Framework64'); expect(result).toContain('v4.0.30319'); }); }); describe('findVersion', () => { it('should throw error for invalid version', () => { mockOptions = new MSBuildOptions({ toolsVersion: 'invalid', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); expect(() => finder.findVersion()).toThrow('version unknown'); }); it('should return null when vswhere.exe does not exist', () => { mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).toBeNull(); }); it('should handle missing ProgramFiles(x86) environment variable', () => { const originalEnv = process.env['ProgramFiles(x86)']; delete process.env['ProgramFiles(x86)']; mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).toBeNull(); // Restore if (originalEnv) { process.env['ProgramFiles(x86)'] = originalEnv; } }); it('should find version using vswhere.exe', () => { const mockInstallations = [ { installationPath: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community', installationVersion: '15.0.0.0' } ]; mockFs.existsSync.mockReturnValue(true); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).not.toBeNull(); expect(Array.isArray(result)).toBe(true); }); it('should handle execSync errors gracefully', () => { mockFs.existsSync.mockReturnValue(true); mockExecSync.mockImplementation(() => { throw new Error('Command failed'); }); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).toBeNull(); }); it('should find version for auto mode', () => { mockOptions = new MSBuildOptions({ toolsVersion: 'auto' as any, windir: 'C:\\Windows' as any as any }); mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).toBeNull(); }); it('should find pre-v12 version without requiring vswhere.exe', () => { mockOptions = new MSBuildOptions({ toolsVersion: '4.0', windir: 'C:\\Windows' as any }); // existsSync returns true only for the .NET Framework path (no vswhere) mockFs.existsSync.mockImplementation((p: any) => { return String(p).includes('Framework'); }); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); // vswhere should NOT have been called for a pre-v12 version expect(mockExecSync).not.toHaveBeenCalled(); expect(result).not.toBeNull(); expect(result![0]).toContain('MSBuild.exe'); expect(result![1]).toBe('4.0'); }); it('should return null when pre-v12 .NET Framework path does not exist', () => { mockOptions = new MSBuildOptions({ toolsVersion: '4.0', windir: 'C:\\Windows' as any }); mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(mockExecSync).not.toHaveBeenCalled(); expect(result).toBeNull(); }); it('should find MSBuild 18.0 by matching installationVersion major number', () => { mockOptions = new MSBuildOptions({ toolsVersion: '18.0', windir: 'C:\\Windows' as any }); // Simulate a VS 18 installation whose path may not contain '18' as a substring const mockInstallations = [ { installationPath: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community', installationVersion: '17.8.0.0' }, { installationPath: 'C:\\Program Files\\Microsoft Visual Studio\\2026\\Community', installationVersion: '18.0.0.0' } ]; // vswhere.exe itself must also "exist" to allow the lookup to proceed mockFs.existsSync.mockImplementation((p: any) => { const s = String(p); return s.includes('vswhere') || s.includes('MSBuild'); }); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).not.toBeNull(); expect(result![0]).toContain('2026'); expect(result![0]).toContain('MSBuild.exe'); expect(result![1]).toBe('18.0'); }); it('should return null path when no VS 18 installation is found', () => { mockOptions = new MSBuildOptions({ toolsVersion: '18.0', windir: 'C:\\Windows' as any }); // Only VS 17 is installed const mockInstallations = [ { installationPath: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community', installationVersion: '17.8.0.0' } ]; mockFs.existsSync.mockReturnValue(true); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).not.toBeNull(); expect(result![0]).toBeNull(); }); it('should find MSBuild 16.0 (VS 2019) by installationVersion', () => { mockOptions = new MSBuildOptions({ toolsVersion: '16.0', windir: 'C:\\Windows' as any }); const mockInstallations = [ { installationPath: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community', installationVersion: '16.11.5.0' } ]; mockFs.existsSync.mockImplementation((p: any) => { const s = String(p); return s.includes('vswhere') || s.includes('MSBuild'); }); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).not.toBeNull(); expect(result![0]).toContain('2019'); expect(result![1]).toBe('16.0'); }); it('should find MSBuild 17.0 (VS 2022) by installationVersion', () => { mockOptions = new MSBuildOptions({ toolsVersion: '17.0', windir: 'C:\\Windows' as any }); const mockInstallations = [ { installationPath: 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community', installationVersion: '17.8.0.0' } ]; mockFs.existsSync.mockImplementation((p: any) => { const s = String(p); return s.includes('vswhere') || s.includes('MSBuild'); }); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); const finder = new MSBuildFinder(mockOptions); const result = finder.findVersion(); expect(result).not.toBeNull(); expect(result![0]).toContain('2022'); expect(result![1]).toBe('17.0'); }); }); describe('concatCorrectPath', () => { it('should handle version 16.0 and above', () => { mockOptions = new MSBuildOptions({ toolsVersion: '16.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatCorrectPath('C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community'); expect(result).toContain('MSBuild'); expect(result).toContain('Current'); }); it('should return empty string when installation path does not match version', () => { mockOptions = new MSBuildOptions({ toolsVersion: '16.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatCorrectPath('C:\\Invalid\\Path'); expect(result).toBe(''); }); it('should handle version 15.0 and below', () => { mockOptions = new MSBuildOptions({ toolsVersion: '15.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatCorrectPath('C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community'); expect(result).toContain('MSBuild'); expect(result).toContain('15.0'); }); it('should handle version 12.0 to 14.0', () => { mockOptions = new MSBuildOptions({ toolsVersion: '12.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatCorrectPath('C:\\Program Files (x86)\\MSBuild'); expect(result).toContain('MSBuild'); expect(result).toContain('12.0'); }); it('should handle pre-v12 versions', () => { mockOptions = new MSBuildOptions({ toolsVersion: '4.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatCorrectPath(''); expect(result).toContain('Microsoft.Net'); expect(result).toContain('MSBuild.exe'); }); }); describe('concatPreV12', () => { it('should build path for x64 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); mockOptions = new MSBuildOptions({ toolsVersion: '4.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatPreV12('4.0'); expect(result).toContain('Framework64'); expect(result).toContain('v4.0.30319'); }); it('should build path for x86 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x86'), configurable: true }); mockOptions = new MSBuildOptions({ toolsVersion: '4.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatPreV12('4.0'); expect(result).toContain('Framework'); expect(result).not.toContain('Framework64'); }); }); describe('concatV15AndBelow', () => { it('should include amd64 for x64 builds', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatV15AndBelow('C:\\VS2017', '15.0'); expect(result).toContain('amd64'); }); it('should not include amd64 for x86 builds', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x86'), configurable: true }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatV15AndBelow('C:\\VS2017', '15.0'); expect(result).not.toContain('amd64'); }); }); describe('concatV16AndAbove', () => { it('should return empty string for non-matching installation path', () => { mockOptions = new MSBuildOptions({ toolsVersion: '16.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatV16AndAbove('C:\\Invalid\\Path', '16.0'); expect(result).toBe(''); }); it('should return correct path for VS 2019', () => { mockOptions = new MSBuildOptions({ toolsVersion: '16.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatV16AndAbove('C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community', '16.0'); expect(result).toContain('Current'); expect(result).toContain('MSBuild.exe'); }); it('should return correct path for VS 2022', () => { mockOptions = new MSBuildOptions({ toolsVersion: '17.0', windir: 'C:\\Windows' as any as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.concatV16AndAbove('C:\\Program Files\\Microsoft Visual Studio\\2022\\Community', '17.0'); expect(result).toContain('Current'); }); }); describe('getInstalledVersion', () => { it('should return first existing path', () => { mockFs.existsSync.mockImplementation((path) => { return path === 'C:\\existing\\path'; }); const finder = new MSBuildFinder(mockOptions); const result = finder.getInstalledVersion(['C:\\nonexistent', 'C:\\existing\\path', 'C:\\another']); expect(result).toBe('C:\\existing\\path'); }); it('should return null when no paths exist', () => { mockFs.existsSync.mockReturnValue(false); const finder = new MSBuildFinder(mockOptions); const result = finder.getInstalledVersion(['C:\\path1', 'C:\\path2']); expect(result).toBeNull(); }); it('should skip empty paths', () => { mockFs.existsSync.mockImplementation((path) => { return path === 'C:\\valid'; }); const finder = new MSBuildFinder(mockOptions); const result = finder.getInstalledVersion(['', 'C:\\valid']); expect(result).toBe('C:\\valid'); }); }); describe('getx64_dir', () => { it('should return amd64 for x64 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x64'), configurable: true }); const finder = new MSBuildFinder(mockOptions); const result = finder.getx64_dir(); expect(result).toBe('amd64'); }); it('should return empty string for x86 architecture', () => { Object.defineProperty(ArchitectureService, 'Architecture', { get: jest.fn(() => 'x86'), configurable: true }); const finder = new MSBuildFinder(mockOptions); const result = finder.getx64_dir(); expect(result).toBe(''); }); }); describe('getLatestAvailableVersion', () => { it('should return null when no versions are found', () => { mockFs.existsSync.mockReturnValue(false); mockOptions = new MSBuildOptions({ toolsVersion: 'auto' as any, windir: 'C:\\Windows' as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.getLatestAvailableVersion(); expect(result).toBeNull(); }); it('should return latest version when multiple versions are found', () => { const mockInstallations = [ { installationPath: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community', installationVersion: '16.11.5.0' } ]; mockFs.existsSync.mockReturnValue(true); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); mockOptions = new MSBuildOptions({ toolsVersion: 'auto' as any, windir: 'C:\\Windows' as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.getLatestAvailableVersion(); expect(result).not.toBeNull(); if (result) { expect(result[0]).toBeDefined(); expect(result[1]).toBeDefined(); } }); it('should auto-detect MSBuild 18 when a VS 18 installation is present', () => { const mockInstallations = [ { installationPath: 'C:\\Program Files\\Microsoft Visual Studio\\2026\\Community', installationVersion: '18.0.0.0' } ]; mockFs.existsSync.mockImplementation((p: any) => { const s = String(p); return s.includes('vswhere') || s.includes('MSBuild'); }); mockExecSync.mockReturnValue(Buffer.from(JSON.stringify(mockInstallations))); mockOptions = new MSBuildOptions({ toolsVersion: 'auto' as any, windir: 'C:\\Windows' as any }); const finder = new MSBuildFinder(mockOptions); const result = finder.getLatestAvailableVersion(); expect(result).not.toBeNull(); expect(result![1]).toBe('18.0'); expect(result![0]).toContain('MSBuild.exe'); }); }); });