From b6b8cbcf587084afd9af21d7c79a926c7f61f3bc Mon Sep 17 00:00:00 2001
From: Rob Bultman <rob.bultman@gmail.com>
Date: Fri, 2 Aug 2024 10:41:28 -0400
Subject: [PATCH 1/4] Add OvenOpState 2.6

---
 .github/workflows/tests.yaml             |  1 +
 src/python_testing/TC_OVENOPSTATE_2_6.py | 62 ++++++++++++++++++++++++
 src/python_testing/TC_OpstateCommon.py   | 40 +++++++++------
 3 files changed, 88 insertions(+), 15 deletions(-)
 create mode 100644 src/python_testing/TC_OVENOPSTATE_2_6.py

diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index c19eccb9a30457..8c070168213170 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -587,6 +587,7 @@ jobs:
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_3.py'
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_4.py'
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_5.py'
+                  scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_6.py'
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_1.py'
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_2.py'
                   scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_MWOCTRL_2_4.py'
diff --git a/src/python_testing/TC_OVENOPSTATE_2_6.py b/src/python_testing/TC_OVENOPSTATE_2_6.py
new file mode 100644
index 00000000000000..09436f4e76cc0b
--- /dev/null
+++ b/src/python_testing/TC_OVENOPSTATE_2_6.py
@@ -0,0 +1,62 @@
+#
+#    Copyright (c) 2024 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.
+#
+
+# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
+# for details about the block below.
+#
+# === BEGIN CI TEST ARGUMENTS ===
+# test-runner-runs: run1
+# test-runner-run/run1/app: ${ALL_CLUSTERS_APP}
+# test-runner-run/run1/factoryreset: True
+# test-runner-run/run1/quiet: True
+# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json
+# test-runner-run/run1/script-args: --endpoint 1 --int-arg PIXIT.WAITTIME.REBOOT:5 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto
+# === END CI TEST ARGUMENTS ===
+
+
+import chip.clusters as Clusters
+from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
+from TC_OpstateCommon import TC_OPSTATE_BASE, TestInfo
+
+
+class TC_OVENOPSTATE_2_6(MatterBaseTest, TC_OPSTATE_BASE):
+    def __init__(self, *args):
+        super().__init__(*args)
+
+        test_info = TestInfo(
+            pics_code="OVENOPSTATE",
+            cluster=Clusters.OvenCavityOperationalState
+        )
+
+        super().setup_base(test_info=test_info)
+
+    def steps_TC_OVENOPSTATE_2_6(self) -> list[TestStep]:
+        return self.steps_TC_OPSTATE_BASE_2_6()
+
+    def pics_TC_OVENOPSTATE_2_6(self) -> list[str]:
+        return ["OVENOPSTATE.S", "OVENOPSTATE.S.A0002"]
+
+    @async_test_body
+    async def test_TC_OVENOPSTATE_2_6(self):
+        # endpoint = self.matter_test_config.endpoint
+
+        # await self.TEST_TC_OPSTATE_BASE_2_6(endpoint=endpoint)
+        await self.TEST_TC_OPSTATE_BASE_2_6(endpoint=1)
+
+
+if __name__ == "__main__":
+    default_matter_test_main()
diff --git a/src/python_testing/TC_OpstateCommon.py b/src/python_testing/TC_OpstateCommon.py
index ece50ac90c65b6..ea26db9b5b758a 100644
--- a/src/python_testing/TC_OpstateCommon.py
+++ b/src/python_testing/TC_OpstateCommon.py
@@ -1267,25 +1267,32 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             await self.read_and_expect_value(endpoint=endpoint,
                                              attribute=attributes.OperationalState,
                                              expected_value=cluster.Enums.OperationalStateEnum.kRunning)
-            count = sub_handler.attribute_report_counts[attributes.CountdownTime]
-            asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+            countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
+            if countdownTime is not NullValue:
+                count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+                asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
         else:
             self.skip_step(3)
 
         sub_handler.reset()
