More core testing and some development and fixes
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded

This commit is contained in:
Jonatan Nilsson 2022-02-14 08:15:50 +00:00
parent 4f4bc8cf6a
commit e540a54844
12 changed files with 809 additions and 167 deletions

View file

@ -65,7 +65,7 @@ export default class Application extends EventEmitter {
updateLog(message) { updateLog(message) {
this.ctx.db.data.core[this.name].updater += message this.ctx.db.data.core[this.name].updater += message
this.ctx.db.log.info(message) this.ctx.log.info(message)
return message return message
} }
@ -76,18 +76,25 @@ export default class Application extends EventEmitter {
if (this.ctx.db.data.core[this.name].updater !== this.msgStatic) { if (this.ctx.db.data.core[this.name].updater !== this.msgStatic) {
this.ctx.db.data.core[this.name].updater = '' this.ctx.db.data.core[this.name].updater = ''
this.updateLog(this.msgStatic) this.updateLog(this.msgStatic)
return this.ctx.db.write() return this.ctx.db.write().then(function() { return null })
} }
return Promise.resolve() return Promise.resolve(null)
} }
if (this.updating) return if (this.updating) return null
this.updating = true this.updating = true
return this._update() return this._update()
.then(() => { .then((result) => {
this.updating = false this.updating = false
return this.ctx.db.write() return this.ctx.db.write()
.then(function() { return result })
})
.then((result) => {
if (result) {
this.emit('updated', result)
}
return result
}) })
.catch((err) => { .catch((err) => {
this.updating = false this.updating = false
@ -124,7 +131,7 @@ export default class Application extends EventEmitter {
// If the versino matches the latest installed, then there's nothing to do // If the versino matches the latest installed, then there's nothing to do
if (this.ctx.db.data.core[this.name].latestInstalled === latest.version) { if (this.ctx.db.data.core[this.name].latestInstalled === latest.version) {
this.updateLog('Already up to date, nothing to do. ') this.updateLog('Already up to date, nothing to do. ')
return return null
} }
// Make the id for the vesion the version number. Allows for easy lookup // Make the id for the vesion the version number. Allows for easy lookup
@ -137,7 +144,7 @@ export default class Application extends EventEmitter {
// Check if the existing version found was already installed. // Check if the existing version found was already installed.
if (found.installed) { if (found.installed) {
this.updateLog('Version was already installed, nothing to do. ') this.updateLog('Version was already installed, nothing to do. ')
return return null
} }
// We found existing on, update the keys of the one in the databse with // We found existing on, update the keys of the one in the databse with
@ -153,12 +160,12 @@ export default class Application extends EventEmitter {
// listed and avoid at all cost. // listed and avoid at all cost.
if (latest.failtodownload && latest.failtodownload > 3) { if (latest.failtodownload && latest.failtodownload > 3) {
this.updateLog('Version failed to download too many times, skipping this version. ') this.updateLog('Version failed to download too many times, skipping this version. ')
return return null
} }
if (latest.failtoinstall && latest.failtoinstall > 3) { if (latest.failtoinstall && latest.failtoinstall > 3) {
this.updateLog('Version failed to install too many times, skipping this version. ') this.updateLog('Version failed to install too many times, skipping this version. ')
return return null
} }
// Combine the logs // Combine the logs
@ -269,6 +276,7 @@ export default class Application extends EventEmitter {
this.ctx.db.data.core[this.name].latestInstalled = latest.version this.ctx.db.data.core[this.name].latestInstalled = latest.version
latest.installed = true latest.installed = true
latest.log = log latest.log = log
return latest
} }
registerModule(module, version = '') { registerModule(module, version = '') {

View file

@ -36,6 +36,7 @@ export default class Core {
if (!util || !(util instanceof Util)) throw new Error('util not instance of Util') if (!util || !(util instanceof Util)) throw new Error('util not instance of Util')
if (!db || !(db instanceof Low)) throw new Error('db not instance of Low') if (!db || !(db instanceof Low)) throw new Error('db not instance of Low')
this.running = false
this.db = db this.db = db
this.util = util this.util = util
this.log = log this.log = log
@ -56,8 +57,6 @@ export default class Core {
this.log.info(`Found applications: ${names.join(', ')}.`) this.log.info(`Found applications: ${names.join(', ')}.`)
let lastError = null
for (let name of names) { for (let name of names) {
try { try {
let provConstructor = Core.providers.get(this.db.config[name].provider) let provConstructor = Core.providers.get(this.db.config[name].provider)
@ -74,20 +73,22 @@ export default class Core {
this.applicationMap.set(name, application) this.applicationMap.set(name, application)
} catch (err) { } catch (err) {
this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider}: ${err.message}`) this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider}: ${err.message}`)
lastError = err
} }
} }
if (names.length && !this.applications.length) { if (names.length && !this.applications.length) {
throw lastError return Promise.reject(new Error('None of the application were successful in running'))
} }
} }
async run() { async run() {
if (this.running) return
this.running = true
this.log.info(`Running updater on ${this.applications.length} apps`) this.log.info(`Running updater on ${this.applications.length} apps`)
await Promise.all(this.applications.map((app) => { await Promise.all(this.applications.map((app) => {
return app.update().catch(err => { return app.update().catch(err => {
this.log.error(err, `Error updating ${app.name}: ${err.message}`) app.ctx.log.error(err, `Error updating: ${err.message}`)
}) })
})) }))
@ -101,9 +102,11 @@ export default class Core {
found = true found = true
}, },
err => { err => {
this.log.error(err, `Error running application ${app.name}: ${err.message}`) app.ctx.log.error(err, `Error running: ${err.message}`)
} }
) )
app.on('updated', this.runApplication.bind(this, app))
} }
if (!found) { if (!found) {
@ -126,14 +129,16 @@ export default class Core {
await application.closeServer() await application.closeServer()
try { try {
this.log.info(`Attempting to run application ${name} version ${version.version}`) application.ctx.log.info(`Attempting to run version ${version.version}`)
await application.runVersion(version.version) await application.runVersion(version.version)
found = true found = true
version.stable = 1
await this.db.write()
break break
} catch(err) { } catch(err) {
version.stable-- version.stable = Math.min(version.stable, 0) - 1
await this.db.write() await this.db.write()
this.log.error(err, `Error starting ${name} ${version.version}: ${err.message}`) application.ctx.log.error(err, `Error starting ${version.version}: ${err.message}`)
} }
} }

36
core/runner.mjs Normal file
View file

@ -0,0 +1,36 @@
import Util from './util.mjs'
import fs from 'fs/promises'
import getLog from './log.mjs'
import GetDB from './db.mjs'
import Core from './core.mjs'
export async function runner(root_import_meta_url, configname = 'config.json', dbname = 'db.json') {
if (!root_import_meta_url) {
throw new Error('ServiceRunner must be called with the full string from "import.meta.url" from a file residing in the root directory')
}
const util = new Util(root_import_meta_url)
let config = configname
if (typeof(config) === 'string') {
let fullpath = util.getPathFromRoot('./' + config)
try {
config = JSON.parse(await fs.readFile(fullpath))
} catch (err) {
throw new Error(`critical error opening ${fullpath}: ${err.message}`)
}
}
const log = getLog(config.name)
runner.log = log
const db = await GetDB(config, log, util.getPathFromRoot('./' + dbname))
const core = new Core(db, util, log, function() {})
await core.init()
await core.run()
return core
}
runner.log = getLog('runner')

View file

@ -1,6 +1,7 @@
import os from 'os'
import path from 'path' import path from 'path'
import fs from 'fs/promises' import fs from 'fs/promises'
import { spawn } from 'child_process' import { spawn, execSync } from 'child_process'
import { fileURLToPath, pathToFileURL } from 'url' import { fileURLToPath, pathToFileURL } from 'url'
export default class Util { export default class Util {
@ -136,6 +137,9 @@ export default class Util {
processor.stderr.on('data', function(data) { processor.stderr.on('data', function(data) {
stream(data.toString().replace(/\r\n/g, '\n')) stream(data.toString().replace(/\r\n/g, '\n'))
}) })
processor.stdin.on('error', function() {
clearInterval(timeOuter)
})
processor.on('error', function(err) { processor.on('error', function(err) {
clearInterval(timeOuter) clearInterval(timeOuter)
rej(err) rej(err)
@ -149,4 +153,46 @@ export default class Util {
}) })
}) })
} }
runCommandBackground(command, options = [], folder = null, stream = function() {}) {
let fullcommand = path.join(folder ? folder : '', command)
if (command.indexOf('/') >= 0 || command.indexOf('\\') >= 0) {
fullcommand = command
}
stream(`[Command] ${fullcommand} ${options.join(' ')}\n`)
let processor = spawn(command, options, {
shell: true,
cwd: folder,
})
let timeOuter = setInterval(function() {
try {
processor.stdin.write('n\n')
} catch {}
}, 250)
processor.stdout.on('data', function(data) {
stream(data.toString().replace(/\r\n/g, '\n'))
})
processor.stderr.on('data', function(data) {
stream(data.toString().replace(/\r\n/g, '\n'))
})
processor.stdin.on('error', function() {
clearInterval(timeOuter)
})
processor.on('error', function(err) {
clearInterval(timeOuter)
})
processor.on('exit', function (code) {
clearInterval(timeOuter)
})
processor._kill = processor.kill
processor.kill = function() {
if(os.platform() === 'win32'){
execSync('taskkill /pid ' + processor.pid + ' /T /F')
}else{
processor.kill();
}
}
return processor
}
} }

View file

@ -32,7 +32,7 @@ t.after(function() {
}) })
}) })
t.timeout(10000).test('should run update and install correctly', async function(){ t.skip().timeout(10000).test('should run update and install correctly', async function(){
try { try {
await app.update() await app.update()
} catch (err) { } catch (err) {

View file

@ -265,10 +265,11 @@ t.timeout(250).describe('#update()', function() {
provider = new StaticProvider() provider = new StaticProvider()
app = new Application(ctx, provider, 'teststatic') app = new Application(ctx, provider, 'teststatic')
stubWrite.reset() stubWrite.reset().resolves()
await app.update() let result = await app.update()
assert.strictEqual(result, null)
assert.match(ctx.db.data.core.teststatic.updater, /static/i) assert.match(ctx.db.data.core.teststatic.updater, /static/i)
assert.match(ctx.db.data.core.teststatic.updater, /nothing/i) assert.match(ctx.db.data.core.teststatic.updater, /nothing/i)
let old = ctx.db.data.core.teststatic.updater let old = ctx.db.data.core.teststatic.updater
@ -276,16 +277,19 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(stubWrite.callCount, 1) assert.strictEqual(stubWrite.callCount, 1)
await app.update() await app.update()
assert.strictEqual(result, null)
assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 1) assert.strictEqual(stubWrite.callCount, 1)
ctx.db.data.core.teststatic.updater = 'asdf' ctx.db.data.core.teststatic.updater = 'asdf'
await app.update() await app.update()
assert.strictEqual(result, null)
assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 2) assert.strictEqual(stubWrite.callCount, 2)
await app.update() await app.update()
assert.strictEqual(result, null)
assert.strictEqual(ctx.db.data.core.teststatic.updater, old) assert.strictEqual(ctx.db.data.core.teststatic.updater, old)
assert.strictEqual(stubWrite.callCount, 2) assert.strictEqual(stubWrite.callCount, 2)
}) })
@ -305,10 +309,11 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(app.updating, true) assert.strictEqual(app.updating, true)
assert.strictEqual(provider.getLatestVersion.callCount, 1) assert.strictEqual(provider.getLatestVersion.callCount, 1)
app.update() let result = await app.update()
await setImmediate() await setImmediate()
assert.strictEqual(provider.getLatestVersion.callCount, 1) assert.strictEqual(provider.getLatestVersion.callCount, 1)
assert.strictEqual(result, null)
}) })
t.test('should check for latest version', async function() { t.test('should check for latest version', async function() {
@ -494,10 +499,13 @@ t.timeout(250).describe('#update()', function() {
t.test('should not call npm install if stat fails to find index.mjs', async function() { t.test('should not call npm install if stat fails to find index.mjs', async function() {
const assertError = new Error('File not found') const assertError = new Error('File not found')
const assertTarget = util.getPathFromRoot('./testapp/123456789/index.mjs') const assertTarget = util.getPathFromRoot('./testapp/123456789/index.mjs')
const stubUpdated = stub()
stubRunCommand.rejects(new Error('should not be seen')) stubRunCommand.rejects(new Error('should not be seen'))
stubFsStat.rejects(assertError) stubFsStat.rejects(assertError)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
app.once('updated', stubUpdated)
let err = await assert.isRejected(app.update()) let err = await assert.isRejected(app.update())
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
@ -519,6 +527,7 @@ t.timeout(250).describe('#update()', function() {
err = await assert.isRejected(app.update()) err = await assert.isRejected(app.update())
assert.notOk(stubUpdated.called)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError) assert.strictEqual(err, assertError)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
@ -538,10 +547,12 @@ t.timeout(250).describe('#update()', function() {
}) })
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
await app.update() let result = await new Promise(function(res, rej) {
app.once('updated', res)
app.update().catch(rej)
})
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.strictEqual(stubFsStat.callCount, 2) assert.strictEqual(stubFsStat.callCount, 2)
assert.strictEqual(stubFsStat.secondCall[0], assertTarget) assert.strictEqual(stubFsStat.secondCall[0], assertTarget)
assert.ok(stubExtract.called) assert.ok(stubExtract.called)
@ -552,6 +563,7 @@ t.timeout(250).describe('#update()', function() {
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
let version = ctx.db.data.core.testapp.versions[0] let version = ctx.db.data.core.testapp.versions[0]
assert.ok(version.log) assert.ok(version.log)
assert.strictEqual(result, version)
assert.match(version.log, /package\.json/i) assert.match(version.log, /package\.json/i)
assert.match(version.log, /contain/i) assert.match(version.log, /contain/i)
assert.ok(version.log.endsWith('\n')) assert.ok(version.log.endsWith('\n'))
@ -563,8 +575,9 @@ t.timeout(250).describe('#update()', function() {
provider.downloadVersion.reset() provider.downloadVersion.reset()
await app.update() result = await app.update()
assert.strictEqual(result, null)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.notOk(provider.downloadVersion.called) assert.notOk(provider.downloadVersion.called)
assert.match(ctx.db.data.core.testapp.updater, /already/i) assert.match(ctx.db.data.core.testapp.updater, /already/i)
@ -578,6 +591,7 @@ t.timeout(250).describe('#update()', function() {
const assertError = new Error('Nagisa') const assertError = new Error('Nagisa')
const assertTarget = util.getPathFromRoot('./testapp/123456789') const assertTarget = util.getPathFromRoot('./testapp/123456789')
const assertTargetCheck = util.getPathFromRoot('./testapp/123456789/') const assertTargetCheck = util.getPathFromRoot('./testapp/123456789/')
const stubUpdated = stub()
provider.getLatestVersion.resolves(assertVersion) provider.getLatestVersion.resolves(assertVersion)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
@ -590,8 +604,11 @@ t.timeout(250).describe('#update()', function() {
return Promise.reject(assertError) return Promise.reject(assertError)
}) })
app.once('updated', stubUpdated)
let err = await assert.isRejected(app.update()) let err = await assert.isRejected(app.update())
assert.notOk(stubUpdated.called)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.strictEqual(err, assertError) assert.strictEqual(err, assertError)
assert.strictEqual(stubRunCommand.firstCall[0], 'npm.cmd') assert.strictEqual(stubRunCommand.firstCall[0], 'npm.cmd')
@ -641,12 +658,16 @@ t.timeout(250).describe('#update()', function() {
assert.notStrictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version) assert.notStrictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 0)
await app.update() let result = await new Promise(function(res, rej) {
app.once('updated', res)
app.update().catch(rej)
})
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.strictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version) assert.strictEqual(ctx.db.data.core.testapp.latestInstalled, assertVersion.version)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(result, assertVersion)
assert.ok(assertVersion.log) assert.ok(assertVersion.log)
assert.ok(stubWrite.callCount >= 4) assert.ok(stubWrite.callCount >= 4)
assert.strictEqual(assertVersion.installed, true) assert.strictEqual(assertVersion.installed, true)
@ -663,6 +684,7 @@ t.timeout(250).describe('#update()', function() {
const assertNewLink = 'THE last pain' const assertNewLink = 'THE last pain'
const assertNewFilename = 'The place of hope.7z' const assertNewFilename = 'The place of hope.7z'
const assertStable = 100 const assertStable = 100
const stubUpdated = stub()
const oldLog = 'The Smell of Sea\n' const oldLog = 'The Smell of Sea\n'
const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog, stable: assertStable } const assertVersion = { version: '123456789', link: 'httplinkhere', filename: 'test.7z', log: oldLog, stable: assertStable }
@ -672,13 +694,19 @@ t.timeout(250).describe('#update()', function() {
provider.getLatestVersion.resolves({ version: assertVersion.version, link: assertNewLink, filename: assertNewFilename }) provider.getLatestVersion.resolves({ version: assertVersion.version, link: assertNewLink, filename: assertNewFilename })
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
await app.update() app.once('updated', stubUpdated)
let result = await app.update()
assert.ok(stubUpdated.called)
assert.strictEqual(result, stubUpdated.firstCall[0])
assert.ok(assertVersion.log) assert.ok(assertVersion.log)
assert.ok(stubWrite.callCount >= 4) assert.ok(stubWrite.callCount >= 4)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1) assert.strictEqual(ctx.db.data.core.testapp.versions.length, 1)
assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion) assert.strictEqual(ctx.db.data.core.testapp.versions[0], assertVersion)
assert.strictEqual(result, assertVersion)
assert.strictEqual(assertVersion.stable, assertStable) assert.strictEqual(assertVersion.stable, assertStable)
assert.ok(assertVersion.log) assert.ok(assertVersion.log)
assert.ok(assertVersion.log.startsWith(oldLog)) assert.ok(assertVersion.log.startsWith(oldLog))
@ -694,13 +722,23 @@ t.timeout(250).describe('#update()', function() {
t.test('should do nothing if latestInstalled matches version', async function() { t.test('should do nothing if latestInstalled matches version', async function() {
const assertError = new Error('should not be seen') const assertError = new Error('should not be seen')
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
const stubUpdated = stub()
provider.getLatestVersion.resolves(assertVersion) provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError) provider.downloadVersion.rejects(assertError)
ctx.db.data.core.testapp.updater = '' ctx.db.data.core.testapp.updater = ''
ctx.db.data.core.testapp.latestInstalled = assertVersion.version ctx.db.data.core.testapp.latestInstalled = assertVersion.version
await app.update() app.once('updated', stubUpdated)
let result = await app.update()
assert.strictEqual(result, null)
assert.notOk(stubUpdated.called)
result = await app.update()
assert.notOk(stubUpdated.called)
assert.strictEqual(result, null)
assert.ok(stubWrite.callCount >= 1) assert.ok(stubWrite.callCount >= 1)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.notOk(provider.downloadVersion.called) assert.notOk(provider.downloadVersion.called)
@ -711,6 +749,7 @@ t.timeout(250).describe('#update()', function() {
t.test('should do nothing if installed version is found', async function() { t.test('should do nothing if installed version is found', async function() {
const assertError = new Error('should not be seen') const assertError = new Error('should not be seen')
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
const stubUpdated = stub()
provider.getLatestVersion.resolves(assertVersion) provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError) provider.downloadVersion.rejects(assertError)
ctx.db.data.core.testapp.updater = '' ctx.db.data.core.testapp.updater = ''
@ -720,8 +759,11 @@ t.timeout(250).describe('#update()', function() {
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', installed: true }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', installed: true })
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '333.333.333.333', installed: true }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '333.333.333.333', installed: true })
app.once('updated', stubUpdated)
await app.update() await app.update()
assert.notOk(stubUpdated.called)
assert.ok(stubWrite.callCount >= 1) assert.ok(stubWrite.callCount >= 1)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.notOk(provider.downloadVersion.called) assert.notOk(provider.downloadVersion.called)
@ -732,13 +774,18 @@ t.timeout(250).describe('#update()', function() {
t.test('should do nothing it exists and failtodownload is higher than 3', async function() { t.test('should do nothing it exists and failtodownload is higher than 3', async function() {
const assertError = new Error('should not be seen') const assertError = new Error('should not be seen')
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
const stubUpdated = stub()
provider.getLatestVersion.resolves(assertVersion) provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError) provider.downloadVersion.rejects(assertError)
ctx.db.data.core.testapp.updater = '' ctx.db.data.core.testapp.updater = ''
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtodownload: 4 })
await app.update() app.once('updated', stubUpdated)
let result = await app.update()
assert.notOk(stubUpdated.called)
assert.strictEqual(result, null)
assert.ok(stubWrite.callCount >= 1) assert.ok(stubWrite.callCount >= 1)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.notOk(provider.downloadVersion.called) assert.notOk(provider.downloadVersion.called)
@ -750,13 +797,18 @@ t.timeout(250).describe('#update()', function() {
t.test('should do nothing it exists and failtoinstall is higher than 3', async function() { t.test('should do nothing it exists and failtoinstall is higher than 3', async function() {
const assertError = new Error('should not be seen') const assertError = new Error('should not be seen')
const assertVersion = { version: '999.888.777.666', filename: 'test.7z' } const assertVersion = { version: '999.888.777.666', filename: 'test.7z' }
const stubUpdated = stub()
provider.getLatestVersion.resolves(assertVersion) provider.getLatestVersion.resolves(assertVersion)
provider.downloadVersion.rejects(assertError) provider.downloadVersion.rejects(assertError)
ctx.db.data.core.testapp.updater = '' ctx.db.data.core.testapp.updater = ''
ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 }) ctx.db.upsert(ctx.db.data.core.testapp.versions, { id: '999.888.777.666', version: '999.888.777.666', link: 'httplinkhere', filename: 'test.7z', failtoinstall: 4 })
await app.update() app.once('updated', stubUpdated)
let result = await app.update()
assert.notOk(stubUpdated.called)
assert.strictEqual(result, null)
assert.ok(stubWrite.callCount >= 1) assert.ok(stubWrite.callCount >= 1)
assert.strictEqual(app.updating, false) assert.strictEqual(app.updating, false)
assert.notOk(provider.downloadVersion.called) assert.notOk(provider.downloadVersion.called)

View file

@ -3,20 +3,22 @@ import fs from 'fs/promises'
import http from 'http' import http from 'http'
import Util from '../core/util.mjs' import Util from '../core/util.mjs'
import { request } from '../core/client.mjs' import { request } from '../core/client.mjs'
import { setTimeout } from 'timers/promises'
import { prettyPrintMessage } from './helpers.mjs'
import { pipeline } from 'stream'
const util = new Util(import.meta.url) const util = new Util(import.meta.url)
const port = 61412 const port = 61412
const defaultHandler = function(req, res) {
res.statusCode = 200
res.end('{"a":1}');
}
let server = null
let prefix = `http://localhost:${port}/`
let handler = defaultHandler
let files = []
let logs = []
t.describe('', function() { t.timeout(5000).describe('', function() {
let server = null
let prefix = `http://localhost:${port}/`
let files = []
let logs = []
let versions = []
let requests = []
let processor
t.before(function(cb) { t.before(function(cb) {
server = http.createServer(function(req, res) { server = http.createServer(function(req, res) {
req.on('error', function(err) { req.on('error', function(err) {
@ -25,32 +27,87 @@ t.describe('', function() {
res.on('error', function(err) { res.on('error', function(err) {
console.log('error', err) console.log('error', err)
}) })
handler(req, res)
requests.push(req.url)
console.log(req.url)
if (req.url === '/releases') {
res.statusCode = 200
let output = versions.map(x => {
return {
name: x[0],
body: x[1],
assets: [{
name: x[2],
browser_download_url: prefix + 'files/' + x[2]
}]
}
}) })
res.end(JSON.stringify(output));
return
} else if (req.url.startsWith('/files')) {
let filename = req.url.substring(req.url.lastIndexOf('/') + 1)
return fs.open(util.getPathFromRoot('./' + filename))
.then(function(file) {
pipeline(file.createReadStream(), res, function(err) {
if (err) {
console.log(err)
res.statusCode = 404
res.end(JSON.stringify({ error: 'unknown url' }))
}
})
}).catch(function(err) {
console.log(err)
res.statusCode = 404
res.end(JSON.stringify({ error: 'unknown url' }))
})
}
res.statusCode = 404
res.end(JSON.stringify({ error: 'unknown url' }))
})
fs.rm(util.getPathFromRoot('./db.json'), { force: true }).then(function() {
server.listen(port, cb) server.listen(port, cb)
}, cb)
}) })
t.after(function() { t.after(function() {
return Promise.resolve()
return Promise.all(files.map(function(file) { return Promise.all(files.map(function(file) {
return fs.rm(file, { force: true, recursive: true }) return fs.rm(file, { force: true, recursive: true })
})) }))
.then(function() {
if (processor) {
processor.kill()
}
})
}) })
const stable_version_1 = ` const version_1_stable = `
export function start(http, port, ctx) { export function start(http, port, ctx) {
const server = http.createServer(function (req, res) { const server = http.createServer(function (req, res) {
res.writeHead(200); res.writeHead(200);
res.end(JSON.stringify({ version: 'stable_version_1' })) res.end(JSON.stringify({ version: 'v1' }))
}) })
return server.listenAsync(port, '0.0.0.0') return server.listenAsync(port, '0.0.0.0')
.then(() => { .then(() => {
ctx.log.info(\`Server is listening on \${port} serving stable_version_1\`) ctx.log.info({ port: port, listening: true }, \`Server is listening on \${port} serving v1\`)
}) })
} }
` `
const version_2_nolisten = `
export function start(http, port, ctx) {
}
`
const version_3_crashing = `
export function start(http, port, ctx) {
}
`
function file(relative) { function file(relative) {
let file = util.getPathFromRoot(relative) let file = util.getPathFromRoot(relative)
files.push(file) files.push(file)
@ -58,13 +115,245 @@ t.describe('', function() {
} }
function log(message) { function log(message) {
logs.push(message) let lines = message.split('\n')
console.log(message) for (let line of lines) {
if (!line.trim()) continue
logs.push(line)
}
}
function parseLine(line) {
if (line[0] === '{') {
return JSON.parse(line)
}
return {
msg: line
}
}
let logIndex = 0
function catchupLog() {
if (logs.length > logIndex) {
for (; logIndex < logs.length; logIndex++) {
prettyPrintMessage(logs[logIndex])
}
}
}
let logWaitIndex = 0
function hasLogLine(regMatch) {
if (logs.length > logWaitIndex) {
for (; logWaitIndex < logs.length; logWaitIndex++) {
if (typeof(regMatch) === 'function') {
let res = regMatch(parseLine(logs[logWaitIndex]))
if (res) return true
}
else if (logs[logWaitIndex].match(regMatch)) {
return true
}
}
}
return false
}
function findInLogs(regMatch) {
for (let i = 0; i < logs.length; i++) {
if (typeof(regMatch) === 'function') {
let res = regMatch(parseLine(logs[i]))
if (res) return true
}
else if (logs[i].match(regMatch)) {
return true
}
}
}
async function waitUntilListening() {
let listeningLine = null
while (processor.exitCode == null
&& !hasLogLine((rec) => { listeningLine = rec; return rec.listening && rec.port })) {
catchupLog()
await setTimeout(10)
}
catchupLog()
return listeningLine
}
async function waitUntilClosed(listening) {
while (true) {
catchupLog()
try {
await request({}, `http://localhost:${listening.port}/`)
} catch (err) {
break
}
await setTimeout(25)
}
catchupLog()
logs.splice(0, logs.length); logIndex = 0; logWaitIndex = 0; console.log('\n-------\n')
}
function startRunner() {
return util.runCommandBackground('node', ['runner.mjs'], util.getPathFromRoot('./'), log)
} }
t.test('should be fully operational', async function() { t.test('should be fully operational', async function() {
console.log()
let index = file('./index.mjs') let index = file('./index.mjs')
await fs.writeFile(index, stable_version_1) await fs.writeFile(index, version_1_stable)
await util.runCommand(util.get7zipExecutable(), ['a', file('./v1.7z'), index], util.getPathFromRoot('./testapp/'), log) await util.runCommand(util.get7zipExecutable(), ['a', file('./v1-sc.7z'), index], util.getPathFromRoot('./testapp'))
processor = startRunner()
while (processor.exitCode == null) {
catchupLog()
await setTimeout(10)
}
catchupLog()
let secondLast = parseLine(logs[logs.length - 2])
let last = parseLine(logs[logs.length - 1])
assert.match(secondLast.msg, /creating/i)
assert.match(secondLast.msg, /application/i)
assert.match(secondLast.msg, /testapp/i)
assert.match(secondLast.msg, /0 releases/i)
assert.match(last.err.message, /none/i)
assert.match(last.err.message, /successful/i)
// Reset our log
logs.splice(0, logs.length); logIndex = 0; logWaitIndex = 0; console.log('\n-------\n')
const assertNameVersion1 = 'v1_ok'
file(`./testapp/${assertNameVersion1}`)
versions.splice(0, 0, [assertNameVersion1, 'ok version', 'v1-sc.7z'])
processor = startRunner()
let listening = await waitUntilListening()
let checkListening = await request({}, `http://localhost:${listening.port}/`)
assert.strictEqual(checkListening.body.version, 'v1')
while (!hasLogLine(/core is running/)) {
await setTimeout(10)
}
let db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core.testapp.active, assertNameVersion1)
assert.strictEqual(db.core.testapp.versions.length, 1)
assert.strictEqual(db.core.testapp.versions[0].stable, 1)
assert.strictEqual(db.core.testapp.versions[0].installed, true)
// Create our second version
await fs.writeFile(index, version_2_nolisten)
await util.runCommand(util.get7zipExecutable(), ['a', file('./v2-sc.7z'), index], util.getPathFromRoot('./testapp'))
const assertNameVersion2 = 'v2_nolisten'
file(`./testapp/${assertNameVersion2}`)
versions.splice(0, 0, [assertNameVersion2, 'no listen version', 'v2-sc.7z'])
requests.splice(0, requests.length)
assert.strictEqual(requests.length, 0)
// wait a second for it to trigger an update
await setTimeout(500)
while (requests.length < 2) {
catchupLog()
await setTimeout(25)
}
while (!hasLogLine(/Error starting v2_nolisten/)) {
catchupLog()
await setTimeout(10)
}
while (!hasLogLine(/Attempting to run version v1_ok/)) {
catchupLog()
await setTimeout(10)
}
while (!hasLogLine(/Server is listening on 31313 serving v1/)) {
catchupLog()
await setTimeout(10)
}
catchupLog()
checkListening = await request({}, `http://localhost:${listening.port}/`)
assert.strictEqual(checkListening.body.version, 'v1')
await setTimeout(10)
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core.testapp.active, assertNameVersion1)
assert.strictEqual(db.core.testapp.versions.length, 2)
assert.strictEqual(db.core.testapp.versions[0].stable, -1)
assert.strictEqual(db.core.testapp.versions[0].installed, true)
assert.strictEqual(db.core.testapp.versions[1].stable, 1)
assert.strictEqual(db.core.testapp.versions[1].installed, true)
processor.kill()
// Wait for ports to be marked as closed
await waitUntilClosed()
processor = startRunner()
listening = await waitUntilListening()
assert.ok(listening)
checkListening = await request({}, `http://localhost:${listening.port}/`)
assert.strictEqual(checkListening.body.version, 'v1')
while (!hasLogLine(/core is running/)) {
await setTimeout(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core.testapp.active, assertNameVersion1)
assert.strictEqual(db.core.testapp.versions.length, 2)
assert.strictEqual(db.core.testapp.versions[0].stable, -2)
assert.strictEqual(db.core.testapp.versions[1].stable, 1)
assert.ok(findInLogs(/Attempting to run version v2_nolisten/))
assert.ok(findInLogs(/Error starting v2_nolisten/))
processor.kill()
await waitUntilClosed()
processor = startRunner()
listening = await waitUntilListening()
assert.ok(listening)
checkListening = await request({}, `http://localhost:${listening.port}/`)
assert.strictEqual(checkListening.body.version, 'v1')
while (!hasLogLine(/core is running/)) {
await setTimeout(10)
}
db = JSON.parse(await fs.readFile(util.getPathFromRoot('./db.json')))
assert.strictEqual(db.core.testapp.active, assertNameVersion1)
assert.strictEqual(db.core.testapp.versions.length, 2)
assert.strictEqual(db.core.testapp.versions[0].stable, -2)
assert.strictEqual(db.core.testapp.versions[1].stable, 1)
assert.notOk(findInLogs(/Attempting to run version v2_nolisten/))
assert.notOk(findInLogs(/Error starting v2_nolisten/))
while (processor.exitCode == null) {
catchupLog()
await setTimeout(10)
}
catchupLog()
}) })
}) })

View file

@ -267,6 +267,7 @@ t.describe('#init()', function() {
} }
t.beforeEach(function() { t.beforeEach(function() {
log.error.reset()
core = new Core(db, util, log, function() {}) core = new Core(db, util, log, function() {})
core.util = fakeUtil = { core.util = fakeUtil = {
verifyConfig: stub(), verifyConfig: stub(),
@ -312,7 +313,10 @@ t.describe('#init()', function() {
fakeUtil.getAppNames.returns([assertAppName]) fakeUtil.getAppNames.returns([assertAppName])
let err = await assert.isRejected(core.init()) let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(err.message, /successful/i)
assert.match(err.message, /none/i)
assert.strictEqual(fakeProvider.firstCall[0], assertConfig[assertAppName]) assert.strictEqual(fakeProvider.firstCall[0], assertConfig[assertAppName])
}) })
@ -330,7 +334,9 @@ t.describe('#init()', function() {
fakeUtil.getAppNames.returns([assertAppName]) fakeUtil.getAppNames.returns([assertAppName])
let err = await assert.isRejected(core.init()) let err = await assert.isRejected(core.init())
assert.strictEqual(err, assertError) assert.strictEqual(log.error.firstCall[0], assertError)
assert.match(err.message, /successful/i)
assert.match(err.message, /none/i)
assert.strictEqual(fakeProviderConfig.firstCall[0], assertConfig[assertAppName]) assert.strictEqual(fakeProviderConfig.firstCall[0], assertConfig[assertAppName])
}) })
@ -433,6 +439,7 @@ t.describe('#run()', function() {
let testAppOneName let testAppOneName
let testAppTwoName let testAppTwoName
let stubRunApplication let stubRunApplication
let stubOnOrOnce
let stubWrite let stubWrite
t.beforeEach(function() { t.beforeEach(function() {
@ -453,9 +460,19 @@ t.describe('#run()', function() {
log.warn.reset() log.warn.reset()
log.error.reset() log.error.reset()
for (let name of [testAppOneName, testAppTwoName]) { for (let name of [testAppOneName, testAppTwoName]) {
let onOrOnce = stub()
let app = { let app = {
name: name, name: name,
fresh: false, fresh: false,
on: onOrOnce,
once: onOrOnce,
ctx: {
log: {
info: stub(),
warn: stub(),
error: stub()
},
},
update: stub().resolves(), update: stub().resolves(),
startAutoupdater: stub(), startAutoupdater: stub(),
} }
@ -472,20 +489,81 @@ t.describe('#run()', function() {
await core.run() await core.run()
assert.strictEqual(log.error.callCount, 2) assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(log.error.firstCall[0], assertFirstError) assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.match(log.error.firstCall[1], new RegExp(testAppOneName)) assert.match(core.applications[0].ctx.log.error.firstCall[1], /updat/)
assert.match(log.error.firstCall[1], /updat/) assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.match(log.error.firstCall[1], new RegExp(assertFirstError.message)) assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.strictEqual(log.error.secondCall[0], assertSecondError) assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(log.error.secondCall[1], new RegExp(testAppTwoName)) assert.match(core.applications[1].ctx.log.error.firstCall[1], /updat/)
assert.match(log.error.secondCall[1], /updat/) assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
assert.match(log.error.secondCall[1], new RegExp(assertSecondError.message))
}) })
t.test('app.on updated should be hooked eventually and should call runApplication', async function() {
assert.notOk(core.applicationMap.get(testAppOneName).once.called)
assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
assert.notOk(core.applicationMap.get(testAppTwoName).on.called)
const assertFirstError = new Error('Manatsu')
const assertSecondError = new Error('no Photograph')
core.applicationMap.get(testAppOneName).update.returnWith(function() {
assert.notOk(core.applicationMap.get(testAppOneName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
return Promise.reject(assertFirstError)
})
core.applicationMap.get(testAppTwoName).update.returnWith(function() {
assert.notOk(core.applicationMap.get(testAppTwoName).once.called)
assert.notOk(core.applicationMap.get(testAppOneName).on.called)
return Promise.reject(assertSecondError)
})
core.runApplication.returnWith(function(app) {
assert.notOk(app.once.called)
assert.notOk(app.on.called)
return Promise.resolve()
})
await core.run()
assert.ok(core.applicationMap.get(testAppOneName).on.called)
assert.ok(core.applicationMap.get(testAppTwoName).on.called)
assert.strictEqual(core.applicationMap.get(testAppOneName).on.firstCall[0], 'updated')
assert.strictEqual(core.applicationMap.get(testAppTwoName).on.firstCall[0], 'updated')
assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.match(core.applications[0].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(core.applications[1].ctx.log.error.firstCall[1], /updat/)
assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
assert.strictEqual(stubRunApplication.callCount, 2)
assert.strictEqual(stubRunApplication.getCallN(1)[0], core.applications[0])
assert.strictEqual(stubRunApplication.getCallN(2)[0], core.applications[1])
core.runApplication.resolves()
core.applications[0].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 3)
assert.strictEqual(stubRunApplication.getCallN(3)[0], core.applications[0])
core.applications[0].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 4)
assert.strictEqual(stubRunApplication.getCallN(4)[0], core.applications[0])
core.applications[1].on.firstCall[1]()
assert.strictEqual(stubRunApplication.callCount, 5)
assert.strictEqual(stubRunApplication.getCallN(5)[0], core.applications[1])
})
t.test('should call startAutoupdater on all applications', async function() { t.test('should call startAutoupdater on all applications', async function() {
stubRunApplication.rejects(new Error('not seen')) stubRunApplication.rejects(new Error('not seen'))
assert.notOk(core.applications[0].startAutoupdater.called)
assert.notOk(core.applications[1].startAutoupdater.called)
await assert.isRejected(core.run()) await assert.isRejected(core.run())
@ -493,6 +571,19 @@ t.describe('#run()', function() {
assert.ok(core.applications[1].startAutoupdater.called) assert.ok(core.applications[1].startAutoupdater.called)
}) })
t.test('should be safe to call multiple times', async function() {
await core.run()
assert.strictEqual(stubRunApplication.callCount, 2)
await core.run()
await core.run()
await core.run()
await core.run()
assert.strictEqual(stubRunApplication.callCount, 2)
})
t.test('should call runApplication on all applications', async function() { t.test('should call runApplication on all applications', async function() {
const assertFirstError = new Error('Manatsu') const assertFirstError = new Error('Manatsu')
const assertSecondError = new Error('no Photograph') const assertSecondError = new Error('no Photograph')
@ -503,17 +594,14 @@ t.describe('#run()', function() {
await assert.isRejected(core.run()) await assert.isRejected(core.run())
assert.strictEqual(log.error.callCount, 2) assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(stubRunApplication.firstCall[0], core.applications[0]) assert.strictEqual(core.applications[0].ctx.log.error.firstCall[0], assertFirstError)
assert.strictEqual(stubRunApplication.secondCall[0], core.applications[1]) assert.match(core.applications[0].ctx.log.error.firstCall[1], /run/)
assert.strictEqual(log.error.firstCall[0], assertFirstError) assert.match(core.applications[0].ctx.log.error.firstCall[1], new RegExp(assertFirstError.message))
assert.match(log.error.firstCall[1], new RegExp(testAppOneName)) assert.strictEqual(core.applications[1].ctx.log.error.callCount, 1)
assert.match(log.error.firstCall[1], /run/) assert.strictEqual(core.applications[1].ctx.log.error.firstCall[0], assertSecondError)
assert.match(log.error.firstCall[1], new RegExp(assertFirstError.message)) assert.match(core.applications[1].ctx.log.error.firstCall[1], /run/)
assert.strictEqual(log.error.secondCall[0], assertSecondError) assert.match(core.applications[1].ctx.log.error.firstCall[1], new RegExp(assertSecondError.message))
assert.match(log.error.secondCall[1], new RegExp(testAppTwoName))
assert.match(log.error.secondCall[1], /run/)
assert.match(log.error.secondCall[1], new RegExp(assertSecondError.message))
}) })
t.test('should throw if all applications fail', async function() { t.test('should throw if all applications fail', async function() {
@ -530,7 +618,8 @@ t.describe('#run()', function() {
await core.run() await core.run()
assert.strictEqual(log.error.callCount, 1) assert.strictEqual(core.applications[0].ctx.log.error.callCount, 1)
assert.strictEqual(core.applications[1].ctx.log.error.callCount, 0)
}) })
t.test('should not throw if all applications are running', async function() { t.test('should not throw if all applications are running', async function() {
@ -561,6 +650,13 @@ t.describe('#runApplication()', function() {
testApp = { testApp = {
name: testAppName, name: testAppName,
fresh: false, fresh: false,
ctx: {
log: {
info: stub(),
warn: stub(),
error: stub(),
},
},
closeServer: stub(), closeServer: stub(),
runVersion: stub(), runVersion: stub(),
} }
@ -597,12 +693,12 @@ t.describe('#runApplication()', function() {
let err = await assert.isRejected(core.runApplication(testApp)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(log.error.firstCall[0], assertError) assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertVersion))
assert.match(log.error.firstCall[1], new RegExp(assertVersion)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], assertVersion) assert.strict(testApp.runVersion.firstCall[0], assertVersion)
assert.match(err.message, /no/i) assert.match(err.message, /no/i)
assert.match(err.message, /found/i) assert.match(err.message, /found/i)
@ -630,13 +726,13 @@ t.describe('#runApplication()', function() {
let err = await assert.isRejected(core.runApplication(testApp)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(log.error.callCount, 1) assert.strictEqual(testApp.ctx.log.error.callCount, 1)
assert.strictEqual(log.error.firstCall[0], assertError) assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('42'))
assert.match(log.error.firstCall[1], new RegExp('42')) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '42') assert.strict(testApp.runVersion.firstCall[0], '42')
}) })
@ -663,13 +759,13 @@ t.describe('#runApplication()', function() {
let err = await assert.isRejected(core.runApplication(testApp)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(log.error.callCount, 2) assert.strictEqual(testApp.ctx.log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError) assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('31'))
assert.match(log.error.firstCall[1], new RegExp('31')) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.firstCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '31') assert.strict(testApp.runVersion.firstCall[0], '31')
assert.strict(testApp.runVersion.firstCall[0], '32') assert.strict(testApp.runVersion.firstCall[0], '32')
}) })
@ -698,17 +794,16 @@ t.describe('#runApplication()', function() {
let err = await assert.isRejected(core.runApplication(testApp)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(log.error.callCount, 2) assert.strictEqual(testApp.ctx.log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError) assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('30'))
assert.match(log.error.firstCall[1], new RegExp('30')) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.firstCall[1], new RegExp(assertError.message)) assert.strictEqual(testApp.ctx.log.error.secondCall[0], assertError)
assert.strictEqual(log.error.secondCall[0], assertError) assert.match(testApp.ctx.log.error.secondCall[1], new RegExp('32'))
assert.match(log.error.secondCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.secondCall[1], new RegExp(assertError.message))
assert.match(log.error.secondCall[1], new RegExp('32'))
assert.match(log.error.secondCall[1], new RegExp(assertError.message))
assert.strict(testApp.runVersion.firstCall[0], '30') assert.strict(testApp.runVersion.firstCall[0], '30')
assert.strict(testApp.runVersion.firstCall[0], '32') assert.strict(testApp.runVersion.firstCall[0], '32')
}) })
@ -736,16 +831,15 @@ t.describe('#runApplication()', function() {
let err = await assert.isRejected(core.runApplication(testApp)) let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError) assert.notStrictEqual(err, assertError)
assert.ok(log.error.called) assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(log.error.callCount, 2) assert.strictEqual(testApp.ctx.log.error.callCount, 2)
assert.strictEqual(log.error.firstCall[0], assertError) assert.strictEqual(testApp.ctx.log.error.firstCall[0], assertError)
assert.match(log.error.firstCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp('30'))
assert.match(log.error.firstCall[1], new RegExp('30')) assert.match(testApp.ctx.log.error.firstCall[1], new RegExp(assertError.message))
assert.match(log.error.firstCall[1], new RegExp(assertError.message)) assert.match(testApp.ctx.log.error.secondCall[1], new RegExp('31'))
assert.match(log.error.secondCall[1], new RegExp(testAppName)) assert.match(testApp.ctx.log.error.secondCall[1], new RegExp(assertError.message))
assert.match(log.error.secondCall[1], new RegExp('31'))
assert.match(log.error.secondCall[1], new RegExp(assertError.message))
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1) assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, -2) assert.strictEqual(db.data.core[testAppName].versions[1].stable, -2)
@ -753,6 +847,47 @@ t.describe('#runApplication()', function() {
assert.ok(stubWrite.callCount, 2) assert.ok(stubWrite.callCount, 2)
}) })
t.test('should always go to -1 minimum on crash', async function() {
const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError)
testApp.fresh = false
db.data.core[testAppName].versions.push({
id: '28',
version: 'v28',
installed: true,
stable: 5,
}, {
id: '29',
version: 'v29',
installed: true,
stable: 1,
}, {
id: '30',
version: 'v30',
installed: true,
stable: 0,
}, {
id: '31',
version: 'v31',
installed: true,
stable: -1,
})
let err = await assert.isRejected(core.runApplication(testApp))
assert.notStrictEqual(err, assertError)
assert.notOk(log.error.called)
assert.ok(testApp.ctx.log.error.called)
assert.strictEqual(testApp.ctx.log.error.callCount, 3)
assert.strictEqual(db.data.core[testAppName].versions[0].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, -1)
assert.strictEqual(db.data.core[testAppName].versions[3].stable, -1)
assert.ok(stubWrite.callCount, 2)
})
t.test('should throw if no stable version is found', async function() { t.test('should throw if no stable version is found', async function() {
const assertError = new Error('Daikichi to Rin') const assertError = new Error('Daikichi to Rin')
testApp.runVersion.rejects(assertError) testApp.runVersion.rejects(assertError)
@ -805,7 +940,7 @@ t.describe('#runApplication()', function() {
id: '70', id: '70',
version: 'v70', version: 'v70',
installed: true, installed: true,
stable: 0, stable: 100,
}, { }, {
id: '71', id: '71',
version: 'v71', version: 'v71',
@ -820,5 +955,51 @@ t.describe('#runApplication()', function() {
await core.runApplication(testApp) await core.runApplication(testApp)
assert.notOk(log.error.called) assert.notOk(log.error.called)
assert.notOk(testApp.ctx.log.error.called)
assert.strictEqual(testApp.runVersion.callCount, 1)
assert.strictEqual(testApp.runVersion.firstCall[0], 'v70')
assert.strictEqual(db.data.core[testAppName].versions[0].stable, 1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, 0)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, 0)
assert.ok(stubWrite.called)
})
t.test('should succeed if running a minus one on fresh', async function() {
const assertError = new Error('Daikichi to Rin')
let count = 0
testApp.fresh = true
testApp.runVersion.returnWith(function() {
if (count > 0) return Promise.reject(assertError)
count++
return Promise.resolve()
})
testApp.fresh = true
db.data.core[testAppName].versions.push({
id: '70',
version: 'v70',
installed: true,
stable: -1,
}, {
id: '71',
version: 'v71',
installed: true,
stable: 0,
}, {
id: '72',
version: 'v72',
installed: true,
stable: 0,
})
await core.runApplication(testApp)
assert.notOk(log.error.called)
assert.notOk(testApp.ctx.log.error.called)
assert.strictEqual(testApp.runVersion.callCount, 1)
assert.strictEqual(testApp.runVersion.firstCall[0], 'v70')
assert.strictEqual(db.data.core[testAppName].versions[0].stable, 1)
assert.strictEqual(db.data.core[testAppName].versions[1].stable, 0)
assert.strictEqual(db.data.core[testAppName].versions[2].stable, 0)
assert.ok(stubWrite.called)
}) })
}) })

View file

@ -27,3 +27,64 @@ export function createFakeContext(config = { }, util = new Util(import.meta.url)
} }
}) })
} }
var colors = {
'bold' : [1, 22],
'italic' : [3, 23],
'underline' : [4, 24],
'inverse' : [7, 27],
'white' : [37, 39],
'grey' : [90, 39],
'black' : [30, 39],
'blue' : [34, 39],
'cyan' : [36, 39],
'green' : [32, 39],
'magenta' : [35, 39],
'red' : [31, 39],
'yellow' : [33, 39]
};
let levels = {
10: 'TRACE',
20: 'DEBUG',
30: 'INFO',
40: 'WARN',
50: 'ERROR',
60: 'FATAL',
}
var levelcolor = {
10: 'white', // TRACE
20: 'yellow', // DEBUG
30: 'cyan', // INFO
40: 'magenta', // WARN
50: 'red', // ERROR
60: 'inverse', // FATAL
};
function style(str, color) {
if (!str)
return '';
var codes = colors[color];
if (codes) {
return '\x1B[' + codes[0] + 'm' + str +
'\x1B[' + codes[1] + 'm';
} else {
return str;
}
}
export function prettyPrintMessage(line) {
if (line[0] === '{') {
try {
let rec = JSON.parse(line)
console.log(`[${rec.time.substr(11).replace('Z', '')}] ${style(levels[rec.level], levelcolor[rec.level])}: ${rec.name}: ${style(rec.msg, 'cyan')}`)
if (rec.err && rec.err.message && rec.err.stack) {
let err = new Error(rec.err.message)
err.stack = rec.err.stack
console.log(err)
}
return
} catch (err){ console.log(err)}
}
console.log(line)
}

View file

@ -3,7 +3,7 @@ import Util from '../../core/util.mjs'
import fs from 'fs/promises' import fs from 'fs/promises'
import GitProvider from '../../core/providers/git.mjs' import GitProvider from '../../core/providers/git.mjs'
t.describe('test', function() { t.skip().describe('test', function() {
t.after(function() { t.after(function() {
return fs.rm('./test/providers/file.7z') return fs.rm('./test/providers/file.7z')
.catch(function() { }) .catch(function() { })

View file

@ -1,47 +1,28 @@
import fs from 'fs' import fs from 'fs'
import Core from '../core/core.mjs' import { runner } from '../core/runner.mjs'
import GetDB from '../core/db.mjs'
import getLog from '../core/log.mjs'
import Util from '../core/util.mjs'
const name = 'service-core-runner'
const util = new Util(import.meta.url)
const log = getLog(name)
const config = {
name: name,
testapp: {
port: 31313,
provider: 'static',
}
}
try { try {
fs.rmSync(util.getPathFromRoot('./db.json')) fs.rmSync(util.getPathFromRoot('./db.json'))
} catch {} } catch {}
GetDB(config, log, util.getPathFromRoot('./db.json')).then(async function(db) { runner(import.meta.url, {
const core = new Core(db, util, log, function() {}) name: 'test-runner',
testapp: {
console.log(db.data) port: 31313,
provider: 'git',
await core.init() url: 'http://localhost:61412/releases',
updateEvery: 0.014,
console.log(db.data) heartbeatTimeout: 500,
heartbeatAttempts: 2,
db.data.core.testapp.versions.push({ heartbeatAttemptsWait: 250,
id: 'ok', }
version: 'ok', }, 'db.json')
stable: 0,
installed: true,
})
await core.run()
})
.then( .then(
function() { function(core) {
log.info('core is running') core.log.info('core is running')
}, },
function(err) { function(err) {
log.error(err, 'Error starting runner') runner.log.error(err, 'Error starting runner')
process.exit(1) process.exit(1)
}) }
)

View file

@ -1,17 +0,0 @@
export function start(http, port, ctx) {
const server = http.createServer(function (req, res) {
res.writeHead(200);
res.end(JSON.stringify({ version: 'exampleindex' }))
})
return new Promise(function(res, rej) {
server.listen(port || 4000, '0.0.0.0', function(err) {
if (err) {
return rej(err)
}
ctx.log.event.info(`Server is listening on ${port} serving exampleindex`)
ctx.log.info(`Server is listening on ${port} serving exampleindex`)
res()
})
})
}