228 lines
8.7 KiB
C
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 */
|