Skip to content

Commit

Permalink
Merge 4eb09cb into ded6a14
Browse files Browse the repository at this point in the history
  • Loading branch information
mojadev committed Aug 15, 2018
2 parents ded6a14 + 4eb09cb commit a719259
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 107 deletions.
193 changes: 99 additions & 94 deletions src/JestProcessManagement/JestProcess.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,99 @@
import { platform } from 'os'
import { Runner, ProjectWorkspace } from 'jest-editor-support'
import { WatchMode } from '../Jest'

export class JestProcess {
static readonly keepAliveLimit = 5
private runner: Runner
private projectWorkspace: ProjectWorkspace
private onExitCallback: Function
private jestSupportEvents: Map<string, (...args: any[]) => void>
private resolve: Function
private keepAliveCounter: number
public keepAlive: boolean
public stopRequested: boolean
watchMode: WatchMode

private startRunner() {
this.stopRequested = false
let exited = false

const options = {
noColor: true,
shell: platform() === 'win32',
}
this.runner = new Runner(this.projectWorkspace, options)

this.restoreJestEvents()

this.runner.start(this.watchMode !== WatchMode.None, this.watchMode === WatchMode.WatchAll)

this.runner.on('debuggerProcessExit', () => {
if (!exited) {
exited = true
if (--this.keepAliveCounter > 0) {
this.runner.removeAllListeners()
this.startRunner()
} else if (this.onExitCallback) {
this.onExitCallback(this)
if (this.stopRequested) {
this.resolve()
}
}
}
})
}

private restoreJestEvents() {
for (const [event, callback] of this.jestSupportEvents.entries()) {
this.runner.on(event, callback)
}
}

constructor({
projectWorkspace,
watchMode = WatchMode.None,
keepAlive = false,
}: {
projectWorkspace: ProjectWorkspace
watchMode?: WatchMode
keepAlive?: boolean
}) {
this.keepAlive = keepAlive
this.watchMode = watchMode
this.projectWorkspace = projectWorkspace
this.keepAliveCounter = keepAlive ? JestProcess.keepAliveLimit : 1
this.jestSupportEvents = new Map()

this.startRunner()
}

public onExit(callback: Function) {
this.onExitCallback = callback
}

public onJestEditorSupportEvent(event, callback) {
this.jestSupportEvents.set(event, callback)
this.runner.on(event, callback)
return this
}

public stop() {
this.stopRequested = true
this.keepAliveCounter = 1
this.jestSupportEvents.clear()
this.runner.closeProcess()
return new Promise(resolve => {
this.resolve = resolve
})
}

public runJestWithUpdateForSnapshots(callback) {
this.runner.runJestWithUpdateForSnapshots(callback)
}
}
import { platform } from 'os'
import { Runner, ProjectWorkspace, Options } from 'jest-editor-support'
import { WatchMode } from '../Jest'
import { createProcessInWSL } from './WslProcess'

export class JestProcess {
static readonly keepAliveLimit = 5
private runner: Runner
private projectWorkspace: ProjectWorkspace
private onExitCallback: Function
private jestSupportEvents: Map<string, (...args: any[]) => void>
private resolve: Function
private keepAliveCounter: number
public keepAlive: boolean
public stopRequested: boolean
watchMode: WatchMode

private startRunner() {
this.stopRequested = false
let exited = false

const options: Options = {
noColor: true,
shell: platform() === 'win32',
}
// If Windows Subsystem for Linux is used, spawn in wsl
if (this.projectWorkspace.pathToJest && this.projectWorkspace.pathToJest.startsWith('wsl')) {
options.createProcess = createProcessInWSL
}
this.runner = new Runner(this.projectWorkspace, options)

this.restoreJestEvents()

this.runner.start(this.watchMode !== WatchMode.None, this.watchMode === WatchMode.WatchAll)

this.runner.on('debuggerProcessExit', () => {
if (!exited) {
exited = true
if (--this.keepAliveCounter > 0) {
this.runner.removeAllListeners()
this.startRunner()
} else if (this.onExitCallback) {
this.onExitCallback(this)
if (this.stopRequested) {
this.resolve()
}
}
}
})
}

private restoreJestEvents() {
for (const [event, callback] of this.jestSupportEvents.entries()) {
this.runner.on(event, callback)
}
}

constructor({
projectWorkspace,
watchMode = WatchMode.None,
keepAlive = false,
}: {
projectWorkspace: ProjectWorkspace
watchMode?: WatchMode
keepAlive?: boolean
}) {
this.keepAlive = keepAlive
this.watchMode = watchMode
this.projectWorkspace = projectWorkspace
this.keepAliveCounter = keepAlive ? JestProcess.keepAliveLimit : 1
this.jestSupportEvents = new Map()

this.startRunner()
}

public onExit(callback: Function) {
this.onExitCallback = callback
}

public onJestEditorSupportEvent(event, callback) {
this.jestSupportEvents.set(event, callback)
this.runner.on(event, callback)
return this
}

public stop() {
this.stopRequested = true
this.keepAliveCounter = 1
this.jestSupportEvents.clear()
this.runner.closeProcess()
return new Promise(resolve => {
this.resolve = resolve
})
}

public runJestWithUpdateForSnapshots(callback) {
this.runner.runJestWithUpdateForSnapshots(callback)
}
}
70 changes: 70 additions & 0 deletions src/JestProcessManagement/WslProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict'
import * as child_process from 'child_process'

import { ProjectWorkspace, Options } from '../../node_modules/jest-editor-support'

