A test with the mock arch

Change-Id: I9af804df9cf1019f0b9c5535d7d79aca28229f88
diff --git a/.clang-tidy b/.clang-tidy
index 2eacd36..ff9ba18 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,4 +1,5 @@
-Checks: 'readability-*,portability-*,performance-*,misc-*,bugprone-*,modernize-*'
-HeaderFilterRegex: '.*'
+Checks: 'readability-*,portability-*,performance-*,misc-*,bugprone-*,modernize-*
+         ,-modernize-deprecated-headers,-clang-analyzer-valist.Uninitialized'
+HeaderFilterRegex: '^(?!third_party).+'
 FormatStyle: file
 WarningsAsErrors: '*'
diff --git a/.gitmodules b/.gitmodules
index d274169..aecbc7a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -5,3 +5,7 @@
 [submodule "driver/linux"]
 	path = driver/linux
 	url = sso://hafnium/hafnium/driver/linux
+[submodule "third_party/googletest"]
+	path = third_party/googletest
+	url = sso://hafnium/hafnium/third_party/googletest
+	shallow = true
diff --git a/BUILD.gn b/BUILD.gn
index cef1259..622f91e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1,6 +1,23 @@
 import("//build/image/image.gni")
 
-group("arch_images") {
+group("tests") {
+  testonly = true
+
+  deps = [
+    ":unit_tests",
+    ":vm_tests",
+  ]
+}
+
+group("unit_tests") {
+  testonly = true
+
+  deps = [
+    "//src:unit_tests($mock_toolchain)",
+  ]
+}
+
+group("vm_tests") {
   testonly = true
 
   deps = [
diff --git a/Makefile b/Makefile
index 4c2476c..467b068 100644
--- a/Makefile
+++ b/Makefile
@@ -45,9 +45,9 @@
 .PHONY: format
 format:
 	@echo "Formatting..."
-	@find src/ -name *.c -o -name *.h | xargs clang-format -style file -i
-	@find inc/ -name *.c -o -name *.h | xargs clang-format -style file -i
-	@find test/ -name *.c -o -name *.h | xargs clang-format -style file -i
+	@find src/ -name *.c -o -name *.cc -o -name *.h | xargs clang-format -style file -i
+	@find inc/ -name *.c -o -name *.cc -o -name *.h | xargs clang-format -style file -i
+	@find test/ -name *.c -o -name *.cc -o -name *.h | xargs clang-format -style file -i
 	@find . \( -name *.gn -o -name *.gni \) -exec $(GN) format {} \;
 
 # see .clang-tidy.
@@ -55,12 +55,12 @@
 tidy: $(OUT_DIR)/build.ninja
 	@$(NINJA) -C $(OUT_DIR)
 	@echo "Tidying..."
-	@find src/ -name *.c -exec clang-tidy -p $(OUT_DIR) -fix {} \;
-	@find test/ -name *.c -exec clang-tidy -p $(OUT_DIR) -fix {} \;
+	@find src/ \( -name *.c -o -name *.cc \) -exec clang-tidy -p $(OUT_DIR) -fix {} \;
+	@find test/ \( -name *.c -o -name *.cc \) -exec clang-tidy -p $(OUT_DIR) -fix {} \;
 
 .PHONY: check
 check: $(OUT_DIR)/build.ninja
 	@$(NINJA) -C $(OUT_DIR)
 	@echo "Checking..."
-	@find src/ -name *.c -exec clang-check -p $(OUT_DIR) -analyze {} \;
-	@find test/ -name *.c -exec clang-check -p $(OUT_DIR) -analyze {} \;
+	@find src/ \( -name *.c -o -name *.cc \) -exec clang-check -p $(OUT_DIR) -analyze {} \;
+	@find test/ \( -name *.c -o -name *.cc \) -exec clang-check -p $(OUT_DIR) -analyze {} \;
diff --git a/build/BUILD.gn b/build/BUILD.gn
index cd5feb1..52793dd 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -16,39 +16,43 @@
 
   cflags_cc = [ "-std=c++14" ]
 
-  if (is_debug) {
-    defines = [ "DEBUG=1" ]
-  } else {
-    defines = [ "DEBUG=0" ]
-  }
-
-  # Configuration specific for the bare metal images
-  if (current_toolchain == arch_toolchain) {
+  if (current_toolchain == mock_toolchain ||
+      current_toolchain == arch_toolchain) {
     include_dirs = [
       "//inc",
       "//src/arch/${arch}/inc",
-      "${root_gen_dir}/inc",
     ]
 
-    cflags += [
-      "-mcpu=${arch_cpu}",
-      "-fno-stack-protector",
-      "-fno-builtin",
-      "-ffreestanding",
-      "-fpic",
-    ]
-
-    defines += [
+    defines = [
       # TODO: move these build args for the platforms
       "MAX_CPUS=8",
       "MAX_VMS=16",
       "STACK_SIZE=4096",
     ]
 
-    # TODO: this should be arch specific but it is currenly used by the
-    # platform generic code to map the memory
-    if (arch_aarch64_use_pl011) {
-      defines += [ "PL011_BASE=${arch_aarch64_pl011_base_address}" ]
+    if (is_debug) {
+      defines += [ "DEBUG=1" ]
+    } else {
+      defines += [ "DEBUG=0" ]
+    }
+
+    # Configuration specific for the bare metal images.
+    if (current_toolchain == arch_toolchain) {
+      include_dirs += [ "${root_gen_dir}/inc" ]
+
+      cflags += [
+        "-mcpu=${arch_cpu}",
+        "-fno-stack-protector",
+        "-fno-builtin",
+        "-ffreestanding",
+        "-fpic",
+      ]
+
+      # TODO: this should be arch specific but it is currenly used by the
+      # platform generic code to map the memory
+      if (arch_aarch64_use_pl011) {
+        defines += [ "PL011_BASE=${arch_aarch64_pl011_base_address}" ]
+      }
     }
   }
 }
@@ -67,5 +71,7 @@
 }
 
 config("lto") {
-  cflags = [ "-flto" ]
+  if (current_toolchain == arch_toolchain) {
+    cflags = [ "-flto" ]
+  }
 }
diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn
index b4100be..605539a 100644
--- a/build/BUILDCONFIG.gn
+++ b/build/BUILDCONFIG.gn
@@ -31,7 +31,7 @@
 assert(host_os == "linux", "Only linux builds are currently supported")
 
 # Check that the requested architecture is supported
-assert(arch == "aarch64", "Unsupported arch: $arch")
+assert(arch == "aarch64" || arch == "mock", "Unsupported arch: $arch")
 
 # Setup the standard variables
 if (target_os == "") {
@@ -77,10 +77,12 @@
 if (use_gcc) {
   host_toolchain = "//build/toolchain/host:gcc"
   target_toolchain = "//build/toolchain/host:gcc"
+  mock_toolchain = "//build/toolchain/host:gcc_mock_arch"
   arch_toolchain = "//build/toolchain/arch:gcc_${arch}"
 } else {
   host_toolchain = "//build/toolchain/host:clang"
   target_toolchain = "//build/toolchain/host:clang"
+  mock_toolchain = "//build/toolchain/host:clang_mock_arch"
   arch_toolchain = "//build/toolchain/arch:clang_${arch}"
 }
 
diff --git a/build/toolchain/host/BUILD.gn b/build/toolchain/host/BUILD.gn
index 8ab38e5..7382097 100644
--- a/build/toolchain/host/BUILD.gn
+++ b/build/toolchain/host/BUILD.gn
@@ -104,6 +104,12 @@
       command = "cp -af {{source}} {{output}}"
       description = "COPY {{source}} {{output}}"
     }
+
+    if (defined(invoker.toolchain_args)) {
+      toolchain_args = {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
   }
 }
 
@@ -115,9 +121,35 @@
   cflags = "-fcolor-diagnostics"
 }
 
+cc_toolchain("clang_mock_arch") {
+  ar = "llvm-ar"
+  cc = "clang"
+  cxx = "clang++"
+  cflags = "-fcolor-diagnostics"
+
+  toolchain_args = {
+    # When building for the host, use the mock architecture to make things
+    # testable.
+    arch = "mock"
+  }
+}
+
 cc_toolchain("gcc") {
   ar = "ar"
   cc = "gcc"
   cxx = "g++"
   cflags = "-fdiagnostics-color=always"
 }
+
+cc_toolchain("gcc_mock_arch") {
+  ar = "ar"
+  cc = "gcc"
+  cxx = "g++"
+  cflags = "-fdiagnostics-color=always"
+
+  toolchain_args = {
+    # When building for the host, use the mock architecture to make things
+    # testable.
+    arch = "mock"
+  }
+}
diff --git a/kokoro/ubuntu/test.sh b/kokoro/ubuntu/test.sh
index 6d146b6..cb28a0d 100755
--- a/kokoro/ubuntu/test.sh
+++ b/kokoro/ubuntu/test.sh
@@ -9,8 +9,14 @@
 set -x
 
 TIMEOUT="timeout --foreground"
-OUT="out/aarch64/qemu/clang_aarch64"
-HFTEST="timeout --foreground 30s ./test/vm/hftest.py --out $OUT --initrd"
+OUT="out/aarch64/qemu"
+HFTEST="$TIMEOUT 30s ./test/vm/hftest.py --out $OUT/clang_aarch64 --initrd"
+
+# Run the host unit tests
+mkdir -p $OUT/clang_mock_arch/test_log/unit_tests
+$TIMEOUT 30s $OUT/clang_mock_arch/unit_tests \
+  --gtest_output="xml:$OUT/clang_mock_arch/test_log/unit_tests/sponge_log.xml" \
+  | tee $OUT/clang_mock_arch/test_log/unit_tests/sponge_log.log
 
 # Run the tests with a timeout so they can't loop forever.
 $HFTEST primary_only_test
diff --git a/src/BUILD.gn b/src/BUILD.gn
index acac35f..82b12c8 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -46,6 +46,11 @@
   sources = [
     "fdt.c",
   ]
+
+  deps = [
+    ":common",
+    ":common_debug",
+  ]
 }
 
 source_set("memiter") {
@@ -53,3 +58,16 @@
     "memiter.c",
   ]
 }
+
+executable("unit_tests") {
+  testonly = true
+
+  sources = [
+    "fdt_test.cc",
+  ]
+
+  deps = [
+    ":fdt",
+    "//third_party:gtest_main",
+  ]
+}
diff --git a/src/arch/aarch64/inc/hf/arch/cpu.h b/src/arch/aarch64/inc/hf/arch/cpu.h
index f2fb4c8..b4d9614 100644
--- a/src/arch/aarch64/inc/hf/arch/cpu.h
+++ b/src/arch/aarch64/inc/hf/arch/cpu.h
@@ -1,6 +1,5 @@
 #pragma once
 
-#include <stdalign.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
diff --git a/src/arch/mock/BUILD.gn b/src/arch/mock/BUILD.gn
new file mode 100644
index 0000000..60c3428
--- /dev/null
+++ b/src/arch/mock/BUILD.gn
@@ -0,0 +1,6 @@
+# Mock implementation of putchar logs to the console.
+source_set("putchar") {
+  sources = [
+    "putchar.c",
+  ]
+}
diff --git a/src/arch/mock/inc/hf/arch/addr.h b/src/arch/mock/inc/hf/arch/addr.h
new file mode 100644
index 0000000..d8ba79d
--- /dev/null
+++ b/src/arch/mock/inc/hf/arch/addr.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <stdint.h>
+
+#define PAGE_BITS 12
+
+/* Integer type large enough to hold a physical address. */
+typedef uintptr_t uintpaddr_t;
+
+/* Integer type large enough to hold a virtual address. */
+typedef uintptr_t uintvaddr_t;
diff --git a/src/arch/mock/inc/hf/arch/cpu.h b/src/arch/mock/inc/hf/arch/cpu.h
new file mode 100644
index 0000000..9fbd041
--- /dev/null
+++ b/src/arch/mock/inc/hf/arch/cpu.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdint.h>
+
+struct arch_regs {
+	uint32_t r[5];
+};
+
+static inline struct cpu *cpu(void)
+{
+	/* TODO: */
+	return NULL;
+}
+
+static inline void arch_irq_disable(void)
+{
+	/* TODO */
+}
+
+static inline void arch_irq_enable(void)
+{
+	/* TODO */
+}
diff --git a/src/arch/mock/putchar.c b/src/arch/mock/putchar.c
new file mode 100644
index 0000000..3452b37
--- /dev/null
+++ b/src/arch/mock/putchar.c
@@ -0,0 +1,8 @@
+#include <stdio.h>
+
+#include "hf/dlog.h"
+
+void arch_putchar(char c)
+{
+	putchar(c);
+}
diff --git a/src/cpu.c b/src/cpu.c
index 217baea..cec490e 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -1,5 +1,7 @@
 #include "hf/cpu.h"
 
+#include <stdalign.h>
+
 #include "hf/arch/cpu.h"
 
 #include "hf/api.h"
diff --git a/src/fdt_test.cc b/src/fdt_test.cc
new file mode 100644
index 0000000..cb0d14c
--- /dev/null
+++ b/src/fdt_test.cc
@@ -0,0 +1,68 @@
+extern "C" {
+
+#include "hf/fdt.h"
+}
+
+#include <gmock/gmock.h>
+
+using ::testing::Eq;
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ *       model = "SomeModel";
+ *       compatible = "Nothing";
+ *       #address-cells = <2>;
+ *       #size-cells = <2>;
+ *
+ *       memory@0 {
+ *           device_type = "memory";
+ *           reg = <0x00000000 0x00000000 0x00000000 0x20000000>;
+ *       };
+ *
+ *       cpus {
+ *           #address-cells = <1>;
+ *           #size-cells = <0>;
+ *       };
+ *
+ * };
+ *
+ * $ dtc --boot-cpu 0 --in-format dts --out-format dtb --out-version 17 test.dts
+ * | xxd
+ */
+
+static const uint8_t test_dtb[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
+	0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0a,
+	0x00, 0x00, 0x00, 0x00, 0x53, 0x6f, 0x6d, 0x65, 0x4d, 0x6f, 0x64, 0x65,
+	0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08,
+	0x00, 0x00, 0x00, 0x06, 0x4e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
+	0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x40, 0x30, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2c,
+	0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x63, 0x70, 0x75, 0x73,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09,
+	0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x00, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74,
+	0x69, 0x62, 0x6c, 0x65, 0x00, 0x23, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
+	0x73, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x23, 0x73, 0x69, 0x7a,
+	0x65, 0x2d, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x00, 0x64, 0x65, 0x76, 0x69,
+	0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x00, 0x72, 0x65, 0x67, 0x00};
+
+TEST(fdt, total_size)
+{
+	EXPECT_THAT(fdt_total_size((struct fdt_header *)&test_dtb[0]),
+		    Eq(sizeof(test_dtb)));
+}
diff --git a/test/vm/hftest.py b/test/vm/hftest.py
index e21cf35..0e85dd2 100755
--- a/test/vm/hftest.py
+++ b/test/vm/hftest.py
@@ -9,6 +9,7 @@
 import xml.etree.ElementTree as ET
 
 import argparse
