From 995783b6bb2e664ec379f57cc10b594eebeee624 Mon Sep 17 00:00:00 2001 From: Giles Knap Date: Fri, 14 Jul 2023 13:23:50 +0000 Subject: [PATCH] all tests working --- src/ibek/ioc.py | 8 +-- tests/samples/generate_samples.sh | 12 ++++ tests/samples/pydantic/generate_samples.sh | 23 ------ tests/test_cli.py | 14 ++-- tests/test_render.py | 83 +++++++++++++--------- 5 files changed, 75 insertions(+), 65 deletions(-) delete mode 100755 tests/samples/pydantic/generate_samples.sh diff --git a/src/ibek/ioc.py b/src/ibek/ioc.py index 339045361..43e84bda2 100644 --- a/src/ibek/ioc.py +++ b/src/ibek/ioc.py @@ -18,8 +18,7 @@ class Entity(BaseSettings): """ - A baseclass for all generated Entity classes. Provides the - deserialize entry point. + A baseclass for all generated Entity classes. """ type: str = Field(description="The type of this entity") @@ -39,6 +38,8 @@ def add_ibek_attributes(cls, entity: Entity): for arg, value in entity_dict.items(): if arg in ids: # add this entity to the global id index + if value in id_to_entity: + raise ValueError(f"Duplicate id {value} in {list(id_to_entity)}") id_to_entity[value] = entity elif isinstance(value, str): # Jinja expansion of any of the Entity's string args/values @@ -75,7 +76,7 @@ def lookup_instance(cls, id): try: return id_to_entity[id] except KeyError: - raise KeyError(f"object id {id} not in {list(id_to_entity)}") + raise KeyError(f"object {id} not found in {list(id_to_entity)}") validators[full_arg_name] = lookup_instance arg_type = object @@ -148,7 +149,6 @@ class NewIOC(IOC): def clear_entity_model_ids(): """Resets the global id_to_entity dict.""" - global id_to_entity id_to_entity.clear() diff --git a/tests/samples/generate_samples.sh b/tests/samples/generate_samples.sh index bb362c17c..322eff3ee 100755 --- a/tests/samples/generate_samples.sh +++ b/tests/samples/generate_samples.sh @@ -53,6 +53,18 @@ echo making values_test IOC ibek build-startup ${SAMPLES_DIR}/values_test/values.ibek.ioc.yaml ${DEFS}/*/*.support.yaml --out /tmp/ioc/st.cmd --db-out /tmp/ioc/make_db.sh cp /tmp/ioc/st.cmd ${SAMPLES_DIR}/values_test +PYDANTIC_DIR=${SAMPLES_DIR}/pydantic +cd $PYDANTIC_DIR + +echo making the support yaml schema +ibek ibek-schema ${PYDANTIC_DIR}/../schemas/ibek.defs.schema.json + +echo making the pydantic test definition schema +ibek ioc-schema ${PYDANTIC_DIR}/test.ibek.support.yaml $PYDANTIC_DIR/test.ibek.ioc.schema.json + +echo making the pydantic test ioc startup script +ibek build-startup ${PYDANTIC_DIR}/test.ibek.ioc.yaml ${PYDANTIC_DIR}/test.ibek.support.yaml --out $PYDANTIC_DIR/st.cmd --db-out $PYDANTIC_DIR/make_db.sh + diff --git a/tests/samples/pydantic/generate_samples.sh b/tests/samples/pydantic/generate_samples.sh deleted file mode 100755 index 5f9cef76d..000000000 --- a/tests/samples/pydantic/generate_samples.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# this script regenerates the files used in tests -# it is useful after making changes to the code that affect the schemas etc. -# -# please bear in mind that this is cheating tests! It requires visual -# verifiction of the files in the samples folders after running. -# - -export PYDANTIC_DIR=$(realpath $(dirname "${BASH_SOURCE[0]}"))/ -export DEFS=${PYDANTIC_DIR}/../../ibek-defs - -# this is so relative schema mode lines work -cd $PYDANTIC_DIR - -echo making the support yaml schema -ibek ibek-schema ${PYDANTIC_DIR}/../schemas/ibek.defs.schema.json - -echo making the pydantic test definition schema -ibek ioc-schema ${PYDANTIC_DIR}/test.ibek.support.yaml $PYDANTIC_DIR/test.ibek.ioc.schema.json - -echo making the pydantic test ioc startup script -ibek build-startup ${PYDANTIC_DIR}/test.ibek.ioc.yaml ${PYDANTIC_DIR}/test.ibek.support.yaml --out $PYDANTIC_DIR/st.cmd --db-out $PYDANTIC_DIR/make_db.sh diff --git a/tests/test_cli.py b/tests/test_cli.py index 912fbb96b..8f3b5b8ed 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -205,11 +205,12 @@ def test_build_startup_env_vars_and_post_ioc_init( def test_loading_module_twice(tmp_path: Path, samples: Path, ibek_defs: Path): """ - Now that we use pydantic it is OK to create the same entity twice - - Verify this. + Verify we get a sensible error if we try to load a module twice + without clearing the entity model ids """ + clear_entity_model_ids() + definition_file = samples / "pydantic" / "test.ibek.support.yaml" instance_file = samples / "pydantic" / "test.ibek.ioc.yaml" @@ -221,10 +222,11 @@ def test_loading_module_twice(tmp_path: Path, samples: Path, ibek_defs: Path): generic_ioc2 = make_ioc_model(entities2) instance = YAML(typ="safe").load(instance_file) - ioc1 = generic_ioc1(**instance) - ioc2 = generic_ioc2(**instance) + generic_ioc1(**instance) + with pytest.raises(ValueError) as ctx: + generic_ioc2(**instance) - assert ioc1.model_dump() == ioc2.model_dump() + assert "Duplicate id" in str(ctx.value) def test_bad_counter(tmp_path: Path, samples: Path): diff --git a/tests/test_render.py b/tests/test_render.py index 6b1286a37..54fe770ab 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -3,9 +3,8 @@ Entity classes """ from typing import Literal -from unittest.mock import Mock -from ibek.ioc import IOC +from ibek.ioc import clear_entity_model_ids, make_ioc_model from ibek.render import Render @@ -35,12 +34,13 @@ def test_pmac_asyn_ip_port_script(pmac_classes): def test_geobrick_script(pmac_classes): - generated_class = find_entity_class(pmac_classes, "pmac.Geobrick") - ip_port = Mock() - ip_port.name = "my_asyn_port" - pmac_geobrick_instance = generated_class( + pmac_class = find_entity_class(pmac_classes, "pmac.Geobrick") + ip_port_class = find_entity_class(pmac_classes, "pmac.PmacAsynIPPort") + + ip_port_class(name="my_asyn_port", IP="1.1") + pmac_geobrick_instance = pmac_class( name="test_geobrick", - PORT=ip_port, + PORT="my_asyn_port", P="geobrick_one", numAxes=8, idlePoll=200, @@ -63,7 +63,9 @@ def test_geobrick_script(pmac_classes): def test_geobrick_database(pmac_classes): generated_class = find_entity_class(pmac_classes, "pmac.Geobrick") + ip_port_class = find_entity_class(pmac_classes, "pmac.PmacAsynIPPort") + ip_port_class(name="my_asyn_port", IP="1.1") pmac_geobrick_instance = generated_class( name="test_geobrick", PORT="my_asyn_port", @@ -98,8 +100,9 @@ def test_epics_environment_variables(epics_classes): # Using the generic entity env_name = "EPICS_CA_SERVER_PORT" - env_value = 6000 - generated_class = epics_classes.EpicsEnvSet + env_value = "6000" + generated_class = find_entity_class(epics_classes, "epics.EpicsEnvSet") + epics_env_set_instance = generated_class(name=env_name, value=env_value) env_text = render.render_environment_variables(epics_env_set_instance) @@ -111,30 +114,40 @@ def test_entity_disabled_does_not_render_elements(pmac_classes, epics_classes): # There are four elements to check: script, database, environment variables # and post-iocInit + clear_entity_model_ids() + # Entity which has a script and database pmac_geobrick_class = find_entity_class(pmac_classes, "pmac.Geobrick") # Entity which has env_vars - ca_max_array_bytes_class = epics_classes.EpicsCaMaxArrayBytes + ca_max_array_bytes_class = find_entity_class( + epics_classes, "epics.EpicsCaMaxArrayBytes" + ) # Entity which has post_ioc_init - dbpf_class = epics_classes.Dbpf - + dbpf_class = find_entity_class(epics_classes, "epics.Dbpf") # We require pmac asyn IP port instances for the Geobrick class - pmac_asyn_ip_class = pmac_classes.PmacAsynIPPort - brick_one_asyn_ip_port = pmac_asyn_ip_class( - name="geobrick_one_port", IP="172.23.100.101" - ) - brick_two_asyn_ip_port = pmac_asyn_ip_class( - name="geobrick_two_port", IP="172.23.100.101" + pmac_asyn_ip_class = find_entity_class(pmac_classes, "pmac.PmacAsynIPPort") + + # Make an IOC model with our entity classes + Ioc = make_ioc_model( + [pmac_geobrick_class, ca_max_array_bytes_class, dbpf_class, pmac_asyn_ip_class] ) - # Store created instances in a list + # build a list of dictionaries to instantiate an IOC instance_list = [] + instance_list.append( + dict(type="pmac.PmacAsynIPPort", name="geobrick_one_port", IP="172.23.100.101") + ) + instance_list.append( + dict(type="pmac.PmacAsynIPPort", name="geobrick_two_port", IP="172.23.100.101") + ) + # Create two instances of the Geobrick entity, one disabled instance_list.append( - pmac_geobrick_class( + dict( + type="pmac.Geobrick", name="geobrick_enabled", - PORT=brick_one_asyn_ip_port, + PORT="geobrick_one_port", P="geobrick_one", numAxes=8, idlePoll=200, @@ -142,9 +155,10 @@ def test_entity_disabled_does_not_render_elements(pmac_classes, epics_classes): ) ) instance_list.append( - pmac_geobrick_class( + dict( + type="pmac.Geobrick", name="geobrick_disabled", - PORT=brick_two_asyn_ip_port, + PORT="geobrick_two_port", P="geobrick_two", numAxes=8, idlePoll=200, @@ -154,26 +168,31 @@ def test_entity_disabled_does_not_render_elements(pmac_classes, epics_classes): ) # Create two instances of the CA max array bytes entity, one disabled - instance_list.append(ca_max_array_bytes_class()) - instance_list.append(ca_max_array_bytes_class(entity_enabled=False)) + instance_list.append(dict(type="epics.EpicsCaMaxArrayBytes")) + instance_list.append(dict(type="epics.EpicsCaMaxArrayBytes", entity_enabled=False)) # Create two instances of the dpbf class - instance_list.append(dbpf_class(pv="TEST:PV:1", value="pv_value_1")) + instance_list.append(dict(type="epics.Dbpf", pv="TEST:PV:1", value="pv_value_1")) instance_list.append( - dbpf_class(pv="TEST:PV:2", value="pv_value_2", entity_enabled=False) + dict( + type="epics.Dbpf", pv="TEST:PV:2", value="pv_value_2", entity_enabled=False + ) ) # Make an IOC with our instances - ioc = IOC( - "TEST-IOC-01", - "Test IOC", - instance_list, - "test_ioc_image", + ioc = Ioc( + ioc_name="TEST-IOC-01", + description="Test IOC", + entities=instance_list, + generic_ioc_image="test_ioc_image", ) # Render script and check output # ControlerPort, LowLevelDriverPort, Address, Axes, MovingPoll, IdlePoll expected_script = ( + "\n# pmacAsynIPConfigure AsynPortName IPAddress\n" + "pmacAsynIPConfigure geobrick_one_port 172.23.100.101:1025\n" + "pmacAsynIPConfigure geobrick_two_port 172.23.100.101:1025\n" "\n# pmacCreateController AsynPortName PmacAsynPort Address NumAxes " "MovingPollPeriod IdlePollPeriod\n" "pmacCreateController geobrick_enabled geobrick_one_port 0 8 800 200\n"