Skip to content

Commit 240f91c

Browse files
authored
Merge pull request #9961 from quarto-dev/tests-project-cleanup
2 parents 6569731 + 5693872 commit 240f91c

File tree

2 files changed

+104
-42
lines changed

2 files changed

+104
-42
lines changed

tests/smoke/render/render.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
noSupportingFiles,
1616
outputCreated,
1717
} from "../../verify.ts";
18+
import { safeRemoveSync } from "../../../src/core/path.ts";
19+
import { safeExistsSync } from "../../../src/core/path.ts";
1820

1921
export function testSimpleIsolatedRender(
2022
file: string,
@@ -82,11 +84,11 @@ export function cleanoutput(
8284
metadata?: Record<string, any>,
8385
) {
8486
const out = outputForInput(input, to, projectOutDir, metadata);
85-
if (existsSync(out.outputPath)) {
86-
Deno.removeSync(out.outputPath);
87+
if (safeExistsSync(out.outputPath)) {
88+
safeRemoveSync(out.outputPath);
8789
}
88-
if (existsSync(out.supportPath)) {
89-
Deno.removeSync(out.supportPath, { recursive: true });
90+
if (safeExistsSync(out.supportPath)) {
91+
safeRemoveSync(out.supportPath, { recursive: true });
9092
}
9193
}
9294

tests/smoke/smoke-all.test.ts

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { jupyterNotebookToMarkdown } from "../../src/command/convert/jupyter.ts"
4040
import { basename, dirname, join, relative } from "../../src/deno_ral/path.ts";
4141
import { existsSync, WalkEntry } from "fs/mod.ts";
4242
import { quarto } from "../../src/quarto.ts";
43+
import { safeExistsSync, safeRemoveSync } from "../../src/core/path.ts";
4344

4445
async function fullInit() {
4546
await initYamlIntelligenceResourcesFromFilesystem();
@@ -208,15 +209,22 @@ if (Deno.args.length === 0) {
208209
}
209210
}
210211

212+
// To store project path we render before testing file testSpecs
211213
const renderedProjects: Set<string> = new Set();
214+
// To store information of all the project we render so that we can cleanup after testing
215+
const testedProjects: Set<string> = new Set();
216+
217+
// Create an array to hold all the promises for the tests of files
218+
let testFilesPromises = [];
212219

213220
for (const { path: fileName } of files) {
214221
const input = relative(Deno.cwd(), fileName);
215222

216223
const metadata = input.endsWith("md") // qmd or md
217224
? readYamlFromMarkdown(Deno.readTextFileSync(input))
218225
: readYamlFromMarkdown(await jupyterNotebookToMarkdown(input, false));
219-
const testSpecs = [];
226+
227+
const testSpecs: QuartoInlineTestSpec[] = [];
220228

221229
if (hasTestSpecs(metadata)) {
222230
testSpecs.push(...resolveTestSpecs(input, metadata));
@@ -231,51 +239,100 @@ for (const { path: fileName } of files) {
231239
}
232240
}
233241

234-
// FIXME this will leave the project in a dirty state
235-
// tests run asynchronously so we can't clean up until all tests are done
236-
// and I don't know of a way to wait for that
242+
// Get project path for this input and store it if this is a project (used for cleaning)
243+
const projectPath = findProjectDir(input);
244+
if (projectPath) testedProjects.add(projectPath);
237245

238-
if ((metadata["_quarto"] as any)?.["render-project"]) {
239-
const projectPath = findProjectDir(input);
240-
if (projectPath && !renderedProjects.has(projectPath)) {
246+
// Render project before testing individual document if required
247+
if (
248+
(metadata["_quarto"] as any)?.["render-project"] &&
249+
projectPath &&
250+
!renderedProjects.has(projectPath)
251+
) {
241252
await quarto(["render", projectPath]);
242253
renderedProjects.add(projectPath);
243254
}
244-
}
245255

246-
for (const testSpec of testSpecs) {
247-
const {
248-
format,
249-
verifyFns,
250-
//deno-lint-ignore no-explicit-any
251-
} = testSpec as any;
252-
if (format === "editor-support-crossref") {
253-
const tempFile = Deno.makeTempFileSync();
254-
testQuartoCmd("editor-support", ["crossref", "--input", input, "--output", tempFile], verifyFns, {
255-
teardown: () => {
256-
Deno.removeSync(tempFile);
257-
return Promise.resolve();
258-
}
259-
}, `quarto editor-support crossref < ${input}`);
260-
} else {
261-
testQuartoCmd("render", [input, "--to", format], verifyFns, {
262-
prereq: async () => {
263-
setInitializer(fullInit);
264-
await initState();
265-
return Promise.resolve(true);
266-
},
267-
teardown: () => {
268-
cleanoutput(input, format, undefined, metadata);
269-
return Promise.resolve();
270-
},
271-
});
256+
testFilesPromises.push(new Promise<void>(async (resolve, reject) => {
257+
try {
258+
259+
// Create an array to hold all the promises for the testSpecs
260+
let testSpecPromises = [];
261+
262+
for (const testSpec of testSpecs) {
263+
const {
264+
format,
265+
verifyFns,
266+
//deno-lint-ignore no-explicit-any
267+
} = testSpec as any;
268+
testSpecPromises.push(new Promise<void>((testSpecResolve, testSpecReject) => {
269+
try {
270+
if (format === "editor-support-crossref") {
271+
const tempFile = Deno.makeTempFileSync();
272+
testQuartoCmd("editor-support", ["crossref", "--input", input, "--output", tempFile], verifyFns, {
273+
teardown: () => {
274+
Deno.removeSync(tempFile);
275+
testSpecResolve(); // Resolve the promise for the testSpec
276+
return Promise.resolve();
277+
}
278+
}, `quarto editor-support crossref < ${input}`);
279+
} else {
280+
testQuartoCmd("render", [input, "--to", format], verifyFns, {
281+
prereq: async () => {
282+
setInitializer(fullInit);
283+
await initState();
284+
return Promise.resolve(true);
285+
},
286+
teardown: () => {
287+
cleanoutput(input, format, undefined, metadata);
288+
testSpecResolve(); // Resolve the promise for the testSpec
289+
return Promise.resolve();
290+
},
291+
});
292+
}
293+
} catch (error) {
294+
testSpecReject(error);
295+
}
296+
}));
297+
298+
}
299+
300+
// Wait for all the promises to resolve
301+
await Promise.all(testSpecPromises);
302+
303+
// Resolve the promise for the file
304+
resolve();
305+
306+
} catch (error) {
307+
reject(error);
272308
}
273-
}
309+
}));
274310
}
275311

312+
// Wait for all the promises to resolve
313+
// Meaning all the files have been tested and we can clean
314+
Promise.all(testFilesPromises).then(() => {
315+
// Clean up any projects that were tested
316+
for (const project of testedProjects) {
317+
// Clean project output directory
318+
const projectOutDir = join(project, findProjectOutputDir(undefined, project));
319+
if (safeExistsSync(projectOutDir)) {
320+
safeRemoveSync(projectOutDir, { recursive: true });
321+
}
322+
// Clean hidden .quarto directory
323+
const hiddenQuarto = join(project, ".quarto");
324+
if (safeExistsSync(hiddenQuarto)) {
325+
safeRemoveSync(hiddenQuarto, { recursive: true });
326+
}
327+
}
328+
}).catch((_error) => {});
329+
330+
276331
function findProjectDir(input: string): string | undefined {
277332
let dir = dirname(input);
278-
while (dir !== "" && dir !== ".") {
333+
// This is used for smoke-all tests and should stop there
334+
// to avoid side effect of _quarto.yml outside of Quarto tests folders
335+
while (dir !== "" && dir !== "." && !/smoke-all$/.test(dir)) {
279336
const filename = ["_quarto.yml", "_quarto.yaml"].find((file) => {
280337
const yamlPath = join(dir, file);
281338
if (existsSync(yamlPath)) {
@@ -294,8 +351,11 @@ function findProjectDir(input: string): string | undefined {
294351
}
295352
}
296353

297-
function findProjectOutputDir(input: string) {
298-
const dir = findProjectDir(input);
354+
function findProjectOutputDir(input?: string, dir?: string) {
355+
if (dir === undefined && input === undefined) {
356+
throw new Error("Either input or dir must be provided");
357+
}
358+
dir = dir ?? findProjectDir(input!);
299359
if (!dir) {
300360
return;
301361
}

0 commit comments

Comments
 (0)