Skip to content

Commit

Permalink
Merge pull request #4388 from James-Yu/unit-test-build
Browse files Browse the repository at this point in the history
Unit test build
  • Loading branch information
James-Yu authored Sep 16, 2024
2 parents cbe57e3 + b60e0aa commit bda06e9
Show file tree
Hide file tree
Showing 15 changed files with 766 additions and 199 deletions.
21 changes: 10 additions & 11 deletions src/compile/build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as vscode from 'vscode'
import * as path from 'path'
import * as cs from 'cross-spawn'
import { pickRootPath } from '../utils/quick-pick'
import { lw } from '../lw'
import type { ProcessEnv, RecipeStep, Step } from '../types'
Expand Down Expand Up @@ -143,6 +142,7 @@ async function build(skipSelection: boolean = false, rootFile: string | undefine
*/
async function buildLoop() {
if (isBuilding) {
logger.log('Another build loop is already running.')
return
}

Expand All @@ -156,16 +156,15 @@ async function buildLoop() {
if (step === undefined) {
break
}
lw.compile.lastSteps.push(step)
const env = spawnProcess(step)
const success = await monitorProcess(step, env)
skipped = skipped && !(step.isExternal || !step.isSkipped)
skipped = skipped && !step.isExternal && step.isSkipped
if (success && queue.isLastStep(step)) {
await afterSuccessfulBuilt(step, skipped)
}
}
isBuilding = false
setTimeout(() => lw.compile.compiledPDFWriting--, 1000)
setTimeout(() => lw.compile.compiledPDFWriting--, vscode.workspace.getConfiguration('latex-workshop').get('latex.watch.pdf.delay') as number)
}

/**
Expand Down Expand Up @@ -208,20 +207,20 @@ function spawnProcess(step: Step): ProcessEnv {
const args = step.args
if (args && !step.name.endsWith(lw.constant.MAGIC_PROGRAM_ARGS_SUFFIX)) {
// All optional arguments are given as a unique string (% !TeX options) if any, so we use {shell: true}
lw.compile.process = cs.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true})
lw.compile.process = lw.external.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true})
} else {
lw.compile.process = cs.spawn(step.command, args, {cwd: path.dirname(step.rootFile), env})
lw.compile.process = lw.external.spawn(step.command, args ?? [], {cwd: path.dirname(step.rootFile), env})
}
} else if (!step.isExternal) {
let cwd = path.dirname(step.rootFile)
if (step.command === 'latexmk' && step.rootFile === lw.root.subfiles.path && lw.root.dir.path) {
cwd = lw.root.dir.path
}
logger.log(`cwd: ${cwd}`)
lw.compile.process = cs.spawn(step.command, step.args, {cwd, env})
lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd, env})
} else {
logger.log(`cwd: ${step.cwd}`)
lw.compile.process = cs.spawn(step.command, step.args, {cwd: step.cwd})
lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd: step.cwd})
}
logger.log(`LaTeX build process spawned with PID ${lw.compile.process.pid}.`)
return env
Expand All @@ -245,13 +244,13 @@ async function monitorProcess(step: Step, env: ProcessEnv): Promise<boolean> {
return false
}
let stdout = ''
lw.compile.process.stdout.on('data', (msg: Buffer | string) => {
lw.compile.process.stdout?.on('data', (msg: Buffer | string) => {
stdout += msg
logger.logCompiler(msg.toString())
})

let stderr = ''
lw.compile.process.stderr.on('data', (msg: Buffer | string) => {
lw.compile.process.stderr?.on('data', (msg: Buffer | string) => {
stderr += msg
logger.logCompiler(msg.toString())
})
Expand Down Expand Up @@ -384,7 +383,7 @@ function handleNoRetryError(configuration: vscode.WorkspaceConfiguration, step:
* shows an error message to the user and clears the BuildToolQueue.
*/
function handleExternalCommandError() {
logger.log(`Build returns with error on PID ${lw.compile.process?.pid}.`)
logger.log(`Build with external command returns error on PID ${lw.compile.process?.pid}.`)
logger.refreshStatus('x', 'errorForeground', undefined, 'warning')
void logger.showErrorMessageWithCompilerLogButton('Build terminated with error.')
queue.clear()
Expand Down
6 changes: 2 additions & 4 deletions src/compile/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type { ChildProcessWithoutNullStreams } from 'child_process'
import type { Step } from '../types'
import type { ChildProcess } from 'child_process'
import { build, autoBuild } from './build'
import { terminate } from './terminate'

export const compile = {
build,
autoBuild,
terminate,
lastSteps: [] as Step[],
lastAutoBuildTime: 0,
compiledPDFPath: '',
compiledPDFWriting: 0,
process: undefined as ChildProcessWithoutNullStreams | undefined
process: undefined as ChildProcess | undefined
}
4 changes: 2 additions & 2 deletions src/compile/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ function findRecipe(rootFile: string, langId: string, recipeName?: string): Reci
}
}
// Find default recipe of last used
if (recipe === undefined && defaultRecipeName === 'lastUsed' && recipes.find(candidate => candidate.name === state.prevRecipe?.name)) {
recipe = state.prevRecipe
if (recipe === undefined && defaultRecipeName === 'lastUsed') {
recipe = recipes.find(candidate => candidate.name === state.prevRecipe?.name)
}
// If still not found, fallback to 'first'
if (recipe === undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/compile/terminate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cp from 'child_process'
import { lw } from '../lw'
import { queue } from './queue'

const logger = lw.log('Build', 'Recipe')
const logger = lw.log('Build', 'Terminate')

/**
* Terminate the current process of LaTeX building. This OS-specific function
Expand Down
1 change: 1 addition & 0 deletions src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function initStatusBarItem() {

function clearCompilerMessage() {
COMPILER_PANEL.clear()
CACHED_COMPILER.length = 0
}

function showLog() {
Expand Down
1 change: 0 additions & 1 deletion test/suites/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export function sleep(ms: number) {
export async function reset() {
await vscode.commands.executeCommand('workbench.action.closeAllEditors')
await Promise.all(Object.values(lw.cache.promises))
lw.compile.lastSteps = []
lw.compile.lastAutoBuildTime = 0
lw.compile.compiledPDFPath = ''
lw.compile.compiledPDFWriting = 0
Expand Down
100 changes: 50 additions & 50 deletions test/units/01_core_file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
const fixture = path.basename(__filename).split('.')[0]

before(() => {
mock.object(lw, 'file')
mock.init(lw, 'file')
})

after(() => {
Expand Down Expand Up @@ -79,82 +79,82 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir)
})

it('should get output directory with absolute `latex.outDir` and root', async () => {
await set.config('latex.outDir', '/output')
it('should get output directory with absolute `latex.outDir` and root', () => {
set.config('latex.outDir', '/output')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), '/output')
})

it('should get output directory with relative `latex.outDir` and root', async () => {
await set.config('latex.outDir', 'output')
it('should get output directory with relative `latex.outDir` and root', () => {
set.config('latex.outDir', 'output')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), 'output')
})

it('should get output directory with relative `latex.outDir` with leading `./` and root', async () => {
await set.config('latex.outDir', './output')
it('should get output directory with relative `latex.outDir` with leading `./` and root', () => {
set.config('latex.outDir', './output')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), 'output')
})

it('should get output directory with relative `latex.outDir`, root, and an input latex', async () => {
it('should get output directory with relative `latex.outDir`, root, and an input latex', () => {
const texPath = get.path(fixture, 'main.tex')

await set.config('latex.outDir', 'output')
set.config('latex.outDir', 'output')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(texPath), 'output')
})

it('should get output directory with placeholder in `latex.outDir` and root', async () => {
await set.config('latex.outDir', '%DIR%')
it('should get output directory with placeholder in `latex.outDir` and root', () => {
set.config('latex.outDir', '%DIR%')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path)
})

it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', async () => {
it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', () => {
const rootDir = get.path(fixture)
const texPath = get.path(fixture, 'main.tex')

await set.config('latex.outDir', '%DIR%')
set.config('latex.outDir', '%DIR%')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir)
})

it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', async () => {
await set.config('latex.outDir', '%DIR%')
it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', () => {
set.config('latex.outDir', '%DIR%')
set.root(fixture, 'main.tex')
lw.file.setTeXDirs(lw.root.file.path ?? '', '/output')
assert.pathStrictEqual(lw.file.getOutDir(), '/output')
})

it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', async () => {
await set.config('latex.outDir', '/output')
it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', () => {
set.config('latex.outDir', '/output')
set.root(fixture, 'main.tex')
lw.file.setTeXDirs(lw.root.file.path ?? '', '/trap')
assert.pathStrictEqual(lw.file.getOutDir(), '/output')
})

it('should ignore output directory from last compilation if no `outdir` is recorded', async () => {
await set.config('latex.outDir', '%DIR%')
it('should ignore output directory from last compilation if no `outdir` is recorded', () => {
set.config('latex.outDir', '%DIR%')
set.root(fixture, 'main.tex')
lw.file.setTeXDirs(lw.root.file.path ?? '')
assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path)
})

it('should handle empty `latex.outDir` correctly', async () => {
await set.config('latex.outDir', '')
it('should handle empty `latex.outDir` correctly', () => {
set.config('latex.outDir', '')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), './')
})

it('should handle absolute `latex.outDir` with trailing slashes correctly', async () => {
await set.config('latex.outDir', '/output/')
it('should handle absolute `latex.outDir` with trailing slashes correctly', () => {
set.config('latex.outDir', '/output/')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), '/output')
})

it('should handle relative `latex.outDir` with trailing slashes correctly', async () => {
await set.config('latex.outDir', 'output/')
it('should handle relative `latex.outDir` with trailing slashes correctly', () => {
set.config('latex.outDir', 'output/')
set.root(fixture, 'main.tex')
assert.pathStrictEqual(lw.file.getOutDir(), 'output')
})
Expand Down Expand Up @@ -184,7 +184,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
const texPath = get.path(fixture, 'main.tex')
const flsPath = get.path(fixture, 'output', 'main.fls')

await set.config('latex.outDir', 'output')
set.config('latex.outDir', 'output')
assert.pathStrictEqual(await lw.file.getFlsPath(texPath), flsPath)
})

Expand Down Expand Up @@ -233,18 +233,18 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
])
})

it('should correctly find BibTeX files in `latex.bibDirs`', async () => {
it('should correctly find BibTeX files in `latex.bibDirs`', () => {
set.root(fixture, 'main.tex')
await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ])
set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ])
const result = lw.file.getBibPath('sub.bib', lw.root.dir.path ?? '')
assert.listStrictEqual(result, [
path.resolve(lw.root.dir.path ?? '', 'subdir', 'sub.bib')
])
})

it('should return an empty array when no BibTeX file is found', async () => {
it('should return an empty array when no BibTeX file is found', () => {
set.root(fixture, 'main.tex')
await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ])
set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ])
const result = lw.file.getBibPath('nonexistent.bib', path.resolve(lw.root.dir.path ?? '', 'output'))
assert.listStrictEqual(result, [ ])
})
Expand All @@ -258,29 +258,29 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
])
})

it('should handle case when kpsewhich is disabled and BibTeX file not found', async () => {
it('should handle case when kpsewhich is disabled and BibTeX file not found', () => {
const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' })
await set.config('kpsewhich.bibtex.enabled', false)
set.config('kpsewhich.bibtex.enabled', false)
set.root(fixture, 'main.tex')
const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '')
stub.restore()
assert.listStrictEqual(result, [ ])
})

it('should handle case when kpsewhich is enabled and BibTeX file not found', async () => {
it('should handle case when kpsewhich is enabled and BibTeX file not found', () => {
const nonPath = get.path(fixture, 'nonexistent.bib')

const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' })
await set.config('kpsewhich.bibtex.enabled', true)
set.config('kpsewhich.bibtex.enabled', true)
set.root(fixture, 'main.tex')
const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '')
stub.restore()
assert.listStrictEqual(result, [ nonPath ])
})

it('should return an empty array when kpsewhich is enabled but file is not found', async () => {
it('should return an empty array when kpsewhich is enabled but file is not found', () => {
const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' })
await set.config('kpsewhich.bibtex.enabled', true)
set.config('kpsewhich.bibtex.enabled', true)
set.root(fixture, 'main.tex')
const result = lw.file.getBibPath('another-nonexistent.bib', lw.root.dir.path ?? '')
stub.restore()
Expand Down Expand Up @@ -330,40 +330,40 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
})

describe('lw.file.getJobname', () => {
it('should return the jobname if present in configuration', async () => {
it('should return the jobname if present in configuration', () => {
const texPath = get.path(fixture, 'main.tex')

await set.config('latex.jobname', 'myJob')
set.config('latex.jobname', 'myJob')
assert.strictEqual(lw.file.getJobname(texPath), 'myJob')
})

it('should return the name of the input texPath if jobname is empty', async () => {
it('should return the name of the input texPath if jobname is empty', () => {
const texPath = get.path(fixture, 'main.tex')

await set.config('latex.jobname', '')
set.config('latex.jobname', '')
const expectedJobname = path.parse(texPath).name
assert.strictEqual(lw.file.getJobname(texPath), expectedJobname)
})

it('should return the name of the input texPath if configuration is not set', async () => {
it('should return the name of the input texPath if configuration is not set', () => {
const texPath = get.path(fixture, 'main.tex')

await set.config('latex.jobname', undefined) // Ensuring the jobname is not set
set.config('latex.jobname', undefined) // Ensuring the jobname is not set
const expectedJobname = path.parse(texPath).name
assert.strictEqual(lw.file.getJobname(texPath), expectedJobname)
})
})

describe('lw.file.getPdfPath', () => {
it('should return the correct PDF path when outDir is empty', async () => {
await set.config('latex.outDir', '')
it('should return the correct PDF path when outDir is empty', () => {
set.config('latex.outDir', '')
set.root(fixture, 'main.tex')
const texpath = lw.root.file.path ?? ''
assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('.tex', '.pdf'))
})

it('should return the correct PDF path when outDir is specified', async () => {
await set.config('latex.outDir', 'output')
it('should return the correct PDF path when outDir is specified', () => {
set.config('latex.outDir', 'output')
set.root(fixture, 'main.tex')
const texpath = lw.root.file.path ?? ''
assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('main.tex', 'output/main.pdf'))
Expand Down Expand Up @@ -503,16 +503,16 @@ describe(path.basename(__filename).split('.')[0] + ':', () => {
})

describe('kpsewhich', () => {
it('should call kpsewhich with correct arguments', async () => {
await set.config('kpsewhich.path', 'kpse')
it('should call kpsewhich with correct arguments', () => {
set.config('kpsewhich.path', 'kpse')
const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' })
lw.file.kpsewhich('article.cls')
stub.restore()
sinon.assert.calledWith(stub, 'kpse', ['article.cls'], sinon.match.any)
})

it('should handle isBib flag correctly', async () => {
await set.config('kpsewhich.path', 'kpse')
it('should handle isBib flag correctly', () => {
set.config('kpsewhich.path', 'kpse')
const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' })
lw.file.kpsewhich('reference.bib', true)
stub.restore()
Expand Down
Loading

0 comments on commit bda06e9

Please sign in to comment.