Преглед изворни кода

tests (test_device_config.py): output file and line info

Include file and line number info relevant to the error detected.
Some lines are the entity, others line 0.

This should allow github to pick up the messages for showing inline in
pull requests, instead of burying them under the test details..
Jason Rumney пре 2 месеци
родитељ
комит
3a89df17f4
1 измењених фајлова са 33 додато и 19 уклоњено
  1. 33 19
      tests/test_device_config.py

+ 33 - 19
tests/test_device_config.py

@@ -400,12 +400,15 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
         Check that the entity has a dps list and each dps has an id,
         type and name, and any other consistency checks.
         """
+        fname = f"custom_components/tuya_local/devices/{cfg}"
+        line = entity._config.__line__
         self.assertIsNotNone(
-            entity._config.get("entity"), f"entity type missing in {cfg}"
+            entity._config.get("entity"),
+            f"{fname}:{line}: entity type missing in {cfg}",
         )
         e = entity.config_id
         self.assertIsNotNone(
-            entity._config.get("dps"), f"dps missing from {e} in {cfg}"
+            entity._config.get("dps"), f"{fname}:{line}: dps missing from {e} in {cfg}"
         )
         functions = set()
         extra = set()
@@ -415,28 +418,32 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
         # Basic checks of dps, and initialising of redirects and extras sets
         # for later checking
         for dp in entity.dps():
+            line = dp._config.__line__
             self.assertIsNotNone(
-                dp._config.get("id"), f"dp id missing from {e} in {cfg}"
+                dp._config.get("id"), f"{fname}:{line}: dp id missing from {e} in {cfg}"
             )
             self.assertIsNotNone(
-                dp._config.get("type"), f"dp type missing from {e} in {cfg}"
+                dp._config.get("type"),
+                f"{fname}:{line}: dp type missing from {e} in {cfg}",
             )
             self.assertIsNotNone(
-                dp._config.get("name"), f"dp name missing from {e} in {cfg}"
+                dp._config.get("name"),
+                f"{fname}:{line}: dp name missing from {e} in {cfg}",
             )
             extra.add(dp.name)
             mappings = dp._config.get("mapping", [])
             self.assertIsInstance(
                 mappings,
                 list,
-                f"mapping is not a list in {cfg}; entity {e}, dp {dp.name}",
+                f"{fname}:{line}: mapping is not a list in {cfg}; entity {e}, dp {dp.name}",
             )
             for m in mappings:
+                line = m.__line__
                 conditions = m.get("conditions", [])
                 self.assertIsInstance(
                     conditions,
                     list,
-                    f"conditions is not a list in {cfg}; entity {e}, dp {dp.name}",
+                    f"{fname}:{line}: conditions is not a list in {cfg}; entity {e}, dp {dp.name}",
                 )
                 for c in conditions:
                     if c.get("value_redirect"):
@@ -448,22 +455,27 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
                 if m.get("value_mirror"):
                     redirects.add(m.get("value_mirror"))
 
+        line = entity._config.__line__
         # Check redirects all exist
         for redirect in redirects:
-            self.assertIn(redirect, extra, f"dp {redirect} missing from {e} in {cfg}")
+            self.assertIn(
+                redirect,
+                extra,
+                f"{fname}:{line}: dp {redirect} missing from {e} in {cfg}",
+            )
 
         # Check dps that are required for this entity type all exist
         expected = KNOWN_DPS.get(entity.entity)
         for rule in expected["required"]:
             self.assertTrue(
                 self.dp_match(rule, functions, extra, known, True),
-                f"{cfg} missing required {self.rule_broken_msg(rule)} in {e}",
+                f"{fname}:{line}: {cfg} missing required {self.rule_broken_msg(rule)} in {e}",
             )
 
         for rule in expected["optional"]:
             self.assertTrue(
                 self.dp_match(rule, functions, extra, known, False),
-                f"{cfg} expecting {self.rule_broken_msg(rule)} in {e}",
+                f"{fname}:{line}: {cfg} expecting {self.rule_broken_msg(rule)} in {e}",
             )
 
         # Check for potential typos in extra attributes
@@ -473,7 +485,7 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
                 self.assertLess(
                     fuzz.ratio(attr, dp),
                     85,
-                    f"Probable typo {attr} is too similar to {dp} in {cfg} {e}",
+                    f"{fname}:{line}: Probable typo {attr} is too similar to {dp} in {cfg} {e}",
                 )
 
         # Check that sensors with mapped values are of class enum and vice versa
@@ -484,12 +496,12 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
                 self.assertEqual(
                     entity.device_class,
                     SensorDeviceClass.ENUM,
-                    f"{cfg} {e} has mapped values but does not have a device class of enum",
+                    f"{fname}:{line}: {cfg} {e} has mapped values but does not have a device class of enum",
                 )
             if entity.device_class == SensorDeviceClass.ENUM:
                 self.assertIsNotNone(
                     sensor.options,
-                    f"{cfg} {e} has a device class of enum, but has no mapped values",
+                    f"{fname}:{line}: {cfg} {e} has a device class of enum, but has no mapped values",
                 )
 
     def test_config_files_parse(self):
@@ -503,27 +515,28 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
             if isinstance(parsed, str) or isinstance(parsed._config, str):
                 self.fail(f"unparsable yaml in {cfg}")
 
+            fname = f"custom_components/tuya_local/devices/{cfg}"
             try:
                 YAML_SCHEMA(parsed._config)
             except vol.MultipleInvalid as e:
-                self.fail(f"Validation error in {cfg}: {e}")
+                self.fail(f"{fname}:0: Validation error in {cfg}: {e}")
 
             self.assertIsNotNone(
                 parsed._config.get("name"),
-                f"name missing from {cfg}",
+                f"{fname}:0: name missing from {cfg}",
             )
             count = 0
             for entity in parsed.all_entities():
                 self.check_entity(entity, cfg)
                 entities.append(entity.config_id)
                 count += 1
-            assert count > 0, f"No entities found in {cfg}"
+            assert count > 0, f"{fname}:0: No entities found in {cfg}"
 
             # check entities are unique
             self.assertCountEqual(
                 entities,
                 set(entities),
-                f"Duplicate entities in {cfg}",
+                f"{fname}:0: Duplicate entities in {cfg}",
             )
 
     def test_configs_can_be_matched(self):
@@ -532,6 +545,7 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
             optional = set()
             required = set()
             parsed = TuyaDeviceConfig(cfg)
+            fname = f"custom_components/tuya_local/devices/{cfg}"
             products = parsed._config.get("products")
             # Configs with a product list can be matched by product id
             if products:
@@ -552,14 +566,14 @@ class TestDeviceConfig(IsolatedAsyncioTestCase):
             self.assertGreater(
                 len(required),
                 0,
-                msg=f"No required dps found in {cfg}",
+                msg=f"{fname}:0: No required dps found in {cfg}",
             )
 
             for dp in required:
                 self.assertNotIn(
                     dp,
                     optional,
-                    msg=f"Optional dp {dp} is required in {cfg}",
+                    msg=f"{fname}:0: Optional dp {dp} is required in {cfg}",
                 )
 
     # Most of the device_config functionality is exercised during testing of