TestsNode.test_updating_outputs()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nop 1
dl 0
loc 10
rs 9.95
c 0
b 0
f 0
1
# -*- coding: utf-8 -
2
3
"""Test the created constraints against approved constraints.
4
5
This file is part of project oemof (github.com/oemof/oemof). It's copyrighted
6
by the contributors recorded in the version control history of the file,
7
available from its original location oemof/tests/test_network_classes.py
8
9
SPDX-FileCopyrightText: Stephan Günther <>
10
SPDX-FileCopyrightText: Uwe Krien <[email protected]>
11
SPDX-FileCopyrightText: Simon Hilpert <>
12
SPDX-FileCopyrightText: Cord Kaldemeyer <>
13
14
SPDX-License-Identifier: MIT
15
"""
16
17
from traceback import format_exception_only as feo
18
19
import pytest
20
21
from oemof.network.energy_system import EnergySystem as EnSys
22
from oemof.network.network import Bus
23
from oemof.network.network import Edge
24
from oemof.network.network import Node
25
from oemof.network.network import Transformer
26
from oemof.network.network import registry_changed_to
27
from oemof.network.network import temporarily_modifies_registry
28
29
30
class TestsNode:
31
    def setup(self):
32
        self.energysystem = EnSys()
33
        Node.registry = self.energysystem
34
35
    def test_that_attributes_cannot_be_added(self):
36
        node = Node()
37
        with pytest.raises(AttributeError):
38
            node.foo = "bar"
39
40
    def test_symmetric_input_output_assignment(self):
41
        n1 = Node(label="<N1>")
42
43
        n2 = Node(label="<From N1>", inputs=[n1])
44
        assert n1 in n2.inputs, (
45
            "{0} not in {1}.inputs, ".format(n1, n2)
46
            + "although it should be by construction."
47
        )
48
49
        assert (
50
            n2 in n1.outputs
51
        ), "{0} in {1}.inputs but {1} not in {0}.outputs.".format(n1, n2)
52
53
        n3 = Node(label="<To N1>", outputs=[n1])
54
        assert n1 in n3.outputs, (
55
            "{0} not in {1}.outputs, ".format(n1, n3)
56
            + "although it should be by construction.",
57
        )
58
        assert n3 in n1.inputs, (
59
            "{0} in {1}.outputs but {1} not in {0}.inputs.".format(n1, n3),
60
        )
61
62
    def test_accessing_outputs_of_a_node_without_output_flows(self):
63
        n = Node()
64
        exception = None
65
        outputs = None
66
        try:
67
            outputs = n.outputs
68
        except Exception as e:
69
            exception = e
70
        assert exception is None, (
71
            "\n  Test accessing `outputs` on {} having no outputs.".format(n)
72
            + "\n  Got unexpected exception:\n"
73
            + "\n      {}".format(feo(type(exception), exception)[0]),
74
        )
75
        assert len(outputs) == 0, (
76
            "\n  Failure when testing `len(outputs)`."
77
            + "\n  Expected: 0."
78
            + "\n  Got     : {}".format(len(outputs)),
79
        )
80
81
    def test_accessing_inputs_of_a_node_without_input_flows(self):
82
        n = Node()
83
        exception = None
84
        inputs = None
85
        try:
86
            inputs = n.inputs
87
        except Exception as e:
88
            exception = e
89
        assert exception is None, (
90
            "\n  Test accessing `inputs` on {} having no inputs.".format(n)
91
            + "\n  Got unexpected exception:\n"
92
            + "\n      {}".format(feo(type(exception), exception)[0]),
93
        )
94
        assert len(inputs) == 0, (
95
            "\n  Failure when testing `len(inputs)`."
96
            + "\n  Expected: 0."
97
            + "\n  Got     : {}".format(len(inputs)),
98
        )
99
100
    def test_that_the_outputs_attribute_of_a_node_is_a_mapping(self):
101
        n = Node()
102
        exception = None
103
        try:
104
            n.outputs.values()
105
        except AttributeError as e:
106
            exception = e
107
        assert exception is None, (
108
            "\n  Test accessing `outputs.values()`"
109
            + " on {} having no inputs.".format(n)
110
            + "\n  Got unexpected exception:\n"
111
            + "\n      {}".format(feo(type(exception), exception)[0]),
112
        )
113
114
    def test_that_nodes_do_not_get_undead_flows(self):
115
        """Newly created nodes should only have flows assigned to them.
116
117
        A new node `n`, which re-used a previously used label `l`, retained the
118
        flows of those nodes which where labeled `l` before `n`. This incorrect
119
        behaviour is a problem if somebody wants to use different nodes with
120
        the same label in multiple energy systems. While this feature currently
121
        isn't used, it also lead to weird behaviour when running tests.
122
123
        This test ensures that new nodes only have those flows which are
124
        assigned to them on construction.
125
        """