-        self.step(4)
-        logging.info('Test will now collect data for 30 seconds')
-        time.sleep(30)
+        countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
+        if countdownTime is not NullValue:
+            self.step(4)
+            logging.info('Test will now collect data for 30 seconds')
+            time.sleep(30)
 
-        count = sub_handler.attribute_report_counts[attributes.CountdownTime]
-        sub_handler.reset()
-        asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
-        asserts.assert_greater_equal(count, 0, "Did not receive any reports for CountdownTime")
+            count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+            sub_handler.reset()
+            asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
+            asserts.assert_greater_equal(count, 0, "Did not receive any reports for CountdownTime")
+        else:
+            self.skip_step(4)
 
         attr_value = await self.read_expect_success(
             endpoint=endpoint,
             attribute=attributes.OperationalState)
-        if attr_value == cluster.Enums.OperationalStateEnum.kRunning:
+        countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
+        if attr_value == cluster.Enums.OperationalStateEnum.kRunning and countdownTime is not NullValue:
             self.step(5)
             wait_count = 0
             while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
@@ -1301,8 +1308,8 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             self.skip_step(5)
 
         sub_handler.reset()
-        self.step(6)
         if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_RUNNING")):
+            self.step(6)
             self.send_manual_or_pipe_command(name="OperationalStateChange",
                                              device=self.device,
                                              operation="Start")
@@ -1310,8 +1317,10 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             await self.read_and_expect_value(endpoint=endpoint,
                                              attribute=attributes.OperationalState,
                                              expected_value=cluster.Enums.OperationalStateEnum.kRunning)
-            count = sub_handler.attribute_report_counts[attributes.CountdownTime]
-            asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+            countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
+            if countdownTime is not NullValue:
+                count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+                asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
         else:
             self.skip_step(6)
 
@@ -1321,8 +1330,9 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
                                          expected_value=cluster.Enums.OperationalStateEnum.kRunning)
 
         sub_handler.reset()
-        self.step(8)
-        if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_PAUSED")):
+        countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
+        if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_PAUSED")) and countdownTime is not NullValue:
+            self.step(8)
             self.send_manual_or_pipe_command(name="OperationalStateChange",
                                              device=self.device,
                                              operation="Pause")

From 500c0247a2e44a95d19496325626d0dafff792a8 Mon Sep 17 00:00:00 2001
From: Rob Bultman <rob.bultman@gmail.com>
Date: Fri, 2 Aug 2024 13:15:05 -0400
Subject: [PATCH 2/4] Added steps to me TP easier to write

---
 src/python_testing/TC_OpstateCommon.py | 71 +++++++++++++++-----------
 1 file changed, 41 insertions(+), 30 deletions(-)

diff --git a/src/python_testing/TC_OpstateCommon.py b/src/python_testing/TC_OpstateCommon.py
index ea26db9b5b758a..81def79eef2e49 100644
--- a/src/python_testing/TC_OpstateCommon.py
+++ b/src/python_testing/TC_OpstateCommon.py
@@ -1231,15 +1231,20 @@ def steps_TC_OPSTATE_BASE_2_6(self) -> list[TestStep]:
                  TestStep(2, "Subscribe to CountdownTime attribute"),
                  TestStep(3, "Manually put the DUT into a state where it will use the CountdownTime attribute, "
                              "the initial value of the CountdownTime is greater than 30, "
-                             "and it will begin counting down the CountdownTime attribute."),
-                 TestStep(4, "Over a period of 30 seconds, TH counts all report transactions with an attribute "
+                             "and it will begin counting down the CountdownTime attribute. "
+                             "Test harness reads the CountdownTime attribute."),
+                 TestStep(4, "Test harness reads the CountdownTime attribute."),
+                 TestStep(5, "Over a period of 30 seconds, TH counts all report transactions with an attribute "
                              "report for the CountdownTime attribute in numberOfReportsReceived"),
