New test framework

This commit is contained in:
James Yu 2022-12-27 23:00:28 +08:00
parent 95172d110a
commit 76f59606f6
9 changed files with 312 additions and 69 deletions

79
.vscode/launch.json vendored
View File

@ -2,23 +2,6 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Node Program",
"program": "${file}",
"request": "launch",
"cwd": "${fileDirname}",
"skipFiles": [
"<node_internals>/**"
],
"type": "pwa-node"
},
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Run Extension",
"type": "extensionHost",
@ -34,72 +17,30 @@
"preLaunchTask": "task-watch-all"
},
{
"name": "Run Extension With Sample",
"name": "Run Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"${workspaceFolder}/test/fixtures/testground/",
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}/samples/sample"
"--extensionTestsPath=${workspaceFolder}/out/test/suites/index"
],
"outFiles": [
"${workspaceFolder}/out/src/**/*.js"
],
"preLaunchTask": "task-watch-all"
},
{
"name": "Run Unit Tests with unittest/fixture001",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"${workspaceFolder}/test/fixtures/unittest/fixture001",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/unittest.index"
],
"outFiles": [
"${workspaceFolder}/out/src/**/*.js",
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "task-watch-all"
},
{
"name": "Run Unit Tests with unittest/fixture020",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"${workspaceFolder}/test/fixtures/unittest/fixture020_structure/",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/unittest.index"
],
"outFiles": [
"${workspaceFolder}/out/src/**/*.js",
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "task-watch-all",
"env": {
"LATEXWORKSHOP_CI": "1"
"LATEXWORKSHOP_CI": "1",
"LATEXWORKSHOP_CISUITE": ""
}
},
{
"name": "Run Unit Tests with unittest/fixture030",
"type": "extensionHost",
"name": "Python: Current File",
"type": "python",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"${workspaceFolder}/test/fixtures/unittest/fixture030_linter/",
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test/unittest.index"
],
"outFiles": [
"${workspaceFolder}/out/src/**/*.js",
"${workspaceFolder}/out/test/**/*.js"
],
"preLaunchTask": "task-watch-all",
"env": {
"LATEXWORKSHOP_CI": "1"
}
"program": "${file}",
"console": "integratedTerminal"
}
]
}
}

21
package-lock.json generated
View File

@ -27,6 +27,7 @@
"@types/micromatch": "4.0.2",
"@types/mocha": "9.1.1",
"@types/node": "16.11.68",
"@types/rimraf": "^3.0.2",
"@types/tmp": "0.2.3",
"@types/vscode": "1.67.0",
"@types/workerpool": "6.1.0",
@ -245,6 +246,16 @@
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"node_modules/@types/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==",
"dev": true,
"dependencies": {
"@types/glob": "*",
"@types/node": "*"
}
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.3.13.tgz",
@ -4466,6 +4477,16 @@
"integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==",
"dev": true
},
"@types/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==",
"dev": true,
"requires": {
"@types/glob": "*",
"@types/node": "*"
}
},
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.3.13.tgz",

View File

@ -2567,6 +2567,7 @@
"@types/micromatch": "4.0.2",
"@types/mocha": "9.1.1",
"@types/node": "16.11.68",
"@types/rimraf": "^3.0.2",
"@types/tmp": "0.2.3",
"@types/vscode": "1.67.0",
"@types/workerpool": "6.1.0",

0
test/fixtures/testground/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,2 @@
{
}

View File

