Add gtest unittest framework.

bsdiff and bspatch are in C and had no unittest. This patch keeps
those programs as C code, but adds the C++ gtest unittest framework
to allow testing them. Two simple unittests added to validate that the
unittests work.

BUG=None
TEST=make test; ./unittests

Change-Id: I8bca6b0c6bc5d5880464183d50a602c9886d20d0
diff --git a/.gitignore b/.gitignore
index f4bff1b..f74437d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@
 # Executables
 bsdiff
 bspatch
+unittests
diff --git a/Makefile b/Makefile
index a1aa54e..212a92a 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,7 @@
 
 INSTALL = install
 CFLAGS += -O3 -Wall -Werror
+CXXFLAGS += -std=c++11
 
 DESTDIR ?=
 PREFIX = /usr
@@ -18,19 +19,48 @@
 INSTALL_PROGRAM ?= $(INSTALL) -c -m 755
 INSTALL_MAN ?= $(INSTALL) -c -m 444
 
-.PHONY: all clean
+.PHONY: all test clean
 all: $(BINARIES)
+test: unittests
 clean:
-	rm -f *.o $(BINARIES)
+	rm -f *.o $(BINARIES) unittests
 
-bsdiff: bsdiff.o
-bsdiff: LDLIBS += -lbz2 -ldivsufsort -ldivsufsort64
-bspatch: bspatch.o extents.o exfile.o
-bspatch: LDLIBS += -lbz2
+BSDIFF_LIBS = -lbz2 -ldivsufsort -ldivsufsort64
+BSDIFF_OBJS = \
+  bsdiff.o
 
-bspatch.o: extents.h exfile.h
-extents.o: extents.h exfile.h
-exfile.o: exfile.h
+BSPATCH_LIBS = -lbz2
+BSPATCH_OBJS = \
+  bspatch.o \
+  exfile.o \
+  extents.o
+
+UNITTEST_LIBS = -lgtest
+UNITTEST_OBJS = \
+  bsdiff_unittest.o \
+  test_utils.o \
+  testrunner.o
+
+bsdiff: $(BSDIFF_OBJS) bsdiff_main.o
+bsdiff: LDLIBS += $(BSDIFF_LIBS)
+
+bspatch: $(BSPATCH_OBJS) bspatch_main.o
+bspatch: LDLIBS += $(BSPATCH_LIBS)
+
+unittests: LDLIBS += $(BSDIFF_LIBS) $(BSPATCH_LIBS) $(UNITTEST_LIBS)
+unittests: $(BSPATCH_OBJS) $(BSDIFF_OBJS) $(UNITTEST_OBJS)
+	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $^ $(LDLIBS)
+
+# Source file dependencies.
+bsdiff.o: bsdiff.c
+bsdiff_main.o: bsdiff_main.c bsdiff.h
+bsdiff_unittest.o: bsdiff_unittest.cc bsdiff.h test_utils.h
+bspatch.o: bspatch.c exfile.h extents.h
+bspatch_main.o: bspatch_main.c bspatch.h
+exfile.o: exfile.c exfile.h
+extents.o: extents.c extents.h exfile.h
+testrunner.o: testrunner.cc
+test_utils.o: test_utils.cc test_utils.h
 
 install:
 	mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MAN1DIR)
diff --git a/bsdiff.c b/bsdiff.c
index 90d9134..8154376 100644
--- a/bsdiff.c
+++ b/bsdiff.c
@@ -3,7 +3,7 @@
  * All rights reserved
  *
  * Redistribution and use in source and binary forms, with or without
- * modification, are permitted providing that the following conditions 
+ * modification, are permitted providing that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
@@ -102,8 +102,8 @@
 	if(x<0) buf[7]|=0x80;
 }
 
