From a323cf03b375641b701b4380d891c7f5d6461523 Mon Sep 17 00:00:00 2001 From: Joshua Gleitze <dev@joshuagleitze.de> Date: Fri, 3 Apr 2020 10:44:32 +0300 Subject: [PATCH] test(gradle): stop mocking the file system (#5826) --- .../gradle/__snapshots__/index.spec.ts.snap | 6 +- lib/manager/gradle/index.spec.ts | 193 +++++++++--------- test/util.ts | 18 ++ 3 files changed, 117 insertions(+), 100 deletions(-) diff --git a/lib/manager/gradle/__snapshots__/index.spec.ts.snap b/lib/manager/gradle/__snapshots__/index.spec.ts.snap index 68432b5d63..b979d1c730 100644 --- a/lib/manager/gradle/__snapshots__/index.spec.ts.snap +++ b/lib/manager/gradle/__snapshots__/index.spec.ts.snap @@ -555,7 +555,7 @@ Array [ "NO_PROXY": "localhost", "PATH": "/tmp/path", }, - "timeout": 900000, + "timeout": 60000, }, }, ] @@ -589,7 +589,7 @@ Array [ "NO_PROXY": "localhost", "PATH": "/tmp/path", }, - "timeout": 900000, + "timeout": 60000, }, }, ] @@ -610,7 +610,7 @@ Array [ }, }, Object { - "cmd": "docker run --rm --name=renovate_gradle --label=renovate_child -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle bash -l -c \\"./gradlew --init-script renovate-plugin.gradle renovate\\"", + "cmd": "docker run --rm --name=renovate_gradle --label=renovate_child -v \\"localDir\\":\\"localDir\\" -w \\"localDir\\" renovate/gradle bash -l -c \\"gradle --init-script renovate-plugin.gradle renovate\\"", "options": Object { "cwd": "localDir", "encoding": "utf-8", diff --git a/lib/manager/gradle/index.spec.ts b/lib/manager/gradle/index.spec.ts index da3e7ab634..2b6a35c060 100644 --- a/lib/manager/gradle/index.spec.ts +++ b/lib/manager/gradle/index.spec.ts @@ -1,10 +1,8 @@ -import { toUnix } from 'upath'; -import _fs from 'fs-extra'; -import fsReal, { Stats } from 'fs'; +import fs from 'fs-extra'; +import * as upath from 'upath'; import { exec as _exec } from 'child_process'; import * as _os from 'os'; import tmp, { DirectoryResult } from 'tmp-promise'; -import * as path from 'path'; import { platform as _platform } from '../../platform'; import { envMock, mockExecAll } from '../../../test/execUtil'; import * as _env from '../../util/exec/env'; @@ -14,21 +12,23 @@ import * as _util from '../../util'; import { ifSystemSupportsGradle } from './__testutil__/gradle'; import * as _manager from '.'; import { ExtractConfig } from '../common'; +import { GRADLE_DEPENDENCY_REPORT_FILENAME } from './gradle-updates-report'; +import { replacingSerializer } from '../../../test/util'; const fixtures = 'lib/manager/gradle/__fixtures__'; - -const config = { - localDir: 'localDir', +const standardUpdatesReport = () => + fs.readFile(`${fixtures}/updatesReport.json`, 'utf8'); +const emptyUpdatesReport = () => + fs.readFile(`${fixtures}/updatesReportEmpty.json`, 'utf8'); +const multiProjectUpdatesReport = () => + fs.readFile(`${fixtures}/MultiProjectUpdatesReport.json`, 'utf8'); + +const baseConfig = { gradle: { timeout: 60, }, }; -const updatesDependenciesReport = fsReal.readFileSync( - 'lib/manager/gradle/__fixtures__/updatesReport.json', - 'utf8' -); - const gradleOutput = { stdout: 'gradle output', stderr: '', @@ -42,13 +42,11 @@ function resetMocks() { async function setupMocks() { resetMocks(); - jest.mock('fs-extra'); jest.mock('child_process'); jest.mock('../../util/exec/env'); jest.mock('../../platform'); jest.mock('os'); - const fs: jest.Mocked<typeof _fs> = require('fs-extra'); const os: jest.Mocked<typeof _os> = require('os'); const platform: jest.Mocked<typeof _platform> = require('../../platform') .platform; @@ -58,42 +56,58 @@ async function setupMocks() { platform.getFile.mockResolvedValue('some content'); env.getChildProcessEnv.mockReturnValue(envMock.basic); - await util.setUtilConfig(config); + await util.setUtilConfig(baseConfig); - return [require('.'), exec, fs, util, os]; + return [require('.'), exec, util, os]; } describe('manager/gradle', () => { describe('extractPackageFile', () => { let manager: typeof _manager; let exec: jest.Mock<typeof _exec>; - let fs: jest.Mocked<typeof _fs>; let util: jest.Mocked<typeof _util>; let os: jest.Mocked<typeof _os>; let docker: typeof _docker; + let config: ExtractConfig; beforeAll(async () => { - [manager, exec, fs, util, os] = await setupMocks(); + [manager, exec, util, os] = await setupMocks(); docker = require('../../util/exec/docker'); }); afterAll(resetMocks); - beforeEach(() => { - fs.readFile.mockResolvedValue(updatesDependenciesReport as any); - fs.mkdir.mockResolvedValue(); - fs.exists.mockResolvedValue(true); - fs.stat.mockResolvedValue({ - mode: 0o755, - isFile: () => true, - } as Stats); + beforeEach(async () => { exec.mockReset(); docker.resetPrefetchedImages(); os.platform.mockReturnValue('linux'); + + const gradleDir = await tmp.dir({ unsafeCleanup: true }); + config = { ...baseConfig, localDir: gradleDir.path }; + expect.addSnapshotSerializer( + replacingSerializer(upath.toUnix(gradleDir.path), 'localDir') + ); }); + async function initializeWorkingDir( + addGradleWrapper: boolean, + updatesReport: Promise<string> | string | null, + dir: string = config.localDir + ) { + if (addGradleWrapper) { + await fs.copy(`${fixtures}/gradle-wrappers/6`, dir); + } + if (updatesReport) { + await fs.writeFile( + `${dir}/${GRADLE_DEPENDENCY_REPORT_FILENAME}`, + await updatesReport + ); + } + } + it('should return gradle dependencies', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', @@ -105,6 +119,7 @@ describe('manager/gradle', () => { it('should return gradle.kts dependencies', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle.kts', @@ -116,13 +131,8 @@ describe('manager/gradle', () => { it('should return empty if there are no dependencies', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, emptyUpdatesReport()); - fs.readFile.mockResolvedValue( - fsReal.readFileSync( - 'lib/manager/gradle/__fixtures__/updatesReportEmpty.json', - 'utf8' - ) as any - ); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', ]); @@ -133,8 +143,8 @@ describe('manager/gradle', () => { it('should return empty if there is no dependency report', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, null); - fs.exists.mockResolvedValue(false); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', ]); @@ -145,11 +155,12 @@ describe('manager/gradle', () => { it('should return empty if renovate report is invalid', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); - - const renovateReport = ` + await initializeWorkingDir( + true, + ` Invalid JSON] - `; - fs.readFile.mockResolvedValue(renovateReport as any); + ` + ); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', @@ -160,12 +171,7 @@ describe('manager/gradle', () => { it('should use repositories only for current project', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); - - const multiProjectUpdatesReport = fsReal.readFileSync( - 'lib/manager/gradle/__fixtures__/MultiProjectUpdatesReport.json', - 'utf8' - ); - fs.readFile.mockResolvedValue(multiProjectUpdatesReport as any); + await initializeWorkingDir(true, multiProjectUpdatesReport()); const dependencies = await manager.extractAllPackageFiles(config, [ 'build.gradle', @@ -176,6 +182,7 @@ describe('manager/gradle', () => { it('should execute gradlew when available', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); await manager.extractAllPackageFiles(config, ['build.gradle']); expect(execSnapshots).toMatchSnapshot(); @@ -183,6 +190,8 @@ describe('manager/gradle', () => { it('should execute gradlew.bat when available on Windows', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); + os.platform.mockReturnValue('win32'); await manager.extractAllPackageFiles(config, ['build.gradle']); @@ -191,7 +200,7 @@ describe('manager/gradle', () => { it('should execute gradle if gradlew is not available', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); - fs.stat.mockRejectedValue(new Error()); + await initializeWorkingDir(false, standardUpdatesReport()); await manager.extractAllPackageFiles(config, ['build.gradle']); expect(execSnapshots).toMatchSnapshot(); @@ -199,7 +208,7 @@ describe('manager/gradle', () => { it('should return null and gradle should not be executed if no root build.gradle', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); - fs.stat.mockRejectedValue(new Error()); + await initializeWorkingDir(false, null); const packageFiles = ['foo/build.gradle']; expect( @@ -212,6 +221,13 @@ describe('manager/gradle', () => { it('should return gradle dependencies for build.gradle in subdirectories if there is gradlew in the same directory', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); + await fs.mkdirs(`${config.localDir}/foo`); + await initializeWorkingDir( + true, + standardUpdatesReport(), + `${config.localDir}/foo` + ); const dependencies = await manager.extractAllPackageFiles(config, [ 'foo/build.gradle', @@ -222,51 +238,48 @@ describe('manager/gradle', () => { it('should configure the renovate report plugin', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); + await initializeWorkingDir(true, standardUpdatesReport()); await manager.extractAllPackageFiles(config, ['build.gradle']); - expect(toUnix(fs.writeFile.mock.calls[0][0] as string)).toBe( - 'localDir/renovate-plugin.gradle' - ); + await expect( + fs.access( + `${config.localDir}/renovate-plugin.gradle`, + fs.constants.F_OK + ) + ).resolves.toBe(undefined); expect(execSnapshots).toMatchSnapshot(); }); it('should use docker if required', async () => { - util.setUtilConfig({ ...config, binarySource: BinarySource.Docker }); + const configWithDocker = { binarySource: BinarySource.Docker, ...config }; + util.setUtilConfig(configWithDocker); + await initializeWorkingDir(false, standardUpdatesReport()); const execSnapshots = mockExecAll(exec, gradleOutput); - const configWithDocker = { - binarySource: BinarySource.Docker, - ...config, - }; await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']); expect(execSnapshots).toMatchSnapshot(); }); it('should use docker even if gradlew is available', async () => { - util.setUtilConfig({ ...config, binarySource: BinarySource.Docker }); - const execSnapshots = mockExecAll(exec, gradleOutput); + const configWithDocker = { binarySource: BinarySource.Docker, ...config }; + util.setUtilConfig(configWithDocker); + await initializeWorkingDir(true, standardUpdatesReport()); - const configWithDocker = { - binarySource: BinarySource.Docker, - ...config, - gradle: {}, - }; + const execSnapshots = mockExecAll(exec, gradleOutput); await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']); expect(execSnapshots).toMatchSnapshot(); }); it('should use docker even if gradlew.bat is available on Windows', async () => { + const configWithDocker = { binarySource: BinarySource.Docker, ...config }; + util.setUtilConfig(configWithDocker); os.platform.mockReturnValue('win32'); + await initializeWorkingDir(true, standardUpdatesReport()); const execSnapshots = mockExecAll(exec, gradleOutput); - const configWithDocker = { - binarySource: BinarySource.Docker, - ...config, - gradle: {}, - }; await manager.extractAllPackageFiles(configWithDocker, ['build.gradle']); expect(execSnapshots).toMatchSnapshot(); @@ -282,11 +295,11 @@ describe('manager/gradle', () => { }); afterAll(resetMocks); - it('should update an existing module dependency', () => { + it('should update an existing module dependency', async () => { const execSnapshots = mockExecAll(exec, gradleOutput); - const buildGradleContent = fsReal.readFileSync( - 'lib/manager/gradle/__fixtures__/build.gradle.example1', + const buildGradleContent = await fs.readFile( + `${fixtures}/build.gradle.example1`, 'utf8' ); // prettier-ignore @@ -374,45 +387,41 @@ describe('manager/gradle', () => { }); describe('executeGradle integration', () => { - const SUCCESS_FILE = 'success.indicator'; + const SUCCESS_FILE_NAME = 'success.indicator'; let workingDir: DirectoryResult; let testRunConfig: ExtractConfig; + let successFile: string; const manager = require('.'); beforeEach(async () => { workingDir = await tmp.dir({ unsafeCleanup: true }); - testRunConfig = { ...config, localDir: workingDir.path }; - await _fs.copy(`${fixtures}/minimal-project`, workingDir.path); - await _fs.copy(`${fixtures}/gradle-wrappers/6`, workingDir.path); + successFile = ''; + testRunConfig = { ...baseConfig, localDir: workingDir.path }; + await fs.copy(`${fixtures}/minimal-project`, workingDir.path); + await fs.copy(`${fixtures}/gradle-wrappers/6`, workingDir.path); const mockPluginContent = ` allprojects { tasks.register("renovate") { doLast { - new File('${SUCCESS_FILE}').write 'success' + new File('${SUCCESS_FILE_NAME}').write 'success' } } }`; - await _fs.writeFile( - path.join(workingDir.path, 'renovate-plugin.gradle'), + await fs.writeFile( + `${workingDir.path}/renovate-plugin.gradle`, mockPluginContent ); + successFile = `${workingDir.path}/${SUCCESS_FILE_NAME}`; }); ifSystemSupportsGradle(6).it( 'executes an executable gradle wrapper', async () => { - const gradlew = await fsReal.promises.stat( - path.join(workingDir.path, 'gradlew') - ); + const gradlew = await fs.stat(`${workingDir.path}/gradlew`); await manager.executeGradle(testRunConfig, workingDir.path, gradlew); - await expect( - fsReal.promises.readFile( - path.join(workingDir.path, SUCCESS_FILE), - 'utf8' - ) - ).resolves.toBe('success'); + await expect(fs.readFile(successFile, 'utf8')).resolves.toBe('success'); }, 120000 ); @@ -420,20 +429,10 @@ allprojects { ifSystemSupportsGradle(6).it( 'executes a not-executable gradle wrapper', async () => { - await fsReal.promises.chmod( - path.join(workingDir.path, 'gradlew'), - '444' - ); - const gradlew = await fsReal.promises.stat( - path.join(workingDir.path, 'gradlew') - ); + await fs.chmod(`${workingDir.path}/gradlew`, '444'); + const gradlew = await fs.stat(`${workingDir.path}/gradlew`); await manager.executeGradle(testRunConfig, workingDir.path, gradlew); - await expect( - fsReal.promises.readFile( - path.join(workingDir.path, SUCCESS_FILE), - 'utf8' - ) - ).resolves.toBe('success'); + await expect(fs.readFile(successFile, 'utf8')).resolves.toBe('success'); }, 120000 ); diff --git a/test/util.ts b/test/util.ts index 72050b23b5..7e20b32dc0 100644 --- a/test/util.ts +++ b/test/util.ts @@ -31,3 +31,21 @@ export function getName(file: string): string { const [, name] = /lib\/(.*?)\.spec\.ts$/.exec(file.replace(/\\/g, '/')); return name; } + +/** + * Can be used to search and replace strings in jest snapshots. + * @example + * expect.addSnapshotSerializer( + * replacingSerializer(upath.toUnix(gradleDir.path), 'localDir') + * ); + */ +export const replacingSerializer = ( + search: string, + replacement: string +): jest.SnapshotSerializerPlugin => ({ + test: value => typeof value === 'string' && value.includes(search), + serialize: (val, config, indent, depth, refs, printer) => { + const replaced = (val as string).replace(search, replacement); + return printer(replaced, config, indent, depth, refs); + }, +}); -- GitLab