blob: 3b0736168108f4247fe7d09b76be36c637b88f74 [file] [log] [blame]
#include "./rpc_fuzzing/rpc_session.h"
#include <cstdint>
#include <optional>
#include <variant>
#include "google/protobuf/descriptor.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/random/random.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/internal/serialization.h"
#include "./rpc_fuzzing/proto_field_path.h"
#include "./rpc_fuzzing/rpc_potential_dfg.h"
#include "./rpc_fuzzing/rpc_sequence.h"
#include "./rpc_fuzzing/testdata/mini_blogger.pb.h"
#include "./rpc_fuzzing/testdata/mini_blogger.grpc.pb.h"
namespace fuzztest::internal {
namespace {
using ::testing::AnyOf;
using ::testing::Conditional;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::IsTrue;
using ::testing::NanSensitiveDoubleEq;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::ResultOf;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
using ::testing::VariantWith;
template <typename T>
auto ValueIs(const T& v) {
if constexpr (std::is_same_v<T, double>) {
return FieldsAre(VariantWith<double>(NanSensitiveDoubleEq(v)));
} else {
return FieldsAre(VariantWith<T>(v));
}
}
template <typename... T>
auto SubsAre(const T&... v) {
return FieldsAre(VariantWith<std::vector<IRObject>>(ElementsAre(v...)));
}
class RpcDomainTest : public ::testing::Test {
public:
RpcDomainTest()
: pool_(*ABSL_DIE_IF_NULL(google::protobuf::DescriptorPool::generated_pool())),
mini_blogger_serivce_(*ABSL_DIE_IF_NULL(
pool_.FindServiceByName("fuzztest.internal.MiniBlogger"))),
log_in_user_method_(*ABSL_DIE_IF_NULL(
mini_blogger_serivce_.FindMethodByName("LogInUser"))),
log_out_user_method_(*ABSL_DIE_IF_NULL(
mini_blogger_serivce_.FindMethodByName("LogOutUser"))),
get_user_posts_method_(*ABSL_DIE_IF_NULL(
mini_blogger_serivce_.FindMethodByName("GetUserPosts"))) {}
protected:
RpcNode GetLogInUserNode() const {
return RpcNode(log_in_user_method_, std::make_unique<LogInUserRequest>());
}
RpcNode GetLogOutUserNode() const {
return RpcNode(log_out_user_method_, std::make_unique<LogOutUserRequest>());
}
RpcNode GetGetUserPostsNode() const {
return RpcNode(get_user_posts_method_,
std::make_unique<GetUserPostsRequest>());
}
RpcDataFlowGraph CreateRandomGraph(RpcSessionImpl<MiniBlogger>& rpc_domain) {
absl::BitGen gen;
RpcDataFlowGraph graph = rpc_domain.Init(gen);
for (int i = 0; i < 100; ++i) {
rpc_domain.Mutate(graph, gen, false);
}
// Generate an order.
(void)graph.GetOrderedNodeIds();
return graph;
}
const google::protobuf::DescriptorPool& pool_;
const google::protobuf::ServiceDescriptor& mini_blogger_serivce_;
const google::protobuf::MethodDescriptor& log_in_user_method_;
const google::protobuf::MethodDescriptor& log_out_user_method_;
const google::protobuf::MethodDescriptor& get_user_posts_method_;
};
TEST_F(RpcDomainTest, InitGeneratesARandomSingleCallWithoutDependencies) {
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
for (int i = 0; i < 100; ++i) {
RpcDataFlowGraph value = rpc_domain.Init(bitgen);
EXPECT_THAT(value.GetAllNodes(),
ElementsAre(Pair(0, ResultOf(
[](const RpcNode& node) {
return node.dependencies().empty();
},
IsTrue()))));
}
}
TEST_F(RpcDomainTest, MutationOnlyInsertsNodesThatDependOnExistingNodes) {
constexpr std::string_view kMethodsDependingLogInUser[] = {"GetUserPosts",
"LogOutUser"};
RpcDataFlowGraph graph;
graph.AddNode(0, GetLogInUserNode());
absl::flat_hash_set<std::string> inserted_methods;
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
for (int j = 0; j < 100; ++j) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, false);
if (mutated_graph.NodeNum() != 2) continue;
// Mutated through insertion.
inserted_methods.insert(mutated_graph.GetSequence()[1].method().name());
}
EXPECT_THAT(inserted_methods,
UnorderedElementsAreArray(kMethodsDependingLogInUser));
}
TEST_F(RpcDomainTest, InsertedNodesHaveDependenciesOnExistingNodes) {
constexpr RpcNodeID kFromNodeID = 0;
RpcDataFlowGraph graph;
graph.AddNode(kFromNodeID, GetLogInUserNode());
bool insertion_triggerred = false;
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
for (int j = 0; j < 100; ++j) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, false);
if (mutated_graph.NodeNum() != 2) continue;
// Mutated through insertion.
insertion_triggerred = true;
const RpcNode inserted_node = mutated_graph.GetSequence()[1];
EXPECT_THAT(
inserted_node.dependencies(),
ElementsAre(FieldsAre(
kFromNodeID, GetFieldPath<LogInUserResponse>("session_id"),
Conditional(inserted_node.method().name() == "LogOutUser",
AnyOf(GetFieldPath<LogOutUserRequest>(
"log_out_info.session_info.session_id"),
GetFieldPath<LogOutUserRequest>(
"log_out_info.session_id")),
GetFieldPath<GetUserPostsRequest>("session_id")))));
}
EXPECT_TRUE(insertion_triggerred);
}
TEST_F(RpcDomainTest, InsertedNodeSelectsRandomAlternativeDepWithinSameOneOf) {
constexpr RpcNodeID kFromNodeID = 0;
RpcDataFlowGraph graph;
graph.AddNode(kFromNodeID, GetLogInUserNode());
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::flat_hash_set<std::string> sink_fields;
absl::BitGen bitgen;
for (int j = 0; j < 200; ++j) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, false);
if (mutated_graph.NodeNum() != 2 ||
mutated_graph.GetSequence()[1].method().name() != "LogOutUser")
continue;
// Either `log_out_info.session_info.session_id` or
// `log_out_info.session_id` will be selected as the sink.
EXPECT_EQ(mutated_graph.GetSequence()[1].dependencies().size(), 1);
sink_fields.insert(
mutated_graph.GetSequence()[1].dependencies()[0].to_field.ToString());
}
EXPECT_THAT(sink_fields,
UnorderedElementsAre("log_out_info.session_info.session_id",
"log_out_info.session_id"));
}
TEST_F(RpcDomainTest, MutationOnlyDeletesTailNodes) {
constexpr std::string_view kMethodsDependingLogInUser[] = {"GetUserPosts",
"LogOutUser"};
// Node id 0.
RpcNode log_in_user_node = GetLogInUserNode();
// Node id 1.
RpcNode log_out_user_node = GetLogOutUserNode();
// Node id 2.
RpcNode get_user_posts_node = GetGetUserPostsNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
get_user_posts_node.AddDependency(
RpcDataFlowEdge{0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<GetUserPostsRequest>("session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
graph.AddNode(2, get_user_posts_node);
absl::flat_hash_set<std::string> deleted_methods;
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
for (int j = 0; j < 100; ++j) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, false);
if (mutated_graph.NodeNum() != 2) continue;
// Mutated through deletion.
if (mutated_graph.GetSequence()[1].method().name() == "LogOutUser") {
deleted_methods.insert("GetUserPosts");
} else {
deleted_methods.insert("LogOutUser");
}
}
EXPECT_THAT(deleted_methods,
UnorderedElementsAreArray(kMethodsDependingLogInUser));
}
TEST_F(RpcDomainTest, OnlyShrinkMutationDecreasesNodeNumOrRequest) {
RpcNode log_in_user_node = GetLogInUserNode();
auto get_user_post_request = std::make_unique<GetUserPostsRequest>();
get_user_post_request->set_max_posts(100);
RpcNode log_out_user_node(get_user_posts_method_,
std::move(get_user_post_request));
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
bool static_field_shrinked = false;
for (int i = 0; i < 100; ++i) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, true);
if (mutated_graph.NodeNum() == 2) {
const RpcNode& node = mutated_graph.GetNode(1);
int new_max_posts = node.request().GetReflection()->GetInt32(
node.request(),
node.request().GetDescriptor()->FindFieldByName("max_posts"));
EXPECT_LE(new_max_posts, 100);
if (new_max_posts < 100) {
static_field_shrinked = true;
}
} else {
EXPECT_EQ(mutated_graph.NodeNum(), 1);
}
}
EXPECT_TRUE(static_field_shrinked);
}
TEST_F(RpcDomainTest, MutateEventuallyChangesStaticFieldInRequest) {
RpcDataFlowGraph graph;
graph.AddNode(0, GetLogInUserNode());
RpcSessionImpl<MiniBlogger> rpc_domain;
absl::BitGen bitgen;
bool static_field_changed = false;
for (int i = 0; i < 100; ++i) {
RpcDataFlowGraph mutated_graph = graph;
rpc_domain.Mutate(mutated_graph, bitgen, false);
if (mutated_graph.NodeNum() == graph.NodeNum() &&
mutated_graph.GetNode(0).method().name() ==
graph.GetNode(0).method().name()) {
// Mutated through static field mutation.
if (!google::protobuf::util::MessageDifferencer::Equals(
mutated_graph.GetNode(0).request(), graph.GetNode(0).request())) {
static_field_changed = true;
break;
}
}
}
EXPECT_TRUE(static_field_changed);
}
TEST_F(RpcDomainTest, FromValueTransformsSequenceToRpcGraph) {
RpcSequence sequence = {GetLogInUserNode(), GetLogOutUserNode(),
GetGetUserPostsNode()};
RpcSessionImpl<MiniBlogger> rpc_domain;
std::optional<RpcDataFlowGraph> graph = rpc_domain.FromValue(sequence);
ASSERT_TRUE(graph.has_value());
ASSERT_EQ(graph->NodeNum(), 3);
std::vector<RpcNode> all_nodes{graph->GetNode(0), graph->GetNode(1),
graph->GetNode(2)};
EXPECT_EQ(all_nodes, sequence);
}
TEST_F(RpcDomainTest, ParseCorpusInClearTextFromReturnsRpcDataFlowGraph) {
constexpr absl::string_view kObjectText =
R"(FUZZTESTv1
sub {
sub { s: "fuzztest.internal.MiniBlogger.LogInUser" }
sub { s: "" }
sub { }
}
sub {
sub { s: "fuzztest.internal.MiniBlogger.LogOutUser" }
sub { s: "" }
sub {
sub {
sub { i: 0 }
sub { s: "session_id" }
sub { s: "log_out_info.session_info.session_id" }
}
}
})";
std::optional<IRObject> obj = IRObject::FromString(kObjectText);
ASSERT_TRUE(obj.has_value());
RpcNode log_in_user_node = GetLogInUserNode();
RpcNode log_out_user_node = GetLogOutUserNode();
RpcDataFlowEdge edge = {
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")};
log_out_user_node.AddDependency(edge);
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
auto obj2 = rpc_domain.SerializeCorpus(graph);
EXPECT_EQ(obj->ToString(), obj2.ToString());
}
TEST_F(RpcDomainTest, ParseCorpusReturnsRpcDataFlowGraph) {
RpcNode log_in_user_node = GetLogInUserNode();
RpcNode log_out_user_node = GetLogOutUserNode();
RpcDataFlowEdge edge = {
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")};
log_out_user_node.AddDependency(edge);
/* Set up log_in_user_node */
IRObject log_in_user_node_obj;
auto& log_in_user_node_subs = log_in_user_node_obj.MutableSubs();
log_in_user_node_subs.push_back(
IRObject::FromCorpus(log_in_user_node.method().full_name()));
log_in_user_node_subs.push_back(
IRObject::FromCorpus(log_in_user_node.request().SerializeAsString()));
log_in_user_node_subs.push_back(IRObject{});
/* Set up log_out_user_node*/
IRObject log_out_user_node_obj;
auto& log_out_user_node_subs = log_out_user_node_obj.MutableSubs();
log_out_user_node_subs.push_back(
IRObject::FromCorpus(log_out_user_node.method().full_name()));
log_out_user_node_subs.push_back(
IRObject::FromCorpus(log_out_user_node.request().SerializeAsString()));
IRObject edge_obj;
auto& edge_subs = edge_obj.MutableSubs();
edge_subs.push_back(IRObject::FromCorpus(edge.from_node_id));
edge_subs.push_back(IRObject::FromCorpus(edge.from_field.ToString()));
edge_subs.push_back(IRObject::FromCorpus(edge.to_field.ToString()));
IRObject edges;
edges.MutableSubs().push_back(edge_obj);
log_out_user_node_subs.push_back(edges);
IRObject obj;
auto& subs = obj.MutableSubs();
subs.push_back(log_in_user_node_obj);
subs.push_back(log_out_user_node_obj);
RpcSessionImpl<MiniBlogger> rpc_domain;
auto graph = rpc_domain.ParseCorpus(obj);
ASSERT_TRUE(graph.has_value());
EXPECT_EQ(graph->NodeNum(), 2);
EXPECT_EQ(graph->GetNode(0), log_in_user_node);
EXPECT_EQ(graph->GetNode(1), log_out_user_node);
}
TEST_F(RpcDomainTest, SerializesCorpusReturnsIRObjectOfSpecificStructure) {
RpcNode log_in_user_node = GetLogInUserNode();
RpcNode log_out_user_node = GetLogOutUserNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
IRObject obj = rpc_domain.SerializeCorpus(graph);
EXPECT_THAT(
obj,
SubsAre(
/*log_in_user_node*/
SubsAre(ValueIs<std::string>(log_in_user_method_.full_name()),
ValueIs<std::string>(
log_in_user_node.request().SerializeAsString()),
ValueIs<std::monostate>({})),
/*log_out_user_node*/
SubsAre(
ValueIs<std::string>(log_out_user_method_.full_name()),
ValueIs<std::string>(
log_out_user_node.request().SerializeAsString()),
/*dependencies*/
SubsAre(SubsAre(
ValueIs<std::uint64_t>(0) /*from_node_id*/,
ValueIs<std::string>("session_id") /*from_field*/,
ValueIs<
std::string>(/*to_field*/
"log_out_info.session_info.session_id"))))));
}
TEST_F(RpcDomainTest, SerializesCorpusAndParsesCorpusReturnTheSameObject) {
RpcSessionImpl<MiniBlogger> rpc_domain;
for (int i = 0; i < 100; ++i) {
RpcDataFlowGraph graph = CreateRandomGraph(rpc_domain);
EXPECT_THAT(rpc_domain.ParseCorpus(rpc_domain.SerializeCorpus(graph)),
Optional(Eq(graph)));
}
}
TEST_F(RpcDomainTest, ValidRpcNodeShouldOnlyDependOnPreviousNodes) {
RpcNode log_out_user_node = GetLogOutUserNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
EXPECT_THAT(
rpc_domain.ValidateCorpusValue(graph),
IsInvalid(
"The dependencies should only come from previously executed nodes."));
}
TEST_F(RpcDomainTest, ValidRpcNodeDependencyMatchesPotentialDependency) {
RpcNode log_out_user_node = GetLogOutUserNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<RegisterUserResponse>("success"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, GetLogInUserNode());
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
EXPECT_THAT(
rpc_domain.ValidateCorpusValue(graph),
IsInvalid(
"The dependency is not defined in the potential data flow graph."));
}
TEST_F(RpcDomainTest,
ValidRpcNodeShouldHaveAtMostOneDependencyForEachDynamicField) {
RpcNode log_in_user_node = GetLogInUserNode();
RpcNode log_out_user_node = GetLogOutUserNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
EXPECT_THAT(
rpc_domain.ValidateCorpusValue(graph),
IsInvalid("One sink field should have at most one concrete dependency!"));
}
TEST_F(RpcDomainTest, ValidRpcNodePassValidationTest) {
RpcNode log_in_user_node = GetLogInUserNode();
RpcNode log_out_user_node = GetLogOutUserNode();
log_out_user_node.AddDependency(RpcDataFlowEdge{
0, GetFieldPath<LogInUserResponse>("session_id"),
GetFieldPath<LogOutUserRequest>("log_out_info.session_info.session_id")});
RpcDataFlowGraph graph;
graph.AddNode(0, log_in_user_node);
graph.AddNode(1, log_out_user_node);
RpcSessionImpl<MiniBlogger> rpc_domain;
EXPECT_OK(rpc_domain.ValidateCorpusValue(graph));
}
} // namespace
} // namespace fuzztest::internal