summaryrefslogtreecommitdiff
path: root/build-aux
diff options
context:
space:
mode:
Diffstat (limited to 'build-aux')
-rw-r--r--build-aux/measurestack/analyze.py12
-rw-r--r--build-aux/measurestack/test_app_plugins.py138
2 files changed, 144 insertions, 6 deletions
diff --git a/build-aux/measurestack/analyze.py b/build-aux/measurestack/analyze.py
index 93bf885..f454b7e 100644
--- a/build-aux/measurestack/analyze.py
+++ b/build-aux/measurestack/analyze.py
@@ -232,10 +232,10 @@ class AnalyzeResult(typing.NamedTuple):
class SkipModel(typing.NamedTuple):
"""Running the skipmodel calls `.fn(chain, ...)` with the chain
consisting of the last `.nchain` items (if .nchain is an int), or
- the chain starting with the *last* occurance of `.nchain` (if
- .nchain is a collection). If the chain is not that long or does
- not contain a member of the collection, then .fn is not called and
- the call is *not* skipped.
+ the chain starting with the *last* occurance of `.nchain` in
+ chain[:-1] (if .nchain is a collection). If the chain is not that
+ long or does not contain a member of the collection, then .fn is
+ not called and the call is *not* skipped.
"""
@@ -248,9 +248,9 @@ class SkipModel(typing.NamedTuple):
_chain = chain[-self.nchain :]
return self.fn(_chain, call), len(_chain)
else:
- for i in reversed(range(len(chain))):
+ for i in reversed(range(len(chain) - 1)):
if chain[i].base() in self.nchain:
- _chain = chain[i - 1 :]
+ _chain = chain[i:]
return self.fn(_chain, call), len(_chain)
return False, 0
diff --git a/build-aux/measurestack/test_app_plugins.py b/build-aux/measurestack/test_app_plugins.py
new file mode 100644
index 0000000..abdccca
--- /dev/null
+++ b/build-aux/measurestack/test_app_plugins.py
@@ -0,0 +1,138 @@
+# build-aux/measurestack/test_app_plugins.py - Tests for app_plugins.py
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# pylint: disable=unused-variable
+
+import typing
+
+from . import analyze, app_plugins, util, vcg
+from .analyze import BaseName, Node, QName, SkipModel
+
+
+def aprime_gen(l: int, n: int) -> typing.Sequence[int]:
+ """Return an `l`-length sequence of nonnegative
+ integers such that any `n`-length-or-shorter combination of
+ members with repeats allowed can be uniquely identified by its
+ sum.
+
+ (If that were "product" instead of "sum", the obvious solution
+ would be the first `l` primes.)
+
+ """
+ seq = [1]
+ while len(seq) < l:
+ x = seq[-1] * n + 1
+ seq.append(x)
+ return seq
+
+
+def aprime_decompose(
+ aprimes: typing.Sequence[int], tot: int
+) -> tuple[typing.Collection[int], typing.Collection[int]]:
+ ret_idx = []
+ ret_val = []
+ while tot:
+ idx = max(i for i in range(len(aprimes)) if aprimes[i] <= tot)
+ val = aprimes[idx]
+ ret_idx.append(idx)
+ ret_val.append(val)
+ tot -= val
+ return ret_idx, ret_val
+
+
+def aprime_assert(
+ aprimes: typing.Sequence[int], act_sum: int, exp_idxs: typing.Collection[int]
+) -> None:
+ act_idxs, act_vals = aprime_decompose(aprimes, act_sum)
+ exp_sum = sum(aprimes[i] for i in exp_idxs)
+ # exp_vals = [aprimes[i] for i in exp]
+
+ act_str = f"{act_sum}:{[f's[{v}]' for v in sorted(act_idxs)]}"
+ exp_str = f"{exp_sum}:{[f's[{v}]' for v in sorted(exp_idxs)]}"
+ if act_str != exp_str:
+ assert f"act={act_str}" == f"exp={exp_str}"
+
+
+def test_assert_msg_fail() -> None:
+ num_funcs = 7
+ max_call_depth = 7
+ s = aprime_gen(num_funcs, max_call_depth)
+
+ class TestApplication:
+ def extra_nodes(self) -> typing.Collection[Node]:
+ # 1 2 3 4 5 6 7 <= call_depth
+ # - main() s[0]
+ # - __assert_msg_fail() s[1] *
+ # - __lm_light_printf() s[3]
+ # - fmt_vfctprintf() s[6]
+ # - stdio_putchar() s[5]
+ # - __assert_msg_fail() s[1] **
+ # - __lm_abort() s[2]
+ # - stdio_flush() s[4] (inconsequential)
+ # - __lm_abort() s[2] (inconsequential)
+ # ----
+ # sum(s[i] for i in [0, 1, 3, 6, 5, 1, 2])
+ ret = [
+ # main.c
+ util.synthetic_node("main", s[0], {"__assert_msg_fail"}),
+ # assert.c
+ util.synthetic_node(
+ "__assert_msg_fail", s[1], {"__lm_light_printf", "__lm_abort"}
+ ),
+ # intercept.c / libfmt/libmisc.c
+ util.synthetic_node("__lm_abort", s[2]),
+ util.synthetic_node(
+ "__lm_light_printf", s[3], {"fmt_vfctprintf", "stdio_flush"}
+ ),
+ util.synthetic_node("stdio_flush", s[4]),
+ util.synthetic_node("stdio_putchar", s[5], {"__assert_msg_fail"}),
+ # printf.c
+ util.synthetic_node("fmt_vfctprintf", s[6], {"stdio_putchar"}),
+ ]
+ assert num_funcs == len(s) == len(ret) == len(set(n.nstatic for n in ret))
+ return ret
+
+ def indirect_callees(
+ self, elem: vcg.VCGElem
+ ) -> tuple[typing.Collection[QName], bool]:
+ return [], False
+
+ def skipmodels(self) -> dict[BaseName, SkipModel]:
+ models = app_plugins.LibMiscPlugin(arg_c_fnames=[]).skipmodels()
+ assert BaseName("__assert_msg_fail") in models
+ orig_model = models[BaseName("__assert_msg_fail")]
+
+ def wrapped_model_fn(chain: typing.Sequence[QName], call: QName) -> bool:
+ assert len(chain) > 1
+ assert chain[-1] == QName("__assert_msg_fail")
+ assert (
+ "=>".join(str(c) for c in chain)
+ == "__assert_msg_fail=>__lm_light_printf=>fmt_vfctprintf=>stdio_putchar=>__assert_msg_fail"
+ )
+ assert call in [QName("__lm_light_printf"), QName("__lm_abort")]
+ return orig_model.fn(chain, call)
+
+ models[BaseName("__assert_msg_fail")] = SkipModel(
+ orig_model.nchain, wrapped_model_fn
+ )
+ return models
+
+ def test_filter(name: QName) -> tuple[int, bool]:
+ if name.base() == BaseName("main"):
+ return 1, True
+ return 0, False
+
+ result = analyze.analyze(
+ ci_fnames=[],
+ app_func_filters={
+ "Main": test_filter,
+ },
+ app=TestApplication(),
+ cfg_max_call_depth=max_call_depth,
+ )
+
+ aprime_assert(
+ s, result.groups["Main"].rows[QName("main")].nstatic, [0, 1, 3, 6, 5, 1, 2]
+ )