Skip to content

Commit

Permalink
wip: add first IPC PoC
Browse files Browse the repository at this point in the history
  • Loading branch information
noomorph committed Apr 18, 2022
1 parent 8569053 commit 9e10195
Show file tree
Hide file tree
Showing 18 changed files with 229 additions and 42 deletions.
22 changes: 22 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true

[{*.ats,*.cts,*.mts,*.ts}]
indent_size = 2
tab_width = 2

[{*.cjs,*.js}]
indent_size = 2
tab_width = 2

[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}]
indent_size = 2
12 changes: 8 additions & 4 deletions detox/runners/jest/environment.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// @ts-nocheck
const NodeEnvironment = require('jest-environment-node');

const DetoxError = require('../../src/errors/DetoxError');
const Timer = require('../../src/utils/Timer');
const DetoxWorkerContext = require('../../src/DetoxWorkerContext');
const { DetoxError } = require('../../src/errors');
const ipcClient = require('../../src/ipc/client');

const DetoxCoreListener = require('./listeners/DetoxCoreListener');
const DetoxInitErrorListener = require('./listeners/DetoxInitErrorListener');
const Timer = require('./utils/Timer');
const assertExistingContext = require('./utils/assertExistingContext');
const assertJestCircus26 = require('./utils/assertJestCircus26');
const wrapErrorWithNoopLifecycle = require('./utils/wrapErrorWithNoopLifecycle');
Expand Down Expand Up @@ -46,9 +48,11 @@ class DetoxCircusEnvironment extends NodeEnvironment {

async setup() {
await super.setup();
await ipcClient.init({});
const detoxConfig = await ipcClient.getDetoxConfig();

this.global.detox = require('../../src')
._setGlobal(this.global);
DetoxWorkerContext.global = this.global;
this.global.detox = new DetoxWorkerContext(detoxConfig);
}

async teardown() {
Expand Down
6 changes: 6 additions & 0 deletions detox/runners/jest/globalSetup.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
const DetoxGlobalContext = require('../../src/DetoxGlobalContext');
const configuration = require('../../src/configuration');
const ipcServer = require('../../src/ipc/server');

module.exports = async function detoxGlobalSetup(override) {
const config = await configuration.composeDetoxConfig({ override });

await ipcServer.start({
sessionId: `detox-${process.pid}`,
detoxConfig: config,
});

const globalContext = global['detox'] = new DetoxGlobalContext(config);
try {
await globalContext.setup();
Expand Down
5 changes: 0 additions & 5 deletions detox/runners/jest/globalTeardown.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
module.exports = async function detoxGlobalTeardown() {
const instance = global['detox'];

if (instance) {
await instance.cleanup();
}
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-nocheck
const DetoxRuntimeError = require('../errors/DetoxRuntimeError');
const { DetoxRuntimeError } = require('../../../src/errors');

const Deferred = require('./Deferred');
const Deferred = require('../../../src/utils/Deferred');

class Timer {
/**
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions detox/src/client/Client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Client', () => {

jest.mock('../utils/logger');
log = require('../utils/logger');
log.level.mockReturnValue('debug');
log._level.mockReturnValue('debug');

const AsyncWebSocket = jest.genMockFromModule('./AsyncWebSocket');
mockAws = new AsyncWebSocket();
Expand Down Expand Up @@ -503,7 +503,7 @@ describe('Client', () => {
['debug'],
['trace'],
])(`should throw "testFailed" error with view hierarchy (on --loglevel %s)`, async (loglevel) => {
log.level.mockReturnValue(loglevel);
log._level.mockReturnValue(loglevel);
mockAws.mockResponse('testFailed', { details: 'this is an error', viewHierarchy: 'mock-hierarchy' });
await expect(client.execute(anInvocation)).rejects.toThrowErrorMatchingSnapshot();
});
Expand All @@ -513,7 +513,7 @@ describe('Client', () => {
['warn'],
['info'],
])(`should throw "testFailed" error without view hierarchy but with a hint (on --loglevel %s)`, async (loglevel) => {
log.level.mockReturnValue(loglevel);
log._level.mockReturnValue(loglevel);
mockAws.mockResponse('testFailed', { details: 'this is an error', viewHierarchy: 'mock-hierarchy' });
const executionPromise = client.execute(anInvocation);
await expect(executionPromise).rejects.toThrowErrorMatchingSnapshot();
Expand Down
4 changes: 2 additions & 2 deletions detox/src/client/actions/actions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-nocheck
const { DetoxInternalError, DetoxRuntimeError } = require('../../errors');
const { getDetoxLevel } = require('../../utils/logger');
const logger = require('../../utils/logger');
const formatJSONStatus = require('../actions/formatters/SyncStatusFormatter');

class Action {
Expand Down Expand Up @@ -201,7 +201,7 @@ class Invoke extends Action {

if (response.params.viewHierarchy) {
/* istanbul ignore next */
if (/^(debug|trace)$/.test(getDetoxLevel())) {
if (/^(debug|trace)$/.test(logger.level)) {
debugInfo = 'View Hierarchy:\n' + response.params.viewHierarchy;
} else {
hint = 'To print view hierarchy on failed actions/matches, use log-level verbose or higher.';
Expand Down
4 changes: 4 additions & 0 deletions detox/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module.exports = {
// ...here the new life begins...
hook() {
// TODO: noop

},
};
66 changes: 66 additions & 0 deletions detox/src/ipc/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const ipc = require('node-ipc').default;

const Deferred = require('../utils/Deferred');

const state = {
open: false,
detoxConfig: new Deferred(),
};

module.exports = {
async init({
serverId = process.env.DETOX_IPC_SERVER_ID,
workerId = process.env.JEST_WORKER_ID,
}) {
return new Promise((resolve, reject) => {
ipc.config.id = `${serverId}-${process.env.JEST_WORKER_ID}`;
ipc.config.retry = 1000;
ipc.config.sync = true;
ipc.connectTo(serverId, function() {
const server = state.server = ipc.of[serverId];
server.on('error', reject);
server.on('connect', () => {
state.open = true;

server.emit('app.message', {
type: 'registerWorker',
workerId,
});

resolve();
});

server.on('disconnect', () => {
state.open = false;
});

server.on('app.message', ({ type, ...payload }) => {
switch (type) {
case 'registerWorkerDone': {
const { detoxConfig } = payload;
state.detoxConfig.resolve(detoxConfig);
break;
}
}
});
});
});
},

async getDetoxConfig() {
return state.detoxConfig.promise;
},

log(level, meta, ...args) {
if (state.open) {
state.server.emit('app.message', {
type: 'log',
level,
meta,
args,
});
} else {
console.error('Whoops...', level, meta, ...args);
}
},
};
46 changes: 46 additions & 0 deletions detox/src/ipc/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const ipc = require('node-ipc').default;

const log = require('../logger');

const state = {
workers: 0,
detoxConfig: null,
};

module.exports = {
async start({ sessionId, detoxConfig }) {
state.detoxConfig = detoxConfig;

debugger;
ipc.config.id = process.env.DETOX_IPC_SERVER_ID = `detox-${sessionId}`;
ipc.config.retry = 1500;
ipc.config.sync = true;

return new Promise((resolve, reject) => {
ipc.serve(function() {
resolve();

ipc.server.on('app.message', function(data, socket) {
const { type, ...payload } = data;
switch (type) {
case 'log': {
const { level, args } = payload;
return log[level](args);
}

case 'registerWorker': {
const { workerId } = payload;
state.workers = Math.max(state.workers, +workerId);
return ipc.server.emit(socket, 'app.message', {
type: 'registerWorkerDone',
detoxConfig: state.detoxConfig,
});
}
}
});
});

ipc.server.start();
});
},
};
3 changes: 2 additions & 1 deletion detox/src/logger/BunyanLogger.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class BunyanLogger {
constructor(config, bunyan) {
this._config = config;
this._bunyan = bunyan || null; // TODO
this._bunyan = bunyan || console; // TODO
this._bunyan.child = () => this._bunyan; // TODO: remove this
}

child(overrides) {
Expand Down
52 changes: 51 additions & 1 deletion detox/src/logger/IPCLogger.js
Original file line number Diff line number Diff line change
@@ -1 +1,51 @@
module.exports = require('./NullLogger');
const _ = require('lodash');
const ipcClient = require('../ipc/client');

class IPCLogger {
constructor(config) {
this._config = config;
this._config.level = 'info';
ipcClient.getDetoxConfig().then(config => {
if (config.cliConfig.loglevel) {
this._config.level = config.cliConfig.loglevel;
}
});

}

child(context) {
return new IPCLogger(_.merge({}, this._config, { context }));
}

error() {
return this._send('error', [...arguments]);
}

warn() {
return this._send('warn', [...arguments]);
}

info() {
return this._send('info', [...arguments]);
}

debug() {
return this._send('debug', [...arguments]);
}

trace() {
return this._send('trace', [...arguments]);
}

_send(level, args) {
const hasContext = _.isObject(arguments[0]);
const meta = _.defaults({}, this._config.context, hasContext ? arguments[0] : undefined);
ipcClient.log(level, meta, hasContext ? args.slice(1) : args);
}

get level() {
return this._config.level; // ?
}
}

module.exports = IPCLogger;
15 changes: 10 additions & 5 deletions detox/src/logger/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
function resolveLoggerClass() {
switch (process.env.DETOX_LOGGER_IMPL) {
case 'bunyan': return require('./BunyanLogger');
case 'ipc': return require('./IPCLogger');
default: return require('./NullLogger');
if (global.IS_RUNNING_DETOX_UNIT_TESTS) {
// TODO: return NullLogger maybe?
return require('./BunyanLogger');
}

if (process.env.JEST_WORKER_ID) {
return require('./IPCLogger');
} else {
return require('./BunyanLogger');
}
}

const Logger = resolveLoggerClass();
module.exports = new Logger();
module.exports = new Logger({});
10 changes: 4 additions & 6 deletions detox/src/utils/__mocks__/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ class FakeLogger {
this.opts = opts;
this.log = jest.fn();
this.reinitialize = jest.fn();
this.level = jest.fn();
this.getDetoxLevel = this.getDetoxLevel.bind(this);
this._level = jest.fn();
Object.defineProperty(this, 'level', {
get: () => this._level(),
});

for (const method of METHODS) {
this[method] = jest.fn().mockImplementation((...args) => {
Expand All @@ -22,10 +24,6 @@ class FakeLogger {
return this;
}

getDetoxLevel() {
return this.level();
}

clear() {
this.opts = {};
return this;
Expand Down
1 change: 1 addition & 0 deletions detox/src/utils/trace.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Trace {
constructor() {
this.events = [];
this._timestampProviderFn = Date.now; // TODO: fix me
}

init(timestampProviderFn = Date.now) {
Expand Down
7 changes: 1 addition & 6 deletions detox/test/e2e/global-setup.js
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
async function globalSetup() {
const detox = require('detox');
await detox.globalInit();
}

module.exports = globalSetup;
module.exports = require('detox/runners/jest/globalSetup')
8 changes: 1 addition & 7 deletions detox/test/e2e/global-teardown.js
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
const detox = require('detox');

async function globalTeardown() {
await detox.globalCleanup();
}

module.exports = globalTeardown;
module.exports = require('detox/runners/jest/globalTeardown')

0 comments on commit 9e10195

Please sign in to comment.