Files
2026-02-28 12:32:28 -05:00

228 lines
8.7 KiB
C

#ifndef AWS_TESTING_STREAM_TESTER_H
#define AWS_TESTING_STREAM_TESTER_H
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/io/stream.h>
#ifndef AWS_UNSTABLE_TESTING_API
# error This code is designed for use by AWS owned libraries for the AWS C99 SDK. \
You are welcome to use it, but we make no promises on the stability of this API. \
To enable use of this code, set the AWS_UNSTABLE_TESTING_API compiler flag.
#endif
/**
* Use aws_input_stream tester to test edge cases in systems that take input streams.
* You can make it behave in specific weird ways (e.g. fail on 3rd read).
*
* There are a few ways to set what gets streamed.
* - source_bytes: if set, stream these bytes.
* - source_stream: if set, wrap this stream (but insert weird behavior like failing on 3rd read).
* - autogen_length: autogen streaming content N bytes in length.
*/
enum aws_autogen_style {
AWS_AUTOGEN_LOREM_IPSUM,
AWS_AUTOGEN_ALPHABET,
AWS_AUTOGEN_NUMBERS,
};
struct aws_input_stream_tester_options {
/* bytes to be streamed.
* the stream copies these to its own internal buffer.
* or you can set the autogen_length */
struct aws_byte_cursor source_bytes;
/* wrap another stream */
struct aws_input_stream *source_stream;
/* if non-zero, autogen streaming content N bytes in length */
size_t autogen_length;
/* style of contents (if using autogen) */
enum aws_autogen_style autogen_style;
/* if non-zero, read at most N bytes per read() */
size_t max_bytes_per_read;
/* if non-zero, read 0 bytes the Nth time read() is called */
size_t read_zero_bytes_on_nth_read;
/* If false, EOF is reported by the read() which produces the last few bytes.
* If true, EOF isn't reported until there's one more read(), producing zero bytes.
* This emulates an underlying stream that reports EOF by reading 0 bytes */
bool eof_requires_extra_read;
/* if non-zero, fail the Nth time read() is called, raising `fail_with_error_code` */
size_t fail_on_nth_read;
/* error-code to raise if failing on purpose */
int fail_with_error_code;
};
struct aws_input_stream_tester {
struct aws_input_stream base;
struct aws_allocator *alloc;
struct aws_input_stream_tester_options options;
struct aws_byte_buf source_buf;
struct aws_input_stream *source_stream;
size_t read_count;
bool num_bytes_last_read; /* number of bytes read in the most recent successful read() */
uint64_t total_bytes_read;
};
static inline int s_input_stream_tester_seek(
struct aws_input_stream *stream,
int64_t offset,
enum aws_stream_seek_basis basis) {
struct aws_input_stream_tester *impl = (struct aws_input_stream_tester *)stream->impl;
return aws_input_stream_seek(impl->source_stream, offset, basis);
}
static inline int s_input_stream_tester_read(struct aws_input_stream *stream, struct aws_byte_buf *original_dest) {
struct aws_input_stream_tester *impl = (struct aws_input_stream_tester *)stream->impl;
impl->read_count++;
/* if we're configured to fail, then do it */
if (impl->read_count == impl->options.fail_on_nth_read) {
AWS_FATAL_ASSERT(impl->options.fail_with_error_code != 0);
return aws_raise_error(impl->options.fail_with_error_code);
}
/* cap how much is read, if that's how we're configured */
size_t bytes_to_read = original_dest->capacity - original_dest->len;
if (impl->options.max_bytes_per_read != 0) {
bytes_to_read = aws_min_size(bytes_to_read, impl->options.max_bytes_per_read);
}
if (impl->read_count == impl->options.read_zero_bytes_on_nth_read) {
bytes_to_read = 0;
}
/* pass artificially capped buffer to actual stream */
struct aws_byte_buf capped_buf =
aws_byte_buf_from_empty_array(original_dest->buffer + original_dest->len, bytes_to_read);
if (aws_input_stream_read(impl->source_stream, &capped_buf)) {
return AWS_OP_ERR;
}
size_t bytes_actually_read = capped_buf.len;
original_dest->len += bytes_actually_read;
impl->num_bytes_last_read = bytes_actually_read;
impl->total_bytes_read += bytes_actually_read;
return AWS_OP_SUCCESS;
}
static inline int s_input_stream_tester_get_status(struct aws_input_stream *stream, struct aws_stream_status *status) {
struct aws_input_stream_tester *impl = (struct aws_input_stream_tester *)stream->impl;
if (aws_input_stream_get_status(impl->source_stream, status)) {
return AWS_OP_ERR;
}
/* if we're emulating a stream that requires an additional 0 byte read to realize it's EOF */
if (impl->options.eof_requires_extra_read) {
if (impl->num_bytes_last_read > 0) {
status->is_end_of_stream = false;
}
}
return AWS_OP_SUCCESS;
}
static inline int s_input_stream_tester_get_length(struct aws_input_stream *stream, int64_t *out_length) {
struct aws_input_stream_tester *impl = (struct aws_input_stream_tester *)stream->impl;
return aws_input_stream_get_length(impl->source_stream, out_length);
}
static struct aws_input_stream_vtable s_input_stream_tester_vtable = {
.seek = s_input_stream_tester_seek,
.read = s_input_stream_tester_read,
.get_status = s_input_stream_tester_get_status,
.get_length = s_input_stream_tester_get_length,
};
/* init byte-buf and fill it autogenned content */
static inline void s_byte_buf_init_autogenned(
struct aws_byte_buf *buf,
struct aws_allocator *alloc,
size_t length,
enum aws_autogen_style style) {
aws_byte_buf_init(buf, alloc, length);
struct aws_byte_cursor pattern = {0};
switch (style) {
case AWS_AUTOGEN_LOREM_IPSUM:
pattern = aws_byte_cursor_from_c_str(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore "
"et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
"aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa "
"qui officia deserunt mollit anim id est laborum. ");
break;
case AWS_AUTOGEN_ALPHABET:
pattern = aws_byte_cursor_from_c_str("abcdefghijklmnopqrstuvwxyz");
break;
case AWS_AUTOGEN_NUMBERS:
pattern = aws_byte_cursor_from_c_str("1234567890");
break;
}
struct aws_byte_cursor pattern_cursor = {0};
while (buf->len < buf->capacity) {
if (pattern_cursor.len == 0) {
pattern_cursor = pattern;
}
aws_byte_buf_write_to_capacity(buf, &pattern_cursor);
}
}
static inline uint64_t aws_input_stream_tester_total_bytes_read(const struct aws_input_stream *stream) {
const struct aws_input_stream_tester *impl = (const struct aws_input_stream_tester *)stream->impl;
return impl->total_bytes_read;
}
static inline void s_input_stream_tester_destroy(void *user_data) {
struct aws_input_stream_tester *impl = (struct aws_input_stream_tester *)user_data;
aws_input_stream_release(impl->source_stream);
aws_byte_buf_clean_up(&impl->source_buf);
aws_mem_release(impl->alloc, impl);
}
static inline struct aws_input_stream *aws_input_stream_new_tester(
struct aws_allocator *alloc,
const struct aws_input_stream_tester_options *options) {
struct aws_input_stream_tester *impl =
(struct aws_input_stream_tester *)aws_mem_calloc(alloc, 1, sizeof(struct aws_input_stream_tester));
impl->base.impl = impl;
impl->base.vtable = &s_input_stream_tester_vtable;
aws_ref_count_init(&impl->base.ref_count, impl, s_input_stream_tester_destroy);
impl->alloc = alloc;
impl->options = *options;
if (options->source_stream != NULL) {
AWS_FATAL_ASSERT((options->autogen_length == 0) && (options->source_bytes.len == 0));
impl->source_stream = aws_input_stream_acquire(options->source_stream);
} else {
if (options->autogen_length > 0) {
AWS_FATAL_ASSERT(options->source_bytes.len == 0);
s_byte_buf_init_autogenned(&impl->source_buf, alloc, options->autogen_length, options->autogen_style);
} else {
aws_byte_buf_init_copy_from_cursor(&impl->source_buf, alloc, options->source_bytes);
}
struct aws_byte_cursor source_buf_cursor = aws_byte_cursor_from_buf(&impl->source_buf);
impl->source_stream = aws_input_stream_new_from_cursor(alloc, &source_buf_cursor);
AWS_FATAL_ASSERT(impl->source_stream);
}
return &impl->base;
}
#endif /* AWS_TESTING_STREAM_TESTER_H */