Skip to content

Commit b381ba8

Browse files
Update TestEventTrigger to match SVE2 test plan (project-chip#22695)
* Update TestEventTrigger to match SVE2 test plan - There was a 3-way mismatch between spec, SDK and SVE2 test plans in the TestEventTrigger command. - Since this command is meant for cert testing and 1.0 spec is loced down, it was determined that making behavior match TC-DGEN-2.3 is the best outcome. Fixes project-chip#22232 Changes: - Make all EnableKey errors are ConstraintError - Add test event trigger support to all Linux examples by default with a command that always succeeds in the reserved range - Add integration test for feature - Fix Python IM status codes list that had an old name - Add "commission-only" mode to matter_testing_support.py done while testing this feature Testing done: - All unit tests still pass - Integration tests pass, including new TC_TestEventTrigger.py * Update some comments * Fix CI YAML config
1 parent 0574c55 commit b381ba8

File tree

10 files changed

+160
-19
lines changed

10 files changed

+160
-19
lines changed

.github/workflows/tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ jobs:
285285
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_RR_1_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"'
286286
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_SC_3_6.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021"'
287287
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1" --script "src/python_testing/TC_DA_1_7.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true"'
288+
scripts/run_in_build_env.sh './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace_decode 1 --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_TestEventTrigger.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --bool-arg allow_sdk_dac:true"'
288289
- name: Uploading core files
289290
uses: actions/upload-artifact@v2
290291
if: ${{ failure() && !env.ACT }}

examples/platform/linux/AppMain.cpp

+58-2
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR
7171
#include <app/clusters/ota-requestor/OTATestEventTriggerDelegate.h>
7272
#endif
73+
#include <app/TestEventTriggerDelegate.h>
7374

7475
#include <signal.h>
7576

@@ -142,6 +143,53 @@ static bool EnsureWiFiIsStarted()
142143
}
143144
#endif
144145

146+
class SampleTestEventTriggerDelegate : public TestEventTriggerDelegate
147+
{
148+
public:
149+
/// NOTE: If you copy this, please use the reserved range FFFF_FFFF_<VID_HEX>_xxxx for your trigger codes.
150+
static constexpr uint64_t kSampleTestEventTriggerAlwaysSuccess = static_cast<uint64_t>(0xFFFF'FFFF'FFF1'0000ull);
151+
152+
SampleTestEventTriggerDelegate() { memset(&mEnableKey[0], 0, sizeof(mEnableKey)); }
153+
154+
/**
155+
* @brief Initialize the delegate with a key and an optional other handler
156+
*
157+
* The `otherDelegate` will be called if there is no match of the eventTrigger
158+
* when HandleEventTrigger is called, if it is non-null.
159+
*
160+
* @param enableKey - EnableKey to use for this instance.
161+
* @param otherDelegate - Other delegate (e.g. OTA delegate) where defer trigger. Can be nullptr
162+
* @return CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT if enableKey is wrong size.
163+
*/
164+
CHIP_ERROR Init(ByteSpan enableKey, TestEventTriggerDelegate * otherDelegate)
165+
{
166+
VerifyOrReturnError(enableKey.size() == sizeof(mEnableKey), CHIP_ERROR_INVALID_ARGUMENT);
167+
mOtherDelegate = otherDelegate;
168+
MutableByteSpan ourEnableKeySpan(mEnableKey);
169+
return CopySpanToMutableSpan(enableKey, ourEnableKeySpan);
170+
}
171+
172+
bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override { return enableKey.data_equal(ByteSpan(mEnableKey)); }
173+
174+
CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override
175+
{
176+
ChipLogProgress(Support, "Saw TestEventTrigger: " ChipLogFormatX64, ChipLogValueX64(eventTrigger));
177+
178+
if (eventTrigger == kSampleTestEventTriggerAlwaysSuccess)
179+
{
180+
// Do nothing, successfully
181+
ChipLogProgress(Support, "Handling \"Always success\" internal test event");
182+
return CHIP_NO_ERROR;
183+
}
184+
185+
return (mOtherDelegate != nullptr) ? mOtherDelegate->HandleEventTrigger(eventTrigger) : CHIP_ERROR_INVALID_ARGUMENT;
186+
}
187+
188+
private:
189+
uint8_t mEnableKey[TestEventTriggerDelegate::kEnableKeyLength];
190+
TestEventTriggerDelegate * mOtherDelegate = nullptr;
191+
};
192+
145193
int ChipLinuxAppInit(int argc, char * const argv[], OptionSet * customOptions)
146194
{
147195
CHIP_ERROR err = CHIP_NO_ERROR;
@@ -298,11 +346,19 @@ void ChipLinuxAppMainLoop()
298346
initParams.operationalKeystore = &LinuxDeviceOptions::GetInstance().mCSRResponseOptions.badCsrOperationalKeyStoreForTest;
299347
}
300348

349+
TestEventTriggerDelegate * otherDelegate = nullptr;
301350
#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR
302-
static OTATestEventTriggerDelegate testEventTriggerDelegate{ ByteSpan(
351+
// We want to allow triggering OTA queries if OTA requestor is enabled
352+
static OTATestEventTriggerDelegate otaTestEventTriggerDelegate{ ByteSpan(
303353
LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey) };
304-
initParams.testEventTriggerDelegate = &testEventTriggerDelegate;
354+
otherDelegate = &otaTestEventTriggerDelegate;
305355
#endif
356+
// For general testing of TestEventTrigger, we have a common "core" event trigger delegate.
357+
static SampleTestEventTriggerDelegate testEventTriggerDelegate;
358+
VerifyOrDie(testEventTriggerDelegate.Init(ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey),
359+
otherDelegate) == CHIP_NO_ERROR);
360+
361+
initParams.testEventTriggerDelegate = &testEventTriggerDelegate;
306362

307363
// We need to set DeviceInfoProvider before Server::Init to setup the storage of DeviceInfoProvider properly.
308364
DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider);

src/app/TestEventTriggerDelegate.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class TestEventTriggerDelegate
4444
*
4545
* @param[in] eventTrigger Event trigger to handle.
4646
*
47-
* @return CHIP_ERROR_INVALID_ARGUMENT when eventTrigger is not a valid test event trigger.
47+
* @return CHIP_NO_ERROR on success or another CHIP_ERROR on failure
4848
*/
4949
virtual CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) = 0;
5050
};

