diff options
author | Alex Deymo <deymo@chromium.org> | 2015-04-08 14:10:30 -0700 |
---|---|---|
committer | Alex Deymo <deymo@google.com> | 2015-09-23 10:06:36 -0700 |
commit | a5cff2283b9bc60da9f4e74ba07a2119f5616653 (patch) | |
tree | ad884837d726ea223b081ac7270b304f9f09646a | |
parent | 68b49a819183f290b56fb9bb6c79c4ec86d7ad09 (diff) | |
download | bsdiff-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-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 48 | ||||
-rw-r--r-- | bsdiff.c | 26 | ||||
-rw-r--r-- | bsdiff.h | 20 | ||||
-rw-r--r-- | bsdiff_main.c | 15 | ||||
-rw-r--r-- | bsdiff_unittest.cc | 60 | ||||
-rw-r--r-- | bspatch.c | 61 | ||||
-rw-r--r-- | bspatch.h | 22 | ||||
-rw-r--r-- | bspatch_main.c | 27 | ||||
-rw-r--r-- | exfile.c | 10 | ||||
-rw-r--r-- | exfile.h | 10 | ||||
-rw-r--r-- | extents.c | 8 | ||||
-rw-r--r-- | extents.h | 10 | ||||
-rw-r--r-- | test_utils.cc | 134 | ||||
-rw-r--r-- | test_utils.h | 88 | ||||
-rw-r--r-- | testrunner.cc | 10 |
16 files changed, 485 insertions, 65 deletions
@@ -11,3 +11,4 @@ # Executables bsdiff bspatch +unittests @@ -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) @@ -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); +} @@ -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); +} @@ -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: * @@ -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_ */ @@ -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 @@ -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(); +} |