This is an automated email from the ASF dual-hosted git repository. bcall pushed a commit to branch unit-test-coverage in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit a993b11aabffec7add2b8b949489540edb72aa1d Author: Bryan Call <[email protected]> AuthorDate: Tue Jan 20 19:32:43 2026 -0800 Add mock framework for unit testing (MockNetVC, MockCacheVC) Add test utilities library with mock objects for unit testing: - MockNetVC: Mock NetVConnection for testing network code - MockCacheVC: Mock CacheVConnection for testing cache code - MockIOBuffer: Helper for creating/managing IOBuffers in tests - TestEventProcessor: Controllable event processor for tests This is part of the unit test coverage improvement initiative. --- src/test_utils/CMakeLists.txt | 3 + src/test_utils/MockCacheVC.cc | 322 ++++++++++++++++++++++++++++++++++++ src/test_utils/MockCacheVC.h | 155 +++++++++++++++++ src/test_utils/MockNetVC.cc | 376 ++++++++++++++++++++++++++++++++++++++++++ src/test_utils/MockNetVC.h | 176 ++++++++++++++++++++ 5 files changed, 1032 insertions(+) diff --git a/src/test_utils/CMakeLists.txt b/src/test_utils/CMakeLists.txt index 339994e283..7ace822e9d 100644 --- a/src/test_utils/CMakeLists.txt +++ b/src/test_utils/CMakeLists.txt @@ -21,6 +21,8 @@ add_library( ats_test_utils STATIC MockIOBuffer.cc + MockNetVC.cc + MockCacheVC.cc TestEventProcessor.cc ) @@ -33,6 +35,7 @@ target_include_directories( target_link_libraries( ats_test_utils PUBLIC ts::inkevent + ts::inknet ts::tscore ) diff --git a/src/test_utils/MockCacheVC.cc b/src/test_utils/MockCacheVC.cc new file mode 100644 index 0000000000..af7b6c945b --- /dev/null +++ b/src/test_utils/MockCacheVC.cc @@ -0,0 +1,322 @@ +/** @file + + Mock CacheVConnection implementation for unit testing + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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_utils/MockCacheVC.h" +#include "tscore/ink_memory.h" +#include <cstring> + +MockCacheVC::MockCacheVC() +{ + // Initialize read buffer for simulating cached data + read_buffer_ = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + read_buffer_reader_ = read_buffer_->alloc_reader(); + + // Initialize write capture buffer + write_capture_ = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + write_capture_reader_ = write_capture_->alloc_reader(); +} + +MockCacheVC::~MockCacheVC() +{ + if (read_buffer_) { + free_MIOBuffer(read_buffer_); + } + if (write_capture_) { + free_MIOBuffer(write_capture_); + } + if (header_) { + ats_free(header_); + } +} + +// ============== Test Setup Methods ============== + +void +MockCacheVC::set_read_data(std::string_view data) +{ + // Clear existing data + read_buffer_reader_->consume(read_buffer_reader_->read_avail()); + // Write new data + read_buffer_->write(data.data(), data.size()); + object_size_ = data.size(); +} + +std::string +MockCacheVC::get_written_data() const +{ + std::string result; + int64_t avail = write_capture_reader_->read_avail(); + if (avail > 0) { + result.resize(avail); + IOBufferBlock *block = write_capture_reader_->get_current_block(); + int64_t offset = write_capture_reader_->start_offset; + size_t pos = 0; + + while (block && pos < static_cast<size_t>(avail)) { + const char *start = block->start() + offset; + int64_t len = block->read_avail() - offset; + if (len > 0) { + if (pos + len > static_cast<size_t>(avail)) { + len = avail - pos; + } + memcpy(&result[pos], start, len); + pos += len; + } + block = block->next.get(); + offset = 0; + } + } + return result; +} + +void +MockCacheVC::clear_written_data() +{ + write_capture_reader_->consume(write_capture_reader_->read_avail()); +} + +void +MockCacheVC::set_http_info_for_test(CacheHTTPInfo *info) +{ + http_info_ = info; +} + +void +MockCacheVC::set_ram_cache_hit(bool is_hit) +{ + ram_cache_hit_ = is_hit; +} + +void +MockCacheVC::set_object_size(int64_t size) +{ + object_size_ = size; +} + +void +MockCacheVC::simulate_cache_miss() +{ + if (read_vio_.cont) { + read_vio_.cont->handleEvent(CACHE_EVENT_OPEN_READ_FAILED, this); + } +} + +void +MockCacheVC::simulate_error(int lerrno) +{ + if (read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_ERROR, this); + } + close_error_ = lerrno; +} + +// ============== CacheVConnection Interface Implementation ============== + +VIO * +MockCacheVC::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + read_vio_.op = VIO::READ; + read_vio_.cont = c; + read_vio_.nbytes = nbytes; + read_vio_.ndone = 0; + read_vio_.buffer.writer_for(buf); + read_vio_.vc_server = this; + + // Copy available data from our read_buffer to the provided buffer + int64_t avail = read_buffer_reader_->read_avail(); + if (avail > 0) { + int64_t to_copy = (nbytes == INT64_MAX) ? avail : std::min(avail, nbytes); + buf->write(read_buffer_reader_, to_copy); + read_buffer_reader_->consume(to_copy); + read_vio_.ndone = to_copy; + } + + return &read_vio_; +} + +VIO * +MockCacheVC::do_io_pread(Continuation *c, int64_t nbytes, MIOBuffer *buf, int64_t offset) +{ + // For simplicity, pread behaves like read but skips 'offset' bytes first + if (offset > 0) { + int64_t avail = read_buffer_reader_->read_avail(); + if (offset < avail) { + read_buffer_reader_->consume(offset); + } else { + read_buffer_reader_->consume(avail); + } + } + return do_io_read(c, nbytes, buf); +} + +VIO * +MockCacheVC::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool /* owner ATS_UNUSED */) +{ + write_vio_.op = VIO::WRITE; + write_vio_.cont = c; + write_vio_.nbytes = nbytes; + write_vio_.ndone = 0; + write_vio_.buffer.reader_for(buf); + write_vio_.vc_server = this; + + // Capture any data that's already available + int64_t avail = buf->read_avail(); + if (avail > 0) { + int64_t to_copy = (nbytes == INT64_MAX) ? avail : std::min(avail, nbytes); + write_capture_->write(buf, to_copy); + buf->consume(to_copy); + write_vio_.ndone = to_copy; + } + + return &write_vio_; +} + +void +MockCacheVC::do_io_close(int lerrno) +{ + closed_ = true; + close_error_ = lerrno; + read_vio_.op = VIO::NONE; + write_vio_.op = VIO::NONE; +} + +void +MockCacheVC::reenable(VIO *vio) +{ + if (vio == &read_vio_ && read_vio_.op == VIO::READ) { + int64_t avail = read_buffer_reader_->read_avail(); + if (avail > 0 && read_vio_.cont) { + int64_t remaining = read_vio_.nbytes - read_vio_.ndone; + int64_t to_copy = (remaining == INT64_MAX) ? avail : std::min(avail, remaining); + if (to_copy > 0) { + read_vio_.buffer.writer()->write(read_buffer_reader_, to_copy); + read_buffer_reader_->consume(to_copy); + read_vio_.ndone += to_copy; + read_vio_.cont->handleEvent(VC_EVENT_READ_READY, &read_vio_); + } + } + } else if (vio == &write_vio_ && write_vio_.op == VIO::WRITE) { + IOBufferReader *reader = write_vio_.buffer.reader(); + if (reader) { + int64_t avail = reader->read_avail(); + int64_t remaining = write_vio_.nbytes - write_vio_.ndone; + int64_t to_copy = (remaining == INT64_MAX) ? avail : std::min(avail, remaining); + if (to_copy > 0) { + write_capture_->write(reader, to_copy); + reader->consume(to_copy); + write_vio_.ndone += to_copy; + if (write_vio_.cont) { + write_vio_.cont->handleEvent(VC_EVENT_WRITE_READY, &write_vio_); + } + } + } + } +} + +void +MockCacheVC::reenable_re(VIO *vio) +{ + reenable(vio); +} + +int +MockCacheVC::get_header(void **ptr, int *len) +{ + if (header_ && header_len_ > 0) { + *ptr = header_; + *len = header_len_; + return 0; + } + return -1; +} + +int +MockCacheVC::set_header(void *ptr, int len) +{ + if (header_) { + ats_free(header_); + } + header_ = ats_malloc(len); + header_len_ = len; + memcpy(header_, ptr, len); + return 0; +} + +int +MockCacheVC::get_single_data(void **ptr, int *len) +{ + // Return available data as a single block + int64_t avail = read_buffer_reader_->read_avail(); + if (avail > 0) { + IOBufferBlock *block = read_buffer_reader_->get_current_block(); + if (block) { + *ptr = const_cast<char *>(block->start() + read_buffer_reader_->start_offset); + *len = static_cast<int>(avail); + return 0; + } + } + return -1; +} + +void +MockCacheVC::set_http_info(CacheHTTPInfo *info) +{ + http_info_ = info; +} + +void +MockCacheVC::get_http_info(CacheHTTPInfo **info) +{ + *info = http_info_; +} + +bool +MockCacheVC::is_ram_cache_hit() const +{ + return ram_cache_hit_; +} + +bool +MockCacheVC::set_pin_in_cache(time_t t) +{ + pin_in_cache_ = t; + return true; +} + +time_t +MockCacheVC::get_pin_in_cache() +{ + return pin_in_cache_; +} + +int64_t +MockCacheVC::get_object_size() +{ + return object_size_; +} + +bool +MockCacheVC::is_pread_capable() +{ + return true; // Mock supports pread +} diff --git a/src/test_utils/MockCacheVC.h b/src/test_utils/MockCacheVC.h new file mode 100644 index 0000000000..94ef3fffc1 --- /dev/null +++ b/src/test_utils/MockCacheVC.h @@ -0,0 +1,155 @@ +/** @file + + Mock CacheVConnection for unit testing + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +*/ + +#pragma once + +#include "iocore/cache/Cache.h" +#include "iocore/eventsystem/IOBuffer.h" +#include <string> +#include <string_view> + +/** + * @class MockCacheVC + * @brief Mock CacheVConnection for unit testing + * + * This class provides a testable mock for CacheVConnection that allows: + * - Simulating cache reads (what would be read from cache) + * - Capturing cache writes (what would be written to cache) + * - Simulating cache miss/hit scenarios + * - Testing HTTP header handling + * + * Usage: + * @code + * MockCacheVC mock_cache; + * mock_cache.set_read_data("cached content"); + * mock_cache.set_http_info(&cached_http_info); + * // ... use mock_cache in test ... + * std::string written = mock_cache.get_written_data(); + * @endcode + */ +class MockCacheVC : public CacheVConnection +{ +public: + MockCacheVC(); + ~MockCacheVC() override; + + // ============== Test Setup Methods ============== + + /** + * Set the data that will be available for reading (cache hit scenario) + * @param data Data to be read from cache + */ + void set_read_data(std::string_view data); + + /** + * Get data that has been written to cache + * @return All data written to this mock cache + */ + std::string get_written_data() const; + + /** + * Clear the write buffer (reset captured writes) + */ + void clear_written_data(); + + /** + * Set the HTTP info for this cache entry + * @param info HTTP info to associate with this cache entry + */ + void set_http_info_for_test(CacheHTTPInfo *info); + + /** + * Set whether this is a RAM cache hit + * @param is_hit true if this should simulate a RAM cache hit + */ + void set_ram_cache_hit(bool is_hit); + + /** + * Set the object size + * @param size Size of the cached object + */ + void set_object_size(int64_t size); + + /** + * Simulate a cache miss (sends CACHE_EVENT_OPEN_READ_FAILED) + */ + void simulate_cache_miss(); + + /** + * Simulate a cache error + * @param lerrno Error number + */ + void simulate_error(int lerrno); + + // ============== CacheVConnection Interface Implementation ============== + + VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override; + VIO *do_io_pread(Continuation *c, int64_t nbytes, MIOBuffer *buf, int64_t offset) override; + VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void reenable(VIO *vio) override; + void reenable_re(VIO *vio) override; + + int get_header(void **ptr, int *len) override; + int set_header(void *ptr, int len) override; + int get_single_data(void **ptr, int *len) override; + + void set_http_info(CacheHTTPInfo *info) override; + void get_http_info(CacheHTTPInfo **info) override; + + bool is_ram_cache_hit() const override; + bool set_pin_in_cache(time_t t) override; + time_t get_pin_in_cache() override; + int64_t get_object_size() override; + bool is_pread_capable() override; + + // ============== Test State Access ============== + + bool is_closed() const { return closed_; } + int get_close_error() const { return close_error_; } + +private: + // Read side + MIOBuffer *read_buffer_ = nullptr; + IOBufferReader *read_buffer_reader_ = nullptr; + VIO read_vio_; + + // Write side + MIOBuffer *write_capture_ = nullptr; + IOBufferReader *write_capture_reader_ = nullptr; + VIO write_vio_; + + // Header storage + void *header_ = nullptr; + int header_len_ = 0; + + // HTTP info + CacheHTTPInfo *http_info_ = nullptr; + + // State + bool closed_ = false; + int close_error_ = 0; + bool ram_cache_hit_ = false; + time_t pin_in_cache_ = 0; + int64_t object_size_ = 0; +}; diff --git a/src/test_utils/MockNetVC.cc b/src/test_utils/MockNetVC.cc new file mode 100644 index 0000000000..145bbfe750 --- /dev/null +++ b/src/test_utils/MockNetVC.cc @@ -0,0 +1,376 @@ +/** @file + + Mock NetVConnection implementation for unit testing + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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_utils/MockNetVC.h" +#include "tscore/ink_memory.h" + +MockNetVC::MockNetVC() +{ + // Initialize read buffer for simulating incoming data + read_buffer_ = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + read_buffer_reader_ = read_buffer_->alloc_reader(); + + // Initialize write capture buffer + write_capture_ = new_MIOBuffer(BUFFER_SIZE_INDEX_4K); + write_capture_reader_ = write_capture_->alloc_reader(); +} + +MockNetVC::~MockNetVC() +{ + if (read_buffer_) { + free_MIOBuffer(read_buffer_); + } + if (write_capture_) { + free_MIOBuffer(write_capture_); + } +} + +// ============== Test Setup Methods ============== + +void +MockNetVC::set_read_data(std::string_view data) +{ + // Clear existing data + read_buffer_reader_->consume(read_buffer_reader_->read_avail()); + // Write new data + read_buffer_->write(data.data(), data.size()); +} + +void +MockNetVC::append_read_data(std::string_view data) +{ + read_buffer_->write(data.data(), data.size()); +} + +std::string +MockNetVC::get_written_data() const +{ + std::string result; + int64_t avail = write_capture_reader_->read_avail(); + if (avail > 0) { + result.resize(avail); + // We need to read without consuming, so copy block by block + IOBufferBlock *block = write_capture_reader_->get_current_block(); + int64_t offset = write_capture_reader_->start_offset; + size_t pos = 0; + + while (block && pos < static_cast<size_t>(avail)) { + const char *start = block->start() + offset; + int64_t len = block->read_avail() - offset; + if (len > 0) { + if (pos + len > static_cast<size_t>(avail)) { + len = avail - pos; + } + memcpy(&result[pos], start, len); + pos += len; + } + block = block->next.get(); + offset = 0; + } + } + return result; +} + +void +MockNetVC::clear_written_data() +{ + write_capture_reader_->consume(write_capture_reader_->read_avail()); +} + +void +MockNetVC::simulate_eos() +{ + eos_ = true; + if (read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_EOS, &read_vio_); + } +} + +void +MockNetVC::simulate_error(int lerrno) +{ + error_ = lerrno; + if (read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_ERROR, &read_vio_); + } +} + +void +MockNetVC::simulate_inactivity_timeout() +{ + if (read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_INACTIVITY_TIMEOUT, &read_vio_); + } +} + +void +MockNetVC::simulate_active_timeout() +{ + if (read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_ACTIVE_TIMEOUT, &read_vio_); + } +} + +void +MockNetVC::set_local_address(const sockaddr *addr) +{ + ats_ip_copy(&local_addr, addr); + got_local_addr = true; +} + +void +MockNetVC::set_remote_address(const sockaddr *addr) +{ + ats_ip_copy(&remote_addr, addr); + got_remote_addr = true; +} + +// ============== NetVConnection Interface Implementation ============== + +VIO * +MockNetVC::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) +{ + read_vio_.op = VIO::READ; + read_vio_.cont = c; + read_vio_.nbytes = nbytes; + read_vio_.ndone = 0; + read_vio_.buffer.writer_for(buf); + read_vio_.vc_server = this; + + // Copy available data from our read_buffer to the provided buffer + int64_t avail = read_buffer_reader_->read_avail(); + if (avail > 0) { + int64_t to_copy = (nbytes == INT64_MAX) ? avail : std::min(avail, nbytes); + buf->write(read_buffer_reader_, to_copy); + read_buffer_reader_->consume(to_copy); + read_vio_.ndone = to_copy; + } + + return &read_vio_; +} + +VIO * +MockNetVC::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool /* owner ATS_UNUSED */) +{ + write_vio_.op = VIO::WRITE; + write_vio_.cont = c; + write_vio_.nbytes = nbytes; + write_vio_.ndone = 0; + write_vio_.buffer.reader_for(buf); + write_vio_.vc_server = this; + + // Capture any data that's already available + int64_t avail = buf->read_avail(); + if (avail > 0) { + int64_t to_copy = (nbytes == INT64_MAX) ? avail : std::min(avail, nbytes); + write_capture_->write(buf, to_copy); + buf->consume(to_copy); + write_vio_.ndone = to_copy; + } + + return &write_vio_; +} + +void +MockNetVC::do_io_close(int lerrno) +{ + closed_ = true; + close_error_ = lerrno; + read_vio_.op = VIO::NONE; + write_vio_.op = VIO::NONE; +} + +void +MockNetVC::do_io_shutdown(ShutdownHowTo_t howto) +{ + switch (howto) { + case IO_SHUTDOWN_READ: + read_shutdown_ = true; + read_vio_.op = VIO::NONE; + break; + case IO_SHUTDOWN_WRITE: + write_shutdown_ = true; + write_vio_.op = VIO::NONE; + break; + case IO_SHUTDOWN_READWRITE: + read_shutdown_ = true; + write_shutdown_ = true; + read_vio_.op = VIO::NONE; + write_vio_.op = VIO::NONE; + break; + } +} + +void +MockNetVC::reenable(VIO *vio) +{ + if (vio == &read_vio_ && read_vio_.op == VIO::READ) { + // Check if there's more data to read + int64_t avail = read_buffer_reader_->read_avail(); + if (avail > 0 && read_vio_.cont) { + int64_t remaining = read_vio_.nbytes - read_vio_.ndone; + int64_t to_copy = (remaining == INT64_MAX) ? avail : std::min(avail, remaining); + if (to_copy > 0) { + read_vio_.buffer.writer()->write(read_buffer_reader_, to_copy); + read_buffer_reader_->consume(to_copy); + read_vio_.ndone += to_copy; + read_vio_.cont->handleEvent(VC_EVENT_READ_READY, &read_vio_); + } + } else if (eos_ && read_vio_.cont) { + read_vio_.cont->handleEvent(VC_EVENT_EOS, &read_vio_); + } + } else if (vio == &write_vio_ && write_vio_.op == VIO::WRITE) { + // Capture any newly available write data + IOBufferReader *reader = write_vio_.buffer.reader(); + if (reader) { + int64_t avail = reader->read_avail(); + int64_t remaining = write_vio_.nbytes - write_vio_.ndone; + int64_t to_copy = (remaining == INT64_MAX) ? avail : std::min(avail, remaining); + if (to_copy > 0) { + write_capture_->write(reader, to_copy); + reader->consume(to_copy); + write_vio_.ndone += to_copy; + if (write_vio_.cont) { + write_vio_.cont->handleEvent(VC_EVENT_WRITE_READY, &write_vio_); + } + } + } + } +} + +void +MockNetVC::reenable_re(VIO *vio) +{ + reenable(vio); +} + +void +MockNetVC::set_active_timeout(ink_hrtime timeout_in) +{ + active_timeout_ = timeout_in; +} + +void +MockNetVC::set_inactivity_timeout(ink_hrtime timeout_in) +{ + inactivity_timeout_ = timeout_in; + default_inactivity_timeout_ = false; +} + +void +MockNetVC::set_default_inactivity_timeout(ink_hrtime timeout_in) +{ + inactivity_timeout_ = timeout_in; + default_inactivity_timeout_ = true; +} + +bool +MockNetVC::is_default_inactivity_timeout() +{ + return default_inactivity_timeout_; +} + +void +MockNetVC::cancel_active_timeout() +{ + active_timeout_ = 0; +} + +void +MockNetVC::cancel_inactivity_timeout() +{ + inactivity_timeout_ = 0; +} + +ink_hrtime +MockNetVC::get_active_timeout() +{ + return active_timeout_; +} + +ink_hrtime +MockNetVC::get_inactivity_timeout() +{ + return inactivity_timeout_; +} + +void +MockNetVC::apply_options() +{ + // No-op for mock +} + +SOCKET +MockNetVC::get_socket() +{ + return -1; // Invalid socket for mock +} + +int +MockNetVC::set_tcp_congestion_control(tcp_congestion_control_side /* side ATS_UNUSED */) +{ + return 0; // Success for mock +} + +void +MockNetVC::set_local_addr() +{ + // No-op for mock - use set_local_address() instead +} + +void +MockNetVC::set_remote_addr() +{ + // No-op for mock - use set_remote_address() instead +} + +void +MockNetVC::set_remote_addr(const sockaddr *addr) +{ + set_remote_address(addr); +} + +void +MockNetVC::set_mptcp_state() +{ + // No-op for mock +} + +int +MockNetVC::populate_protocol(std::string_view *results, int n) const +{ + if (n > 0) { + results[0] = "tcp"; + return 1; + } + return 0; +} + +const char * +MockNetVC::protocol_contains(std::string_view tag) const +{ + if (tag == "tcp") { + return "tcp"; + } + return nullptr; +} diff --git a/src/test_utils/MockNetVC.h b/src/test_utils/MockNetVC.h new file mode 100644 index 0000000000..6fb7d0f2d7 --- /dev/null +++ b/src/test_utils/MockNetVC.h @@ -0,0 +1,176 @@ +/** @file + + Mock NetVConnection for unit testing + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +*/ + +#pragma once + +#include "iocore/net/NetVConnection.h" +#include "iocore/eventsystem/IOBuffer.h" +#include <string> +#include <string_view> + +/** + * @class MockNetVC + * @brief Mock NetVConnection for unit testing + * + * This class provides a testable mock for NetVConnection that allows: + * - Simulating incoming data (what would be read from a network connection) + * - Capturing outgoing data (what would be written to a network connection) + * - Simulating network events (EOS, errors, timeouts) + * + * Usage: + * @code + * MockNetVC mock_vc; + * mock_vc.set_read_data("GET / HTTP/1.1\r\n\r\n"); + * // ... use mock_vc in test ... + * std::string written = mock_vc.get_written_data(); + * @endcode + */ +class MockNetVC : public NetVConnection +{ +public: + MockNetVC(); + ~MockNetVC() override; + + // ============== Test Setup Methods ============== + + /** + * Set the data that will be available for reading + * @param data Data to be read from this mock connection + */ + void set_read_data(std::string_view data); + + /** + * Append more data to be read (simulates data arriving later) + * @param data Additional data to be read + */ + void append_read_data(std::string_view data); + + /** + * Get data that has been written to this connection + * @return All data written to this mock connection + */ + std::string get_written_data() const; + + /** + * Clear the write buffer (reset captured writes) + */ + void clear_written_data(); + + /** + * Simulate an end-of-stream condition + */ + void simulate_eos(); + + /** + * Simulate an error condition + * @param lerrno The error number to simulate + */ + void simulate_error(int lerrno); + + /** + * Simulate an inactivity timeout + */ + void simulate_inactivity_timeout(); + + /** + * Simulate an active timeout + */ + void simulate_active_timeout(); + + /** + * Set the local address for this connection + */ + void set_local_address(const sockaddr *addr); + + /** + * Set the remote address for this connection + */ + void set_remote_address(const sockaddr *addr); + + // ============== NetVConnection Interface Implementation ============== + + VIO *do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf) override; + VIO *do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner = false) override; + void do_io_close(int lerrno = -1) override; + void do_io_shutdown(ShutdownHowTo_t howto) override; + + void reenable(VIO *vio) override; + void reenable_re(VIO *vio) override; + + // Timeout methods (store values but don't actually time out) + void set_active_timeout(ink_hrtime timeout_in) override; + void set_inactivity_timeout(ink_hrtime timeout_in) override; + void set_default_inactivity_timeout(ink_hrtime timeout_in) override; + bool is_default_inactivity_timeout() override; + void cancel_active_timeout() override; + void cancel_inactivity_timeout() override; + ink_hrtime get_active_timeout() override; + ink_hrtime get_inactivity_timeout() override; + virtual void apply_options() override; + + // Note: Address methods are not virtual in NetVConnection. + // They use the protected local_addr/remote_addr members directly. + // We set these via set_local_address() and set_remote_address() above. + + // Misc methods + SOCKET get_socket() override; + int set_tcp_congestion_control(tcp_congestion_control_side side) override; + void set_local_addr() override; + void set_remote_addr() override; + void set_remote_addr(const sockaddr *) override; + void set_mptcp_state() override; + + int populate_protocol(std::string_view *results, int n) const override; + const char *protocol_contains(std::string_view tag) const override; + + // ============== Test State Access ============== + + bool is_closed() const { return closed_; } + int get_close_error() const { return close_error_; } + bool is_read_shutdown() const { return read_shutdown_; } + bool is_write_shutdown() const { return write_shutdown_; } + +private: + // Read side + MIOBuffer *read_buffer_ = nullptr; + IOBufferReader *read_buffer_reader_ = nullptr; + VIO read_vio_; + + // Write side + MIOBuffer *write_capture_ = nullptr; + IOBufferReader *write_capture_reader_ = nullptr; + VIO write_vio_; + + // State + bool closed_ = false; + int close_error_ = 0; + bool read_shutdown_ = false; + bool write_shutdown_ = false; + bool eos_ = false; + int error_ = 0; + + // Timeouts + ink_hrtime active_timeout_ = 0; + ink_hrtime inactivity_timeout_ = 0; + bool default_inactivity_timeout_ = false; +};
