From 6b82d79132b809c613899157a3071f7a5a3c4760 Mon Sep 17 00:00:00 2001 From: asac Date: Thu, 18 Jul 2019 15:25:57 +0300 Subject: [PATCH] test install script added --- psknode/tests/basic.js | 28 ++++ psknode/tests/util/tir.js | 271 ++++++++++++++++++++++++++++++++++++++ tests/installTest.js | 3 +- 3 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 psknode/tests/basic.js create mode 100644 psknode/tests/util/tir.js diff --git a/psknode/tests/basic.js b/psknode/tests/basic.js new file mode 100644 index 0000000..e617f93 --- /dev/null +++ b/psknode/tests/basic.js @@ -0,0 +1,28 @@ +const path = require('path'); + +const tir = require(path.resolve(path.join(__dirname, "util/tir.js"))); +const assert = require('double-check').assert; + +const domain = 'local'; +const agent = 'exampleAgent'; +const agents = [agent]; + +const swarm = { + echo: { + say: function(input) { + this.return('Echo ' + input); + } + } +}; + +assert.callback('Basic Test', (finished) => { + tir.addDomain(domain, agents, swarm); + + tir.launch(3000, () => { + tir.interact(domain, agent).startSwarm("echo", "say", "Hello").onReturn(result => { + assert.equal("Echo Hello", result); + finished(); + tir.tearDown(0); + }); + }); +}, 2000); \ No newline at end of file diff --git a/psknode/tests/util/tir.js b/psknode/tests/util/tir.js new file mode 100644 index 0000000..7a8fac6 --- /dev/null +++ b/psknode/tests/util/tir.js @@ -0,0 +1,271 @@ +/** + * Test Integration Runner + * + * This API abstracts the setup, initialization and + * the internal butchery for PSKY. + * + */ + +const path = require('path'); + +const __dName = __dirname; +require(path.resolve(path.join(__dName, "../../bundles/pskruntime.js"))); +require(path.resolve(path.join(__dName, "../../bundles/psknode.js"))); +require(path.resolve(path.join(__dName, "../../bundles/consoleTools.js"))); + +const os = require('os'); +const fs = require('fs'); +const interact = require('interact'); + +const child_process = require('child_process'); +const pskdb = require('pskdb'); + +const createKey = function(name) { + let parsed = '' + name; + parsed.replace(/^[A-Za-z0-9 ]+/g, ' '); + return parsed + .split(' ') + .map((word, idx) => + idx === 0 + ? word.toLocaleLowerCase() + : word.substr(0, 1).toLocaleUpperCase() + word.toLowerCase().substr(1) + ) + .join(''); +}; + +const rmDeep = folder => { + if (fs.existsSync(folder)) { + fs.readdirSync(folder).forEach(file => { + const curPath = path.join(folder, file); + if (fs.lstatSync(curPath).isDirectory()) { + rmDeep(curPath); + } else { + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(folder); + } +}; + +const createConstitution = (prefix, describer, options) => { + let opts = Object.assign( + { + nl: '\n', + semi: ';', + tab: ' ' + }, + options + ); + + const file = path.join(prefix, 'constitution.js'); + const contents = Object.keys(describer) + .reduce((c, name) => { + let line = "$$.swarms.describe('" + name + "', {"; + line += Object.keys(describer[name]) + .reduce((f, prop) => { + if (typeof describer[name][prop] === 'object') { + f.push(opts.nl + opts.tab + prop + ': ' + JSON.stringify(describer[name][prop])); + } else { + f.push(opts.nl + opts.tab + prop + ': ' + describer[name][prop].toString()); + } + return f; + }, []) + .join(','); + line += opts.nl + '})' + opts.semi; + c.push(line); + return c; + }, []) + .join(opts.nl); + fs.writeFileSync(file, contents); + return file; +}; + +const Tir = function() { + const domainConfigs = {}; + const rootFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'psk_')); + + let testerNode = null; + + /** + * Adds a domain to the configuration, in a fluent way. + * Does not launch anything, just stores the configuration. + * + * @param string domain The name of the domain + * @param array agents The agents to be inserted + * @param object|string constitution The swarm describer, either as an object or as a string file path + * @returns this + */ + this.addDomain = (domain, agents, constitution) => { + let workspace = path.join(rootFolder, 'nodes', createKey(domain)); + let domainConfig = { + name: domain, + agents, + constitution, + workspace: workspace, + conf: path.join(workspace, 'conf'), + inbound: path.join(workspace, 'inbound'), + outbound: path.join(workspace, 'outbound') + }; + domainConfigs[domain] = domainConfig; + return this; + }; + + this.createConstitution = createConstitution; + + /** + * Launches all the configured domains. + * + * @param integer tearDownAfter The number of miliseconds the TIR will tear down, even if the test fails. If missing, you must call tearDown + * @param function callable The callback + */ + this.launch = (tearDownAfter, callable) => { + if (callable === undefined && tearDownAfter.call) { + callable = tearDownAfter; + tearDownAfter = null; + } + + if (testerNode !== null) { + throw new Error('Test node already launched!'); + return; + } + console.info('[TIR] setting working folder root', rootFolder); + + const confFolder = path.join(rootFolder, 'conf'); + + console.info('[TIR] pskdb on', confFolder); + pskdb.startDB(confFolder); + + fs.mkdirSync(path.join(rootFolder, 'nodes')); + + console.info('[TIR] start building nodes...'); + Object.keys(domainConfigs).forEach(name => { + const domainConfig = domainConfigs[name]; + this.buildDomainConfiguration(domainConfig); + }); + + testerNode = child_process.spawn( + 'node', + [path.resolve(path.join(__dName, "../../core/launcher.js")), confFolder, rootFolder], + { stdio: 'inherit' } + ); + + setTimeout(() => { + if (tearDownAfter !== null) { + setTimeout(() => this.tearDown(1), tearDownAfter); + } + callable(); + }, 10); + }; + + /** + * Builds the config for a node. + * + * @param object domainConfig The domain configuration stored by addDomain + */ + this.buildDomainConfiguration = domainConfig => { + console.info('[TIR] domain ' + domainConfig.name + ' in workspace', domainConfig.workspace); + console.info('[TIR] domain ' + domainConfig.name + ' inbound', domainConfig.inbound); + + fs.mkdirSync(domainConfig.workspace); + + let constitutionFile = domainConfig.constitution; + if (typeof domainConfig.constitution !== 'string') { + constitutionFile = createConstitution(domainConfig.workspace, domainConfig.constitution); + } + + let transaction = $$.blockchain.beginTransaction({}); + let domain = transaction.lookup('DomainReference', domainConfig.name); + domain.init('system', domainConfig.name); + domain.setWorkspace(domainConfig.workspace); + domain.setConstitution(constitutionFile); + domain.addLocalInterface('local', domainConfig.inbound); + console.log('>>DOMAIN<<', domainConfig.name, domain.localInterfaces); + try { + transaction.add(domain); + $$.blockchain.commit(transaction); + } catch (err) { + console.log('Got an error', err); + } + + if ( + domainConfig.agents && + Array.isArray(domainConfig.agents) && + domainConfig.agents.length > 0 + ) { + let domainBlockChain = pskdb.createDBHandler(domainConfig.conf); + console.info('[TIR] domain ' + domainConfig.name + ' starting agents...'); + + domainConfig.agents.forEach(agentName => { + console.info('[TIR] domain ' + domainConfig.name + ' agent', agentName); + let trans = domainBlockChain.beginTransaction({}); + let agent = trans.lookup('Agent', agentName); + trans.add(agent); + domainBlockChain.commit(trans); + }); + } + }; + this.getDomainConfig = domainName => { + return domainConfigs[domainName]; + }; + /** + * Interacts with an agent of a domain. + * + * @param string domain The name of the domain + * @param string agent The name of the agent + * @returns swarm + */ + this.interact = (domain, agent) => { + const domainConfig = domainConfigs[domain]; + if (domainConfig === undefined) { + throw new Error( + 'Could not find domain ' + domain + ' in ' + Object.keys(domainConfigs).join(', ') + ); + } else { + var returnChannel = path.join( + domainConfig.outbound, + Math.random() + .toString(36) + .substr(2, 9) + ); + try { + fs.mkdirSync(domainConfig.outbound); + } catch (err) { + //dir allready exists + } + console.info( + '[TIR] Interacting with ' + domainConfig.name + '/' + agent + ' on', + returnChannel + ); + return interact.createNodeInteractionSpace(agent, domainConfig.inbound, returnChannel); + } + }; + + /** + * Tears down all the nodes + * + * @param exitStatus The exit status, to exit the process. + */ + this.tearDown = exitStatus => { + console.info('[TIR] Tearing down...'); + if (testerNode) { + console.info('[TIR] Killing node', testerNode.pid); + process.kill(testerNode.pid); + testerNode = null; + } + setTimeout(() => { + try { + console.info('[TIR] Removing temporary folder', rootFolder); + rmDeep(rootFolder); + console.info('[TIR] Temporary folder removed', rootFolder); + } catch (e) { + //just avoid to display error on console + } + + if (exitStatus !== undefined) { + process.exit(exitStatus); + } + }, 100); + }; +}; + +module.exports = new Tir(); diff --git a/tests/installTest.js b/tests/installTest.js index 6dc8912..873f081 100644 --- a/tests/installTest.js +++ b/tests/installTest.js @@ -1,2 +1,3 @@ -//run to test that the installation is working +const path = require('path'); +require(path.resolve(path.join(__dirname, "../psknode/tests/basic.js")));