126
        # We don't want a registry as we are re-using a label on purpose.
127
        # Having a registry would just throw and error generated by the DEFAULT
128
        # grouping in this case.
129
        Node.registry = None
130
        flow = object()
131
        old = Node(label="A reused label")
132
        bus = Bus(label="bus", inputs={old: flow})
133
        assert bus.inputs[old].flow == flow, (
134
            ("\n  Expected: {0}" + "\n  Got     : {1} instead").format(
135
                flow, bus.inputs[old].flow
136
            ),
137
        )
138
        assert old.outputs[bus].flow == flow, (
139
            ("\n  Expected: {}" + "\n  Got     : {} instead").format(
140
                flow, old.outputs[bus].flow
141
            ),
142
        )
143
        new = Node(label="A reused label")
144
        assert new.outputs == {}, (
145
            "\n  Expected an empty dictionary of outputs."
146
            + "\n  Got: {} instead".format(new.outputs),
147
        )
148
149
    def test_modifying_outputs_after_construction(self):
150
        """One should be able to add and delete outputs of a node."""
151
        node = Node("N1")
152
        bus = Node("N2")
153
        flow = "flow"
154
        assert node.outputs == {}, (
155
            "\n  Expected an empty dictionary of outputs."
156
            + "\n  Got: {} (== {}) instead".format(
157
                node.outputs, dict(node.outputs)
158
            ),
159
        )
160
        node.outputs[bus] = flow
161
        assert node.outputs == {bus: flow}, (
162
            "\n  Expected {} as `node.outputs`."
163
            + "\n  Got    : {} (== {}) instead"
164
        ).format({bus: flow}, node.outputs, dict(node.outputs))
165
166
        assert node.outputs[bus] == flow, (
167
            "\n  Expected {} as `node.outputs[bus]`."
168
            + "\n  Got    : {} instead"
169
        ).format(flow, node.outputs[bus])
170
171
        del node.outputs[bus]
172
        assert node.outputs == {}, (
173
            "\n  Expected an empty dictionary of outputs."
174
            + "\n  Got: {} (== {}) instead"
175
        ).format(node.outputs, dict(node.outputs))
176
177
    def test_modifying_inputs_after_construction(self):
178
        """One should be able to add and delete inputs of a node."""
179
        node = Node("N1")
180
        bus = Bus("N2")
181
        flow = "flow"
182
183
        assert node.inputs == {}, (
184
            "\n  Expected an empty dictionary of inputs."
185
            + "\n  Got: {} (== {}) instead"
186
        ).format(node.inputs, dict(node.inputs))
187
188
        node.inputs[bus] = flow
189
        assert node.inputs == {bus: flow}, (
190
            "\n  Expected {} as `node.inputs`."
191
            + "\n  Got    : {} (== {}) instead"
192
        ).format({bus: flow}, node.inputs, dict(node.inputs))
193
        assert node.inputs[bus] == flow, (
194
            "\n  Expected {} as `node.inputs[bus]`."
195
            + "\n  Got    : {} instead"
196
        ).format(flow, node.inputs[bus])
197
        del node.inputs[bus]
198
        assert node.inputs == {}, (
199
            "\n  Expected an empty dictionary of inputs."
200
            + "\n  Got: {} (== {}) instead"
201
        ).format(node.inputs, dict(node.inputs))
202
203
    def test_output_input_symmetry_after_modification(self):
204
        n1 = Node("N1")
205
        n2 = Node("N2")
206
        flow = "flow"
207
208
        n1.outputs[n2] = flow
209
        assert n2.inputs == {n1: flow}
210
211
    def test_input_output_symmetry_after_modification(self):
212
        n1 = Node("N1")
213
        n2 = Node("N2")
214
        flow = "flow"
215
216
        n1.inputs[n2] = flow
217
        assert n2.outputs == {n1: flow}
218
219
    def test_updating_inputs(self):
220
        n1 = Node("N1")
221
        n2 = Node("N2")
222
        n1n2 = "n1n2"
223
224
        n2.inputs.update({n1: n1n2})
225
        assert n2.inputs == {n1: n1n2}
226
        assert n2.inputs[n1] == n1n2
227
        assert n1.outputs == {n2: n1n2}
228
        assert n1.outputs[n2] == n1n2
229
230
    def test_updating_outputs(self):
231
        n1 = Node("N1")
232
        n2 = Node("N2")
233
        n1n2 = "n1n2"
234
235
        n1.outputs.update({n2: n1n2})
236
        assert n2.inputs == {n1: n1n2}
