Skip to content

Commit

Permalink
fix: make sde log wait for DAT file to be fully written and improve…
Browse files Browse the repository at this point in the history
… error handling (#123)

Fixes #122
  • Loading branch information
chrispcampbell authored Sep 28, 2021
1 parent d92343e commit 34f25f8
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 69 deletions.
12 changes: 4 additions & 8 deletions src/Helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,7 @@ export let ensureDir = (dir, defaultDir, modelDirname) => {
return dirName
}
export let fileExists = pathname => {
let exists = fs.existsSync(pathname)
if (!exists) {
console.error(`${pathname} not found`)
}
return exists
return fs.existsSync(pathname)
}
export let linkCSourceFiles = (modelDirname, buildDirname) => {
let cDirname = path.join(new URL('.', import.meta.url).pathname, 'c')
Expand Down Expand Up @@ -255,9 +251,9 @@ export let execCmd = cmd => {
// Run a command line silently in the "sh" shell. Print error output on error.
let exitCode = 0
let result = sh.exec(cmd, { silent: true })
if (sh.error()) {
console.log(result.stderr)
exitCode = 1
if (result.code !== 0) {
console.error(result.stderr)
exitCode = result.code
}
return exitCode
}
Expand Down
12 changes: 9 additions & 3 deletions src/sde-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ export let handler = argv => {
build(argv.model, argv)
}
export let build = async (model, opts) => {
opts.genc = true
await generate(model, opts)
compile(model, opts)
try {
opts.genc = true
await generate(model, opts)
compile(model, opts)
} catch (e) {
// Exit with a non-zero error code if any step failed
console.error(`ERROR: ${e.message}\n`)
process.exit(1)
}
}

export default {
Expand Down
77 changes: 41 additions & 36 deletions src/sde-compare.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,50 +28,55 @@ export let handler = argv => {
compare(argv.vensimlog, argv.sdelog, argv)
}
export let compare = async (vensimfile, sdefile, opts) => {
if (!fileExists(vensimfile)) {
throw new Error(`Vensim DAT file not found: ${vensimfile}`)
}
if (!fileExists(sdefile)) {
throw new Error(`SDEverywhere DAT file not found: ${sdefile}`)
}

let vensimLog = await readDat(vensimfile)
let sdeLog = await readDat(sdefile)

if (vensimLog.size === 0) {
throw new Error(`Vensim DAT file did not contain data: ${vensimfile}`)
}
if (sdeLog.size === 0) {
throw new Error(`SDEverywhere DAT file did not contain data: ${sdefile}`)
}

if (opts.precision) {
ε = opts.precision
}
if (fileExists(vensimfile) && fileExists(sdefile)) {
let vensimLog = await readDat(vensimfile)
let sdeLog = await readDat(sdefile)
if (vensimLog.size > 0 && sdeLog.size > 0) {
let noDATDifference = true
for (let varName of vensimLog.keys()) {
let sdeValues = sdeLog.get(varName)
// Ignore variables that are not found in the SDE log file.
if (sdeValues && (!opts.name || varName === opts.name)) {
let vensimValue = undefined
let vensimValues = vensimLog.get(varName)
// Filter on time t, the key in the values list.
for (let t of vensimValues.keys()) {
if (!opts.times || R.find(time => isEqual(time, t), opts.times)) {
// In Vensim log files, const vars only have one value at the initial time.
if (vensimValues.size > 1 || !vensimValue) {
vensimValue = vensimValues.get(t)
}
let sdeValue = sdeValues.get(t)
let diff = difference(sdeValue, vensimValue)
if (diff > ε) {
let diffPct = (diff * 100).toFixed(6)
pr(`${varName} time=${t.toFixed(2)} vensim=${vensimValue} sde=${sdeValue} diff=${diffPct}%`)
noDATDifference = false
}
}

let noDATDifference = true
for (let varName of vensimLog.keys()) {
let sdeValues = sdeLog.get(varName)
// Ignore variables that are not found in the SDE log file.
if (sdeValues && (!opts.name || varName === opts.name)) {
let vensimValue = undefined
let vensimValues = vensimLog.get(varName)
// Filter on time t, the key in the values list.
for (let t of vensimValues.keys()) {
if (!opts.times || R.find(time => isEqual(time, t), opts.times)) {
// In Vensim log files, const vars only have one value at the initial time.
if (vensimValues.size > 1 || !vensimValue) {
vensimValue = vensimValues.get(t)
}
let sdeValue = sdeValues.get(t)
let diff = difference(sdeValue, vensimValue)
if (diff > ε) {
let diffPct = (diff * 100).toFixed(6)
pr(`${varName} time=${t.toFixed(2)} vensim=${vensimValue} sde=${sdeValue} diff=${diffPct}%`)
noDATDifference = false
}
}
}
if (noDATDifference) {
pr(`Data were the same for ${vensimfile} and ${sdefile}`)
}
} else {
if (vensimLog.size === 0) {
console.error(`${vensimfile} did not contain Vensim data`)
}
if (sdeLog.size === 0) {
console.error(`${sdefile} did not contain Vensim data`)
}
}
}
if (noDATDifference) {
pr(`Data were the same for ${vensimfile} and ${sdefile}`)
}
}
let isZero = value => {
return Math.abs(value) < ε
Expand Down
2 changes: 1 addition & 1 deletion src/sde-compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export let handler = argv => {
compile(argv.model, argv)
}
export let compile = (model, opts) => {
let { modelDirname, modelName, modelPathname } = modelPathProps(model)
let { modelDirname, modelName } = modelPathProps(model)
// Ensure the build directory exists.
let buildDirname = buildDir(opts.builddir, modelDirname)
// Link SDEverywhere C source files into the build directory.
Expand Down
2 changes: 1 addition & 1 deletion src/sde-exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export let handler = argv => {
exec(argv.model, argv)
}
export let exec = (model, opts) => {
let { modelDirname, modelName, modelPathname } = modelPathProps(model)
let { modelDirname, modelName } = modelPathProps(model)
// Ensure the build and output directories exist.
let buildDirname = buildDir(opts.builddir, modelDirname)
let outputDirname = outputDir(opts.outfile, modelDirname)
Expand Down
8 changes: 7 additions & 1 deletion src/sde-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ let exportDat = async (logPathname, datPathname) => {
let stream = fs.createWriteStream(datPathname, 'utf8')
let iVarKey = 0
let finished = () => {}
stream.on('finish', () => {
// Resolve the promise after `stream.end()` has been called and all data has been flushed
// to the output file
finished()
})
let writeContinuation = () => {
while (iVarKey < varKeys.length) {
let varKey = varKeys[iVarKey++]
Expand All @@ -74,8 +79,9 @@ let exportDat = async (logPathname, datPathname) => {
}
}
}
// End the stream after the last line is emitted; this will trigger a `finish` event after
// all data has been flushed to the output file
stream.end()
finished()
}
return new Promise(resolve => {
// Start the write on the next event loop tick so we can return immediately.
Expand Down
44 changes: 25 additions & 19 deletions src/sde-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,32 @@ export let handler = argv => {
test(argv.model, argv)
}
export let test = async (model, opts) => {
// Run the model and save output to an SDE log file.
let { modelDirname, modelName } = modelPathProps(model)
let logPathname
if (opts.outfile) {
logPathname = opts.outfile
} else {
let outputDirname = outputDir(opts.outfile, modelDirname)
logPathname = path.join(outputDirname, `${modelName}.txt`)
opts.outfile = logPathname
try {
// Run the model and save output to an SDE log file.
let { modelDirname, modelName } = modelPathProps(model)
let logPathname
if (opts.outfile) {
logPathname = opts.outfile
} else {
let outputDirname = outputDir(opts.outfile, modelDirname)
logPathname = path.join(outputDirname, `${modelName}.txt`)
opts.outfile = logPathname
}
await run(model, opts)
// Convert the TSV log file to a DAT file in the same directory.
opts.dat = true
await log(logPathname, opts)
// Assume there is a Vensim-created DAT file named {modelName}.dat in the model directory.
// Compare it to the SDE DAT file.
let vensimPathname = path.join(modelDirname, `${modelName}.dat`)
let p = path.parse(logPathname)
let sdePathname = path.format({ dir: p.dir, name: p.name, ext: '.dat' })
await compare(vensimPathname, sdePathname, opts)
} catch (e) {
// Exit with a non-zero error code if any step failed
console.error(`ERROR: ${e.message}\n`)
process.exit(1)
}
await run(model, opts)
// Convert the TSV log file to a DAT file in the same directory.
opts.dat = true
await log(logPathname, opts)
// Assume there is a Vensim-created DAT file named {modelName}.dat in the model directory.
// Compare it to the SDE DAT file.
let vensimPathname = path.join(modelDirname, `${modelName}.dat`)
let p = path.parse(logPathname)
let sdePathname = path.format({ dir: p.dir, name: p.name, ext: '.dat' })
await compare(vensimPathname, sdePathname, opts)
}

export default {
Expand Down

0 comments on commit 34f25f8

Please sign in to comment.