| // Copyright 2020 The Pigweed 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 |
| // |
| // https://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 "pw_log_rpc/log_filter.h" |
| |
| #include <array> |
| #include <cstddef> |
| #include <cstring> |
| |
| #include "gtest/gtest.h" |
| #include "pw_bytes/endian.h" |
| #include "pw_log/levels.h" |
| #include "pw_log/log.h" |
| #include "pw_log/proto/log.pwpb.h" |
| #include "pw_log/proto_utils.h" |
| #include "pw_log_rpc/log_filter_map.h" |
| #include "pw_log_tokenized/metadata.h" |
| #include "pw_result/result.h" |
| #include "pw_status/status.h" |
| #include "pw_status/try.h" |
| |
| namespace pw::log_rpc { |
| namespace { |
| |
| constexpr uint32_t kSampleModule = 0x1234; |
| constexpr uint32_t kSampleFlags = 0x3; |
| const std::array<std::byte, cfg::kMaxThreadNameBytes - 7> kSampleThread = { |
| std::byte('R'), std::byte('P'), std::byte('C')}; |
| constexpr char kSampleMessage[] = "message"; |
| constexpr auto kSampleModuleLittleEndian = |
| bytes::CopyInOrder<uint32_t>(endian::little, kSampleModule); |
| |
| // Creates and encodes a log entry in the provided buffer. |
| template <uintptr_t log_level, uintptr_t module, uintptr_t flags> |
| Result<ConstByteSpan> EncodeLogEntry(std::string_view message, |
| ByteSpan buffer, |
| ConstByteSpan thread) { |
| auto metadata = log_tokenized::Metadata::Set<log_level, module, flags, 0>(); |
| return log::EncodeTokenizedLog(metadata, |
| as_bytes(span<const char>(message)), |
| /*ticks_since_epoch=*/0, |
| thread, |
| buffer); |
| } |
| |
| Status EncodeFilterRule(const Filter::Rule& rule, |
| log::FilterRule::StreamEncoder& encoder) { |
| PW_TRY( |
| encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal)); |
| PW_TRY(encoder.WriteModuleEquals(rule.module_equals)); |
| PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set)); |
| PW_TRY(encoder.WriteThreadEquals(rule.thread_equals)); |
| return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action)); |
| } |
| |
| Result<ConstByteSpan> EncodeFilter(const Filter& filter, ByteSpan buffer) { |
| log::Filter::MemoryEncoder encoder(buffer); |
| for (auto& rule : filter.rules()) { |
| log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); |
| PW_TRY(EncodeFilterRule(rule, rule_encoder)); |
| } |
| return ConstByteSpan(encoder); |
| } |
| |
| void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) { |
| EXPECT_EQ(rule.level_greater_than_or_equal, |
| expected_rule.level_greater_than_or_equal); |
| EXPECT_EQ(rule.module_equals, expected_rule.module_equals); |
| EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set); |
| EXPECT_EQ(rule.thread_equals, expected_rule.thread_equals); |
| EXPECT_EQ(rule.action, expected_rule.action); |
| } |
| |
| TEST(FilterMap, RetrieveFiltersById) { |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{ |
| std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{ |
| std::byte(0xca), std::byte(0xfe), std::byte(0xc0), std::byte(0xc0)}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3{ |
| std::byte(0xfa), std::byte(0xfe), std::byte(0xf1), std::byte(0xf0)}; |
| std::array<Filter, 3> filters = { |
| Filter(filter_id1, {}), |
| Filter(filter_id2, {}), |
| Filter(filter_id3, {}), |
| }; |
| |
| FilterMap filter_map(filters); |
| |
| // Check that each filters() element points to the same object provided. |
| span<const Filter> filter_list = filter_map.filters(); |
| ASSERT_EQ(filter_list.size(), filters.size()); |
| size_t i = 0; |
| for (auto& filter : filter_list) { |
| EXPECT_EQ(&filter, &filters[i++]); |
| } |
| |
| auto filter_result = filter_map.GetFilterFromId(filter_id3); |
| ASSERT_TRUE(filter_result.ok()); |
| EXPECT_EQ(filter_result.value(), &filters[2]); |
| |
| filter_result = filter_map.GetFilterFromId(filter_id2); |
| ASSERT_TRUE(filter_result.ok()); |
| EXPECT_EQ(filter_result.value(), &filters[1]); |
| |
| filter_result = filter_map.GetFilterFromId(filter_id1); |
| ASSERT_TRUE(filter_result.ok()); |
| EXPECT_EQ(filter_result.value(), &filters[0]); |
| |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> invalid_id{ |
| std::byte(0xd0), std::byte(0x1c), std::byte(0xe7), std::byte(0xea)}; |
| filter_result = filter_map.GetFilterFromId(invalid_id); |
| ASSERT_EQ(filter_result.status(), Status::NotFound()); |
| } |
| |
| TEST(Filter, UpdateFilterRules) { |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)}; |
| std::array<Filter::Rule, 4> rules; |
| const std::array<Filter::Rule, 4> new_rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, |
| .any_flags_set = 0x0f, |
| .module_equals{std::byte(123)}, |
| .thread_equals{}, |
| }, |
| { |
| .action = Filter::Rule::Action::kInactive, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0xef, |
| .module_equals{}, |
| .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = 0x1234, |
| .module_equals{std::byte(99)}, |
| .thread_equals{}, |
| }, |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0, |
| .module_equals{std::byte(4)}, |
| .thread_equals{std::byte('P'), |
| std::byte('O'), |
| std::byte('W'), |
| std::byte('E'), |
| std::byte('R')}, |
| }, |
| }}; |
| |
| Filter filter(filter_id, rules); |
| const Filter new_filter(filter_id, |
| const_cast<std::array<Filter::Rule, 4>&>(new_rules)); |
| std::byte buffer[256]; |
| auto encode_result = EncodeFilter(new_filter, buffer); |
| ASSERT_EQ(encode_result.status(), OkStatus()); |
| EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus()); |
| |
| size_t i = 0; |
| for (const auto& rule : filter.rules()) { |
| VerifyRule(rule, new_rules[i++]); |
| } |
| |
| // A new filter with no rules should clear filter. |
| const Filter empty_filter(filter_id, {}); |
| std::memset(buffer, 0, sizeof(buffer)); |
| encode_result = EncodeFilter(empty_filter, buffer); |
| ASSERT_EQ(encode_result.status(), OkStatus()); |
| EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus()); |
| const Filter::Rule empty_rule{ |
| .action = Filter::Rule::Action::kInactive, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0, |
| .module_equals{}, |
| .thread_equals{}, |
| }; |
| for (const auto& rule : filter.rules()) { |
| VerifyRule(rule, empty_rule); |
| } |
| EXPECT_TRUE(empty_filter.rules().empty()); |
| |
| // Passing a new filter with less rules. |
| const std::array<Filter::Rule, 2> few_rules{{ |
| { |
| .action = Filter::Rule::Action::kInactive, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0xef, |
| .module_equals{}, |
| .thread_equals{}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = 0x1234, |
| .module_equals{std::byte(99)}, |
| .thread_equals{std::byte('P'), |
| std::byte('O'), |
| std::byte('W'), |
| std::byte('E'), |
| std::byte('R')}, |
| }, |
| }}; |
| const Filter filter_few_rules( |
| filter_id, const_cast<std::array<Filter::Rule, 2>&>(few_rules)); |
| std::memset(buffer, 0, sizeof(buffer)); |
| encode_result = EncodeFilter(filter_few_rules, buffer); |
| ASSERT_EQ(encode_result.status(), OkStatus()); |
| EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus()); |
| i = 0; |
| for (const auto& rule : filter.rules()) { |
| if (i >= few_rules.size()) { |
| VerifyRule(rule, empty_rule); |
| } else { |
| VerifyRule(rule, few_rules[i++]); |
| } |
| } |
| |
| // Passing a new filter with extra rules. |
| const std::array<Filter::Rule, 6> extra_rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, |
| .any_flags_set = 0x0f, |
| .module_equals{std::byte(123)}, |
| .thread_equals{std::byte('P'), |
| std::byte('O'), |
| std::byte('W'), |
| std::byte('E'), |
| std::byte('R')}, |
| }, |
| { |
| .action = Filter::Rule::Action::kInactive, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0xef, |
| .module_equals{}, |
| .thread_equals{}, |
| }, |
| { |
| .action = Filter::Rule::Action::kInactive, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0xef, |
| .module_equals{}, |
| .thread_equals{}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = 0x1234, |
| .module_equals{std::byte(99)}, |
| .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')}, |
| }, |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0, |
| .module_equals{std::byte(4)}, |
| .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = 0x1234, |
| .module_equals{std::byte('M'), |
| std::byte('0'), |
| std::byte('L'), |
| std::byte('O'), |
| std::byte('G')}, |
| .thread_equals{}, |
| }, |
| }}; |
| const Filter filter_extra_rules( |
| filter_id, const_cast<std::array<Filter::Rule, 6>&>(extra_rules)); |
| std::memset(buffer, 0, sizeof(buffer)); |
| encode_result = EncodeFilter(filter_extra_rules, buffer); |
| ASSERT_EQ(encode_result.status(), OkStatus()); |
| EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus()); |
| i = 0; |
| for (const auto& rule : filter.rules()) { |
| VerifyRule(rule, extra_rules[i++]); |
| } |
| |
| // A filter with no rules buffer cannot get rules updated. |
| Filter filter_no_rules(filter_id, {}); |
| EXPECT_EQ(filter_no_rules.UpdateRulesFromProto(encode_result.value()), |
| Status::FailedPrecondition()); |
| } |
| |
| TEST(FilterTest, FilterLogsRuleDefaultDrop) { |
| const std::array<Filter::Rule, 2> rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals{kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals{kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| // This rule catches all logs. |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, |
| .any_flags_set = 0, |
| .module_equals = {}, |
| .thread_equals{}, |
| }, |
| }}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)}; |
| const Filter filter(filter_id, |
| const_cast<std::array<Filter::Rule, 2>&>(rules)); |
| |
| std::array<std::byte, 50> buffer; |
| const Result<ConstByteSpan> log_entry_info = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_info.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_debug = |
| EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_debug.status(), OkStatus()); |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_debug.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_warn = |
| EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_warn.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_error = |
| EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_error.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_info_different = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_info_different.status(), OkStatus()); |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_info_different.value())); |
| // Because the last rule catches all logs, the filter default action is not |
| // applied. |
| const Filter filter_default_drop( |
| filter_id, const_cast<std::array<Filter::Rule, 2>&>(rules)); |
| EXPECT_TRUE( |
| filter_default_drop.ShouldDropLog(log_entry_info_different.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_flags = |
| EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_flags.status(), OkStatus()); |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_flags.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_module = |
| EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_module.status(), OkStatus()); |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_module.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_thread = |
| EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_same_thread.status(), OkStatus()); |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_thread.value())); |
| } |
| |
| TEST(FilterTest, FilterLogsKeepLogsWhenNoRuleMatches) { |
| // There is no rule that catches all logs. |
| const std::array<Filter::Rule, 1> rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| }}; |
| |
| // Filters should not share rules if they are mutable, to avoid race |
| // conditions. |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)}; |
| const Filter filter(filter_id, |
| const_cast<std::array<Filter::Rule, 1>&>(rules)); |
| |
| std::array<std::byte, 50> buffer; |
| const Result<ConstByteSpan> log_entry_info = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_info.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_debug = |
| EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_debug.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_warn = |
| EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_warn.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_error = |
| EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_error.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_info_different = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_info_different.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_flags = |
| EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_flags.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_module = |
| EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_module.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_thread = |
| EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_same_thread.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_thread.value())); |
| } |
| |
| TEST(FilterTest, FilterLogsKeepLogsWhenRulesEmpty) { |
| // Filters should not share rules if they are mutable, to avoid race |
| // conditions. |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)}; |
| const Filter filter(filter_id, {}); |
| |
| std::array<std::byte, 50> buffer; |
| const Result<ConstByteSpan> log_entry_info = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_info.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_debug = |
| EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_debug.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_warn = |
| EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_warn.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_error = |
| EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_error.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_info_different = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_info_different.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_flags = |
| EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_flags.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_module = |
| EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {}); |
| ASSERT_EQ(log_entry_same_module.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value())); |
| |
| buffer.fill(std::byte(0)); |
| const Result<ConstByteSpan> log_entry_same_thread = |
| EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_same_thread.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_thread.value())); |
| } |
| |
| TEST(FilterTest, FilterLogsFirstRuleWins) { |
| const std::array<Filter::Rule, 2> rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| }}; |
| const std::array<Filter::Rule, 2> rules_reversed{{ |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| }}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{ |
| std::byte(0xfe), std::byte(0xed), std::byte(0xba), std::byte(0xb1)}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{ |
| std::byte(0), std::byte(0), std::byte(0), std::byte(2)}; |
| const Filter filter(filter_id1, |
| const_cast<std::array<Filter::Rule, 2>&>(rules)); |
| const Filter filter_reverse_rules( |
| filter_id2, const_cast<std::array<Filter::Rule, 2>&>(rules_reversed)); |
| |
| std::array<std::byte, 50> buffer; |
| const Result<ConstByteSpan> log_entry_info = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_info.status(), OkStatus()); |
| EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value())); |
| EXPECT_TRUE(filter_reverse_rules.ShouldDropLog(log_entry_info.value())); |
| } |
| |
| TEST(FilterTest, DropFilterRuleDueToThreadName) { |
| const std::array<std::byte, cfg::kMaxThreadNameBytes - 7> kDropThread = { |
| std::byte('L'), std::byte('O'), std::byte('G')}; |
| const std::array<Filter::Rule, 2> rules{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kDropThread.begin(), kDropThread.end()}, |
| }, |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| }}; |
| |
| const std::array<Filter::Rule, 2> drop_rule{{ |
| { |
| .action = Filter::Rule::Action::kDrop, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kDropThread.begin(), kDropThread.end()}, |
| }, |
| }}; |
| |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{ |
| std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)}; |
| // A filter's thread_equals name that does and does not match the log's thread |
| // name. |
| const Filter filter(filter_id1, |
| const_cast<std::array<Filter::Rule, 2>&>(rules)); |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{ |
| std::byte(0), std::byte(0), std::byte(0), std::byte(2)}; |
| // A filter's thread_equals name that does not match the log's thread name. |
| const Filter filter_with_unregistered_filter_rule( |
| filter_id2, const_cast<std::array<Filter::Rule, 2>&>(drop_rule)); |
| std::array<std::byte, 50> buffer; |
| const Result<ConstByteSpan> log_entry_thread = |
| EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>( |
| kSampleMessage, buffer, kSampleThread); |
| ASSERT_EQ(log_entry_thread.status(), OkStatus()); |
| // Set filter rules to kDrop to showcase the output difference. |
| // Drop_rule not being dropped, while rules is dropped successfully. |
| EXPECT_TRUE(filter.ShouldDropLog(log_entry_thread.value())); |
| EXPECT_FALSE(filter_with_unregistered_filter_rule.ShouldDropLog( |
| log_entry_thread.value())); |
| } |
| |
| TEST(FilterTest, UpdateFilterWithLargeThreadNamePasses) { |
| // Threads are limited to a size of kMaxThreadNameBytes. |
| // However, the excess bytes will not be in the updated rules. |
| const std::array<std::byte, cfg::kMaxThreadNameBytes + 1> |
| kThreadNameLongerThanAllowed = { |
| std::byte('L'), |
| std::byte('O'), |
| std::byte('C'), |
| std::byte('A'), |
| std::byte('L'), |
| std::byte('E'), |
| std::byte('G'), |
| std::byte('R'), |
| std::byte('E'), |
| std::byte('S'), |
| std::byte('S'), |
| }; |
| |
| const std::array<Filter::Rule, 2> rule{{ |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kThreadNameLongerThanAllowed.begin(), |
| kThreadNameLongerThanAllowed.end()}, |
| }, |
| { |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }, |
| }}; |
| |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)}; |
| Filter filter(filter_id, const_cast<std::array<Filter::Rule, 2>&>(rule)); |
| std::byte buffer[256]; |
| auto encode_result = EncodeFilter(filter, buffer); |
| ASSERT_EQ(encode_result.status(), OkStatus()); |
| EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus()); |
| size_t i = 0; |
| for (const auto& rules : filter.rules()) { |
| VerifyRule(rules, rule[i++]); |
| } |
| } |
| |
| TEST(FilterTest, UpdateFilterWithLargeThreadNameFails) { |
| const std::array<Filter::Rule, 1> rule_with_more_than_ten_bytes{{{ |
| .action = Filter::Rule::Action::kKeep, |
| .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, |
| .any_flags_set = kSampleFlags, |
| .module_equals = {kSampleModuleLittleEndian.begin(), |
| kSampleModuleLittleEndian.end()}, |
| .thread_equals = {kSampleThread.begin(), kSampleThread.end()}, |
| }}}; |
| const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{ |
| std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)}; |
| Filter filter( |
| filter_id, |
| const_cast<std::array<Filter::Rule, 1>&>(rule_with_more_than_ten_bytes)); |
| std::byte buffer[256]; |
| log::Filter::MemoryEncoder encoder(buffer); |
| { |
| std::array<const std::byte, cfg::kMaxThreadNameBytes + 1> |
| kThreadNameLongerThanAllowed = { |
| std::byte('L'), |
| std::byte('O'), |
| std::byte('C'), |
| std::byte('A'), |
| std::byte('L'), |
| std::byte('E'), |
| std::byte('G'), |
| std::byte('R'), |
| std::byte('E'), |
| std::byte('S'), |
| std::byte('S'), |
| }; |
| // Stream encoder writes to the buffer when it goes out of scope. |
| log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); |
| ASSERT_EQ(rule_encoder.WriteThreadEquals(kThreadNameLongerThanAllowed), |
| OkStatus()); |
| } |
| EXPECT_EQ(filter.UpdateRulesFromProto(ConstByteSpan(encoder)), |
| Status::InvalidArgument()); |
| } |
| } // namespace |
| } // namespace pw::log_rpc |