htest: service boilerplate for secondary VMs.
In order to keep it easy to add tests, we need to avoid requiring new
VMs for each case as this will lead to an explosion of duplication and
build artifacts.
Allowing secondary VMs to contain selectable services for different
tests means they don't each require a separate VM and run into resource
limits. The resulting services are also avoid boilerplate so the source
gets to the point of the test more easily.
The primary VMs are also refactored to encourage splitting tests across
file but bundling them into the same VM so as to cut down on build
artifacts and make it easier to find and run tests.
Assertions can now be used in any context of a test, including in a test
service.
Change-Id: Id3b8a7579d0facdfceb9d77f62ef57241b31a88a
diff --git a/test/hftest/BUILD.gn b/test/hftest/BUILD.gn
index bc956f8..5f2469a 100644
--- a/test/hftest/BUILD.gn
+++ b/test/hftest/BUILD.gn
@@ -45,9 +45,16 @@
source_set("hftest_secondary_vm") {
testonly = true
+ public_configs = [ ":hftest_config" ]
+
+ sources = [
+ "hftest_service.c",
+ ]
+
deps = [
"//src:common",
"//src:dlog",
+ "//src:memiter",
"//src/arch/${plat_arch}:entry",
"//src/arch/${plat_arch}/hftest:entry",
"//src/arch/${plat_arch}/hftest:hf_call",
diff --git a/test/hftest/hftest.c b/test/hftest/hftest.c
index 6a66fed..04a07a6 100644
--- a/test/hftest/hftest.c
+++ b/test/hftest/hftest.c
@@ -19,8 +19,11 @@
#include <stdalign.h>
#include <stdint.h>
+#include "hf/arch/vm/power_mgmt.h"
+
#include "hf/fdt.h"
#include "hf/memiter.h"
+#include "hf/std.h"
alignas(4096) uint8_t kstack[4096];
@@ -29,6 +32,13 @@
extern struct hftest_test hftest_begin[];
extern struct hftest_test hftest_end[];
+static struct hftest_context global_context;
+
+struct hftest_context *hftest_get_context(void)
+{
+ return &global_context;
+}
+
static void json(void)
{
struct hftest_test *test;
@@ -62,9 +72,11 @@
if (!tests_in_suite) {
HFTEST_LOG(" \"tests\": [");
}
- /* It's easier to put the comma at the start of the line
+ /*
+ * It's easier to put the comma at the start of the line
* than the end even
- * though the JSON looks a bit funky. */
+ * though the JSON looks a bit funky.
+ */
HFTEST_LOG(" %c\"%s\"",
tests_in_suite ? ',' : ' ', test->name);
++tests_in_suite;
@@ -78,37 +90,43 @@
HFTEST_LOG("}");
}
+static noreturn void abort(void)
+{
+ HFTEST_LOG("FAIL");
+ shutdown();
+}
+
static void run_test(hftest_test_fn set_up, hftest_test_fn test,
hftest_test_fn tear_down)
{
- struct hftest_context ctx = {
- .failures = 0,
- };
+ /* 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(&ctx);
- if (ctx.failures) {
- goto fail;
+ set_up();
+ if (ctx->failures) {
+ abort();
}
}
- test(&ctx);
- if (ctx.failures) {
- goto fail;
+ /* Run the test. */
+ test();
+ if (ctx->failures) {
+ abort();
}
+ /* Run any tear down functions. */
if (tear_down) {
- tear_down(&ctx);
- if (ctx.failures) {
- goto fail;
+ tear_down();
+ if (ctx->failures) {
+ abort();
}
}
- HFTEST_LOG("PASS");
- return;
-
-fail:
- HFTEST_LOG("FAIL");
+ HFTEST_LOG("FINISHED");
}
static void run(struct memiter *args)
@@ -151,8 +169,10 @@
}
switch (test->kind) {
- /* The first entries in the suite are the set up and tear down
- * functions. */
+ /*
+ * 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;
@@ -167,13 +187,16 @@
return;
}
break;
+ default:
+ /* Ignore other kinds. */
+ break;
}
}
HFTEST_LOG("Unable to find requested tests.");
}
-void help(void)
+static void help(void)
{
HFTEST_LOG("usage:");
HFTEST_LOG("");
@@ -193,7 +216,7 @@
HFTEST_LOG(" Run the named test from the named test suite.");
}
-void main(const struct fdt_header *fdt)
+void kmain(const struct fdt_header *fdt)
{
struct fdt_node n;
const char *bootargs;
@@ -241,8 +264,3 @@
help();
}
-
-void kmain(const struct fdt_header *fdt)
-{
- main(fdt);
-}
diff --git a/test/hftest/hftest.py b/test/hftest/hftest.py
index 4a00a52..2a2a07e 100755
--- a/test/hftest/hftest.py
+++ b/test/hftest/hftest.py
@@ -35,12 +35,12 @@
def qemu(image, initrd, args, log):
qemu_args = [
"timeout", "--foreground", "5s",
- "./prebuilts/linux-x64/qemu/qemu-system-aarch64", "-M", "virt,gic_version=3", "-cpu",
- "cortex-a57", "-smp", "4", "-m", "16M", "-machine", "virtualization=true",
+ "./prebuilts/linux-x64/qemu/qemu-system-aarch64", "-M", "virt,gic_version=3",
+ "-cpu", "cortex-a57", "-smp", "4", "-m", "16M", "-machine", "virtualization=true",
"-nographic", "-nodefaults", "-serial", "stdio", "-kernel", image,
]
if initrd:
- qemu_args += ["-initrd", initrd]
+ qemu_args += ["-initrd", initrd]
if args:
qemu_args += ["-append", args]
# Save the log to a file.
@@ -132,7 +132,8 @@
sponge_log.write(out)
sponge_log.write("\r\n\r\n")
hftest_out = hftest_lines(out)
- if hftest_out[-1] == "PASS":
+ if hftest_out[-1] == "FINISHED" and not any(
+ l.startswith('Failure:') for l in hftest_out):
print(" PASS")
else:
failures_from_suite += 1
diff --git a/test/hftest/hftest_service.c b/test/hftest/hftest_service.c
new file mode 100644
index 0000000..048d267
--- /dev/null
+++ b/test/hftest/hftest_service.c
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * 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 <stdalign.h>
+#include <stdint.h>
+
+#include "hf/memiter.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+
+alignas(4096) uint8_t kstack[4096];
+
+HFTEST_ENABLE();
+
+extern struct hftest_test hftest_begin[];
+extern struct hftest_test hftest_end[];
+
+static alignas(HF_MAILBOX_SIZE) uint8_t send[HF_MAILBOX_SIZE];
+static alignas(HF_MAILBOX_SIZE) uint8_t recv[HF_MAILBOX_SIZE];
+
+static hf_ipaddr_t send_addr = (hf_ipaddr_t)send;
+static hf_ipaddr_t recv_addr = (hf_ipaddr_t)recv;
+
+static struct hftest_context global_context;
+
+struct hftest_context *hftest_get_context(void)
+{
+ return &global_context;
+}
+
+/** Find the service with the name passed in the arguments. */
+static hftest_test_fn find_service(struct memiter *args)
+{
+ struct memiter service_name;
+ struct hftest_test *test;
+
+ if (!memiter_parse_str(args, &service_name)) {
+ return NULL;
+ }
+
+ for (test = hftest_begin; test < hftest_end; ++test) {
+ if (test->kind == HFTEST_KIND_SERVICE &&
+ memiter_iseq(&service_name, test->name)) {
+ return test->fn;
+ }
+ }
+
+ return NULL;
+}
+
+static noreturn void abort(void)
+{
+ HFTEST_LOG("Service contained failures.");
+ for (;;) {
+ /*
+ * Hang if the service aborts as a secondary can't power down
+ * the machine.
+ */
+ }
+}
+
+noreturn void kmain(void)
+{
+ struct memiter args;
+ hftest_test_fn service;
+ struct hf_mailbox_receive_return res;
+ struct hftest_context *ctx;
+
+ /* Prepare the context. */
+
+ /* Set up the mailbox. */
+ hf_vm_configure(send_addr, recv_addr);
+
+ /* Receive the name of the service to run. */
+ res = hf_mailbox_receive(true);
+ memiter_init(&args, recv, res.size);
+ service = find_service(&args);
+ hf_mailbox_clear();
+
+ /* Check the service was found. */
+ if (service == NULL) {
+ HFTEST_LOG_FAILURE();
+ HFTEST_LOG(HFTEST_LOG_INDENT
+ "Unable to find requested service");
+ for (;;) {
+ /* Hang if the service was unknown. */
+ }
+ }
+
+ /* Clean the context. */
+ ctx = hftest_get_context();
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->abort = abort;
+ ctx->send = send;
+ ctx->recv = recv;
+
+ /* Pause so the next time cycles are given the service will be run. */
+ hf_vcpu_yield();
+
+ /* Let the service run. */
+ service();
+
+ /* Cleanly handle it if the service returns. */
+ if (ctx->failures) {
+ abort();
+ }
+
+ for (;;) {
+ /* Hang if the service returns. */
+ }
+}
diff --git a/test/hftest/inc/hftest.h b/test/hftest/inc/hftest.h
index 290b467..894d7f0 100644
--- a/test/hftest/inc/hftest.h
+++ b/test/hftest/inc/hftest.h
@@ -37,27 +37,39 @@
*/
#define TEST(suite, test) HFTEST_TEST(suite, test)
+/*
+ * Define a test service.
+ */
+#define TEST_SERVICE(service) HFTEST_TEST_SERVICE(service)
+
/* Assertions. */
-#define ASSERT_EQ(x, y) ASSERT_OP(x, y, ==, true)
-#define ASSERT_NE(x, y) ASSERT_OP(x, y, !=, true)
-#define ASSERT_LE(x, y) ASSERT_OP(x, y, <=, true)
-#define ASSERT_LT(x, y) ASSERT_OP(x, y, <, true)
-#define ASSERT_GE(x, y) ASSERT_OP(x, y, >=, true)
-#define ASSERT_GT(x, y) ASSERT_OP(x, y, >, true)
+#define ASSERT_EQ(x, y) HFTEST_ASSERT_OP(x, y, ==, true)
+#define ASSERT_NE(x, y) HFTEST_ASSERT_OP(x, y, !=, true)
+#define ASSERT_LE(x, y) HFTEST_ASSERT_OP(x, y, <=, true)
+#define ASSERT_LT(x, y) HFTEST_ASSERT_OP(x, y, <, true)
+#define ASSERT_GE(x, y) HFTEST_ASSERT_OP(x, y, >=, true)
+#define ASSERT_GT(x, y) HFTEST_ASSERT_OP(x, y, >, true)
#define ASSERT_TRUE(x) ASSERT_EQ(x, true);
#define ASSERT_FALSE(x) ASSERT_EQ(x, false);
-#define EXPECT_EQ(x, y) ASSERT_OP(x, y, ==, false)
-#define EXPECT_NE(x, y) ASSERT_OP(x, y, !=, false)
-#define EXPECT_LE(x, y) ASSERT_OP(x, y, <=, false)
-#define EXPECT_LT(x, y) ASSERT_OP(x, y, <, false)
-#define EXPECT_GE(x, y) ASSERT_OP(x, y, >=, false)
-#define EXPECT_GT(x, y) ASSERT_OP(x, y, >, false)
+#define EXPECT_EQ(x, y) HFTEST_ASSERT_OP(x, y, ==, false)
+#define EXPECT_NE(x, y) HFTEST_ASSERT_OP(x, y, !=, false)
+#define EXPECT_LE(x, y) HFTEST_ASSERT_OP(x, y, <=, false)
+#define EXPECT_LT(x, y) HFTEST_ASSERT_OP(x, y, <, false)
+#define EXPECT_GE(x, y) HFTEST_ASSERT_OP(x, y, >=, false)
+#define EXPECT_GT(x, y) HFTEST_ASSERT_OP(x, y, >, false)
#define EXPECT_TRUE(x) EXPECT_EQ(x, true);
#define EXPECT_FALSE(x) EXPECT_EQ(x, false);
+/* Service utilities. */
+#define SERVICE_SELECT(vm_id, service, send_buffer) \
+ HFTEST_SERVICE_SELECT(vm_id, service, send_buffer)
+
+#define SERVICE_SEND_BUFFER() HFTEST_SERVICE_SEND_BUFFER()
+#define SERVICE_RECV_BUFFER() HFTEST_SERVICE_RECV_BUFFER()
+
/*
* This must be used exactly once in a test image to signal to the linker that
* the .hftest section is allowed to be included in the generated image.
@@ -69,185 +81,10 @@
*/
#define HFTEST_LOG_PREFIX "[hftest] "
-/* Above this point is the public API. Below are the implementation details. */
-
-/* Log with the HFTEST_LOG_PREFIX and a new line. The zero is added so there is
- * always at least one variadic argument. */
-#define HFTEST_LOG(...) HFTEST_LOG_IMPL(__VA_ARGS__, 0)
-#define HFTEST_LOG_IMPL(format, ...) \
- dlog("%s" format "\n", HFTEST_LOG_PREFIX, __VA_ARGS__)
-
-/* Helper to wrap the argument in quotes. */
-#define HFTEST_STR(str) #str
-
-/* Sections are names such that when the linker sorts them, all entries for the
- * same test suite are contiguous and the set up and tear down entries come
- * before the tests. This order simplifies test discovery in the running image.
- */
-#define HFTEST_SET_UP_SECTION(suite_name) \
- HFTEST_STR(.hftest.suite_name .1set_up)
-#define HFTEST_TEAR_DOWN_SECTION(suite_name) \
- HFTEST_STR(.hftest.suite_name .1tear_down)
-#define HFTEST_TEST_SECTION(suite_name, test_name) \
- HFTEST_STR(.hftest.suite_name .2test.test_name)
-
-/* Helpers to construct unique identifiers. */
-#define HFTEST_SET_UP_STRUCT(suite_name) hftest_set_up_##suite_name
-#define HFTEST_TEAR_DOWN_STRUCT(suite_name) hftest_tear_down_##suite_name
-#define HFTEST_TEST_STRUCT(suite_name, test_name) \
- hftest_test_##suite_name##_##test_name
-
-#define HFTEST_SET_UP_FN(suite_name) hftest_set_up_fn_##suite_name
-#define HFTEST_TEAR_DOWN_FN(suite_name) hftest_tear_down_fn_##suite_name
-#define HFTEST_TEST_FN(suite_name, test_name) \
- hftest_test_fn_##suite_name##_##test_name
-
-/* Register test functions. */
-#define HFTEST_SET_UP(suite_name) \
- static void HFTEST_SET_UP_FN(suite_name)(struct hftest_context * \
- hftest_ctx); \
- const struct hftest_test __attribute__((used)) \
- __attribute__((section(HFTEST_SET_UP_SECTION(suite_name)))) \
- HFTEST_SET_UP_STRUCT(suite_name) = { \
- .suite = #suite_name, \
- .kind = HFTEST_KIND_SET_UP, \
- .fn = HFTEST_SET_UP_FN(suite_name), \
- }; \
- static void HFTEST_SET_UP_FN(suite_name)( \
- __attribute__((unused)) struct hftest_context * hftest_ctx)
-
-#define HFTEST_TEAR_DOWN(suite_name) \
- static void HFTEST_TEAR_DOWN_FN(suite_name)(struct hftest_context * \
- hftest_ctx); \
- const struct hftest_test __attribute__((used)) \
- __attribute__((section(HFTEST_TEAR_DOWN_SECTION(suite_name)))) \
- HFTEST_TEAR_DOWN_STRUCT(suite_name) = { \
- .suite = #suite_name, \
- .kind = HFTEST_KIND_TEAR_DOWN, \
- .fn = HFTEST_TEAR_DOWN_FN(suite_name), \
- }; \
- static void HFTEST_TEAR_DOWN_FN(suite_name)( \
- __attribute__((unused)) struct hftest_context * hftest_ctx)
-
-#define HFTEST_TEST(suite_name, test_name) \
- static void HFTEST_TEST_FN( \
- suite_name, test_name)(struct hftest_context * hftest_ctx); \
- const struct hftest_test __attribute__((used)) __attribute__( \
- (section(HFTEST_TEST_SECTION(suite_name, test_name)))) \
- HFTEST_TEST_STRUCT(suite_name, test_name) = { \
- .suite = #suite_name, \
- .kind = HFTEST_KIND_TEST, \
- .name = #test_name, \
- .fn = HFTEST_TEST_FN(suite_name, test_name), \
- }; \
- static void HFTEST_TEST_FN(suite_name, test_name)( \
- __attribute__((unused)) struct hftest_context * hftest_ctx)
-
-/* Context for tests. */
-struct hftest_context {
- uint32_t failures;
-};
-
-/* A test case. */
-typedef void (*hftest_test_fn)(struct hftest_context *);
-
-enum hftest_kind {
- HFTEST_KIND_SET_UP = 0,
- HFTEST_KIND_TEST = 1,
- HFTEST_KIND_TEAR_DOWN = 2,
-};
-
-struct hftest_test {
- const char *suite;
- enum hftest_kind kind;
- const char *name;
- hftest_test_fn fn;
-};
-
/*
- * This union can store any of the primitive types supported by the assertion
- * macros.
- *
- * It does not include pointers as comparison of pointers is not often needed
- * and could be a mistake for string comparison. If pointer comparison is needed
- * and explicit assertion such as ASSERT_PTR_EQ() would be more appropriate.
+ * Indentation used e.g. to give the reason for an assertion failure.
*/
-union hftest_any {
- bool b;
- char c;
- signed char sc;
- unsigned char uc;
- signed short ss;
- unsigned short us;
- signed int si;
- unsigned int ui;
- signed long int sli;
- unsigned long int uli;
- signed long long int slli;
- unsigned long long int ulli;
-};
+#define HFTEST_LOG_INDENT " "
-/* _Generic formatting doesn't seem to be supported so doing this manually. */
-/* clang-format off */
-
-/* Select the union member to match the type of the expression. */
-#define hftest_any_get(any, x) \
- _Generic((x), \
- bool: (any).b, \
- char: (any).c, \
- signed char: (any).sc, \
- unsigned char: (any).uc, \
- signed short: (any).ss, \
- unsigned short: (any).us, \
- signed int: (any).si, \
- unsigned int: (any).ui, \
- signed long int: (any).sli, \
- unsigned long int: (any).uli, \
- signed long long int: (any).slli, \
- unsigned long long int: (any).ulli)
-
-/*
- * dlog format specifier for types. Note, these aren't the standard specifiers
- * for the types.
- */
-#define hftest_dlog_format(x) \
- _Generic((x), \
- bool: "%u", \
- char: "%c", \
- signed char: "%d", \
- unsigned char: "%u", \
- signed short: "%d", \
- unsigned short: "%u", \
- signed int: "%d", \
- unsigned int: "%u", \
- signed long int: "%d", \
- unsigned long int: "%u", \
- signed long long int: "%d", \
- unsigned long long int: "%u")
-
-/* clang-format on */
-
-#define ASSERT_OP(lhs, rhs, op, fatal) \
- do { \
- union hftest_any lhs_value; \
- union hftest_any rhs_value; \
- hftest_any_get(lhs_value, lhs) = (lhs); \
- hftest_any_get(rhs_value, rhs) = (rhs); \
- if (!(hftest_any_get(lhs_value, lhs) \
- op hftest_any_get(rhs_value, rhs))) { \
- ++hftest_ctx->failures; \
- dlog(HFTEST_LOG_PREFIX " %s:%u: Failure\n", __FILE__, \
- __LINE__); \
- dlog(HFTEST_LOG_PREFIX " %s %s %s (%s=", #lhs, #op, \
- #rhs, #lhs); \
- dlog(hftest_dlog_format(lhs), \
- hftest_any_get(lhs_value, lhs)); \
- dlog(", %s=", #rhs); \
- dlog(hftest_dlog_format(rhs), \
- hftest_any_get(rhs_value, rhs)); \
- dlog(")\n"); \
- if (fatal) { \
- return; \
- } \
- } \
- } while (0)
+/* Above this point is the public API. Now include the implementation. */
+#include <hftest_impl.h>
diff --git a/test/hftest/inc/hftest_impl.h b/test/hftest/inc/hftest_impl.h
new file mode 100644
index 0000000..7a7be34
--- /dev/null
+++ b/test/hftest/inc/hftest_impl.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdnoreturn.h>
+
+/*
+ * Log with the HFTEST_LOG_PREFIX and a new line. The zero is added so there is
+ * always at least one variadic argument.
+ */
+#define HFTEST_LOG(...) HFTEST_LOG_IMPL(__VA_ARGS__, 0)
+#define HFTEST_LOG_IMPL(format, ...) \
+ dlog("%s" format "\n", HFTEST_LOG_PREFIX, __VA_ARGS__)
+
+/* Helper to wrap the argument in quotes. */
+#define HFTEST_STR(str) #str
+
+/*
+ * Sections are names such that when the linker sorts them, all entries for the
+ * same test suite are contiguous and the set up and tear down entries come
+ * before the tests. This order simplifies test discovery in the running image.
+ */
+#define HFTEST_SET_UP_SECTION(suite_name) \
+ HFTEST_STR(.hftest.suite.suite_name .1set_up)
+#define HFTEST_TEAR_DOWN_SECTION(suite_name) \
+ HFTEST_STR(.hftest.suite.suite_name .1tear_down)
+#define HFTEST_TEST_SECTION(suite_name, test_name) \
+ HFTEST_STR(.hftest.suite.suite_name .2test.test_name)
+#define HFTEST_SERVICE_SECTION(service_name) \
+ HFTEST_STR(.hftest.service.service_name)
+
+/* Helpers to construct unique identifiers. */
+#define HFTEST_SET_UP_STRUCT(suite_name) hftest_set_up_##suite_name
+#define HFTEST_TEAR_DOWN_STRUCT(suite_name) hftest_tear_down_##suite_name
+#define HFTEST_TEST_STRUCT(suite_name, test_name) \
+ hftest_test_##suite_name##_##test_name
+#define HFTEST_SERVICE_STRUCT(service_name) hftest_service_##service_name
+
+#define HFTEST_SET_UP_FN(suite_name) hftest_set_up_fn_##suite_name
+#define HFTEST_TEAR_DOWN_FN(suite_name) hftest_tear_down_fn_##suite_name
+#define HFTEST_TEST_FN(suite_name, test_name) \
+ hftest_test_fn_##suite_name##_##test_name
+#define HFTEST_SERVICE_FN(service_name) hftest_service_fn_##service_name
+
+/* Register test functions. */
+#define HFTEST_SET_UP(suite_name) \
+ static void HFTEST_SET_UP_FN(suite_name)(void); \
+ const struct hftest_test __attribute__((used)) \
+ __attribute__((section(HFTEST_SET_UP_SECTION(suite_name)))) \
+ HFTEST_SET_UP_STRUCT(suite_name) = { \
+ .suite = #suite_name, \
+ .kind = HFTEST_KIND_SET_UP, \
+ .fn = HFTEST_SET_UP_FN(suite_name), \
+ }; \
+ static void HFTEST_SET_UP_FN(suite_name)(void)
+
+#define HFTEST_TEAR_DOWN(suite_name) \
+ static void HFTEST_TEAR_DOWN_FN(suite_name)(void); \
+ const struct hftest_test __attribute__((used)) \
+ __attribute__((section(HFTEST_TEAR_DOWN_SECTION(suite_name)))) \
+ HFTEST_TEAR_DOWN_STRUCT(suite_name) = { \
+ .suite = #suite_name, \
+ .kind = HFTEST_KIND_TEAR_DOWN, \
+ .fn = HFTEST_TEAR_DOWN_FN(suite_name), \
+ }; \
+ static void HFTEST_TEAR_DOWN_FN(suite_name)(void)
+
+#define HFTEST_TEST(suite_name, test_name) \
+ static void HFTEST_TEST_FN(suite_name, test_name)(void); \
+ const struct hftest_test __attribute__((used)) __attribute__( \
+ (section(HFTEST_TEST_SECTION(suite_name, test_name)))) \
+ HFTEST_TEST_STRUCT(suite_name, test_name) = { \
+ .suite = #suite_name, \
+ .kind = HFTEST_KIND_TEST, \
+ .name = #test_name, \
+ .fn = HFTEST_TEST_FN(suite_name, test_name), \
+ }; \
+ static void HFTEST_TEST_FN(suite_name, test_name)(void)
+
+#define HFTEST_TEST_SERVICE(service_name) \
+ static void HFTEST_SERVICE_FN(service_name)(void); \
+ const struct hftest_test __attribute__((used)) \
+ __attribute__((section(HFTEST_SERVICE_SECTION(service_name)))) \
+ HFTEST_SERVICE_STRUCT(service_name) = { \
+ .kind = HFTEST_KIND_SERVICE, \
+ .name = #service_name, \
+ .fn = HFTEST_SERVICE_FN(service_name), \
+ }; \
+ static void HFTEST_SERVICE_FN(service_name)(void)
+
+/* Context for tests. */
+struct hftest_context {
+ uint32_t failures;
+ noreturn void (*abort)(void);
+
+ /* These are used in services. */
+ void *send;
+ void *recv;
+};
+
+struct hftest_context *hftest_get_context(void);
+
+/* A test case. */
+typedef void (*hftest_test_fn)(void);
+
+enum hftest_kind {
+ HFTEST_KIND_SET_UP = 0,
+ HFTEST_KIND_TEST = 1,
+ HFTEST_KIND_TEAR_DOWN = 2,
+ HFTEST_KIND_SERVICE = 3,
+};
+
+/**
+ * The .hftest section contains an array of this struct which describes the test
+ * functions contained in the image allowing the image to inspect the tests it
+ * contains.
+ */
+struct hftest_test {
+ const char *suite;
+ enum hftest_kind kind;
+ const char *name;
+ hftest_test_fn fn;
+};
+
+/*
+ * This union can store any of the primitive types supported by the assertion
+ * macros.
+ *
+ * It does not include pointers as comparison of pointers is not often needed
+ * and could be a mistake for string comparison. If pointer comparison is needed
+ * and explicit assertion such as ASSERT_PTR_EQ() would be more appropriate.
+ */
+union hftest_any {
+ bool b;
+ char c;
+ signed char sc;
+ unsigned char uc;
+ signed short ss;
+ unsigned short us;
+ signed int si;
+ unsigned int ui;
+ signed long int sli;
+ unsigned long int uli;
+ signed long long int slli;
+ unsigned long long int ulli;
+};
+
+/* _Generic formatting doesn't seem to be supported so doing this manually. */
+/* clang-format off */
+
+/* Select the union member to match the type of the expression. */
+#define hftest_any_get(any, x) \
+ _Generic((x), \
+ bool: (any).b, \
+ char: (any).c, \
+ signed char: (any).sc, \
+ unsigned char: (any).uc, \
+ signed short: (any).ss, \
+ unsigned short: (any).us, \
+ signed int: (any).si, \
+ unsigned int: (any).ui, \
+ signed long int: (any).sli, \
+ unsigned long int: (any).uli, \
+ signed long long int: (any).slli, \
+ unsigned long long int: (any).ulli)
+
+/*
+ * dlog format specifier for types. Note, these aren't the standard specifiers
+ * for the types.
+ */
+#define hftest_dlog_format(x) \
+ _Generic((x), \
+ bool: "%u", \
+ char: "%c", \
+ signed char: "%d", \
+ unsigned char: "%u", \
+ signed short: "%d", \
+ unsigned short: "%u", \
+ signed int: "%d", \
+ unsigned int: "%u", \
+ signed long int: "%d", \
+ unsigned long int: "%u", \
+ signed long long int: "%d", \
+ unsigned long long int: "%u")
+
+/* clang-format on */
+
+#define HFTEST_LOG_FAILURE() \
+ dlog(HFTEST_LOG_PREFIX "Failure: %s:%u\n", __FILE__, __LINE__);
+
+#define HFTEST_ASSERT_OP(lhs, rhs, op, fatal) \
+ do { \
+ union hftest_any lhs_value; \
+ union hftest_any rhs_value; \
+ hftest_any_get(lhs_value, lhs) = (lhs); \
+ hftest_any_get(rhs_value, rhs) = (rhs); \
+ if (!(hftest_any_get(lhs_value, lhs) \
+ op hftest_any_get(rhs_value, rhs))) { \
+ struct hftest_context *ctx = hftest_get_context(); \
+ ++ctx->failures; \
+ HFTEST_LOG_FAILURE(); \
+ dlog(HFTEST_LOG_PREFIX HFTEST_LOG_INDENT \
+ "%s %s %s (%s=", \
+ #lhs, #op, #rhs, #lhs); \
+ dlog(hftest_dlog_format(lhs), \
+ hftest_any_get(lhs_value, lhs)); \
+ dlog(", %s=", #rhs); \
+ dlog(hftest_dlog_format(rhs), \
+ hftest_any_get(rhs_value, rhs)); \
+ dlog(")\n"); \
+ if (fatal) { \
+ ctx->abort(); \
+ } \
+ } \
+ } while (0)
+
+/**
+ * Select the service to run in a service VM.
+ */
+#define HFTEST_SERVICE_SELECT(vm_id, service, send_buffer) \
+ do { \
+ struct hf_vcpu_run_return run_res; \
+ \
+ /* \
+ * Let the service configure its mailbox and wait for a \
+ * message. \
+ */ \
+ run_res = hf_vcpu_run(vm_id, 0); \
+ ASSERT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT); \
+ \
+ /* Send the selected service to run and let it be handled. */ \
+ memcpy(send_buffer, service, strlen(service)); \
+ ASSERT_EQ(hf_mailbox_send(vm_id, strlen(service)), 0); \
+ run_res = hf_vcpu_run(vm_id, 0); \
+ ASSERT_EQ(run_res.code, HF_VCPU_RUN_YIELD); \
+ } while (0)
+
+#define HFTEST_SERVICE_SEND_BUFFER() hftest_get_context()->send
+#define HFTEST_SERVICE_RECV_BUFFER() hftest_get_context()->recv