src/app/clusters/general-diagnostics-server/general-diagnostics-server.cpp

+7-10
Original file line numberDiff line numberDiff line change
@@ -364,23 +364,20 @@ bool emberAfGeneralDiagnosticsClusterTestEventTriggerCallback(CommandHandler * c
364364

365365
auto * triggerDelegate = chip::Server::GetInstance().GetTestEventTriggerDelegate();
366366

367+
// Spec says "EnableKeyMismatch" but this never existed prior to 1.0 SVE2 and mismatches
368+
// test plans as well. ConstraintError is specified for most other errors, so
369+
// we keep the behavior as close as possible, except for EnableKeyMismatch which
370+
// is going to be a ConstraintError.
367371
if (triggerDelegate == nullptr || !triggerDelegate->DoesEnableKeyMatch(commandData.enableKey))
368372
{
369-
commandObj->AddStatus(commandPath, Status::UnsupportedAccess);
373+
commandObj->AddStatus(commandPath, Status::ConstraintError);
370374
return true;
371375
}
372376

373377
CHIP_ERROR handleEventTriggerResult = triggerDelegate->HandleEventTrigger(commandData.eventTrigger);
374-
Status returnStatus = StatusIB(handleEventTriggerResult).mStatus;
375-
376-
// When HandleEventTrigger returns INVALID_ARGUMENT we convert that into InvalidCommand to be spec
377-
// compliant.
378-
if (handleEventTriggerResult == CHIP_ERROR_INVALID_ARGUMENT)
379-
{
380-
returnStatus = Status::InvalidCommand;
381-
}
382378

383-
commandObj->AddStatus(commandPath, returnStatus);
379+
// When HandleEventTrigger fails, we simply convert any error to INVALID_COMMAND
380+
commandObj->AddStatus(commandPath, (handleEventTriggerResult != CHIP_NO_ERROR) ? Status::InvalidCommand : Status::Success);
384381
return true;
385382
}
386383

