Repository: NTNU-IHB/PythonFMU (python)

Issue:
Rename internal getters and setters
Rename internal getters and setter which uses \_\_XXX__ method names to __XXX 

Pull request description:
Getter setter improvements
Fix #23 

Additional actions:
- Use more pythonic behavior (getter returns the variable list)
- Define simpler `get_value` and `set_value` method to simplify interfacing with third-party Python code - instead of having to override all `get_` and `set_` methods.

Patch:
diff --git a/pythonfmu/fmi2slave.py b/pythonfmu/fmi2slave.py
index 756ddcb..bdb89ad 100644
--- a/pythonfmu/fmi2slave.py
+++ b/pythonfmu/fmi2slave.py
@@ -1,7 +1,7 @@
 import datetime
 from abc import ABC, abstractmethod
 from collections import namedtuple, OrderedDict
-from typing import ClassVar, Dict, List, Optional
+from typing import Any, ClassVar, Dict, List, Optional
 from uuid import uuid1
 from xml.etree.ElementTree import Element, SubElement
 
@@ -10,7 +10,7 @@ from .variables import Boolean, Integer, Real, ScalarVariable, String
 
 ModelOptions = namedtuple('ModelOptions', ['name', 'value', 'cli'])
 
-FMI2_MODEL_OPTIONS = [
+FMI2_MODEL_OPTIONS: List[ModelOptions] = [
     ModelOptions("needsExecutionTool", True, "no-external-tool"),
     ModelOptions("canHandleVariableCommunicationStepSize", True, "no-variable-step"),
     ModelOptions("canInterpolateInputs", False, "interpolate-inputs"),
@@ -34,8 +34,8 @@ class Fmi2Slave(ABC):
     def __init__(self, instance_name: str):
         self.vars = OrderedDict()
         self.instance_name = instance_name
-        if self.modelName is None:
-            self.modelName = self.__class__.__name__
+        if self.__class__.modelName is None:
+            self.__class__.modelName = self.__class__.__name__
 
     def to_xml(self, model_options: Dict[str, str] = dict()) -> Element:
         """Build the XML representation of the model.
@@ -98,17 +98,16 @@ class Fmi2Slave(ABC):
         return root
 
     def __apply_start_value(self, var: ScalarVariable):
-        refs = [None]
         vrs = [var.value_reference]
 
         if isinstance(var, Integer):
-            self.__get_integer__(vrs, refs)
+            refs = self.get_integer(vrs)
         elif isinstance(var, Real):
-            self.__get_real__(vrs, refs)
+            refs = self.get_real(vrs)
         elif isinstance(var, Boolean):
-            self.__get_boolean__(vrs, refs)
+            refs = self.get_boolean(vrs)
         elif isinstance(var, String):
-            self.__get_string__(vrs, refs)
+            refs = self.get_string(vrs)
         else:
             raise Exception(f"Unsupported type!")
 
@@ -139,74 +138,94 @@ class Fmi2Slave(ABC):
     def terminate(self):
         pass
 
-    def __get_integer__(self, vrs: List[int], refs: List[int]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def get_value(self, name: str) -> Any:
+        """Generic variable getter.
+        
+        Args:
+            name (str): Name of the variable
+
+        Returns:
+            (Any) Value of the variable
+        """
+        return getattr(self, name)
+
+    def set_value(self, name: str, value: Any):
+        """Generic variable setter.
+        
+        Args:
+            name (str): Name of the variable
+            value (Any): Value of the variable
+        """
+        setattr(self, name, value)
+
+    def get_integer(self, vrs: List[int]) -> List[int]:
+        refs = list()
+        for vr in vrs:
             var = self.vars[vr]
             if isinstance(var, Integer):
-                refs[i] = getattr(self, var.name)
+                refs.append(int(self.get_value(var.name)))
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Integer!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Integer!")
+        return refs
 
-    def __get_real__(self, vrs: List[int], refs: List[float]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def get_real(self, vrs: List[int]) -> List[float]:
+        refs = list()
+        for vr in vrs:
             var = self.vars[vr]
             if isinstance(var, Real):
-                refs[i] = getattr(self, var.name)
+                refs.append(float(self.get_value(var.name)))
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Real!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Real!")
+        return refs
 
-    def __get_boolean__(self, vrs: List[int], refs: List[bool]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def get_boolean(self, vrs: List[int]) -> List[bool]:
+        refs = list()
+        for vr in vrs:
             var = self.vars[vr]
             if isinstance(var, Boolean):
-                refs[i] = getattr(self, var.name)
+                refs.append(bool(self.get_value(var.name)))
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Boolean!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Boolean!")
+        return refs
 
-    def __get_string__(self, vrs: List[int], refs: List[str]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def get_string(self, vrs: List[int]) -> List[str]:
+        refs = list()
+        for vr in vrs:
             var = self.vars[vr]
             if isinstance(var, String):
-                refs[i] = getattr(self, var.name)
+                refs.append(str(self.get_value(var.name)))
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type String!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type String!")
+        return refs
 
-    def __set_integer__(self, vrs: List[int], values: List[int]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def set_integer(self, vrs: List[int], values: List[int]):
+        for vr, value in zip(vrs, values):
             var = self.vars[vr]
             if isinstance(var, Integer):
-                setattr(self, var.name, values[i])
+                self.set_value(var.name, value)
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Integer!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Integer!")
 
-    def __set_real__(self, vrs: List[int], values: List[float]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def set_real(self, vrs: List[int], values: List[float]):
+        for vr, value in zip(vrs, values):
             var = self.vars[vr]
             if isinstance(var, Real):
-                setattr(self, var.name, values[i])
+                self.set_value(var.name, value)
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Real!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Real!")
 
-    def __set_boolean__(self, vrs: List[int], values: List[bool]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def set_boolean(self, vrs: List[int], values: List[bool]):
+        for vr, value in zip(vrs, values):
             var = self.vars[vr]
             if isinstance(var, Boolean):
-                setattr(self, var.name, values[i])
+                self.set_value(var.name, value)
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type Boolean!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type Boolean!")
 
-    def __set_string__(self, vrs: List[int], values: List[str]):
-        for i in range(len(vrs)):
-            vr = vrs[i]
+    def set_string(self, vrs: List[int], values: List[str]):
+        for vr, value in zip(vrs, values):
             var = self.vars[vr]
             if isinstance(var, String):
-                setattr(self, var.name, values[i])
+                self.set_value(var.name, value)
             else:
-                raise Exception(f"Variable with valueReference={vr} is not of type String!")
+                raise TypeError(f"Variable with valueReference={vr} is not of type String!")
diff --git a/pythonfmu/pythonfmu-export/cpp/PyObjectWrapper.cpp b/pythonfmu/pythonfmu-export/cpp/PyObjectWrapper.cpp
index 1a187c7..84b0ffa 100644
--- a/pythonfmu/pythonfmu-export/cpp/PyObjectWrapper.cpp
+++ b/pythonfmu/pythonfmu-export/cpp/PyObjectWrapper.cpp
@@ -130,93 +130,77 @@ void PyObjectWrapper::terminate()
 void PyObjectWrapper::getInteger(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIInteger* values) const
 {
     PyObject* vrs = PyList_New(nvr);
-    PyObject* refs = PyList_New(nvr);
     for (int i = 0; i < nvr; i++) {
         PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
-        PyList_SetItem(refs, i, Py_BuildValue("i", 0));
     }
-    auto f = PyObject_CallMethod(pInstance_, "__get_integer__", "(OO)", vrs, refs);
+    auto refs = PyObject_CallMethod(pInstance_, "get_integer", "O", vrs);
     Py_DECREF(vrs);
-    if (f == nullptr) {
+    if (refs == nullptr) {
         handle_py_exception("[getInteger] PyObject_CallMethod");
     }
-    Py_DECREF(f);
 
     for (int i = 0; i < nvr; i++) {
         PyObject* value = PyList_GetItem(refs, i);
-        values[i] = static_cast<int>(PyLong_AsLong(value));
+        values[i] = static_cast<cppfmu::FMIInteger>(PyLong_AsLong(value));
     }
-
     Py_DECREF(refs);
 }
 
 void PyObjectWrapper::getReal(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIReal* values) const
 {
     PyObject* vrs = PyList_New(nvr);
-    PyObject* refs = PyList_New(nvr);
     for (int i = 0; i < nvr; i++) {
         PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
-        PyList_SetItem(refs, i, Py_BuildValue("d", 0.0));
     }
 
-    auto f = PyObject_CallMethod(pInstance_, "__get_real__", "(OO)", vrs, refs);
+    auto refs = PyObject_CallMethod(pInstance_, "get_real", "O", vrs);
     Py_DECREF(vrs);
-    if (f == nullptr) {
+    if (refs == nullptr) {
         handle_py_exception("[getReal] PyObject_CallMethod");
     }
-    Py_DECREF(f);
 
     for (int i = 0; i < nvr; i++) {
         PyObject* value = PyList_GetItem(refs, i);
         values[i] = PyFloat_AsDouble(value);
     }
-
     Py_DECREF(refs);
 }
 
 void PyObjectWrapper::getBoolean(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIBoolean* values) const
 {
     PyObject* vrs = PyList_New(nvr);
-    PyObject* refs = PyList_New(nvr);
     for (int i = 0; i < nvr; i++) {
         PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
-        PyList_SetItem(refs, i, Py_BuildValue("i", 0));
     }
-    auto f = PyObject_CallMethod(pInstance_, "__get_boolean__", "(OO)", vrs, refs);
+    auto refs = PyObject_CallMethod(pInstance_, "get_boolean", "O", vrs);
     Py_DECREF(vrs);
-    if (f == nullptr) {
+    if (refs == nullptr) {
         handle_py_exception("[getBoolean] PyObject_CallMethod");
     }
-    Py_DECREF(f);
 
     for (int i = 0; i < nvr; i++) {
         PyObject* value = PyList_GetItem(refs, i);
         values[i] = PyObject_IsTrue(value);
     }
-
     Py_DECREF(refs);
 }
 
 void PyObjectWrapper::getString(const cppfmu::FMIValueReference* vr, std::size_t nvr, cppfmu::FMIString* values) const
 {
     PyObject* vrs = PyList_New(nvr);
-    PyObject* refs = PyList_New(nvr);
     for (int i = 0; i < nvr; i++) {
         PyList_SetItem(vrs, i, Py_BuildValue("i", vr[i]));
-        PyList_SetItem(refs, i, Py_BuildValue("s", ""));
     }
-    auto f = PyObject_CallMethod(pInstance_, "__get_string__", "(OO)", vrs, refs);
+    auto refs = PyObject_CallMethod(pInstance_, "get_string", "O", vrs);
     Py_DECREF(vrs);
-    if (f == nullptr) {
+    if (refs == nullptr) {
         handle_py_exception("[getString] PyObject_CallMethod");
     }
-    Py_DECREF(f);
 
     for (int i = 0; i < nvr; i++) {
         PyObject* value = PyList_GetItem(refs, i);
         values[i] = PyBytes_AsString(PyUnicode_AsEncodedString(value, "utf-8", nullptr));
     }
-
     Py_DECREF(refs);
 }
 
@@ -229,7 +213,7 @@ void PyObjectWrapper::setInteger(const cppfmu::FMIValueReference* vr, std::size_
         PyList_SetItem(refs, i, Py_BuildValue("i", values[i]));
     }
 
-    auto f = PyObject_CallMethod(pInstance_, "__set_integer__", "(OO)", vrs, refs);
+    auto f = PyObject_CallMethod(pInstance_, "set_integer", "(OO)", vrs, refs);
     Py_DECREF(vrs);
     Py_DECREF(refs);
     if (f == nullptr) {
@@ -247,7 +231,7 @@ void PyObjectWrapper::setReal(const cppfmu::FMIValueReference* vr, std::size_t n
         PyList_SetItem(refs, i, Py_BuildValue("d", values[i]));
     }
 
-    auto f = PyObject_CallMethod(pInstance_, "__set_real__", "(OO)", vrs, refs);
+    auto f = PyObject_CallMethod(pInstance_, "set_real", "(OO)", vrs, refs);
     Py_DECREF(vrs);
     Py_DECREF(refs);
     if (f == nullptr) {
@@ -265,7 +249,7 @@ void PyObjectWrapper::setBoolean(const cppfmu::FMIValueReference* vr, std::size_
         PyList_SetItem(refs, i, PyBool_FromLong(values[i]));
     }
 
-    auto f = PyObject_CallMethod(pInstance_, "__set_boolean__", "(OO)", vrs, refs);
+    auto f = PyObject_CallMethod(pInstance_, "set_boolean", "(OO)", vrs, refs);
     Py_DECREF(vrs);
     Py_DECREF(refs);
     if (f == nullptr) {
@@ -283,7 +267,7 @@ void PyObjectWrapper::setString(const cppfmu::FMIValueReference* vr, std::size_t
         PyList_SetItem(refs, i, Py_BuildValue("s", value[i]));
     }
 
-    auto f = PyObject_CallMethod(pInstance_, "__set_string__", "(OO)", vrs, refs);
+    auto f = PyObject_CallMethod(pInstance_, "set_string", "(OO)", vrs, refs);
     Py_DECREF(vrs);
     Py_DECREF(refs);
     if (f == nullptr) {
diff --git a/pythonfmu/variables.py b/pythonfmu/variables.py
index ffe123b..1d169e8 100644
--- a/pythonfmu/variables.py
+++ b/pythonfmu/variables.py
@@ -136,7 +136,7 @@ class Boolean(ScalarVariable):
         attrib = dict()
         for key, value in self.__attrs.items():
             if value is not None:
-                attrib[key] = str(value)
+                attrib[key] = str(value).lower()
         parent = super().to_xml()
         SubElement(parent, "Boolean", attrib)
 


Test patch:
diff --git a/pythonfmu/tests/pythonslave.py b/pythonfmu/tests/pythonslave.py
index c5aea48..937679f 100644
--- a/pythonfmu/tests/pythonslave.py
+++ b/pythonfmu/tests/pythonslave.py
@@ -12,11 +12,20 @@ class PythonSlave(Fmi2Slave):
         super().__init__(instance_name)
 
         self.intParam = 42
+        self.intOut = 23
         self.realOut = 3.0
         self.booleanVariable = True
         self.stringVariable = "Hello World!"
+        self.realIn = 2./3.
+        self.booleanParameter = False
+        self.stringParameter = "dog"
         self.register_variable(
             Integer("intParam", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable))
+        self.register_variable(Real("realIn", causality=Fmi2Causality.input))
+        self.register_variable(Boolean("booleanParameter", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable))
+        self.register_variable(String("stringParameter", causality=Fmi2Causality.parameter, variability=Fmi2Variability.tunable))
+
+        self.register_variable(Integer("intOut", causality=Fmi2Causality.output))
         self.register_variable(Real("realOut", causality=Fmi2Causality.output))
         self.register_variable(Boolean("booleanVariable", causality=Fmi2Causality.local))
         self.register_variable(String("stringVariable", causality=Fmi2Causality.local))
diff --git a/pythonfmu/tests/test_builder.py b/pythonfmu/tests/test_builder.py
index f5dd586..bfd9678 100644
--- a/pythonfmu/tests/test_builder.py
+++ b/pythonfmu/tests/test_builder.py
@@ -30,8 +30,6 @@ lib_extension = ({"Darwin": "so", "Linux": "so", "Windows": "dll"}).get(
     platform.system(), None
 )
 
-# TODO test xml
-
 
 def test_zip_content(tmp_path):
     script_file = Path(__file__).parent / DEMO
diff --git a/pythonfmu/tests/test_fmi2slave.py b/pythonfmu/tests/test_fmi2slave.py
new file mode 100644
index 0000000..10c727d
--- /dev/null
+++ b/pythonfmu/tests/test_fmi2slave.py
@@ -0,0 +1,87 @@
+import pytest
+
+from pythonfmu import Fmi2Slave, Boolean, Integer, Real, String
+
+# TODO test xml
+
+FMI2PY = dict((
+    (Boolean, bool),
+    (Integer, int),
+    (Real, float),
+    (String, str)
+))
+PY2FMI = dict([(v, k) for k, v in FMI2PY.items()])
+
+
+@pytest.mark.parametrize("model", ["theModelName", None])
+def test_Fmi2Slave_constructor(model):
+    class Slave(Fmi2Slave):
+        modelName = model
+        def do_step(self, t, dt):
+            return True
+
+    if model is None:
+        _ = Slave("instanceSlave")
+        assert Slave.modelName == "Slave"
+    else:
+        _ = Slave("instanceSlave")
+        assert Slave.modelName == model
+
+@pytest.mark.parametrize("fmi_type", FMI2PY)
+@pytest.mark.parametrize("value", [
+    False, 
+    22, 
+    2./3., 
+    "hello_world"
+])
+def test_Fmi2Slave_getters(fmi_type, value):
+    
+    class Slave(Fmi2Slave):
+        def __init__(self, instance_name):
+            super().__init__(instance_name)
+            self.var = value
+            self.register_variable(PY2FMI[type(value)]("var"))
+
+        def do_step(self, t, dt):
+            return True
+    
+    py_type = FMI2PY[fmi_type]
+    fmi_type_name = fmi_type.__qualname__.lower()
+
+    slave = Slave("instanceSlave")
+    if type(value) is py_type:
+        assert getattr(slave, f"get_{fmi_type_name}")([0, ]) == [value, ]
+    else:
+        with pytest.raises(TypeError):
+            getattr(slave, f"get_{fmi_type_name}")([0, ])
+
+
+@pytest.mark.parametrize("fmi_type", FMI2PY)
+@pytest.mark.parametrize("value", [
+    False, 
+    22, 
+    2./3., 
+    "hello_world",
+])
+def test_Fmi2Slave_setters(fmi_type, value):
+    class Slave(Fmi2Slave):
+        def __init__(self, instance_name):
+            super().__init__(instance_name)
+            self.var = None
+            self.register_variable(PY2FMI[type(value)]("var"))
+
+        def do_step(self, t, dt):
+            return True
+    
+    slave = Slave("instanceSlave")
+    py_type = FMI2PY[fmi_type]
+    fmi_type_name = fmi_type.__qualname__.lower()
+
+    if type(value) is py_type:
+        set_method = getattr(slave, f"set_{fmi_type_name}")
+        set_method([0, ], [value, ])
+        assert getattr(slave, f"get_{fmi_type_name}")([0, ]) == [value, ]
+    else:
+        set_method = getattr(slave, f"set_{fmi_type_name}")
+        with pytest.raises(TypeError):
+            set_method([0, ], [value, ])
diff --git a/pythonfmu/tests/test_integration.py b/pythonfmu/tests/test_integration.py
index 99f3d88..93c52c3 100644
--- a/pythonfmu/tests/test_integration.py
+++ b/pythonfmu/tests/test_integration.py
@@ -28,6 +28,83 @@ def test_integration_demo(tmp_path):
     assert res["realOut"][-1] == pytest.approx(res["time"][-1], rel=1e-7)
 
 
+@pytest.mark.integration
+def test_integration_get(tmp_path):
+
+    script_file = Path(__file__).parent / DEMO
+
+    FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false")
+
+    fmu = tmp_path / "PythonSlave.fmu"
+    assert fmu.exists()
+    model = pyfmi.load_fmu(str(fmu))
+    
+    to_test = {
+        "intParam": 42,
+        "intOut": 23,
+        "realOut": 3.0,
+        "booleanVariable": True,
+        "stringVariable": "Hello World!",
+        "realIn": 2./3.,
+        "booleanParameter": False,
+        "stringParameter": "dog"
+    }
+
+    variables = model.get_model_variables()
+    for key, value in to_test.items():
+        var = variables[key]
+        if var.type == pyfmi.fmi.FMI2_INTEGER:
+            model_value = model.get_integer([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_REAL:
+            model_value = model.get_real([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_BOOLEAN:
+            model_value = model.get_boolean([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_STRING:
+            model_value = model.get_string([var.value_reference,])[0]
+        else:
+            pytest.xfail("Unsupported type")
+        
+        assert model_value == value
+
+
+@pytest.mark.integration
+def test_integration_set(tmp_path):
+
+    script_file = Path(__file__).parent / DEMO
+
+    FmuBuilder.build_FMU(script_file, dest=tmp_path, needsExecutionTool="false")
+
+    fmu = tmp_path / "PythonSlave.fmu"
+    assert fmu.exists()
+    model = pyfmi.load_fmu(str(fmu))
+    
+    to_test = {
+        "intParam": 20,
+        "realIn": 1./3.,
+        "booleanParameter": True,
+        "stringParameter": "cat"
+    }
+
+    variables = model.get_model_variables()
+    for key, value in to_test.items():
+        var = variables[key]
+        if var.type == pyfmi.fmi.FMI2_INTEGER:
+            model.set_integer([var.value_reference, ], [value, ])
+            model_value = model.get_integer([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_REAL:
+            model.set_real([var.value_reference, ], [value, ])
+            model_value = model.get_real([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_BOOLEAN:
+            model.set_boolean([var.value_reference, ], [value, ])
+            model_value = model.get_boolean([var.value_reference,])[0]
+        elif var.type == pyfmi.fmi.FMI2_STRING:
+            model.set_string([var.value_reference, ], [value, ])
+            model_value = model.get_string([var.value_reference,])[0]
+        else:
+            pytest.xfail("Unsupported type")
+        
+        assert model_value == value
+
 # TODO fmpy generate a Segmentation fault at line PyObject* sys_module = PyImport_ImportModule("sys"); in PyObjectWrapper
 # @pytest.mark.integration
 # def test_simple_integration_fmpy(tmp_path):
diff --git a/pythonfmu/tests/test_variables.py b/pythonfmu/tests/test_variables.py
index d613eb9..b77d48c 100644
--- a/pythonfmu/tests/test_variables.py
+++ b/pythonfmu/tests/test_variables.py
@@ -104,6 +104,7 @@ def test_Boolean_constructor(name, start):
 @pytest.mark.parametrize("name,start", [
     ("boolean_name", None),
     ("boolean_another_name", True),
+    ("boolean_yet_another", False),
 ])
 def test_Boolean_to_xml(name, start):
     r = Boolean(name, start)
@@ -111,7 +112,7 @@ def test_Boolean_to_xml(name, start):
     children = list(xml)
     assert len(children) == 1
     if start is not None:
-        assert children[0].attrib['start'] == str(start)
+        assert children[0].attrib['start'] == str(start).lower()
 
 @pytest.mark.parametrize("name,start", [
     ("integer_name", None),