summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Deymo <deymo@chromium.org>2015-04-08 14:10:30 -0700
committerAlex Deymo <deymo@google.com>2015-09-23 10:06:36 -0700
commita5cff2283b9bc60da9f4e74ba07a2119f5616653 (patch)
treead884837d726ea223b081ac7270b304f9f09646a
parent68b49a819183f290b56fb9bb6c79c4ec86d7ad09 (diff)
downloadbsdiff-a5cff2283b9bc60da9f4e74ba07a2119f5616653.tar.gz
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
-rw-r--r--.gitignore1
-rw-r--r--Makefile48
-rw-r--r--bsdiff.c26
-rw-r--r--bsdiff.h20
-rw-r--r--bsdiff_main.c15
-rw-r--r--bsdiff_unittest.cc60
-rw-r--r--bspatch.c61
-rw-r--r--bspatch.h22
-rw-r--r--bspatch_main.c27
-rw-r--r--exfile.c10
-rw-r--r--exfile.h10
-rw-r--r--extents.c8
-rw-r--r--extents.h10
-rw-r--r--test_utils.cc134
-rw-r--r--test_utils.h88
-rw-r--r--testrunner.cc10
16 files changed, 485 insertions, 65 deletions
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 @@ BINARIES += $(BINARIES-y)
INSTALL = install
CFLAGS += -O3 -Wall -Werror
+CXXFLAGS += -std=c++11
DESTDIR ?=
PREFIX = /usr
@@ -18,19 +19,48 @@ MAN1DIR = $(MANDIR)/man1
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 @@ static void offtout(off_t x,u_char *buf)
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 @@ int main(int argc,char *argv[])
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 @@ int main(int argc,char *argv[])
/* 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 @@ int main(int argc,char *argv[])
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 @@ int main(int argc,char *argv[])
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 @@ int main(int argc,char *argv[])
(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 @@ int main(int argc,char *argv[])
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 9f18301..884c642 100644
--- a/bspatch.c
+++ b/bspatch.c
@@ -76,12 +76,10 @@ static ex_t *parse_extent_str(const char *ex_str, size_t *ex_count_p)
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;
@@ -95,19 +93,18 @@ int main(int argc,char * argv[])
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)
@@ -120,7 +117,7 @@ int main(int argc,char * argv[])
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 */
@@ -136,25 +133,25 @@ int main(int argc,char * argv[])
/* 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);
@@ -162,16 +159,17 @@ int main(int argc,char * argv[])
/* 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);
@@ -249,20 +247,21 @@ int main(int argc,char * argv[])
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_fopen(const char *path, const char *fopen_mode, ex_t *ex_arr,
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();
+}