blob: cc118d02140b27060d0643c6ad168e6390f55694 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "parser.h"
#include <sstream>
#include <lib/mock-function/mock-function.h>
#include <zxtest/zxtest.h>
namespace netdump::parser::test {
// The `FilterBuilder` that builds nothing but calls a mock function for each of the operations.
class MockFilterBuilder : public FilterBuilder<std::monostate> {
public:
mock_function::MockFunction<std::monostate, uint16_t, TokenPtr> frame_length_mock;
std::monostate frame_length(uint16_t length, TokenPtr comparator) override {
return frame_length_mock.Call(length, comparator);
}
mock_function::MockFunction<std::monostate, uint16_t> ethertype_mock;
std::monostate ethertype(uint16_t type) override {
return ethertype_mock.Call(type);
}
mock_function::MockFunction<std::monostate, std::array<uint8_t, ETH_ALEN>, TokenPtr>
mac_mock;
std::monostate mac(std::array<uint8_t, ETH_ALEN> address, TokenPtr addr_type) override {
return mac_mock.Call(address, addr_type);
}
mock_function::MockFunction<std::monostate, uint8_t> ip_version_mock;
std::monostate ip_version(uint8_t version) override {
return ip_version_mock.Call(version);
}
mock_function::MockFunction<std::monostate, uint8_t, uint16_t, TokenPtr> ip_pkt_length_mock;
std::monostate ip_pkt_length(uint8_t version, uint16_t length, TokenPtr comparator) override {
return ip_pkt_length_mock.Call(version, length, comparator);
}
mock_function::MockFunction<std::monostate, uint8_t, uint8_t> ip_protocol_mock;
std::monostate ip_protocol(uint8_t version, uint8_t protocol) override {
return ip_protocol_mock.Call(version, protocol);
}
mock_function::MockFunction<std::monostate, uint32_t, TokenPtr> ipv4_address_mock;
std::monostate ipv4_address(uint32_t address, TokenPtr type) override {
return ipv4_address_mock.Call(address, type);
}
mock_function::MockFunction<std::monostate, std::array<uint8_t, IP6_ADDR_LEN>, TokenPtr>
ipv6_address_mock;
std::monostate ipv6_address(std::array<uint8_t, IP6_ADDR_LEN> address, TokenPtr addr_type)
override {
return ipv6_address_mock.Call(address, addr_type);
}
mock_function::MockFunction<std::monostate, std::vector<std::pair<uint16_t, uint16_t>>,
TokenPtr>
ports_mock;
std::monostate ports(std::vector<std::pair<uint16_t, uint16_t>> ranges, TokenPtr port_type)
override {
return ports_mock.Call(ranges, port_type);
}
mock_function::MockFunction<std::monostate, std::monostate> negation_mock;
std::monostate negation(std::monostate filter) override {
return negation_mock.Call(filter);
}
mock_function::MockFunction<std::monostate, std::monostate, std::monostate> conjunction_mock;
std::monostate conjunction(std::monostate left, std::monostate right) override {
return conjunction_mock.Call(left, right);
}
mock_function::MockFunction<std::monostate, std::monostate, std::monostate> disjunction_mock;
std::monostate disjunction(std::monostate left, std::monostate right) override {
return disjunction_mock.Call(left, right);
}
explicit MockFilterBuilder(const Tokenizer& tokenizer)
: FilterBuilder<std::monostate>(tokenizer) {}
#define NETDUMP_APPLY_TO_ALL_MOCKS(fn) \
do { \
frame_length_mock.fn(); \
ethertype_mock.fn(); \
mac_mock.fn(); \
ip_version_mock.fn(); \
ip_pkt_length_mock.fn(); \
ip_protocol_mock.fn(); \
ipv4_address_mock.fn(); \
ipv6_address_mock.fn(); \
ports_mock.fn(); \
negation_mock.fn(); \
conjunction_mock.fn(); \
disjunction_mock.fn(); \
} while (false)
inline void verify_and_clear_all() {
NETDUMP_APPLY_TO_ALL_MOCKS(VerifyAndClear);
}
inline void expect_no_call_all() {
NETDUMP_APPLY_TO_ALL_MOCKS(ExpectNoCall);
}
#undef NETDUMP_APPLY_TO_ALL_MOCKS
};
// Test token cursor transitions in the parse environment.
TEST(NetdumpParserTests, EnvironmentPlusPlusTest) {
Tokenizer tkz{};
Environment env(std::vector<TokenPtr>{tkz.PORT, tkz.HOST});
EXPECT_EQ(tkz.PORT, *env);
++env;
EXPECT_EQ(tkz.HOST, *env);
}
TEST(NetdumpParserTests, EnvironmentMinusMinusTest) {
Tokenizer tkz{};
Environment env(std::vector<TokenPtr>{tkz.TCP, tkz.IP6});
EXPECT_EQ(tkz.TCP, *env);
++env;
EXPECT_EQ(tkz.IP6, *env);
--env;
EXPECT_EQ(tkz.TCP, *env);
}
TEST(NetdumpParserTests, EnvironmentGuardsTest) {
Tokenizer tkz{};
Environment env(std::vector<TokenPtr>{tkz.ICMP, tkz.ARP});
EXPECT_EQ(tkz.ICMP, *env);
--env;
EXPECT_EQ(tkz.ICMP, *env);
++env;
++env;
EXPECT_TRUE(env.at_end());
++env;
EXPECT_TRUE(env.at_end());
--env;
EXPECT_EQ(tkz.ARP, *env);
}
TEST(NetdumpParserTests, EnvironmentEndDereferenceTest) {
Tokenizer tkz{};
Environment env(std::vector<TokenPtr>{tkz.ICMP});
++env;
EXPECT_TRUE(env.at_end());
ASSERT_DEATH([&env]() { *env; });
}
TEST(NetdumpParserTests, EnvironmentFullWalkTest) {
Tokenizer tkz{};
Environment env(std::vector<TokenPtr>{tkz.AND, tkz.DNS, tkz.DHCP, tkz.SRC});
EXPECT_EQ(env.begin(), env.cur());
EXPECT_EQ(tkz.AND, *env);
++env;
EXPECT_EQ(tkz.DNS, *env);
--env;
EXPECT_EQ(tkz.AND, *env);
--env;
EXPECT_EQ(tkz.AND, *env);
++env;
++env;
EXPECT_EQ(tkz.DHCP, *env);
++env;
EXPECT_FALSE(env.at_end());
EXPECT_EQ(tkz.SRC, *env);
++env;
EXPECT_EQ(env.end(), env.cur());
++env;
EXPECT_TRUE(env.at_end());
--env;
EXPECT_EQ(tkz.SRC, *env);
EXPECT_FALSE(env.has_error());
env.error_loc = env.cur();
env.error_cause = "cause";
env.reset();
EXPECT_EQ(tkz.AND, *env);
EXPECT_TRUE(env.has_error());
env.clear_error();
EXPECT_EQ(std::nullopt, env.error_loc);
EXPECT_EQ("", env.error_cause);
}
// Totally invalid filter string should end up with no filter operation calls after parsing.
TEST(NetdumpParserTests, ParseErrorTest) {
Tokenizer tkz{};
MockFilterBuilder builder(tkz);
Parser parser(tkz);
builder.expect_no_call_all();
std::variant<std::monostate, ParseError> filter = parser.parse("mumble jumble", &builder);
// Error state as expected if `filter` holds a `ParseError`.
EXPECT_TRUE(std::holds_alternative<ParseError>(filter));
builder.verify_and_clear_all();
}
class TestParser : public Parser {
public:
void highlight_error_test() {
Environment env(std::vector<TokenPtr>{tkz_.AND, tkz_.DNS, tkz_.DHCP});
env.error_loc = std::nullopt;
// Just returns the string if no error location.
// The use of EXPECT_STR_EQ (which only allows C-strings) gives much nicer debug messages
// than EXPECT_EQ between `std::string`.
EXPECT_STR_EQ("spec", highlight_error("spec", &env).c_str());
std::stringstream expect_string1;
env.error_loc = env.end();
expect_string1 << "spec" << ANSI_HIGHLIGHT_ERROR << "*" << ANSI_RESET;
// Reproduce spec string and append error marker if error location is at end.
EXPECT_STR_EQ(expect_string1.str().c_str(), highlight_error("spec", &env).c_str());
std::stringstream expect_string2;
env.error_loc = env.begin();
expect_string2 << ANSI_HIGHLIGHT_ERROR << tkz_.AND->get_term() << ANSI_RESET << " "
<< tkz_.DNS->get_term() << " "
<< tkz_.DHCP->get_term();
EXPECT_STR_EQ(expect_string2.str().c_str(), highlight_error("spec", &env).c_str());
std::stringstream expect_string3;
env.reset();
++env;
env.error_loc = env.cur();
--env; // This tests error is highlighted by error location, not `env` state.
expect_string3 << tkz_.AND->get_term() << " "
<< ANSI_HIGHLIGHT_ERROR << tkz_.DNS->get_term() << ANSI_RESET << " "
<< tkz_.DHCP->get_term();
EXPECT_STR_EQ(expect_string3.str().c_str(), highlight_error("spec", &env).c_str());
std::stringstream expect_string4;
env.reset();
++env;
++env;
env.error_loc = env.cur();
expect_string4 << tkz_.AND->get_term() << " "
<< tkz_.DNS->get_term() << " "
<< ANSI_HIGHLIGHT_ERROR << tkz_.DHCP->get_term() << ANSI_RESET;
EXPECT_STR_EQ(expect_string4.str().c_str(), highlight_error("spec", &env).c_str());
}
explicit TestParser(const Tokenizer& tokenizer)
: Parser(tokenizer) {}
};
TEST(NetdumpParserTests, HighlightErrorTest) {
TestParser(Tokenizer{}).highlight_error_test();
}
} // namespace netdump::parser::test