From 13889140230190763ed647168303d59debc0a762 Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Wed, 3 Nov 2021 16:48:59 +0100 Subject: [PATCH] Make it possible for apps into the placeholder directory to have custom tests (#11332) --- .../templates/partials/test_cluster.zapt | 14 ++++ examples/placeholder/linux/BUILD.gn | 9 ++ examples/placeholder/linux/apps/app1/tests.js | 28 +++++++ .../linux/include/MatterCallbacks.h | 78 ++++++++++++++++++ .../placeholder/linux/include/TestCommand.h | 82 +++++++++++++++++++ examples/placeholder/linux/main.cpp | 23 +++++- examples/placeholder/templates/helper.js | 32 ++++++++ examples/placeholder/templates/templates.json | 42 ++++++++++ .../placeholder/templates/tests-commands.zapt | 23 ++++++ examples/platform/linux/Options.cpp | 11 ++- examples/platform/linux/Options.h | 1 + scripts/examples/gn_build_test_example.sh | 4 + .../common/ClusterTestGeneration.js | 41 ++++++++++ 13 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 examples/placeholder/linux/apps/app1/tests.js create mode 100644 examples/placeholder/linux/include/MatterCallbacks.h create mode 100644 examples/placeholder/linux/include/TestCommand.h create mode 100644 examples/placeholder/templates/helper.js create mode 100644 examples/placeholder/templates/templates.json create mode 100644 examples/placeholder/templates/tests-commands.zapt diff --git a/examples/chip-tool/templates/partials/test_cluster.zapt b/examples/chip-tool/templates/partials/test_cluster.zapt index bf65435ceedb7e..e5cdc80497b299 100644 --- a/examples/chip-tool/templates/partials/test_cluster.zapt +++ b/examples/chip-tool/templates/partials/test_cluster.zapt @@ -58,15 +58,18 @@ class {{filename}}: public TestCommand {{#chip_tests_items}} {{#unless (isTestOnlyCluster cluster)}} + {{#unless isWait}} {{#unless isCommand}} chip::Callback::CallbackfailureArguments}})> {{>failureCallback}} { {{>failureResponse}}, this }; chip::Callback::CallbacksuccessArguments}})> {{>successCallback}} { {{>successResponse}}, this }; {{/unless}} {{/unless}} + {{/unless}} {{/chip_tests_items}} {{#chip_tests_items}} {{#unless (isTestOnlyCluster cluster)}} + {{#unless isWait}} {{#unless isCommand}} static void {{>failureResponse}}({{> failureArguments}}) { @@ -83,6 +86,7 @@ class {{filename}}: public TestCommand bool mReceivedReport_{{index}} = false; {{/if}} + {{/unless}} {{/unless}} {{/unless}} {{/chip_tests_items}} @@ -98,6 +102,16 @@ class {{filename}}: public TestCommand { return {{command}}({{#chip_tests_item_parameters}}{{#not_first}}, {{/not_first}}{{#if (isString type)}}"{{/if}}{{definedValue}}{{#if (isString type)}}"{{/if}}{{/chip_tests_item_parameters}}); } + {{else if isWait}} + CHIP_ERROR {{>testCommand}}() + { + ChipLogError(chipTool, "[Endpoint: {{endpoint}} Cluster: {{cluster}} {{#if isAttribute}}Attribute: {{attribute}}{{else}}Command: {{wait}}{{/if}}] {{label}}"); + {{#*inline "waitForTypeName"}}{{#if isAttribute}}Attribute{{else}}Command{{/if}}{{/inline}} + {{#*inline "waitForTypeId"}}chip::app::Clusters::{{asUpperCamelCase cluster}}::{{#if isAttribute}}Attributes::{{attribute}}{{else}}Commands::{{wait}}{{/if}}::Id{{/inline}} + ClearAttributeAndCommandPaths(); + m{{>waitForTypeName}}Path = chip::app::Concrete{{>waitForTypeName}}Path({{endpoint}}, chip::app::Clusters::{{asUpperCamelCase cluster}}::Id, {{>waitForTypeId}}); + return CHIP_NO_ERROR; + } {{else}} {{#*inline "failureResponse"}}OnFailureResponse_{{index}}{{/inline}} {{#*inline "successResponse"}}OnSuccessResponse_{{index}}{{/inline}} diff --git a/examples/placeholder/linux/BUILD.gn b/examples/placeholder/linux/BUILD.gn index b4a9fded006376..3b193356b15154 100644 --- a/examples/placeholder/linux/BUILD.gn +++ b/examples/placeholder/linux/BUILD.gn @@ -28,6 +28,13 @@ chip_data_model("configuration") { is_server = true } +config("includes") { + include_dirs = [ + ".", + "include", + ] +} + executable("chip-${chip_tests_zap_config}") { sources = [ "main.cpp" ] @@ -37,6 +44,8 @@ executable("chip-${chip_tests_zap_config}") { "${chip_root}/src/lib", ] + include_dirs = [ "include" ] + cflags = [ "-Wconversion" ] output_dir = root_out_dir diff --git a/examples/placeholder/linux/apps/app1/tests.js b/examples/placeholder/linux/apps/app1/tests.js new file mode 100644 index 00000000000000..b48f9853011538 --- /dev/null +++ b/examples/placeholder/linux/apps/app1/tests.js @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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. + */ + +function getTests() +{ + const tests = []; + + return tests.join(', '); +} + +// +// Module exports +// +exports.getTests = getTests; diff --git a/examples/placeholder/linux/include/MatterCallbacks.h b/examples/placeholder/linux/include/MatterCallbacks.h new file mode 100644 index 00000000000000..c4c2a6d7d608c7 --- /dev/null +++ b/examples/placeholder/linux/include/MatterCallbacks.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2021 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +TestCommand * gTestCommand = nullptr; + +void OnPlatformEvent(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg) +{ + switch (event->Type) + { + case chip::DeviceLayer::DeviceEventType::kCommissioningComplete: + ChipLogError(Zcl, "Commissioning complete"); + + TestCommand * command = reinterpret_cast(arg); + if (command == nullptr) + { + ChipLogError(Zcl, "No tests."); + return; + } + + gTestCommand = command; + gTestCommand->NextTest(); + break; + } +} + +void MatterPostCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath) +{ + VerifyOrReturn(gTestCommand != nullptr); + + ChipLogError(Zcl, "Receive command: Endpoint: %u, Cluster: " ChipLogFormatMEI ", Command: " ChipLogFormatMEI, + commandPath.mEndpointId, ChipLogValueMEI(commandPath.mClusterId), ChipLogValueMEI(commandPath.mCommandId)); + + gTestCommand->CheckCommandPath(commandPath); +} + +void MatterPostAttributeReadCallback(const chip::app::ConcreteAttributePath & attributePath) +{ + VerifyOrReturn(gTestCommand != nullptr); + + ChipLogError(Zcl, "Receive READ attribute command: Endpoint: %u, Cluster: " ChipLogFormatMEI ", Attribute: " ChipLogFormatMEI, + attributePath.mEndpointId, ChipLogValueMEI(attributePath.mClusterId), ChipLogValueMEI(attributePath.mAttributeId)); + + gTestCommand->CheckAttributePath(attributePath); +} + +void MatterPostAttributeWriteCallback(const chip::app::ConcreteAttributePath & attributePath) +{ + VerifyOrReturn(gTestCommand != nullptr); + + ChipLogError(Zcl, "Receive WRITE attribute command: Endpoint: %u, Cluster: " ChipLogFormatMEI ", Attribute: " ChipLogFormatMEI, + attributePath.mEndpointId, ChipLogValueMEI(attributePath.mClusterId), ChipLogValueMEI(attributePath.mAttributeId)); + + gTestCommand->CheckAttributePath(attributePath); +} diff --git a/examples/placeholder/linux/include/TestCommand.h b/examples/placeholder/linux/include/TestCommand.h new file mode 100644 index 00000000000000..6aad332a399396 --- /dev/null +++ b/examples/placeholder/linux/include/TestCommand.h @@ -0,0 +1,82 @@ +/* + * + * Copyright (c) 2021 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +class TestCommand +{ +public: + TestCommand(const char * commandName) : mCommandPath(0, 0, 0), mAttributePath(0, 0, 0) {} + virtual ~TestCommand() {} + + virtual void NextTest() = 0; + void Wait() {} + void SetCommandExitStatus(CHIP_ERROR status) + { + chip::DeviceLayer::PlatformMgr().StopEventLoopTask(); + exit(CHIP_NO_ERROR == status ? EXIT_SUCCESS : EXIT_FAILURE); + } + + CHIP_ERROR Log(const char * message) + { + ChipLogProgress(chipTool, "%s", message); + NextTest(); + return CHIP_NO_ERROR; + } + + void CheckCommandPath(const chip::app::ConcreteCommandPath & commandPath) + { + if (commandPath == mCommandPath) + { + NextTest(); + return; + } + + ChipLogError(chipTool, "CommandPath does not match"); + SetCommandExitStatus(CHIP_ERROR_INTERNAL); + } + + void CheckAttributePath(const chip::app::ConcreteAttributePath & attributePath) + { + if (attributePath == mAttributePath) + { + NextTest(); + return; + } + + ChipLogError(chipTool, "AttributePath does not match"); + return SetCommandExitStatus(CHIP_ERROR_INTERNAL); + } + + void ClearAttributeAndCommandPaths() + { + mCommandPath = chip::app::ConcreteCommandPath(0, 0, 0); + mAttributePath = chip::app::ConcreteAttributePath(0, 0, 0); + } + +protected: + chip::app::ConcreteCommandPath mCommandPath; + chip::app::ConcreteAttributePath mAttributePath; +}; diff --git a/examples/placeholder/linux/main.cpp b/examples/placeholder/linux/main.cpp index 32a380ef4ef60d..e0252bb2bc886c 100644 --- a/examples/placeholder/linux/main.cpp +++ b/examples/placeholder/linux/main.cpp @@ -17,13 +17,34 @@ */ #include "AppMain.h" +#include "Options.h" -#include #include +#include "MatterCallbacks.h" + +void RunTestCommand() +{ + const char * command = LinuxDeviceOptions::GetInstance().command; + if (command == nullptr) + { + return; + } + + auto test = GetTestCommand(command); + if (test.get() == nullptr) + { + ChipLogError(chipTool, "Specified test command does not exists: %s", command); + return; + } + + chip::DeviceLayer::PlatformMgr().AddEventHandler(OnPlatformEvent, reinterpret_cast(test.get())); +} + int main(int argc, char * argv[]) { VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0); + RunTestCommand(); ChipLinuxAppMainLoop(); return 0; } diff --git a/examples/placeholder/templates/helper.js b/examples/placeholder/templates/helper.js new file mode 100644 index 00000000000000..18965aa9158922 --- /dev/null +++ b/examples/placeholder/templates/helper.js @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * 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. + */ + +function getTests() +{ + try { + const appTest = require('../linux/apps/' + process.env.TARGET_APP + '/tests.js'); + return appTest.getTests(); + } catch (e) { + console.info("No tests configuration has been found."); + return ''; + } +} + +// +// Module exports +// +exports.getTests = getTests; diff --git a/examples/placeholder/templates/templates.json b/examples/placeholder/templates/templates.json new file mode 100644 index 00000000000000..4e422b59efd955 --- /dev/null +++ b/examples/placeholder/templates/templates.json @@ -0,0 +1,42 @@ +{ + "name": "Placeholder templates", + "version": "chip-v1", + "helpers": [ + "../../../src/app/zap-templates/partials/helper.js", + "../../../src/app/zap-templates/common/StringHelper.js", + "../../../src/app/zap-templates/templates/app/helper.js", + "../../../src/app/zap-templates/templates/chip/helper.js", + "../../../src/app/zap-templates/common/ClusterTestGeneration.js", + "helper.js" + ], + "override": "../../../src/app/zap-templates/common/override.js", + "partials": [ + { + "name": "header", + "path": "../../../src/app/zap-templates/partials/header.zapt" + }, + { + "name": "clusters_header", + "path": "../../../src/app/zap-templates/partials/clusters_header.zapt" + }, + { + "name": "cluster_header", + "path": "../../../src/app/zap-templates/partials/cluster_header.zapt" + }, + { + "name": "test_cluster", + "path": "../../../examples/chip-tool/templates/partials/test_cluster.zapt" + }, + { + "name": "commandValue", + "path": "../../../examples/chip-tool/templates/partials/test_cluster_command_value.zapt" + } + ], + "templates": [ + { + "path": "tests-commands.zapt", + "name": "Tests Commands header", + "output": "test/Commands.h" + } + ] +} diff --git a/examples/placeholder/templates/tests-commands.zapt b/examples/placeholder/templates/tests-commands.zapt new file mode 100644 index 00000000000000..f23545eebd8ac5 --- /dev/null +++ b/examples/placeholder/templates/tests-commands.zapt @@ -0,0 +1,23 @@ +{{> header}} + +#pragma once + +#include "TestCommand.h" + +{{#if (getTests)}} +{{>test_cluster tests=(getTests)}} +{{/if}} + +std::unique_ptrGetTestCommand(std::string testName) +{ + {{#if (getTests)}} + {{#chip_tests (getTests)}} + if (testName == "{{filename}}") + { + return std::unique_ptr<{{filename}}>(new {{filename}}()); + } + {{/chip_tests}} + {{/if}} + + return nullptr; +} diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 9815bba80ad15f..320e60837aa543 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -45,7 +45,8 @@ enum kDeviceOption_Passcode = 0x1009, kDeviceOption_SecuredDevicePort = 0x100a, kDeviceOption_SecuredCommissionerPort = 0x100b, - kDeviceOption_UnsecuredCommissionerPort = 0x100c + kDeviceOption_UnsecuredCommissionerPort = 0x100c, + kDeviceOption_Command = 0x100d }; constexpr unsigned kAppUsageLength = 64; @@ -70,6 +71,7 @@ OptionDef sDeviceOptionDefs[] = { { "secured-device-port", kArgumentRequired, kDeviceOption_SecuredDevicePort }, { "secured-commissioner-port", kArgumentRequired, kDeviceOption_SecuredCommissionerPort }, { "unsecured-commissioner-port", kArgumentRequired, kDeviceOption_UnsecuredCommissionerPort }, + { "command", kArgumentRequired, kDeviceOption_Command }, {} }; @@ -119,6 +121,9 @@ const char * sDeviceOptionHelp = "\n" " --unsecured-commissioner-port \n" " A 16-bit unsigned integer specifying the port to use for unsecured commissioner messages (default is 5550).\n" + "\n" + " --command \n" + " A name for a command to execute during startup.\n" "\n"; bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue) @@ -184,6 +189,10 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, LinuxDeviceOptions::GetInstance().unsecuredCommissionerPort = static_cast(atoi(aValue)); break; + case kDeviceOption_Command: + LinuxDeviceOptions::GetInstance().command = aValue; + break; + default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", aProgram, aName); retval = false; diff --git a/examples/platform/linux/Options.h b/examples/platform/linux/Options.h index e2a76f9366f45b..e347f0297d53df 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -38,6 +38,7 @@ struct LinuxDeviceOptions uint32_t securedDevicePort = CHIP_PORT; uint32_t securedCommissionerPort = CHIP_PORT + 2; uint32_t unsecuredCommissionerPort = CHIP_UDC_PORT; + const char * command = nullptr; static LinuxDeviceOptions & GetInstance(); }; diff --git a/scripts/examples/gn_build_test_example.sh b/scripts/examples/gn_build_test_example.sh index 49c025b6ad854b..1e79a10851a2ef 100755 --- a/scripts/examples/gn_build_test_example.sh +++ b/scripts/examples/gn_build_test_example.sh @@ -42,7 +42,11 @@ function runZAP() { touch "$ZAP_OUTPUT_DIR"/af-gen-event.h fi + # Generates the generic files for the given zap configuration "$CHIP_ROOT"/scripts/tools/zap/generate.py "$ZAP_INPUT_FILE" -o "$ZAP_OUTPUT_DIR" + + # Generates the specific files for the given zap configuration + TARGET_APP=$APP_DIR "$CHIP_ROOT"/scripts/tools/zap/generate.py "$ZAP_INPUT_FILE" -t "$INPUT_DIR"/../templates/templates.json -o "$ZAP_OUTPUT_DIR" } function runGN() { diff --git a/src/app/zap-templates/common/ClusterTestGeneration.js b/src/app/zap-templates/common/ClusterTestGeneration.js index 4e276995a159a1..42bb454c998356 100644 --- a/src/app/zap-templates/common/ClusterTestGeneration.js +++ b/src/app/zap-templates/common/ClusterTestGeneration.js @@ -34,6 +34,7 @@ const { asUpperCamelCase } = require(basePath + 'src/app/zap-templa const kClusterName = 'cluster'; const kEndpointName = 'endpoint'; const kCommandName = 'command'; +const kWaitCommandName = 'wait'; const kIndexName = 'index'; const kValuesName = 'values'; const kConstraintsName = 'constraints'; @@ -69,6 +70,39 @@ function setDefault(test, name, defaultValue) } function setDefaultType(test) +{ + if (kWaitCommandName in test) { + setDefaultTypeForWaitCommand(test); + } else { + setDefaultTypeForCommand(test); + } +} + +function setDefaultTypeForWaitCommand(test) +{ + const type = test[kWaitCommandName]; + switch (type) { + case 'readAttribute': + test.isAttribute = true; + test.isReadAttribute = true; + break; + case 'writeAttribute': + test.isAttribute = true; + test.isWriteAttribute = true; + break; + case 'subscribeAttribute': + test.isAttribute = true; + test.isSubscribeAttribute = true; + break; + default: + test.isCommand = true; + break; + } + + test.isWait = true; +} + +function setDefaultTypeForCommand(test) { const type = test[kCommandName]; switch (type) { @@ -101,6 +135,8 @@ function setDefaultType(test) test.isCommand = true; break; } + + test.isWait = false; } function setDefaultArguments(test) @@ -155,6 +191,11 @@ function setDefaultResponse(test) throwError(test, errorStr); } + // Step that waits for a particular event does not requires constraints nor expected values. + if (test.isWait) { + return; + } + if (!test.isAttribute) { return; }