tests: Add client escape test

Add a small testcase for the escape-parsing code.

Change-Id: I2296d1bd0b1a97b46565e56ed867dd39a159baec
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/test/Makefile.am b/test/Makefile.am
index 67c7af1..1ddf5bd 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -11,7 +11,8 @@
 	test-ringbuffer-simple-poll \
 	test-ringbuffer-boundary-poll \
 	test-ringbuffer-poll-force \
-	test-config-parse-logsize
+	test-config-parse-logsize \
+	test-client-escape
 
 EXTRA_DIST = ringbuffer-test-utils.c
 
diff --git a/test/test-client-escape.c b/test/test-client-escape.c
new file mode 100644
index 0000000..76e9c15
--- /dev/null
+++ b/test/test-client-escape.c
@@ -0,0 +1,231 @@
+/**
+ * Copyright © 2019 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#define main __main
+#define read __read
+#include "console-client.c"
+#undef main
+#undef read
+
+struct test {
+	enum esc_type	esc_type;
+	union {
+		struct ssh_esc_state ssh;
+		struct str_esc_state str;
+	} esc_state;
+	const char	*in[4];
+	int		n_in;
+	const char	*exp_out;
+	int		exp_rc;
+};
+
+struct test_ctx {
+	struct console_client	client;
+	struct test		*test;
+	uint8_t			out[4096];
+	int			cur_in;
+	int			cur_out;
+};
+
+
+struct test tests[] = {
+	{
+		/* no escape code */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a"},
+		.n_in		= 1,
+		.exp_out	= "a",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* no escape code, multiple reads */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a", "b"},
+		.n_in		= 2,
+		.exp_out	= "ab",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* ssh escape in one read */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a\r~."},
+		.n_in		= 1,
+		.exp_out	= "a\r",
+		.exp_rc		= PROCESS_ESC,
+	},
+	{
+		/* ssh escape, partial ~ is not output. */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a\r~"},
+		.n_in		= 1,
+		.exp_out	= "a\r",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* ssh escape split into individual reads */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a", "\r", "~", "."},
+		.n_in		= 4,
+		.exp_out	= "a\r",
+		.exp_rc		= PROCESS_ESC,
+	},
+	{
+		/* ssh escape, escaped. */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a\r~~."},
+		.n_in		= 1,
+		.exp_out	= "a\r~.",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* ssh escape, escaped ~, and not completed. */
+		.esc_type	= ESC_TYPE_SSH,
+		.in		= {"a\r~~"},
+		.n_in		= 1,
+		.exp_out	= "a\r~",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* str escape, no match */
+		.esc_type	= ESC_TYPE_STR,
+		.esc_state	= { .str = { .str = (const uint8_t *)"c" } },
+		.in		= {"ab"},
+		.n_in		= 1,
+		.exp_out	= "ab",
+		.exp_rc		= PROCESS_EXIT,
+	},
+	{
+		/* str escape, one byte, as one read */
+		.esc_type	= ESC_TYPE_STR,
+		.esc_state	= { .str = { .str = (const uint8_t *)"b" } },
+		.in		= {"abc"},
+		.n_in		= 1,
+		.exp_out	= "ab",
+		.exp_rc		= PROCESS_ESC,
+	},
+	{
+		/* str escape, multiple bytes, as one read */
+		.esc_type	= ESC_TYPE_STR,
+		.esc_state	= { .str = { .str = (const uint8_t *)"bc" } },
+		.in		= {"abcd"},
+		.n_in		= 1,
+		.exp_out	= "abc",
+		.exp_rc		= PROCESS_ESC,
+	},
+	{
+		/* str escape, multiple bytes, split over reads */
+		.esc_type	= ESC_TYPE_STR,
+		.esc_state	= { .str = { .str = (const uint8_t *)"bc" } },
+		.in		= {"ab", "cd"},
+		.n_in		= 2,
+		.exp_out	= "abc",
+		.exp_rc		= PROCESS_ESC,
+	},
+	{
+		/* str escape, not matched due to intermediate data */
+		.esc_type	= ESC_TYPE_STR,
+		.esc_state	= { .str = { .str = (const uint8_t *)"ab" } },
+		.in		= {"acb"},
+		.n_in		= 1,
+		.exp_out	= "acb",
+		.exp_rc		= PROCESS_EXIT,
+	},
+};
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+struct test_ctx ctxs[ARRAY_SIZE(tests)];
+
+/* stubs for console socket */
+const char *console_socket_path = "";
+const size_t console_socket_path_len = 1;
+const char *console_socket_path_readable = 0;
+
+int write_buf_to_fd(int fd, const uint8_t *buf, size_t len)
+{
+	struct test_ctx *ctx = &ctxs[fd];
+
+	assert(ctx->cur_out + len <= sizeof(ctx->out));
+	memcpy(ctx->out + ctx->cur_out, buf, len);
+	ctx->cur_out += len;
+
+	return 0;
+}
+
+ssize_t __read(int fd, void *buf, size_t len)
+{
+	struct test_ctx *ctx = &ctxs[fd];
+	const char *inbuf;
+	size_t inlen;
+
+	if (ctx->cur_in >= ctx->test->n_in)
+		return 0;
+
+	inbuf = ctx->test->in[ctx->cur_in];
+	inlen = strlen(inbuf);
+	assert(inlen <= len);
+	memcpy(buf, inbuf, inlen);
+	ctx->cur_in++;
+	return inlen;
+}
+
+void run_one_test(int idx, struct test *test, struct test_ctx *ctx)
+{
+	size_t exp_out_len;
+	int rc;
+
+	/* we store the index into the context array as a FD, so we
+	 * can refer to it through the read & write callbacks.
+	 */
+	ctx->client.console_sd = idx;
+	ctx->client.fd_in = idx;
+	ctx->client.esc_type = test->esc_type;
+	memcpy(&ctx->client.esc_state, &test->esc_state,
+			sizeof(test->esc_state));
+	ctx->test = test;
+
+	for (;;) {
+		rc = process_tty(&ctx->client);
+		if (rc != PROCESS_OK)
+			break;
+	}
+
+	exp_out_len = strlen(test->exp_out);
+
+#ifdef DEBUG
+	printf("got: rc %d %s(%d), exp: rc %d %s(%ld)\n",
+			rc, ctx->out, ctx->cur_out,
+			test->exp_rc, test->exp_out, exp_out_len);
+	fflush(stdout);
+#endif
+	assert(rc == test->exp_rc);
+	assert(exp_out_len == ctx->cur_out);
+	assert(!memcmp(ctx->out, test->exp_out, exp_out_len));
+}
+
+int main(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tests); i++)
+		run_one_test(i, &tests[i], &ctxs[i]);
+
+	return EXIT_SUCCESS;
+}