blob: 75d6e493d3ee00143bb79aa352fe7805f251cca4 [file] [log] [blame]
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <stdlib.h>
#include <string.h>
#include "subprocess.h"
#include "test_common.h"
#define TEST_STRING "hello world"
#define TEST_STRING_LN TEST_STRING "\n"
static void test_subprocess_output_to_buffer(void)
{
char output_buffer[__builtin_strlen(TEST_STRING_LN)];
struct subprocess_target output = {
.type = TARGET_BUFFER,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
const char *const argv[] = {
"echo", TEST_STRING, NULL
};
TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
"Return value of \"echo 'hello world'\" is 0");
TEST_EQ(memcmp(output_buffer, TEST_STRING_LN, sizeof(output_buffer)), 0,
"Output is \"hello world\\n\"");
TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer),
"The entire output buffer should have been used.");
}
static void test_subprocess_output_to_buffer_null_terminated(void)
{
char output_buffer[__builtin_strlen(TEST_STRING_LN) + 1];
struct subprocess_target output = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
const char *const argv[] = {
"echo", TEST_STRING, NULL
};
TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
"Return value of \"echo 'hello world'\" is 0");
TEST_STR_EQ(output_buffer, TEST_STRING_LN,
"Output is \"hello world\\n\"");
TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer) - 1,
"The entire output buffer should have been used.");
}
#define TEST_STRING_2 "hello\0world!"
static void test_subprocess_input_buffer(void)
{
char input_buffer[sizeof(TEST_STRING_2)];
char output_buffer[20];
char error_buffer[20];
memcpy(input_buffer, TEST_STRING_2, sizeof(input_buffer));
struct subprocess_target input = {
.type = TARGET_BUFFER,
.buffer = {
.buf = input_buffer,
.size = sizeof(input_buffer),
},
};
struct subprocess_target output = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
struct subprocess_target error = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = error_buffer,
.size = sizeof(error_buffer),
},
};
const char *const argv[] = {"cat", NULL};
TEST_EQ(subprocess_run(argv, &input, &output, &error), 0,
"Return value of \"cat\" is 0");
TEST_EQ(memcmp(output_buffer, TEST_STRING_2, sizeof(TEST_STRING_2)),
0, "Output is \"hello\\0world!\"");
TEST_STR_EQ(error_buffer, "", "No output captured on stderr");
TEST_EQ(output.buffer.bytes_consumed, sizeof(TEST_STRING_2),
"Bytes consumed is correct");
TEST_EQ(error.buffer.bytes_consumed, 0, "No bytes used for error");
}
static void test_subprocess_input_null_terminated(void)
{
char input_buffer[20];
char output_buffer[20];
char error_buffer[20];
memcpy(input_buffer, TEST_STRING_2, sizeof(TEST_STRING_2));
struct subprocess_target input = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = input_buffer,
},
};
struct subprocess_target output = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
struct subprocess_target error = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = error_buffer,
.size = sizeof(error_buffer),
},
};
const char *const argv[] = {"cat", NULL};
TEST_EQ(subprocess_run(argv, &input, &output, &error), 0,
"Return value of \"cat\" is 0");
TEST_STR_EQ(output_buffer, "hello", "Output is \"hello\"");
TEST_STR_EQ(error_buffer, "", "No output captured on stderr");
TEST_EQ(output.buffer.bytes_consumed, 5, "5 bytes used");
TEST_EQ(error.buffer.bytes_consumed, 0, "No bytes used for error");
}
static void test_subprocess_small_output_buffer(void)
{
char output_buffer[3];
struct subprocess_target output = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
const char *const argv[] = {
"echo", TEST_STRING, NULL
};
TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
"Return value of \"echo 'hello world'\" is 0");
TEST_STR_EQ(output_buffer, "he",
"Output is \"he\" (truncated to small buffer)");
TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer) - 1,
"The entire output buffer should have been used.");
}
static void test_subprocess_return_code_failure(void)
{
const char *const argv[] = {"false", NULL};
TEST_NEQ(subprocess_run(argv, NULL, NULL, NULL), 0,
"Return value of \"false\" is nonzero");
}
struct cb_ctx {
char buffer[49 * 1024];
char *ptr;
};
static ssize_t input_cb(char *buf, size_t buf_sz, void *data)
{
struct cb_ctx *ctx = (struct cb_ctx *)data;
size_t len = (ctx->buffer + sizeof(ctx->buffer)) - ctx->ptr;
if (len > buf_sz)
len = buf_sz;
memcpy(buf, ctx->ptr, len);
ctx->ptr += len;
return len;
}
static void test_subprocess_input_from_cb(void)
{
struct cb_ctx ctx;
char output_buffer[sizeof(ctx.buffer)];
const char *const argv[] = {"cat", NULL};
/* Initialize the input buffer with some data */
for (size_t i = 0; i < sizeof(ctx.buffer); i++)
ctx.buffer[i] = (char)i;
ctx.ptr = ctx.buffer;
struct subprocess_target output = {
.type = TARGET_BUFFER,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
struct subprocess_target input = {
.type = TARGET_CALLBACK,
.callback = {
.cb = input_cb,
.data = &ctx,
},
};
TEST_EQ(subprocess_run(argv, &input, &output, NULL), 0,
"Return value of \"cat\" is zero.");
TEST_EQ(memcmp(ctx.buffer, output_buffer, sizeof(output_buffer)), 0,
"The input buffer is equal to the output buffer.");
TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer),
"The entire output buffer should have been used.");
}
static ssize_t output_cb(char *buf, size_t buf_sz, void *data)
{
struct cb_ctx *ctx = (struct cb_ctx *)data;
if (ctx->ptr + buf_sz > ctx->buffer + sizeof(ctx->buffer)) {
TEST_TRUE(0, "Test failed as there is not enough space in the "
"output buffer.");
return -1;
}
memcpy(ctx->ptr, buf, buf_sz);
ctx->ptr += buf_sz;
return 0;
}
static void test_subprocess_output_to_cb(void)
{
struct cb_ctx ctx;
char output_buffer[sizeof(ctx.buffer)];
const char *const argv[] = {
"bc", "-l", NULL
};
ctx.ptr = ctx.buffer;
struct subprocess_target input = {
.type = TARGET_BUFFER_NULL_TERMINATED,
.buffer = {
.buf = (char *)"for (i = 0; i <= 10000; i += 1) i\n",
},
};
struct subprocess_target target_via_buffer = {
.type = TARGET_BUFFER,
.buffer = {
.buf = output_buffer,
.size = sizeof(output_buffer),
},
};
struct subprocess_target target_via_cb = {
.type = TARGET_CALLBACK,
.callback = {
.cb = output_cb,
.data = &ctx,
},
};
TEST_EQ(subprocess_run(argv, &input, &target_via_buffer, NULL), 0,
"Return value is zero when using buffer.");
TEST_EQ(subprocess_run(argv, &input, &target_via_cb, NULL), 0,
"Return value is zero when using callback.");
TEST_EQ(ctx.ptr - ctx.buffer, target_via_buffer.buffer.bytes_consumed,
"Both commmand invocations used the same number of bytes.");
TEST_EQ(memcmp(output_buffer, ctx.buffer,
target_via_buffer.buffer.bytes_consumed),
0, "Both output buffers are equivalent.");
}
int main(int argc, char *argv[])
{
test_subprocess_output_to_buffer();
test_subprocess_output_to_buffer_null_terminated();
test_subprocess_input_buffer();
test_subprocess_input_null_terminated();
test_subprocess_small_output_buffer();
test_subprocess_return_code_failure();
test_subprocess_input_from_cb();
test_subprocess_output_to_cb();
return gTestSuccess ? 0 : 255;
}