UNPKG

@stryker-mutator/core

Version:

The extendable JavaScript mutation testing framework

278 lines 15.2 kB
import path from 'path'; import { npmRunPathEnv } from 'npm-run-path'; import { expect } from 'chai'; import sinon from 'sinon'; import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { normalizeWhitespaces } from '@stryker-mutator/util'; import { Sandbox } from '../../../src/sandbox/sandbox.js'; import { coreTokens } from '../../../src/di/index.js'; import { TemporaryDirectory } from '../../../src/utils/temporary-directory.js'; import { fileUtils } from '../../../src/utils/file-utils.js'; import { Project } from '../../../src/fs/index.js'; import { FileSystemTestDouble } from '../../helpers/file-system-test-double.js'; describe(Sandbox.name, () => { let temporaryDirectoryMock; let symlinkJunctionStub; let findNodeModulesListStub; let execaCommandMock; let unexpectedExitHandlerMock; let moveDirectoryRecursiveSyncStub; let fsTestDouble; const SANDBOX_WORKING_DIR = path.resolve('.stryker-tmp/sandbox-123'); const BACKUP_DIR = 'backup-123'; beforeEach(() => { temporaryDirectoryMock = sinon.createStubInstance(TemporaryDirectory); temporaryDirectoryMock.getRandomDirectory.withArgs('sandbox').returns(SANDBOX_WORKING_DIR).withArgs('backup').returns(BACKUP_DIR); symlinkJunctionStub = sinon.stub(fileUtils, 'symlinkJunction'); findNodeModulesListStub = sinon.stub(fileUtils, 'findNodeModulesList'); moveDirectoryRecursiveSyncStub = sinon.stub(fileUtils, 'moveDirectoryRecursiveSync'); execaCommandMock = sinon.stub(); unexpectedExitHandlerMock = { registerHandler: sinon.stub(), dispose: sinon.stub(), }; fsTestDouble = new FileSystemTestDouble(Object.create(null)); symlinkJunctionStub.resolves(); findNodeModulesListStub.resolves(['node_modules']); }); function createSut(project = new Project(fsTestDouble, Object.keys(fsTestDouble.files).reduce((fileDescriptions, fileName) => { fileDescriptions[fileName] = { mutate: true }; return fileDescriptions; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument }, Object.create(null)))) { return testInjector.injector .provideValue(coreTokens.project, project) .provideValue(coreTokens.temporaryDirectory, temporaryDirectoryMock) .provideValue(coreTokens.execa, execaCommandMock) .provideValue(coreTokens.unexpectedExitRegistry, unexpectedExitHandlerMock) .injectClass(Sandbox); } describe('init()', () => { describe('with inPlace = false', () => { beforeEach(() => { testInjector.options.inPlace = false; }); it('should have created a sandbox folder', async () => { const sut = createSut(); await sut.init(); expect(temporaryDirectoryMock.getRandomDirectory).calledWith('sandbox'); expect(temporaryDirectoryMock.createDirectory).calledWith(SANDBOX_WORKING_DIR); }); it('should copy regular input files', async () => { fsTestDouble.files[path.resolve('a', 'main.js')] = 'foo("bar")'; fsTestDouble.files[path.resolve('a', 'b.txt')] = 'b content'; fsTestDouble.files[path.resolve('c', 'd', 'e.log')] = 'e content'; const project = new Project(fsTestDouble, { [path.resolve('a', 'main.js')]: { mutate: true }, [path.resolve('a', 'b.txt')]: { mutate: false }, [path.resolve('c', 'd', 'e.log')]: { mutate: false }, }); project.files.get(path.resolve('a', 'main.js')).setContent('foo("mutated")'); const sut = createSut(project); await sut.init(); expect(fsTestDouble.files[path.join(SANDBOX_WORKING_DIR, 'a', 'main.js')]).eq('foo("mutated")'); expect(fsTestDouble.files[path.join(SANDBOX_WORKING_DIR, 'a', 'b.txt')]).eq('b content'); expect(fsTestDouble.files[path.join(SANDBOX_WORKING_DIR, 'c', 'd', 'e.log')]).eq('e content'); }); it('should be able to copy a local file', async () => { fsTestDouble.files['localFile.txt'] = 'foobar'; const sut = createSut(); await sut.init(); expect(fsTestDouble.files[path.join(SANDBOX_WORKING_DIR, 'localFile.txt')]).eq('foobar'); }); it('should symlink node modules in sandbox directory if exists', async () => { const sut = createSut(); await sut.init(); expect(findNodeModulesListStub).calledWith(process.cwd()); expect(symlinkJunctionStub).calledWith(path.resolve('node_modules'), path.join(SANDBOX_WORKING_DIR, 'node_modules')); }); }); describe('with inPlace = true', () => { beforeEach(() => { testInjector.options.inPlace = true; }); it('should have created a backup directory', async () => { const sut = createSut(); await sut.init(); expect(temporaryDirectoryMock.getRandomDirectory).calledWith('backup'); expect(temporaryDirectoryMock.createDirectory).calledWith(BACKUP_DIR); }); it('should not override the current file if no changes were detected', async () => { fsTestDouble.files[path.resolve('a', 'b.txt')] = 'b content'; const sut = createSut(); await sut.init(); expect(Object.keys(fsTestDouble.files)).lengthOf(1); }); it('should override original file if changes were detected', async () => { // Arrange const fileName = path.resolve('a', 'b.js'); fsTestDouble.files[fileName] = 'b content'; const project = new Project(fsTestDouble, { [fileName]: { mutate: true } }); project.files.get(fileName).setContent('b mutated content'); // Act const sut = createSut(project); await sut.init(); // Assert expect(fsTestDouble.files[fileName]).eq('b mutated content'); }); it('should backup the original before overriding it', async () => { // Arrange const fileName = path.resolve('a', 'b.js'); const originalContent = 'b content'; const mutatedContent = 'b mutated content'; fsTestDouble.files[fileName] = originalContent; const project = new Project(fsTestDouble, { [fileName]: { mutate: true } }); project.files.get(fileName).setContent(mutatedContent); const expectedBackupFileName = path.join(path.join(BACKUP_DIR, 'a'), 'b.js'); // Act const sut = createSut(project); await sut.init(); // Assert expect(fsTestDouble.files[expectedBackupFileName]).eq(originalContent); expect(fsTestDouble.files[fileName]).eq(mutatedContent); }); it('should log the backup file location', async () => { // Arrange const fileName = path.resolve('a', 'b.js'); const originalContent = 'b content'; const mutatedContent = 'b mutated content'; fsTestDouble.files[fileName] = originalContent; const project = new Project(fsTestDouble, { [fileName]: { mutate: true } }); project.files.get(fileName).setContent(mutatedContent); const expectedBackupFileName = path.join(path.join(BACKUP_DIR, 'a'), 'b.js'); // Act const sut = createSut(project); await sut.init(); // Assert expect(testInjector.logger.debug).calledWith('Stored backup file at %s', expectedBackupFileName); }); it('should register an unexpected exit handler', async () => { // Act const sut = createSut(); await sut.init(); // Assert expect(unexpectedExitHandlerMock.registerHandler).called; }); }); it('should symlink node modules in sandbox directory if node_modules exist', async () => { findNodeModulesListStub.resolves(['node_modules', 'packages/a/node_modules']); const sut = createSut(); await sut.init(); const calls = symlinkJunctionStub.getCalls(); expect(calls[0]).calledWithExactly(path.resolve('node_modules'), path.join(SANDBOX_WORKING_DIR, 'node_modules')); expect(calls[1]).calledWithExactly(path.resolve('packages', 'a', 'node_modules'), path.join(SANDBOX_WORKING_DIR, 'packages', 'a', 'node_modules')); }); it('should not symlink node_modules in sandbox directory if no node_modules exist', async () => { findNodeModulesListStub.resolves([]); const sut = createSut(); await sut.init(); expect(testInjector.logger.debug).calledWithMatch('Could not find a node_modules'); expect(testInjector.logger.debug).calledWithMatch(process.cwd()); expect(symlinkJunctionStub).not.called; }); it('should log a warning if "node_modules" already exists in the working folder', async () => { findNodeModulesListStub.resolves(['node_modules']); symlinkJunctionStub.rejects(factory.fileAlreadyExistsError()); const sut = createSut(); await sut.init(); expect(testInjector.logger.warn).calledWithMatch(normalizeWhitespaces(`Could not symlink "node_modules" in sandbox directory, it is already created in the sandbox. Please remove the node_modules from your sandbox files. Alternatively, set \`symlinkNodeModules\` to \`false\` to disable this warning.`)); }); it('should log a warning if linking "node_modules" results in an unknown error', async () => { findNodeModulesListStub.resolves(['basePath/node_modules']); const error = new Error('unknown'); symlinkJunctionStub.rejects(error); const sut = createSut(); await sut.init(); expect(testInjector.logger.warn).calledWithMatch(normalizeWhitespaces('Unexpected error while trying to symlink "basePath/node_modules" in sandbox directory.'), error); }); it('should not symlink node modules in sandbox directory if `symlinkNodeModules` is `false`', async () => { testInjector.options.symlinkNodeModules = false; const sut = createSut(); await sut.init(); expect(symlinkJunctionStub).not.called; expect(findNodeModulesListStub).not.called; }); it('should execute the buildCommand in the sandbox', async () => { testInjector.options.buildCommand = 'npm run build'; const sut = createSut(); await sut.init(); expect(execaCommandMock).calledWith('npm run build', { cwd: SANDBOX_WORKING_DIR, env: npmRunPathEnv() }); expect(testInjector.logger.info).calledWith('Running build command "%s" in "%s".', 'npm run build', SANDBOX_WORKING_DIR); }); it('should not execute a build command when non is configured', async () => { testInjector.options.buildCommand = undefined; const sut = createSut(); await sut.init(); expect(execaCommandMock).not.called; }); it('should execute the buildCommand before the node_modules are symlinked', async () => { // It is important to first run the buildCommand, otherwise the build dependencies are not correctly resolved testInjector.options.buildCommand = 'npm run build'; const sut = createSut(); await sut.init(); expect(execaCommandMock).calledBefore(symlinkJunctionStub); }); }); describe('dispose', () => { it("shouldn't do anything when inPlace = false", () => { const sut = createSut(); sut.dispose(); expect(moveDirectoryRecursiveSyncStub).not.called; }); it('should recover from the backup dir synchronously if inPlace = true', () => { testInjector.options.inPlace = true; const sut = createSut(); sut.dispose(); expect(moveDirectoryRecursiveSyncStub).calledWith(BACKUP_DIR, process.cwd()); }); it('should recover from the backup dir if stryker exits unexpectedly while inPlace = true', () => { testInjector.options.inPlace = true; const errorStub = sinon.stub(console, 'error'); createSut(); unexpectedExitHandlerMock.registerHandler.callArg(0); expect(moveDirectoryRecursiveSyncStub).calledWith(BACKUP_DIR, process.cwd()); expect(errorStub).calledWith(`Detecting unexpected exit, recovering original files from ${BACKUP_DIR}`); }); }); describe('workingDirectory', () => { it('should retrieve the sandbox directory when inPlace = false', async () => { const sut = createSut(); await sut.init(); expect(sut.workingDirectory).eq(SANDBOX_WORKING_DIR); }); it('should retrieve the cwd directory when inPlace = true', async () => { testInjector.options.inPlace = true; const sut = createSut(); await sut.init(); expect(sut.workingDirectory).eq(process.cwd()); }); }); // describe(Sandbox.prototype.sandboxFileFor.name, () => { // it('should return the sandbox file if exists', async () => { // const originalFileName = path.resolve('src/foo.js'); // fsTestDouble.push(new File(originalFileName, '')); // const sut = createSut(); // await sut.init(); // const actualSandboxFile = sut.sandboxFileFor(originalFileName); // expect(actualSandboxFile).eq(path.join(SANDBOX_WORKING_DIR, 'src/foo.js')); // }); // it("should throw when the sandbox file doesn't exists", async () => { // const notExistingFile = 'src/bar.js'; // fsTestDouble.push(new File(path.resolve('src/foo.js'), '')); // const sut = createSut(); // await sut.init(); // expect(() => sut.sandboxFileFor(notExistingFile)).throws('Cannot find sandbox file for src/bar.js'); // }); // }); describe(Sandbox.prototype.originalFileFor.name, () => { it('should remap the file to the original', async () => { const sut = createSut(); await sut.init(); const sandboxFile = path.join(SANDBOX_WORKING_DIR, 'src/foo.js'); expect(sut.originalFileFor(sandboxFile)).eq(path.resolve('src/foo.js')); }); }); }); //# sourceMappingURL=sandbox.spec.js.map