diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 1ac9f82f88374d..9024b0e3a207ee 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -537,6 +537,7 @@ jobs:
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/TestIdChecks.py'
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/TestMatterTestingSupport.py'
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/TestSpecParsingDeviceType.py'
+ scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/TestSpecParsingSelection.py'
scripts/run_in_python_env.sh out/venv 'python3 src/python_testing/TestSpecParsingSupport.py'
- name: Run Tests
diff --git a/src/python_testing/TC_AccessChecker.py b/src/python_testing/TC_AccessChecker.py
index 5f3b3d69ec2146..03595d7ac1dfe5 100644
--- a/src/python_testing/TC_AccessChecker.py
+++ b/src/python_testing/TC_AccessChecker.py
@@ -28,7 +28,7 @@
from chip.testing.global_attribute_ids import GlobalAttributeIds
from chip.testing.matter_testing import (AttributePathLocation, ClusterPathLocation, MatterBaseTest, TestStep, async_test_body,
default_matter_test_main)
-from chip.testing.spec_parsing import XmlCluster, build_xml_clusters
+from chip.testing.spec_parsing import XmlCluster
from chip.tlv import uint
@@ -72,7 +72,8 @@ async def setup_class(self):
self.user_params["use_pase_only"] = False
super().setup_class()
await self.setup_class_helper()
- self.xml_clusters, self.problems = build_xml_clusters()
+ self.build_spec_xmls()
+
acl_attr = Clusters.AccessControl.Attributes.Acl
self.default_acl = await self.read_single_attribute_check_success(cluster=Clusters.AccessControl, attribute=acl_attr)
self._record_errors()
diff --git a/src/python_testing/TC_DeviceConformance.py b/src/python_testing/TC_DeviceConformance.py
index a7843a521105a0..5c761acf4c7f61 100644
--- a/src/python_testing/TC_DeviceConformance.py
+++ b/src/python_testing/TC_DeviceConformance.py
@@ -47,16 +47,14 @@
device_type_id_type, is_valid_device_type_id)
from chip.testing.matter_testing import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, DeviceTypePathLocation,
MatterBaseTest, ProblemNotice, ProblemSeverity, async_test_body, default_matter_test_main)
-from chip.testing.spec_parsing import CommandType, build_xml_clusters, build_xml_device_types
+from chip.testing.spec_parsing import CommandType
from chip.tlv import uint
class DeviceConformanceTests(BasicCompositionTests):
async def setup_class_helper(self):
await super().setup_class_helper()
- self.xml_clusters, self.problems = build_xml_clusters()
- self.xml_device_types, problems = build_xml_device_types()
- self.problems.extend(problems)
+ self.build_spec_xmls()
def _get_device_type_id(self, device_type_name: str) -> int:
id = [id for id, dt in self.xml_device_types.items() if dt.name.lower() == device_type_name.lower()]
diff --git a/src/python_testing/TC_pics_checker.py b/src/python_testing/TC_pics_checker.py
index 07e8613cc1cc4d..510429dff0e36c 100644
--- a/src/python_testing/TC_pics_checker.py
+++ b/src/python_testing/TC_pics_checker.py
@@ -20,9 +20,9 @@
from chip.testing.basic_composition import BasicCompositionTests
from chip.testing.global_attribute_ids import GlobalAttributeIds
from chip.testing.matter_testing import (AttributePathLocation, ClusterPathLocation, CommandPathLocation, FeaturePathLocation,
- MatterBaseTest, ProblemLocation, TestStep, async_test_body, default_matter_test_main)
+ MatterBaseTest, TestStep, UnknownProblemLocation, async_test_body,
+ default_matter_test_main)
from chip.testing.pics import accepted_cmd_pics_str, attribute_pics_str, feature_pics_str, generated_cmd_pics_str
-from chip.testing.spec_parsing import build_xml_clusters
from mobly import asserts
@@ -31,10 +31,7 @@ class TC_PICS_Checker(MatterBaseTest, BasicCompositionTests):
async def setup_class(self):
super().setup_class()
await self.setup_class_helper(False)
- # build_xml_cluster returns a list of issues found when paring the XML
- # Problems in the XML shouldn't cause test failure, but we want them recorded
- # so they are added to the list of problems that get output when the test set completes.
- self.xml_clusters, self.problems = build_xml_clusters()
+ self.build_spec_xmls()
def _check_and_record_errors(self, location, required, pics):
if required and not self.check_pics(pics):
@@ -178,7 +175,7 @@ def test_TC_IDM_10_4(self):
self.step(7)
if self.is_pics_sdk_ci_only:
- self.record_error("PICS check", location=ProblemLocation(),
+ self.record_error("PICS check", location=UnknownProblemLocation(),
problem="PICS PICS_SDK_CI_ONLY found in PICS list. This PICS is disallowed for certification.")
self.success = False
diff --git a/src/python_testing/TestConformanceTest.py b/src/python_testing/TestConformanceTest.py
index ddf3e421a7f75c..37457d80c00426 100644
--- a/src/python_testing/TestConformanceTest.py
+++ b/src/python_testing/TestConformanceTest.py
@@ -22,7 +22,7 @@
from chip.testing.conformance import ConformanceDecision
from chip.testing.global_attribute_ids import GlobalAttributeIds
from chip.testing.matter_testing import MatterBaseTest, async_test_body, default_matter_test_main
-from chip.testing.spec_parsing import build_xml_clusters, build_xml_device_types
+from chip.testing.spec_parsing import PrebuiltDataModelDirectory, build_xml_clusters, build_xml_device_types
from mobly import asserts
from TC_DeviceConformance import DeviceConformanceTests
@@ -118,8 +118,10 @@ def is_mandatory(conformance):
class TestConformanceSupport(MatterBaseTest, DeviceConformanceTests):
def setup_class(self):
- self.xml_clusters, self.problems = build_xml_clusters()
- self.xml_device_types, problems = build_xml_device_types()
+ # Latest fully qualified version
+ # TODO: It might be good to find a way to run this against each directory.
+ self.xml_clusters, self.problems = build_xml_clusters(PrebuiltDataModelDirectory.k1_4)
+ self.xml_device_types, problems = build_xml_device_types(PrebuiltDataModelDirectory.k1_4)
self.problems.extend(problems)
@async_test_body
diff --git a/src/python_testing/TestSpecParsingDeviceType.py b/src/python_testing/TestSpecParsingDeviceType.py
index 66a41b0fc5785b..baf7ac4d9c42e6 100644
--- a/src/python_testing/TestSpecParsingDeviceType.py
+++ b/src/python_testing/TestSpecParsingDeviceType.py
@@ -35,8 +35,9 @@ def test_spec_device_parsing(self):
print(str(d))
def setup_class(self):
- self.xml_clusters, self.xml_cluster_problems = build_xml_clusters()
- self.xml_device_types, self.xml_device_types_problems = build_xml_device_types()
+ # Latest fully qualified release
+ self.xml_clusters, self.xml_cluster_problems = build_xml_clusters(PrebuiltDataModelDirectory.k1_4)
+ self.xml_device_types, self.xml_device_types_problems = build_xml_device_types(PrebuiltDataModelDirectory.k1_4)
self.device_type_id = 0xBBEF
self.revision = 2
diff --git a/src/python_testing/TestSpecParsingSelection.py b/src/python_testing/TestSpecParsingSelection.py
new file mode 100644
index 00000000000000..51e76a8f59ab94
--- /dev/null
+++ b/src/python_testing/TestSpecParsingSelection.py
@@ -0,0 +1,149 @@
+#
+# Copyright (c) 2025 Project CHIP Authors
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import chip.clusters as Clusters
+from chip.testing.conformance import ConformanceDecision, ConformanceException
+from chip.testing.global_attribute_ids import is_standard_attribute_id
+from chip.testing.matter_testing import MatterBaseTest, default_matter_test_main
+from chip.testing.spec_parsing import PrebuiltDataModelDirectory, build_xml_clusters, dm_from_spec_version
+from chip.tlv import uint
+from mobly import asserts, signals
+from TC_DeviceConformance import DeviceConformanceTests
+
+
+class TestSpecParsingSelection(MatterBaseTest, DeviceConformanceTests):
+ def setup_class(self):
+ # Overriding the DeviceConformanceTest setup_class so we don't go out to a real device
+ pass
+
+ def test_dm_from_spec_version(self):
+ asserts.assert_equal(dm_from_spec_version(0x01030000), PrebuiltDataModelDirectory.k1_3,
+ "Incorrect directory selected for 1.3 with patch 0")
+ asserts.assert_equal(dm_from_spec_version(0x01030100), PrebuiltDataModelDirectory.k1_3,
+ "Incorrect directory selected for 1.3 with patch 1")
+ asserts.assert_equal(dm_from_spec_version(0x01040100), PrebuiltDataModelDirectory.k1_4_1,
+ "Incorrect directory selected for 1.4.1")
+ asserts.assert_equal(dm_from_spec_version(0x01040100), PrebuiltDataModelDirectory.k1_4_1,
+ "Incorrect directory selected for 1.4.1")
+ asserts.assert_equal(dm_from_spec_version(0x01050000), PrebuiltDataModelDirectory.kMaster,
+ "Incorrect directory selected for 1.5")
+
+ # We don't have data model files for 1.2, so these should error
+ with asserts.assert_raises(ConformanceException, "Expected assertion was not raised for spec version 1.2"):
+ dm_from_spec_version(0x01020000)
+
+ # Any dot release besides 0 and 1 for 1.4 should error
+ with asserts.assert_raises(ConformanceException, "Data model incorrectly identified for 1.4.2"):
+ dm_from_spec_version(0x01040200)
+
+ with asserts.assert_raises(ConformanceException, "Data model incorrectly identified for 1.4.FF"):
+ dm_from_spec_version(0x0104FF00)
+
+ # Any dot release besides 0 for 1.5 should error
+ with asserts.assert_raises(ConformanceException, "Data model incorrectly identified for 1.5.1"):
+ dm_from_spec_version(0x01050100)
+ with asserts.assert_raises(ConformanceException, "Data model incorrectly identified for 1.5.FF"):
+ dm_from_spec_version(0x0105FF00)
+
+ # Any value with stuff in reserved should error
+ with asserts.assert_raises(ConformanceException, "Error not returned for specification revision with non-zero reserved values"):
+ dm_from_spec_version(0x01030001)
+ with asserts.assert_raises(ConformanceException, "Error not returned for specification revision with non-zero reserved values"):
+ dm_from_spec_version(0x01040001)
+ with asserts.assert_raises(ConformanceException, "Error not returned for specification revision with non-zero reserved values"):
+ dm_from_spec_version(0x01040101)
+ with asserts.assert_raises(ConformanceException, "Error not returned for specification revision with non-zero reserved values"):
+ dm_from_spec_version(0x01050001)
+
+ def _create_device(self, spec_version: uint, tc_enabled: bool):
+ # Build at 1.4.1 so we can have TC info
+ xml_clusters, _ = build_xml_clusters(PrebuiltDataModelDirectory.k1_4_1)
+
+ gc_feature_map = Clusters.GeneralCommissioning.Bitmaps.Feature.kTermsAndConditions if tc_enabled else 0
+
+ def create_cluster_globals(cluster, feature_map):
+ spec_attributes = xml_clusters[cluster.id].attributes
+ spec_accepted_commands = xml_clusters[cluster.id].accepted_commands
+ spec_generated_commands = xml_clusters[cluster.id].generated_commands
+ # Build just the lists - basic composition checks the wildcard against the lists, conformance just uses lists
+ attributes = [id for id, a in spec_attributes.items() if a.conformance(
+ feature_map, [], []).decision == ConformanceDecision.MANDATORY]
+ accepted_commands = [id for id, c in spec_accepted_commands.items() if c.conformance(
+ feature_map, [], []).decision == ConformanceDecision.MANDATORY]
+ generated_commands = [id for id, c in spec_generated_commands.items() if c.conformance(
+ feature_map, [], []).decision == ConformanceDecision.MANDATORY]
+ attr = cluster.Attributes
+
+ resp = {}
+ non_global_attrs = [a for a in attributes if is_standard_attribute_id(a)]
+ for attribute_id in non_global_attrs:
+ # We don't use the values in these tests, set them all to 0. The types are wrong, but it shouldn't matter
+ resp[Clusters.ClusterObjects.ALL_ATTRIBUTES[cluster.id][attribute_id]] = 0
+
+ resp[attr.AttributeList] = attributes
+ resp[attr.AcceptedCommandList] = accepted_commands
+ resp[attr.GeneratedCommandList] = generated_commands
+ resp[attr.FeatureMap] = feature_map
+ resp[attr.ClusterRevision] = xml_clusters[cluster.id].revision
+
+ return resp
+
+ def get_tlv(resp):
+ # This only works because there are no structs in here.
+ # structs need special handling. Beware.
+ return {k.attribute_id: v for k, v in resp.items()}
+
+ gc_resp = create_cluster_globals(Clusters.GeneralCommissioning, gc_feature_map)
+ bi_resp = create_cluster_globals(Clusters.BasicInformation, 0)
+ bi_resp[Clusters.BasicInformation.Attributes.SpecificationVersion] = spec_version
+
+ self.endpoints = {0: {Clusters.GeneralCommissioning: gc_resp, Clusters.BasicInformation: bi_resp}}
+ self.endpoints_tlv = {0: {Clusters.GeneralCommissioning.id: get_tlv(
+ gc_resp), Clusters.BasicInformation.id: get_tlv(bi_resp)}}
+
+ def _run_conformance_against_device(self, spec_version: uint, tc_enabled: bool, expect_success_conformance: bool, expect_success_revisions: bool):
+ self._create_device(spec_version, tc_enabled)
+ # build the spec XMLs for the stated version
+ self.build_spec_xmls()
+ success, problems = self.check_conformance(ignore_in_progress=False, is_ci=False, allow_provisional=False)
+ problem_strs = [str(p) for p in problems]
+ problem_str = "\n".join(problem_strs)
+ asserts.assert_equal(success, expect_success_conformance,
+ f"Improper conformance result for spec version {spec_version:08X}, TC: {tc_enabled} problems: {problem_str}")
+
+ success, problems = self.check_revisions(ignore_in_progress=False)
+ asserts.assert_equal(success, expect_success_revisions,
+ f"Improper revision result for spec version {spec_version:08X}, TC: {tc_enabled} problems: {problems}")
+
+ def test_conformance(self):
+
+ # 1.4 is OK if TC is off
+ self._run_conformance_against_device(0x01040000, False, expect_success_conformance=True, expect_success_revisions=True)
+ # 1.4.1 is OK if TC is off
+ self._run_conformance_against_device(0x01040100, False, expect_success_conformance=True, expect_success_revisions=True)
+ # 1.4.1 is OK if TC is on
+ self._run_conformance_against_device(0x01040100, True, expect_success_conformance=True, expect_success_revisions=True)
+ # 1.4 is NOT OK if TC is on
+ self._run_conformance_against_device(0x01040000, True, expect_success_conformance=False, expect_success_revisions=True)
+
+ # Check that we get a test failure on a bad spec revision
+ self._create_device(0xFFFFFFFF, False)
+ with asserts.assert_raises(signals.TestFailure, "Exception not properly raised for bad spec type"):
+ self.build_spec_xmls()
+
+
+if __name__ == "__main__":
+ default_matter_test_main()
diff --git a/src/python_testing/TestSpecParsingSupport.py b/src/python_testing/TestSpecParsingSupport.py
index 35c1f88d121268..ce461f89682046 100644
--- a/src/python_testing/TestSpecParsingSupport.py
+++ b/src/python_testing/TestSpecParsingSupport.py
@@ -253,7 +253,8 @@ def get_access_enum_from_string(access_str: str) -> Clusters.AccessControl.Enums
class TestSpecParsingSupport(MatterBaseTest):
def setup_class(self):
super().setup_class()
- self.spec_xml_clusters, self.spec_problems = build_xml_clusters()
+ # Latest fully certified build
+ self.spec_xml_clusters, self.spec_problems = build_xml_clusters(PrebuiltDataModelDirectory.k1_4)
self.all_spec_clusters = set([(id, c.name, c.pics) for id, c in self.spec_xml_clusters.items()])
def test_build_xml_override(self):
diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/basic_composition.py b/src/python_testing/matter_testing_infrastructure/chip/testing/basic_composition.py
index debf902e76a414..089cec929435a3 100644
--- a/src/python_testing/matter_testing_infrastructure/chip/testing/basic_composition.py
+++ b/src/python_testing/matter_testing_infrastructure/chip/testing/basic_composition.py
@@ -31,6 +31,8 @@
import chip.clusters.ClusterObjects
import chip.tlv
from chip.clusters.Attribute import ValueDecodeFailure
+from chip.testing.conformance import ConformanceException
+from chip.testing.spec_parsing import PrebuiltDataModelDirectory, build_xml_clusters, build_xml_device_types, dm_from_spec_version
from mobly import asserts
@@ -210,3 +212,23 @@ def fail_current_test(self, msg: Optional[str] = None):
asserts.fail(msg=self.problems[-1].problem)
else:
asserts.fail(msg)
+
+ def _get_dm(self) -> PrebuiltDataModelDirectory:
+ try:
+ spec_version = self.endpoints[0][Clusters.BasicInformation][Clusters.BasicInformation.Attributes.SpecificationVersion]
+ except KeyError:
+ asserts.fail(
+ "Specification Version not found on device - ensure device bas a basic information cluster on EP0 supporting Specification Version")
+ try:
+ return dm_from_spec_version(spec_version)
+ except ConformanceException as e:
+ asserts.fail(f"Unable to identify specification version: {e}")
+
+ def build_spec_xmls(self):
+ dm = self._get_dm()
+ logging.info("----------------------------------------------------------------------------------")
+ logging.info(f"-- Running tests against Specification version {dm.dirname}")
+ logging.info("----------------------------------------------------------------------------------")
+ self.xml_clusters, self.problems = build_xml_clusters(dm)
+ self.xml_device_types, problems = build_xml_device_types(dm)
+ self.problems.extend(problems)
diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py
index 49249cc7dabb0f..bac18a7d408414 100644
--- a/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py
+++ b/src/python_testing/matter_testing_infrastructure/chip/testing/matter_testing.py
@@ -803,7 +803,12 @@ def __str__(self):
return msg
-ProblemLocation = typing.Union[ClusterPathLocation, DeviceTypePathLocation]
+class UnknownProblemLocation:
+ def __str__(self):
+ return '\n Unknown Locations - see message for more details'
+
+
+ProblemLocation = typing.Union[ClusterPathLocation, DeviceTypePathLocation, UnknownProblemLocation]
# ProblemSeverity is not using StrEnum, but rather Enum, since StrEnum only
# appeared in 3.11. To make it JSON serializable easily, multiple inheritance
diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/runner.py b/src/python_testing/matter_testing_infrastructure/chip/testing/runner.py
index ea060074662b5c..8a773fe647da56 100644
--- a/src/python_testing/matter_testing_infrastructure/chip/testing/runner.py
+++ b/src/python_testing/matter_testing_infrastructure/chip/testing/runner.py
@@ -75,6 +75,7 @@ def run_test_with_mock_read(self, read_cache: Attribute.AsyncReadTransaction.Rea
self.default_controller.Read = AsyncMock(return_value=read_cache)
# This doesn't need to do anything since we are overriding the read anyway
self.default_controller.FindOrEstablishPASESession = AsyncMock(return_value=None)
+ self.default_controller.GetConnectedDevice = AsyncMock(return_value=None)
with asyncio.Runner() as runner:
return run_tests_no_exit(self.test_class, self.config, runner.get_loop(),
hooks, self.default_controller, self.stack)
diff --git a/src/python_testing/matter_testing_infrastructure/chip/testing/spec_parsing.py b/src/python_testing/matter_testing_infrastructure/chip/testing/spec_parsing.py
index e18df47855caef..a514c232344302 100644
--- a/src/python_testing/matter_testing_infrastructure/chip/testing/spec_parsing.py
+++ b/src/python_testing/matter_testing_infrastructure/chip/testing/spec_parsing.py
@@ -582,7 +582,7 @@ def get_data_model_directory(data_model_directory: Union[PrebuiltDataModelDirect
return path.joinpath(data_model_level.dirname)
-def build_xml_clusters(data_model_directory: Union[PrebuiltDataModelDirectory, Traversable] = PrebuiltDataModelDirectory.k1_4_1) -> typing.Tuple[dict[uint, XmlCluster], list[ProblemNotice]]:
+def build_xml_clusters(data_model_directory: Union[PrebuiltDataModelDirectory, Traversable]) -> typing.Tuple[dict[uint, XmlCluster], list[ProblemNotice]]:
"""
Build XML clusters from the specified data model directory.
This function supports both pre-built locations and full paths.
@@ -851,7 +851,7 @@ def parse_single_device_type(root: ElementTree.Element) -> tuple[dict[int, XmlDe
return device_types, problems
-def build_xml_device_types(data_model_directory: typing.Union[PrebuiltDataModelDirectory, Traversable] = PrebuiltDataModelDirectory.k1_4_1) -> tuple[dict[int, XmlDeviceType], list[ProblemNotice]]:
+def build_xml_device_types(data_model_directory: typing.Union[PrebuiltDataModelDirectory, Traversable]) -> tuple[dict[int, XmlDeviceType], list[ProblemNotice]]:
top = get_data_model_directory(data_model_directory, DataModelLevel.kDeviceType)
device_types: dict[int, XmlDeviceType] = {}
problems: list[ProblemNotice] = []
@@ -881,3 +881,26 @@ def build_xml_device_types(data_model_directory: typing.Union[PrebuiltDataModelD
device_types.pop(-1)
return device_types, problems
+
+
+def dm_from_spec_version(specification_version: uint) -> PrebuiltDataModelDirectory:
+ ''' Returns the data model directory for a given specification revision.
+
+ input: specification revision attribute data from the basic information cluster
+ output: PrebuiltDataModelDirectory
+ raises: ConformanceException if the given specification_version does not conform to a known data model
+ '''
+ # Specification version attribute is 2 bytes major, 2 bytes minor, 2 bytes dot 2 bytes reserved.
+ # However, 1.3 allowed the dot to be any value
+ if specification_version < 0x01040000:
+ specification_version &= 0xFFFF00FF
+
+ version_to_dm = {0x01030000: PrebuiltDataModelDirectory.k1_3,
+ 0x01040000: PrebuiltDataModelDirectory.k1_4,
+ 0x01040100: PrebuiltDataModelDirectory.k1_4_1,
+ 0x01050000: PrebuiltDataModelDirectory.kMaster}
+
+ if specification_version not in version_to_dm.keys():
+ raise ConformanceException(f"Unknown specification_version {specification_version:08X}")
+
+ return version_to_dm[specification_version]
diff --git a/src/python_testing/test_metadata.yaml b/src/python_testing/test_metadata.yaml
index 850a6ee756615d..5bd91ecf542152 100644
--- a/src/python_testing/test_metadata.yaml
+++ b/src/python_testing/test_metadata.yaml
@@ -55,6 +55,8 @@ not_automated:
reason: Unit test - does not run against an app
- name: TestConformanceTest.py
reason: Unit test - does not run against an app
+ - name: TestSpecParsingSelection.py
+ reason: Unit test - does not run against an app
- name: TestMatterTestingSupport.py
reason: Unit test - does not run against an app
- name: TestSpecParsingSupport.py
diff --git a/src/python_testing/test_testing/example_pics_xml_basic_info.xml b/src/python_testing/test_testing/example_pics_xml_basic_info.xml
index 3d488c3ae90ace..b1c3c5347bd648 100644
--- a/src/python_testing/test_testing/example_pics_xml_basic_info.xml
+++ b/src/python_testing/test_testing/example_pics_xml_basic_info.xml
@@ -199,6 +199,27 @@ Draft
O
false
+
+ BINFO.S.A0015
+ Does the DUT(server) support the SpecificationVersion attribute?
+ 9.2.1. Attributes - index.html[pdf]
+ M
+ true
+
+
+ BINFO.S.A0016
+ Does the DUT(server) support the MaxPathsPerInvoke attribute?
+ 9.2.1. Attributes - index.html[pdf]
+ M
+ false
+
+
+ BINFO.S.A0017
+ Does the DUT(server) support the DeviceLocation attribute?
+ 9.2.1. Attributes - index.html[pdf]
+ O
+ false
+
diff --git a/src/python_testing/test_testing/test_IDM_10_4.py b/src/python_testing/test_testing/test_IDM_10_4.py
index f7e93e9f47ea33..a03be21c342fa6 100644
--- a/src/python_testing/test_testing/test_IDM_10_4.py
+++ b/src/python_testing/test_testing/test_IDM_10_4.py
@@ -49,6 +49,7 @@ def create_read(include_reachable: bool = False, include_max_paths: bool = False
bi.ProductLabel: 'myProduct',
bi.SerialNumber: 'ABCD1234',
bi.LocalConfigDisabled: False,
+ bi.SpecificationVersion: 0x01040000,
bi.UniqueID: 'Hashy-McHashface'}
if include_reachable:
attrs_bi[bi.Reachable] = True