src/controller/python/chip/interaction_model/__init__.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Status(enum.IntEnum):
4242
Deprecated84 = 0x84
4343
InvalidCommand = 0x85
4444
UnsupportedAttribute = 0x86
45-
InvalidValue = 0x87
45+
ConstraintError = 0x87
4646
UnsupportedWrite = 0x88
4747
ResourceExhausted = 0x89
4848
Deprecated8a = 0x8a
@@ -62,15 +62,18 @@ class Status(enum.IntEnum):
6262
Reserved98 = 0x98
6363
Reserved99 = 0x99
6464
Reserved9a = 0x9a
65-
ConstraintError = 0x9b
6665
Busy = 0x9c
6766
Deprecatedc0 = 0xc0
6867
Deprecatedc1 = 0xc1
6968
Deprecatedc2 = 0xc2
7069
UnsupportedCluster = 0xc3
7170
Deprecatedc4 = 0xc4
7271
NoUpstreamSubscription = 0xc5
73-
InvalidArgument = 0xc6
72+
NeedsTimedInteraction = 0xc6
73+
UnsupportedEvent = 0xc7
74+
PathsExhausted = 0xc8
75+
TimedRequestMismatch = 0xc9
76+
FailsafeRequired = 0xca
7477

7578

7679
class InteractionModelError(ChipStackException):

src/controller/python/test/test_scripts/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,7 @@ class AttributeWriteRequest:
10641064
requests = [
10651065
AttributeWriteRequest("Basic", "NodeLabel", "Test"),
10661066
AttributeWriteRequest("Basic", "Location",
1067-
"a pretty loooooooooooooog string", IM.Status.InvalidValue),
1067+
"a pretty loooooooooooooog string", IM.Status.ConstraintError),
10681068
]
10691069
failed_zcl = []
10701070
for req in requests:

src/controller/python/test/test_scripts/cluster_objects.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async def TestWriteRequest(cls, devCtrl):
145145
AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40,
146146
AttributeId=5), Status=chip.interaction_model.Status.Success),
147147
AttributeStatus(Path=AttributePath(EndpointId=0, ClusterId=40,
148-
AttributeId=6), Status=chip.interaction_model.Status.InvalidValue)
148+
AttributeId=6), Status=chip.interaction_model.Status.ConstraintError)
149149
]
150150

151151
logger.info(f"Received WriteResponse: {res}")

src/protocols/interaction_model/StatusCodeList.h

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
* include this file, then undefine the macro.
2323
*/
2424

