diff --git a/api/request_source/request_source_plugin.proto b/api/request_source/request_source_plugin.proto index 1af69a60a..5d56802d9 100644 --- a/api/request_source/request_source_plugin.proto +++ b/api/request_source/request_source_plugin.proto @@ -7,24 +7,41 @@ import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; import "api/client/options.proto"; -// Configuration for FileBasedPluginRequestSource (plugin name: -// "nighthawk.file-based-request-source-plugin") +// Configuration for OptionsListFromFileRequestSourceFactory (plugin name: +// "nighthawk.file-options-list-request-source-plugin") // The factory will load the RequestOptionsList from the file, and then passes it to the // requestSource it generates. The resulting request source will loop over the RequestOptionsList it // is passed. -message FileBasedPluginConfig { +message FileBasedOptionsListRequestSourceConfig { // The file_path is the path to a file that contains a RequestOptionList in json or yaml format. + // This field is required. string file_path = 1; // The pluginfactory makes requestSources that will generate requests from the RequestOptionList // up to num_requests number of times. If num_requests exceeds the number of RequestOptions in the - // RequestOptionList located in the file at file_path, it will loop. num_requests = 0 means no - // limit on the number of requests to be produced. - google.protobuf.UInt32Value num_requests = 2 [(validate.rules).uint32 = {gte: 0, lte: 1000000}]; - // The pluginfactory will load the file located in file_path as long as it is below max_file_size, - // if it's too large it will throw an error. + // RequestOptionList located in the file at file_path, it will loop. num_requests = 0 means it + // will loop indefinitely, though it will still terminate by normal mechanisms. + uint32 num_requests = 2; + // The pluginfactory will load the file located in file_path as long as it is below max_file_size + // in bytes, if it's too large it will throw an error. This field is optional with a default of + // 1000000. google.protobuf.UInt32Value max_file_size = 3 [(validate.rules).uint32 = {lte: 1000000}]; } +// Configuration for OptionsListFromProtoRequestSourceFactory (plugin name: +// "nighthawk.in-line-options-list-request-source-plugin") +// The resulting request source will loop over the RequestOptionsList it +// is passed. +message InLineOptionsListRequestSourceConfig { + // The options_list will be used to generate Requests in the RequestSource. This field is + // required. + nighthawk.client.RequestOptionsList options_list = 1; + // The pluginfactory makes requestSources that will generate requests from the RequestOptionList + // up to num_requests number of times. If num_requests exceeds the number of RequestOptions in the + // options_list, it will loop. num_requests = 0 means it will loop indefinitely, though it will + // still terminate by normal mechanisms. + uint32 num_requests = 2; +} + // Configuration for StubPluginRequestSource (plugin name: "nighthawk.stub-request-source-plugin") // The plugin does nothing. This is for testing and comparison of the Request Source Plugin Factory // mechanism using a minimal version of plugin that does not require a more complicated proto or diff --git a/source/request_source/request_options_list_plugin_impl.cc b/source/request_source/request_options_list_plugin_impl.cc index 7fd25f3aa..bdb9a3fbc 100644 --- a/source/request_source/request_options_list_plugin_impl.cc +++ b/source/request_source/request_options_list_plugin_impl.cc @@ -10,23 +10,24 @@ #include "common/request_source_impl.h" namespace Nighthawk { -std::string OptionsListFromFileRequestSourceFactory::name() const { +std::string FileBasedOptionsListRequestSourceFactory::name() const { return "nighthawk.file-based-request-source-plugin"; } -Envoy::ProtobufTypes::MessagePtr OptionsListFromFileRequestSourceFactory::createEmptyConfigProto() { - return std::make_unique(); +Envoy::ProtobufTypes::MessagePtr +FileBasedOptionsListRequestSourceFactory::createEmptyConfigProto() { + return std::make_unique(); } -RequestSourcePtr OptionsListFromFileRequestSourceFactory::createRequestSourcePlugin( +RequestSourcePtr FileBasedOptionsListRequestSourceFactory::createRequestSourcePlugin( const Envoy::Protobuf::Message& message, Envoy::Api::Api& api, Envoy::Http::RequestHeaderMapPtr header) { const auto& any = dynamic_cast(message); - nighthawk::request_source::FileBasedPluginConfig config; + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config; Envoy::MessageUtil util; - + uint32_t max_file_size = config.has_max_file_size() ? config.max_file_size().value() : 1000000; util.unpackTo(any, config); - if (api.fileSystem().fileSize(config.file_path()) > config.max_file_size().value()) { + if (api.fileSystem().fileSize(config.file_path()) > max_file_size) { throw NighthawkException("file size must be less than max_file_size"); } @@ -34,23 +35,54 @@ RequestSourcePtr OptionsListFromFileRequestSourceFactory::createRequestSourcePlu { Envoy::Thread::LockGuard lock_guard(file_lock_); // Reading the file only the first time. - if (options_list_.options_size() == 0) { - util.loadFromFile(config.file_path(), options_list_, + if (!options_list_.has_value()) { + nighthawk::client::RequestOptionsList loaded_list; + util.loadFromFile(config.file_path(), loaded_list, Envoy::ProtobufMessage::getStrictValidationVisitor(), api, true); + options_list_ = loaded_list; + } + } + return std::make_unique(config.num_requests(), std::move(header), + options_list_.value()); +} + +REGISTER_FACTORY(FileBasedOptionsListRequestSourceFactory, RequestSourcePluginConfigFactory); + +std::string InLineOptionsListRequestSourceFactory::name() const { + return "nighthawk.in-line-options-list-request-source-plugin"; +} + +Envoy::ProtobufTypes::MessagePtr InLineOptionsListRequestSourceFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +RequestSourcePtr InLineOptionsListRequestSourceFactory::createRequestSourcePlugin( + const Envoy::Protobuf::Message& message, Envoy::Api::Api&, + Envoy::Http::RequestHeaderMapPtr header) { + const auto& any = dynamic_cast(message); + nighthawk::request_source::InLineOptionsListRequestSourceConfig config; + Envoy::MessageUtil::unpackTo(any, config); + // Locking to avoid issues with multiple threads calling this at the same time and trying to set + // the options_list_ + { + Envoy::Thread::LockGuard lock_guard(options_list_lock_); + // Only loading the config into memory the first time. + if (!options_list_.has_value()) { + options_list_ = config.options_list(); } } - return std::make_unique(config.num_requests().value(), - std::move(header), options_list_); + return std::make_unique(config.num_requests(), std::move(header), + options_list_.value()); } -REGISTER_FACTORY(OptionsListFromFileRequestSourceFactory, RequestSourcePluginConfigFactory); +REGISTER_FACTORY(InLineOptionsListRequestSourceFactory, RequestSourcePluginConfigFactory); -RequestOptionsListRequestSource::RequestOptionsListRequestSource( +OptionsListRequestSource::OptionsListRequestSource( const uint32_t total_requests, Envoy::Http::RequestHeaderMapPtr header, const nighthawk::client::RequestOptionsList& options_list) : header_(std::move(header)), options_list_(options_list), total_requests_(total_requests) {} -RequestGenerator RequestOptionsListRequestSource::get() { +RequestGenerator OptionsListRequestSource::get() { request_count_.push_back(0); uint32_t& lambda_counter = request_count_.back(); RequestGenerator request_generator = [this, lambda_counter]() mutable -> RequestPtr { @@ -72,7 +104,8 @@ RequestGenerator RequestOptionsListRequestSource::get() { header->setMethod(envoy::config::core::v3::RequestMethod_Name(request_option.request_method())); const uint32_t content_length = request_option.request_body_size().value(); if (content_length > 0) { - header->setContentLength(content_length); + header->setContentLength( + content_length); // Content length is used later in stream_decoder to populate the body } for (const envoy::config::core::v3::HeaderValueOption& option_header : request_option.request_headers()) { @@ -84,6 +117,6 @@ RequestGenerator RequestOptionsListRequestSource::get() { return request_generator; } -void RequestOptionsListRequestSource::initOnThread() {} +void OptionsListRequestSource::initOnThread() {} } // namespace Nighthawk \ No newline at end of file diff --git a/source/request_source/request_options_list_plugin_impl.h b/source/request_source/request_options_list_plugin_impl.h index 3fbf485ff..8de3d7ef9 100644 --- a/source/request_source/request_options_list_plugin_impl.h +++ b/source/request_source/request_options_list_plugin_impl.h @@ -1,4 +1,4 @@ -// Implementations of RequestSourceConfigFactories that make a RequestOptionsListRequestSource. +// Implementations of RequestSourceConfigFactories that make a OptionsListRequestSource. #pragma once #include "envoy/registry/registry.h" @@ -25,14 +25,13 @@ namespace Nighthawk { // RequestGenerator produced by get() will use options from the options_list to overwrite values in // the default header, and create new requests. if total_requests is greater than the length of // options_list, it will loop. This is not thread safe. -class RequestOptionsListRequestSource : public RequestSource { +class OptionsListRequestSource : public RequestSource { public: - RequestOptionsListRequestSource(const uint32_t total_requests, - Envoy::Http::RequestHeaderMapPtr header, - const nighthawk::client::RequestOptionsList& options_list); + OptionsListRequestSource(const uint32_t total_requests, Envoy::Http::RequestHeaderMapPtr header, + const nighthawk::client::RequestOptionsList& options_list); // This get function is not thread safe, because multiple threads calling get simultaneously will - // result in a collision as it attempts to update its request_count_. + // result in a collision. RequestGenerator get() override; // default implementation @@ -45,40 +44,75 @@ class RequestOptionsListRequestSource : public RequestSource { const uint32_t total_requests_; }; -// Factory that creates a RequestOptionsListRequestSource from a FileBasedPluginConfig proto. -// Registered as an Envoy plugin. -// Implementation of RequestSourceConfigFactory which produces a RequestSource that keeps an -// RequestOptionsList in memory, and loads it with the RequestOptions taken from a file. All plugins -// configuration are specified in the request_source_plugin.proto. This class is not thread-safe, -// because it loads its RequestOptionlist in memory from a file when first called. -// Usage: assume you are passed an appropriate Any type object called config, an Api object called -// api, and a default header called header. auto& config_factory = +// Factory that creates a OptionsListRequestSource from a FileBasedOptionsListRequestSourceConfig +// proto. Registered as an Envoy plugin. Implementation of RequestSourceConfigFactory which produces +// a RequestSource that keeps an RequestOptionsList in memory, and loads it with the RequestOptions +// taken from a file. All plugins configuration are specified in the request_source_plugin.proto. +// This class is thread-safe, +// Usage: assume you are passed an appropriate Any type object called config, an Api +// object called api, and a default header called header. auto& config_factory = // Envoy::Config::Utility::getAndCheckFactoryByName( // "nighthawk.file-based-request-source-plugin"); // RequestSourcePtr plugin = // config_factory.createRequestSourcePlugin(config, std::move(api), std::move(header)); -class OptionsListFromFileRequestSourceFactory : public virtual RequestSourcePluginConfigFactory { +class FileBasedOptionsListRequestSourceFactory : public virtual RequestSourcePluginConfigFactory { public: std::string name() const override; Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override; - // This implementation is not thread safe. Only the first call to createRequestSourcePlugin will - // load the file from memory and subsequent calls just make a copy of the options_list that was - // already loaded. The OptionsListFromFileRequestSourceFactory will not work with multiple - // different files for this reason. - // This method will also error if the file can not be loaded correctly, e.g. the file is too large - // or could not be found. + // This implementation is thread safe. There is currently a behaviour such that only the first + // call to createRequestSourcePlugin will load the options list into memory and subsequent calls + // just make a copy of the options_list that was already loaded. The + // FileBasedOptionsListRequestSourceFactory will not work with multiple different files for this + // reason. + // TODO: This memory saving is likely a premature optimization, and should be removed. + // This method will also error if the + // file can not be loaded correctly, e.g. the file is too large or could not be found. RequestSourcePtr createRequestSourcePlugin(const Envoy::Protobuf::Message& message, Envoy::Api::Api& api, Envoy::Http::RequestHeaderMapPtr header) override; private: Envoy::Thread::MutexBasicLockable file_lock_; - nighthawk::client::RequestOptionsList options_list_; + absl::optional options_list_; }; // This factory will be activated through RequestSourceFactory in factories.h -DECLARE_FACTORY(OptionsListFromFileRequestSourceFactory); +DECLARE_FACTORY(FileBasedOptionsListRequestSourceFactory); + +// Factory that creates a OptionsListRequestSource from a InLineOptionsListRequestSourceConfig +// proto. Registered as an Envoy plugin. Implementation of RequestSourceConfigFactory which produces +// a RequestSource that keeps an RequestOptionsList in memory, and loads it with the RequestOptions +// passed to it from the config. All plugins configuration are specified in the +// request_source_plugin.proto. +// This class is thread-safe, +// Usage: assume you are passed an appropriate Any type object called +// config, an Api object called api, and a default header called header. auto& config_factory = +// Envoy::Config::Utility::getAndCheckFactoryByName( +// "nighthawk.in-line-options-list-request-source-plugin"); +// RequestSourcePtr plugin = +// config_factory.createRequestSourcePlugin(config, std::move(api), std::move(header)); + +class InLineOptionsListRequestSourceFactory : public virtual RequestSourcePluginConfigFactory { +public: + std::string name() const override; + Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + // This implementation is thread safe. There is currently a behaviour such that only the first + // call to createRequestSourcePlugin will load the options list into memory and subsequent calls + // just make a copy of the options_list that was already loaded. + // TODO: This memory saving is likely a premature optimization, and should be removed. + RequestSourcePtr createRequestSourcePlugin(const Envoy::Protobuf::Message& message, + Envoy::Api::Api& api, + Envoy::Http::RequestHeaderMapPtr header) override; + +private: + Envoy::Thread::MutexBasicLockable options_list_lock_; + absl::optional options_list_; +}; + +// This factory will be activated through RequestSourceFactory in factories.h +DECLARE_FACTORY(InLineOptionsListRequestSourceFactory); } // namespace Nighthawk \ No newline at end of file diff --git a/test/request_source/request_source_plugin_test.cc b/test/request_source/request_source_plugin_test.cc index fc20b64ea..eee187522 100644 --- a/test/request_source/request_source_plugin_test.cc +++ b/test/request_source/request_source_plugin_test.cc @@ -17,10 +17,25 @@ namespace Nighthawk { namespace { -using nighthawk::request_source::FileBasedPluginConfig; +using nighthawk::request_source::FileBasedOptionsListRequestSourceConfig; +using nighthawk::request_source::InLineOptionsListRequestSourceConfig; using nighthawk::request_source::StubPluginConfig; using ::testing::NiceMock; using ::testing::Test; +nighthawk::request_source::FileBasedOptionsListRequestSourceConfig +MakeFileBasedPluginConfigWithTestYaml(absl::string_view request_file) { + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config; + config.mutable_file_path()->assign(request_file); + config.mutable_max_file_size()->set_value(4000); + return config; +} +nighthawk::request_source::InLineOptionsListRequestSourceConfig +MakeInLinePluginConfig(nighthawk::client::RequestOptionsList options_list, int num_requests) { + nighthawk::request_source::InLineOptionsListRequestSourceConfig config; + *config.mutable_options_list() = std::move(options_list); + config.set_num_requests(num_requests); + return config; +} class StubRequestSourcePluginTest : public Test { public: @@ -34,15 +49,14 @@ class FileBasedRequestSourcePluginTest : public Test { FileBasedRequestSourcePluginTest() : api_(Envoy::Api::createApiForTest(stats_store_)) {} Envoy::Stats::MockIsolatedStatsStore stats_store_; Envoy::Api::ApiPtr api_; - nighthawk::request_source::FileBasedPluginConfig - MakeFileBasedPluginConfigWithTestYaml(absl::string_view request_file) { - nighthawk::request_source::FileBasedPluginConfig config; - config.mutable_file_path()->assign(request_file); - config.mutable_max_file_size()->set_value(4000); - return config; - } }; +class InLineRequestSourcePluginTest : public Test { +public: + InLineRequestSourcePluginTest() : api_(Envoy::Api::createApiForTest(stats_store_)) {} + Envoy::Stats::MockIsolatedStatsStore stats_store_; + Envoy::Api::ApiPtr api_; +}; TEST_F(StubRequestSourcePluginTest, CreateEmptyConfigProtoCreatesCorrectType) { auto& config_factory = Envoy::Config::Utility::getAndCheckFactoryByName( @@ -99,13 +113,13 @@ TEST_F(FileBasedRequestSourcePluginTest, CreateEmptyConfigProtoCreatesCorrectTyp Envoy::Config::Utility::getAndCheckFactoryByName( "nighthawk.file-based-request-source-plugin"); const Envoy::ProtobufTypes::MessagePtr empty_config = config_factory.createEmptyConfigProto(); - const nighthawk::request_source::FileBasedPluginConfig expected_config; + const nighthawk::request_source::FileBasedOptionsListRequestSourceConfig expected_config; EXPECT_EQ(empty_config->DebugString(), expected_config.DebugString()); EXPECT_TRUE(Envoy::MessageUtil()(*empty_config, expected_config)); } TEST_F(FileBasedRequestSourcePluginTest, FactoryRegistrationUsesCorrectPluginName) { - nighthawk::request_source::FileBasedPluginConfig config; + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config; Envoy::ProtobufWkt::Any config_any; config_any.PackFrom(config); auto& config_factory = @@ -115,8 +129,9 @@ TEST_F(FileBasedRequestSourcePluginTest, FactoryRegistrationUsesCorrectPluginNam } TEST_F(FileBasedRequestSourcePluginTest, CreateRequestSourcePluginCreatesCorrectPluginType) { - nighthawk::request_source::FileBasedPluginConfig config = MakeFileBasedPluginConfigWithTestYaml( - TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config = + MakeFileBasedPluginConfigWithTestYaml( + TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); Envoy::ProtobufWkt::Any config_any; config_any.PackFrom(config); auto& config_factory = @@ -125,14 +140,15 @@ TEST_F(FileBasedRequestSourcePluginTest, CreateRequestSourcePluginCreatesCorrect auto header = Envoy::Http::RequestHeaderMapImpl::create(); RequestSourcePtr plugin = config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); - EXPECT_NE(dynamic_cast(plugin.get()), nullptr); + EXPECT_NE(dynamic_cast(plugin.get()), nullptr); } TEST_F(FileBasedRequestSourcePluginTest, CreateRequestSourcePluginGetsWorkingRequestGeneratorThatEndsAtNumRequest) { - nighthawk::request_source::FileBasedPluginConfig config = MakeFileBasedPluginConfigWithTestYaml( - TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); - config.mutable_num_requests()->set_value(2); + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config = + MakeFileBasedPluginConfigWithTestYaml( + TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); + config.set_num_requests(2); Envoy::ProtobufWkt::Any config_any; config_any.PackFrom(config); auto& config_factory = @@ -142,10 +158,13 @@ TEST_F(FileBasedRequestSourcePluginTest, RequestSourcePtr file_based_request_source = config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); Nighthawk::RequestGenerator generator = file_based_request_source->get(); - Nighthawk::RequestPtr request = generator(); + Nighthawk::RequestPtr request1 = generator(); Nighthawk::RequestPtr request2 = generator(); Nighthawk::RequestPtr request3 = generator(); - Nighthawk::HeaderMapPtr header1 = request->header(); + ASSERT_NE(request1, nullptr); + ASSERT_NE(request2, nullptr); + + Nighthawk::HeaderMapPtr header1 = request1->header(); Nighthawk::HeaderMapPtr header2 = request2->header(); EXPECT_EQ(header1->getPathValue(), "/a"); EXPECT_EQ(header2->getPathValue(), "/b"); @@ -153,10 +172,10 @@ TEST_F(FileBasedRequestSourcePluginTest, } TEST_F(FileBasedRequestSourcePluginTest, - CreateRequestSourcePluginWithMoreNumRequestsThanInFileGetsWorkingRequestGeneratorThatLoops) { - nighthawk::request_source::FileBasedPluginConfig config = MakeFileBasedPluginConfigWithTestYaml( - TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); - config.mutable_num_requests()->set_value(4); + CreateRequestSourcePluginWithMoreNumRequestsThanInFileGetsRequestGeneratorThatLoops) { + nighthawk::request_source::FileBasedOptionsListRequestSourceConfig config = + MakeFileBasedPluginConfigWithTestYaml( + TestEnvironment::runfilesPath("test/request_source/test_data/test-config.yaml")); Envoy::ProtobufWkt::Any config_any; config_any.PackFrom(config); auto& config_factory = @@ -166,10 +185,126 @@ TEST_F(FileBasedRequestSourcePluginTest, RequestSourcePtr file_based_request_source = config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); Nighthawk::RequestGenerator generator = file_based_request_source->get(); - Nighthawk::RequestPtr request = generator(); + Nighthawk::RequestPtr request1 = generator(); + Nighthawk::RequestPtr request2 = generator(); + Nighthawk::RequestPtr request3 = generator(); + ASSERT_NE(request1, nullptr); + ASSERT_NE(request2, nullptr); + ASSERT_NE(request3, nullptr); + + Nighthawk::HeaderMapPtr header1 = request1->header(); + Nighthawk::HeaderMapPtr header2 = request2->header(); + Nighthawk::HeaderMapPtr header3 = request3->header(); + EXPECT_EQ(header1->getPathValue(), "/a"); + EXPECT_EQ(header2->getPathValue(), "/b"); + EXPECT_EQ(header3->getPathValue(), "/a"); +} + +TEST_F(InLineRequestSourcePluginTest, CreateEmptyConfigProtoCreatesCorrectType) { + auto& config_factory = + Envoy::Config::Utility::getAndCheckFactoryByName( + "nighthawk.in-line-options-list-request-source-plugin"); + const Envoy::ProtobufTypes::MessagePtr empty_config = config_factory.createEmptyConfigProto(); + const nighthawk::request_source::InLineOptionsListRequestSourceConfig expected_config; + EXPECT_EQ(empty_config->DebugString(), expected_config.DebugString()); + EXPECT_TRUE(Envoy::MessageUtil()(*empty_config, expected_config)); +} + +TEST_F(InLineRequestSourcePluginTest, FactoryRegistrationUsesCorrectPluginName) { + nighthawk::request_source::InLineOptionsListRequestSourceConfig config; + Envoy::ProtobufWkt::Any config_any; + config_any.PackFrom(config); + auto& config_factory = + Envoy::Config::Utility::getAndCheckFactoryByName( + "nighthawk.in-line-options-list-request-source-plugin"); + EXPECT_EQ(config_factory.name(), "nighthawk.in-line-options-list-request-source-plugin"); +} + +TEST_F(InLineRequestSourcePluginTest, CreateRequestSourcePluginCreatesCorrectPluginType) { + Envoy::MessageUtil util; + nighthawk::client::RequestOptionsList options_list; + util.loadFromFile(/*file to load*/ TestEnvironment::runfilesPath( + "test/request_source/test_data/test-config.yaml"), + /*out parameter*/ options_list, + /*validation visitor*/ Envoy::ProtobufMessage::getStrictValidationVisitor(), + /*Api*/ *api_, + /*use api boosting*/ true); + nighthawk::request_source::InLineOptionsListRequestSourceConfig config = + MakeInLinePluginConfig(options_list, /*num_requests*/ 2); + Envoy::ProtobufWkt::Any config_any; + config_any.PackFrom(config); + auto& config_factory = + Envoy::Config::Utility::getAndCheckFactoryByName( + "nighthawk.in-line-options-list-request-source-plugin"); + auto header = Envoy::Http::RequestHeaderMapImpl::create(); + RequestSourcePtr plugin = + config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); + EXPECT_NE(dynamic_cast(plugin.get()), nullptr); +} + +TEST_F(InLineRequestSourcePluginTest, + CreateRequestSourcePluginGetsWorkingRequestGeneratorThatEndsAtNumRequest) { + Envoy::MessageUtil util; + nighthawk::client::RequestOptionsList options_list; + util.loadFromFile(/*file to load*/ TestEnvironment::runfilesPath( + "test/request_source/test_data/test-config.yaml"), + /*out parameter*/ options_list, + /*validation visitor*/ Envoy::ProtobufMessage::getStrictValidationVisitor(), + /*Api*/ *api_, + /*use api boosting*/ true); + nighthawk::request_source::InLineOptionsListRequestSourceConfig config = + MakeInLinePluginConfig(options_list, /*num_requests*/ 2); + Envoy::ProtobufWkt::Any config_any; + config_any.PackFrom(config); + auto& config_factory = + Envoy::Config::Utility::getAndCheckFactoryByName( + "nighthawk.in-line-options-list-request-source-plugin"); + auto header = Envoy::Http::RequestHeaderMapImpl::create(); + RequestSourcePtr plugin = + config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); + Nighthawk::RequestGenerator generator = plugin->get(); + Nighthawk::RequestPtr request1 = generator(); + Nighthawk::RequestPtr request2 = generator(); + Nighthawk::RequestPtr request3 = generator(); + ASSERT_NE(request1, nullptr); + ASSERT_NE(request2, nullptr); + + Nighthawk::HeaderMapPtr header1 = request1->header(); + Nighthawk::HeaderMapPtr header2 = request2->header(); + EXPECT_EQ(header1->getPathValue(), "/a"); + EXPECT_EQ(header2->getPathValue(), "/b"); + EXPECT_EQ(request3, nullptr); +} + +TEST_F(InLineRequestSourcePluginTest, + CreateRequestSourcePluginWithMoreNumRequestsThanInListGetsRequestGeneratorThatLoops) { + Envoy::MessageUtil util; + nighthawk::client::RequestOptionsList options_list; + util.loadFromFile(/*file to load*/ TestEnvironment::runfilesPath( + "test/request_source/test_data/test-config.yaml"), + /*out parameter*/ options_list, + /*validation visitor*/ Envoy::ProtobufMessage::getStrictValidationVisitor(), + /*Api*/ *api_, + /*use api boosting*/ true); + nighthawk::request_source::InLineOptionsListRequestSourceConfig config = + MakeInLinePluginConfig(options_list, /*num_requests*/ 4); + Envoy::ProtobufWkt::Any config_any; + config_any.PackFrom(config); + auto& config_factory = + Envoy::Config::Utility::getAndCheckFactoryByName( + "nighthawk.in-line-options-list-request-source-plugin"); + auto header = Envoy::Http::RequestHeaderMapImpl::create(); + RequestSourcePtr plugin = + config_factory.createRequestSourcePlugin(config_any, *api_, std::move(header)); + Nighthawk::RequestGenerator generator = plugin->get(); + Nighthawk::RequestPtr request1 = generator(); Nighthawk::RequestPtr request2 = generator(); Nighthawk::RequestPtr request3 = generator(); - Nighthawk::HeaderMapPtr header1 = request->header(); + ASSERT_NE(request1, nullptr); + ASSERT_NE(request2, nullptr); + ASSERT_NE(request3, nullptr); + + Nighthawk::HeaderMapPtr header1 = request1->header(); Nighthawk::HeaderMapPtr header2 = request2->header(); Nighthawk::HeaderMapPtr header3 = request3->header(); EXPECT_EQ(header1->getPathValue(), "/a");