This repository has been archived by the owner on Dec 13, 2018. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: sorry for a huge diff. it was a spontaneous hack :( this implements a jest runner that can spawn atom processes and run tests in them. Video: https://pxl.cl/d2bF to run tests you need to run jest and pass the config for atom tests: - `cd modules/jest-atom-runner/` - `yarn watch` - `jest --config ~/nuclide/jest/atom.jest.config.js` This will pick up any tests that are located under any of the `__atom_tests__` directories within `nuclide/` project. This is not run on any of the CIs/Sandcastle yet. next step would be: 1. dogfooding it and making sure it works reliably 2. adding a sandcastle step to our macos build 3. polishing (the IPC part is gnarly :( ) Reviewed By: hansonw Differential Revision: D7771785 fbshipit-source-id: 68dbce1d95d0f9e90a68718b43b4523489d3973c
- Loading branch information
1 parent
e323c69
commit da233f8
Showing
19 changed files
with
1,422 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the license found in the LICENSE file in | ||
* the root directory of this source tree. | ||
* | ||
* @flow | ||
* @format | ||
*/ | ||
|
||
test('atom', () => { | ||
expect(1).toBe(1); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the license found in the LICENSE file in | ||
* the root directory of this source tree. | ||
* | ||
* @flow | ||
* @format | ||
*/ | ||
|
||
test('atom', () => { | ||
expect(2).toBe(2); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the license found in the LICENSE file in | ||
* the root directory of this source tree. | ||
* | ||
* @flow | ||
* @format | ||
*/ | ||
|
||
// eslint-disable-next-line rulesdir/no-commonjs | ||
module.exports = {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the license found in the LICENSE file in | ||
* the root directory of this source tree. | ||
* | ||
* @noflow | ||
*/ | ||
'use strict'; | ||
|
||
/* eslint | ||
comma-dangle: [1, always-multiline], | ||
prefer-object-spread/prefer-object-spread: 0, | ||
rulesdir/no-commonjs: 0, | ||
*/ | ||
|
||
const path = require('path'); | ||
|
||
module.exports = { | ||
rootDir: path.resolve(__dirname, '..'), | ||
testMatch: ['**/__atom_tests__/**/*.js?(x)'], | ||
transform: { | ||
'\\.js$': '<rootDir>/modules/nuclide-jest/jestTransformer.js', | ||
}, | ||
setupFiles: [], | ||
testFailureExitCode: 0, | ||
forceExit: true, | ||
testPathIgnorePatterns: ['/node_modules/'], | ||
runner: path.resolve(__dirname, '../modules/jest-atom-runner/build/index.js'), | ||
moduleNameMapper: { | ||
oniguruma: path.resolve(__dirname, './__mocks__/emptyObject.js'), | ||
}, | ||
testEnvironment: '<rootDir>/modules/jest-atom-runner/build/environment.js', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"presets": ["flow"], | ||
"plugins": [ | ||
["transform-es2015-modules-commonjs", {"allowTopLevelThis": true}] | ||
], | ||
"retainLines": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"rulesdir/modules-dependencies": [1, {"allowDevDependencies": true}] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "jest-atom-runner", | ||
"version": "0.7.1-dev", | ||
"description": "Jest runner that spawns atom/electron workers instead of node", | ||
"author": "dabramov", | ||
"license": "BSD-3-Clause", | ||
"homepage": "https://nuclide.io/", | ||
"repository": "https://github.com/facebook/nuclide/tree/master/modules/jest-atom-runner", | ||
"private": true, | ||
"devDependencies": { | ||
"async-to-generator": "1.1.0", | ||
"babel-cli": "6.26.0", | ||
"babel-plugin-transform-es2015-modules-commonjs": "6.26.0", | ||
"babel-preset-flow": "6.23.0", | ||
"jest-haste-map": "22.4.3", | ||
"jest-message-util": "22.4.3", | ||
"jest-runner": "22.4.3", | ||
"jest-runtime": "22.4.3", | ||
"mkdirp": "0.5.1", | ||
"node-ipc": "9.1.1" | ||
}, | ||
"scripts": { | ||
"watch": "babel src --out-dir build --watch" | ||
}, | ||
"dependencies": { | ||
"jest-environment-jsdom": "22.4.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
/** | ||
* Copyright (c) 2017-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @flow | ||
* @format | ||
*/ | ||
|
||
/* This is a Jest worker. An abstraction class that knows how to start up | ||
* an Atom process and communicate with it */ | ||
|
||
/* eslint-disable rulesdir/prefer-nuclide-uri */ | ||
|
||
/* eslint-disable-next-line rulesdir/consistent-import-name */ | ||
import type {IPCServer, Socket} from './ipc-server'; | ||
import type {ServerID, WorkerID, MessageType} from './utils'; | ||
import type {Test, GlobalConfig, TestResult} from './types'; | ||
|
||
// eslint-disable-next-line rulesdir/consistent-import-name | ||
import {spawn} from 'child_process'; | ||
import mkdirp from 'mkdirp'; | ||
import path from 'path'; | ||
import fs from 'fs'; | ||
import os from 'os'; | ||
import { | ||
makeUniqWorkerId, | ||
mergeIPCIDs, | ||
parseMessage, | ||
makeMessage, | ||
MESSAGE_TYPES, | ||
parseJSON, | ||
} from './utils'; | ||
|
||
const TMP_DIR = path.resolve(os.tmpdir(), 'jest-atom-runner'); | ||
|
||
// Atom resolves to its testing framework based on what's specified | ||
// under the "atomTestRunner" key in the package.json in the parent directory | ||
// of the first passed path. | ||
// so if we run `atom -t /some_dir/__tests__/1-test.js` | ||
// it'll look up `/some_dir/package.json` and then require whatever file is | ||
// specified in "atomTestRunner" of this packages.json. | ||
// To work around (or rather make atom execute arbitrary code) we | ||
// will create a dummy `/tmp/packages.json` with `atomTestRunner` pointing | ||
// to the file that we want to inject into atom's runtime. | ||
const createDummyPackageJson = () => { | ||
mkdirp.sync(path.resolve(TMP_DIR)); | ||
const packageJsonPath = path.resolve(TMP_DIR, 'package.json'); | ||
fs.writeFileSync( | ||
packageJsonPath, | ||
JSON.stringify({atomTestRunner: require.resolve('./atomTestRunner')}), | ||
); | ||
}; | ||
|
||
type OnMessageCallback = (MessageType, data?: string) => void; | ||
type TestRunResolver = {resolve: TestResult => void, reject: Error => void}; | ||
|
||
class AtomTestWorker { | ||
_childProcess: child_process$ChildProcess; | ||
_ipcServer: IPCServer; | ||
_serverID: ServerID; | ||
_workerID: WorkerID; | ||
_alive: boolean; // whether the worker is up and running | ||
_socket: ?Socket; | ||
_onMessageCallbacks: Array<OnMessageCallback>; | ||
_globalConfig: GlobalConfig; | ||
_runningTests: Map<string, TestRunResolver>; | ||
|
||
constructor({ | ||
ipcServer, | ||
serverID, | ||
globalConfig, | ||
}: { | ||
ipcServer: IPCServer, | ||
serverID: ServerID, | ||
globalConfig: GlobalConfig, | ||
}) { | ||
this._ipcServer = ipcServer; | ||
this._serverID = serverID; | ||
this._alive = false; | ||
this._onMessageCallbacks = []; | ||
this._workerID = makeUniqWorkerId(); | ||
this._globalConfig = globalConfig; | ||
this._runningTests = new Map(); | ||
} | ||
|
||
async start() { | ||
const {_serverID: serverID, _ipcServer: ipcServer} = this; | ||
return new Promise(resolve => { | ||
createDummyPackageJson(); | ||
const workerID = this._workerID; | ||
const atomPathArg = path.resolve( | ||
TMP_DIR, | ||
mergeIPCIDs({serverID, workerID}), | ||
); | ||
|
||
let firstMessage = false; | ||
ipcServer.on(workerID, (message, socket) => { | ||
const {messageType, data} = parseMessage(message); | ||
if (!firstMessage) { | ||
firstMessage = true; | ||
this._alive = true; | ||
this._socket = socket; | ||
resolve(); | ||
} else { | ||
this._onMessage((messageType: MessageType), data); | ||
} | ||
}); | ||
|
||
this._childProcess = spawn('atom', ['-t', atomPathArg], { | ||
stdio: ['inherit', 'inherit', 'inherit'], | ||
}); | ||
}); | ||
} | ||
|
||
async stop() { | ||
this.send(makeMessage({messageType: MESSAGE_TYPES.SHUT_DOWN})); | ||
this._childProcess.kill('SIGTERM'); | ||
} | ||
|
||
send(message: string) { | ||
if (!this._socket || !this._alive || !this._workerID) { | ||
throw new Error("Can't interact with the worker before it comes alive"); | ||
} | ||
this._ipcServer.emit(this._socket, this._workerID, message); | ||
} | ||
|
||
_onMessage(messageType: MessageType, data: string) { | ||
switch (messageType) { | ||
case MESSAGE_TYPES.TEST_RESULT: { | ||
const testResult: TestResult = parseJSON(data); | ||
const {testFilePath} = testResult; | ||
const runningTest = this._runningTests.get(testFilePath); | ||
if (!runningTest) { | ||
throw new Error(` | ||
Can't find any references to the test result that returned from the worker. | ||
returned test path: ${testFilePath} | ||
list of tests that we know has been running in the worker: | ||
${Array.from(this._runningTests) | ||
.map(([key, _]) => key) | ||
.join(', ')} | ||
`); | ||
} | ||
|
||
runningTest.resolve(testResult); | ||
this._runningTests.delete(testFilePath); | ||
} | ||
} | ||
} | ||
|
||
runTest(test: Test): Promise<TestResult> { | ||
if (this._runningTests.has(test.path)) { | ||
throw new Error( | ||
"Can't run the same times in the same worker at the same time", | ||
); | ||
} | ||
return new Promise((resolve, reject) => { | ||
// Ideally we don't want to pass all thing info with every test | ||
// because it never changes. We should try to initialize it | ||
// when the worker starts and keep it there for the whole run | ||
// (if it's a single run and not a watch mode of course, in that case | ||
// it'll be able to change) | ||
const rawModuleMap = test.context.moduleMap.getRawModuleMap(); | ||
const config = test.context.config; | ||
const globalConfig = this._globalConfig; | ||
|
||
this.send( | ||
makeMessage({ | ||
messageType: MESSAGE_TYPES.RUN_TEST, | ||
data: JSON.stringify({ | ||
rawModuleMap, | ||
config, | ||
globalConfig, | ||
path: test.path, | ||
}), | ||
}), | ||
); | ||
|
||
this._runningTests.set(test.path, {resolve, reject}); | ||
}); | ||
} | ||
|
||
isBusy() { | ||
return this._runningTests.size > 0; | ||
} | ||
} | ||
|
||
export default AtomTestWorker; |
Oops, something went wrong.