-int main(int argc,char *argv[])
-{
+int bsdiff(const char* old_filename, const char* new_filename,
+           const char* patch_filename) {
 	int fd;
 	u_char *old,*new;
 	off_t oldsize,newsize;
@@ -122,16 +122,14 @@
 	BZFILE * pfbz2;
 	int bz2err;
 
-	if(argc!=4) errx(1,"usage: %s oldfile newfile patchfile\n",argv[0]);
-
 	/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
 		that we never try to malloc(0) and get a NULL pointer */
-	if(((fd=open(argv[1],O_RDONLY,0))<0) ||
+	if(((fd=open(old_filename,O_RDONLY,0))<0) ||
 		((oldsize=lseek(fd,0,SEEK_END))==-1) ||
 		((old=malloc(oldsize+1))==NULL) ||
 		(lseek(fd,0,SEEK_SET)!=0) ||
 		(read(fd,old,oldsize)!=oldsize) ||
-		(close(fd)==-1)) err(1,"%s",argv[1]);
+		(close(fd)==-1)) err(1,"%s",old_filename);
 
 	if(((I=malloc((oldsize+1)*sizeof(saidx_t)))==NULL)) err(1,NULL);
 
@@ -139,12 +137,12 @@
 
 	/* Allocate newsize+1 bytes instead of newsize bytes to ensure
 		that we never try to malloc(0) and get a NULL pointer */
-	if(((fd=open(argv[2],O_RDONLY,0))<0) ||
+	if(((fd=open(new_filename,O_RDONLY,0))<0) ||
 		((newsize=lseek(fd,0,SEEK_END))==-1) ||
 		((new=malloc(newsize+1))==NULL) ||
 		(lseek(fd,0,SEEK_SET)!=0) ||
 		(read(fd,new,newsize)!=newsize) ||
-		(close(fd)==-1)) err(1,"%s",argv[2]);
+		(close(fd)==-1)) err(1,"%s",new_filename);
 
 	if(((db=malloc(newsize+1))==NULL) ||
 		((eb=malloc(newsize+1))==NULL)) err(1,NULL);
@@ -152,8 +150,8 @@
 	eblen=0;
 
 	/* Create the patch file */
-	if ((pf = fopen(argv[3], "w")) == NULL)
-		err(1, "%s", argv[3]);
+	if ((pf = fopen(patch_filename, "w")) == NULL)
+		err(1, "%s", patch_filename);
 
 	/* Header is
 		0	8	 "BSDIFF40"
@@ -170,7 +168,7 @@
 	offtout(0, header + 16);
 	offtout(newsize, header + 24);
 	if (fwrite(header, 32, 1, pf) != 1)
-		err(1, "fwrite(%s)", argv[3]);
+		err(1, "fwrite(%s)", patch_filename);
 
 	/* Compute the differences, writing ctrl as we go */
 	if ((pfbz2 = BZ2_bzWriteOpen(&bz2err, pf, 9, 0, 0)) == NULL)
@@ -199,7 +197,7 @@
 				(old[scsc+lastoffset] == new[scsc]))
 				oldscore++;
 
-			if(((len==oldscore) && (len!=0)) || 
+			if(((len==oldscore) && (len!=0)) ||
 				(len>oldscore+8)) break;
 
 			if((scan+lastoffset<oldsize) &&
@@ -316,7 +314,7 @@
 	if (fseeko(pf, 0, SEEK_SET))
 		err(1, "fseeko");
 	if (fwrite(header, 32, 1, pf) != 1)
-		err(1, "fwrite(%s)", argv[3]);
+		err(1, "fwrite(%s)", patch_filename);
 	if (fclose(pf))
 		err(1, "fclose");
 
diff --git a/bsdiff.h b/bsdiff.h
new file mode 100644
index 0000000..f797b13
--- /dev/null
+++ b/bsdiff.h
@@ -0,0 +1,20 @@
+/* Copyright 2015 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.
+ */
+
+#ifndef _BSDIFF_BSDIFF_H_
+#define _BSDIFF_BSDIFF_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int bsdiff(const char* old_filename, const char* new_filename,
+           const char* patch_filename);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _BSDIFF_BSDIFF_H_ */
diff --git a/bsdiff_main.c b/bsdiff_main.c
new file mode 100644
index 0000000..49491bf
--- /dev/null
+++ b/bsdiff_main.c
@@ -0,0 +1,15 @@
+/* Copyright 2015 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.
+ */
+
+#include <err.h>
+
+#include "bsdiff.h"
+
+int main(int argc, char *argv[]) {
+  if (argc!=4)
+    errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);
+
+  return bsdiff(argv[1], argv[2], argv[3]);
+}
diff --git a/bsdiff_unittest.cc b/bsdiff_unittest.cc
new file mode 100644
index 0000000..9b4cd53
--- /dev/null
+++ b/bsdiff_unittest.cc
@@ -0,0 +1,60 @@
+// Copyright 2015 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.
+
+#include "bsdiff.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include <vector>
+
+#include "test_utils.h"
+
+using std::string;
+using std::vector;
+using test_utils::BsdiffPatchFile;
+
+class BsdiffTest : public testing::Test {
+ protected:
+  BsdiffTest()
+    : old_file_("bsdiff_oldfile.XXXXXX"),
+      new_file_("bsdiff_newfile.XXXXXX"),
+      patch_file_("bsdiff_patchfile.XXXXXX") {
+  }
+  ~BsdiffTest() override {}
+
+  test_utils::ScopedTempFile old_file_;
+  test_utils::ScopedTempFile new_file_;
+  test_utils::ScopedTempFile patch_file_;
+};
+
+// Check that a file with no changes has a very small patch (no extra data).
+TEST_F(BsdiffTest, EqualEmptyFiles) {
+  // Empty old and new files.
+  EXPECT_EQ(0, bsdiff(old_file_.filename().c_str(),
+                      new_file_.filename().c_str(),
+                      patch_file_.filename().c_str()));
+  BsdiffPatchFile patch;
+  EXPECT_TRUE(patch.LoadFromFile(patch_file_.filename()));
+  EXPECT_TRUE(patch.IsValid());
+
+  // An empty bz2 file will have 14 bytes.
+  EXPECT_EQ(14, patch.diff_len);
+  EXPECT_EQ(14, patch.extra_len);
+}
+
+TEST_F(BsdiffTest, EqualSmallFiles) {
+  string some_text = "Hello world!";
+  vector<uint8_t> vec_some_text(some_text.begin(), some_text.end());
+  test_utils::WriteFile(old_file_.filename(), vec_some_text);
+  EXPECT_EQ(0, bsdiff(old_file_.filename().c_str(),
+                      new_file_.filename().c_str(),
+                      patch_file_.filename().c_str()));
+  BsdiffPatchFile patch;
+  EXPECT_TRUE(patch.LoadFromFile(patch_file_.filename()));
+  EXPECT_TRUE(patch.IsValid());
+
+  // An empty bz2 file will have 14 bytes.
+  EXPECT_EQ(14, patch.diff_len);
+  EXPECT_EQ(14, patch.extra_len);
+}
diff --git a/bspatch.c b/bspatch.c
index ab77722..b44991c 100644
--- a/bspatch.c
+++ b/bspatch.c
@@ -3,7 +3,7 @@
  * All rights reserved
  *
  * Redistribution and use in source and binary forms, with or without
- * modification, are permitted providing that the following conditions 
+ * modification, are permitted providing that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
@@ -75,12 +75,10 @@
 	return ex_arr;
 }
 
