|
5 | 5 |
|
6 | 6 | #include "source/common/buffer/buffer_impl.h" |
7 | 7 | #include "source/common/http/message_impl.h" |
| 8 | +#include "source/common/network/address_impl.h" |
8 | 9 | #include "source/common/stream_info/stream_info_impl.h" |
9 | 10 |
|
10 | 11 | #include "test/common/stats/stat_test_utility.h" |
|
21 | 22 | #include "test/test_common/utility.h" |
22 | 23 |
|
23 | 24 | #include "absl/strings/str_format.h" |
| 25 | +#include "contrib/golang/common/dso/test/mocks.h" |
24 | 26 | #include "contrib/golang/filters/http/source/golang_filter.h" |
25 | 27 | #include "gmock/gmock.h" |
26 | 28 |
|
27 | 29 | using testing::_; |
28 | 30 | using testing::AtLeast; |
29 | 31 | using testing::InSequence; |
30 | 32 | using testing::Invoke; |
| 33 | +using testing::Return; |
| 34 | +using testing::SaveArg; |
31 | 35 |
|
32 | 36 | namespace Envoy { |
33 | 37 | namespace Extensions { |
34 | 38 | namespace HttpFilters { |
35 | 39 | namespace Golang { |
36 | | -namespace { |
37 | 40 |
|
38 | 41 | class TestFilter : public Filter { |
39 | 42 | public: |
| 43 | + using Filter::continueStatusInternal; |
40 | 44 | using Filter::Filter; |
| 45 | + DecodingProcessorState& testDecodingState() { return decoding_state_; } |
| 46 | + HttpRequestInternal* testReq() { return req_; } |
41 | 47 | }; |
42 | 48 |
|
| 49 | +namespace { |
| 50 | + |
43 | 51 | class GolangHttpFilterTest : public testing::Test { |
44 | 52 | public: |
45 | 53 | GolangHttpFilterTest() { |
@@ -191,6 +199,72 @@ TEST_F(GolangHttpFilterTest, InvalidConfigForRouteConfigFilter) { |
191 | 199 | "golang filter failed to parse plugin config"); |
192 | 200 | } |
193 | 201 |
|
| 202 | +// Regression test for https://github.com/envoyproxy/envoy/issues/44320. |
| 203 | +TEST_F(GolangHttpFilterTest, BufferedDataAfterDestroyDuringContinue) { |
| 204 | + auto dso_lib = std::make_shared<NiceMock<Dso::MockHttpFilterDsoImpl>>(); |
| 205 | + ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1)); |
| 206 | + ON_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _)) |
| 207 | + .WillByDefault(Return(static_cast<uint64_t>(GolangStatus::Running))); |
| 208 | + |
| 209 | + bool destroyed = false; |
| 210 | + bool data_called_after_destroy = false; |
| 211 | + ON_CALL(*dso_lib, envoyGoFilterOnHttpData(_, _, _, _)) |
| 212 | + .WillByDefault(Invoke( |
| 213 | + [&destroyed, &data_called_after_destroy](processState*, GoUint64, GoUint64, GoUint64) { |
| 214 | + if (destroyed) { |
| 215 | + data_called_after_destroy = true; |
| 216 | + } |
| 217 | + return static_cast<uint64_t>(GolangStatus::Continue); |
| 218 | + })); |
| 219 | + ON_CALL(*dso_lib, envoyGoFilterOnHttpDestroy(_, _)) |
| 220 | + .WillByDefault(Invoke([&destroyed](httpRequest*, int) { destroyed = true; })); |
| 221 | + |
| 222 | + const auto yaml = R"EOF( |
| 223 | + library_id: test |
| 224 | + library_path: test |
| 225 | + plugin_name: test |
| 226 | + )EOF"; |
| 227 | + envoy::extensions::filters::http::golang::v3alpha::Config proto_config; |
| 228 | + TestUtility::loadFromYaml(yaml, proto_config); |
| 229 | + NiceMock<Server::Configuration::MockFactoryContext> mock_context; |
| 230 | + auto config = std::make_shared<FilterConfig>(proto_config, dso_lib, "", mock_context); |
| 231 | + config->newGoPluginConfig(); |
| 232 | + |
| 233 | + Network::Address::InstanceConstSharedPtr addr( |
| 234 | + (*Network::Address::PipeInstance::create("/test/test.sock")).release()); |
| 235 | + NiceMock<Http::MockStreamDecoderFilterCallbacks> mock_callbacks; |
| 236 | + NiceMock<Http::MockStreamEncoderFilterCallbacks> mock_enc_callbacks; |
| 237 | + NiceMock<Envoy::Network::MockConnection> mock_connection; |
| 238 | + ON_CALL(mock_callbacks, connection()) |
| 239 | + .WillByDefault(Return(OptRef<const Network::Connection>{mock_connection})); |
| 240 | + mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr); |
| 241 | + mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr); |
| 242 | + EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(true)); |
| 243 | + |
| 244 | + auto filter = std::make_shared<TestFilter>(config, dso_lib, 0); |
| 245 | + filter->setDecoderFilterCallbacks(mock_callbacks); |
| 246 | + filter->setEncoderFilterCallbacks(mock_enc_callbacks); |
| 247 | + |
| 248 | + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; |
| 249 | + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, |
| 250 | + filter->decodeHeaders(request_headers, false)); |
| 251 | + |
| 252 | + Buffer::OwnedImpl body("request body"); |
| 253 | + EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter->decodeData(body, false)); |
| 254 | + |
| 255 | + EXPECT_CALL(mock_callbacks, continueDecoding()).WillOnce(Invoke([&filter]() { |
| 256 | + filter->onDestroy(); |
| 257 | + })); |
| 258 | + |
| 259 | + filter->continueStatusInternal(filter->testDecodingState(), GolangStatus::Continue); |
| 260 | + |
| 261 | + EXPECT_FALSE(data_called_after_destroy) |
| 262 | + << "envoyGoFilterOnHttpData must not be called after onDestroy"; |
| 263 | + |
| 264 | + ASSERT_NE(nullptr, filter->testReq()); |
| 265 | + delete filter->testReq(); |
| 266 | +} |
| 267 | + |
194 | 268 | } // namespace |
195 | 269 | } // namespace Golang |
196 | 270 | } // namespace HttpFilters |
|
0 commit comments