237
        assert n2.inputs[n1] == n1n2
238
        assert n1.outputs == {n2: n1n2}
239
        assert n1.outputs[n2] == n1n2
240
241
    def test_error_for_duplicate_label_argument(self):
242
        """
243
        `Node.__init__` should fail if positional and keyword args collide.
244
        """
245
        with pytest.raises(TypeError):
246
            Node("Positional Label", label="Keyword Label")
247
248
    def test_node_input_output_type_assertions(self):
249
        """
250
        `Node`s should only accept `Node` instances as input/output targets.
251
        """
252
        with pytest.raises(AssertionError):
253
            Node("A node with an output", outputs={"Not a Node": "A Flow"})
254
            Node("A node with an input", inputs={"Not a Node": "A Flow"})
255
256
    def test_node_label_without_private_attribute(self):
257
        """
258
        A `Node` with no explicit `label` doesn't have a `_label` attribute.
259
        """
260
        n = Node()
261
        with pytest.raises(AttributeError):
262
            n._label
263
264
    def test_node_label_if_its_not_explicitly_specified(self):
265
        """If not explicitly given, a `Node`'s label is based on its `id`."""
266
        n = Node()
267
        assert "0x{:x}>".format(id(n)) in n.label
268
269
270
class TestsEdge:
271
    def setup(self):
272
        Node.registry = None
273
274
    def test_edge_construction_side_effects(self):
275
        """Constructing an `Edge` should affect it's input/output `Node`s.
276
277
        When constructing an `Edge`, the `inputs` and `outputs` of its output
278
        and input `Node`s should be set appropriately.
279
        """
280
        source = Node(label="source")
281
        target = Node(label="target")
282
        edge = Edge(input=source, output=target)
283
        assert target in source.outputs, (
284
            "{} not in {} after constructing {}.".format(
285
                target, source.outputs, edge
286
            ),
287
        )
288
        assert source in target.inputs, (
289
            "{} not in {} after constructing {}.".format(
290
                source, target.outputs, edge
291
            ),
292
        )
293
294
    def test_label_as_positional_argument(self):
295
        o = object()
296
        n = Node(o)
297
        assert n.label is o, (
298
            "Setting `label` as positional parameter argument failed."
299
            "\n  Expected: {!r}"
300
            "\n  Got     : {!r}"
301
        ).format(o, n.label)
302
303
    def test_edge_failure_for_colliding_arguments(self):
304
        """
305
        `Edge` initialisation fails when colliding arguments are supplied.
306
        """
307
        with pytest.raises(ValueError):
308
            Edge(flow=object(), values=object())
309
310
    def test_alternative_edge_construction_from_mapping(self):
311
        """`Edge.from_object` treats mappings as keyword arguments."""
312
        i, o, f = (Node("input"), Node("output"), "flow")
313
        with pytest.raises(ValueError):
314
            Edge.from_object({"flow": i, "values": o})
315
        edge = Edge.from_object({"input": i, "output": o, "flow": f})
316
        assert edge.input == i
317
        assert edge.output == o
318
        assert edge.values == f
319
        assert edge.flow == f
320
321
    def test_flow_setter(self):
322
        """`Edge.flow`'s setter relays to `values`."""
323
        e = Edge(values="initial values")
324
        assert e.flow == "initial values"
325
        assert e.values == "initial values"
326
        e.flow = "new values set via `e.flow`"
327
        assert e.flow == "new values set via `e.flow`"
328
        assert e.values == "new values set via `e.flow`"
329
330
    def test_delayed_registration_when_setting_input(self):
331
        """`Edge` registration gets delayed until input and output are set."""
332
        i, o = (Node("input"), Node("output"))
333
        with registry_changed_to(EnSys()):
334
            e = Edge(output=o)
335
            assert e not in Node.registry.groups.values()
336
            e.input = i
337
            assert e in Node.registry.groups.values()
338
339
340
class TestsEnergySystemNodesIntegration:
341
    def setup(self):
342
        self.es = EnSys()
343
        Node.registry = self.es
344
345
    def test_node_registration(self):
346
        assert Node.registry == self.es
347
        b1 = Bus(label="<B1>")
348
        assert self.es.entities[0] == b1
349
        b2 = Bus(label="<B2>")
350
        assert self.es.entities[1] == b2
351
        t1 = Transformer(label="<TF1>", inputs=[b1], outputs=[b2])
352
        assert t1 in self.es.entities
353
354
    def test_registry_modification_decorator(self):
355
        Node("registered")
356
        assert "registered" in self.es.groups
357
358
        @temporarily_modifies_registry
359
        def create_a_node():
360
            Node("not registered")
361
362
        create_a_node()
363
        assert "not registered" not in self.es.groups
364