@ -52,9 +52,41 @@ async function runTestsOnEachFixture(targetName: 'build' | 'rootfile' | 'viewer'
clearTimeout(nodejsTimeout)
}
}
async function runTestground() {
try {
const extensionDevelopmentPath = path.resolve(__dirname, '../../')
const extensionTestsPath = path.resolve(__dirname, './suites/index')
const fixtures = glob.sync('test/fixtures/testground', { cwd: extensionDevelopmentPath })
for (const fixture of fixtures) {
await runTests({
version: '1.71.0',
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [
fixture,
'--user-data-dir=' + tmpFile.dirSync({ unsafeCleanup: true }).name,
'--extensions-dir=' + tmpFile.dirSync({ unsafeCleanup: true }).name,
'--lang=C',
'--disable-keytar',
'--disable-telemetry',
'--disable-gpu'
],
extensionTestsEnv: {
LATEXWORKSHOP_CI: '1'
}
})
}
} catch (error) {
console.error(error)
console.error('Failed to run tests')
process.exit(1)
}
}
async function main() {
try {
await runTestground()
await runTestsOnEachFixture('rootfile')
await runTestsOnEachFixture('build')
await runTestsOnEachFixture('viewer')

View File

@ -0,0 +1,83 @@
import * as vscode from 'vscode'
import * as path from 'path'
import * as fs from 'fs'
import rimraf from 'rimraf'
import * as assert from 'assert'
import { Extension, activate } from '../../src/main'
import { assertBuild, runTest, writeTest } from './utils'
suite('Build TeX files test suite', () => {
let extension: Extension | undefined
const suiteName = path.basename(__filename).replace('.test.js', '')
let testground = path.resolve(__dirname, '../../../test/fixtures/testground')
suiteSetup(async () => {
await vscode.commands.executeCommand('latex-workshop.activate')
extension = vscode.extensions.getExtension<ReturnType<typeof activate>>('James-Yu.latex-workshop')?.exports.extension
assert.ok(extension)
testground = path.resolve(extension.extensionRoot, 'test/fixtures/testground')
})
setup(async () => {
await vscode.commands.executeCommand('latex-workshop.activate')
})
teardown(async () => {
if (path.basename(testground) === 'testground') {
rimraf(testground + '/*', (e) => {if (e) {console.error(e)}})
fs.closeSync(fs.openSync(testground + '/.gitkeep', 'a'))
}
await vscode.commands.executeCommand('workbench.action.closeAllEditors')
await vscode.workspace.getConfiguration().update('latex-workshop.latex.tools', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.outDir', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.recipes', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.build.forceRecipeUsage', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.doNotPrompt', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.useSubFile', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.search.rootFiles.include', undefined)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.search.rootFiles.exclude', undefined)
})
function writeMainTeX() {
writeTest({fixture: testground, fileName: 'main.tex'}, '\\documentclass{article}', '\\begin{document}', 'abc', '\\end{document}')
}
function writeSubFileTeX() {
writeTest({fixture: testground, fileName: 'main.tex'}, '\\documentclass{article}', '\\usepackage{subfiles}', '\\begin{document}', 'main main', '\\subfile{sub/s}', '\\end{document}')
writeTest({fixture: testground, fileName: 'sub/s.tex'}, '\\documentclass[../main.tex]{subfiles}', '\\begin{document}', 'sub sub', '\\end{document}')
}
runTest({suiteName, fixtureName: 'testground', testName: 'build'}, async () => {
writeMainTeX()
await assertBuild({fixture: testground, texFileName: 'main.tex', pdfFileName: 'main.pdf', extension})
})
runTest({suiteName, fixtureName: 'testground', testName: 'build with subfiles'}, async () => {
writeSubFileTeX()
await assertBuild({fixture: testground, texFileName: 'main.tex', pdfFileName: 'main.pdf', extension})
})
runTest({suiteName, fixtureName: 'testground', testName: 'same placeholders multiple times'}, async () => {
const tools = [{name: 'latexmk', command: 'latexmk', args: ['-synctex=1', '-interaction=nonstopmode', '-file-line-error', '-pdf', '%DOC%', '%DOC%', '%DOC%']}]
await vscode.workspace.getConfiguration().update('latex-workshop.latex.tools', tools)
writeMainTeX()
await assertBuild({fixture: testground, texFileName: 'main.tex', pdfFileName: 'main.pdf', extension})
})
runTest({suiteName, fixtureName: 'testground', testName: 'auto-detect subfile root 1'}, async () => {
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.doNotPrompt', true)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.useSubFile', true)
writeSubFileTeX()
await assertBuild({fixture: testground, texFileName: 'sub/s.tex', pdfFileName: 'sub/s.pdf', extension})
})
runTest({suiteName, fixtureName: 'testground', testName: 'auto-detect subfile root 2'}, async () => {
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.doNotPrompt', true)
await vscode.workspace.getConfiguration().update('latex-workshop.latex.rootFile.useSubFile', false)
writeSubFileTeX()
await assertBuild({fixture: testground, texFileName: 'sub/s.tex', pdfFileName: 'main.pdf', extension})
})
})

36
test/suites/index.ts Normal file
View File

@ -0,0 +1,36 @@
import * as path from 'path'
import Mocha from 'mocha'
import glob from 'glob'
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
})
return new Promise((resolve, reject) => {
glob('**/**.test.js', { cwd: __dirname }, (error, files) => {
if (error) {
return reject(error)
}
// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(__dirname, f)))
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
reject(new Error(`${failures} tests failed.`))
} else {
resolve()
}
})
} catch (runError) {
console.error(runError)
reject(runError)
}
})
})
}

