From ee9b9a36ce18bcc7eb3a35ef15c6ff99a44bbfcd Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Fri, 12 Nov 2021 18:05:32 +0100 Subject: [PATCH] [CI] Add support for parsing PICS code at runtime using an optionaly specified file containing enabled PICS items (#11729) * Add support for parsing PICS code at runtime using an optionaly specified file containing enabled PICS items * Update generated code --- examples/chip-tool/BUILD.gn | 1 + .../chip-tool/commands/common/Command.cpp | 114 ++++++++---- examples/chip-tool/commands/common/Command.h | 71 ++++---- .../chip-tool/commands/common/Commands.cpp | 9 + .../chip-tool/commands/tests/TestCommand.cpp | 23 +++ .../chip-tool/commands/tests/TestCommand.h | 13 +- .../templates/partials/test_cluster.zapt | 2 +- examples/placeholder/linux/BUILD.gn | 1 + .../linux/include/MatterCallbacks.h | 6 + .../placeholder/linux/include/TestCommand.h | 23 +++ examples/platform/linux/Options.cpp | 11 +- examples/platform/linux/Options.h | 1 + src/app/tests/suites/certification/PICS.yaml | 5 - src/app/tests/suites/pics/BUILD.gn | 31 ++++ .../pics/PICSBooleanExpressionParser.cpp | 167 ++++++++++++++++++ .../suites/pics/PICSBooleanExpressionParser.h | 48 +++++ .../tests/suites/pics/PICSBooleanReader.cpp | 74 ++++++++ src/app/tests/suites/pics/PICSBooleanReader.h | 31 ++++ .../common/ClusterTestGeneration.js | 14 +- .../chip-tool/zap-generated/test/Commands.h | 4 +- 20 files changed, 561 insertions(+), 88 deletions(-) create mode 100644 src/app/tests/suites/pics/BUILD.gn create mode 100644 src/app/tests/suites/pics/PICSBooleanExpressionParser.cpp create mode 100644 src/app/tests/suites/pics/PICSBooleanExpressionParser.h create mode 100644 src/app/tests/suites/pics/PICSBooleanReader.cpp create mode 100644 src/app/tests/suites/pics/PICSBooleanReader.h diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index 10741799f6e8a6..cfbffd3fcceadd 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -54,6 +54,7 @@ executable("chip-tool") { deps = [ "${chip_root}/src/app/server", + "${chip_root}/src/app/tests/suites/pics", "${chip_root}/src/controller/data_model", "${chip_root}/src/lib", "${chip_root}/src/platform", diff --git a/examples/chip-tool/commands/common/Command.cpp b/examples/chip-tool/commands/common/Command.cpp index e1c9ba153c1d82..c9cd0a900c97b9 100644 --- a/examples/chip-tool/commands/common/Command.cpp +++ b/examples/chip-tool/commands/common/Command.cpp @@ -37,10 +37,19 @@ bool Command::InitArguments(int argc, char ** argv) bool isValidCommand = false; size_t argsCount = mArgs.size(); - VerifyOrExit(argsCount == (size_t)(argc), + size_t argsOptionalCount = 0; + for (size_t i = 0; i < argsCount; i++) + { + if (mArgs[i].optional) + { + argsOptionalCount++; + } + } + + VerifyOrExit((size_t)(argc) >= (argsCount - argsOptionalCount) && (size_t)(argc) <= argsCount, ChipLogError(chipTool, "InitArgs: Wrong arguments number: %d instead of %zu", argc, argsCount)); - for (size_t i = 0; i < argsCount; i++) + for (size_t i = 0; i < (size_t) argc; i++) { if (!InitArgument(i, argv[i])) { @@ -48,6 +57,10 @@ bool Command::InitArguments(int argc, char ** argv) } } + for (size_t i = (size_t) argc; i < argsCount; i++) + { + } + isValidCommand = true; exit: @@ -93,12 +106,16 @@ bool Command::InitArgument(size_t argIndex, char * argValue) switch (arg.type) { case ArgumentType::Attribute: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); char * value = reinterpret_cast(arg.value); isValidArgument = (strcmp(argValue, value) == 0); break; } case ArgumentType::String: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); const char ** value = reinterpret_cast(arg.value); *value = argValue; isValidArgument = true; @@ -106,6 +123,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::CharString: { + if (arg.optional) + arg.value = &(reinterpret_cast> *>(arg.value))->Emplace(); auto * value = static_cast *>(arg.value); *value = chip::Span(argValue, strlen(argValue)); isValidArgument = true; @@ -113,6 +132,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::OctetString: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); auto * value = static_cast(arg.value); // We support two ways to pass an octet string argument. If it happens // to be all-ASCII, you can just pass it in. Otherwise you can pass in @@ -167,6 +188,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) case ArgumentType::Boolean: case ArgumentType::Number_uint8: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); uint8_t * value = reinterpret_cast(arg.value); // stringstream treats uint8_t as char, which is not what we want here. @@ -190,6 +213,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_uint16: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); uint16_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -202,6 +227,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_uint32: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); uint32_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -214,6 +241,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_uint64: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); uint64_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -226,6 +255,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_int8: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); int8_t * value = reinterpret_cast(arg.value); // stringstream treats int8_t as char, which is not what we want here. @@ -249,6 +280,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_int16: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); int16_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -261,6 +294,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_int32: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); int32_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -273,6 +308,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Number_int64: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); int64_t * value = reinterpret_cast(arg.value); std::stringstream ss; isHexNotation ? ss << std::hex << argValue : ss << argValue; @@ -285,6 +322,8 @@ bool Command::InitArgument(size_t argIndex, char * argValue) } case ArgumentType::Address: { + if (arg.optional) + arg.value = &(reinterpret_cast *>(arg.value))->Emplace(); AddressWithInterface * value = reinterpret_cast(arg.value); isValidArgument = ParseAddressWithInterface(argValue, value); break; @@ -299,82 +338,89 @@ bool Command::InitArgument(size_t argIndex, char * argValue) return isValidArgument; } -size_t Command::AddArgument(const char * name, const char * value) +size_t Command::AddArgument(const char * name, const char * value, bool optional) { Argument arg; - arg.type = ArgumentType::Attribute; - arg.name = name; - arg.value = const_cast(reinterpret_cast(value)); + arg.type = ArgumentType::Attribute; + arg.name = name; + arg.value = const_cast(reinterpret_cast(value)); + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, char ** value) +size_t Command::AddArgument(const char * name, char ** value, bool optional) { Argument arg; - arg.type = ArgumentType::CharString; - arg.name = name; - arg.value = reinterpret_cast(value); + arg.type = ArgumentType::CharString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, chip::CharSpan * value) +size_t Command::AddArgument(const char * name, chip::CharSpan * value, bool optional) { Argument arg; - arg.type = ArgumentType::CharString; - arg.name = name; - arg.value = reinterpret_cast(value); + arg.type = ArgumentType::CharString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, chip::ByteSpan * value) +size_t Command::AddArgument(const char * name, chip::ByteSpan * value, bool optional) { Argument arg; - arg.type = ArgumentType::OctetString; - arg.name = name; - arg.value = reinterpret_cast(value); + arg.type = ArgumentType::OctetString; + arg.name = name; + arg.value = reinterpret_cast(value); + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, AddressWithInterface * out) +size_t Command::AddArgument(const char * name, AddressWithInterface * out, bool optional) { Argument arg; - arg.type = ArgumentType::Address; - arg.name = name; - arg.value = reinterpret_cast(out); + arg.type = ArgumentType::Address; + arg.name = name; + arg.value = reinterpret_cast(out); + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type) +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, bool optional) { Argument arg; - arg.type = type; - arg.name = name; - arg.value = out; - arg.min = min; - arg.max = max; + arg.type = type; + arg.name = name; + arg.value = out; + arg.min = min; + arg.max = max; + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); } -size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out) +size_t Command::AddArgument(const char * name, int64_t min, uint64_t max, void * out, bool optional) { Argument arg; - arg.type = ArgumentType::Number_uint8; - arg.name = name; - arg.value = out; - arg.min = min; - arg.max = max; + arg.type = ArgumentType::Number_uint8; + arg.name = name; + arg.value = out; + arg.min = min; + arg.max = max; + arg.optional = optional; mArgs.emplace_back(arg); return mArgs.size(); diff --git a/examples/chip-tool/commands/common/Command.h b/examples/chip-tool/commands/common/Command.h index e3da77dd679108..853d81349d6f7e 100644 --- a/examples/chip-tool/commands/common/Command.h +++ b/examples/chip-tool/commands/common/Command.h @@ -74,6 +74,7 @@ struct Argument int64_t min; uint64_t max; void * value; + bool optional; }; class Command @@ -91,10 +92,11 @@ class Command const char * GetName(void) const { return mName; } const char * GetAttribute(void) const; const char * GetArgumentName(size_t index) const; + bool GetArgumentIsOptional(size_t index) const { return mArgs[index].optional; } size_t GetArgumentsCount(void) const { return mArgs.size(); } bool InitArguments(int argc, char ** argv); - size_t AddArgument(const char * name, const char * value); + size_t AddArgument(const char * name, const char * value, bool optional = false); /** * @brief * Add a char string command argument @@ -103,90 +105,89 @@ class Command * @param value A pointer to a `char *` where the argv value will be stored * @returns The number of arguments currently added to the command */ - size_t AddArgument(const char * name, char ** value); + size_t AddArgument(const char * name, char ** value, bool optional = false); /** * Add an octet string command argument */ - size_t AddArgument(const char * name, chip::ByteSpan * value); - size_t AddArgument(const char * name, chip::Span * value); - size_t AddArgument(const char * name, AddressWithInterface * out); - size_t AddArgument(const char * name, int64_t min, uint64_t max, bool * out) + size_t AddArgument(const char * name, chip::ByteSpan * value, bool optional = false); + size_t AddArgument(const char * name, chip::Span * value, bool optional = false); + size_t AddArgument(const char * name, AddressWithInterface * out, bool optional = false); + size_t AddArgument(const char * name, int64_t min, uint64_t max, bool * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Boolean); + return AddArgument(name, min, max, reinterpret_cast(out), Boolean, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, int8_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, int8_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_int8); + return AddArgument(name, min, max, reinterpret_cast(out), Number_int8, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, int16_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, int16_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_int16); + return AddArgument(name, min, max, reinterpret_cast(out), Number_int16, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, int32_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, int32_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_int32); + return AddArgument(name, min, max, reinterpret_cast(out), Number_int32, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, int64_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, int64_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_int64); + return AddArgument(name, min, max, reinterpret_cast(out), Number_int64, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, uint8_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint8_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_uint8); + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint8, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, uint16_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint16_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_uint16); + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint16, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, uint32_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint32_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_uint32); + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint32, optional); } - size_t AddArgument(const char * name, int64_t min, uint64_t max, uint64_t * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, uint64_t * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast(out), Number_uint64); + return AddArgument(name, min, max, reinterpret_cast(out), Number_uint64, optional); } template ::value>> - size_t AddArgument(const char * name, int64_t min, uint64_t max, T * out) + size_t AddArgument(const char * name, int64_t min, uint64_t max, T * out, bool optional = false) { - return AddArgument(name, min, max, reinterpret_cast *>(out)); + return AddArgument(name, min, max, reinterpret_cast *>(out), optional); } template size_t AddArgument(const char * name, chip::Optional * value) { - // We always require our args to be provided for the moment. - return AddArgument(name, &value->Emplace()); + return AddArgument(name, reinterpret_cast(value), true); } template size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::Optional * value) { - // We always require our args to be provided for the moment. - return AddArgument(name, min, max, &value->Emplace()); + return AddArgument(name, min, max, reinterpret_cast(value), true); } template - size_t AddArgument(const char * name, chip::app::DataModel::Nullable * value) + size_t AddArgument(const char * name, chip::app::DataModel::Nullable * value, bool optional = false) { // We always require our args to be provided for the moment. - return AddArgument(name, &value->SetNonNull()); + return AddArgument(name, &value->SetNonNull(), optional); } template - size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::app::DataModel::Nullable * value) + size_t AddArgument(const char * name, int64_t min, uint64_t max, chip::app::DataModel::Nullable * value, + bool optional = false) { // We always require our args to be provided for the moment. - return AddArgument(name, min, max, &value->SetNonNull()); + return AddArgument(name, min, max, &value->SetNonNull(), optional); } virtual CHIP_ERROR Run() = 0; private: bool InitArgument(size_t argIndex, char * argValue); - size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type); - size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, bool optional); + size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, bool optional); const char * mName = nullptr; std::vector mArgs; diff --git a/examples/chip-tool/commands/common/Commands.cpp b/examples/chip-tool/commands/common/Commands.cpp index 18550d724853e7..c99ea1a274eee3 100644 --- a/examples/chip-tool/commands/common/Commands.cpp +++ b/examples/chip-tool/commands/common/Commands.cpp @@ -255,7 +255,16 @@ void Commands::ShowCommand(std::string executable, std::string clusterName, Comm for (size_t i = 0; i < argumentsCount; i++) { arguments += " "; + bool isOptional = command->GetArgumentIsOptional(i); + if (isOptional) + { + arguments += "["; + } arguments += command->GetArgumentName(i); + if (isOptional) + { + arguments += "]"; + } } fprintf(stderr, " %s %s %s\n", executable.c_str(), clusterName.c_str(), arguments.c_str()); } diff --git a/examples/chip-tool/commands/tests/TestCommand.cpp b/examples/chip-tool/commands/tests/TestCommand.cpp index 02d77dcd98a702..7263614751e3d9 100644 --- a/examples/chip-tool/commands/tests/TestCommand.cpp +++ b/examples/chip-tool/commands/tests/TestCommand.cpp @@ -29,6 +29,11 @@ void TestCommand::OnDeviceConnectedFn(void * context, chip::DeviceProxy * device auto * command = static_cast(context); VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "Device connected, but cannot run the test, as the context is null")); command->mDevice = device; + + if (command->mPICSFilePath.HasValue()) + { + command->PICS.SetValue(PICSBooleanReader::Read(command->mPICSFilePath.Value())); + } command->NextTest(); } @@ -132,3 +137,21 @@ bool TestCommand::CheckValueAsString(const char * itemName, chip::CharSpan curre return true; } + +bool TestCommand::ShouldSkip(const char * expression) +{ + // If there is no PICS configuration file, considers that nothing should be skipped. + if (!PICS.HasValue()) + { + return false; + } + + std::map pics(PICS.Value()); + bool shouldSkip = !PICSBooleanExpressionParser::Eval(expression, pics); + if (shouldSkip) + { + ChipLogProgress(chipTool, " **** Skipping: %s == false\n", expression); + WaitForMs(0); + } + return shouldSkip; +} diff --git a/examples/chip-tool/commands/tests/TestCommand.h b/examples/chip-tool/commands/tests/TestCommand.h index 0e8ed8974624d7..5fd42d96b240cc 100644 --- a/examples/chip-tool/commands/tests/TestCommand.h +++ b/examples/chip-tool/commands/tests/TestCommand.h @@ -21,6 +21,8 @@ #include "../common/CHIPCommand.h" #include #include +#include +#include #include #include #include @@ -36,6 +38,7 @@ class TestCommand : public CHIPCommand { AddArgument("node-id", 0, UINT64_MAX, &mNodeId); AddArgument("delayInMs", 0, UINT64_MAX, &mDelayInMs); + AddArgument("PICS", &mPICSFilePath); } /////////// CHIPCommand Interface ///////// @@ -214,12 +217,16 @@ class TestCommand : public CHIPCommand chip::Callback::Callback mOnDeviceConnectedCallback; chip::Callback::Callback mOnDeviceConnectionFailureCallback; + bool ShouldSkip(const char * expression); + void Wait() { - if (mDelayInMs) + if (mDelayInMs.HasValue()) { - chip::test_utils::SleepMillis(mDelayInMs); + chip::test_utils::SleepMillis(mDelayInMs.Value()); } }; - uint64_t mDelayInMs = 0; + chip::Optional mDelayInMs; + chip::Optional mPICSFilePath; + chip::Optional> PICS; }; diff --git a/examples/chip-tool/templates/partials/test_cluster.zapt b/examples/chip-tool/templates/partials/test_cluster.zapt index 52176922bded08..a6841557b1fef9 100644 --- a/examples/chip-tool/templates/partials/test_cluster.zapt +++ b/examples/chip-tool/templates/partials/test_cluster.zapt @@ -32,7 +32,7 @@ class {{filename}}: public TestCommand {{#chip_tests_items}} case {{index}}: ChipLogProgress(chipTool, " ***** Test Step {{index}} : {{label}}\n"); - err = Test{{asUpperCamelCase label}}_{{index}}(); + err = {{#if PICS}}ShouldSkip("{{PICS}}") ? CHIP_NO_ERROR : {{/if}}Test{{asUpperCamelCase label}}_{{index}}(); break; {{/chip_tests_items}} } diff --git a/examples/placeholder/linux/BUILD.gn b/examples/placeholder/linux/BUILD.gn index 3b193356b15154..0e0b8601990e5f 100644 --- a/examples/placeholder/linux/BUILD.gn +++ b/examples/placeholder/linux/BUILD.gn @@ -41,6 +41,7 @@ executable("chip-${chip_tests_zap_config}") { deps = [ ":configuration", "${chip_root}/examples/platform/linux:app-main", + "${chip_root}/src/app/tests/suites/pics", "${chip_root}/src/lib", ] diff --git a/examples/placeholder/linux/include/MatterCallbacks.h b/examples/placeholder/linux/include/MatterCallbacks.h index ca8bf57feb8eb1..b7a9c45de6dd76 100644 --- a/examples/placeholder/linux/include/MatterCallbacks.h +++ b/examples/placeholder/linux/include/MatterCallbacks.h @@ -42,6 +42,12 @@ TestCommand * GetTargetTest() return nullptr; } + const char * PICSFilePath = LinuxDeviceOptions::GetInstance().PICS; + if (PICSFilePath != nullptr) + { + test->PICS.SetValue(PICSBooleanReader::Read(PICSFilePath)); + } + return test.get(); } diff --git a/examples/placeholder/linux/include/TestCommand.h b/examples/placeholder/linux/include/TestCommand.h index 86b40a6bd6cc1b..d4fcf6512f5d26 100644 --- a/examples/placeholder/linux/include/TestCommand.h +++ b/examples/placeholder/linux/include/TestCommand.h @@ -23,6 +23,9 @@ #include #include +#include +#include + #include #include #include @@ -99,6 +102,26 @@ class TestCommand mAttributePath = chip::app::ConcreteAttributePath(0, 0, 0); } + bool ShouldSkip(const char * expression) + { + // If there is no PICS configuration file, considers that nothing should be skipped. + if (!PICS.HasValue()) + { + return false; + } + + std::map pics(PICS.Value()); + bool shouldSkip = !PICSBooleanExpressionParser::Eval(expression, pics); + if (shouldSkip) + { + ChipLogProgress(chipTool, " **** Skipping: %s == false\n", expression); + NextTest(); + } + return shouldSkip; + } + + chip::Optional> PICS; + std::atomic_bool isRunning{ true }; protected: diff --git a/examples/platform/linux/Options.cpp b/examples/platform/linux/Options.cpp index 320e60837aa543..b68e34dd28a1d5 100644 --- a/examples/platform/linux/Options.cpp +++ b/examples/platform/linux/Options.cpp @@ -46,7 +46,8 @@ enum kDeviceOption_SecuredDevicePort = 0x100a, kDeviceOption_SecuredCommissionerPort = 0x100b, kDeviceOption_UnsecuredCommissionerPort = 0x100c, - kDeviceOption_Command = 0x100d + kDeviceOption_Command = 0x100d, + kDeviceOption_PICS = 0x100e }; constexpr unsigned kAppUsageLength = 64; @@ -72,6 +73,7 @@ OptionDef sDeviceOptionDefs[] = { { "secured-commissioner-port", kArgumentRequired, kDeviceOption_SecuredCommissionerPort }, { "unsecured-commissioner-port", kArgumentRequired, kDeviceOption_UnsecuredCommissionerPort }, { "command", kArgumentRequired, kDeviceOption_Command }, + { "PICS", kArgumentRequired, kDeviceOption_PICS }, {} }; @@ -124,6 +126,9 @@ const char * sDeviceOptionHelp = "\n" " --command \n" " A name for a command to execute during startup.\n" + "\n" + " --PICS \n" + " A file containing PICS items.\n" "\n"; bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue) @@ -193,6 +198,10 @@ bool HandleOption(const char * aProgram, OptionSet * aOptions, int aIdentifier, LinuxDeviceOptions::GetInstance().command = aValue; break; + case kDeviceOption_PICS: + LinuxDeviceOptions::GetInstance().PICS = 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 2f1ee87627aed0..8bb086c1b184c0 100644 --- a/examples/platform/linux/Options.h +++ b/examples/platform/linux/Options.h @@ -39,6 +39,7 @@ struct LinuxDeviceOptions uint32_t securedCommissionerPort = CHIP_PORT + 2; uint32_t unsecuredCommissionerPort = CHIP_UDC_PORT; const char * command = nullptr; + const char * PICS = nullptr; static LinuxDeviceOptions & GetInstance(); }; diff --git a/src/app/tests/suites/certification/PICS.yaml b/src/app/tests/suites/certification/PICS.yaml index 94d70422ee3ca5..c730e07ef46992 100644 --- a/src/app/tests/suites/certification/PICS.yaml +++ b/src/app/tests/suites/certification/PICS.yaml @@ -17,20 +17,15 @@ name: PICS Items PICS: - label: "Does the device support discovery over Bluetooth Low Power (BLE)" id: BLE - value: false - label: "Does the device support discovery over WiFi?" id: WIFI - value: true - label: "Does the device support manufacturing date" id: MANF_DATE - value: true - label: "Does the device support part number" id: PART_NUM - value: true - label: "Does the device support Intermediate CA Certificate" id: DM_ICACERT - value: true diff --git a/src/app/tests/suites/pics/BUILD.gn b/src/app/tests/suites/pics/BUILD.gn new file mode 100644 index 00000000000000..4517f460684977 --- /dev/null +++ b/src/app/tests/suites/pics/BUILD.gn @@ -0,0 +1,31 @@ +# 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. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +static_library("pics") { + output_name = "libPICS" + + sources = [ + "PICSBooleanExpressionParser.cpp", + "PICSBooleanExpressionParser.h", + "PICSBooleanReader.cpp", + "PICSBooleanReader.h", + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ "${chip_root}/src/lib/support" ] +} diff --git a/src/app/tests/suites/pics/PICSBooleanExpressionParser.cpp b/src/app/tests/suites/pics/PICSBooleanExpressionParser.cpp new file mode 100644 index 00000000000000..15263b754ebfb6 --- /dev/null +++ b/src/app/tests/suites/pics/PICSBooleanExpressionParser.cpp @@ -0,0 +1,167 @@ +/* + * + * 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. + */ + +#include "PICSBooleanExpressionParser.h" + +#include + +bool PICSBooleanExpressionParser::Eval(std::string expression, std::map & PICS) +{ + std::vector tokens; + uint8_t index = 0; + + Tokenize(expression, tokens); + return EvaluateExpression(tokens, PICS, index); +} + +void PICSBooleanExpressionParser::Tokenize(std::string & expression, std::vector & tokens) +{ + if (expression.empty()) + { + return; + } + + std::string s; + + for (size_t i = 0; i < expression.size(); i++) + { + char c = expression[i]; + switch (c) + { + case ' ': + case '\n': + case '\t': + if (s.empty()) + { + continue; + } + break; + + case '(': + case ')': + case '!': + if (!s.empty() > 0) + { + tokens.push_back(s); + s.clear(); + } + + tokens.push_back(std::string(1, c)); + break; + + case '&': + case '|': + if (!s.empty() && s.back() == c) + { + s.pop_back(); + if (s.size()) + { + tokens.push_back(s); + s.clear(); + } + + tokens.push_back(std::string(1, c) + std::string(1, c)); + break; + } + + s.push_back(c); + break; + + default: + s.push_back(c); + break; + } + } + + if (s.size()) + { + tokens.push_back(s); + } +} + +bool PICSBooleanExpressionParser::EvaluateExpression(std::vector & tokens, std::map & PICS, + uint8_t & index) +{ + bool leftExpr = EvaluateSubExpression(tokens, PICS, index); + if (index >= tokens.size()) + { + return leftExpr; + } + + if (tokens[index] == ")") + { + return leftExpr; + } + + std::string token = tokens[index]; + if (token == "&&") + { + index++; + bool rightExpr = EvaluateExpression(tokens, PICS, index); + return leftExpr && rightExpr; + } + else if (token == "||") + { + index++; + bool rightExpr = EvaluateExpression(tokens, PICS, index); + return leftExpr || rightExpr; + } + else + { + ChipLogError(chipTool, "Unknown token: '%s'", token.c_str()); + abort(); + } +} + +bool PICSBooleanExpressionParser::EvaluateSubExpression(std::vector & tokens, std::map & PICS, + uint8_t & index) +{ + std::string token = tokens[index]; + if (token == "(") + { + index++; + bool expr = EvaluateExpression(tokens, PICS, index); + if (tokens[index] != ")") + { + ChipLogError(chipTool, "Missing ')'"); + abort(); + } + + index++; + return expr; + } + else if (token == "!") + { + index++; + bool expr = EvaluateSubExpression(tokens, PICS, index); + return !expr; + } + else + { + index++; + + if (PICS.find(token) == PICS.end()) + { + // By default, let's consider that if a PICS item is not defined, it is |false|. + // It allows to create a file that only contains enabled features. + return false; + } + + return PICS[token]; + } +} diff --git a/src/app/tests/suites/pics/PICSBooleanExpressionParser.h b/src/app/tests/suites/pics/PICSBooleanExpressionParser.h new file mode 100644 index 00000000000000..a9ecd8b36713b5 --- /dev/null +++ b/src/app/tests/suites/pics/PICSBooleanExpressionParser.h @@ -0,0 +1,48 @@ +/** + * + * 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. + */ + +/** + * @file + * @brief Declaration of PICS Boolean Expression parser, a class that + * implements PICS condition parsing for YAML tests. + */ + +#include +#include +#include + +class PICSBooleanExpressionParser +{ +public: + /** + * @brief + * This function returns a boolean which is the result of evaluating the + * boolean logic expressed into the PICS expression. + * + * @param [in] expression An expression containing PICS code such as + * "!DT_CTRL_CONCATENATED_QR_CODE_1 && DT_CTRL_CONCATENATED_QR_CODE_2" + * @param [in] PICS A map of enabled/disabled PICS code + * + * @returns A boolean as the result of evaluating the expression. + */ + static bool Eval(std::string expression, std::map & PICS); + +private: + static void Tokenize(std::string & expression, std::vector & tokens); + static bool EvaluateExpression(std::vector & tokens, std::map & PICS, uint8_t & index); + static bool EvaluateSubExpression(std::vector & tokens, std::map & PICS, uint8_t & index); +}; diff --git a/src/app/tests/suites/pics/PICSBooleanReader.cpp b/src/app/tests/suites/pics/PICSBooleanReader.cpp new file mode 100644 index 00000000000000..909f68aa3b4bdf --- /dev/null +++ b/src/app/tests/suites/pics/PICSBooleanReader.cpp @@ -0,0 +1,74 @@ +/** + * + * 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. + */ + +#include "PICSBooleanReader.h" + +#include + +#include +#include + +std::map PICSBooleanReader::Read(std::string filepath) +{ + std::ifstream f(filepath); + if (!f.is_open()) + { + ChipLogError(chipTool, "Error reading: %s", filepath.c_str()); + abort(); + } + + std::map PICS; + std::string line; + std::string key; + std::string value; + uint16_t lineNumber = 0; + while (std::getline(f, line)) + { + if (line.empty()) + { + continue; + } + + std::stringstream ss(line); + + std::getline(ss, key, '='); + if (key.empty()) + { + ChipLogError(chipTool, "Missing PICS key at line %u", lineNumber + 1); + abort(); + } + + std::getline(ss, value); + if (value == "0") + { + PICS[key] = false; + } + else if (value == "1") + { + PICS[key] = true; + } + else + { + ChipLogError(chipTool, "%s: PICS value should be either '0' or '1', got '%s'", key.c_str(), value.c_str()); + abort(); + } + + lineNumber++; + } + + return PICS; +} diff --git a/src/app/tests/suites/pics/PICSBooleanReader.h b/src/app/tests/suites/pics/PICSBooleanReader.h new file mode 100644 index 00000000000000..6f8e240c6678f1 --- /dev/null +++ b/src/app/tests/suites/pics/PICSBooleanReader.h @@ -0,0 +1,31 @@ +/** + * + * 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. + */ + +/** + * @file + * @brief Declaration of PICS Boolean Reader, a class that read and parse + * a file with PICS Code and their enabled/disabled state. + */ + +#include +#include + +class PICSBooleanReader +{ +public: + static std::map Read(std::string filepath); +}; diff --git a/src/app/zap-templates/common/ClusterTestGeneration.js b/src/app/zap-templates/common/ClusterTestGeneration.js index c9a033e9cbcc30..14bde361a46e8f 100644 --- a/src/app/zap-templates/common/ClusterTestGeneration.js +++ b/src/app/zap-templates/common/ClusterTestGeneration.js @@ -149,10 +149,13 @@ function setDefaultPICS(test) return; } - if (!PICS.has(test[kPICSName])) { - const errorStr = 'PICS database does not contains any defined value for: ' + test[kPICSName]; - throwError(test, errorStr); - } + const items = test[kPICSName].split(/[&|() !]+/g).filter(item => item.length); + items.forEach(key => { + if (!PICS.has(key)) { + const errorStr = 'PICS database does not contains any defined value for: ' + key; + throwError(test, errorStr); + } + }) } function setDefaultArguments(test) @@ -309,9 +312,6 @@ function parse(filename) // Filter disabled tests yaml.tests = yaml.tests.filter(test => !test.disabled); - // Filter tests based on PICS - yaml.tests = yaml.tests.filter(test => test[kPICSName] == '' || PICS.get(test[kPICSName]).value == true); - yaml.tests.forEach((test, index) => { setDefault(test, kIndexName, index); }); diff --git a/zzz_generated/chip-tool/zap-generated/test/Commands.h b/zzz_generated/chip-tool/zap-generated/test/Commands.h index 8ff802472670a1..07a28cc850ff51 100644 --- a/zzz_generated/chip-tool/zap-generated/test/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/test/Commands.h @@ -12218,11 +12218,11 @@ class Test_TC_DM_1_1 : public TestCommand break; case 11: ChipLogProgress(chipTool, " ***** Test Step 11 : Query ManufacturingDate\n"); - err = TestQueryManufacturingDate_11(); + err = ShouldSkip("MANF_DATE") ? CHIP_NO_ERROR : TestQueryManufacturingDate_11(); break; case 12: ChipLogProgress(chipTool, " ***** Test Step 12 : Query PartNumber\n"); - err = TestQueryPartNumber_12(); + err = ShouldSkip("PART_NUM") ? CHIP_NO_ERROR : TestQueryPartNumber_12(); break; case 13: ChipLogProgress(chipTool, " ***** Test Step 13 : Query ProductURL\n");