Bugfixes, pretty printing, new tests added

Spec result: 108 specs pass, 6 skipped, 67 fail
This commit is contained in:
Tyler Akins 2023-04-09 18:53:55 -05:00
parent a1e4398547
commit e0e9189355
No known key found for this signature in database
GPG Key ID: 8F3B8C432F4393BD
7 changed files with 210 additions and 49 deletions

111
mo
View File

@ -786,7 +786,7 @@ mo::parsePartial() {
moR=$'\r'
moIndentation="$moN${moPrevious//$moR/$moN}"
moIndentation=${moIndentation##*$moN}
mo::debug "Adding indentation: '$moIndentation'"
mo::debug "Adding indentation to partial: '$moIndentation'"
mo::standaloneProcessBefore moPrevious "$moPrevious"
mo::standaloneProcessAfter moContent "$moContent"
moStandaloneContent=$'\n'
@ -810,6 +810,8 @@ mo::parsePartial() {
exit 1
fi
mo::indentLines moResult "$moResult" "$moIndentation"
# Delimiters are reset when loading a new partial
mo::parse moResult "$moResult" "$moCurrent" "" "{{" "}}" "" $'\n'
@ -823,7 +825,7 @@ mo::parsePartial() {
exit 1
fi
mo::indentLines moResult "${moResult%.}" "$moIndentation"
moResult=${moResult%.}
fi
local "$1" && mo::indirectArray "$1" "$moPrevious$moResult" "$moContent" "$moStandaloneContent"
@ -1317,6 +1319,42 @@ mo::isArray() {
}
# Internal: Determine if an array index exists.
#
# $1 - Variable name to check
# $2 - The index to check
#
# Has to check if the variable is an array and if the index is valid for that
# type of array.
#
# Returns true (0) if everything was ok, 1 if there's any condition that fails.
mo::isArrayIndexValid() {
local moDeclare moTest
moDeclare=$(declare -p "$1")
moTest=""
if [[ "${moDeclare:0:10}" == "declare -a" ]]; then
# Numerically indexed array - must check if the index looks like a
# number because using a string to index a numerically indexed array
# will appear like it worked.
if [[ "$2" == "0" ]] || [[ "$2" =~ ^[1-9][0-9]*$ ]]; then
# Index looks like a number
eval "moTest=\"\${$1[$2]+ok}\""
fi
elif [[ "${moDeclare:0:10}" == "declare -A" ]]; then
# Associative array
eval "moTest=\"\${$1[$2]+ok}\""
fi
if [[ -n "$moTest" ]]; then
return 0;
fi
return 1
}
# Internal: Determine if a variable is assigned, even if it is assigned an empty
# value.
#
@ -1442,24 +1480,24 @@ mo::evaluateListOfSingles() {
#
# Returns nothing
mo::evaluateSingle() {
local moResult moCurrent moVarNameParts moType moArg
local moResult moCurrent moType moArg
moCurrent=$2
moType=$3
moArg=$4
mo::debug "Evaluating $moType: $moArg"
mo::debug "Evaluating $moType: $moArg ($moCurrent)"
if [[ "$moType" == "VALUE" ]]; then
moResult=$moArg
elif [[ "$moArg" == "." ]]; then
mo::evaluateVariable moResult "$moCurrent"
mo::evaluateVariable moResult "$moCurrent" ""
elif [[ "$moArg" == "@key" ]]; then
mo::evaluateKey moResult "$moCurrent"
elif mo::isFunction "$moArg"; then
mo::evaluateFunction moResult "" "$moArg"
else
mo::split moVarNameParts "$moArg" .
mo::evaluateVariable moResult "$moArg"
mo::evaluateVariable moResult "$moArg" "$moCurrent"
fi
local "$1" && mo::indirect "$1" "$moResult"
@ -1491,16 +1529,19 @@ mo::evaluateKey() {
#
# $1 - Destination variable name
# $2 - Variable name
# $3 - Current value
#
# Returns nothing.
mo::evaluateVariable() {
local moResult moCurrent moArg moNameParts moJoined moKey moValue
moArg=$2
moCurrent=$3
moResult=""
mo::split moNameParts "$moArg" .
mo::findVariableName moNameParts "$moArg" "$moCurrent"
mo::debug "Evaluate variable ($moArg + $moCurrent): ${moNameParts[*]}"
if [[ -z "${moNameParts[1]-}" ]]; then
if [[ -z "${moNameParts[1]}" ]]; then
if mo::isArray "$moArg"; then
eval mo::join moResult "," "\${$moArg[@]}"
else
@ -1523,6 +1564,58 @@ mo::evaluateVariable() {
}
# Internal: Find the name of a variable to use
#
# $1 - Destination variable name, receives an array
# $2 - Variable name from the template
# $3 - The name of the "current value", from block parsing
#
# The array contains the following values
# [0] - Variable name
# [1] - Array index, or empty string
#
# Example variables
# a="a"
# b="b"
# c=("c.0" "c.1")
# d=([b]="d.b" [d]="d.d")
#
# Given these inputs, produce these outputs
# a c => a
# a c.0 => a
# b d => d.b
# b d.d => d.b
# a d => d.a
# a d.d => d.a
# c.0 d => c.0
# d.b d => d.b
# Returns nothing.
mo::findVariableName() {
local moVar moCurrent moNameParts moResultBase moResultIndex
moVar=$2
moCurrent=$3
moResultBase=$moVar
moResultIndex=""
if [[ "$moVar" == *.* ]]; then
mo::debug "Find variable name; name has dot: $moVar"
moResultBase=${moVar%%.*}
moResultIndex=${moVar#*.}
elif [[ -n "$moCurrent" ]]; then
moCurrent=${moCurrent%%.*}
mo::debug "Find variable name; look in array: $moCurrent"
if mo::isArrayIndexValid "$moCurrent" "$moVar"; then
moResultBase=$moCurrent
moResultIndex=$moVar
fi
fi
local "$1" && mo::indirectArray "$1" "$moResultBase" "$moResultIndex"
}
# Internal: Join / implode an array
#
# $1 - Variable name to receive the joined content

View File

@ -11,28 +11,45 @@ const fsPromises = require("fs").promises;
//
// To override any test property, just define that property.
const testOverrides = {
'Interpolation -> HTML Escaping': {
skip: 'HTML escaping is not supported'
"Interpolation -> HTML Escaping": {
skip: "HTML escaping is not supported"
},
'Interpolation -> Implicit Iterators - HTML Escaping': {
skip: 'HTML escaping is not supported'
"Interpolation -> Implicit Iterators - HTML Escaping": {
skip: "HTML escaping is not supported"
},
'Lambdas -> Escaping': {
skip: 'HTML escaping is not supported'
"Lambdas -> Escaping": {
skip: "HTML escaping is not supported"
},
'Sections -> Dotted Names - Broken Chains': {
"Sections -> Deeply Nested Contexts": {
skip: "Nested objects are not supported"
},
"Sections -> Dotted Names - Broken Chains": {
// Complex objects are not supported
template: `"{{#a.b}}Here{{/a.b}}" == ""`
},
'Sections -> Dotted Names - Falsey': {
"Sections -> Dotted Names - Falsey": {
// Complex objects are not supported
data: { a: { b: false } },
template: `"{{#a.b}}Here{{/a.b}}" == ""`
},
'Sections -> Dotted Names - Truthy': {
"Sections -> Dotted Names - Truthy": {
// Complex objects are not supported
data: { a: { b: true } },
template: `"{{#a.b}}Here{{/a.b}}" == "Here"`
},
"Sections -> Implicit Iterator - Array": {
skip: "Nested arrays are not supported"
},
"Sections -> List": {
// Arrays of objects are not supported
data: { list: [1, 2, 3] },
template: `"{{#list}}{{.}}{{/list}}"`
},
"Sections -> List Context": {
skip: "Deeply nested objects are not supported"
},
"Sections -> List Contexts": {
skip: "Deeply nested objects are not supported"
}
};
@ -110,7 +127,7 @@ function addToEnvironmentObjectConvertedToAssociativeArray(name, value) {
const values = [];
for (const [k, v] of Object.entries(value)) {
if (typeof v === 'object') {
if (typeof v === "object") {
if (v) {
// An object - abort
return `# ${name}.${k} is an object that can not be converted to an associative array`;
@ -123,7 +140,7 @@ function addToEnvironmentObjectConvertedToAssociativeArray(name, value) {
}
}
return `declare -A ${name}\n${name}=(${values.join(' ')})`;
return `declare -A ${name}\n${name}=(${values.join(" ")})`;
}
function addToEnvironmentObject(name, value) {
@ -134,10 +151,7 @@ function addToEnvironmentObject(name, value) {
// Sometimes the __tag__ property of the code in the lambdas may be
// missing. Compensate by detecting commonly defined languages.
if (
(value.__tag__ === "code") ||
(value.ruby && value.php && value.perl)
) {
if (value.__tag__ === "code" || (value.ruby && value.php && value.perl)) {
if (value.bash) {
return `${name}() { ${value.bash}; }`;
}
@ -145,7 +159,6 @@ function addToEnvironmentObject(name, value) {
return `${name}() { perl -e 'print ((${value.perl})->("'"$1"'"))'; }`;
}
return addToEnvironmentObjectConvertedToAssociativeArray(name, value);
}
@ -201,16 +214,20 @@ function setupEnvironment(test) {
function executeScript(test) {
return new Promise((resolve) => {
exec("bash spec-runner/spec-script 2>&1", {
timeout: 2000
}, (err, stdout) => {
if (err) {
test.scriptError = err.toString();
}
exec(
"bash spec-runner/spec-script 2>&1",
{
timeout: 2000
},
(err, stdout) => {
if (err) {
test.scriptError = err.toString();
}
test.output = stdout;
resolve();
});
test.output = stdout;
resolve();
}
);
});
}
@ -232,9 +249,9 @@ function detectFailure(test) {
function showFailureDetails(test) {
console.log(`FAILURE: ${test.fullName}`);
console.log('');
console.log("");
console.log(test.desc);
console.log('');
console.log("");
console.log(JSON.stringify(test, null, 4));
}
@ -261,12 +278,12 @@ function runTest(testSet, test) {
test.script = buildScript(test);
if (test.skip) {
debug('Skipping test:', testSet.fullName, `$(${test.skip})`);
debug("Skipping test:", testSet.fullName, `$(${test.skip})`);
return Promise.resolve();
}
debug('Running test:', testSet.fullName);
debug("Running test:", testSet.fullName);
return setupEnvironment(test)
.then(() => executeScript(test))
@ -303,7 +320,9 @@ function processSpecFile(filename) {
testSet.pass += 1;
}
}
console.log(`### ${testSet.name} Results = ${testSet.pass} passed, ${testSet.fail} failed, ${testSet.skip} skipped`);
console.log(
`### ${testSet.name} Results = ${testSet.pass} passed, ${testSet.fail} failed, ${testSet.skip} skipped`
);
return testSet;
});
@ -318,11 +337,14 @@ if (process.argv.length < 3) {
processArraySequentially(process.argv.slice(2), processSpecFile).then(
(result) => {
console.log('=========================================');
console.log('');
console.log('Failed Test Summary');
console.log('');
let pass = 0, fail = 0, skip = 0, total = 0;
console.log("=========================================");
console.log("");
console.log("Failed Test Summary");
console.log("");
let pass = 0,
fail = 0,
skip = 0,
total = 0;
for (const testSet of result) {
pass += testSet.pass;
@ -330,7 +352,9 @@ processArraySequentially(process.argv.slice(2), processSpecFile).then(
skip += testSet.skip;
total += testSet.tests.length;
console.log(`* ${testSet.name}: ${testSet.tests.length} total, ${testSet.pass} pass, ${testSet.fail} fail, ${testSet.skip} skip`);
console.log(
`* ${testSet.name}: ${testSet.tests.length} total, ${testSet.pass} pass, ${testSet.fail} fail, ${testSet.skip} skip`
);
for (const test of testSet.tests) {
if (test.isFailure) {
@ -339,8 +363,10 @@ processArraySequentially(process.argv.slice(2), processSpecFile).then(
}
}
console.log('');
console.log(`Final result: ${total} total, ${pass} pass, ${fail} fail, ${skip} skip`);
console.log("");
console.log(
`Final result: ${total} total, ${pass} pass, ${fail} fail, ${skip} skip`
);
if (fail) {
process.exit(1);

View File

@ -50,7 +50,7 @@ runTest() (
echo "Actual:"
echo "$testActual"
if [[ -n "${MO_DEBUG-}" ]]; then
if [[ -n "${MO_DEBUG_TEST-}" ]]; then
declare -p testExpected
declare -p testActual
fi

View File

@ -0,0 +1,3 @@
|
{{content}}
|

14
tests/list-contexts Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
. ../run-tests
a=foo
b=wrong
declare -A sec
sec=([b]="bar")
declare -A c
c=([d]="baz")
template="{{#sec}}{{a}} {{b}} {{c.d}}{{/sec}}"
expected="foo bar baz"
runTest

View File

@ -24,8 +24,9 @@ line 2
Indented:
line 1
line 2
line 2
EOF
# This one looks odd, but if you check the spec spec/specs/partials.yaml, name "Standalone Indentation" (mirrors "standalone-indentation" in tests/), then the spec clearly shows that the indentation is applied before rendering.
}
runTest

24
tests/standalone-indentation Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
cd "${0%/*}" || exit 1
. ../run-tests
content=$'<\n->'
template() {
cat <<EOF
\
{{>fixtures/standalone-indentation.partial}}
/
EOF
}
expected() {
cat <<EOF
\
|
<
->
|
/
EOF
}
runTest