Add kernel tests

BUG=chromium-os:38139
BRANCH=none
TEST=make runtests

Change-Id: Iee7c965d5c29063259c66d0ccb117c60f4f4a92e
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/42314
diff --git a/Makefile b/Makefile
index 6129697..d51a18b 100644
--- a/Makefile
+++ b/Makefile
@@ -425,6 +425,7 @@
 	vboot_common3_tests \
 	vboot_display_tests \
 	vboot_firmware_tests \
+	vboot_kernel_tests \
 	vboot_nvstorage_test
 
 # Grrr
@@ -952,6 +953,7 @@
 	${RUNTEST} ${BUILD_RUN}/tests/vboot_common3_tests ${TEST_KEYS}
 	${RUNTEST} ${BUILD_RUN}/tests/vboot_display_tests
 	${RUNTEST} ${BUILD_RUN}/tests/vboot_firmware_tests
+	${RUNTEST} ${BUILD_RUN}/tests/vboot_kernel_tests
 	${RUNTEST} ${BUILD_RUN}/tests/vboot_nvstorage_test
 
 .PHONY: runfutiltests
@@ -993,7 +995,8 @@
 
 # Generate addtional coverage stats just for firmware subdir, because the
 # per-directory stats for the whole project don't include their own subdirs.
-	lcov -e ${COV_INFO}.local '${SRCDIR}/firmware/*' \
+	lcov -r ${COV_INFO}.local '*/stub/*' -o ${COV_INFO}.nostub
+	lcov -e ${COV_INFO}.nostub '${SRCDIR}/firmware/*' \
 		-o ${COV_INFO}.firmware
 
 .PHONY: coverage
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index ec77de3..a9a6fdf 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -157,8 +157,7 @@
   int recovery = VBNV_RECOVERY_LK_UNSPECIFIED;
 
   /* Sanity Checks */
