mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
test(esp_http_client): extend mock transport with async, EAGAIN injection, response queue
This commit is contained in:
+126
@@ -22,10 +22,20 @@ typedef struct {
|
||||
size_t read_offset; /*!< Current position in response data */
|
||||
size_t bytes_processed; /*!< Bytes processed (for error injection) */
|
||||
char *response_buffer; /*!< Internal copy of response data */
|
||||
int async_polls_left; /*!< Remaining "in progress" returns from mock_connect_async() */
|
||||
int wb_reads_left; /*!< Remaining EAGAIN injections for mock_read() */
|
||||
int wb_writes_left; /*!< Remaining EAGAIN injections for mock_write() */
|
||||
const char *resp_queue[8]; /*!< FIFO of queued response pointers (caller-owned) */
|
||||
size_t resp_queue_len[8]; /*!< Lengths matching resp_queue entries */
|
||||
int resp_queue_count; /*!< Number of entries queued */
|
||||
int resp_queue_next; /*!< Index of the next entry to pop */
|
||||
char req_capture[2048]; /*!< Bytes written since the last request boundary */
|
||||
size_t req_capture_len; /*!< Number of valid bytes in req_capture */
|
||||
} mock_http_transport_ctx_t;
|
||||
|
||||
// Forward declarations of transport function implementations
|
||||
static int mock_connect(esp_transport_handle_t t, const char *host, int port, int timeout_ms);
|
||||
static int mock_connect_async(esp_transport_handle_t t, const char *host, int port, int timeout_ms);
|
||||
static int mock_read(esp_transport_handle_t t, char *buffer, int len, int timeout_ms);
|
||||
static int mock_write(esp_transport_handle_t t, const char *buffer, int len, int timeout_ms);
|
||||
static int mock_close(esp_transport_handle_t t);
|
||||
@@ -99,6 +109,45 @@ static int mock_connect(esp_transport_handle_t t, const char *host, int port, in
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mock async connect implementation
|
||||
*
|
||||
* Simulates a non-blocking connect(): returns "in progress" for
|
||||
* `async_connect_polls` calls, then reports success. Honors
|
||||
* MOCK_TRANSPORT_MODE_CONNECT_FAIL to simulate an async connect failure.
|
||||
*/
|
||||
static int mock_connect_async(esp_transport_handle_t t, const char *host, int port, int timeout_ms)
|
||||
{
|
||||
mock_http_transport_ctx_t *ctx = esp_transport_get_context_data(t);
|
||||
if (!ctx) {
|
||||
ESP_LOGE(TAG, "Invalid transport context");
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->config.track_calls) {
|
||||
ctx->stats.connect_calls++;
|
||||
}
|
||||
|
||||
if (ctx->config.mode == MOCK_TRANSPORT_MODE_CONNECT_FAIL) {
|
||||
ESP_LOGD(TAG, "Mock connect_async failed (simulated)");
|
||||
return -1; /* ASYNC_TRANS_CONNECT_FAIL */
|
||||
}
|
||||
|
||||
if (ctx->async_polls_left > 0) {
|
||||
ctx->async_polls_left--;
|
||||
ESP_LOGD(TAG, "Mock connect_async: still connecting (%d polls left)", ctx->async_polls_left);
|
||||
return 0; /* ASYNC_TRANS_CONNECTING */
|
||||
}
|
||||
|
||||
ctx->is_connected = true;
|
||||
ctx->read_offset = 0;
|
||||
ctx->bytes_processed = 0;
|
||||
|
||||
ESP_LOGI(TAG, "Mock connect_async succeeded");
|
||||
return 1; /* ASYNC_TRANS_CONNECT_PASS */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mock read implementation
|
||||
*/
|
||||
@@ -110,6 +159,14 @@ static int mock_read(esp_transport_handle_t t, char *buffer, int len, int timeou
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Would-block injection: simulate a non-blocking socket returning EAGAIN
|
||||
if (ctx->wb_reads_left > 0) {
|
||||
ctx->wb_reads_left--;
|
||||
ESP_LOGD(TAG, "Mock read: EAGAIN (simulated would-block, %d left)", ctx->wb_reads_left);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->config.track_calls) {
|
||||
ctx->stats.read_calls++;
|
||||
}
|
||||
@@ -190,6 +247,14 @@ static int mock_write(esp_transport_handle_t t, const char *buffer, int len, int
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Would-block injection: simulate a non-blocking socket returning EAGAIN
|
||||
if (ctx->wb_writes_left > 0) {
|
||||
ctx->wb_writes_left--;
|
||||
ESP_LOGD(TAG, "Mock write: EAGAIN (simulated would-block, %d left)", ctx->wb_writes_left);
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->config.track_calls) {
|
||||
ctx->stats.write_calls++;
|
||||
}
|
||||
@@ -229,6 +294,24 @@ static int mock_write(esp_transport_handle_t t, const char *buffer, int len, int
|
||||
}
|
||||
}
|
||||
|
||||
// Request boundary: previous response fully consumed and a new request starts.
|
||||
// Pop the next queued response into the active buffer via set_response()
|
||||
// (which owns free/realloc + read_offset reset) and start a fresh capture.
|
||||
if (ctx->resp_queue_next < ctx->resp_queue_count &&
|
||||
ctx->response_buffer && ctx->read_offset >= ctx->config.response_len) {
|
||||
mock_http_transport_set_response(t, ctx->resp_queue[ctx->resp_queue_next],
|
||||
ctx->resp_queue_len[ctx->resp_queue_next]);
|
||||
ctx->resp_queue_next++;
|
||||
ctx->req_capture_len = 0;
|
||||
}
|
||||
|
||||
// Capture written bytes (request content) for test assertions, capped to
|
||||
// avoid overflowing the fixed-size buffer.
|
||||
size_t space = sizeof(ctx->req_capture) - ctx->req_capture_len;
|
||||
size_t copy = (size_t)len < space ? (size_t)len : space;
|
||||
memcpy(ctx->req_capture + ctx->req_capture_len, buffer, copy);
|
||||
ctx->req_capture_len += copy;
|
||||
|
||||
// Normal write (just track it, don't actually store)
|
||||
ctx->bytes_processed += len;
|
||||
|
||||
@@ -388,6 +471,11 @@ esp_transport_handle_t mock_http_transport_create(const mock_http_transport_conf
|
||||
ctx->config.track_calls = true;
|
||||
}
|
||||
|
||||
// Initialize error-injection countdown counters from config
|
||||
ctx->async_polls_left = ctx->config.async_connect_polls;
|
||||
ctx->wb_reads_left = ctx->config.would_block_reads;
|
||||
ctx->wb_writes_left = ctx->config.would_block_writes;
|
||||
|
||||
// Set context
|
||||
esp_transport_set_context_data(transport, ctx);
|
||||
|
||||
@@ -400,6 +488,7 @@ esp_transport_handle_t mock_http_transport_create(const mock_http_transport_conf
|
||||
mock_poll_read,
|
||||
mock_poll_write,
|
||||
mock_destroy);
|
||||
esp_transport_set_async_connect_func(transport, mock_connect_async);
|
||||
|
||||
ESP_LOGI(TAG, "Mock HTTP transport created (mode=%d)", ctx->config.mode);
|
||||
return transport;
|
||||
@@ -452,6 +541,11 @@ esp_err_t mock_http_transport_set_config(esp_transport_handle_t transport,
|
||||
ctx->read_offset = 0;
|
||||
ctx->bytes_processed = 0;
|
||||
|
||||
// Re-initialize error-injection countdown counters from the new config
|
||||
ctx->async_polls_left = ctx->config.async_connect_polls;
|
||||
ctx->wb_reads_left = ctx->config.would_block_reads;
|
||||
ctx->wb_writes_left = ctx->config.would_block_writes;
|
||||
|
||||
ESP_LOGD(TAG, "Mock transport config updated (mode=%d)", ctx->config.mode);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -527,3 +621,35 @@ esp_err_t mock_http_transport_set_response(esp_transport_handle_t transport,
|
||||
ESP_LOGD(TAG, "Mock transport response updated (%zu bytes)", ctx->config.response_len);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mock_http_transport_queue_response(esp_transport_handle_t t,
|
||||
const char *data, size_t len)
|
||||
{
|
||||
mock_http_transport_ctx_t *ctx = esp_transport_get_context_data(t);
|
||||
if (!ctx || ctx->resp_queue_count >= 8 || !data) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Queue entries are caller-owned (e.g. string literals); store the pointer only.
|
||||
ctx->resp_queue[ctx->resp_queue_count] = data;
|
||||
ctx->resp_queue_len[ctx->resp_queue_count] = len > 0 ? len : strlen(data);
|
||||
ctx->resp_queue_count++;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mock_http_transport_get_last_request(esp_transport_handle_t t,
|
||||
char *buf, size_t buf_len, size_t *out_len)
|
||||
{
|
||||
mock_http_transport_ctx_t *ctx = esp_transport_get_context_data(t);
|
||||
if (!ctx || !buf || buf_len == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t n = ctx->req_capture_len < buf_len - 1 ? ctx->req_capture_len : buf_len - 1;
|
||||
memcpy(buf, ctx->req_capture, n);
|
||||
buf[n] = '\0';
|
||||
if (out_len) {
|
||||
*out_len = n;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ typedef struct {
|
||||
int bytes_before_error; /*!< Number of bytes to process before injecting error (-1 = no limit) */
|
||||
int connect_delay_ms; /*!< Delay before connect succeeds (0 = immediate) */
|
||||
bool track_calls; /*!< Enable call tracking for verification */
|
||||
int async_connect_polls; /*!< connect_async returns "in progress" this many times, then succeeds */
|
||||
int would_block_reads; /*!< First N reads return -1 with errno = EAGAIN */
|
||||
int would_block_writes; /*!< First N writes return -1 with errno = EAGAIN */
|
||||
} mock_http_transport_config_t;
|
||||
|
||||
/**
|
||||
@@ -47,6 +50,9 @@ typedef struct {
|
||||
.bytes_before_error = -1, \
|
||||
.connect_delay_ms = 0, \
|
||||
.track_calls = true, \
|
||||
.async_connect_polls = 0, \
|
||||
.would_block_reads = 0, \
|
||||
.would_block_writes = 0, \
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,6 +159,45 @@ esp_err_t mock_http_transport_set_response(esp_transport_handle_t transport,
|
||||
const char *response_data,
|
||||
size_t response_len);
|
||||
|
||||
/**
|
||||
* @brief Queue a response to be served after the next request boundary
|
||||
*
|
||||
* Appends to a FIFO of up to 8 responses. When mock_write() detects a request
|
||||
* boundary (the previously active response has been fully read and a new
|
||||
* write arrives), the next queued response is popped into the active buffer
|
||||
* via mock_http_transport_set_response(). Useful for testing keep-alive
|
||||
* request/response sequences on a single connection.
|
||||
*
|
||||
* @param[in] t Mock transport handle
|
||||
* @param[in] data Response data to queue (caller-owned, must outlive its use - e.g. a string literal)
|
||||
* @param[in] len Length of data (0 = use strlen)
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if t is NULL, data is NULL, or the queue is full (8 entries)
|
||||
*/
|
||||
esp_err_t mock_http_transport_queue_response(esp_transport_handle_t t,
|
||||
const char *data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Retrieve the bytes captured from the most recent request
|
||||
*
|
||||
* mock_write() appends every written byte (capped at 2048 bytes) into an
|
||||
* internal capture buffer, reset at each request boundary. This lets tests
|
||||
* assert on the serialized request content (headers, body).
|
||||
*
|
||||
* @param[in] t Mock transport handle
|
||||
* @param[out] buf Buffer to receive the captured request bytes, NUL-terminated
|
||||
* @param[in] buf_len Size of buf (including space for the NUL terminator)
|
||||
* @param[out] out_len Optional, number of bytes copied (excluding NUL terminator)
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if t is NULL, buf is NULL, or buf_len is 0
|
||||
*/
|
||||
esp_err_t mock_http_transport_get_last_request(esp_transport_handle_t t,
|
||||
char *buf, size_t buf_len, size_t *out_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user