aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_formula.py
blob: 8074e5adedae56b07cb974da1a32086b6d2e39da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# generat3D - evaluate Daz DSON scenes and write .dbz files
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import unittest

from generat3d import Error
from generat3d.formula import Graph


def push(key):
    return {"op": "push", "key": key}


def val(v):
    return {"op": "push", "val": v}


class GraphTest(unittest.TestCase):
    def setUp(self):
        self.g = Graph()
        self.g.add_prop("a", 0.5)
        self.g.add_prop("b")
        self.g.add_prop("c", 0.0, 0.0, 1.0, clamped=True)

    def test_base_values(self):
        self.assertEqual(self.g.evaluate(), {"a": 0.5, "b": 0.0, "c": 0.0})

    def test_sum_stage(self):
        self.g.add_formula("b", [push("a"), val(2), {"op": "mult"}])
        self.g.add_formula("b", [push("a"), val(1), {"op": "add"}])
        self.assertEqual(self.g.evaluate()["b"], 1.0 + 1.5)

    def test_mult_stage(self):
        self.g.set_value("b", 3.0)
        self.g.add_formula("b", [push("a")], stage="mult")
        self.assertEqual(self.g.evaluate()["b"], 1.5)

    def test_chain_and_clamp(self):
        self.g.add_formula("b", [push("a"), val(4), {"op": "mult"}])
        self.g.add_formula("c", [push("b")])
        out = self.g.evaluate()
        self.assertEqual(out["b"], 2.0)
        self.assertEqual(out["c"], 1.0)

    def test_sub_div_neg(self):
        self.g.add_formula("b", [val(1), push("a"), {"op": "sub"}, val(4), {"op": "div"},
                                 {"op": "neg"}])
        self.assertEqual(self.g.evaluate()["b"], -0.125)

    def test_unknown_input_drops_formula(self):
        self.g.add_formula("b", [push("missing")])
        self.g.add_formula("b", [val(7)])
        self.assertEqual(self.g.evaluate()["b"], 7)

    def test_output_without_prop_is_ignored(self):
        self.g.add_formula("nowhere", [val(1)])
        self.assertNotIn("nowhere", self.g.evaluate())

    def test_cycle_is_fatal(self):
        self.g.add_formula("a", [push("b")])
        self.g.add_formula("b", [push("a")])
        with self.assertRaisesRegex(Error, "formula cycle: a -> b -> a"):
            self.g.evaluate()

    def test_unknown_op_is_fatal(self):
        self.g.add_formula("b", [val(1), {"op": "pow"}], source="/data/x.dsf#m")
        with self.assertRaisesRegex(Error, r"/data/x.dsf#m: unknown formula operation 'pow'"):
            self.g.evaluate()

    def test_unknown_stage_is_fatal(self):
        with self.assertRaisesRegex(Error, "unknown formula stage 'avg'"):
            self.g.add_formula("b", [val(1)], stage="avg")

    def test_stack_errors_are_fatal(self):
        self.g.add_formula("b", [{"op": "add"}], source="f")
        with self.assertRaisesRegex(Error, "f: malformed formula for b"):
            self.g.evaluate()

    def test_leftover_stack_is_fatal(self):
        self.g.add_formula("b", [val(1), val(2)], source="f")
        with self.assertRaisesRegex(Error, "f: malformed formula for b"):
            self.g.evaluate()