-                 TestStep(5, "Until the current operation finishes, TH counts all report transactions with "
+                 TestStep(6, "Test harness reads the CountdownTime attribute."),
+                 TestStep(7, "Until the current operation finishes, TH counts all report transactions with "
                              "an attribute report for the CountdownTime attribute in numberOfReportsReceived and saves up to 5 such reports."),
-                 TestStep(6, "Manually put the DUT into a state where it will use the CountdownTime attribute, "
-                             "the initial value of the CountdownTime is greater than 30, and it will begin counting down the CountdownTime attribute."),
-                 TestStep(7, "TH reads from the DUT the OperationalState attribute"),
-                 TestStep(8, "Manually put the device in the Paused(0x02) operational state")
+                 TestStep(8, "Manually put the DUT into a state where it will use the CountdownTime attribute, "
+                             "the initial value of the CountdownTime is greater than 30, and it will begin counting down the CountdownTime attribute."
+                             "Test harness reads the CountdownTime attribute."),
+                 TestStep(9, "TH reads from the DUT the OperationalState attribute"),
+                 TestStep(10, "Test harness reads the CountdownTime attribute."),
+                 TestStep(11, "Manually put the device in the Paused(0x02) operational state")
                  ]
         return steps
 
@@ -1275,9 +1280,10 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             self.skip_step(3)
 
         sub_handler.reset()
+        self.step(4)
         countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
         if countdownTime is not NullValue:
-            self.step(4)
+            self.step(5)
             logging.info('Test will now collect data for 30 seconds')
             time.sleep(30)
 
@@ -1286,30 +1292,34 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
             asserts.assert_greater_equal(count, 0, "Did not receive any reports for CountdownTime")
         else:
-            self.skip_step(4)
+            self.skip_step(5)
 
-        attr_value = await self.read_expect_success(
-            endpoint=endpoint,
-            attribute=attributes.OperationalState)
+        self.step(6)
         countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
-        if attr_value == cluster.Enums.OperationalStateEnum.kRunning and countdownTime is not NullValue:
-            self.step(5)
-            wait_count = 0
-            while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
-                time.sleep(1)
-                wait_count = wait_count + 1
-                attr_value = await self.read_expect_success(
-                    endpoint=endpoint,
-                    attribute=attributes.OperationalState)
-            count = sub_handler.attribute_report_counts[attributes.CountdownTime]
-            asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
-            asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+        if countdownTime is not NullValue:
+            attr_value = await self.read_expect_success(
+                endpoint=endpoint,
+                attribute=attributes.OperationalState)
+            if attr_value == cluster.Enums.OperationalStateEnum.kRunning:
+                self.step(7)
+                wait_count = 0
+                while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
+                    time.sleep(1)
+                    wait_count = wait_count + 1
+                    attr_value = await self.read_expect_success(
+                        endpoint=endpoint,
+                        attribute=attributes.OperationalState)
+                count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+                asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
+                asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+            else:
+                self.skip_step(7)
         else:
-            self.skip_step(5)
+            self.skip_step(7)
 
         sub_handler.reset()
         if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_RUNNING")):
-            self.step(6)
+            self.step(8)
             self.send_manual_or_pipe_command(name="OperationalStateChange",
                                              device=self.device,
                                              operation="Start")
@@ -1322,17 +1332,18 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
                 count = sub_handler.attribute_report_counts[attributes.CountdownTime]
                 asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
         else:
-            self.skip_step(6)
+            self.skip_step(8)
 
-        self.step(7)
+        self.step(9)
         await self.read_and_expect_value(endpoint=endpoint,
                                          attribute=attributes.OperationalState,
                                          expected_value=cluster.Enums.OperationalStateEnum.kRunning)
 
         sub_handler.reset()
+        self.step(10)
         countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
         if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_PAUSED")) and countdownTime is not NullValue:
-            self.step(8)
+            self.step(11)
             self.send_manual_or_pipe_command(name="OperationalStateChange",
                                              device=self.device,
                                              operation="Pause")
