From 2d5040d94acea55e23a2d72cb3ae88535a92fd22 Mon Sep 17 00:00:00 2001 From: Rob Cresswell Date: Mon, 2 Dec 2019 12:38:04 +0000 Subject: [PATCH] chore: introduce Jest This patch introduces Jest, and converts the remediation tests to use it. It also removes a bunch of the virtualenv handling from the remediation tests, as it seems superfluous. Finally, due to changing the imports to stop casting as any, some additional typing was needed on the test utils function defs --- .gitignore | 3 + jest.config.js | 20 +++++++ package.json | 6 +- test/remediation.spec.ts | 117 +++++++++++++++++++++++++++++++++++++++ test/remediation.test.ts | 110 ------------------------------------ test/test-utils.ts | 10 ++-- test/tsconfig.json | 4 ++ 7 files changed, 154 insertions(+), 116 deletions(-) create mode 100644 jest.config.js create mode 100644 test/remediation.spec.ts delete mode 100644 test/remediation.test.ts create mode 100644 test/tsconfig.json diff --git a/.gitignore b/.gitignore index fb964cc4..b6a0fa19 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ package-lock.json .nyc_output .eslintcache + +# Test output reports, coverage, etc +reports/ \ No newline at end of file diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..5e756e72 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,20 @@ +module.exports = { + verbose: true, + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: false, // Run yarn test:coverage to generate coverage reports + collectCoverageFrom: ['lib/**/*.ts'], + coverageReporters: ['html', 'text-summary'], + coverageDirectory: '/reports/coverage', + testMatch: ['**/*.spec.ts'], // Remove when all tests are using Jest + modulePathIgnorePatterns: ['/dist', ''], + reporters: [ + 'default', + [ + 'jest-junit', + { + outputDirectory: '/reports/jest', + }, + ], + ], +}; diff --git a/package.json b/package.json index 4f2c64c9..42a2ae0d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "format": "prettier --write '{lib,test}/**/*.{js,ts}'", "prepare": "npm run build", "test": "npm run test:pysrc && npm run test:js", - "test:js": "cross-env TS_NODE_PROJECT=tsconfig-test.json tap --node-arg=-r --node-arg=ts-node/register ./test/*.test.{js,ts} -R spec --timeout=900", + "test:js": "cross-env TS_NODE_PROJECT=tsconfig-test.json tap --node-arg=-r --node-arg=ts-node/register ./test/*.test.{js,ts} -R spec --timeout=900 && jest", "test:pysrc": "python -m unittest discover pysrc", "lint": "npm run build-tests && npm run format:check && eslint --cache '{lib,test}/**/*.{js,ts}'", "semantic-release": "semantic-release" @@ -28,6 +28,7 @@ }, "devDependencies": { "@snyk/types-tap": "^1.1.0", + "@types/jest": "^24.0.23", "@types/node": "^6.14.6", "@types/tmp": "^0.1.0", "@typescript-eslint/eslint-plugin": "^1.13.0", @@ -35,9 +36,12 @@ "cross-env": "^5.2.0", "eslint": "^5.16.0", "eslint-config-prettier": "^6.0.0", + "jest": "^24.9.0", + "jest-junit": "^10.0.0", "prettier": "^1.18.2", "sinon": "^2.3.2", "tap": "^12.6.1", + "ts-jest": "^24.2.0", "ts-node": "^8.1.0", "typescript": "^3.4.5" } diff --git a/test/remediation.spec.ts b/test/remediation.spec.ts new file mode 100644 index 00000000..9f2d840c --- /dev/null +++ b/test/remediation.spec.ts @@ -0,0 +1,117 @@ +import { readdirSync, readFileSync } from 'fs'; +import * as path from 'path'; +import { applyRemediationToManifests } from '../lib'; +import { chdirWorkspaces, deactivateVirtualenv } from './test-utils'; +import { activateVirtualenv, pipInstall } from './test-utils'; + +function readDirAsFiles(dir: string) { + return readdirSync(dir).reduce( + (files: { [fileName: string]: string }, fileName) => { + files[fileName] = readFileSync(path.join(dir, fileName), 'utf8'); + return files; + }, + {} + ); +} + +describe('remediation', () => { + beforeEach(() => { + activateVirtualenv('remediation'); + }); + + afterEach(() => { + deactivateVirtualenv(); + }); + + it('fixes a pip app', async () => { + chdirWorkspaces('pip-app'); + pipInstall(); + + const upgrades = { + 'Django@1.6.1': { upgradeTo: 'Django@2.0.1' }, + 'transitive@1.0.0': { upgradeTo: 'transitive@1.1.1' }, + }; + + const manifests = { + 'requirements.txt': readFileSync('requirements.txt', 'utf8'), + }; + + const expectedUpdatedManifests = readDirAsFiles( + '../../fixtures/updated-manifest' + ); + + const result = await applyRemediationToManifests( + '.', + manifests, + upgrades, + {} + ); + + expect(result).toEqual(expectedUpdatedManifests); + }); + + it('retains python markers', async () => { + chdirWorkspaces('pip-app-with-python-markers'); + pipInstall(); + + const upgrades = { + 'click@7.0': { upgradeTo: 'click@7.1' }, + }; + + const manifests = { + 'requirements.txt': readFileSync('requirements.txt', 'utf8'), + }; + + const expectedUpdatedManifests = readDirAsFiles( + '../../fixtures/updated-manifests-with-python-markers' + ); + + const result = await applyRemediationToManifests( + '.', + manifests, + upgrades, + {} + ); + + expect(result).toEqual(expectedUpdatedManifests); + }); + + it('handles no-op upgrades', async () => { + chdirWorkspaces('pip-app'); + pipInstall(); + + const upgrades = {}; + + const manifests = { + 'requirements.txt': readFileSync('requirements.txt', 'utf8'), + }; + + const result = await applyRemediationToManifests( + '.', + manifests, + upgrades, + {} + ); + + expect(result).toEqual(manifests); + }); + + it('cannot fix a Pipfile app', async () => { + chdirWorkspaces('pipfile-pipapp'); + + const upgrades = { + 'Django@1.6.1': { upgradeTo: 'Django@2.0.1' }, + 'transitive@1.0.0': { upgradeTo: 'transitive@1.1.1' }, + }; + + const manifests = { + Pipfile: readFileSync('Pipfile', 'utf8'), + }; + + await expect( + applyRemediationToManifests('.', manifests, upgrades, {}) + ).rejects.toMatchObject({ + message: 'Remediation only supported for requirements.txt file', + }); + }); +}); diff --git a/test/remediation.test.ts b/test/remediation.test.ts deleted file mode 100644 index 7c3656a9..00000000 --- a/test/remediation.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { test } from 'tap'; -import * as fs from 'fs'; -import * as path from 'path'; - -import plugin = require('../lib'); -import { chdirWorkspaces } from './test-utils'; -const testUtils = require('./test-utils') as any; - -function readDirAsFiles(dir: string) { - const res = {}; - fs.readdirSync(dir).forEach((fn) => { - res[fn] = fs.readFileSync(path.join(dir, fn), 'utf8'); - }); - return res; -} - -test('remediation', async (t) => { - chdirWorkspaces('pip-app'); - t.teardown(testUtils.activateVirtualenv('pip-app')); - const upgrades = { - 'Django@1.6.1': { upgradeTo: 'Django@2.0.1' }, - 'transitive@1.0.0': { upgradeTo: 'transitive@1.1.1' }, - }; - - const manifests = { - 'requirements.txt': fs.readFileSync('requirements.txt', 'utf8'), - }; - - const expectedUpdatedManifests = readDirAsFiles( - '../../fixtures/updated-manifest' - ); - - const result = await plugin.applyRemediationToManifests( - '.', - manifests, - upgrades, - {} - ); - - t.same(result, expectedUpdatedManifests, 'remediation as expected'); -}); - -// TODO(kyegupov): tests for other additional attributes -test('remediation - retains python markers', async (t) => { - chdirWorkspaces('pip-app-with-python-markers'); - const venvCreated = testUtils.ensureVirtualenv('pip-app-with-python-markers'); - t.teardown(testUtils.activateVirtualenv('pip-app-with-python-markers')); - if (venvCreated) { - testUtils.pipInstall(); - } - const upgrades = { - 'click@7.0': { upgradeTo: 'click@7.1' }, - }; - - const manifests = { - 'requirements.txt': fs.readFileSync('requirements.txt', 'utf8'), - }; - - const expectedUpdatedManifests = readDirAsFiles( - '../../fixtures/updated-manifests-with-python-markers' - ); - - const result = await plugin.applyRemediationToManifests( - '.', - manifests, - upgrades, - {} - ); - - t.same(result, expectedUpdatedManifests, 'remediation as expected'); -}); - -test('remediation - no-op upgrades', async (t) => { - chdirWorkspaces('pip-app'); - t.teardown(testUtils.activateVirtualenv('pip-app')); - const upgrades = {}; - - const manifests = { - 'requirements.txt': fs.readFileSync('requirements.txt', 'utf8'), - }; - - const result = await plugin.applyRemediationToManifests( - '.', - manifests, - upgrades, - {} - ); - - t.same(result, manifests, 'remediation as expected (unchanged)'); -}); - -test('remediation for Pipfile', async (t) => { - chdirWorkspaces('pipfile-pipapp'); - t.teardown(testUtils.activateVirtualenv('pipfile-pipapp')); - const upgrades = { - 'Django@1.6.1': { upgradeTo: 'Django@2.0.1' }, - 'transitive@1.0.0': { upgradeTo: 'transitive@1.1.1' }, - }; - - const manifests = { - Pipfile: fs.readFileSync('Pipfile', 'utf8'), - }; - - try { - await plugin.applyRemediationToManifests('.', manifests, upgrades, {}); - t.fail('expected exception'); - } catch (e) { - t.match(e.message, 'Remediation only supported for requirements.txt file'); - } -}); diff --git a/test/test-utils.ts b/test/test-utils.ts index ab5224d1..650ed6c0 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -24,7 +24,7 @@ function getActiveVenvName() { : null; } -function activateVirtualenv(venvName) { +function activateVirtualenv(venvName: string) { const venvDir = path.join(path.resolve(__dirname), '.venvs', venvName); const binDir = path.resolve(venvDir, binDirName); @@ -78,7 +78,7 @@ function deactivateVirtualenv() { }; } -function ensureVirtualenv(venvName) { +function ensureVirtualenv(venvName: string) { const venvsBaseDir = path.join(path.resolve(__dirname), '.venvs'); try { fs.accessSync(venvsBaseDir, fs.constants.R_OK); @@ -96,7 +96,7 @@ function ensureVirtualenv(venvName) { } } -function createVenv(venvDir) { +function createVenv(venvDir: string) { let revert = () => {}; if (process.env.VIRTUAL_ENV) { revert = deactivateVirtualenv(); @@ -140,7 +140,7 @@ function pipInstall() { } } -function pipUninstall(pkgName) { +function pipUninstall(pkgName: string) { const proc = subProcess.executeSync('pip', ['uninstall', '-y', pkgName]); if (proc.status !== 0) { throw new Error( @@ -182,6 +182,6 @@ function setWorkonHome() { }; } -export function chdirWorkspaces(dir) { +export function chdirWorkspaces(dir: string) { process.chdir(path.resolve(__dirname, 'workspaces', dir)); } diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..c2dc6eb7 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.json", + "include": ["**/*.ts", "setup.ts"] +}