25+
/// WARNING: If you touch this list, please also update src/controller/python/chip/interaction_model/__init__.py
26+
2527
// clang-format off
2628
CHIP_IM_STATUS_CODE(Success , SUCCESS , 0x0)
2729
CHIP_IM_STATUS_CODE(Failure , FAILURE , 0x01)
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#
2+
# Copyright (c) 2022 Project CHIP Authors
3+
# All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
from imaplib import Commands
19+
from matter_testing_support import MatterBaseTest, default_matter_test_main, async_test_body
20+
from chip.interaction_model import Status, InteractionModelError
21+
import chip.clusters as Clusters
22+
import logging
23+
from mobly import asserts
24+
25+
# Assumes `--enable-key 000102030405060708090a0b0c0d0e0f` on Linux app command line, or a DUT
26+
# that has that Enable Key
27+
kExpectedKey = bytes([b for b in range(16)])
28+
29+
kBadKey = bytes([(b + 1) for b in range(16)])
30+
31+
kAllZerosKey = bytes(b"\x00" * 16)
32+
33+
# Assumes `SampleTestEventTriggerDelegate` as it exists in Linux AppMain.cpp
34+
kValidEventTrigger = 0xFFFF_FFFF_FFF1_0000
35+
kInvalidEventTrigger = 0 # Per TC-DGEN-2.3
36+
37+
38+
class TestEventTrigger(MatterBaseTest):
39+
@async_test_body
40+
async def test_all_zeros_key(self):
41+
dev_ctrl = self.default_controller
42+
with asserts.assert_raises_regex(InteractionModelError, "ConstraintError", "All-zero TestEventTrigger key must return ConstraintError"):
43+
await dev_ctrl.SendCommand(self.dut_node_id, endpoint=0, payload=Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=kAllZerosKey, eventTrigger=kValidEventTrigger))
44+
45+
@async_test_body
46+
async def test_incorrect_key(self):
47+
dev_ctrl = self.default_controller
48+
test_event_triggers_enabled = await self.read_single_attribute(dev_ctrl, self.dut_node_id, endpoint=0, attribute=Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled)
49+
asserts.assert_true(test_event_triggers_enabled, "This test expects Test Event Triggers are Enabled")
50+
51+
with asserts.assert_raises_regex(InteractionModelError, "ConstraintError", "Bad TestEventTrigger key must return ConstraintError"):
52+
await dev_ctrl.SendCommand(self.dut_node_id, endpoint=0, payload=Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=kBadKey, eventTrigger=kValidEventTrigger))
53+
54+
@async_test_body
55+
async def test_correct_key_valid_code(self):
56+
dev_ctrl = self.default_controller
57+
test_event_triggers_enabled = await self.read_single_attribute(dev_ctrl, self.dut_node_id, endpoint=0, attribute=Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled)
58+
asserts.assert_true(test_event_triggers_enabled, "This test expects Test Event Triggers are Enabled")
59+
60+
# No response to command --> Success yields "None".
61+
asserts.assert_is_none(await dev_ctrl.SendCommand(self.dut_node_id, endpoint=0, payload=Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=kExpectedKey, eventTrigger=kValidEventTrigger)))
62+
63+
@async_test_body
64+
async def test_correct_key_invalid_code(self):
65+
dev_ctrl = self.default_controller
66+
test_event_triggers_enabled = await self.read_single_attribute(dev_ctrl, self.dut_node_id, endpoint=0, attribute=Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled)
67+
asserts.assert_true(test_event_triggers_enabled, "This test expects Test Event Triggers are Enabled")
68+
69+
with asserts.assert_raises_regex(InteractionModelError, "InvalidCommand", "Unsupported EventTrigger must return InvalidCommand"):
70+
await dev_ctrl.SendCommand(self.dut_node_id, endpoint=0, payload=Clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=kExpectedKey, eventTrigger=kInvalidEventTrigger))
71+
72+
73+
if __name__ == "__main__":
74+
default_matter_test_main()

src/python_testing/matter_testing_support.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class MatterTestConfig:
122122
logs_path: pathlib.Path = None
123123
paa_trust_store_path: pathlib.Path = None
124124
ble_interface_id: int = None
125+
commission_only: bool = False
125126

126127
admin_vendor_id: int = _DEFAULT_ADMIN_VENDOR_ID
127128
case_admin_subject: int = None
@@ -428,6 +429,7 @@ def populate_commissioning_args(args: argparse.Namespace, config: MatterTestConf
428429
return True
429430

430431
config.commissioning_method = args.commissioning_method
432+
config.commission_only = args.commission_only
431433

432434
if args.dut_node_id is None:
433435
print("error: When --commissioning-method present, --dut-node-id is mandatory!")
@@ -585,6 +587,9 @@ def parse_matter_test_args(argv: List[str]) -> MatterTestConfig:
585587
commission_group.add_argument('--case-admin-subject', action="store", type=int_decimal_or_hex,
586588
metavar="CASE_ADMIN_SUBJECT", help="Set the CASE admin subject to an explicit value (default to commissioner Node ID)")
587589

590+
commission_group.add_argument('--commission-only', action="store_true", default=False,
591+
help="If true, test exits after commissioning without running subsequent tests")
592+
588593
code_group = parser.add_mutually_exclusive_group(required=False)
589594

590595
code_group.add_argument('-q', '--qr-code', type=str,
@@ -731,7 +736,10 @@ def default_matter_test_main(argv=None, **kwargs):
731736
if matter_test_config.commissioning_method is not None:
732737
runner.add_test_class(test_config, CommissionDeviceTest, None)
733738

734-
runner.add_test_class(test_config, test_class, tests)
739+
# Add the tests selected unless we have a commission-only request
740+
if not matter_test_config.commission_only:
741+
runner.add_test_class(test_config, test_class, tests)
742+
735743
try:
736744
runner.run()
737745
ok = runner.results.is_all_pass and ok

0 commit comments

Comments
 (0)