+import datetime
 import json
 import os
 import re
@@ -81,6 +82,9 @@
         test_re = re.compile(args.test or ".*")
         sponge = ET.Element("testsuites")
         sponge.set("name", args.initrd)
+        sponge.set(
+            "timestamp",
+            datetime.datetime.now().replace(microsecond=0).isoformat())
         for suite in tests["suites"]:
             if not suite_re.match(suite["name"]):
                 continue
@@ -93,6 +97,7 @@
                     continue
                 sponge_test = ET.SubElement(sponge_suite, "testcase")
                 sponge_test.set("name", test)
+                sponge_test.set("classname", suite['name'])
                 sponge_test.set("status", "run")
                 tests_run_from_suite += 1
                 if tests_run_from_suite == 1:
diff --git a/third_party/BUILD.gn b/third_party/BUILD.gn
new file mode 100644
index 0000000..af59e92
--- /dev/null
+++ b/third_party/BUILD.gn
@@ -0,0 +1,38 @@
+config("gtest_config") {
+  visibility = [ ":gtest" ]
+
+  include_dirs = [
+    "googletest/googletest/include",
+    "googletest/googlemock/include",
+  ]
+
+  ldflags = [ "-pthread" ]
+}
+
+source_set("gtest") {
+  testonly = true
+
+  sources = [
+    "googletest/googlemock/src/gmock-all.cc",
+    "googletest/googletest/src/gtest-all.cc",
+  ]
+
+  include_dirs = [
+    "googletest/googletest",
+    "googletest/googlemock",
+  ]
+
+  all_dependent_configs = [ ":gtest_config" ]
+}
+
+source_set("gtest_main") {
+  testonly = true
+
+  sources = [
+    "googletest/googlemock/src/gmock_main.cc",
+  ]
+
+  deps = [
+    ":gtest",
+  ]
+}
diff --git a/third_party/googletest b/third_party/googletest
new file mode 160000
index 0000000..c7a8998
--- /dev/null
+++ b/third_party/googletest
@@ -0,0 +1 @@
+Subproject commit c7a899855656fb0bba2c98ba70bc26333471eb92