@@ -1340,4 +1351,4 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
             count = sub_handler.attribute_report_counts[attributes.CountdownTime]
             asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
         else:
-            self.skip_step(8)
+            self.skip_step(11)

From 5b61ea06b997c2f867320cf7b1af3f476861a7ff Mon Sep 17 00:00:00 2001
From: Rob Bultman <rob.bultman@gmail.com>
Date: Fri, 2 Aug 2024 14:34:10 -0400
Subject: [PATCH 3/4] Reduce sleep, fix checks

---
 src/python_testing/TC_OpstateCommon.py | 37 ++++++++++++--------------
 1 file changed, 17 insertions(+), 20 deletions(-)

diff --git a/src/python_testing/TC_OpstateCommon.py b/src/python_testing/TC_OpstateCommon.py
index 81def79eef2e49..a6451400abd4b7 100644
--- a/src/python_testing/TC_OpstateCommon.py
+++ b/src/python_testing/TC_OpstateCommon.py
@@ -1284,8 +1284,8 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
         countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
         if countdownTime is not NullValue:
             self.step(5)
-            logging.info('Test will now collect data for 30 seconds')
-            time.sleep(30)
+            logging.info('Test will now collect data for 10 seconds')
+            time.sleep(10)
 
             count = sub_handler.attribute_report_counts[attributes.CountdownTime]
             sub_handler.reset()
@@ -1296,24 +1296,21 @@ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
 
         self.step(6)
         countdownTime = await self.read_expect_success(endpoint=endpoint, attribute=attributes.CountdownTime)
-        if countdownTime is not NullValue:
-            attr_value = await self.read_expect_success(
-                endpoint=endpoint,
-                attribute=attributes.OperationalState)
-            if attr_value == cluster.Enums.OperationalStateEnum.kRunning:
-                self.step(7)
-                wait_count = 0
-                while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
-                    time.sleep(1)
-                    wait_count = wait_count + 1
-                    attr_value = await self.read_expect_success(
-                        endpoint=endpoint,
-                        attribute=attributes.OperationalState)
-                count = sub_handler.attribute_report_counts[attributes.CountdownTime]
-                asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
-                asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
-            else:
-                self.skip_step(7)
+        attr_value = await self.read_expect_success(
+            endpoint=endpoint,
+            attribute=attributes.OperationalState)
+        if attr_value == cluster.Enums.OperationalStateEnum.kRunning and countdownTime is not NullValue:
+            self.step(7)
+            wait_count = 0
+            while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
+                time.sleep(1)
+                wait_count = wait_count + 1
+                attr_value = await self.read_expect_success(
+                    endpoint=endpoint,
+                    attribute=attributes.OperationalState)
+            count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+            asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
+            asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
         else:
             self.skip_step(7)
 

From b15a46630a42621387badb46796bb22edf9f1144 Mon Sep 17 00:00:00 2001
From: Rob Bultman <rob.bultman@gmail.com>
Date: Mon, 5 Aug 2024 16:06:18 -0400
Subject: [PATCH 4/4] Changed test

---
 src/python_testing/TC_OpstateCommon.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/python_testing/TC_OpstateCommon.py b/src/python_testing/TC_OpstateCommon.py
index a6451400abd4b7..edc72f253eead5 100644
--- a/src/python_testing/TC_OpstateCommon.py
+++ b/src/python_testing/TC_OpstateCommon.py
@@ -1102,7 +1102,8 @@ async def TEST_TC_OPSTATE_BASE_2_5(self, endpoint=1):
                                      f"Completion event error code mismatched from expectation on endpoint {endpoint}.")
 
                 if event_data.totalOperationalTime is not NullValue:
-                    asserts.assert_less_equal(initial_countdown_time, event_data.totalOperationalTime,
+                    time_diff = abs(initial_countdown_time - event_data.totalOperationalTime)
+                    asserts.assert_less_equal(time_diff, 1,
                                               f"The total operation time shall be at least {initial_countdown_time:.1f}")
 
                 asserts.assert_equal(0, event_data.pausedTime,