import pytest from math import inf from struct import unpack import pandemonium from pandemonium import ( Vector3, GDString, NodePath, Object, Node, CanvasItem, Node2D, PluginScript, OpenSimplexNoise, OS, Error, OK, exposed, ) @exposed class virtualtestbedcls(Node): def _to_string(self): # Implemented for test_bindings::test_virtual_to_string_customize return GDString("
") def _notification(self, what): on_notification = getattr(self, "on_notification", None) if on_notification: on_notification(what) def test_free_node(): v = Node.new() v.free() # `check_memory_leak` auto fixture will do the bookkeeping def test_expose_contains_constant(): assert "OK" in dir(pandemonium) assert OK is not None def test_expose_contains_class(): assert "Node" in dir(pandemonium) assert Node is not None def test_expose_contains_builtins(): assert "Vector3" in dir(pandemonium) assert Vector3 is not None def test_call_one_arg_short(current_node): with pytest.raises(TypeError) as exc: current_node.get_child() assert str(exc.value) == "get_child() takes exactly one argument (0 given)" def test_call_too_few_args(current_node): with pytest.raises(TypeError) as exc: current_node.move_child() assert str(exc.value) == "move_child() takes exactly 2 positional arguments (0 given)" def test_call_with_defaults_and_too_few_args(current_node): with pytest.raises(TypeError) as exc: current_node.add_child() assert str(exc.value) == "add_child() takes at least 1 positional argument (0 given)" def test_call_none_in_base_type_args(current_node): with pytest.raises(TypeError) as exc: # signature: def get_child(self, pandemonium_int idx) current_node.get_child(None) assert str(exc.value) == "an integer is required" def test_call_none_in_builtin_args(current_node): with pytest.raises(TypeError) as exc: # signature: def get_node(self, NodePath path not None) current_node.get_node(None) assert str(exc.value) == "Invalid value None, must be str or NodePath" def test_call_none_in_bindings_args(current_node): with pytest.raises(TypeError) as exc: # signature: def get_path_to(self, Node node not None) current_node.get_path_to(None) assert ( str(exc.value) == "Argument 'node' has incorrect type (expected pandemonium.bindings.Node, got NoneType)" ) def test_call_too_many_args(current_node): with pytest.raises(TypeError) as exc: current_node.get_child(1, 2) assert str(exc.value) == "get_child() takes exactly one argument (2 given)" def test_call_with_default_and_too_many_args(current_node): with pytest.raises(TypeError) as exc: current_node.add_child(1, 2, 3) assert str(exc.value) == "add_child() takes at most 2 positional arguments (3 given)" def test_call_with_defaults(generate_obj): node = generate_obj(Node) child = generate_obj(Node) # signature: void add_child(Node node, bool legible_unique_name=false) node.add_child(child) # legible_unique_name is False by default, check name is not human-redable children_names = [str(x.name) for x in node.get_children()] assert children_names == ["@@2"] def test_call_returns_enum(generate_obj): node = generate_obj(Node) ret = node.connect("foo", node, "bar") assert isinstance(ret, Error) def test_call_with_kwargs(generate_obj): node = generate_obj(Node) child = generate_obj(Node) new_child = generate_obj(Node) node.add_child(child, legible_unique_name=True) # Check name is readable children_names = [str(x.name) for x in node.get_children()] assert children_names == ["Node"] # Kwargs are passed out of order node.add_child_below_node(legible_unique_name=True, child_node=new_child, node=node) # Check names are still readable children_names = [str(x.name) for x in node.get_children()] assert children_names == ["Node", "Node2"] def test_inheritance(generate_obj): node = generate_obj(Node) # CanvasItem is a direct subclass of Node canvas_item = generate_obj(CanvasItem) assert isinstance(node, Object) assert isinstance(canvas_item, Object) assert isinstance(canvas_item, Node) # TODO: headless server end up with a static memory leak # when instanciating a Node2D... if not OS.has_feature("Server"): # Node2D is a grand child subclass of Node node2d = generate_obj(Node2D) assert isinstance(node, Object) assert isinstance(node2d, Object) assert isinstance(node2d, Node) def test_call_with_refcounted_return_value(current_node): script = current_node.get_script() assert isinstance(script, PluginScript) def test_call_with_refcounted_param_value(generate_obj): node = generate_obj(Node) script = PluginScript() node.set_script(script) def test_access_property(generate_obj): node = generate_obj(Node) path = NodePath("/foo/bar") node._import_path = path assert node._import_path == path @pytest.mark.xfail(reason="Create Python class from Python not implemented yet") def test_new_on_overloaded_class(generate_obj): node = generate_obj(virtualtestbedcls) # Make sure doing MyClass.new() doesn't return an instance of the # Pandemonium class we inherit from assert isinstance(node, virtualtestbedcls) @pytest.mark.xfail(reason="Create Python class from Python not implemented yet") def test_virtual_call_overloaded_notification(generate_obj): node = generate_obj(virtualtestbedcls) notifications = [] def _on_notification(what): notifications.append(what) node.on_notification = _on_notification try: node.notification(1) node.notification(2) node.notification(3) finally: node.on_notification = None assert notifications == [1, 2, 3] @pytest.mark.xfail(reason="Pluginscript doesn't support _to_string overloading") def test_virtual_to_string_customize(generate_obj): node = generate_obj(virtualtestbedcls) # Object.to_string() can be customized when defining _to_string() expected = GDString("
") assert node._to_string() == expected assert node.to_string() == expected # Try to access undefined _to_string node = generate_obj(Node) with pytest.raises(AttributeError): node._to_string() @pytest.fixture(params=["pandemonium_class", "python_subclass"]) def node_for_access(request, current_node, generate_obj): if request.param == "pandemonium_class": return generate_obj(Node) else: return current_node @pytest.mark.xfail(reason="Current implement uses Object.callv which doesn't inform of the failure") def test_virtual_call__to_string_not_customized(node_for_access): with pytest.raises(AttributeError): node_for_access._to_string() @pytest.mark.xfail(reason="Current implement uses Object.callv which doesn't inform of the failure") def test_virtual_call__notification_not_customized(node_for_access): with pytest.raises(AttributeError): node_for_access._notification(42) def test_access_unknown_attribute(node_for_access): with pytest.raises(AttributeError): node_for_access.dummy def test_call_unknown_method(node_for_access): with pytest.raises(AttributeError): node_for_access.dummy(42) def test_create_refcounted_value(): script1_ref1 = PluginScript() script2_ref1 = PluginScript() script1_ref2 = script1_ref1 script2_ref2 = script2_ref1 del script1_ref1 def test_late_initialized_bindings_and_float_param_ret(): # OpenSimplexNoise is refcounted, so no need to create it with `generate_obj` obj = OpenSimplexNoise() # Float are tricky given they must be converted back and forth to double ret = obj.get_noise_1d(inf) assert ret == 0 # Build a double number that cannot be reprented on a float double_only_number, = unpack("!d", b"\x11" * 8) ret = obj.get_noise_1d(double_only_number) assert ret == pytest.approx(-0.02726514) # Now try with better parameter to have a correct return value ret = obj.get_noise_3d(100, 200, 300) assert ret == pytest.approx(-0.10482934) def test_bad_meth_to_create_non_refcounted_object(): with pytest.raises(RuntimeError): Node() def test_bad_meth_to_create_refcounted_object(): with pytest.raises(RuntimeError): OpenSimplexNoise.new()