Skip to content

Feature/refactor test runner #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
setup Test Runner
  • Loading branch information
ShMcK committed Nov 13, 2019
commit a92f8667c5e6b9c9fdd60111ef517d98627f79e3
10 changes: 10 additions & 0 deletions src/actions/runTest/channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as vscode from 'vscode'

let channel: vscode.OutputChannel

export const getOutputChannel = (name: string): vscode.OutputChannel => {
if (!channel) {
channel = vscode.window.createOutputChannel(name)
}
return channel
}
153 changes: 0 additions & 153 deletions src/actions/runTest/index.ts
Original file line number Diff line number Diff line change
@@ -1,153 +0,0 @@
import * as vscode from 'vscode'
import node from '../../services/node'

// TODO: use tap parser to make it easier to support other test runners

// ensure only latest run_test action is taken
let currentId = 0

// quick solution to prevent processing multiple results
// NOTE: may be possible to kill child process early
const shouldExitEarly = (processId: number): boolean => {
return currentId !== processId
}

let channel: vscode.OutputChannel

const getOutputChannel = (name: string): vscode.OutputChannel => {
if (!channel) {
channel = vscode.window.createOutputChannel(name)
}
return channel
}

interface Callbacks {
onSuccess(): void
onFail(): void
onRun(): void
onError(): void
}

interface TestRunnerConfig {
command: string
parser(output: string): boolean
}

export const createTestRunner = (config: TestRunnerConfig, callbacks: Callbacks) => {

const outputChannelName = 'TEST_OUTPUT'

return {
run() {
console.log('------------------- run test ------------------')
const processId = ++currentId
callbacks.onRun()

try {
const {stdout} = await node.exec(config.command)
}
}
}
}

async function runTest({onSuccess, onFail, onRun, onError}: Callbacks): Promise<void> {




// TODO: verify test runner for args
// jest CLI docs https://jestjs.io/docs/en/cli
// const testArgs = [
// '--json',
// '--onlyChanged',
// '--env=node',
// '--maxConcurrency=4',
// '--maxWorkers=4'
// ]

const commandLine = `npm test -- ${testArgs.join(' ')}`

try {
// capture position early on test start
// in case position changes
const {stdout} = await node.exec(commandLine)
if (shouldExitEarly(processId)) {
// exit early
return
}

if (stdout) {
const lines = stdout.split(/\r{0,1}\n/)
for (const line of lines) {
if (line.length > 0) {
const regExp = /^{\"numFailedTestSuites/
const matches = regExp.exec(line)
if (matches && matches.length) {
const result = JSON.parse(line)

if (result.success) {
if (shouldExitEarly(processId)) {
// exit early
return
}
console.log('success!')
onSuccess()
} else {
console.log('NOT SUCCESS?')
}
}
}
}
}
} catch (err) {
if (shouldExitEarly(processId)) {
// exit early
return
}
// error contains output & error message
// output can be parsed as json
const {stdout, stderr} = err
console.log('TEST FAILED', stdout)

if (!stdout) {
console.error('SOMETHING WENT WRONG WITH A PASSING TEST')
onError()
return
}
// test runner failed
channel = getOutputChannel(outputChannelName)

if (stdout) {
const lines = stdout.split(/\r{0,1}\n/)

for (const line of lines) {
if (line.length > 0) {
const dataRegExp = /^{\"numFailedTestSuites"/
const matches = dataRegExp.exec(line)

if (matches && matches.length) {
const result = JSON.parse(line)
const firstError = result.testResults.find((t: any) => t.status === 'failed')

if (firstError) {
if (shouldExitEarly(processId)) {
// exit early
return
}
onFail()
} else {
console.error('NOTE: PARSER DID NOT WORK FOR ', line)
}
}
}
}
}

if (stderr) {
channel.show(false)
channel.appendLine(stderr)
}
}
}

export default runTest
69 changes: 69 additions & 0 deletions src/actions/runTest/testRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import node from '../../services/node'
import {getOutputChannel} from './channel'
import {setLatestProcess, isLatestProcess} from './throttle'

// TODO: use tap parser to make it easier to support other test runners
// TODO: how to load test runner parser
// TODO: where to instantiate test runner


interface Callbacks {
onSuccess(): void
onFail(): void
onRun(): void
onError(): void
}

interface TestRunnerConfig {
command: string
parser(output: string): Error | null
}

export const createTestRunner = (config: TestRunnerConfig, callbacks: Callbacks) => {

const outputChannelName = 'TEST_OUTPUT'

return async () => {
console.log('------------------- run test ------------------')

// track processId to prevent multiple
const processId = setLatestProcess()
if (!isLatestProcess(processId)) {return }

// flag as running
callbacks.onRun()

let result: {stdout: string | undefined, stderr: string | undefined}
try {
result = await node.exec(config.command)
} catch (err) {
result = err
}
const {stdout, stderr} = result

// simple way to throttle requests
if (!stdout || !isLatestProcess(processId)) {return }

if (stderr) {
callbacks.onError()

// open terminal with error string
const channel = getOutputChannel(outputChannelName)
channel.show(false)
channel.appendLine(stderr)
return
}

// pass or fail?
const testsFailed = config.parser(stdout)
if (testsFailed === null) {
callbacks.onSuccess()
} else {
// open terminal with failed test string
const channel = getOutputChannel(outputChannelName)
channel.show(false)
channel.appendLine(testsFailed.message)
callbacks.onFail()
}
}
}
8 changes: 8 additions & 0 deletions src/actions/runTest/throttle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ensure only latest run_test action is taken
let currentId = 0

export const setLatestProcess = () => currentId++

// quick solution to prevent processing multiple results
// NOTE: may be possible to kill child process early
export const isLatestProcess = (processId: number): boolean => currentId === processId