mirror of https://github.com/grpc/grpc.git
[call-v3] Yodel - a call testing framework (#36635)
Introduce "Yodel" - a framework for testing things vaguely related to calls.
This is breaking up some work I did for transport test suites - we've got a nice way of spawning test-only promises and tracking them visually, and support for setting up an environment that can run as a test or a fuzzer. I'm making that piece a little more reusable, and then rebasing the transport test suite atop that infrastructure.
Closes #36635
COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36635 from ctiller:transport-refs-6 843a9f4b7e
PiperOrigin-RevId: 637022756
pull/36727/head
parent
aa83a3fe32
commit
621aa4e5ce
26 changed files with 1004 additions and 752 deletions
@ -0,0 +1,81 @@ |
||||
# Copyright 2024 gRPC 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. |
||||
|
||||
load( |
||||
"//bazel:grpc_build_system.bzl", |
||||
"grpc_cc_library", |
||||
"grpc_package", |
||||
"grpc_proto_library", |
||||
) |
||||
|
||||
grpc_package(name = "test/core/call/yodel") |
||||
|
||||
exports_files( |
||||
["fuzzer_main.cc"], |
||||
visibility = ["//test:__subpackages__"], |
||||
) |
||||
|
||||
grpc_cc_library( |
||||
name = "yodel_test", |
||||
testonly = 1, |
||||
srcs = ["yodel_test.cc"], |
||||
hdrs = ["yodel_test.h"], |
||||
external_deps = [ |
||||
"absl/functional:any_invocable", |
||||
"absl/log", |
||||
"absl/random", |
||||
"absl/random:bit_gen_ref", |
||||
"absl/strings", |
||||
"gtest", |
||||
], |
||||
visibility = ["//test:__subpackages__"], |
||||
deps = [ |
||||
"//:debug_location", |
||||
"//:event_engine_base_hdrs", |
||||
"//:iomgr_timer", |
||||
"//:promise", |
||||
"//src/core:call_arena_allocator", |
||||
"//src/core:call_spine", |
||||
"//src/core:cancel_callback", |
||||
"//src/core:metadata", |
||||
"//src/core:promise_factory", |
||||
"//src/core:resource_quota", |
||||
"//test/core/event_engine/fuzzing_event_engine", |
||||
"//test/core/test_util:grpc_test_util", |
||||
], |
||||
) |
||||
|
||||
grpc_cc_library( |
||||
name = "test_main", |
||||
testonly = 1, |
||||
srcs = ["test_main.cc"], |
||||
external_deps = ["absl/random"], |
||||
visibility = ["//test:__subpackages__"], |
||||
deps = [ |
||||
"yodel_test", |
||||
"//:grpc_trace", |
||||
"//test/core/test_util:grpc_test_util", |
||||
], |
||||
) |
||||
|
||||
grpc_proto_library( |
||||
name = "fuzzer_proto", |
||||
srcs = ["fuzzer.proto"], |
||||
has_services = False, |
||||
visibility = ["//test:__subpackages__"], |
||||
deps = [ |
||||
"//test/core/event_engine/fuzzing_event_engine:fuzzing_event_engine_proto", |
||||
"//test/core/test_util:fuzz_config_vars_proto", |
||||
], |
||||
) |
@ -0,0 +1,12 @@ |
||||
Yodel is a foundational test framework for unit testing parts of a call. |
||||
|
||||
It provides infrastructure to write tests around some call actor (the various |
||||
frameworks built atop of it specify what that actor is). It also provides |
||||
utilities to fill in various call details, interact with promises in a |
||||
way that's convenient to debug, and run as either a unit test or a fuzzer. |
||||
|
||||
Various frameworks are built atop it: |
||||
- transports use it as part of the transport test_suite |
||||
|
||||
Planned: |
||||
- interceptors & filters should also use this |
@ -0,0 +1,59 @@ |
||||
# Copyright 2024 gRPC 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. |
||||
|
||||
""" |
||||
Generate one transport test & associated fuzzer |
||||
""" |
||||
|
||||
load("//bazel:grpc_build_system.bzl", "grpc_cc_test") |
||||
load("//test/core/test_util:grpc_fuzzer.bzl", "grpc_proto_fuzzer") |
||||
|
||||
def grpc_yodel_test(name, deps): |
||||
grpc_cc_test( |
||||
name = name + "_test", |
||||
srcs = [], |
||||
tags = ["no_windows", "no_mac"], |
||||
deps = [ |
||||
"//test/core/call/yodel:test_main", |
||||
] + deps, |
||||
uses_polling = False, |
||||
) |
||||
|
||||
grpc_proto_fuzzer( |
||||
name = name + "_fuzzer", |
||||
srcs = ["//test/core/call/yodel:fuzzer_main.cc"], |
||||
tags = ["no_windows", "no_mac"], |
||||
external_deps = [ |
||||
"absl/log:check", |
||||
"gtest", |
||||
], |
||||
deps = [ |
||||
"//test/core/call/yodel:yodel_test", |
||||
"//test/core/call/yodel:fuzzer_proto", |
||||
"//:event_engine_base_hdrs", |
||||
"//:config_vars", |
||||
"//:exec_ctx", |
||||
"//:gpr", |
||||
"//:grpc_unsecure", |
||||
"//:iomgr_timer", |
||||
"//src/core:default_event_engine", |
||||
"//src/core:env", |
||||
"//src/core:experiments", |
||||
"//test/core/event_engine/fuzzing_event_engine", |
||||
"//test/core/test_util:fuzz_config_vars", |
||||
"//test/core/test_util:proto_bit_gen", |
||||
] + deps, |
||||
corpus = "corpus/%s" % name, |
||||
proto = None, |
||||
) |
@ -0,0 +1,462 @@ |
||||
// Copyright 2024 gRPC 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.
|
||||
|
||||
#ifndef GRPC_TEST_CORE_CALL_YODEL_YODEL_TEST_H |
||||
#define GRPC_TEST_CORE_CALL_YODEL_YODEL_TEST_H |
||||
|
||||
#include "absl/functional/any_invocable.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/random/bit_gen_ref.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include <grpc/event_engine/event_engine.h> |
||||
|
||||
#include "src/core/lib/gprpp/debug_location.h" |
||||
#include "src/core/lib/promise/cancel_callback.h" |
||||
#include "src/core/lib/promise/detail/promise_factory.h" |
||||
#include "src/core/lib/promise/promise.h" |
||||
#include "src/core/lib/transport/call_arena_allocator.h" |
||||
#include "src/core/lib/transport/call_spine.h" |
||||
#include "src/core/lib/transport/metadata.h" |
||||
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h" |
||||
#include "test/core/test_util/test_config.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class YodelTest; |
||||
|
||||
namespace yodel_detail { |
||||
|
||||
// Capture the name and location of a test step.
|
||||
class NameAndLocation { |
||||
public: |
||||
// Allow implicit construction from a string, to capture the start location
|
||||
// from the variadic StartTestSeq name argument.
|
||||
// NOLINTNEXTLINE
|
||||
NameAndLocation(const char* name, SourceLocation location = {}) |
||||
: location_(location), name_(name) {} |
||||
|
||||
SourceLocation location() const { return location_; } |
||||
absl::string_view name() const { return name_; } |
||||
|
||||
private: |
||||
SourceLocation location_; |
||||
absl::string_view name_; |
||||
}; |
||||
|
||||
// Capture the state of a test step.
|
||||
class ActionState { |
||||
public: |
||||
enum State : uint8_t { |
||||
// Initial state: construction of this step in the sequence has not been
|
||||
// performed.
|
||||
kNotCreated, |
||||
// The step has been created, but not yet started (the initial poll of the
|
||||
// created promise has not occurred).
|
||||
kNotStarted, |
||||
// The step has been polled, but it's not yet been completed.
|
||||
kStarted, |
||||
// The step has been completed.
|
||||
kDone, |
||||
// The step has been cancelled.
|
||||
kCancelled, |
||||
}; |
||||
|
||||
// Generate a nice little prefix for log messages.
|
||||
static absl::string_view StateString(State state); |
||||
|
||||
ActionState(NameAndLocation name_and_location, int step); |
||||
|
||||
State Get() const { return state_; } |
||||
void Set(State state, SourceLocation whence = {}); |
||||
const NameAndLocation& name_and_location() const { |
||||
return name_and_location_; |
||||
} |
||||
SourceLocation location() const { return name_and_location().location(); } |
||||
const char* file() const { return location().file(); } |
||||
int line() const { return location().line(); } |
||||
absl::string_view name() const { return name_and_location().name(); } |
||||
int step() const { return step_; } |
||||
bool IsDone(); |
||||
|
||||
private: |
||||
const NameAndLocation name_and_location_; |
||||
const int step_; |
||||
std::atomic<State> state_; |
||||
}; |
||||
|
||||
class SequenceSpawner { |
||||
public: |
||||
SequenceSpawner( |
||||
NameAndLocation name_and_location, |
||||
absl::AnyInvocable<void(absl::string_view, Promise<Empty>)> |
||||
promise_spawner, |
||||
absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation, int)> |
||||
action_state_factory) |
||||
: name_and_location_(name_and_location), |
||||
promise_spawner_( |
||||
std::make_shared< |
||||
absl::AnyInvocable<void(absl::string_view, Promise<Empty>)>>( |
||||
std::move(promise_spawner))), |
||||
action_state_factory_(action_state_factory) {} |
||||
|
||||
template <typename First, typename... FollowUps> |
||||
void Start(First first, FollowUps... followups) { |
||||
using Factory = promise_detail::OncePromiseFactory<void, First>; |
||||
using FactoryPromise = typename Factory::Promise; |
||||
using Result = typename FactoryPromise::Result; |
||||
auto action_state = action_state_factory_(name_and_location_, step_); |
||||
++step_; |
||||
auto next = MakeNext<Result>(std::move(followups)...); |
||||
(*promise_spawner_)( |
||||
name_and_location_.name(), |
||||
[spawner = promise_spawner_, first = Factory(std::move(first)), |
||||
next = std::move(next), action_state = std::move(action_state), |
||||
name_and_location = name_and_location_]() mutable { |
||||
action_state->Set(ActionState::kNotStarted); |
||||
auto promise = first.Make(); |
||||
(*spawner)(name_and_location.name(), |
||||
WrapPromiseAndNext(std::move(action_state), |
||||
std::move(promise), std::move(next))); |
||||
return Empty{}; |
||||
}); |
||||
} |
||||
|
||||
private: |
||||
template <typename Arg, typename FirstFollowUp, typename... FollowUps> |
||||
absl::AnyInvocable<void(Arg)> MakeNext(FirstFollowUp first, |
||||
FollowUps... followups) { |
||||
using Factory = promise_detail::OncePromiseFactory<Arg, FirstFollowUp>; |
||||
using FactoryPromise = typename Factory::Promise; |
||||
using Result = typename FactoryPromise::Result; |
||||
auto action_state = action_state_factory_(name_and_location_, step_); |
||||
++step_; |
||||
auto next = MakeNext<Result>(std::move(followups)...); |
||||
return [spawner = promise_spawner_, factory = Factory(std::move(first)), |
||||
next = std::move(next), action_state = std::move(action_state), |
||||
name_and_location = name_and_location_](Arg arg) mutable { |
||||
action_state->Set(ActionState::kNotStarted); |
||||
(*spawner)( |
||||
name_and_location.name(), |
||||
WrapPromiseAndNext(std::move(action_state), |
||||
factory.Make(std::move(arg)), std::move(next))); |
||||
}; |
||||
} |
||||
|
||||
template <typename R, typename P> |
||||
static Promise<Empty> WrapPromiseAndNext( |
||||
std::shared_ptr<ActionState> action_state, P promise, |
||||
absl::AnyInvocable<void(R)> next) { |
||||
return Promise<Empty>(OnCancel( |
||||
[action_state, promise = std::move(promise), |
||||
next = std::move(next)]() mutable -> Poll<Empty> { |
||||
action_state->Set(ActionState::kStarted); |
||||
auto r = promise(); |
||||
if (auto* p = r.value_if_ready()) { |
||||
action_state->Set(ActionState::kDone); |
||||
next(std::move(*p)); |
||||
return Empty{}; |
||||
} else { |
||||
return Pending{}; |
||||
} |
||||
}, |
||||
[action_state]() { action_state->Set(ActionState::kCancelled); })); |
||||
} |
||||
|
||||
template <typename Arg> |
||||
absl::AnyInvocable<void(Arg)> MakeNext() { |
||||
// Enforce last-arg is Empty so we don't drop things
|
||||
return [](Empty) {}; |
||||
} |
||||
|
||||
NameAndLocation name_and_location_; |
||||
std::shared_ptr<absl::AnyInvocable<void(absl::string_view, Promise<Empty>)>> |
||||
promise_spawner_; |
||||
absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation, int)> |
||||
action_state_factory_; |
||||
int step_ = 1; |
||||
}; |
||||
|
||||
template <typename Context> |
||||
auto SpawnerForContext( |
||||
Context context, |
||||
grpc_event_engine::experimental::EventEngine* event_engine) { |
||||
return [context = std::move(context), event_engine]( |
||||
absl::string_view name, Promise<Empty> promise) mutable { |
||||
// Pass new promises via event engine to allow fuzzers to explore
|
||||
// reorderings of possibly interleaved spawns.
|
||||
event_engine->Run([name, context, promise = std::move(promise)]() mutable { |
||||
context.SpawnInfallible(name, std::move(promise)); |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
class TestRegistry { |
||||
public: |
||||
TestRegistry() : next_(root_) { root_ = this; } |
||||
|
||||
struct Test { |
||||
absl::string_view file; |
||||
int line; |
||||
std::string test_type; |
||||
std::string name; |
||||
absl::AnyInvocable<YodelTest*(const fuzzing_event_engine::Actions&, |
||||
absl::BitGenRef) const> |
||||
make; |
||||
}; |
||||
|
||||
static std::vector<Test> AllTests(); |
||||
|
||||
protected: |
||||
~TestRegistry() { |
||||
Crash("unreachable: TestRegistry should never be destroyed"); |
||||
} |
||||
|
||||
private: |
||||
virtual void ContributeTests(std::vector<Test>& tests) = 0; |
||||
|
||||
TestRegistry* next_; |
||||
static TestRegistry* root_; |
||||
}; |
||||
|
||||
class SimpleTestRegistry final : public TestRegistry { |
||||
public: |
||||
SimpleTestRegistry() {} |
||||
~SimpleTestRegistry() = delete; |
||||
|
||||
void RegisterTest( |
||||
absl::string_view file, int line, absl::string_view test_type, |
||||
absl::string_view name, |
||||
absl::AnyInvocable<YodelTest*(const fuzzing_event_engine::Actions&, |
||||
absl::BitGenRef) const> |
||||
create); |
||||
|
||||
static SimpleTestRegistry& Get() { |
||||
static SimpleTestRegistry* const p = new SimpleTestRegistry; |
||||
return *p; |
||||
} |
||||
|
||||
private: |
||||
void ContributeTests(std::vector<Test>& tests) override; |
||||
|
||||
std::vector<Test> tests_; |
||||
}; |
||||
|
||||
template <typename /*test_type*/, typename T> |
||||
class ParameterizedTestRegistry final : public TestRegistry { |
||||
public: |
||||
ParameterizedTestRegistry() {} |
||||
~ParameterizedTestRegistry() = delete; |
||||
|
||||
void RegisterTest(absl::string_view file, int line, |
||||
absl::string_view test_type, absl::string_view name, |
||||
absl::AnyInvocable<YodelTest*( |
||||
const T&, const fuzzing_event_engine::Actions&, |
||||
absl::BitGenRef) const> |
||||
make) { |
||||
tests_.push_back({file, line, test_type, name, std::move(make)}); |
||||
} |
||||
|
||||
void RegisterParameter(absl::string_view name, T value) { |
||||
parameters_.push_back({name, std::move(value)}); |
||||
} |
||||
|
||||
static ParameterizedTestRegistry& Get() { |
||||
static ParameterizedTestRegistry* const p = new ParameterizedTestRegistry; |
||||
return *p; |
||||
} |
||||
|
||||
private: |
||||
struct ParameterizedTest { |
||||
absl::string_view file; |
||||
int line; |
||||
absl::string_view test_type; |
||||
absl::string_view name; |
||||
absl::AnyInvocable<YodelTest*( |
||||
const T&, const fuzzing_event_engine::Actions&, absl::BitGenRef) const> |
||||
make; |
||||
}; |
||||
struct Parameter { |
||||
absl::string_view name; |
||||
T value; |
||||
}; |
||||
|
||||
void ContributeTests(std::vector<Test>& tests) override { |
||||
for (const auto& test : tests_) { |
||||
for (const auto& parameter : parameters_) { |
||||
tests.push_back({test.file, test.line, std::string(test.test_type), |
||||
absl::StrCat(test.name, "/", parameter.name), |
||||
[test = &test, parameter = ¶meter]( |
||||
const fuzzing_event_engine::Actions& actions, |
||||
absl::BitGenRef rng) { |
||||
return test->make(parameter->value, actions, rng); |
||||
}}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
std::vector<ParameterizedTest> tests_; |
||||
std::vector<Parameter> parameters_; |
||||
}; |
||||
|
||||
} // namespace yodel_detail
|
||||
|
||||
class YodelTest : public ::testing::Test { |
||||
public: |
||||
void RunTest(); |
||||
|
||||
protected: |
||||
YodelTest(const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng); |
||||
|
||||
// Helpers to generate various random values.
|
||||
// When we're fuzzing, delegates to the fuzzer input to generate this data.
|
||||
std::string RandomString(int min_length, int max_length, |
||||
absl::string_view character_set); |
||||
std::string RandomStringFrom( |
||||
std::initializer_list<absl::string_view> choices); |
||||
std::string RandomMetadataKey(); |
||||
std::string RandomMetadataValue(absl::string_view key); |
||||
std::string RandomMetadataBinaryKey(); |
||||
std::string RandomMetadataBinaryValue(); |
||||
std::vector<std::pair<std::string, std::string>> RandomMetadata(); |
||||
std::string RandomMessage(); |
||||
absl::BitGenRef rng() { return rng_; } |
||||
|
||||
// Alternative for Seq for test driver code.
|
||||
// Registers each step so that WaitForAllPendingWork() can report progress,
|
||||
// and wait for completion... AND generate good failure messages when a
|
||||
// sequence doesn't complete in a timely manner.
|
||||
// Uses the `SpawnInfallible` method on `context` to provide an execution
|
||||
// environment for each step.
|
||||
// Initiates each step in a different event engine closure to maximize
|
||||
// opportunities for fuzzers to reorder the steps, or thready-tsan to expose
|
||||
// potential threading issues.
|
||||
template <typename Context, typename... Actions> |
||||
void SpawnTestSeq(Context context, |
||||
yodel_detail::NameAndLocation name_and_location, |
||||
Actions... actions) { |
||||
yodel_detail::SequenceSpawner( |
||||
name_and_location, |
||||
yodel_detail::SpawnerForContext(std::move(context), |
||||
event_engine_.get()), |
||||
[this](yodel_detail::NameAndLocation name_and_location, int step) { |
||||
auto action = std::make_shared<yodel_detail::ActionState>( |
||||
name_and_location, step); |
||||
pending_actions_.push(action); |
||||
return action; |
||||
}) |
||||
.Start(std::move(actions)...); |
||||
} |
||||
|
||||
auto MakeCall(ClientMetadataHandle client_initial_metadata) { |
||||
auto* arena = call_arena_allocator_->MakeArena(); |
||||
return MakeCallPair(std::move(client_initial_metadata), event_engine_.get(), |
||||
arena, call_arena_allocator_, nullptr); |
||||
} |
||||
|
||||
void WaitForAllPendingWork(); |
||||
|
||||
template <typename T> |
||||
T TickUntil(absl::FunctionRef<Poll<T>()> poll) { |
||||
absl::optional<T> result; |
||||
TickUntilTrue([poll, &result]() { |
||||
auto r = poll(); |
||||
if (auto* p = r.value_if_ready()) { |
||||
result = std::move(*p); |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
return std::move(*result); |
||||
} |
||||
|
||||
const std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>& |
||||
event_engine() { |
||||
return event_engine_; |
||||
} |
||||
|
||||
private: |
||||
class WatchDog; |
||||
|
||||
virtual void TestImpl() = 0; |
||||
|
||||
void Timeout(); |
||||
void TickUntilTrue(absl::FunctionRef<bool()> poll); |
||||
|
||||
// Called after the test has run, but before the event engine is shut down.
|
||||
virtual void Shutdown() {} |
||||
|
||||
grpc::testing::TestGrpcScope grpc_scope_; |
||||
absl::BitGenRef rng_; |
||||
const std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> |
||||
event_engine_; |
||||
const RefCountedPtr<CallArenaAllocator> call_arena_allocator_; |
||||
std::queue<std::shared_ptr<yodel_detail::ActionState>> pending_actions_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#define YODEL_TEST(test_type, name) \ |
||||
class YodelTest_##name : public grpc_core::test_type { \
|
||||
public: \
|
||||
using test_type::test_type; \
|
||||
void TestBody() override { RunTest(); } \
|
||||
\
|
||||
private: \
|
||||
void TestImpl() override; \
|
||||
static grpc_core::YodelTest* Create( \
|
||||
const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
|
||||
return new YodelTest_##name(actions, rng); \
|
||||
} \
|
||||
static int registered_; \
|
||||
}; \
|
||||
int YodelTest_##name::registered_ = \
|
||||
(grpc_core::yodel_detail::SimpleTestRegistry::Get().RegisterTest( \
|
||||
__FILE__, __LINE__, #test_type, #name, &Create), \
|
||||
0); \
|
||||
void YodelTest_##name::TestImpl() |
||||
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define YODEL_TEST_P(test_type, parameter_type, name) \ |
||||
class YodelTest_##name : public grpc_core::test_type { \
|
||||
public: \
|
||||
using test_type::test_type; \
|
||||
void TestBody() override { RunTest(); } \
|
||||
\
|
||||
private: \
|
||||
void TestImpl() override; \
|
||||
static grpc_core::YodelTest* Create( \
|
||||
const parameter_type& parameter, \
|
||||
const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
|
||||
return new YodelTest_##name(parameter, actions, rng); \
|
||||
} \
|
||||
static int registered_; \
|
||||
}; \
|
||||
int YodelTest_##name::registered_ = \
|
||||
(grpc_core::yodel_detail::ParameterizedTestRegistry< \
|
||||
grpc_core::test_type, parameter_type>::Get() \
|
||||
.RegisterTest(__FILE__, __LINE__, #test_type, #name, &Create), \
|
||||
0); \
|
||||
void YodelTest_##name::TestImpl() |
||||
|
||||
#define YODEL_TEST_PARAM(test_type, parameter_type, name, value) \ |
||||
int YodelTestParam_##name = \
|
||||
(grpc_core::yodel_detail::ParameterizedTestRegistry< \
|
||||
grpc_core::test_type, parameter_type>::Get() \
|
||||
.RegisterParameter(#name, value), \
|
||||
0) |
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
#endif // GRPC_TEST_CORE_CALL_YODEL_YODEL_TEST_H
|
@ -1,29 +0,0 @@ |
||||
// Copyright 2023 gRPC 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 "test/core/transport/test_suite/fixture.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
TransportFixtureRegistry& TransportFixtureRegistry::Get() { |
||||
static TransportFixtureRegistry* registry = new TransportFixtureRegistry(); |
||||
return *registry; |
||||
} |
||||
void TransportFixtureRegistry::RegisterFixture( |
||||
absl::string_view name, |
||||
absl::AnyInvocable<TransportFixture*() const> create) { |
||||
fixtures_.push_back({name, std::move(create)}); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -1,77 +0,0 @@ |
||||
// Copyright 2023 gRPC 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.
|
||||
|
||||
#ifndef GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_FIXTURE_H |
||||
#define GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_FIXTURE_H |
||||
|
||||
#include "src/core/lib/transport/transport.h" |
||||
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class TransportFixture { |
||||
public: |
||||
struct ClientAndServerTransportPair { |
||||
OrphanablePtr<Transport> client; |
||||
OrphanablePtr<Transport> server; |
||||
}; |
||||
virtual ~TransportFixture() = default; |
||||
virtual ClientAndServerTransportPair CreateTransportPair( |
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> |
||||
event_engine) = 0; |
||||
}; |
||||
|
||||
class TransportFixtureRegistry { |
||||
public: |
||||
static TransportFixtureRegistry& Get(); |
||||
void RegisterFixture(absl::string_view name, |
||||
absl::AnyInvocable<TransportFixture*() const> create); |
||||
|
||||
struct Fixture { |
||||
absl::string_view name; |
||||
absl::AnyInvocable<TransportFixture*() const> create; |
||||
}; |
||||
|
||||
const std::vector<Fixture>& fixtures() const { return fixtures_; } |
||||
|
||||
private: |
||||
std::vector<Fixture> fixtures_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#define TRANSPORT_FIXTURE(name) \ |
||||
class TransportFixture_##name : public grpc_core::TransportFixture { \
|
||||
public: \
|
||||
using TransportFixture::TransportFixture; \
|
||||
ClientAndServerTransportPair CreateTransportPair( \
|
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> \
|
||||
event_engine) override; \
|
||||
\
|
||||
private: \
|
||||
static grpc_core::TransportFixture* Create() { \
|
||||
return new TransportFixture_##name(); \
|
||||
} \
|
||||
static int registered_; \
|
||||
}; \
|
||||
int TransportFixture_##name::registered_ = \
|
||||
(grpc_core::TransportFixtureRegistry::Get().RegisterFixture( \
|
||||
#name, &TransportFixture_##name::Create), \ |
||||
0); \
|
||||
grpc_core::TransportFixture::ClientAndServerTransportPair \
|
||||
TransportFixture_##name::CreateTransportPair( \
|
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> \
|
||||
event_engine GRPC_UNUSED) |
||||
|
||||
#endif // GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_FIXTURE_H
|
@ -1,368 +0,0 @@ |
||||
// Copyright 2023 gRPC 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.
|
||||
|
||||
#ifndef GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H |
||||
#define GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H |
||||
|
||||
#include <initializer_list> |
||||
#include <memory> |
||||
#include <queue> |
||||
|
||||
#include "absl/functional/any_invocable.h" |
||||
#include "absl/log/log.h" |
||||
#include "absl/random/bit_gen_ref.h" |
||||
#include "absl/strings/string_view.h" |
||||
#include "gmock/gmock.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include "src/core/lib/gprpp/time.h" |
||||
#include "src/core/lib/iomgr/timer_manager.h" |
||||
#include "src/core/lib/promise/cancel_callback.h" |
||||
#include "src/core/lib/promise/promise.h" |
||||
#include "src/core/lib/resource_quota/resource_quota.h" |
||||
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h" |
||||
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.pb.h" |
||||
#include "test/core/test_util/test_config.h" |
||||
#include "test/core/transport/test_suite/fixture.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace transport_test_detail { |
||||
|
||||
struct NameAndLocation { |
||||
// NOLINTNEXTLINE
|
||||
NameAndLocation(const char* name, SourceLocation location = {}) |
||||
: location_(location), name_(name) {} |
||||
NameAndLocation Next() const { |
||||
return NameAndLocation(name_, location_, step_ + 1); |
||||
} |
||||
|
||||
SourceLocation location() const { return location_; } |
||||
absl::string_view name() const { return name_; } |
||||
int step() const { return step_; } |
||||
|
||||
private: |
||||
NameAndLocation(absl::string_view name, SourceLocation location, int step) |
||||
: location_(location), name_(name), step_(step) {} |
||||
SourceLocation location_; |
||||
absl::string_view name_; |
||||
int step_ = 1; |
||||
}; |
||||
|
||||
class ActionState { |
||||
public: |
||||
enum State : uint8_t { |
||||
kNotCreated, |
||||
kNotStarted, |
||||
kStarted, |
||||
kDone, |
||||
kCancelled, |
||||
}; |
||||
|
||||
static absl::string_view StateString(State state) { |
||||
switch (state) { |
||||
case kNotCreated: |
||||
return "🚦"; |
||||
case kNotStarted: |
||||
return "⏰"; |
||||
case kStarted: |
||||
return "🚗"; |
||||
case kDone: |
||||
return "🏁"; |
||||
case kCancelled: |
||||
return "💥"; |
||||
} |
||||
} |
||||
|
||||
explicit ActionState(NameAndLocation name_and_location); |
||||
|
||||
State Get() const { return state_; } |
||||
void Set(State state, SourceLocation whence = {}) { |
||||
LOG(INFO) << StateString(state) << " " << name() << " [" << step() << "] " |
||||
<< file() << ":" << line() << " @ " << whence.file() << ":" |
||||
<< whence.line(); |
||||
state_ = state; |
||||
} |
||||
const NameAndLocation& name_and_location() const { |
||||
return name_and_location_; |
||||
} |
||||
SourceLocation location() const { return name_and_location().location(); } |
||||
const char* file() const { return location().file(); } |
||||
int line() const { return location().line(); } |
||||
absl::string_view name() const { return name_and_location().name(); } |
||||
int step() const { return name_and_location().step(); } |
||||
bool IsDone(); |
||||
|
||||
private: |
||||
const NameAndLocation name_and_location_; |
||||
std::atomic<State> state_; |
||||
}; |
||||
|
||||
using PromiseSpawner = std::function<void(absl::string_view, Promise<Empty>)>; |
||||
using ActionStateFactory = |
||||
absl::FunctionRef<std::shared_ptr<ActionState>(NameAndLocation)>; |
||||
|
||||
template <typename Context> |
||||
PromiseSpawner SpawnerForContext( |
||||
Context context, |
||||
grpc_event_engine::experimental::EventEngine* event_engine) { |
||||
return [context = std::move(context), event_engine]( |
||||
absl::string_view name, Promise<Empty> promise) mutable { |
||||
// Pass new promises via event engine to allow fuzzers to explore
|
||||
// reorderings of possibly interleaved spawns.
|
||||
event_engine->Run([name, context = std::move(context), |
||||
promise = std::move(promise)]() mutable { |
||||
context.SpawnInfallible(name, std::move(promise)); |
||||
}); |
||||
}; |
||||
} |
||||
|
||||
template <typename Arg> |
||||
using NextSpawner = absl::AnyInvocable<void(Arg)>; |
||||
|
||||
template <typename R, typename P> |
||||
Promise<Empty> WrapPromiseAndNext(std::shared_ptr<ActionState> action_state, |
||||
P promise, NextSpawner<R> next) { |
||||
return Promise<Empty>(OnCancel( |
||||
[action_state, promise = std::move(promise), |
||||
next = std::move(next)]() mutable -> Poll<Empty> { |
||||
action_state->Set(ActionState::kStarted); |
||||
auto r = promise(); |
||||
if (auto* p = r.value_if_ready()) { |
||||
action_state->Set(ActionState::kDone); |
||||
next(std::move(*p)); |
||||
return Empty{}; |
||||
} else { |
||||
return Pending{}; |
||||
} |
||||
}, |
||||
[action_state]() { action_state->Set(ActionState::kCancelled); })); |
||||
} |
||||
|
||||
template <typename Arg> |
||||
NextSpawner<Arg> WrapFollowUps(NameAndLocation, ActionStateFactory, |
||||
PromiseSpawner) { |
||||
return [](Empty) {}; |
||||
} |
||||
|
||||
template <typename Arg, typename FirstFollowUp, typename... FollowUps> |
||||
NextSpawner<Arg> WrapFollowUps(NameAndLocation loc, |
||||
ActionStateFactory action_state_factory, |
||||
PromiseSpawner spawner, FirstFollowUp first, |
||||
FollowUps... follow_ups) { |
||||
using Factory = promise_detail::OncePromiseFactory<Arg, FirstFollowUp>; |
||||
using FactoryPromise = typename Factory::Promise; |
||||
using Result = typename FactoryPromise::Result; |
||||
auto action_state = action_state_factory(loc); |
||||
return [spawner, factory = Factory(std::move(first)), |
||||
next = WrapFollowUps<Result>(loc.Next(), action_state_factory, |
||||
spawner, std::move(follow_ups)...), |
||||
action_state = std::move(action_state), |
||||
name = loc.name()](Arg arg) mutable { |
||||
action_state->Set(ActionState::kNotStarted); |
||||
spawner(name, |
||||
WrapPromiseAndNext(std::move(action_state), |
||||
factory.Make(std::move(arg)), std::move(next))); |
||||
}; |
||||
} |
||||
|
||||
template <typename First, typename... FollowUps> |
||||
void StartSeq(NameAndLocation loc, ActionStateFactory action_state_factory, |
||||
PromiseSpawner spawner, First first, FollowUps... followups) { |
||||
using Factory = promise_detail::OncePromiseFactory<void, First>; |
||||
using FactoryPromise = typename Factory::Promise; |
||||
using Result = typename FactoryPromise::Result; |
||||
auto action_state = action_state_factory(loc); |
||||
auto next = WrapFollowUps<Result>(loc.Next(), action_state_factory, spawner, |
||||
std::move(followups)...); |
||||
spawner( |
||||
loc.name(), |
||||
[spawner, first = Factory(std::move(first)), next = std::move(next), |
||||
action_state = std::move(action_state), name = loc.name()]() mutable { |
||||
action_state->Set(ActionState::kNotStarted); |
||||
auto promise = first.Make(); |
||||
spawner(name, WrapPromiseAndNext(std::move(action_state), |
||||
std::move(promise), std::move(next))); |
||||
return Empty{}; |
||||
}); |
||||
} |
||||
|
||||
}; // namespace transport_test_detail
|
||||
|
||||
class TransportTest : public ::testing::Test { |
||||
public: |
||||
void RunTest(); |
||||
|
||||
protected: |
||||
TransportTest(std::unique_ptr<TransportFixture> fixture, |
||||
const fuzzing_event_engine::Actions& actions, |
||||
absl::BitGenRef rng) |
||||
: event_engine_(std::make_shared< |
||||
grpc_event_engine::experimental::FuzzingEventEngine>( |
||||
[]() { |
||||
grpc_timer_manager_set_threading(false); |
||||
grpc_event_engine::experimental::FuzzingEventEngine::Options |
||||
options; |
||||
return options; |
||||
}(), |
||||
actions)), |
||||
fixture_(std::move(fixture)), |
||||
rng_(rng) {} |
||||
|
||||
void SetServerCallDestination(); |
||||
CallInitiator CreateCall(ClientMetadataHandle client_initial_metadata); |
||||
|
||||
std::string RandomString(int min_length, int max_length, |
||||
absl::string_view character_set); |
||||
std::string RandomStringFrom( |
||||
std::initializer_list<absl::string_view> choices); |
||||
std::string RandomMetadataKey(); |
||||
std::string RandomMetadataValue(absl::string_view key); |
||||
std::string RandomMetadataBinaryKey(); |
||||
std::string RandomMetadataBinaryValue(); |
||||
std::vector<std::pair<std::string, std::string>> RandomMetadata(); |
||||
std::string RandomMessage(); |
||||
absl::BitGenRef rng() { return rng_; } |
||||
|
||||
CallHandler TickUntilServerCall(); |
||||
void WaitForAllPendingWork(); |
||||
|
||||
auto MakeCall(ClientMetadataHandle client_initial_metadata) { |
||||
auto* arena = call_arena_allocator_->MakeArena(); |
||||
return MakeCallPair(std::move(client_initial_metadata), event_engine_.get(), |
||||
arena, call_arena_allocator_, nullptr); |
||||
} |
||||
|
||||
// Alternative for Seq for test driver code.
|
||||
// Registers each step so that WaitForAllPendingWork() can report progress,
|
||||
// and wait for completion... AND generate good failure messages when a
|
||||
// sequence doesn't complete in a timely manner.
|
||||
template <typename Context, typename... Actions> |
||||
void SpawnTestSeq(Context context, |
||||
transport_test_detail::NameAndLocation name_and_location, |
||||
Actions... actions) { |
||||
transport_test_detail::StartSeq( |
||||
name_and_location, |
||||
[this](transport_test_detail::NameAndLocation name_and_location) { |
||||
auto action = std::make_shared<transport_test_detail::ActionState>( |
||||
name_and_location); |
||||
pending_actions_.push(action); |
||||
return action; |
||||
}, |
||||
transport_test_detail::SpawnerForContext(std::move(context), |
||||
event_engine_.get()), |
||||
std::move(actions)...); |
||||
} |
||||
|
||||
private: |
||||
virtual void TestImpl() = 0; |
||||
|
||||
void Timeout(); |
||||
|
||||
class ServerCallDestination final : public UnstartedCallDestination { |
||||
public: |
||||
void StartCall(UnstartedCallHandler unstarted_call_handler) override; |
||||
void Orphaned() override {} |
||||
absl::optional<CallHandler> PopHandler(); |
||||
|
||||
private: |
||||
std::queue<CallHandler> handlers_; |
||||
}; |
||||
|
||||
class WatchDog { |
||||
public: |
||||
explicit WatchDog(TransportTest* test) : test_(test) {} |
||||
~WatchDog() { test_->event_engine_->Cancel(timer_); } |
||||
|
||||
private: |
||||
TransportTest* const test_; |
||||
grpc_event_engine::experimental::EventEngine::TaskHandle const timer_{ |
||||
test_->event_engine_->RunAfter(Duration::Minutes(5), |
||||
[this]() { test_->Timeout(); })}; |
||||
}; |
||||
|
||||
grpc::testing::TestGrpcScope grpc_scope_; |
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> |
||||
event_engine_{ |
||||
std::make_shared<grpc_event_engine::experimental::FuzzingEventEngine>( |
||||
[]() { |
||||
grpc_timer_manager_set_threading(false); |
||||
grpc_event_engine::experimental::FuzzingEventEngine::Options |
||||
options; |
||||
return options; |
||||
}(), |
||||
fuzzing_event_engine::Actions())}; |
||||
std::unique_ptr<TransportFixture> fixture_; |
||||
RefCountedPtr<CallArenaAllocator> call_arena_allocator_{ |
||||
MakeRefCounted<CallArenaAllocator>( |
||||
MakeResourceQuota("test-quota") |
||||
->memory_quota() |
||||
->CreateMemoryAllocator("test-allocator"), |
||||
1024)}; |
||||
RefCountedPtr<ServerCallDestination> server_call_destination_ = |
||||
MakeRefCounted<ServerCallDestination>(); |
||||
TransportFixture::ClientAndServerTransportPair transport_pair_ = |
||||
fixture_->CreateTransportPair(event_engine_); |
||||
std::queue<std::shared_ptr<transport_test_detail::ActionState>> |
||||
pending_actions_; |
||||
absl::BitGenRef rng_; |
||||
}; |
||||
|
||||
class TransportTestRegistry { |
||||
public: |
||||
static TransportTestRegistry& Get(); |
||||
void RegisterTest( |
||||
absl::string_view name, |
||||
absl::AnyInvocable<TransportTest*(std::unique_ptr<TransportFixture>, |
||||
const fuzzing_event_engine::Actions&, |
||||
absl::BitGenRef) const> |
||||
create); |
||||
|
||||
struct Test { |
||||
absl::string_view name; |
||||
absl::AnyInvocable<TransportTest*(std::unique_ptr<TransportFixture>, |
||||
const fuzzing_event_engine::Actions&, |
||||
absl::BitGenRef) const> |
||||
create; |
||||
}; |
||||
|
||||
const std::vector<Test>& tests() const { return tests_; } |
||||
|
||||
private: |
||||
std::vector<Test> tests_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#define TRANSPORT_TEST(name) \ |
||||
class TransportTest_##name : public grpc_core::TransportTest { \
|
||||
public: \
|
||||
using TransportTest::TransportTest; \
|
||||
void TestBody() override { RunTest(); } \
|
||||
\
|
||||
private: \
|
||||
void TestImpl() override; \
|
||||
static grpc_core::TransportTest* Create( \
|
||||
std::unique_ptr<grpc_core::TransportFixture> fixture, \
|
||||
const fuzzing_event_engine::Actions& actions, absl::BitGenRef rng) { \
|
||||
return new TransportTest_##name(std::move(fixture), actions, rng); \
|
||||
} \
|
||||
static int registered_; \
|
||||
}; \
|
||||
int TransportTest_##name::registered_ = \
|
||||
(grpc_core::TransportTestRegistry::Get().RegisterTest(#name, &Create), \
|
||||
0); \
|
||||
void TransportTest_##name::TestImpl() |
||||
|
||||
#endif // GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TEST_H
|
@ -0,0 +1,69 @@ |
||||
// Copyright 2023 gRPC 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 "test/core/transport/test_suite/transport_test.h" |
||||
|
||||
#include <initializer_list> |
||||
|
||||
#include "absl/random/random.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TransportTest
|
||||
|
||||
void TransportTest::SetServerCallDestination() { |
||||
transport_pair_.server->server_transport()->SetCallDestination( |
||||
server_call_destination_); |
||||
} |
||||
|
||||
CallInitiator TransportTest::CreateCall( |
||||
ClientMetadataHandle client_initial_metadata) { |
||||
auto call = MakeCall(std::move(client_initial_metadata)); |
||||
call.handler.SpawnInfallible( |
||||
"start-call", [this, handler = call.handler]() mutable { |
||||
transport_pair_.client->client_transport()->StartCall( |
||||
handler.V2HackToStartCallWithoutACallFilterStack()); |
||||
return Empty{}; |
||||
}); |
||||
return std::move(call.initiator); |
||||
} |
||||
|
||||
CallHandler TransportTest::TickUntilServerCall() { |
||||
auto poll = [this]() -> Poll<CallHandler> { |
||||
auto handler = server_call_destination_->PopHandler(); |
||||
if (handler.has_value()) return std::move(*handler); |
||||
return Pending(); |
||||
}; |
||||
return TickUntil(absl::FunctionRef<Poll<CallHandler>()>(poll)); |
||||
} |
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TransportTest::ServerCallDestination
|
||||
|
||||
void TransportTest::ServerCallDestination::StartCall( |
||||
UnstartedCallHandler handler) { |
||||
handlers_.push(handler.V2HackToStartCallWithoutACallFilterStack()); |
||||
} |
||||
|
||||
absl::optional<CallHandler> TransportTest::ServerCallDestination::PopHandler() { |
||||
if (!handlers_.empty()) { |
||||
auto handler = std::move(handlers_.front()); |
||||
handlers_.pop(); |
||||
return handler; |
||||
} |
||||
return absl::nullopt; |
||||
} |
||||
|
||||
} // namespace grpc_core
|
@ -0,0 +1,87 @@ |
||||
// Copyright 2023 gRPC 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.
|
||||
|
||||
#ifndef GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TRANSPORT_TEST_H |
||||
#define GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TRANSPORT_TEST_H |
||||
|
||||
#include <memory> |
||||
#include <queue> |
||||
|
||||
#include "absl/random/bit_gen_ref.h" |
||||
#include "gmock/gmock.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include "src/core/lib/transport/transport.h" |
||||
#include "test/core/call/yodel/yodel_test.h" |
||||
#include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
struct ClientAndServerTransportPair { |
||||
OrphanablePtr<Transport> client; |
||||
OrphanablePtr<Transport> server; |
||||
}; |
||||
|
||||
using TransportFixture = absl::AnyInvocable<ClientAndServerTransportPair( |
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine>) |
||||
const>; |
||||
|
||||
class TransportTest : public YodelTest { |
||||
protected: |
||||
TransportTest(const TransportFixture& fixture, |
||||
const fuzzing_event_engine::Actions& actions, |
||||
absl::BitGenRef rng) |
||||
: YodelTest(actions, rng), transport_pair_(fixture(event_engine())) {} |
||||
|
||||
void SetServerCallDestination(); |
||||
CallInitiator CreateCall(ClientMetadataHandle client_initial_metadata); |
||||
|
||||
CallHandler TickUntilServerCall(); |
||||
|
||||
private: |
||||
class ServerCallDestination final : public UnstartedCallDestination { |
||||
public: |
||||
void StartCall(UnstartedCallHandler unstarted_call_handler) override; |
||||
void Orphaned() override {} |
||||
absl::optional<CallHandler> PopHandler(); |
||||
|
||||
private: |
||||
std::queue<CallHandler> handlers_; |
||||
}; |
||||
|
||||
void Shutdown() override { |
||||
transport_pair_.client.reset(); |
||||
transport_pair_.server.reset(); |
||||
} |
||||
|
||||
RefCountedPtr<ServerCallDestination> server_call_destination_ = |
||||
MakeRefCounted<ServerCallDestination>(); |
||||
ClientAndServerTransportPair transport_pair_; |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#define TRANSPORT_TEST(name) YODEL_TEST_P(TransportTest, TransportFixture, name) |
||||
|
||||
#define TRANSPORT_FIXTURE(name) \ |
||||
static grpc_core::ClientAndServerTransportPair name( \
|
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> \
|
||||
event_engine); \
|
||||
YODEL_TEST_PARAM(TransportTest, TransportFixture, name, name); \
|
||||
static grpc_core::ClientAndServerTransportPair name( \
|
||||
GRPC_UNUSED \
|
||||
std::shared_ptr<grpc_event_engine::experimental::FuzzingEventEngine> \
|
||||
event_engine) |
||||
|
||||
#endif // GRPC_TEST_CORE_TRANSPORT_TEST_SUITE_TRANSPORT_TEST_H
|
Loading…
Reference in new issue