-#define USAGE_TEMPLATE_STR \
-    "usage: %s oldfile newfile patchfile [old-extents new-extents]\n" \
-    "with extents taking the form \"off_1:len_1,...,off_n:len_n\"\n"
-
-int main(int argc,char * argv[])
-{
+int bspatch(
+    const char* old_filename, const char* new_filename,
+    const char* patch_filename,
+    const char* old_extents, const char* new_extents) {
 	FILE * f, * cpf, * dpf, * epf;
 	BZFILE * cpfbz2, * dpfbz2, * epfbz2;
 	int cbz2err, dbz2err, ebz2err;
@@ -94,19 +92,18 @@
 	off_t lenread;
 	off_t i, j;
 
-	if ((argc != 6) && (argc != 4)) errx(1, USAGE_TEMPLATE_STR, argv[0]);
-	int using_extents = (argc == 6);
+	int using_extents = (old_extents != NULL || new_extents != NULL);
 
 	/* Open patch file */
-	if ((f = fopen(argv[3], "r")) == NULL)
-		err(1, "fopen(%s)", argv[3]);
+	if ((f = fopen(patch_filename, "r")) == NULL)
+		err(1, "fopen(%s)", patch_filename);
 
 	/*
 	File format:
 		0	8	"BSDIFF40"
 		8	8	X
 		16	8	Y
-		24	8	sizeof(newfile)
+		24	8	sizeof(new_filename)
 		32	X	bzip2(control block)
 		32+X	Y	bzip2(diff block)
 		32+X+Y	???	bzip2(extra block)
@@ -119,7 +116,7 @@
 	if (fread(header, 1, 32, f) < 32) {
 		if (feof(f))
 			errx(1, "Corrupt patch\n");
-		err(1, "fread(%s)", argv[3]);
+		err(1, "fread(%s)", patch_filename);
 	}
 
 	/* Check for appropriate magic */
@@ -135,25 +132,25 @@
 
 	/* Close patch file and re-open it via libbzip2 at the right places */
 	if (fclose(f))
-		err(1, "fclose(%s)", argv[3]);
-	if ((cpf = fopen(argv[3], "r")) == NULL)
-		err(1, "fopen(%s)", argv[3]);
+		err(1, "fclose(%s)", patch_filename);
+	if ((cpf = fopen(patch_filename, "r")) == NULL)
+		err(1, "fopen(%s)", patch_filename);
 	if (fseeko(cpf, 32, SEEK_SET))
-		err(1, "fseeko(%s, %lld)", argv[3],
+		err(1, "fseeko(%s, %lld)", patch_filename,
 		    (long long)32);
 	if ((cpfbz2 = BZ2_bzReadOpen(&cbz2err, cpf, 0, 0, NULL, 0)) == NULL)
 		errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
-	if ((dpf = fopen(argv[3], "r")) == NULL)
-		err(1, "fopen(%s)", argv[3]);
+	if ((dpf = fopen(patch_filename, "r")) == NULL)
+		err(1, "fopen(%s)", patch_filename);
 	if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
-		err(1, "fseeko(%s, %lld)", argv[3],
+		err(1, "fseeko(%s, %lld)", patch_filename,
 		    (long long)(32 + bzctrllen));
 	if ((dpfbz2 = BZ2_bzReadOpen(&dbz2err, dpf, 0, 0, NULL, 0)) == NULL)
 		errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
-	if ((epf = fopen(argv[3], "r")) == NULL)
-		err(1, "fopen(%s)", argv[3]);
+	if ((epf = fopen(patch_filename, "r")) == NULL)
+		err(1, "fopen(%s)", patch_filename);
 	if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
-		err(1, "fseeko(%s, %lld)", argv[3],
+		err(1, "fseeko(%s, %lld)", patch_filename,
 		    (long long)(32 + bzctrllen + bzdatalen));
 	if ((epfbz2 = BZ2_bzReadOpen(&ebz2err, epf, 0, 0, NULL, 0)) == NULL)
 		errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
@@ -161,16 +158,17 @@
 	/* Open input file for reading. */
 	if (using_extents) {
 		size_t ex_count = 0;
-		ex_t *ex_arr = parse_extent_str(argv[4], &ex_count);
-		old_file = exfile_fopen(argv[1], "r", ex_arr, ex_count, free);
+		ex_t *ex_arr = parse_extent_str(old_extents, &ex_count);
+		old_file = exfile_fopen(new_filename, "r", ex_arr, ex_count,
+		                        free);
 	} else {
-		old_file = fopen(argv[1], "r");
+		old_file = fopen(new_filename, "r");
 	}
 	if (!old_file ||
 	    fseek(old_file, 0, SEEK_END) != 0 ||
 	    (oldsize = ftell(old_file)) < 0 ||
 	    fseek(old_file, 0, SEEK_SET) != 0)
-		err(1, "cannot obtain the size of %s", argv[1]);
+		err(1, "cannot obtain the size of %s", new_filename);
 	off_t old_file_pos = 0;
 
 	if((new=malloc(newsize+1))==NULL) err(1,NULL);
@@ -247,20 +245,21 @@
 	BZ2_bzReadClose(&dbz2err, dpfbz2);
 	BZ2_bzReadClose(&ebz2err, epfbz2);
 	if (fclose(cpf) || fclose(dpf) || fclose(epf))
-		err(1, "fclose(%s)", argv[3]);
+		err(1, "fclose(%s)", patch_filename);
 
 	/* Write the new file */
 	if (using_extents) {
 		size_t ex_count = 0;
-		ex_t *ex_arr = parse_extent_str(argv[5], &ex_count);
-		new_file = exfile_fopen(argv[2], "w", ex_arr, ex_count, free);
+		ex_t *ex_arr = parse_extent_str(new_extents, &ex_count);
+		new_file = exfile_fopen(new_filename, "w", ex_arr, ex_count,
+		                        free);
 	} else {
-		new_file = fopen(argv[2], "w");
+		new_file = fopen(new_filename, "w");
 	}
 	if (!new_file ||
 	    fwrite_unlocked(new, 1, newsize, new_file) != newsize ||
 	    fclose(new_file) == EOF)
-		err(1,"%s",argv[2]);
+		err(1,"%s",new_filename);
 
 	free(new);
 
diff --git a/bspatch.h b/bspatch.h
new file mode 100644
index 0000000..8071cc4
--- /dev/null
+++ b/bspatch.h
@@ -0,0 +1,22 @@
+/* Copyright 2015 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.
+ */
+
+#ifndef _BSDIFF_BSPATCH_H_
+#define _BSDIFF_BSPATCH_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int bspatch(
+    const char* old_filename, const char* new_filename,
+    const char* patch_filename,
+    const char* old_extents, const char* new_extents);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* _BSDIFF_BSPATCH_H_ */
diff --git a/bspatch_main.c b/bspatch_main.c
new file mode 100644
index 0000000..a5442e9
--- /dev/null
+++ b/bspatch_main.c
@@ -0,0 +1,27 @@
+// Copyright 2015 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.
+
+#include <err.h>
+#include <stdlib.h>
+
+#include "bspatch.h"
+
+#define USAGE_TEMPLATE_STR \
+    "usage: %s oldfile newfile patchfile [old-extents new-extents]\n" \
+    "with extents taking the form \"off_1:len_1,...,off_n:len_n\"\n"
+
+int main(int argc, char * argv[]) {
+  const char* old_extents = NULL;
+  const char* new_extents = NULL;
+
+  if ((argc != 6) && (argc != 4))
+    errx(1, USAGE_TEMPLATE_STR, argv[0]);
+
+  if (argc == 6) {
+    old_extents = argv[4];
+    new_extents = argv[5];
+  }
+
+  return bspatch(argv[1], argv[2], argv[3], old_extents, new_extents);
+}
diff --git a/exfile.c b/exfile.c
index 1cc324f..cd7e1d6 100644
--- a/exfile.c
+++ b/exfile.c
@@ -1,15 +1,19 @@
+// Copyright 2015 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.
+
 #define _GNU_SOURCE
+#include "exfile.h"
+
 #include <assert.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <fcntl.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#include "exfile.h"
-
 /*
  * Extent files implementation.  Some things worth noting:
  *
diff --git a/exfile.h b/exfile.h
index a804456..b09e456 100644
--- a/exfile.h
+++ b/exfile.h
@@ -1,5 +1,9 @@
-#ifndef __EXFILE_H
-#define __EXFILE_H
+// Copyright 2015 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.
+
+#ifndef _BSDIFF_EXFILE_H_
+#define _BSDIFF_EXFILE_H_
 
 #include <stdio.h>
 
@@ -46,4 +50,4 @@
 FILE *exfile_fdopen(int fd, const char *fopen_mode, ex_t *ex_arr,
                     size_t ex_count, void (*ex_free)(void *));
 
-#endif /* __EXFILE_H */
+#endif  /* _BSDIFF_EXFILE_H_ */
diff --git a/extents.c b/extents.c
index 8e1ec19..5f99e85 100644
--- a/extents.c
+++ b/extents.c
@@ -1,11 +1,15 @@
+// Copyright 2015 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.
+
+#include "extents.h"
+
 #include <assert.h>
 #include <errno.h>
 #include <limits.h>
 #include <stdint.h>
 #include <stdlib.h>
 
-#include "extents.h"
-
 
 #define TRUE 1
 #define FALSE 0
diff --git a/extents.h b/extents.h
index 7af1458..f0c795e 100644
--- a/extents.h
+++ b/extents.h
@@ -1,5 +1,9 @@
-#ifndef __EXTENTS_H
-#define __EXTENTS_H
+// Copyright 2015 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.
+
+#ifndef _BSDIFF_EXTENTS_H_
+#define _BSDIFF_EXTENTS_H_
 
 #include "exfile.h"
 
@@ -19,4 +23,4 @@
  * deallocated with free(3). */
 ex_t *extents_parse(const char *ex_str, ex_t *ex_arr, size_t *ex_count_p);
 
-#endif /* __EXTENTS_H */
+#endif  /* _BSDIFF_EXTENTS_H_ */
diff --git a/test_utils.cc b/test_utils.cc
new file mode 100644
index 0000000..b7540e6
--- /dev/null
+++ b/test_utils.cc
@@ -0,0 +1,134 @@
+// Copyright 2015 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.
+
+#include "test_utils.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+namespace {
+
+// If |path| is absolute, or explicit relative to the current working directory,
+// leaves it as is. Otherwise, if TMPDIR is defined in the environment and is
+// non-empty, prepends it to |path|. Otherwise, prepends /tmp.  Returns the
+// resulting path.
+const string PrependTmpdir(const string& path) {
+  if (path[0] == '/')
+    return path;
+
+  const char *tmpdir = getenv("TMPDIR");
+  const string prefix = (tmpdir && *tmpdir ? tmpdir : "/tmp");
+  return prefix + "/" + path;
+}
+
+bool MakeTempFile(const string& base_filename_template,
+                  string* filename) {
+  const string filename_template = PrependTmpdir(base_filename_template);
+  vector<char> result(filename_template.size() + 1, '\0');
+  memcpy(result.data(), filename_template.data(), filename_template.size());
+
+  int mkstemp_fd = mkstemp(result.data());
+  if (mkstemp_fd < 0) {
+    perror("mkstemp()");
+    return false;
+  }
+  close(mkstemp_fd);
+
+  if (filename)
+    *filename = result.data();
+  return true;
+}
+
+}  // namespace
+
+namespace test_utils {
+
+bool ReadFile(const string& path, vector<uint8_t>* out) {
+  FILE* fp = fopen(path.c_str(), "r");
+  if (!fp)
+    return false;
+  out->clear();
+
+  uint8_t buf[16 * 1024];
+  while (true) {
+    size_t bytes_read = fread(buf, 1, sizeof(buf), fp);
+    if (!bytes_read)
+      break;
+    out->insert(out->end(), buf, buf + bytes_read);
+  }
+  bool result = !ferror(fp);
+  fclose(fp);
+  return result;
+}
+
+bool WriteFile(const string& path, vector<uint8_t> contents) {
+  FILE* fp = fopen(path.c_str(), "r");
+  if (!fp)
+    return false;
+  size_t written = fwrite(contents.data(), 1, contents.size(), fp);
+  bool result = written == contents.size() && !ferror(fp);
+  fclose(fp);
+  return result;
+}
+
+ScopedTempFile::ScopedTempFile(const string& pattern) {
+  EXPECT_TRUE(MakeTempFile(pattern, &filename_));
+}
+
+ScopedTempFile::~ScopedTempFile() {
+  if (!filename_.empty() && unlink(filename_.c_str()) < 0) {
+    perror("Unable to remove temporary file");
+  }
+}
+
+bool BsdiffPatchFile::LoadFromFile(const string& filename) {
+  vector<uint8_t> contents;
+  if (!ReadFile(filename, &contents))
+    return false;
+  file_size = contents.size();
+  // Check that the file includes at least the header.
+  TEST_AND_RETURN_FALSE(contents.size() >= kHeaderSize);
+  magic = string(contents.data(), contents.data() + 8);
+  memcpy(&ctrl_len, contents.data() + 8, sizeof(ctrl_len));
+  memcpy(&diff_len, contents.data() + 16, sizeof(diff_len));
+  memcpy(&new_file_len, contents.data() + 24, sizeof(new_file_len));
+
+  TEST_AND_RETURN_FALSE(file_size >= kHeaderSize + ctrl_len + diff_len);
+  extra_len = file_size - kHeaderSize - ctrl_len - diff_len;
+
+  // Sanity check before we attempt to parse the bz2 streams.
+  TEST_AND_RETURN_FALSE(ctrl_len >= 0);
+  TEST_AND_RETURN_FALSE(diff_len >= 0);
+
+  uint8_t* ptr = contents.data() + kHeaderSize;
+  bz2_ctrl = vector<uint8_t>(ptr, ptr + ctrl_len);
+  ptr += ctrl_len;
+  bz2_diff = vector<uint8_t>(ptr, ptr + diff_len);
+  ptr += diff_len;
+  bz2_extra = vector<uint8_t>(ptr, ptr + extra_len);
+
+  return true;
+}
+
+bool BsdiffPatchFile::IsValid() const {
+  TEST_AND_RETURN_FALSE(ctrl_len >= 0);
+  TEST_AND_RETURN_FALSE(diff_len >= 0);
+  TEST_AND_RETURN_FALSE(new_file_len >= 0);
+
+  // TODO(deymo): Test that the length of the decompressed bz2 streams |diff|
+  // plus |extra| are equal to the |new_file_len|.
+  // TODO(deymo): Test that all the |bz2_ctrl| triplets (x, y, z) have a "x"
+  // and "y" value >= 0 ("z" can be negative).
+  return true;
+}
+
+}  // namespace test_utils
diff --git a/test_utils.h b/test_utils.h
new file mode 100644
index 0000000..52cebab
--- /dev/null
+++ b/test_utils.h
@@ -0,0 +1,88 @@
+// Copyright 2015 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.
+
+#ifndef _BSDIFF_TEST_UTILS_H_
+#define _BSDIFF_TEST_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#define TEST_AND_RETURN_FALSE(_x)                                              \
+  do {                                                                         \
+    if (!static_cast<bool>(_x)) {                                              \
+      fprintf(stderr, "%s failed.", #_x);                                      \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+namespace test_utils {
+
+// Reads all the contents of the file |path| into |out|. Returns whether it
+// read up to the end of file.
+bool ReadFile(const std::string& path, std::vector<uint8_t>* out);
+
+// Overrides the file |path| with the contents passed in |out|. Returns whether
+// the operation succeeded.
+bool WriteFile(const std::string& path, std::vector<uint8_t> contents);
+
+// Utility class to create and delete a temp file.
+class ScopedTempFile {
+ public:
+  // Creates a temp file with the passed |pattern|. The pattern should end with
+  // "XXXXXX", that will be replaced with a random string. The file will be
+  // removed when this instance is destroyed.
+  explicit ScopedTempFile(const std::string& pattern);
+  ~ScopedTempFile();
+
+  std::string filename() const { return filename_; }
+  const char* c_str() const { return filename_.c_str(); }
+
+  // Releases the temporary file. It will not be deleted when this instance is
+  // destroyed.
+  void release() { filename_.clear(); }
+
+ private:
+  std::string filename_;
+};
+
+// This struct representes a parsed BSDIFF40 file.
+struct BsdiffPatchFile {
+  static const size_t kHeaderSize = 32;
+
+  // Parses a BSDIFF40 file and stores the contents in the local methods.
+  bool LoadFromFile(const std::string& filename);
+
+  // Returns wheter the patch file is valid.
+  bool IsValid() const;
+
+  // The magic string in the header file. Normally "BSDIFF40".
+  std::string magic;
+
+  // The length of the first (ctrl) bzip2 stream. Negative values are invalid.
+  int64_t ctrl_len = -1;
+
+  // The length of the first (diff) bzip2 stream. Negative values are invalid.
+  int64_t diff_len = -1;
+
+  // The length of the first (diff) bzip2 stream. This value is not stored in
+  // the file, but generated based on the |file_size|.
+  uint64_t extra_len = 0;
+
+  // The length of the new file after applying the patch. Negative values are
+  // invalid.
+  int64_t new_file_len = -1;
+
+  // The three compressed streams.
+  std::vector<uint8_t> bz2_ctrl;
+  std::vector<uint8_t> bz2_diff;
+  std::vector<uint8_t> bz2_extra;
+
+  uint64_t file_size = 0;
+};
+
+
+}  // namespace test_utils
+
+
+#endif  // _BSDIFF_TEST_UTILS_H_
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..40710fd
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,10 @@
+// Copyright 2015 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.
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}