@@ -40,6 +40,7 @@ import { jupyterNotebookToMarkdown } from "../../src/command/convert/jupyter.ts"
4040import { basename , dirname , join , relative } from "../../src/deno_ral/path.ts" ;
4141import { existsSync , WalkEntry } from "fs/mod.ts" ;
4242import { quarto } from "../../src/quarto.ts" ;
43+ import { safeExistsSync , safeRemoveSync } from "../../src/core/path.ts" ;
4344
4445async 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
211213const 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
213220for ( 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+
276331function 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 !== "." && ! / s m o k e - a l l $ / . 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