Skip to content
This repository has been archived by the owner on Jul 28, 2023. It is now read-only.

Commit

Permalink
test: add integration tests (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkozy committed Jul 25, 2018
1 parent f73d18f commit 4d6c2b1
Show file tree
Hide file tree
Showing 27 changed files with 1,574 additions and 279 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
@@ -1 +1,2 @@
test/test.js
test/test.js
utils/testrunner/
7 changes: 6 additions & 1 deletion front_end/host/InspectorFrontendHostOverrides.js
Expand Up @@ -12,7 +12,12 @@
InspectorFrontendHost.stopIndexing = stopIndexing;
InspectorFrontendHost.searchInPath = searchInPath;
InspectorFrontendHost.isolatedFileSystem = name => new self.FileSystem(name);
InspectorFrontendHost.getPreferences = f => getPreferences().then(p => f(p));
InspectorFrontendHost.getPreferences = f => {
const threads = runtime._extensions.find(e => e._descriptor.className === 'Sources.ThreadsSidebarPane');
threads._descriptor.className = 'UI.Widget';
threads._descriptor.title = 'Node processes';
getPreferences().then(p => f(p));
};
InspectorFrontendHost.setPreference = setPreference;
InspectorFrontendHost.removePreference = removePreference;
InspectorFrontendHost.clearPreferences = clearPreferences;
Expand Down
28 changes: 22 additions & 6 deletions front_end/ndb/NdbMain.js
Expand Up @@ -15,13 +15,13 @@ Ndb.NdbMain = class extends Common.Object {
InspectorFrontendAPI.setUseSoftMenu(true);
document.title = 'ndb';
Ndb.NodeProcessManager.instance().then(instance => {
instance.run(NdbProcessInfo.execPath, [NdbProcessInfo.repl]);
instance.debug(NdbProcessInfo.execPath, [NdbProcessInfo.repl]);
if (!Common.moduleSetting('autoStartMain').get())
return;
const main = Ndb.mainConfiguration();
if (main) {
const [execPath, ...args] = main.commandToRun.split(' ');
instance.run(execPath, args);
instance.debug(execPath, args);
}
});
if (Common.moduleSetting('blackboxAnythingOutsideCwd').get()) {
Expand All @@ -35,7 +35,7 @@ Ndb.NdbMain = class extends Common.Object {
regexPatterns.push({pattern: `node_debug_demon[\\/]preload\\.js`});
Common.moduleSetting('skipStackFramesPattern').setAsArray(regexPatterns);
}

this._startRepl();
registerFileSystem('cwd', NdbProcessInfo.cwd).then(_ => {
InspectorFrontendAPI.fileSystemAdded(undefined, {
fileSystemName: 'cwd',
Expand All @@ -45,6 +45,12 @@ Ndb.NdbMain = class extends Common.Object {
});
});
}

async _startRepl() {
const processManager = await Ndb.NodeProcessManager.instance();
processManager.debug(NdbProcessInfo.execPath, [NdbProcessInfo.repl])
.then(this._startRepl.bind(this));
}
};

Ndb.mainConfiguration = () => {
Expand Down Expand Up @@ -90,7 +96,7 @@ Ndb.ContextMenuProvider = class {
contextMenu.debugSection().appendItem(ls`Run this script`, async() => {
const platformPath = Common.ParsedURL.urlToPlatformPath(url, Host.isWin());
const processManager = await Ndb.NodeProcessManager.instance();
processManager.run(NdbProcessInfo.execPath, [platformPath]);
processManager.debug(NdbProcessInfo.execPath, [platformPath]);
});
}
};
Expand Down Expand Up @@ -244,7 +250,8 @@ Ndb.NodeProcessManager = class extends Common.Object {

instance.setTarget(target);
this.dispatchEventToListeners(Ndb.NodeProcessManager.Events.Attached, instance);
if (instance.isRepl()) {
if (instance.isRepl() && !self._replMessageShown) {
self._replMessageShown = true;
const message =
new Common.Console.Message('\u001b[97mWelcome to the ndb \u001b\[92mR\u001b\[33mE\u001b\[31mP\u001b\[39mL\u001b[97m!', Common.Console.MessageLevel.Info, 1, false);
Common.console._messages.push(message);
Expand Down Expand Up @@ -300,10 +307,19 @@ Ndb.NodeProcessManager = class extends Common.Object {
}
}

debug(execPath, args) {
return this._nddService.call('debug', {
execPath, args, options: {
waitAtStart: true,
cwd: NdbProcessInfo.cwd
}
});
}

run(execPath, args) {
return this._nddService.call('run', {
execPath, args, options: {
waitAtStart: true
cwd: NdbProcessInfo.cwd
}
});
}
Expand Down
2 changes: 2 additions & 0 deletions front_end/ndb_ui/NodeProcesses.js
Expand Up @@ -29,13 +29,15 @@ Ndb.NodeProcesses = class extends UI.VBox {
this._pauseAtStartCheckbox = new UI.ToolbarSettingCheckbox(
Common.moduleSetting('pauseAtStart'), Common.UIString('Pause at start'),
Common.UIString('Pause at start'));
this._pauseAtStartCheckbox.element.id = 'pause-at-start-checkbox';
toolbar.appendToolbarItem(this._pauseAtStartCheckbox);
this._waitAtEndCheckbox = new UI.ToolbarSettingCheckbox(
Common.moduleSetting('waitAtEnd'), Common.UIString('Wait for manual disconnect'),
Common.UIString('Wait at end'));
toolbar.appendToolbarItem(this._waitAtEndCheckbox);

this._emptyElement = this.contentElement.createChild('div', 'gray-info-message');
this._emptyElement.id = 'no-running-nodes-msg';
this._emptyElement.textContent = Common.UIString('No running nodes');

this._treeOutline = new UI.TreeOutlineInShadow();
Expand Down
32 changes: 19 additions & 13 deletions front_end/ndb_ui/RunConfiguration.js
Expand Up @@ -15,18 +15,24 @@ Ndb.RunConfiguration = class extends UI.VBox {
}

async update() {
const service = await Ndb.serviceManager.create('npm_service');
const scripts = await service.call('run', {});
const configurations = [];
const main = Ndb.mainConfiguration();
if (main)
configurations.push(main);
this._items.replaceAll(configurations.concat(Object.keys(scripts).map(name => ({
name,
command: scripts[name],
execPath: NdbProcessInfo.npmExecPath,
args: ['run', name]
}))));
const manager = await Ndb.NodeProcessManager.instance();
const result = await manager.run(NdbProcessInfo.npmExecPath, ['--json', 'run']);
if (result.code !== 0 || result.stderr)
return;
try {
const scripts = JSON.parse(result.stdout);
const configurations = [];
const main = Ndb.mainConfiguration();
if (main)
configurations.push(main);
this._items.replaceAll(configurations.concat(Object.keys(scripts).map(name => ({
name,
command: scripts[name],
execPath: NdbProcessInfo.npmExecPath,
args: ['run', name]
}))));
} catch (e) {
}
}

/**
Expand Down Expand Up @@ -56,7 +62,7 @@ Ndb.RunConfiguration = class extends UI.VBox {

async _runConfig(execPath, args) {
const manager = await Ndb.NodeProcessManager.instance();
await manager.run(execPath, args);
await manager.debug(execPath, args);
}

/**
Expand Down
16 changes: 15 additions & 1 deletion front_end/ndb_ui/Terminal.js
Expand Up @@ -12,6 +12,9 @@ Ndb.Terminal = class extends UI.VBox {
this._term = null;
this._service = null;

this._buffer = '';
this._ready = false;

this._terminalShownCallback = null;
this._terminalPromise = new Promise(resolve => this._terminalShownCallback = resolve);
this._init();
Expand All @@ -21,7 +24,12 @@ Ndb.Terminal = class extends UI.VBox {
this._service = await Ndb.serviceManager.create('terminal');
await this._terminalPromise;
this._terminal.on('resize', size => this._service.call('resize', {cols: size.cols, rows: size.rows}));
this._terminal.on('data', data => this._service.call('write', {data: data}));
this._terminal.on('data', data => {
if (this._ready)
this._service.call('write', {data: data});
else
this._buffer += data;
});
this._service.addEventListener(Ndb.Service.Events.Notification, this._onNotification.bind(this));
this._initService();
}
Expand All @@ -34,12 +42,18 @@ Ndb.Terminal = class extends UI.VBox {
}

async _initService() {
this._ready = false;
const nddStore = await (await Ndb.NodeProcessManager.instance()).nddStore();
const {error} = await this._service.call('init', {
cols: this._terminal.cols,
rows: this._terminal.rows,
nddStore: nddStore
});
this._ready = true;
if (this._buffer.length) {
this._service.call('write', {data: this._buffer});
this._buffer = '';
}
if (error)
this._showInitError(error);
}
Expand Down
144 changes: 144 additions & 0 deletions lib/launcher.js
@@ -0,0 +1,144 @@
/**
* @license Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/

const fs = require('fs');
const os = require('os');
const path = require('path');
const puppeteer = require('puppeteer');
const {Writable} = require('stream');
const removeFolder = require('rimraf');
const {URL} = require('url');
const util = require('util');

const {Services} = require('../services/services.js');
const {FileSystem} = require('./file_system_backend.js');
const {SearchBackend} = require('./search_backend.js');
const {NdbPreferences} = require('./preferences.js');

const fsCopyFile = util.promisify(fs.copyFile);
const fsReadFile = util.promisify(fs.readFile);
const fsMkdir = util.promisify(fs.mkdir);
const fsMkdtemp = util.promisify(fs.mkdtemp);
const fsWriteFile = util.promisify(fs.writeFile);

async function launch(options) {
// TODO: remove prepare/restore process streams as soon as we roll pptr with proper fix.
prepareProcessStreams();
const browser = await puppeteer.launch({
appMode: true,
dumpio: true,
userDataDir: await setupUserDataDir(options.configDir, options.doNotCopyPreferences),
args: [
'--app=data:text/html,<style>html{background:#242424;}</style>',
'--enable-features=NetworkService',
'--no-sandbox'
]
});
restoreProcessStreams();

const [frontend] = await browser.pages();
frontend._client.send('Emulation.setDefaultBackgroundColorOverride',
{color: {r: 0x24, g: 0x24, b: 0x24, a: 0xff}});
frontend.on('close', browser.close.bind(browser));
frontend.safeEvaluate = async(...args) => {
try {
return await frontend.evaluate(...args);
} catch (e) {
if (!e.message.includes('Session closed.'))
throw e;
}
};

const [searchBackend, fileSystemBackend, services] = await Promise.all([
SearchBackend.create(frontend),
FileSystem.create(frontend),
Services.create(frontend),
NdbPreferences.create(frontend, options.configDir),
frontend.evaluateOnNewDocument(`NdbProcessInfo = ${JSON.stringify({
execPath: process.execPath,
npmExecPath: process.execPath.replace(/node$/, 'npm').replace(/node\.exe$/, 'npm.cmd'),
argv: options.argv,
cwd: options.cwd,
serviceDir: path.join(__dirname, '..', 'services'),
repl: require.resolve('./repl.js')
})};`),
frontend.evaluateOnNewDocument(`function callFrontend(f) {
${options.debugFrontend ? 'setTimeout(_ => f(), 0)' : 'f()'}
}`),
frontend.setRequestInterception(true),
frontend.exposeFunction('openInNewTab', url => require('opn')(url)),
frontend.exposeFunction('copyText', text => require('clipboardy').write(text))
]);
fileSystemBackend.setSearchBackend(searchBackend);
browser.on('disconnect', services.dispose.bind(services));
const overridesFolder = options.debugFrontend
? path.dirname(require.resolve('../front_end/ndb_app.json'))
: path.dirname(require.resolve('../.local-frontend/ndb_app.js'));
frontend.on('request', requestIntercepted.bind(null, overridesFolder));

await frontend.goto('https://ndb/ndb_app.html?experiments=true');
return frontend;
}

async function setupUserDataDir(configDirectory, doNotCopyPreferences) {
if (!fs.existsSync(configDirectory))
await fsMkdir(configDirectory);

const chromiumPreferencesFile = path.join(configDirectory, 'ChromiumPreferences');
if (!fs.existsSync(chromiumPreferencesFile))
await fsWriteFile(chromiumPreferencesFile, '{}', 'utf8');

const userDataDir = await fsMkdtemp(path.join(os.tmpdir(), 'ndb-'));
const defaultUserDir = path.join(userDataDir, 'Default');
await fsMkdir(defaultUserDir);

const preferencesFile = path.join(defaultUserDir, 'Preferences');
await fsCopyFile(chromiumPreferencesFile, preferencesFile);

process.on('exit', _ => {
if (!doNotCopyPreferences)
fs.copyFileSync(preferencesFile, chromiumPreferencesFile);
removeFolder.sync(userDataDir);
});

return userDataDir;
}

async function requestIntercepted(overridesFolder, interceptedRequest) {
const {pathname} = new URL(interceptedRequest.url());
let fileName = pathname.startsWith('/node_modules/')
? path.join('..', pathname)
: path.join(overridesFolder, pathname);
if (!fs.existsSync(fileName))
fileName = require.resolve(`chrome-devtools-frontend/front_end${pathname}`);
const buffer = await fsReadFile(fileName);
interceptedRequest.respond({
status: 200,
body: buffer
});
}

function redirectPipe(stdStream, devnull, src) {
src.unpipe(stdStream);
src.pipe(devnull);
}

function prepareProcessStreams() {
const devnull = new Writable({
write(chunk, encoding, callback) {
callback();
}
});
process.stderr.on('pipe', redirectPipe.bind(null, process.stderr, devnull));
process.stdout.on('pipe', redirectPipe.bind(null, process.stdout, devnull));
}

function restoreProcessStreams() {
process.stderr.removeAllListeners('pipe');
process.stdout.removeAllListeners('pipe');
}

module.exports = {launch};

0 comments on commit 4d6c2b1

Please sign in to comment.