Infrastructure for running tests under Linux in primary VM.

Includes an initial test of simply inserting and removing the Hafnium
kernel module.

Change-Id: I832a30d902f58ca71f89374300ab39b2ba3ab877
diff --git a/test/hftest/hftest_common.c b/test/hftest/hftest_common.c
new file mode 100644
index 0000000..871aa54
--- /dev/null
+++ b/test/hftest/hftest_common.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2018 The Hafnium Authors.
+ *
+ * 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
+ *
+ *     https://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 "hftest_common.h"
+
+#include "hf/arch/std.h"
+#include "hf/arch/vm/power_mgmt.h"
+
+#include "hf/memiter.h"
+
+#include "hftest.h"
+
+HFTEST_ENABLE();
+
+static struct hftest_test hftest_constructed[HFTEST_MAX_TESTS];
+static size_t hftest_count;
+static struct hftest_test *hftest_list;
+
+static struct hftest_context global_context;
+
+struct hftest_context *hftest_get_context(void)
+{
+	return &global_context;
+}
+
+/**
+ * Adds the given test information to the global list, to be used by
+ * `hftest_use_registered_list`.
+ */
+void hftest_register(struct hftest_test test)
+{
+	if (hftest_count < HFTEST_MAX_TESTS) {
+		hftest_constructed[hftest_count++] = test;
+	} else {
+		HFTEST_FAIL("Too many tests", true);
+	}
+}
+
+/**
+ * Uses the list of tests registered by `hftest_register(...)` as the ones to
+ * run.
+ */
+void hftest_use_registered_list(void)
+{
+	hftest_list = hftest_constructed;
+}
+
+/**
+ * Uses the given list of tests as the ones to run.
+ */
+void hftest_use_list(struct hftest_test list[], size_t count)
+{
+	hftest_list = list;
+	hftest_count = count;
+}
+
+/**
+ * Writes out a JSON structure describing the available tests.
+ */
+void hftest_json(void)
+{
+	const char *suite = NULL;
+	size_t i;
+	size_t suites_in_image = 0;
+	size_t tests_in_suite = 0;
+
+	HFTEST_LOG("{");
+	HFTEST_LOG("  \"suites\": [");
+	for (i = 0; i < hftest_count; ++i) {
+		struct hftest_test *test = &hftest_list[i];
+		if (test->suite != suite) {
+			/* Close out previously open suite. */
+			if (tests_in_suite) {
+				HFTEST_LOG("      ]");
+				HFTEST_LOG("    },");
+			}
+			/* Move onto new suite. */
+			++suites_in_image;
+			suite = test->suite;
+			tests_in_suite = 0;
+			HFTEST_LOG("    {");
+			HFTEST_LOG("      \"name\": \"%s\",", test->suite);
+		}
+		if (test->kind == HFTEST_KIND_SET_UP) {
+			HFTEST_LOG("      \"setup\": true,");
+		}
+		if (test->kind == HFTEST_KIND_TEAR_DOWN) {
+			HFTEST_LOG("      \"teardown\": true,");
+		}
+		if (test->kind == HFTEST_KIND_TEST) {
+			if (!tests_in_suite) {
+				HFTEST_LOG("      \"tests\": [");
+			}
+			/*
+			 * It's easier to put the comma at the start of the line
+			 * than the end even
+			 * though the JSON looks a bit funky.
+			 */
+			HFTEST_LOG("       %c\"%s\"",
+				   tests_in_suite ? ',' : ' ', test->name);
+			++tests_in_suite;
+		}
+	}
+	if (tests_in_suite) {
+		HFTEST_LOG("      ]");
+		HFTEST_LOG("    }");
+	}
+	HFTEST_LOG("  ]");
+	HFTEST_LOG("}");
+}
+
+/**
+ * Logs a failure message and shut down.
+ */
+static noreturn void abort(void)
+{
+	HFTEST_LOG("FAIL");
+	arch_power_off();
+}
+
+static void run_test(hftest_test_fn set_up, hftest_test_fn test,
+		     hftest_test_fn tear_down)
+{
+	/* Prepare the context. */
+	struct hftest_context *ctx = hftest_get_context();
+	memset(ctx, 0, sizeof(*ctx));
+	ctx->abort = abort;
+
+	/* Run any set up functions. */
+	if (set_up) {
+		set_up();
+		if (ctx->failures) {
+			abort();
+		}
+	}
+
+	/* Run the test. */
+	test();
+	if (ctx->failures) {
+		abort();
+	}
+
+	/* Run any tear down functions. */
+	if (tear_down) {
+		tear_down();
+		if (ctx->failures) {
+			abort();
+		}
+	}
+
+	HFTEST_LOG("FINISHED");
+}
+
+/**
+ * Runs the given test case.
+ */
+void hftest_run(struct memiter suite_name, struct memiter test_name)
+{
+	size_t i;
+	bool found_suite = false;
+	const char *suite = NULL;
+	hftest_test_fn suite_set_up = NULL;
+	hftest_test_fn suite_tear_down = NULL;
+
+	for (i = 0; i < hftest_count; ++i) {
+		struct hftest_test *test = &hftest_list[i];
+		/* Find the test suite. */
+		if (found_suite) {
+			if (test->suite != suite) {
+				/* Test wasn't in the suite. */
+				break;
+			}
+		} else {
+			if (test->suite == suite) {
+				/* This isn't the right suite so keep going. */
+				continue;
+			}
+			/* Examine a new suite. */
+			suite = test->suite;
+			if (memiter_iseq(&suite_name, test->suite)) {
+				found_suite = true;
+			}
+		}
+
+		switch (test->kind) {
+		/*
+		 * The first entries in the suite are the set up and tear down
+		 * functions.
+		 */
+		case HFTEST_KIND_SET_UP:
+			suite_set_up = test->fn;
+			break;
+		case HFTEST_KIND_TEAR_DOWN:
+			suite_tear_down = test->fn;
+			break;
+		/* Find the test. */
+		case HFTEST_KIND_TEST:
+			if (memiter_iseq(&test_name, test->name)) {
+				run_test(suite_set_up, test->fn,
+					 suite_tear_down);
+				return;
+			}
+			break;
+		default:
+			/* Ignore other kinds. */
+			break;
+		}
+	}
+
+	HFTEST_LOG("Unable to find requested tests.");
+}
+
+/**
+ * Writes out usage information.
+ */
+void hftest_help(void)
+{
+	HFTEST_LOG("usage:");
+	HFTEST_LOG("");
+	HFTEST_LOG("  help");
+	HFTEST_LOG("");
+	HFTEST_LOG("    Show this help.");
+	HFTEST_LOG("");
+	HFTEST_LOG("  json");
+	HFTEST_LOG("");
+	HFTEST_LOG(
+		"    Print a directory of test suites and tests in "
+		"JSON "
+		"format.");
+	HFTEST_LOG("");
+	HFTEST_LOG("  run <suite> <test>");
+	HFTEST_LOG("");
+	HFTEST_LOG("    Run the named test from the named test suite.");
+}