127
test/suites/utils.ts Normal file
View File

@ -0,0 +1,127 @@
import * as vscode from 'vscode'
import * as path from 'path'
import * as fs from 'fs'
import * as glob from 'glob'
import * as os from 'os'
import * as assert from 'assert'
import { Extension } from '../../src/main'
type RunTestOption = {
suiteName: string,
fixtureName: string,
testName: string,
timeout?: number,
only?: boolean,
win32only?: boolean
}
export function runTest(option: RunTestOption, cb: (fixture: string) => unknown) {
let fixture: string | undefined
if (vscode.workspace.workspaceFile) {
fixture = path.dirname(vscode.workspace.workspaceFile.fsPath)
} else {
fixture = vscode.workspace.workspaceFolders?.[0].uri.fsPath
}
if (fixture === undefined) {
return
}
if (path.basename(fixture) !== option.fixtureName) {
return
}
if (process.env['LATEXWORKSHOP_CISUITE'] && !process.env['LATEXWORKSHOP_CISUITE'].split(',').includes(option.suiteName)) {
return
}
if (option.win32only && os.platform() !== 'win32') {
return
}
const testFunction = option.only ? test.only : test
testFunction(`${option.suiteName}: ${option.testName}`, async () => {
try {
await cb(fixture || '.')
} catch (error) {
await log()
throw error
}
}).timeout(option.timeout || 30000)
}
export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function log() {
await vscode.commands.executeCommand('workbench.action.output.toggleOutput')
await sleep(500)
await vscode.commands.executeCommand('latex-workshop.log')
await sleep(500)
const logMessage = vscode.window.activeTextEditor?.document.getText()
console.log(logMessage)
await vscode.commands.executeCommand('latex-workshop.compilerlog')
await sleep(500)
const compilerLogMessage = vscode.window.activeTextEditor?.document.getText()
console.log(compilerLogMessage)
}
type WriteTestOption = {
fixture: string,
fileName: string
}
export function writeTest(option: WriteTestOption, ...contents: string[]) {
fs.mkdirSync(path.resolve(option.fixture, path.dirname(option.fileName)), {recursive: true})
fs.writeFileSync(path.resolve(option.fixture, option.fileName), contents.join('\n'))
}
type AssertBuildOption = {
fixture: string,
texFileName: string,
pdfFileName: string,
extension?: Extension,
build?: () => unknown,
edits?: (cb: vscode.TextEditorEdit) => unknown,
nobuild?: boolean
}
export async function assertBuild(option: AssertBuildOption) {
const texFilePath = vscode.Uri.file(path.join(option.fixture, option.texFileName))
const pdfFilePath = path.join(option.fixture, option.pdfFileName)
const doc = await vscode.workspace.openTextDocument(texFilePath)
const editor = await vscode.window.showTextDocument(doc)
await option.extension?.manager.findRoot()
await executeBuild(editor, doc, option)
const files = glob.sync('**/**.pdf', { cwd: option.fixture })
assert.strictEqual(files.map(file => path.resolve(option.fixture, file)).join(','), option.pdfFileName === '' ? option.pdfFileName : pdfFilePath)
}
async function executeBuild(editor: vscode.TextEditor, doc: vscode.TextDocument, option: AssertBuildOption) {
if (option.edits) {
await sleep(500)
await editor.edit(option.edits)
await sleep(500)
await doc.save()
if (option.nobuild) {
await sleep(3000)
} else {
await waitBuild(option.extension)
}
return
}
if (option.build) {
await option.build()
return
}
await vscode.commands.executeCommand('latex-workshop.build')
}
export async function waitBuild(extension?: Extension) {
return new Promise<void>((resolve, _) => {
const disposable = extension?.eventBus.on('buildfinished', () => {
resolve()
disposable?.dispose()
})
})
}