From 9524fe77cea3cb67fb62a0ac0273c0f0dda0b9ff Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 7 Oct 2020 17:14:30 +0100 Subject: [PATCH 1/6] [WIP] Working implementation $ node test_protocol/proto_tests.js Working: - TestRunner embeded as a transport, taking in reads/writes + events - TestRunner scripts executing against traffic + events ToDo: - Much tidying - File structure could do with being tidier - Loading TestRunner scripts from ./*.txt - Wrap TestRunner as a jest test - Include TestRunner in prod builds? Could be useful for protocol testing in live builds - Actually write some protocol test scripts --- test_protocol/proto_tests.js | 101 +++++++++++ test_protocol/testrunner.js | 330 +++++++++++++++++++++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 test_protocol/proto_tests.js create mode 100644 test_protocol/testrunner.js diff --git a/test_protocol/proto_tests.js b/test_protocol/proto_tests.js new file mode 100644 index 00000000..1d59505c --- /dev/null +++ b/test_protocol/proto_tests.js @@ -0,0 +1,101 @@ +const EventEmitter = require('eventemitter3'); +class Transport extends EventEmitter { + constructor(r) { + super(); + + this.connected = false; + this.r = r; + this.r.onSendLine = line => { + // server -> client data + this.emit('line', line + '\n'); + }; + } + + isConnected() { + return true; + } + + writeLine(line, cb) { + this.r.addLineFromClient(line); + cb && setTimeout(cb); + } + + connect() { + setTimeout(() => { + this.connected = true; + this.emit('open'); + }); + } + + disposeSocket() { + if (this.connected) { + this.close(); + } + } + + close() { + if (this.connected) { + setTimeout(() => { + this.connected = false; + this.emit('close', false); + }); + } + } + + setEncoding(encoding) { + } +}; + +function createTestRunnerTransport(r) { + return function(...args) { + return new Transport(r, ...args) + }; +} + +function CatchAllMiddleware(r) { + return function(client, raw_events, parsed_events) { + parsed_events.use(theMiddleware); + }; + + function theMiddleware(command, event, client, next) { + r.addEventFromClient(command, event) + next(); + } +} + + + +const TestRunner = require('./testrunner'); +const IRC = require('../src/'); + + +const r = new TestRunner(); +r.load(` +# Testing sending WHO after joining a channel +READ CAP LS 302 +READ NICK $nick +READ USER $ $ $ $ +SEND :src 001 $nick something :Welcome home +SEND :src 005 $nick a b c :is supported +READ JOIN $chan +SEND :$nick JOIN $chan +EVENTWAIT join channel="$chan" nick=$nick +READ WHO $1 +EXPECT $1="#chan" +`); +(async function() { + await r.run(); +})(); + +const bot = new IRC.Client(); +bot.use(CatchAllMiddleware(r)); +bot.connect({ + transport: createTestRunnerTransport(r), + host: 'irc.irc.com', + nick: 'ircfrw_testrunner', +}); +bot.on('registered', function() { + bot.join('#prawnsalad'); +}); +//bot.on('debug', l => console.log('[debug]', l)); +//bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); diff --git a/test_protocol/testrunner.js b/test_protocol/testrunner.js new file mode 100644 index 00000000..7e077c75 --- /dev/null +++ b/test_protocol/testrunner.js @@ -0,0 +1,330 @@ +// Parse 'key=val key="val" key="val val2" "key name"=val' into an object +function kvParse(inp) { + let data = {}; + let pos = 0; + let escapeChar = '\\'; + + while (pos < inp.length) { + let key = ''; + let val = ''; + + key = readToken(); + ffwd(); + if (inp[pos] === '=') { + skip(); + val = readToken({isValue: true}); + } else { + ffwd(); + val = true; + } + + data[key] = val; + } + + return data; + + // Fast forward past whitespace + function ffwd() { + while (inp[pos] === ' ' && pos < inp.length) { + pos++; + } + } + + // Skip the current position + function skip() { + pos++; + } + + // Read a block of characters. Quoted allows spaces + function readToken(opts={isValue:false}) { + let inQuote = false; + let buffer = ''; + + ffwd(); + do { + let cur = inp[pos]; + if (!cur) { + break; + } + + // Opening quote + if (!inQuote && isQuote(cur)) { + inQuote = true; + continue; + } + + // Escaped closing quote = not a closing quote + if (inQuote && isQuote(cur) && isEscaped()) { + buffer += cur; + continue; + } + + // Closing quote + if (inQuote && isQuote(cur)) { + inQuote = false; + skip(); + break; + } + + if (!opts.isValue) { + if (!inQuote && (cur === ' ' || cur === '=')) { + break; + } + } else { + // Values allow = characters + if (!inQuote && cur === ' ') { + break; + } + } + + buffer += cur; + } while(++pos < inp.length) + + return buffer; + } + + function isQuote(char) { + return char === '"'; + } + + function isEscaped() { + return inp[pos-1] === escapeChar; + } +} + +// Splits a string but stops at the first match +function splitOnce(inp, sep=' ') { + let p1, p2 = ''; + let pos = inp.indexOf(sep); + if (pos === -1) { + p1 = inp; + } else { + p1 = inp.substr(0, pos); + p2 = inp.substr(pos + 1); + } + return [p1, p2]; +} + +// A simple promise based Queue +class Queue { + constructor() { + this.items = []; + this.waiting = []; + } + + add(item) { + this.items.push(item); + setTimeout(() => { + this.deliver(); + }); + } + + get() { + let res = null; + let prom = new Promise(resolve => res = resolve); + prom.resolve = res; + this.waiting.push(prom); + setTimeout(() => { + this.deliver(); + }); + return prom; + } + + flush() { + this.waiting.forEach(w => w.resolve()); + } + + deliver() { + if (this.waiting.length > 0 && this.items.length > 0) { + this.waiting.shift().resolve(this.items.shift()); + } + } +} + +class RunnerError extends Error { + constructor(step, message, runner) { + let errMessage = runner && runner.description ? + `[${runner.description}] ` : + ''; + if (step) { + errMessage += `at test line ${step.sourceLineNum}: ` + } + super(errMessage + message); + this.name = 'RunnerError'; + } +} + +// A single actionable step from a test script +class TestStep { + constructor(command, args) { + this.command = command; + this.args = args; + this.sourceLineNum = 0; + } +} + + +// Execute a test script +class TestRunner { + constructor() { + this.description = ''; + this.steps = []; + this.vars = new Map(); + this.clientBuffer = new Queue(); + this.clientEvents = new Queue(); + this.onSendLine = (line) => {}; + } + + load(input) { + let firstComment = ''; + + let steps = []; + input.split('\n').forEach((line, lineNum) => { + // Strip out empty lines and comments, keeping track of line numbers for kept lines + let trimmed = line.trim(); + if (!trimmed) { + return; + } + + // Comment + if (trimmed[0] === '#') { + firstComment = firstComment || trimmed.replace(/^[# ]+/, '').trim(); + return; + } + + let pos = trimmed.indexOf(' '); + let command = trimmed.substr(0, pos).toUpperCase(); + let args = trimmed.substr(pos).trim(); + let step = new TestStep(command, args); + step.sourceLineNum = lineNum; + steps.push(step); + }); + + this.description = firstComment; + this.steps = steps; + } + + async run() { + for(let i=0; i { + let varName = this.varName(stepArg); + if (varName) { + this.vars.set(varName, lineParts[idx]); + } else if (varName === '') { + // empty var name = ignore this value + } else { + if (stepArg !== lineParts[idx]) { + throw new RunnerError(step, `READ expected '${stepArg}', got '${lineParts[idx]}'`, this); + } + } + }); + } + + async commandSEND(step) { + let line = step.args.replace(/(^|\W)\$([a-z0-9_]+)/g, (_, prefix, varName) => { + return prefix + (this.vars.get(varName) || '-'); + }); + + if (typeof this.onSendLine === 'function') { + this.onSendLine(line); + } + } + + async commandEXPECT(step) { + let checks = kvParse(step.args); + for (let prop in checks) { + // Both the key or value could be a variable + let key = this.varName(prop); + key = key === false ? + prop : + this.vars.get(key); + + let val = this.varName(checks[prop]); + val = val === false ? + checks[prop] : + this.vars.get(val); + + if (key !== val) { + throw new RunnerError(step, `EXPECT failed to match '${key}'='${val}'`, this); + } + } + } + + async commandEVENTWAIT(step) { + let [eventName] = splitOnce(step.args); + return this.commandEVENT(step, eventName); + } + + async commandEVENT(step, waitForEventName='') { + let pos = step.args.indexOf(' '); + let eventName = step.args.substr(0, pos); + let checks = kvParse(step.args.substr(pos)); + let name, event = null; + + if (waitForEventName) { + // Ignore all events until we find the one we want + while (name !== waitForEventName) { + ({name, event} = await this.clientEvents.get()); + } + } else { + ({name, event} = await this.clientEvents.get()); + } + + if (name !== eventName) { + throw new RunnerError(step, `EVENT expected event name of '${eventName}', found '${name}'`, this); + } + + for (let key in checks) { + let val = this.varName(checks[key]); + val = val === false ? + checks[key] : + this.vars.get(val); + + if (event[key] !== val) { + throw new RunnerError(step, `EVENT failed to match property '${key}'='${val}', found '${event[key]}'`, this); + } + } + } + + varName(inp) { + return inp[0] === '$' ? + inp.substr(1) : + false; + } +} + +module.exports = TestRunner; From f6f0df79b7d536af1a8205778ced8f3bb09234cb Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 7 Oct 2020 18:28:40 +0100 Subject: [PATCH 2/6] Cleaner file structure; Separate script files --- src/kvparse.js | 93 ++++++++++++++++ test_protocol/index.js | 62 +++++++++++ test_protocol/proto_tests.js | 101 ------------------ .../test_scripts/register_without_cap.txt | 6 ++ test_protocol/test_scripts/who_after_join.txt | 11 ++ test_protocol/testrunner.js | 96 +---------------- test_protocol/testrunnertransport.js | 50 +++++++++ 7 files changed, 224 insertions(+), 195 deletions(-) create mode 100644 src/kvparse.js create mode 100644 test_protocol/index.js delete mode 100644 test_protocol/proto_tests.js create mode 100644 test_protocol/test_scripts/register_without_cap.txt create mode 100644 test_protocol/test_scripts/who_after_join.txt create mode 100644 test_protocol/testrunnertransport.js diff --git a/src/kvparse.js b/src/kvparse.js new file mode 100644 index 00000000..58a78aeb --- /dev/null +++ b/src/kvparse.js @@ -0,0 +1,93 @@ +// Parse 'key=val key="val" key="val val2" "key name"=val' into an object +module.exports = function kvParse(inp) { + let data = {}; + let pos = 0; + let escapeChar = '\\'; + + while (pos < inp.length) { + let key = ''; + let val = ''; + + key = readToken(); + ffwd(); + if (inp[pos] === '=') { + skip(); + val = readToken({isValue: true}); + } else { + ffwd(); + val = true; + } + + data[key] = val; + } + + return data; + + // Fast forward past whitespace + function ffwd() { + while (inp[pos] === ' ' && pos < inp.length) { + pos++; + } + } + + // Skip the current position + function skip() { + pos++; + } + + // Read a block of characters. Quoted allows spaces + function readToken(opts={isValue:false}) { + let inQuote = false; + let buffer = ''; + + ffwd(); + do { + let cur = inp[pos]; + if (!cur) { + break; + } + + // Opening quote + if (!inQuote && isQuote(cur)) { + inQuote = true; + continue; + } + + // Escaped closing quote = not a closing quote + if (inQuote && isQuote(cur) && isEscaped()) { + buffer += cur; + continue; + } + + // Closing quote + if (inQuote && isQuote(cur)) { + inQuote = false; + skip(); + break; + } + + if (!opts.isValue) { + if (!inQuote && (cur === ' ' || cur === '=')) { + break; + } + } else { + // Values allow = characters + if (!inQuote && cur === ' ') { + break; + } + } + + buffer += cur; + } while(++pos < inp.length) + + return buffer; + } + + function isQuote(char) { + return char === '"'; + } + + function isEscaped() { + return inp[pos-1] === escapeChar; + } +} diff --git a/test_protocol/index.js b/test_protocol/index.js new file mode 100644 index 00000000..b9b2f44c --- /dev/null +++ b/test_protocol/index.js @@ -0,0 +1,62 @@ +const fs = require('fs'); +const TestRunner = require('./testrunner'); +const TestRunnerTransport = require('./testrunnertransport'); +const IRC = require('../src/'); + +(async function () { + // Run through each test runner script and run it + let scriptsDir = __dirname + '/test_scripts/'; + let scripts = fs.readdirSync(scriptsDir); + for(let i=0; i { + if (event.nick === bot.user.nick) { + bot.who(event.channel); + } + }); + //bot.on('debug', l => console.log('[debug]', l)); + bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); + + await scriptRun; + bot.connection.end(); +}; + +// Create an irc-framework transport +function createTestRunnerTransport(r) { + return function(...args) { + return new TestRunnerTransport(r, ...args) + }; +} + +// Pass all framework events to TestRunner r +function CatchAllMiddleware(r) { + return function(client, raw_events, parsed_events) { + parsed_events.use(theMiddleware); + }; + + function theMiddleware(command, event, client, next) { + r.addEventFromClient(command, event) + next(); + } +} diff --git a/test_protocol/proto_tests.js b/test_protocol/proto_tests.js deleted file mode 100644 index 1d59505c..00000000 --- a/test_protocol/proto_tests.js +++ /dev/null @@ -1,101 +0,0 @@ -const EventEmitter = require('eventemitter3'); -class Transport extends EventEmitter { - constructor(r) { - super(); - - this.connected = false; - this.r = r; - this.r.onSendLine = line => { - // server -> client data - this.emit('line', line + '\n'); - }; - } - - isConnected() { - return true; - } - - writeLine(line, cb) { - this.r.addLineFromClient(line); - cb && setTimeout(cb); - } - - connect() { - setTimeout(() => { - this.connected = true; - this.emit('open'); - }); - } - - disposeSocket() { - if (this.connected) { - this.close(); - } - } - - close() { - if (this.connected) { - setTimeout(() => { - this.connected = false; - this.emit('close', false); - }); - } - } - - setEncoding(encoding) { - } -}; - -function createTestRunnerTransport(r) { - return function(...args) { - return new Transport(r, ...args) - }; -} - -function CatchAllMiddleware(r) { - return function(client, raw_events, parsed_events) { - parsed_events.use(theMiddleware); - }; - - function theMiddleware(command, event, client, next) { - r.addEventFromClient(command, event) - next(); - } -} - - - -const TestRunner = require('./testrunner'); -const IRC = require('../src/'); - - -const r = new TestRunner(); -r.load(` -# Testing sending WHO after joining a channel -READ CAP LS 302 -READ NICK $nick -READ USER $ $ $ $ -SEND :src 001 $nick something :Welcome home -SEND :src 005 $nick a b c :is supported -READ JOIN $chan -SEND :$nick JOIN $chan -EVENTWAIT join channel="$chan" nick=$nick -READ WHO $1 -EXPECT $1="#chan" -`); -(async function() { - await r.run(); -})(); - -const bot = new IRC.Client(); -bot.use(CatchAllMiddleware(r)); -bot.connect({ - transport: createTestRunnerTransport(r), - host: 'irc.irc.com', - nick: 'ircfrw_testrunner', -}); -bot.on('registered', function() { - bot.join('#prawnsalad'); -}); -//bot.on('debug', l => console.log('[debug]', l)); -//bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); diff --git a/test_protocol/test_scripts/register_without_cap.txt b/test_protocol/test_scripts/register_without_cap.txt new file mode 100644 index 00000000..33643cb0 --- /dev/null +++ b/test_protocol/test_scripts/register_without_cap.txt @@ -0,0 +1,6 @@ +# Testing IRC registration without CAP support +READ CAP LS 302 +READ NICK $nick +READ USER $ $ $ $ +SEND :src 001 $nick something :Welcome home +EVENT registered nick=$nick \ No newline at end of file diff --git a/test_protocol/test_scripts/who_after_join.txt b/test_protocol/test_scripts/who_after_join.txt new file mode 100644 index 00000000..efdd8758 --- /dev/null +++ b/test_protocol/test_scripts/who_after_join.txt @@ -0,0 +1,11 @@ +# Testing sending WHO after joining a channel +READ CAP LS 302 +READ NICK $nick +READ USER $ $ $ $ +SEND :src 001 $nick something :Welcome home +SEND :src 005 $nick a b c :is supported +READ JOIN $chan +SEND :$nick JOIN $chan +EVENTWAIT join channel="$chan" nick=$nick +READ WHO $1 +EXPECT $1="$chan" diff --git a/test_protocol/testrunner.js b/test_protocol/testrunner.js index 7e077c75..b2aa69d2 100644 --- a/test_protocol/testrunner.js +++ b/test_protocol/testrunner.js @@ -1,98 +1,6 @@ -// Parse 'key=val key="val" key="val val2" "key name"=val' into an object -function kvParse(inp) { - let data = {}; - let pos = 0; - let escapeChar = '\\'; - - while (pos < inp.length) { - let key = ''; - let val = ''; - - key = readToken(); - ffwd(); - if (inp[pos] === '=') { - skip(); - val = readToken({isValue: true}); - } else { - ffwd(); - val = true; - } - - data[key] = val; - } - - return data; - - // Fast forward past whitespace - function ffwd() { - while (inp[pos] === ' ' && pos < inp.length) { - pos++; - } - } - - // Skip the current position - function skip() { - pos++; - } - - // Read a block of characters. Quoted allows spaces - function readToken(opts={isValue:false}) { - let inQuote = false; - let buffer = ''; - - ffwd(); - do { - let cur = inp[pos]; - if (!cur) { - break; - } - - // Opening quote - if (!inQuote && isQuote(cur)) { - inQuote = true; - continue; - } - - // Escaped closing quote = not a closing quote - if (inQuote && isQuote(cur) && isEscaped()) { - buffer += cur; - continue; - } - - // Closing quote - if (inQuote && isQuote(cur)) { - inQuote = false; - skip(); - break; - } - - if (!opts.isValue) { - if (!inQuote && (cur === ' ' || cur === '=')) { - break; - } - } else { - // Values allow = characters - if (!inQuote && cur === ' ') { - break; - } - } - - buffer += cur; - } while(++pos < inp.length) - - return buffer; - } - - function isQuote(char) { - return char === '"'; - } - - function isEscaped() { - return inp[pos-1] === escapeChar; - } -} +const kvParse = require('../src/kvparse'); -// Splits a string but stops at the first match +// Splits a string but stops splitting at the first match function splitOnce(inp, sep=' ') { let p1, p2 = ''; let pos = inp.indexOf(sep); diff --git a/test_protocol/testrunnertransport.js b/test_protocol/testrunnertransport.js new file mode 100644 index 00000000..c982cbc9 --- /dev/null +++ b/test_protocol/testrunnertransport.js @@ -0,0 +1,50 @@ +const EventEmitter = require('eventemitter3'); + +class Transport extends EventEmitter { + constructor(r) { + super(); + + this.connected = false; + this.r = r; + this.r.onSendLine = line => { + // server -> client data + this.emit('line', line + '\n'); + }; + } + + isConnected() { + return true; + } + + writeLine(line, cb) { + this.r.addLineFromClient(line); + cb && setTimeout(cb); + } + + connect() { + setTimeout(() => { + this.connected = true; + this.emit('open'); + }); + } + + disposeSocket() { + if (this.connected) { + this.close(); + } + } + + close() { + if (this.connected) { + setTimeout(() => { + this.connected = false; + this.emit('close', false); + }); + } + } + + setEncoding(encoding) { + } +}; + +module.exports = Transport; From aa3251eb5d0eb1c3c6c8ba6b214cfae82d26febb Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 7 Oct 2020 18:54:46 +0100 Subject: [PATCH 3/6] Run protocol tests under jest --- test/protocol.test.js | 9 +++++++++ test_protocol/index.js | 21 +++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 test/protocol.test.js diff --git a/test/protocol.test.js b/test/protocol.test.js new file mode 100644 index 00000000..7b58829b --- /dev/null +++ b/test/protocol.test.js @@ -0,0 +1,9 @@ +'use strict'; +/* globals describe, it */ +const TestProtocol = require('../test_protocol/'); + +describe('protocol test runners', async function() { + it('should run all protocol test scripts', async function() { + await TestProtocol() + }); +}); diff --git a/test_protocol/index.js b/test_protocol/index.js index b9b2f44c..d9e3a3d2 100644 --- a/test_protocol/index.js +++ b/test_protocol/index.js @@ -3,7 +3,17 @@ const TestRunner = require('./testrunner'); const TestRunnerTransport = require('./testrunnertransport'); const IRC = require('../src/'); -(async function () { +module.exports = runScripts; + +isMain = require.main === module; + +(async function() { + if (isMain) { + await runScripts(); + } +})(); + +async function runScripts() { // Run through each test runner script and run it let scriptsDir = __dirname + '/test_scripts/'; let scripts = fs.readdirSync(scriptsDir); @@ -11,7 +21,7 @@ const IRC = require('../src/'); let scriptContent = fs.readFileSync(scriptsDir + scripts[i], 'utf8'); await runScript(scriptContent); } -})(); +}; async function runScript(script) { const r = new TestRunner(); @@ -35,8 +45,11 @@ async function runScript(script) { bot.who(event.channel); } }); - //bot.on('debug', l => console.log('[debug]', l)); - bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); + + if (isMain) { + //bot.on('debug', l => console.log('[debug]', l)); + bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); + } await scriptRun; bot.connection.end(); From 866b2c673df1e32d5c49f1b31c42e422b8f64220 Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 7 Oct 2020 22:56:58 +0100 Subject: [PATCH 4/6] Correct line number reporting --- test_protocol/testrunner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_protocol/testrunner.js b/test_protocol/testrunner.js index b2aa69d2..1fa90687 100644 --- a/test_protocol/testrunner.js +++ b/test_protocol/testrunner.js @@ -104,7 +104,7 @@ class TestRunner { let command = trimmed.substr(0, pos).toUpperCase(); let args = trimmed.substr(pos).trim(); let step = new TestStep(command, args); - step.sourceLineNum = lineNum; + step.sourceLineNum = lineNum+1; steps.push(step); }); From 7cacacdc8e46825a8dff80268521cf98e1cd9b9e Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 7 Oct 2020 22:57:31 +0100 Subject: [PATCH 5/6] TestRunner READWAIT command --- .../test_scripts/register_using_readwait.txt | 10 +++++++++ test_protocol/testrunner.js | 22 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 test_protocol/test_scripts/register_using_readwait.txt diff --git a/test_protocol/test_scripts/register_using_readwait.txt b/test_protocol/test_scripts/register_using_readwait.txt new file mode 100644 index 00000000..162faffd --- /dev/null +++ b/test_protocol/test_scripts/register_using_readwait.txt @@ -0,0 +1,10 @@ +# Testing IRC registration without CAP support + +# wait until we get the USER command +READWAIT USER $ $ $ $ + +# register the client with the nick "testbot" +SEND :src 001 testbot something :Welcome home + +# the client should now trigger a "registered" event +EVENT registered nick=testbot \ No newline at end of file diff --git a/test_protocol/testrunner.js b/test_protocol/testrunner.js index 1fa90687..91a23f4b 100644 --- a/test_protocol/testrunner.js +++ b/test_protocol/testrunner.js @@ -136,8 +136,26 @@ class TestRunner { return this.clientBuffer.get(); } - async commandREAD(step) { - let line = await this.getLineFromClient(); + async commandREADWAIT(step) { + let [commandName] = splitOnce(step.args); + return this.commandREAD(step, commandName); + } + + async commandREAD(step, waitForCommand='') { + let line = ''; + + if (waitForCommand) { + while (true) { + line = await this.getLineFromClient(); + let [command] = splitOnce(line); + if (command.toLowerCase() === waitForCommand.toLowerCase()) { + break; + } + } + } else { + line = await this.getLineFromClient(); + } + let lineParts = line.split(/\ +/); let stepParts = step.args.split(/\ +/); From cad8efaeb737bc3757d62ff9328af5811234f02f Mon Sep 17 00:00:00 2001 From: Darren Date: Thu, 8 Oct 2020 00:29:43 +0100 Subject: [PATCH 6/6] TestRunner RESET command --- test_protocol/index.js | 53 ++++++++++++------- .../test_scripts/script_reset_test.txt | 20 +++++++ test_protocol/testrunner.js | 14 +++-- 3 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 test_protocol/test_scripts/script_reset_test.txt diff --git a/test_protocol/index.js b/test_protocol/index.js index d9e3a3d2..645eee48 100644 --- a/test_protocol/index.js +++ b/test_protocol/index.js @@ -24,35 +24,48 @@ async function runScripts() { }; async function runScript(script) { + let bot = null; + const r = new TestRunner(); r.load(script); + r.onReset = () => { + createNewIrcClient(); + }; // Start running the test runner before creating the client to be sure all events are caught let scriptRun = r.run(); + createNewIrcClient(); + await scriptRun; + if (bot) { + bot.connection.end(); + } - const bot = new IRC.Client(); - bot.use(CatchAllMiddleware(r)); - bot.connect({ - transport: createTestRunnerTransport(r), - host: 'irc.example.net', - nick: 'ircfrw_testrunner', - }); - bot.on('registered', function() { - bot.join('#prawnsalad'); - }); - bot.on('join', event => { - if (event.nick === bot.user.nick) { - bot.who(event.channel); + function createNewIrcClient() { + if (bot) { + bot.connection.end(); } - }); - if (isMain) { - //bot.on('debug', l => console.log('[debug]', l)); - bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); - } + bot = new IRC.Client(); + bot.use(CatchAllMiddleware(r)); + bot.connect({ + transport: createTestRunnerTransport(r), + host: 'irc.example.net', + nick: 'ircfrw_testrunner', + }); + bot.on('registered', function() { + bot.join('#prawnsalad'); + }); + bot.on('join', event => { + if (event.nick === bot.user.nick) { + bot.who(event.channel); + } + }); - await scriptRun; - bot.connection.end(); + if (isMain) { + //bot.on('debug', l => console.log('[debug]', l)); + bot.on('raw', event => console.log(`[raw ${event.from_server?'s':'c'}]`, event.line)); + } + } }; // Create an irc-framework transport diff --git a/test_protocol/test_scripts/script_reset_test.txt b/test_protocol/test_scripts/script_reset_test.txt new file mode 100644 index 00000000..738fdcc0 --- /dev/null +++ b/test_protocol/test_scripts/script_reset_test.txt @@ -0,0 +1,20 @@ +# Test script RESET example + +READWAIT USER $ $ $ $ +SEND :src 001 nick1 something :Welcome home +EVENT registered nick=nick1 + +RESET +READWAIT USER $ $ $ $ +SEND :src 001 nick2 something :Welcome home +EVENT registered nick=nick2 + +RESET +READWAIT USER $ $ $ $ +SEND :src 001 nick3 something :Welcome home +EVENT registered nick=nick3 + +RESET +READWAIT USER $ $ $ $ +SEND :src 001 nick4 something :Welcome home +EVENT registered nick=nick4 diff --git a/test_protocol/testrunner.js b/test_protocol/testrunner.js index 91a23f4b..7372a1eb 100644 --- a/test_protocol/testrunner.js +++ b/test_protocol/testrunner.js @@ -81,6 +81,7 @@ class TestRunner { this.clientBuffer = new Queue(); this.clientEvents = new Queue(); this.onSendLine = (line) => {}; + this.onReset = () => {}; } load(input) { @@ -100,10 +101,8 @@ class TestRunner { return; } - let pos = trimmed.indexOf(' '); - let command = trimmed.substr(0, pos).toUpperCase(); - let args = trimmed.substr(pos).trim(); - let step = new TestStep(command, args); + let [command, args] = splitOnce(trimmed); + let step = new TestStep(command.toUpperCase(), args.trim()); step.sourceLineNum = lineNum+1; steps.push(step); }); @@ -136,6 +135,13 @@ class TestRunner { return this.clientBuffer.get(); } + async commandRESET(step) { + this.vars.clear(); + if (typeof this.onReset === 'function') { + this.onReset(); + } + } + async commandREADWAIT(step) { let [commandName] = splitOnce(step.args); return this.commandREAD(step, commandName);