1 /* Copyright 2019 The ChromiumOS Authors
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5
6 #include <stdlib.h>
7 #include <string.h>
8
9 #include "subprocess.h"
10 #include "common/tests.h"
11
12 #define TEST_STRING "hello world"
13 #define TEST_STRING_LN TEST_STRING "\n"
14
test_subprocess_output_to_buffer(void)15 static void test_subprocess_output_to_buffer(void)
16 {
17 char output_buffer[__builtin_strlen(TEST_STRING_LN)];
18
19 struct subprocess_target output = {
20 .type = TARGET_BUFFER,
21 .buffer = {
22 .buf = output_buffer,
23 .size = sizeof(output_buffer),
24 },
25 };
26 const char *const argv[] = {
27 "echo", TEST_STRING, NULL
28 };
29
30 TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
31 "Return value of \"echo 'hello world'\" is 0");
32 TEST_EQ(memcmp(output_buffer, TEST_STRING_LN, sizeof(output_buffer)), 0,
33 "Output is \"hello world\\n\"");
34 TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer),
35 "The entire output buffer should have been used.");
36 }
37
test_subprocess_output_to_buffer_null_terminated(void)38 static void test_subprocess_output_to_buffer_null_terminated(void)
39 {
40 char output_buffer[__builtin_strlen(TEST_STRING_LN) + 1];
41
42 struct subprocess_target output = {
43 .type = TARGET_BUFFER_NULL_TERMINATED,
44 .buffer = {
45 .buf = output_buffer,
46 .size = sizeof(output_buffer),
47 },
48 };
49 const char *const argv[] = {
50 "echo", TEST_STRING, NULL
51 };
52
53 TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
54 "Return value of \"echo 'hello world'\" is 0");
55 TEST_STR_EQ(output_buffer, TEST_STRING_LN,
56 "Output is \"hello world\\n\"");
57 TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer) - 1,
58 "The entire output buffer should have been used.");
59 }
60
61 #define TEST_STRING_2 "hello\0world!"
62
test_subprocess_input_buffer(void)63 static void test_subprocess_input_buffer(void)
64 {
65 char input_buffer[sizeof(TEST_STRING_2)];
66 char output_buffer[20];
67 char error_buffer[20];
68
69 memcpy(input_buffer, TEST_STRING_2, sizeof(input_buffer));
70
71 struct subprocess_target input = {
72 .type = TARGET_BUFFER,
73 .buffer = {
74 .buf = input_buffer,
75 .size = sizeof(input_buffer),
76 },
77 };
78 struct subprocess_target output = {
79 .type = TARGET_BUFFER_NULL_TERMINATED,
80 .buffer = {
81 .buf = output_buffer,
82 .size = sizeof(output_buffer),
83 },
84 };
85 struct subprocess_target error = {
86 .type = TARGET_BUFFER_NULL_TERMINATED,
87 .buffer = {
88 .buf = error_buffer,
89 .size = sizeof(error_buffer),
90 },
91 };
92 const char *const argv[] = {"cat", NULL};
93
94 TEST_EQ(subprocess_run(argv, &input, &output, &error), 0,
95 "Return value of \"cat\" is 0");
96 TEST_EQ(memcmp(output_buffer, TEST_STRING_2, sizeof(TEST_STRING_2)),
97 0, "Output is \"hello\\0world!\"");
98 TEST_STR_EQ(error_buffer, "", "No output captured on stderr");
99 TEST_EQ(output.buffer.bytes_consumed, sizeof(TEST_STRING_2),
100 "Bytes consumed is correct");
101 TEST_EQ(error.buffer.bytes_consumed, 0, "No bytes used for error");
102 }
103
test_subprocess_input_null_terminated(void)104 static void test_subprocess_input_null_terminated(void)
105 {
106 char input_buffer[20];
107 char output_buffer[20];
108 char error_buffer[20];
109
110 memcpy(input_buffer, TEST_STRING_2, sizeof(TEST_STRING_2));
111
112 struct subprocess_target input = {
113 .type = TARGET_BUFFER_NULL_TERMINATED,
114 .buffer = {
115 .buf = input_buffer,
116 },
117 };
118 struct subprocess_target output = {
119 .type = TARGET_BUFFER_NULL_TERMINATED,
120 .buffer = {
121 .buf = output_buffer,
122 .size = sizeof(output_buffer),
123 },
124 };
125 struct subprocess_target error = {
126 .type = TARGET_BUFFER_NULL_TERMINATED,
127 .buffer = {
128 .buf = error_buffer,
129 .size = sizeof(error_buffer),
130 },
131 };
132 const char *const argv[] = {"cat", NULL};
133
134 TEST_EQ(subprocess_run(argv, &input, &output, &error), 0,
135 "Return value of \"cat\" is 0");
136 TEST_STR_EQ(output_buffer, "hello", "Output is \"hello\"");
137 TEST_STR_EQ(error_buffer, "", "No output captured on stderr");
138 TEST_EQ(output.buffer.bytes_consumed, 5, "5 bytes used");
139 TEST_EQ(error.buffer.bytes_consumed, 0, "No bytes used for error");
140 }
141
test_subprocess_small_output_buffer(void)142 static void test_subprocess_small_output_buffer(void)
143 {
144 char output_buffer[3];
145
146 struct subprocess_target output = {
147 .type = TARGET_BUFFER_NULL_TERMINATED,
148 .buffer = {
149 .buf = output_buffer,
150 .size = sizeof(output_buffer),
151 },
152 };
153 const char *const argv[] = {
154 "echo", TEST_STRING, NULL
155 };
156
157 TEST_EQ(subprocess_run(argv, &subprocess_null, &output, NULL), 0,
158 "Return value of \"echo 'hello world'\" is 0");
159 TEST_STR_EQ(output_buffer, "he",
160 "Output is \"he\" (truncated to small buffer)");
161 TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer) - 1,
162 "The entire output buffer should have been used.");
163 }
164
test_subprocess_return_code_failure(void)165 static void test_subprocess_return_code_failure(void)
166 {
167 const char *const argv[] = {"false", NULL};
168
169 TEST_NEQ(subprocess_run(argv, NULL, NULL, NULL), 0,
170 "Return value of \"false\" is nonzero");
171 }
172
173 struct cb_ctx {
174 char buffer[49 * 1024];
175 char *ptr;
176 };
177
input_cb(char * buf,size_t buf_sz,void * data)178 static ssize_t input_cb(char *buf, size_t buf_sz, void *data)
179 {
180 struct cb_ctx *ctx = (struct cb_ctx *)data;
181 size_t len = (ctx->buffer + sizeof(ctx->buffer)) - ctx->ptr;
182 if (len > buf_sz)
183 len = buf_sz;
184 memcpy(buf, ctx->ptr, len);
185 ctx->ptr += len;
186 return len;
187 }
188
test_subprocess_input_from_cb(void)189 static void test_subprocess_input_from_cb(void)
190 {
191 struct cb_ctx ctx;
192 char output_buffer[sizeof(ctx.buffer)];
193 const char *const argv[] = {"cat", NULL};
194
195 /* Initialize the input buffer with some data */
196 for (size_t i = 0; i < sizeof(ctx.buffer); i++)
197 ctx.buffer[i] = (char)i;
198 ctx.ptr = ctx.buffer;
199
200 struct subprocess_target output = {
201 .type = TARGET_BUFFER,
202 .buffer = {
203 .buf = output_buffer,
204 .size = sizeof(output_buffer),
205 },
206 };
207
208 struct subprocess_target input = {
209 .type = TARGET_CALLBACK,
210 .callback = {
211 .cb = input_cb,
212 .data = &ctx,
213 },
214 };
215 TEST_EQ(subprocess_run(argv, &input, &output, NULL), 0,
216 "Return value of \"cat\" is zero.");
217 TEST_EQ(memcmp(ctx.buffer, output_buffer, sizeof(output_buffer)), 0,
218 "The input buffer is equal to the output buffer.");
219 TEST_EQ(output.buffer.bytes_consumed, sizeof(output_buffer),
220 "The entire output buffer should have been used.");
221 }
222
output_cb(char * buf,size_t buf_sz,void * data)223 static ssize_t output_cb(char *buf, size_t buf_sz, void *data)
224 {
225 struct cb_ctx *ctx = (struct cb_ctx *)data;
226 if (ctx->ptr + buf_sz > ctx->buffer + sizeof(ctx->buffer)) {
227 TEST_TRUE(0, "Test failed as there is not enough space in the "
228 "output buffer.");
229 return -1;
230 }
231 memcpy(ctx->ptr, buf, buf_sz);
232 ctx->ptr += buf_sz;
233 return 0;
234 }
235
test_subprocess_output_to_cb(void)236 static void test_subprocess_output_to_cb(void)
237 {
238 struct cb_ctx ctx;
239 char output_buffer[sizeof(ctx.buffer)];
240 const char *const argv[] = {
241 "bc", "-l", NULL
242 };
243
244 ctx.ptr = ctx.buffer;
245
246 struct subprocess_target input = {
247 .type = TARGET_BUFFER_NULL_TERMINATED,
248 .buffer = {
249 .buf = (char *)"for (i = 0; i <= 10000; i += 1) i\n",
250 },
251 };
252
253 struct subprocess_target target_via_buffer = {
254 .type = TARGET_BUFFER,
255 .buffer = {
256 .buf = output_buffer,
257 .size = sizeof(output_buffer),
258 },
259 };
260
261 struct subprocess_target target_via_cb = {
262 .type = TARGET_CALLBACK,
263 .callback = {
264 .cb = output_cb,
265 .data = &ctx,
266 },
267 };
268
269 TEST_EQ(subprocess_run(argv, &input, &target_via_buffer, NULL), 0,
270 "Return value is zero when using buffer.");
271 TEST_EQ(subprocess_run(argv, &input, &target_via_cb, NULL), 0,
272 "Return value is zero when using callback.");
273 TEST_EQ(ctx.ptr - ctx.buffer, target_via_buffer.buffer.bytes_consumed,
274 "Both commmand invocations used the same number of bytes.");
275 TEST_EQ(memcmp(output_buffer, ctx.buffer,
276 target_via_buffer.buffer.bytes_consumed),
277 0, "Both output buffers are equivalent.");
278 }
279
main(int argc,char * argv[])280 int main(int argc, char *argv[])
281 {
282 test_subprocess_output_to_buffer();
283 test_subprocess_output_to_buffer_null_terminated();
284 test_subprocess_input_buffer();
285 test_subprocess_input_null_terminated();
286 test_subprocess_small_output_buffer();
287 test_subprocess_return_code_failure();
288 test_subprocess_input_from_cb();
289 test_subprocess_output_to_cb();
290
291 return gTestSuccess ? 0 : 255;
292 }
293