From 3f85abfe9bf05e008c43cf6489d26ecb0b7d8ee3 Mon Sep 17 00:00:00 2001 From: Merlin Beutlberger Date: Mon, 13 May 2019 15:42:02 +0200 Subject: [PATCH] [FIX] JSDoc: Implement own tmp dir lifecycle For this a new entity 'BuildContext' has been introduced in the builder. This might be the foundation for future enhancements of the (Custom-) Task API. Resolves https://github.com/SAP/ui5-cli/issues/187 --- lib/builder/BuildContext.js | 58 +++++++++++++++ lib/builder/builder.js | 81 ++++++++++++++++++-- lib/tasks/jsdoc/generateJsdoc.js | 49 ++++++------ lib/types/AbstractBuilder.js | 10 +-- lib/types/application/applicationType.js | 4 +- lib/types/library/LibraryBuilder.js | 3 +- lib/types/library/libraryType.js | 4 +- package-lock.json | 8 -- package.json | 3 +- test/lib/builder/builder.js | 94 ++++++++++++++++++++++++ test/lib/tasks/jsdoc/generateJsdoc.js | 89 +++++++++++++++++----- 11 files changed, 332 insertions(+), 71 deletions(-) create mode 100644 lib/builder/BuildContext.js diff --git a/lib/builder/BuildContext.js b/lib/builder/BuildContext.js new file mode 100644 index 000000000..05a29c672 --- /dev/null +++ b/lib/builder/BuildContext.js @@ -0,0 +1,58 @@ +/** + * Context of a build process + * + * @private + * @memberof module:@ui5/builder.builder + */ +class BuildContext { + constructor() { + this.projectBuildContexts = []; + } + + createProjectContext({project, resources}) { + const projectBuildContext = new ProjectBuildContext({ + buildContext: this, + project, + resources + }); + this.projectBuildContexts.push(projectBuildContext); + return projectBuildContext; + } + + async executeCleanupTasks() { + await Promise.all(this.projectBuildContexts.map((ctx) => { + return ctx.executeCleanupTasks(); + })); + } +} + + +/** + * Build context of a single project. Always part of an overall + * [Build Context]{@link module:@ui5/builder.builder.BuildContext} + * + * @private + * @memberof module:@ui5/builder.builder + */ +class ProjectBuildContext { + constructor({buildContext, project, resources}) { + // this.buildContext = buildContext; + // this.project = project; + // this.resources = resources; + this.queues = { + cleanup: [] + }; + } + + registerCleanupTask(callback) { + this.queues.cleanup.push(callback); + } + + async executeCleanupTasks() { + await Promise.all(this.queues.cleanup.map((callback) => { + return callback(); + })); + } +} + +module.exports = BuildContext; diff --git a/lib/builder/builder.js b/lib/builder/builder.js index 5444a6234..a4a0d415e 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -3,6 +3,7 @@ const resourceFactory = require("@ui5/fs").resourceFactory; const MemAdapter = require("@ui5/fs").adapters.Memory; const typeRepository = require("../types/typeRepository"); const taskRepository = require("../tasks/taskRepository"); +const BuildContext = require("./BuildContext"); const definedTasks = taskRepository.getAllTasks(); @@ -130,6 +131,61 @@ function composeTaskList({dev, selfContained, jsdoc, includedTasks, excludedTask return selectedTasks; } +async function executeCleanupTasks(buildContext) { + log.info("Executing cleanup tasks..."); + await buildContext.executeCleanupTasks(); +} + +function registerCleanupSigHooks(buildContext) { + function createListener(exitCode) { + return function() { + // Asynchronously cleanup resources, then exit + executeCleanupTasks(buildContext).then(() => { + process.exit(exitCode); + }); + }; + } + + const processSignals = { + "SIGHUP": createListener(128 + 1), + "SIGINT": createListener(128 + 2), + "SIGTERM": createListener(128 + 15), + "SIGBREAK": createListener(128 + 21) + }; + + for (const signal in processSignals) { + if (processSignals.hasOwnProperty(signal)) { + process.on(signal, processSignals[signal]); + } + } + + // == TO BE DISCUSSED: Also cleanup for unhandled rejections and exceptions? + // Add additional events like signals since they are registered on the process + // event emitter in a similar fashion + // processSignals["unhandledRejection"] = createListener(1); + // process.once("unhandledRejection", processSignals["unhandledRejection"]); + // processSignals["uncaughtException"] = function(err, origin) { + // const fs = require("fs"); + // fs.writeSync( + // process.stderr.fd, + // `Caught exception: ${err}\n` + + // `Exception origin: ${origin}` + // ); + // createListener(1)(); + // }; + // process.once("uncaughtException", processSignals["uncaughtException"]); + + return processSignals; +} + +function deregisterCleanupSigHooks(signals) { + for (const signal in signals) { + if (signals.hasOwnProperty(signal)) { + process.removeListener(signal, signals[signal]); + } + } +} + /** * Builder * @@ -156,7 +212,7 @@ module.exports = { * @param {Array} [parameters.devExcludeProject=[]] List of projects to be excluded from development build * @returns {Promise} Promise resolving to undefined once build has finished */ - build({ + async build({ tree, destPath, buildDependencies = false, dev = false, selfContained = false, jsdoc = false, includedTasks = [], excludedTasks = [], devExcludeProject = [] @@ -173,6 +229,8 @@ module.exports = { virBasePath: "/" }); + const buildContext = new BuildContext(); + const cleanupSigHooks = registerCleanupSigHooks(buildContext); const projects = {}; // Unique project index to prevent building the same project multiple times const projectWriters = {}; // Collection of memory adapters of already built libraries @@ -235,6 +293,14 @@ module.exports = { name: project.metadata.name }); + const projectContext = buildContext.createProjectContext({ + // project, // TODO 2.0: Add project facade object/instance here + resources: { + workspace, + dependencies: resourceCollections.dependencies + } + }); + if (dev && devExcludeProject.indexOf(project.metadata.name) !== -1) { projectTasks = composeTaskList({dev: false, selfContained, includedTasks, excludedTasks}); } @@ -246,7 +312,8 @@ module.exports = { }, tasks: projectTasks, project, - parentLogger: log + parentLogger: log, + buildContext: projectContext }).then(() => { log.verbose("Finished building project %s. Writing out files...", project.metadata.name); buildLogger.completeWork(1); @@ -265,11 +332,15 @@ module.exports = { }); } - return buildProject(tree).then(() => { + try { + await buildProject(tree); log.info(`Build succeeded in ${getElapsedTime(startTime)}`); - }, (err) => { + } catch (err) { log.error(`Build failed in ${getElapsedTime(startTime)}`); throw err; - }); + } finally { + deregisterCleanupSigHooks(cleanupSigHooks); + await executeCleanupTasks(buildContext); + } } }; diff --git a/lib/tasks/jsdoc/generateJsdoc.js b/lib/tasks/jsdoc/generateJsdoc.js index 0c807fdee..0be8195d6 100644 --- a/lib/tasks/jsdoc/generateJsdoc.js +++ b/lib/tasks/jsdoc/generateJsdoc.js @@ -1,9 +1,11 @@ const log = require("@ui5/logger").getLogger("builder:tasks:jsdoc:generateJsdoc"); const path = require("path"); const makeDir = require("make-dir"); +const os = require("os"); const fs = require("graceful-fs"); -const tmp = require("tmp"); -tmp.setGracefulCleanup(); +const {promisify} = require("util"); +const mkdtemp = promisify(fs.mkdtemp); +const rimraf = promisify(require("rimraf")); const jsdocGenerator = require("../../processors/jsdoc/jsdocGenerator"); const {resourceFactory} = require("@ui5/fs"); @@ -22,12 +24,15 @@ const {resourceFactory} = require("@ui5/fs"); * @param {string} parameters.options.version Project version * @returns {Promise} Promise resolving with undefined once data has been written */ -const generateJsdoc = async function({workspace, dependencies, options} = {}) { +const generateJsdoc = async function({buildContext, workspace, dependencies, options} = {}) { if (!options || !options.projectName || !options.namespace || !options.version || !options.pattern) { throw new Error("[generateJsdoc]: One or more mandatory options not provided"); } - const {sourcePath: resourcePath, targetPath, tmpPath} = await generateJsdoc._createTmpDirs(options.projectName); + const {sourcePath: resourcePath, targetPath, tmpPath, cleanup} = + await generateJsdoc._createTmpDirs(options.projectName); + + buildContext.registerCleanupTask(cleanup); const [writtenResourcesCount] = await Promise.all([ generateJsdoc._writeResourcesToDir({ @@ -64,7 +69,6 @@ const generateJsdoc = async function({workspace, dependencies, options} = {}) { })); }; - /** * Create temporary directories for JSDoc generation processor * @@ -73,7 +77,7 @@ const generateJsdoc = async function({workspace, dependencies, options} = {}) { * @returns {Promise} Promise resolving with sourcePath, targetPath and tmpPath strings */ async function createTmpDirs(projectName) { - const {path: tmpDirPath} = await createTmpDir(projectName); + const tmpDirPath = await generateJsdoc._createTmpDir(projectName); const sourcePath = path.join(tmpDirPath, "src"); // dir will be created by writing project resources below await makeDir(sourcePath, {fs}); @@ -86,7 +90,10 @@ async function createTmpDirs(projectName) { return { sourcePath, targetPath, - tmpPath + tmpPath, + cleanup: async () => { + return rimraf(tmpDirPath); + } }; } @@ -95,28 +102,16 @@ async function createTmpDirs(projectName) { * * @private * @param {string} projectName Project name used for naming the temporary directory - * @param {boolean} [keep=false] Whether to keep the temporary directory - * @returns {Promise} Promise resolving with path of the temporary directory + * @returns {Promise} Promise resolving with path of the temporary directory */ -function createTmpDir(projectName, keep = false) { - // Remove all non alpha-num characters from project name +async function createTmpDir(projectName) { const sanitizedProjectName = projectName.replace(/[^A-Za-z0-9]/g, ""); - return new Promise((resolve, reject) => { - tmp.dir({ - prefix: `ui5-tooling-tmp-jsdoc-${sanitizedProjectName}-`, - keep, - unsafeCleanup: true - }, (err, path) => { - if (err) { - reject(err); - return; - } - - resolve({ - path - }); - }); - }); + + const tmpRootPath = path.join(os.tmpdir(), "ui5-tooling"); + await makeDir(tmpRootPath, {fs}); + + // Appending minus sign also because node docs advise to "avoid trailing X characters in prefix" + return mkdtemp(path.join(tmpRootPath, `jsdoc-${sanitizedProjectName}-`)); } /** diff --git a/lib/types/AbstractBuilder.js b/lib/types/AbstractBuilder.js index 9c894719a..35615c78b 100644 --- a/lib/types/AbstractBuilder.js +++ b/lib/types/AbstractBuilder.js @@ -14,7 +14,7 @@ class AbstractBuilder { * @param {Object} parameters.project Project configuration * @param {GroupLogger} parameters.parentLogger Logger to use */ - constructor({resourceCollections, project, parentLogger}) { + constructor({resourceCollections, project, parentLogger, buildContext}) { if (new.target === AbstractBuilder) { throw new TypeError("Class 'AbstractBuilder' is abstract"); } @@ -26,8 +26,8 @@ class AbstractBuilder { this.tasks = {}; this.taskExecutionOrder = []; - this.addStandardTasks({resourceCollections, project, log: this.log}); - this.addCustomTasks({resourceCollections, project}); + this.addStandardTasks({resourceCollections, project, log: this.log, buildContext}); + this.addCustomTasks({resourceCollections, project, buildContext}); } /** @@ -42,7 +42,7 @@ class AbstractBuilder { * @param {Object} parameters.project Project configuration * @param {Object} parameters.log @ui5/logger logger instance */ - addStandardTasks({resourceCollections, project, log}) { + addStandardTasks({resourceCollections, project, log, buildContext}) { throw new Error("Function 'addStandardTasks' is not implemented"); } @@ -56,7 +56,7 @@ class AbstractBuilder { * @param {ReaderCollection} parameters.resourceCollections.dependencies Workspace Resource * @param {Object} parameters.project Project configuration */ - addCustomTasks({resourceCollections, project}) { + addCustomTasks({resourceCollections, project, buildContext}) { const projectCustomTasks = project.builder && project.builder.customTasks; if (!projectCustomTasks || projectCustomTasks.length === 0) { return; // No custom tasks defined diff --git a/lib/types/application/applicationType.js b/lib/types/application/applicationType.js index b6e85b83b..e2f0d7114 100644 --- a/lib/types/application/applicationType.js +++ b/lib/types/application/applicationType.js @@ -5,8 +5,8 @@ module.exports = { format: function(project) { return new ApplicationFormatter().format(project); }, - build: function({resourceCollections, tasks, project, parentLogger}) { - return new ApplicationBuilder({resourceCollections, project, parentLogger}).build(tasks); + build: function({resourceCollections, tasks, project, parentLogger, buildContext}) { + return new ApplicationBuilder({resourceCollections, project, parentLogger, buildContext}).build(tasks); }, // Export type classes for extensibility diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index 18406bc6d..bd5121c2b 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -18,7 +18,7 @@ const tasks = { // can't require index.js due to circular dependency }; class LibraryBuilder extends AbstractBuilder { - addStandardTasks({resourceCollections, project, parentLogger}) { + addStandardTasks({resourceCollections, project, parentLogger, buildContext}) { const namespace = project.metadata.name.replace(/\./g, "/"); this.addTask("replaceCopyright", () => { @@ -57,6 +57,7 @@ class LibraryBuilder extends AbstractBuilder { } return generateJsdoc({ + buildContext, workspace: resourceCollections.workspace, dependencies: resourceCollections.dependencies, options: { diff --git a/lib/types/library/libraryType.js b/lib/types/library/libraryType.js index 222034322..3b86fd424 100644 --- a/lib/types/library/libraryType.js +++ b/lib/types/library/libraryType.js @@ -5,8 +5,8 @@ module.exports = { format: function(project) { return new LibraryFormatter().format(project); }, - build: function({resourceCollections, tasks, project, parentLogger}) { - return new LibraryBuilder({resourceCollections, project, parentLogger}).build(tasks); + build: function({resourceCollections, tasks, project, parentLogger, buildContext}) { + return new LibraryBuilder({resourceCollections, project, parentLogger, buildContext}).build(tasks); }, // Export type classes for extensibility diff --git a/package-lock.json b/package-lock.json index a302a84ce..7c4bdbb87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7803,14 +7803,6 @@ "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", "dev": true }, - "tmp": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", - "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", - "requires": { - "rimraf": "^2.6.3" - } - }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", diff --git a/package.json b/package.json index d9462d919..82ebcefbb 100644 --- a/package.json +++ b/package.json @@ -111,9 +111,9 @@ "pretty-data": "^0.40.0", "pretty-hrtime": "^1.0.3", "replacestream": "^4.0.3", + "rimraf": "^2.6.3", "semver": "^6.0.0", "tap-xunit": "^2.3.0", - "tmp": "0.1.0", "uglify-es": "^3.2.2", "xml2js": "^0.4.17", "yazl": "^2.5.1" @@ -133,7 +133,6 @@ "nyc": "^14.1.1", "opn-cli": "^4.1.0", "recursive-readdir": "^2.1.1", - "rimraf": "^2.6.3", "sinon": "^7.3.2", "tap-nyan": "^1.1.0" } diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index 1a0792cde..975878066 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -6,6 +6,8 @@ const fs = require("graceful-fs"); const {promisify} = require("util"); const readFile = promisify(fs.readFile); const assert = chai.assert; +const sinon = require("sinon"); +const mock = require("mock-require"); const ui5Builder = require("../../../"); const builder = ui5Builder.builder; @@ -71,6 +73,10 @@ async function checkFileContentsIgnoreLineFeeds(expectedFiles, expectedPath, des } } +test.afterEach.always((t) => { + sinon.restore(); +}); + test("Build application.a", (t) => { const destPath = "./test/tmp/build/application.a/dest"; const expectedPath = path.join("test", "expected", "build", "application.a", "dest"); @@ -91,6 +97,17 @@ test("Build application.a", (t) => { }); }); + +test("Build application.a with error", async (t) => { + const destPath = "./test/tmp/build/application.a/dest"; + + const error = await t.throws(builder.build({ + tree: applicationATreeBadType, + destPath + })); + t.deepEqual(error.message, `Unknown type 'non existent'`); +}); + test("Build application.a [dev mode]", (t) => { const destPath = "./test/tmp/build/application.a/dest-dev"; const expectedPath = path.join("test", "expected", "build", "application.a", "dest-dev"); @@ -320,6 +337,60 @@ test("Build theme.j even without an library", (t) => { }); }); +test.serial("Cleanup", async (t) => { + const BuildContext = require("../../../lib/builder/BuildContext"); + const projectContext = "project context"; + const createProjectContextStub = sinon.stub(BuildContext.prototype, "createProjectContext").returns(projectContext); + const executeCleanupTasksStub = sinon.stub(BuildContext.prototype, "executeCleanupTasks").returns(projectContext); + const applicationType = require("../../../lib/types/application/applicationType"); + const appBuildStub = sinon.stub(applicationType, "build").resolves(); + + const builder = mock.reRequire("../../../lib/builder/builder"); + + function getProcessListenerCount() { + return ["SIGHUP", "SIGINT", "SIGTERM", "SIGBREAK"].map((eventName) => { + return process.listenerCount(eventName); + }); + } + + const listenersBefore = getProcessListenerCount(); + + const destPath = "./test/tmp/build/cleanup"; + // Success case + const pBuildSuccess = builder.build({ + tree: applicationATree, + destPath + }); + t.deepEqual(getProcessListenerCount(), listenersBefore.map((x) => x+1), + "Per signal, one new listener registered"); + + await pBuildSuccess; + t.deepEqual(getProcessListenerCount(), listenersBefore, "All signal listeners got deregistered"); + + t.deepEqual(appBuildStub.callCount, 1, "Build called once"); + t.deepEqual(createProjectContextStub.callCount, 1, "One project context got created"); + const createProjectContextParams = createProjectContextStub.getCall(0).args[0]; + t.truthy(createProjectContextParams.resources.workspace, "resources.workspace object provided"); + t.truthy(createProjectContextParams.resources.dependencies, "resources.dependencies object provided"); + t.deepEqual(Object.keys(createProjectContextParams), ["resources"], + "resource parameter (and no others) provided"); + t.deepEqual(executeCleanupTasksStub.callCount, 1, "Cleanup called once"); + + // Error case + const pBuildError = builder.build({ + tree: applicationATreeBadType, + destPath + }); + t.deepEqual(getProcessListenerCount(), listenersBefore.map((x) => x+1), + "Per signal, one new listener registered"); + + const error = await t.throws(pBuildError); + t.deepEqual(error.message, `Unknown type 'non existent'`); + t.deepEqual(getProcessListenerCount(), listenersBefore, "All signal listeners got deregistered"); + + t.deepEqual(executeCleanupTasksStub.callCount, 2, "Cleanup called twice"); +}); + const applicationATree = { "id": "application.a", "version": "1.0.0", @@ -444,6 +515,29 @@ const applicationATree = { } }; + +const applicationATreeBadType = { + "id": "application.a", + "version": "1.0.0", + "path": applicationAPath, + "_level": 0, + "specVersion": "0.1", + "type": "non existent", + "metadata": { + "name": "application.a" + }, + "resources": { + "configuration": { + "paths": { + "webapp": "webapp" + } + }, + "pathMappings": { + "/": "webapp" + } + } +}; + const applicationGTree = { "id": "application.g", "version": "1.0.0", diff --git a/test/lib/tasks/jsdoc/generateJsdoc.js b/test/lib/tasks/jsdoc/generateJsdoc.js index 9e7878e05..08f8d6691 100644 --- a/test/lib/tasks/jsdoc/generateJsdoc.js +++ b/test/lib/tasks/jsdoc/generateJsdoc.js @@ -1,55 +1,74 @@ const {test} = require("ava"); const sinon = require("sinon"); -const tmp = require("tmp"); +const fs = require("graceful-fs"); +const os = require("os"); const path = require("path"); const mock = require("mock-require"); const generateJsdoc = require("../../../../lib/tasks/jsdoc/generateJsdoc"); -test.beforeEach((t) => { - t.context.tmpStub = sinon.stub(tmp, "dir"); -}); - test.afterEach.always((t) => { sinon.restore(); }); test.serial("createTmpDir successful", async (t) => { - t.context.tmpStub.callsArgWithAsync(1, undefined, "some/path"); + const makeDirStub = sinon.stub().resolves(); + mock("make-dir", makeDirStub); + + const mkdtempStub = sinon.stub(fs, "mkdtemp").callsArgWithAsync(1, undefined, "some/path"); + const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); const res = await generateJsdoc._createTmpDir("som$e.nam3/space"); // non alphanum characters get removed - t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); - t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-somenam3space-"); - t.deepEqual(res, {path: "some/path"}, "Correct path returned"); + const tmpRootPath = path.join(os.tmpdir(), "ui5-tooling"); + + t.deepEqual(makeDirStub.callCount, 1, "One directory got created"); + t.deepEqual(makeDirStub.getCall(0).args[0], tmpRootPath, "Correct tmp root dir got created"); + + t.deepEqual(mkdtempStub.callCount, 1, "mkdtemp is called once"); + t.deepEqual(mkdtempStub.getCall(0).args[0], path.join(tmpRootPath, "jsdoc-somenam3space-")); + t.deepEqual(res, "some/path", "Correct path returned"); + + mock.stop("make-dir"); }); test.serial("createTmpDir error", async (t) => { - t.context.tmpStub.callsArgWithAsync(1, {message: "Dir creation failed"}, "some/path"); + const makeDirStub = sinon.stub().resolves(); + mock("make-dir", makeDirStub); + + const mkdtempStub = sinon.stub(fs, "mkdtemp").callsArgWithAsync(1, {message: "Dir creation failed"}, "some/path"); + const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); const res = await t.throws(generateJsdoc._createTmpDir("some.namespace")); - t.deepEqual(t.context.tmpStub.callCount, 1, "Tmp dir is called once"); - t.deepEqual(t.context.tmpStub.getCall(0).args[0].prefix, "ui5-tooling-tmp-jsdoc-somenamespace-"); + const tmpRootPath = path.join(os.tmpdir(), "ui5-tooling"); + + t.deepEqual(makeDirStub.callCount, 1, "One directory got created"); + t.deepEqual(makeDirStub.getCall(0).args[0], tmpRootPath, "Correct tmp root dir got created"); + + t.deepEqual(mkdtempStub.callCount, 1, "mkdtemp is called once"); + t.deepEqual(mkdtempStub.getCall(0).args[0], path.join(tmpRootPath, "jsdoc-somenamespace-")); t.deepEqual(res, {message: "Dir creation failed"}, "Dir creation failed"); + + mock.stop("make-dir"); }); test.serial("createTmpDirs", async (t) => { const makeDirStub = sinon.stub().resolves(); mock("make-dir", makeDirStub); + const rimrafStub = sinon.stub().resolves(); + mock("rimraf", rimrafStub); const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); - t.context.tmpStub.callsArgWithAsync(1, undefined, "/some/path"); + const createTmpDirStub = sinon.stub(generateJsdoc, "_createTmpDir").resolves("/some/path"); const res = await generateJsdoc._createTmpDirs("some.namespace"); - t.deepEqual(res, { - sourcePath: path.join("/", "some", "path", "src"), - targetPath: path.join("/", "some", "path", "target"), - tmpPath: path.join("/", "some", "path", "tmp") - }, "Correct temporary directories returned"); - t.deepEqual(makeDirStub.callCount, 3, "One directory got created"); + t.deepEqual(createTmpDirStub.callCount, 1, "creteTmpDir called once"); + t.deepEqual(createTmpDirStub.getCall(0).args[0], "some.namespace", "creteTmpDir called with correct argument"); + + t.deepEqual(makeDirStub.callCount, 3, "Three directory got created"); t.deepEqual(makeDirStub.getCall(0).args[0], path.join("/", "some", "path", "src"), "Correct srcdir path got created"); t.deepEqual(makeDirStub.getCall(1).args[0], path.join("/", "some", "path", "target"), @@ -57,7 +76,16 @@ test.serial("createTmpDirs", async (t) => { t.deepEqual(makeDirStub.getCall(2).args[0], path.join("/", "some", "path", "tmp"), "Correct tmp dir path got created"); + t.deepEqual(res.sourcePath, path.join("/", "some", "path", "src"), "Correct temporary src dir path returned"); + t.deepEqual(res.targetPath, path.join("/", "some", "path", "target"), "Correct temporary target dir path returned"); + t.deepEqual(res.tmpPath, path.join("/", "some", "path", "tmp"), "Correct temporary tmp dir path returned"); + + res.cleanup(); + t.deepEqual(rimrafStub.callCount, 1, "Cleanup callback: rimraf called once"); + t.deepEqual(rimrafStub.getCall(0).args[0], "/some/path", "Cleanup callback: rimraf called with correct path"); + mock.stop("make-dir"); + mock.stop("rimraf"); }); test.serial("writeResourcesToDir with byGlobSource", async (t) => { @@ -178,10 +206,12 @@ test.serial("generateJsdoc", async (t) => { mock("../../../../lib/processors/jsdoc/jsdocGenerator", jsdocGeneratorStub); const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); + const cleanupStub = sinon.stub().resolves(); const createTmpDirsStub = sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ sourcePath: "/some/source/path", targetPath: "/some/target/path", tmpPath: "/some/tmp/path", + cleanup: cleanupStub }); const writeResourcesToDirStub = sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(1); const writeDependencyApisToDirStub = sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(0); @@ -190,7 +220,13 @@ test.serial("generateJsdoc", async (t) => { const workspace = { write: writeStub }; + + const registerCleanupTaskStub = sinon.stub(); + const buildContext = { + registerCleanupTask: registerCleanupTaskStub + }; await generateJsdoc({ + buildContext, workspace, dependencies: "dependencies", options: { @@ -205,6 +241,10 @@ test.serial("generateJsdoc", async (t) => { t.deepEqual(createTmpDirsStub.getCall(0).args[0], "some.project", "createTmpDirs got called with correct arguments"); + t.deepEqual(registerCleanupTaskStub.callCount, 1, "registerCleanupTask called once"); + t.deepEqual(registerCleanupTaskStub.getCall(0).args[0], cleanupStub, + "registerCleanupTask called with correct argument"); + t.deepEqual(writeResourcesToDirStub.callCount, 1, "writeResourcesToDir got called once"); t.deepEqual(writeResourcesToDirStub.getCall(0).args[0], { workspace, @@ -249,10 +289,12 @@ test.serial("generateJsdoc with missing resources", async (t) => { sinon.stub(logger, "getLogger").returns(myLoggerInstance); const generateJsdoc = mock.reRequire("../../../../lib/tasks/jsdoc/generateJsdoc"); + const cleanupStub = sinon.stub().resolves(); sinon.stub(generateJsdoc, "_createTmpDirs").resolves({ sourcePath: "/some/source/path", targetPath: "/some/target/path", tmpPath: "/some/tmp/path", + cleanup: cleanupStub }); sinon.stub(generateJsdoc, "_writeResourcesToDir").resolves(0); sinon.stub(generateJsdoc, "_writeDependencyApisToDir").resolves(0); @@ -261,7 +303,12 @@ test.serial("generateJsdoc with missing resources", async (t) => { const workspace = { write: writeStub }; + const registerCleanupTaskStub = sinon.stub(); + const buildContext = { + registerCleanupTask: registerCleanupTaskStub + }; await generateJsdoc({ + buildContext, workspace, dependencies: "dependencies", options: { @@ -272,6 +319,10 @@ test.serial("generateJsdoc with missing resources", async (t) => { } }); + t.deepEqual(registerCleanupTaskStub.callCount, 1, "registerCleanupTask called once"); + t.deepEqual(registerCleanupTaskStub.getCall(0).args[0], cleanupStub, + "registerCleanupTask called with correct argument"); + t.deepEqual(infoLogStub.callCount, 1, "One message has been logged"); t.deepEqual(infoLogStub.getCall(0).args[0], "Failed to find any input resources for project some.project " + "using pattern some pattern. Skipping JSDoc generation...",