Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: make sde log wait for DAT file to be fully written and improve error handling #123

Merged
merged 7 commits into from
Sep 28, 2021
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