/**
* Spawns and returns a Jest process with specific args
*
* @param {string[]} args
* @returns {ChildProcess}
*/
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
*/
import { win32, posix } from 'path'

export const createProcessInWSL = (workspace: ProjectWorkspace, args, options: Options = { shell: true }) => {
// A command could look like `npm run test`, which we cannot use as a command
// as they can only be the first command, so take out the command, and add
// any other bits into the args
const runtimeExecutable = workspace.pathToJest
const parameters = runtimeExecutable.split(' ')
const command = parameters[0]
const initialArgs = parameters.slice(1)
let runtimeArgs = [].concat(initialArgs, args)

// If a path to configuration file was defined, push it to runtimeArgs
if (workspace.pathToConfig) {
runtimeArgs.push('--config')
runtimeArgs.push(workspace.pathToConfig)
}

runtimeArgs = runtimeArgs.map(windowsPathToWSL)

// To use our own commands in create-react, we need to tell the command that
// we're in a CI environment, or it will always append --watch
const env = process.env
env['CI'] = 'true'

const spawnOptions = {
cwd: workspace.rootPath,
env: env,
shell: options.shell,
}

if (workspace.debug) {
console.log(`spawning process with command=${command}, args=${runtimeArgs.toString()}`)
}

return child_process.spawn(command, runtimeArgs, spawnOptions)
}

function windowsPathToWSL(maybePath: string): string {
const isPath = win32.parse(maybePath).dir
if (!isPath) {
return maybePath
}
const windowsPath = maybePath[0].toLocaleLowerCase() + maybePath.substr(1)
const path = windowsPath
.split(win32.sep)
.join(posix.sep)
.replace(/^(\w):/, '/mnt/$1')

return path
}
21 changes: 18 additions & 3 deletions src/TestResults/TestResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function coverageMapWithLowerCaseWindowsDriveLetters(data: JestTotalResul
}

function fileCoverageWithLowerCaseWindowsDriveLetter(fileCoverage: FileCoverage) {
const newFilePath = withLowerCaseWindowsDriveLetter(fileCoverage.path)
const newFilePath = withNormalizedWindowsPath(fileCoverage.path)
if (newFilePath) {
return {
...fileCoverage,
Expand All @@ -82,7 +82,7 @@ export function testResultsWithLowerCaseWindowsDriveLetters(
}

function testResultWithLowerCaseWindowsDriveLetter(testResult: JestFileResults): JestFileResults {
const newFilePath = withLowerCaseWindowsDriveLetter(testResult.name)
const newFilePath = withNormalizedWindowsPath(testResult.name)
if (newFilePath) {
return {
...testResult,
Expand All @@ -93,9 +93,24 @@ function testResultWithLowerCaseWindowsDriveLetter(testResult: JestFileResults):
return testResult
}

export function withLowerCaseWindowsDriveLetter(filePath: string): string | undefined {
export function withNormalizedWindowsPath(filePath: string, platform = process.platform): string | undefined {
if (platform === 'win32') {
filePath = convertWSLPathToWindows(filePath)
}

const match = filePath.match(/^([A-Z]:\\)(.*)$/)
if (match) {
return `${match[1].toLowerCase()}${match[2]}`
}
return filePath
}

function convertWSLPathToWindows(filePath: string) {
const isLinuxPath = filePath.match(/^\/mnt\/(\w)\/(.*)$/)
if (isLinuxPath) {
const normalizedPath = isLinuxPath[2].split(path.posix.sep).join(path.win32.sep)
const driveLetter = `${isLinuxPath[1]}:\\`
filePath = `${driveLetter}${normalizedPath}`
}
return filePath
}
33 changes: 29 additions & 4 deletions tests/JestProcessManagement/JestProcess.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
jest.unmock('../../src/JestProcessManagement/JestProcess')

jest.unmock('path')
import { Runner, ProjectWorkspace } from 'jest-editor-support'
import { JestProcess } from '../../src/JestProcessManagement/JestProcess'
import { EventEmitter } from 'events'
import { WatchMode } from '../../src/Jest'

describe('JestProcess', () => {
let projectWorkspaceMock
let jestProcess
let projectWorkspaceMock: ProjectWorkspace
let jestProcess: JestProcess
const runnerMock = (Runner as any) as jest.Mock<any>
let runnerMockImplementation
let eventEmitter
Expand All @@ -23,7 +23,10 @@ describe('JestProcess', () => {

describe('when creating', () => {
beforeEach(() => {
runnerMock.mockImplementation(() => runnerMockImplementation)
runnerMock.mockImplementation((_, options) => {
runnerMockImplementation.options = options
return runnerMockImplementation
})
})

it('accepts a project workspace argument', () => {
Expand Down Expand Up @@ -88,6 +91,28 @@ describe('JestProcess', () => {
})
expect(runnerMockImplementation.start.mock.calls[0]).toEqual([true, true])
})

it('should define a different createProcess function when running wsl', () => {
projectWorkspaceMock.pathToJest = 'wsl npm run test'

jestProcess = new JestProcess({
projectWorkspace: projectWorkspaceMock,
keepAlive: true,
})

expect(runnerMockImplementation.options.createProcess).toBeDefined()
})

it('should not define createProcess function when not running wsl', () => {
projectWorkspaceMock.pathToJest = 'npm run test'

jestProcess = new JestProcess({
projectWorkspace: projectWorkspaceMock,
keepAlive: true,
})

expect(runnerMockImplementation.options.createProcess).not.toBeDefined()
})
})

describe('when jest-editor-support runner exits', () => {
Expand Down

0 comments on commit a719259

Please sign in to comment.