Repository URL to install this package:
Version:
0.6.0 ▾
|
'use strict';
const assert = require('assert');
const { readFileSync } = require('fs');
const { join } = require('path');
const { inspect } = require('util');
const Client = require('../lib/client.js');
const Server = require('../lib/server.js');
const { parseKey } = require('../lib/protocol/keyParser.js');
const mustCallChecks = [];
const DEFAULT_TEST_TIMEOUT = 30 * 1000;
function noop() {}
function runCallChecks(exitCode) {
if (exitCode !== 0) return;
const failed = mustCallChecks.filter((context) => {
if ('minimum' in context) {
context.messageSegment = `at least ${context.minimum}`;
return context.actual < context.minimum;
}
context.messageSegment = `exactly ${context.exact}`;
return context.actual !== context.exact;
});
failed.forEach((context) => {
console.error('Mismatched %s function calls. Expected %s, actual %d.',
context.name,
context.messageSegment,
context.actual);
console.error(context.stack.split('\n').slice(2).join('\n'));
});
if (failed.length)
process.exit(1);
}
function mustCall(fn, exact) {
return _mustCallInner(fn, exact, 'exact');
}
function mustCallAtLeast(fn, minimum) {
return _mustCallInner(fn, minimum, 'minimum');
}
function _mustCallInner(fn, criteria = 1, field) {
if (process._exiting)
throw new Error('Cannot use common.mustCall*() in process exit handler');
if (typeof fn === 'number') {
criteria = fn;
fn = noop;
} else if (fn === undefined) {
fn = noop;
}
if (typeof criteria !== 'number')
throw new TypeError(`Invalid ${field} value: ${criteria}`);
const context = {
[field]: criteria,
actual: 0,
stack: inspect(new Error()),
name: fn.name || '<anonymous>'
};
// Add the exit listener only once to avoid listener leak warnings
if (mustCallChecks.length === 0)
process.on('exit', runCallChecks);
mustCallChecks.push(context);
function wrapped(...args) {
++context.actual;
return fn.call(this, ...args);
}
// TODO: remove origFn?
wrapped.origFn = fn;
return wrapped;
}
function getCallSite(top) {
const originalStackFormatter = Error.prepareStackTrace;
Error.prepareStackTrace = (err, stack) =>
`${stack[0].getFileName()}:${stack[0].getLineNumber()}`;
const err = new Error();
Error.captureStackTrace(err, top);
// With the V8 Error API, the stack is not formatted until it is accessed
// eslint-disable-next-line no-unused-expressions
err.stack;
Error.prepareStackTrace = originalStackFormatter;
return err.stack;
}
function mustNotCall(msg) {
const callSite = getCallSite(mustNotCall);
return function mustNotCall(...args) {
args = args.map(inspect).join(', ');
const argsInfo = (args.length > 0
? `\ncalled with arguments: ${args}`
: '');
assert.fail(
`${msg || 'function should not have been called'} at ${callSite}`
+ argsInfo);
};
}
function setup(title, configs) {
const {
client: clientCfg_,
server: serverCfg_,
allReady: allReady_,
timeout: timeout_,
debug,
noForceClientReady,
noForceServerReady,
noClientError,
noServerError,
} = configs;
// Make shallow copies of client/server configs to avoid mutating them when
// multiple tests share the same config object reference
let clientCfg;
if (clientCfg_)
clientCfg = { ...clientCfg_ };
let serverCfg;
if (serverCfg_)
serverCfg = { ...serverCfg_ };
let clientClose = false;
let clientReady = false;
let serverClose = false;
let serverReady = false;
const msg = (text) => {
return `${title}: ${text}`;
};
const timeout = (typeof timeout_ === 'number'
? timeout_
: DEFAULT_TEST_TIMEOUT);
const allReady = (typeof allReady_ === 'function' ? allReady_ : undefined);
if (debug) {
if (clientCfg) {
clientCfg.debug = (...args) => {
console.log(`[${title}][CLIENT]`, ...args);
};
}
if (serverCfg) {
serverCfg.debug = (...args) => {
console.log(`[${title}][SERVER]`, ...args);
};
}
}
let timer;
let client;
let clientReadyFn;
let server;
let serverReadyFn;
if (clientCfg) {
client = new Client();
if (!noClientError)
client.on('error', onError);
clientReadyFn = (noForceClientReady ? onReady : mustCall(onReady));
client.on('ready', clientReadyFn)
.on('close', mustCall(onClose));
} else {
clientReady = clientClose = true;
}
if (serverCfg) {
server = new Server(serverCfg);
if (!noServerError)
server.on('error', onError);
serverReadyFn = (noForceServerReady ? onReady : mustCall(onReady));
server.on('connection', mustCall((conn) => {
if (!noServerError)
conn.on('error', onError);
conn.on('ready', serverReadyFn);
server.close();
})).on('close', mustCall(onClose));
} else {
serverReady = serverClose = true;
}
function onError(err) {
const which = (this === client ? 'client' : 'server');
assert(false, msg(`Unexpected ${which} error: ${err.stack}\n`));
}
function onReady() {
if (this === client) {
assert(!clientReady,
msg('Received multiple ready events for client'));
clientReady = true;
} else {
assert(!serverReady,
msg('Received multiple ready events for server'));
serverReady = true;
}
clientReady && serverReady && allReady && allReady();
}
function onClose() {
if (this === client) {
assert(!clientClose,
msg('Received multiple close events for client'));
clientClose = true;
} else {
assert(!serverClose,
msg('Received multiple close events for server'));
serverClose = true;
}
if (clientClose && serverClose)
clearTimeout(timer);
}
process.nextTick(mustCall(() => {
function connectClient() {
if (clientCfg.sock) {
clientCfg.sock.connect(server.address().port, 'localhost');
} else {
clientCfg.host = 'localhost';
clientCfg.port = server.address().port;
}
try {
client.connect(clientCfg);
} catch (ex) {
ex.message = msg(ex.message);
throw ex;
}
}
if (server) {
server.listen(0, 'localhost', mustCall(() => {
if (timeout >= 0) {
timer = setTimeout(() => {
assert(false, msg('Test timed out'));
}, timeout);
}
if (client)
connectClient();
}));
}
}));
return { client, server };
}
const FIXTURES_DIR = join(__dirname, 'fixtures');
const fixture = (() => {
const cache = new Map();
return (file) => {
const existing = cache.get(file);
if (existing !== undefined)
return existing;
const result = readFileSync(join(FIXTURES_DIR, file));
cache.set(file, result);
return result;
};
})();
const fixtureKey = (() => {
const cache = new Map();
return (file, passphrase, bypass) => {
if (typeof passphrase === 'boolean') {
bypass = passphrase;
passphrase = undefined;
}
if (typeof bypass !== 'boolean' || !bypass) {
const existing = cache.get(file);
if (existing !== undefined)
return existing;
}
const fullPath = join(FIXTURES_DIR, file);
const raw = fixture(file);
let key = parseKey(raw, passphrase);
if (Array.isArray(key))
key = key[0];
const result = { key, raw, fullPath };
cache.set(file, result);
return result;
};
})();
function setupSimple(debug, title) {
const { client, server } = setup(title, {
client: { username: 'Password User', password: '12345' },
server: { hostKeys: [ fixtureKey('ssh_host_rsa_key').raw ] },
debug,
});
server.on('connection', mustCall((conn) => {
conn.on('authentication', mustCall((ctx) => {
ctx.accept();
}));
}));
return { client, server };
}
module.exports = {
fixture,
fixtureKey,
FIXTURES_DIR,
mustCall,
mustCallAtLeast,
mustNotCall,
setup,
setupSimple,
};