From fde80708d63d54be18037c598d1e6aef0c65ca08 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 22 Mar 2022 19:17:22 +0100 Subject: [PATCH] treewide: forward compatibility changes Adapt testsuite code and fw4 wrapper to current ucode HEAD semantics, in particular ensure that main.uc is invoked as template since ucode now defaults to raw mode for cli invocations. Signed-off-by: Jo-Philipp Wich --- root/sbin/fw4 | 8 +- root/usr/share/ucode/fw4.uc | 2 - run_tests.sh | 20 +- tests/lib/mocklib.uc | 353 ++++++++++++++++++------------------ tests/lib/mocklib/fs.uc | 307 ++++++++++++++++--------------- tests/lib/mocklib/ubus.uc | 107 ++++++----- tests/lib/mocklib/uci.uc | 235 ++++++++++++------------ 7 files changed, 509 insertions(+), 523 deletions(-) diff --git a/root/sbin/fw4 b/root/sbin/fw4 index 3e7388b..30db129 100755 --- a/root/sbin/fw4 +++ b/root/sbin/fw4 @@ -31,13 +31,13 @@ start() { esac ACTION=start \ - ucode -S -i $MAIN | nft $VERBOSE -f /proc/self/fd/0 + utpl -S $MAIN | nft $VERBOSE -f /proc/self/fd/0 } 1000>$LOCK } print() { ACTION=print \ - ucode -S -i $MAIN + utpl -S $MAIN } stop() { @@ -68,12 +68,12 @@ flush() { reload_sets() { ACTION=reload-sets \ - flock -x $LOCK ucode -S -i $MAIN | nft $VERBOSE -f /proc/self/fd/0 + flock -x $LOCK utpl -S $MAIN | nft $VERBOSE -f /proc/self/fd/0 } lookup() { ACTION=$1 OBJECT=$2 DEVICE=$3 \ - flock -x $LOCK ucode -S -i $MAIN + flock -x $LOCK utpl -S $MAIN } while [ -n "$1" ]; do diff --git a/root/usr/share/ucode/fw4.uc b/root/usr/share/ucode/fw4.uc index 7172a86..37d117c 100644 --- a/root/usr/share/ucode/fw4.uc +++ b/root/usr/share/ucode/fw4.uc @@ -1,5 +1,3 @@ -{% - let fs = require("fs"); let uci = require("uci"); let ubus = require("ubus"); diff --git a/run_tests.sh b/run_tests.sh index dcc6802..af1ae4d 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,14 +1,7 @@ #!/usr/bin/env bash line='........................................' -uenv='{ - "REQUIRE_SEARCH_PATH": [ - "./tests/lib/*.uc", - "./root/usr/share/ucode/*.uc", - "/usr/local/lib/ucode/*.so", - "/usr/lib/ucode/*.so" - ] -}' +ucode='ucode -S -T, -L./tests/lib -L./root/usr/share/ucode' extract_sections() { local file=$1 @@ -70,12 +63,11 @@ run_testcase() { local code=$7 local fail=0 - ucode -S ${uenv:+-e "$uenv"} -e '{ - "MOCK_SEARCH_PATH": [ - "'"$dir"'/files", - "./tests/mocks" - ] - }' ${env:+-e "$(cat "$env")"} -m mocklib -m fw4 -i - <"$in" >"$dir/res.out" 2>"$dir/res.err" + $ucode \ + -D MOCK_SEARCH_PATH='["'"$dir"'/files", "./tests/mocks"]' \ + ${env:+-F "$env"} \ + -l mocklib -l fw4 \ + - <"$in" >"$dir/res.out" 2>"$dir/res.err" printf "%d\n" $? > "$dir/res.code" diff --git a/tests/lib/mocklib.uc b/tests/lib/mocklib.uc index d47ebf3..896efba 100644 --- a/tests/lib/mocklib.uc +++ b/tests/lib/mocklib.uc @@ -1,241 +1,240 @@ -{% - /* strict mode compliance: ensure that global variabes are defined */ - if (!exists(global, 'REQUIRE_SEARCH_PATH')) - global.MOCK_SEARCH_PATH = null; +/* strict mode compliance: ensure that global variabes are defined */ +if (!exists(global, 'REQUIRE_SEARCH_PATH')) + global.MOCK_SEARCH_PATH = null; - if (!exists(global, 'MOCK_SEARCH_PATH')) - global.MOCK_SEARCH_PATH = null; +if (!exists(global, 'MOCK_SEARCH_PATH')) + global.MOCK_SEARCH_PATH = null; - if (!exists(global, 'TRACE_CALLS')) - global.TRACE_CALLS = null; +if (!exists(global, 'TRACE_CALLS')) + global.TRACE_CALLS = null; - let _fs = require("fs"); +let _fs = require("fs"); - /* Force reloading fs module on next require */ - delete global.modules.fs; +/* Force reloading fs module on next require */ +delete global.modules.fs; - let _log = (level, fmt, ...args) => { - let color, prefix; +let _log = (level, fmt, ...args) => { + let color, prefix; - switch (level) { - case 'info': - color = 34; - prefix = '!'; - break; + switch (level) { + case 'info': + color = 34; + prefix = '!'; + break; - case 'warn': - color = 33; - prefix = 'W'; - break; + case 'warn': + color = 33; + prefix = 'W'; + break; - case 'error': - color = 31; - prefix = 'E'; - break; + case 'error': + color = 31; + prefix = 'E'; + break; - default: - color = 0; - prefix = 'I'; - } + default: + color = 0; + prefix = 'I'; + } - let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt); - warn(replace(sprintf(f, ...args), "\n", "\n "), "\n"); - }; + let f = sprintf("\u001b[%d;1m[%s] %s\u001b[0m", color, prefix, fmt); + warn(replace(sprintf(f, ...args), "\n", "\n "), "\n"); +}; - let read_data_file = (path) => { - for (let dir in MOCK_SEARCH_PATH) { - let fd = _fs.open(dir + '/' + path, "r"); +let read_data_file = (path) => { + for (let dir in MOCK_SEARCH_PATH) { + let fd = _fs.open(dir + '/' + path, "r"); - if (fd) { - let data = fd.read("all"); - fd.close(); + if (fd) { + let data = fd.read("all"); + fd.close(); - return data; - } + return data; } + } - return null; - }; - - let read_json_file = (path) => { - let data = read_data_file(path); + return null; +}; - if (data != null) { - try { - return json(data); - } - catch (e) { - _log('error', "Unable to parse JSON data in %s: %s", path, e); +let read_json_file = (path) => { + let data = read_data_file(path); - return NaN; - } + if (data != null) { + try { + return json(data); } + catch (e) { + _log('error', "Unable to parse JSON data in %s: %s", path, e); - return null; - }; - - let format_json = (data) => { - let rv; - - let format_value = (value) => { - switch (type(value)) { - case "object": - return sprintf("{ /* %d keys */ }", length(value)); + return NaN; + } + } - case "array": - return sprintf("[ /* %d items */ ]", length(value)); + return null; +}; - case "string": - if (length(value) > 64) - value = substr(value, 0, 64) + "..."; +let format_json = (data) => { + let rv; - /* fall through */ - return sprintf("%J", value); + let format_value = (value) => { + switch (type(value)) { + case "object": + return sprintf("{ /* %d keys */ }", length(value)); - default: - return sprintf("%J", value); - } - }; + case "array": + return sprintf("[ /* %d items */ ]", length(value)); - switch (type(data)) { - case "object": - rv = "{"; + case "string": + if (length(value) > 64) + value = substr(value, 0, 64) + "..."; - let k = sort(keys(data)); + /* fall through */ + return sprintf("%J", value); - for (let i, n in k) - rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n])); + default: + return sprintf("%J", value); + } + }; - rv += " }"; - break; + switch (type(data)) { + case "object": + rv = "{"; - case "array": - rv = "["; + let k = sort(keys(data)); - for (let i, v in data) - rv += (i ? "," : "") + " " + format_value(v); + for (let i, n in k) + rv += sprintf("%s %J: %s", i ? "," : "", n, format_value(data[n])); - rv += " ]"; - break; + rv += " }"; + break; - default: - rv = format_value(data); - } + case "array": + rv = "["; - return rv; - }; + for (let i, v in data) + rv += (i ? "," : "") + " " + format_value(v); - let trace_call = (ns, func, args) => { - let msg = "[call] " + - (ns ? ns + "." : "") + - func; + rv += " ]"; + break; - for (let k, v in args) { - msg += ' ' + k + ' <'; + default: + rv = format_value(data); + } - switch (type(v)) { - case "array": - case "object": - msg += format_json(v); - break; + return rv; +}; - default: - msg += v; - } +let trace_call = (ns, func, args) => { + let msg = "[call] " + + (ns ? ns + "." : "") + + func; - msg += '>'; - } + for (let k, v in args) { + msg += ' ' + k + ' <'; - switch (TRACE_CALLS) { - case '1': - case 'stdout': - _fs.stdout.write(msg + "\n"); + switch (type(v)) { + case "array": + case "object": + msg += format_json(v); break; - case 'stderr': - _fs.stderr.write(msg + "\n"); - break; + default: + msg += v; } - }; - - /* Prepend mocklib to REQUIRE_SEARCH_PATH */ - for (let pattern in REQUIRE_SEARCH_PATH) { - /* Only consider ucode includes */ - if (!match(pattern, /\*\.uc$/)) - continue; - - let path = replace(pattern, /\*/, 'mocklib'), - stat = _fs.stat(path); - if (!stat || stat.type != 'file') - continue; + msg += '>'; + } - if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) - MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ]; + switch (TRACE_CALLS) { + case '1': + case 'stdout': + _fs.stdout.write(msg + "\n"); + break; - unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc')); + case 'stderr': + _fs.stderr.write(msg + "\n"); break; } +}; + +/* Prepend mocklib to REQUIRE_SEARCH_PATH */ +for (let pattern in REQUIRE_SEARCH_PATH) { + /* Only consider ucode includes */ + if (!match(pattern, /\*\.uc$/)) + continue; + + let path = replace(pattern, /\*/, 'mocklib'), + stat = _fs.stat(path); + + if (!stat || stat.type != 'file') + continue; if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) - MOCK_SEARCH_PATH = [ './mocks' ]; + MOCK_SEARCH_PATH = [ replace(path, /mocklib\.uc$/, '../mocks') ]; - let _print = global.print; + unshift(REQUIRE_SEARCH_PATH, replace(path, /mocklib\.uc$/, 'mocklib/*.uc')); + break; +} - /* Register global mocklib namespace */ - global.mocklib = { - require: function(module) { - let path, res, ex; +if (type(MOCK_SEARCH_PATH) != 'array' || length(MOCK_SEARCH_PATH) == 0) + MOCK_SEARCH_PATH = [ './mocks' ]; - if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1) - path = shift(REQUIRE_SEARCH_PATH); +let _print = global.print; - try { - res = require(module); - } - catch (e) { - ex = e; - } +/* Register global mocklib namespace */ +global.mocklib = { + require: function(module) { + let path, res, ex; - if (path) - unshift(REQUIRE_SEARCH_PATH, path); + if (type(REQUIRE_SEARCH_PATH) == "array" && index(REQUIRE_SEARCH_PATH[0], 'mocklib/*.uc') != -1) + path = shift(REQUIRE_SEARCH_PATH); - if (ex) - die(ex); + try { + res = require(module); + } + catch (e) { + ex = e; + } - return res; - }, + if (path) + unshift(REQUIRE_SEARCH_PATH, path); - I: (...args) => _log('info', ...args), - N: (...args) => _log('notice', ...args), - W: (...args) => _log('warn', ...args), - E: (...args) => _log('error', ...args), + if (ex) + die(ex); - format_json, - read_data_file, - read_json_file, - trace_call - }; + return res; + }, - /* Override stdlib functions */ - global.system = function(argv, timeout) { - trace_call(null, "system", { command: argv, timeout }); + I: (...args) => _log('info', ...args), + N: (...args) => _log('notice', ...args), + W: (...args) => _log('warn', ...args), + E: (...args) => _log('error', ...args), - return 0; - }; + format_json, + read_data_file, + read_json_file, + trace_call +}; - global.time = function() { - trace_call(null, "time"); +/* Override stdlib functions */ +global.system = function(argv, timeout) { + trace_call(null, "system", { command: argv, timeout }); - return 1615382640; - }; + return 0; +}; - global.print = function(...args) { - if (length(args) == 1 && type(args[0]) in ["array", "object"]) - printf("%s\n", format_json(args[0])); - else - _print(...args); - }; +global.time = function() { + trace_call(null, "time"); + + return 1615382640; +}; + +global.print = function(...args) { + if (length(args) == 1 && type(args[0]) in ["array", "object"]) + printf("%s\n", format_json(args[0])); + else + _print(...args); +}; - return global.mocklib; +return global.mocklib; diff --git a/tests/lib/mocklib/fs.uc b/tests/lib/mocklib/fs.uc index 97521cc..6e26552 100644 --- a/tests/lib/mocklib/fs.uc +++ b/tests/lib/mocklib/fs.uc @@ -1,175 +1,174 @@ -{% - let mocklib = global.mocklib, - fs = mocklib.require("fs"); - - return { - readlink: function(path) { - mocklib.trace_call("fs", "readlink", { path }); - - return path + "-link"; - }, - - stat: function(path) { - let file = sprintf("fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), - mock = mocklib.read_json_file(file); - - if (!mock || mock != mock) { - mocklib.I("No stat result fixture defined for fs.stat() call on %s.", path); - mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); - - if (match(path, /\/$/)) - mock = { type: "directory" }; - else - mock = { type: "file" }; - } - - mocklib.trace_call("fs", "stat", { path }); - - return mock; - }, - - unlink: function(path) { - printf("fs.unlink() path <%s>\n", path); - - return true; - }, +let mocklib = global.mocklib, + fs = mocklib.require("fs"); + +return { + readlink: function(path) { + mocklib.trace_call("fs", "readlink", { path }); + + return path + "-link"; + }, + + stat: function(path) { + let file = sprintf("fs/stat~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), + mock = mocklib.read_json_file(file); + + if (!mock || mock != mock) { + mocklib.I("No stat result fixture defined for fs.stat() call on %s.", path); + mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); + + if (match(path, /\/$/)) + mock = { type: "directory" }; + else + mock = { type: "file" }; + } + + mocklib.trace_call("fs", "stat", { path }); + + return mock; + }, + + unlink: function(path) { + printf("fs.unlink() path <%s>\n", path); + + return true; + }, + + popen: (cmdline, mode) => { + let read = (!mode || index(mode, "r") != -1), + path = sprintf("fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')), + mock = mocklib.read_data_file(path); + + if (read && !mock) { + mocklib.I("No stdout fixture defined for fs.popen() command %s.", cmdline); + mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + + return null; + } + + mocklib.trace_call("fs", "popen", { cmdline, mode }); + + return { + read: function(amount) { + let rv; + + switch (amount) { + case "all": + rv = mock; + mock = ""; + break; + + case "line": + let i = index(mock, "\n"); + i = (i > -1) ? i + 1 : mock.length; + rv = substr(mock, 0, i); + mock = substr(mock, i); + break; + + default: + let n = +amount; + n = (n > 0) ? n : 0; + rv = substr(mock, 0, n); + mock = substr(mock, n); + break; + } - popen: (cmdline, mode) => { - let read = (!mode || index(mode, "r") != -1), - path = sprintf("fs/popen~%s.txt", replace(cmdline, /[^A-Za-z0-9_-]+/g, '_')), - mock = mocklib.read_data_file(path); + return rv; + }, - if (read && !mock) { - mocklib.I("No stdout fixture defined for fs.popen() command %s.", cmdline); - mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + write: function() {}, + close: function() {}, + error: function() { return null; } - - mocklib.trace_call("fs", "popen", { cmdline, mode }); - - return { - read: function(amount) { - let rv; - - switch (amount) { - case "all": - rv = mock; - mock = ""; - break; - - case "line": - let i = index(mock, "\n"); - i = (i > -1) ? i + 1 : mock.length; - rv = substr(mock, 0, i); - mock = substr(mock, i); - break; - - default: - let n = +amount; - n = (n > 0) ? n : 0; - rv = substr(mock, 0, n); - mock = substr(mock, n); - break; - } - - return rv; - }, - - write: function() {}, - close: function() {}, - - error: function() { - return null; + }; + }, + + open: (fpath, mode) => { + let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1), + path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), + mock = read ? mocklib.read_data_file(path) : null; + + if (read && !mock) { + mocklib.I("No stdout fixture defined for fs.open() path %s.", fpath); + mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + + return null; + } + + mocklib.trace_call("fs", "open", { path: fpath, mode }); + + return { + read: function(amount) { + let rv; + + switch (amount) { + case "all": + rv = mock; + mock = ""; + break; + + case "line": + let i = index(mock, "\n"); + i = (i > -1) ? i + 1 : mock.length; + rv = substr(mock, 0, i); + mock = substr(mock, i); + break; + + default: + let n = +amount; + n = (n > 0) ? n : 0; + rv = substr(mock, 0, n); + mock = substr(mock, n); + break; } - }; - }, - open: (fpath, mode) => { - let read = (!mode || index(mode, "r") != -1 || index(mode, "+") != -1), - path = sprintf("fs/open~%s.txt", replace(fpath, /[^A-Za-z0-9_-]+/g, '_')), - mock = read ? mocklib.read_data_file(path) : null; + return rv; + }, - if (read && !mock) { - mocklib.I("No stdout fixture defined for fs.open() path %s.", fpath); - mocklib.I("Provide a mock output through the following text file:\n%s\n", path); + write: function() {}, + close: function() {}, + error: function() { return null; } + }; + }, - mocklib.trace_call("fs", "open", { path: fpath, mode }); - - return { - read: function(amount) { - let rv; - - switch (amount) { - case "all": - rv = mock; - mock = ""; - break; - - case "line": - let i = index(mock, "\n"); - i = (i > -1) ? i + 1 : mock.length; - rv = substr(mock, 0, i); - mock = substr(mock, i); - break; - - default: - let n = +amount; - n = (n > 0) ? n : 0; - rv = substr(mock, 0, n); - mock = substr(mock, n); - break; - } - - return rv; - }, - - write: function() {}, - close: function() {}, - - error: function() { - return null; - } - }; - }, + opendir: (path) => { + let file = sprintf("fs/opendir~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), + mock = mocklib.read_json_file(file), + index = 0; - opendir: (path) => { - let file = sprintf("fs/opendir~%s.json", replace(path, /[^A-Za-z0-9_-]+/g, '_')), - mock = mocklib.read_json_file(file), - index = 0; + if (!mock || mock != mock) { + mocklib.I("No stat result fixture defined for fs.opendir() call on %s.", path); + mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); - if (!mock || mock != mock) { - mocklib.I("No stat result fixture defined for fs.opendir() call on %s.", path); - mocklib.I("Provide a mock result through the following JSON file:\n%s\n", file); + mock = []; + } - mock = []; - } + mocklib.trace_call("fs", "opendir", { path }); - mocklib.trace_call("fs", "opendir", { path }); + return { + read: function() { + return mock[index++]; + }, - return { - read: function() { - return mock[index++]; - }, + tell: function() { + return index; + }, - tell: function() { - return index; - }, + seek: function(i) { + index = i; + }, - seek: function(i) { - index = i; - }, + close: function() {}, - close: function() {}, - - error: function() { - return null; - } - }; - }, + error: function() { + return null; + } + }; + }, - error: () => "Unspecified error" - }; + error: () => "Unspecified error" +}; diff --git a/tests/lib/mocklib/ubus.uc b/tests/lib/mocklib/ubus.uc index 985e004..67be902 100644 --- a/tests/lib/mocklib/ubus.uc +++ b/tests/lib/mocklib/ubus.uc @@ -1,69 +1,68 @@ -{% - let mocklib = global.mocklib; - - return { - connect: function() { - let self = this; - - return { - call: (object, method, args) => { - let signature = [ object + "~" + method ]; - - if (type(args) == "object") { - for (let i, k in sort(keys(args))) { - switch (type(args[k])) { - case "string": - case "double": - case "bool": - case "int": - push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_")); - break; - - default: - push(signature, type(args[k])); - } +let mocklib = global.mocklib; + +return { + connect: function() { + let self = this; + + return { + call: (object, method, args) => { + let signature = [ object + "~" + method ]; + + if (type(args) == "object") { + for (let i, k in sort(keys(args))) { + switch (type(args[k])) { + case "string": + case "double": + case "bool": + case "int": + push(signature, k + "-" + replace(args[k], /[^A-Za-z0-9_-]+/g, "_")); + break; + + default: + push(signature, type(args[k])); } } + } - let candidates = []; + let candidates = []; - for (let i = length(signature); i > 0; i--) { - let path = sprintf("ubus/%s.json", join("~", signature)), - mock = mocklib.read_json_file(path); + for (let i = length(signature); i > 0; i--) { + let path = sprintf("ubus/%s.json", join("~", signature)), + mock = mocklib.read_json_file(path); - if (mock != mock) { - self._error = "Invalid argument"; + if (mock != mock) { + self._error = "Invalid argument"; - return null; - } - else if (mock) { - mocklib.trace_call("ctx", "call", { object, method, args }); - - return mock; - } + return null; + } + else if (mock) { + mocklib.trace_call("ctx", "call", { object, method, args }); - push(candidates, path); - pop(signature); + return mock; } - mocklib.I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args); - mocklib.I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates)); + push(candidates, path); + pop(signature); + } + + mocklib.I("No response fixture defined for ubus call %s/%s with arguments %s.", object, method, args); + mocklib.I("Provide a mock response through one of the following JSON files:\n%s\n", join("\n", candidates)); - self._error = "Method not found"; + self._error = "Method not found"; - return null; - }, + return null; + }, - disconnect: () => null, + disconnect: () => null, - error: () => self.error() - }; - }, + error: () => self.error() + }; + }, - error: function() { - let e = this._error; - delete this._error; + error: function() { + let e = this._error; + delete this._error; - return e; - } - }; + return e; + } +}; diff --git a/tests/lib/mocklib/uci.uc b/tests/lib/mocklib/uci.uc index ed6b610..aa7108a 100644 --- a/tests/lib/mocklib/uci.uc +++ b/tests/lib/mocklib/uci.uc @@ -1,154 +1,153 @@ -{% - let mocklib = global.mocklib; +let mocklib = global.mocklib; - let byte = (str, off) => { - let v = ord(str, off); - return length(v) ? v[0] : v; - }; +let byte = (str, off) => { + let v = ord(str, off); + return length(v) ? v[0] : v; +}; - let hash = (s) => { - let h = 7; +let hash = (s) => { + let h = 7; - for (let i = 0; i < length(s); i++) - h = h * 31 + byte(s, i); + for (let i = 0; i < length(s); i++) + h = h * 31 + byte(s, i); - return h; - }; + return h; +}; - let id = (config, t, n) => { - while (true) { - let id = sprintf('cfg%08x', hash(t + n)); +let id = (config, t, n) => { + while (true) { + let id = sprintf('cfg%08x', hash(t + n)); - if (!exists(config, id)) - return id; + if (!exists(config, id)) + return id; - n++; - } - }; - - let fixup_config = (config) => { - let rv = {}; - let n_section = 0; + n++; + } +}; - for (let stype in config) { - switch (type(config[stype])) { - case 'object': - config[stype] = [ config[stype] ]; - /* fall through */ +let fixup_config = (config) => { + let rv = {}; + let n_section = 0; - case 'array': - for (let idx, sobj in config[stype]) { - let sid, anon; + for (let stype in config) { + switch (type(config[stype])) { + case 'object': + config[stype] = [ config[stype] ]; + /* fall through */ - if (exists(sobj, '.name') && !exists(rv, sobj['.name'])) { - sid = sobj['.name']; - anon = false; - } - else { - sid = id(rv, stype, idx); - anon = true; - } + case 'array': + for (let idx, sobj in config[stype]) { + let sid, anon; - rv[sid] = { - '.index': n_section++, - ...sobj, - '.name': sid, - '.type': stype, - '.anonymous': anon - }; + if (exists(sobj, '.name') && !exists(rv, sobj['.name'])) { + sid = sobj['.name']; + anon = false; + } + else { + sid = id(rv, stype, idx); + anon = true; } - break; + rv[sid] = { + '.index': n_section++, + ...sobj, + '.name': sid, + '.type': stype, + '.anonymous': anon + }; } + + break; } + } - for (let n, sid in sort(keys(rv), (a, b) => rv[a]['.index'] - rv[b]['.index'])) - rv[sid]['.index'] = n; + for (let n, sid in sort(keys(rv), (a, b) => rv[a]['.index'] - rv[b]['.index'])) + rv[sid]['.index'] = n; - return rv; - }; + return rv; +}; - return { - cursor: () => ({ - _configs: {}, +return { + cursor: () => ({ + _configs: {}, - load: function(file) { - let basename = replace(file, /^.+\//, ''), - path = sprintf("uci/%s.json", basename), - mock = mocklib.read_json_file(path); + load: function(file) { + let basename = replace(file, /^.+\//, ''), + path = sprintf("uci/%s.json", basename), + mock = mocklib.read_json_file(path); - if (!mock || mock != mock) { - mocklib.I("No configuration fixture defined for uci package %s.", file); - mocklib.I("Provide a mock configuration through the following JSON file:\n%s\n", path); + if (!mock || mock != mock) { + mocklib.I("No configuration fixture defined for uci package %s.", file); + mocklib.I("Provide a mock configuration through the following JSON file:\n%s\n", path); + return null; + } + + this._configs[basename] = fixup_config(mock); + }, + + _get_section: function(config, section) { + if (!exists(this._configs, config)) { + this.load(config); + + if (!exists(this._configs, config)) return null; - } + } - this._configs[basename] = fixup_config(mock); - }, + let cfg = this._configs[config], + extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$"); - _get_section: function(config, section) { - if (!exists(this._configs, config)) { - this.load(config); + if (extended) { + let stype = extended[1], + sindex = +extended[2]; - if (!exists(this._configs, config)) - return null; - } + let sids = sort( + filter(keys(cfg), sid => cfg[sid]['.type'] == stype), + (a, b) => cfg[a]['.index'] - cfg[b]['.index'] + ); - let cfg = this._configs[config], - extended = match(section, "^@([A-Za-z0-9_-]+)\[(-?[0-9]+)\]$"); + if (sindex < 0) + sindex = sids.length + sindex; - if (extended) { - let stype = extended[1], - sindex = +extended[2]; + return cfg[sids[sindex]]; + } - let sids = sort( - filter(keys(cfg), sid => cfg[sid]['.type'] == stype), - (a, b) => cfg[a]['.index'] - cfg[b]['.index'] - ); + return cfg[section]; + }, - if (sindex < 0) - sindex = sids.length + sindex; + get: function(config, section, option) { + let sobj = this._get_section(config, section); - return cfg[sids[sindex]]; - } + if (option && index(option, ".") == 0) + return null; + else if (sobj && option) + return sobj[option]; + else if (sobj) + return sobj[".type"]; + }, - return cfg[section]; - }, + get_all: function(config, section) { + return section ? this._get_section(config, section) : this._configs[config]; + }, - get: function(config, section, option) { - let sobj = this._get_section(config, section); + foreach: function(config, stype, cb) { + let rv = false; - if (option && index(option, ".") == 0) - return null; - else if (sobj && option) - return sobj[option]; - else if (sobj) - return sobj[".type"]; - }, - - get_all: function(config, section) { - return section ? this._get_section(config, section) : this._configs[config]; - }, - - foreach: function(config, stype, cb) { - let rv = false; - - if (exists(this._configs, config)) { - let cfg = this._configs[config], - sids = sort(keys(cfg), (a, b) => cfg[a]['.index'] - cfg[b]['.index']); - - for (let i, sid in sids) { - if (stype == null || cfg[sid]['.type'] == stype) { - if (cb({ ...(cfg[sid]) }) === false) - break; - - rv = true; - } + if (exists(this._configs, config)) { + let cfg = this._configs[config], + sids = sort(keys(cfg), (a, b) => cfg[a]['.index'] - cfg[b]['.index']); + + for (let i, sid in sids) { + if (stype == null || cfg[sid]['.type'] == stype) { + if (cb({ ...(cfg[sid]) }) === false) + break; + + rv = true; } } - - return rv; } - }) - }; + + return rv; + } + }) +}; -- 2.30.2