-  if (!params ||
-      !params->bytes_per_lba ||
+  if (!params->bytes_per_lba ||
       !params->ending_lba) {
     VBDEBUG(("LoadKernel() called with invalid params\n"));
     retval = VBERROR_INVALID_PARAMETER;
@@ -210,27 +209,26 @@
     kernel_subkey = &shared->kernel_subkey;
   }
 
-  do {
     /* Read GPT data */
     gpt.sector_bytes = (uint32_t)blba;
     gpt.drive_sectors = params->ending_lba + 1;
     if (0 != AllocAndReadGptData(params->disk_handle, &gpt)) {
       VBDEBUG(("Unable to read GPT data\n"));
       shcall->check_result = VBSD_LKC_CHECK_GPT_READ_ERROR;
-      break;
+      goto bad_gpt;
     }
 
     /* Initialize GPT library */
     if (GPT_SUCCESS != GptInit(&gpt)) {
       VBDEBUG(("Error parsing GPT\n"));
       shcall->check_result = VBSD_LKC_CHECK_GPT_PARSE_ERROR;
-      break;
+      goto bad_gpt;
     }
 
     /* Allocate kernel header buffers */
     kbuf = (uint8_t*)VbExMalloc(KBUF_SIZE);
     if (!kbuf)
-      break;
+      goto bad_gpt;
 
     /* Loop over candidate kernel partitions */
     while (GPT_SUCCESS == GptNextKernelEntry(&gpt, &part_start, &part_size)) {
@@ -516,7 +514,8 @@
 
 
     } /* while(GptNextKernelEntry) */
-  } while(0);
+
+  bad_gpt:
 
   /* Free kernel buffer */
   if (kbuf)
diff --git a/tests/vboot_kernel_tests.c b/tests/vboot_kernel_tests.c
new file mode 100644
index 0000000..0d49191
--- /dev/null
+++ b/tests/vboot_kernel_tests.c
@@ -0,0 +1,203 @@
+/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Tests for vboot_kernel.c
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cgptlib.h"
+#include "gbb_header.h"
+#include "gpt.h"
+#include "host_common.h"
+#include "load_kernel_fw.h"
+#include "test_common.h"
+#include "vboot_api.h"
+#include "vboot_common.h"
+#include "vboot_kernel.h"
+#include "vboot_nvstorage.h"
+
+#define LOGCALL(fmt, args...) sprintf(call_log + strlen(call_log), fmt, ##args)
+#define TEST_CALLS(expect_log) TEST_STR_EQ(call_log, expect_log, "  calls")
+
+/* Mock data */
+static char call_log[4096];
+static int disk_call_to_fail;
+static int disk_calls;
+static int gpt_init_fail;
+
+static GoogleBinaryBlockHeader gbb;
+static VbExDiskHandle_t handle;
+static VbNvContext vnc;
+static uint8_t shared_data[VB_SHARED_DATA_MIN_SIZE];
+static VbSharedDataHeader *shared = (VbSharedDataHeader *)shared_data;
+static LoadKernelParams lkp;
+
+static void ResetCallLog(void)
+{
+	*call_log = 0;
+}
+
+/**
+ * Reset mock data (for use before each test)
+ */
+static void ResetMocks(void)
+{
+	ResetCallLog();
+
+	disk_call_to_fail = 0;
+	disk_calls = 0;
+
+	gpt_init_fail = 0;
+
+	memset(&gbb, 0, sizeof(gbb));
+	gbb.major_version = GBB_MAJOR_VER;
+	gbb.minor_version = GBB_MINOR_VER;
+	gbb.flags = 0;
+
+	memset(&vnc, 0, sizeof(vnc));
+	VbNvSetup(&vnc);
+	VbNvTeardown(&vnc);                   /* So CRC gets generated */
+
+	memset(&shared_data, 0, sizeof(shared_data));
+	VbSharedDataInit(shared, sizeof(shared_data));
+
+	memset(&lkp, 0, sizeof(lkp));
+	lkp.nv_context = &vnc;
+	lkp.shared_data_blob = shared;
+	lkp.gbb_data = &gbb;
+	lkp.bytes_per_lba = 512;
+	lkp.ending_lba = 1023;
+}
+
+/* Mocks */
+
+VbError_t VbExDiskRead(VbExDiskHandle_t handle, uint64_t lba_start,
+                       uint64_t lba_count, void *buffer)
+{
+	LOGCALL("VbExDiskRead(h, %d, %d)\n", (int)lba_start, (int)lba_count);
+
+	if (++disk_calls == disk_call_to_fail)
+		return VBERROR_SIMULATED;
+
+	return VBERROR_SUCCESS;
+}
+
+VbError_t VbExDiskWrite(VbExDiskHandle_t handle, uint64_t lba_start,
+			uint64_t lba_count, const void *buffer)
+{
+	LOGCALL("VbExDiskWrite(h, %d, %d)\n", (int)lba_start, (int)lba_count);
+
+	if (++disk_calls == disk_call_to_fail)
+		return VBERROR_SIMULATED;
+
+	return VBERROR_SUCCESS;
+}
+
+int GptInit(GptData *gpt)
+{
+	return gpt_init_fail;
+}
+
+/**
+ * Test reading/writing GPT
+ */
+static void ReadWriteGptTest(void)
+{
+	GptData g;
+	GptHeader *h;
+
+	g.sector_bytes = 512;
+	g.drive_sectors = 1024;
+
+	ResetMocks();
+	TEST_EQ(AllocAndReadGptData(handle, &g), 0, "AllocAndRead");
+	TEST_CALLS("VbExDiskRead(h, 1, 1)\n"
+		   "VbExDiskRead(h, 2, 32)\n"
+		   "VbExDiskRead(h, 991, 32)\n"
+		   "VbExDiskRead(h, 1023, 1)\n");
+	ResetCallLog();
+	TEST_EQ(WriteAndFreeGptData(handle, &g), 0, "WriteAndFree");
+	TEST_CALLS("");
+
+	/* Data which is changed is written */
+	ResetMocks();
+	AllocAndReadGptData(handle, &g);
+	g.modified |= GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1;
+	ResetCallLog();
+	TEST_EQ(WriteAndFreeGptData(handle, &g), 0, "WriteAndFree mod 1");
+	TEST_CALLS("VbExDiskWrite(h, 1, 1)\n"
+		   "VbExDiskWrite(h, 2, 32)\n");
+
+	/* Data which is changed is written */
+	ResetMocks();
+	AllocAndReadGptData(handle, &g);
+	g.modified = -1;
+	ResetCallLog();
+	TEST_EQ(WriteAndFreeGptData(handle, &g), 0, "WriteAndFree mod all");
+	TEST_CALLS("VbExDiskWrite(h, 1, 1)\n"
+		   "VbExDiskWrite(h, 2, 32)\n"
+		   "VbExDiskWrite(h, 991, 32)\n"
+		   "VbExDiskWrite(h, 1023, 1)\n");
+
+	/* If legacy signature, don't modify GPT header/entries 1 */
+	ResetMocks();
+	AllocAndReadGptData(handle, &g);
+	h = (GptHeader *)g.primary_header;
+	memcpy(h->signature, GPT_HEADER_SIGNATURE2, GPT_HEADER_SIGNATURE_SIZE);
+	g.modified = -1;
+	ResetCallLog();
+	TEST_EQ(WriteAndFreeGptData(handle, &g), 0, "WriteAndFree mod all");
+	TEST_CALLS("VbExDiskWrite(h, 991, 32)\n"
+		   "VbExDiskWrite(h, 1023, 1)\n");
+
+	/* Error reading */
+	ResetMocks();
+	disk_call_to_fail = 1;
+	TEST_NEQ(AllocAndReadGptData(handle, &g), 0, "AllocAndRead disk fail");
+	WriteAndFreeGptData(handle, &g);
+
+	/* Error writing */
+	ResetMocks();
+	disk_call_to_fail = 5;
+	AllocAndReadGptData(handle, &g);
+	g.modified = -1;
+	TEST_NEQ(WriteAndFreeGptData(handle, &g), 0, "WriteAndFree disk fail");
+}
+
+/**
+ * Trivial invalid calls to LoadKernel()
+ */
+static void InvalidParamsTest(void)
+{
+	ResetMocks();
+	lkp.bytes_per_lba = 0;
+	TEST_EQ(LoadKernel(&lkp), VBERROR_INVALID_PARAMETER, "Bad lba size");
+
+	ResetMocks();
+	lkp.ending_lba = 0;
+	TEST_EQ(LoadKernel(&lkp), VBERROR_INVALID_PARAMETER, "Bad lba count");
+
+	ResetMocks();
+	lkp.bytes_per_lba = 128*1024;
+	TEST_EQ(LoadKernel(&lkp), VBERROR_INVALID_PARAMETER, "Huge lba size");
+
+	ResetMocks();
+	disk_call_to_fail = 1;
+	TEST_EQ(LoadKernel(&lkp), VBERROR_NO_KERNEL_FOUND, "Can't read disk");
+
+	ResetMocks();
+	gpt_init_fail = 1;
+	TEST_EQ(LoadKernel(&lkp), VBERROR_NO_KERNEL_FOUND, "Bad GPT");
+}
+
+int main(void)
+{
+	ReadWriteGptTest();
+	InvalidParamsTest();
+
+	return gTestSuccess ? 0 : 255;
+}