From 0aadf227c19a90a55d6744a153c0b9c121dab5ff Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Thu, 19 May 2016 22:53:23 -0700 Subject: [PATCH] Benchmark & speed up SampleIndexIterator I'm seeing what is possible performance-wise in the current C++ before trying out Go and Rust implementations. * use the google benchmark framework and some real data. * use release builds - I hadn't done this in a while, and there were a few compile errors that manifested only in release mode. Update the readme to suggest using a release build. * optimize the varint decoder and SampleIndexIterator to branch less. * enable link-time optimization for release builds. * add some support for feedback-directed optimization. Ideally "make" would automatically produce the "generate" build outputs with a different object/library/executable suffix, run the generate benchmark, and then produce the "use" builds. This is not that fancy; you have to run an arcane command: alias cmake='cmake -DCMAKE_BUILD_TYPE=Release' cmake -DPROFILE_GENERATE=true -DPROFILE_USE=false .. && \ make recording-bench && \ src/recording-bench && \ cmake -DPROFILE_GENERATE=false -DPROFILE_USE=true .. && \ make recording-bench && \ perf stat -e cycles,instructions,branches,branch-misses \ src/recording-bench --benchmark_repetitions=5 That said, the results are dramatic - at least 50% improvement. (The results weren't stable before as small tweaks to the code caused a huge shift in performance, presumably something something branch alignment something something.) --- .gitignore | 1 + CMakeLists.txt | 36 ++++++++++++++- README.md | 6 +-- prep.sh | 6 +-- src/CMakeLists.txt | 8 +++- src/coding-test.cc | 45 ++++++++++++++++--- src/coding.cc | 63 +++++++++++++++++++------- src/common.h | 3 ++ src/filesystem.h | 37 ---------------- src/http.cc | 4 ++ src/moonfire-db.h | 1 + src/mp4.h | 1 + src/recording-bench.cc | 66 ++++++++++++++++++++++++++++ src/recording-test.cc | 1 + src/recording.cc | 27 +++++++----- src/recording.h | 8 +--- src/sqlite.cc | 2 +- src/testdata/video_sample_index.bin | Bin 0 -> 6038 bytes src/testutil.h | 43 ++++++++++++++++++ src/uuid.h | 6 --- 20 files changed, 273 insertions(+), 91 deletions(-) create mode 100644 src/recording-bench.cc create mode 100644 src/testdata/video_sample_index.bin diff --git a/.gitignore b/.gitignore index 42b5fb9..5bb3549 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.swp build debug +release obj-* cameras.sql debian/files diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d2395..91d092b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,25 @@ else() set(CMAKE_CXX_STANDARD 11) endif() -set(CMAKE_CXX_FLAGS "-Wall -Werror -pedantic-errors ${CMAKE_CXX_FLAGS}") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb") +set(CMAKE_CXX_FLAGS "-Wall -Werror -pedantic-errors -ggdb ${CMAKE_CXX_FLAGS}") + +option(LTO "Use link-time optimization" ON) +option(FPROFILE_GENERATE "Compile executable to generate usage data" OFF) +option(FPROFILE_USE "Compile executable using generated usage data" OFF) + +if(LTO) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto") + set(CMAKE_AR "gcc-ar") + set(CMAKE_RANLIB "gcc-ranlib") + set(CMAKE_LD "gcc-ld") +endif() + +if(PROFILE_GENERATE) + set(CMAKE_CXX_FLAGS "-fprofile-generate ${CMAKE_CXX_FLAGS}") +endif() +if(PROFILE_USE) + set(CMAKE_CXX_FLAGS "-fprofile-use -fprofile-correction ${CMAKE_CXX_FLAGS}") +endif() # # Dependencies. @@ -115,6 +132,21 @@ set_target_properties(GMock PROPERTIES IMPORTED_LOCATION "${binary_dir}/${CMAKE_STATIC_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}" IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}") +ExternalProject_Add( + GBenchmarkProject + URL "https://github.com/google/benchmark/archive/v1.0.0.tar.gz" + URL_HASH "SHA1=4f778985dce02d2e63262e6f388a24b595254a93" + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + INSTALL_COMMAND "") +ExternalProject_Get_Property(GBenchmarkProject source_dir binary_dir) +set(GBenchmark_INCLUDE_DIR ${source_dir}/include) +add_library(GBenchmark STATIC IMPORTED) +add_dependencies(GBenchmark GBenchmarkProject) +set_target_properties(GBenchmark PROPERTIES + IMPORTED_LOCATION "${binary_dir}/src/${CMAKE_STATIC_LIBRARY_PREFIX}benchmark${CMAKE_STATIC_LIBRARY_SUFFIX}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}") + # # Subdirectories. # diff --git a/README.md b/README.md index c55264d..1666f13 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,9 @@ For instructions, you can skip to "[Camera configuration and hard disk mounting] Once prerequisites are installed, Moonfire NVR can be built as follows: - $ mkdir build - $ cd build - $ cmake .. + $ mkdir release + $ cd release + $ cmake -DCMAKE_BUILD_TYPE=Release .. $ make $ sudo make install diff --git a/prep.sh b/prep.sh index 3086c96..fd30beb 100755 --- a/prep.sh +++ b/prep.sh @@ -169,9 +169,9 @@ fi # if [ "${FORCE_BUILD:-0}" -eq 1 ]; then # Remove previous build, if any - [ -d build ] && rm -fr build 2>/dev/null - mkdir build; cd build - cmake .. && make && sudo make install + [ -d release ] && rm -fr release 2>/dev/null + mkdir release; cd release + cmake -DCMAKE_BUILD_TYPE=Release .. && make && sudo make install if [ -x "${SERVICE_BIN}" ]; then echo "Binary installed..."; echo else diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 17a3172..fceca19 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,7 @@ install_programs(/bin FILES moonfire-nvr) # Tests. include_directories(${GTest_INCLUDE_DIR}) include_directories(${GMock_INCLUDE_DIR}) +include_directories(${GBenchmark_INCLUDE_DIR}) set(MOONFIRE_NVR_TESTS coding @@ -88,8 +89,13 @@ set(MOONFIRE_NVR_TESTS foreach(test ${MOONFIRE_NVR_TESTS}) add_executable(${test}-test ${test}-test.cc testutil.cc) - target_link_libraries(${test}-test GTest GMock moonfire-nvr-lib ) + target_link_libraries(${test}-test GTest GMock moonfire-nvr-lib) add_test(NAME ${test}-test COMMAND ${test}-test WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) endforeach(test) + +foreach(bench recording) + add_executable(${bench}-bench ${bench}-bench.cc testutil.cc) + target_link_libraries(${bench}-bench GTest GMock GBenchmark moonfire-nvr-lib) +endforeach(bench) diff --git a/src/coding-test.cc b/src/coding-test.cc index 15909ee..d358d7a 100644 --- a/src/coding-test.cc +++ b/src/coding-test.cc @@ -58,18 +58,53 @@ TEST(VarintTest, Simple) { EXPECT_EQ(UINT32_C(1), out); EXPECT_TRUE(DecodeVar32(&p, &out, &error_message)); EXPECT_EQ(UINT32_C(300), out); + EXPECT_EQ(0, p.size()); +} + +TEST(VarintTest, AllDecodeSizes) { + std::string error_message; + const uint32_t kToDecode[]{ + 1, + 1 | (2 << 7), + 1 | (2 << 7) | (3 << 14), + 1 | (2 << 7) | (3 << 14) | (4 << 21), + 1 | (2 << 7) | (3 << 14) | (4 << 21) | (5 << 28), + }; + for (size_t i = 0; i < sizeof(kToDecode) / sizeof(kToDecode[0]); ++i) { + auto in = kToDecode[i]; + std::string foo; + AppendVar32(in, &foo); + ASSERT_EQ(i + 1, foo.size()); + re2::StringPiece p(foo); + uint32_t out; + + // Slow path: last bytes of the buffer. + DecodeVar32(&p, &out, &error_message); + EXPECT_EQ(in, out) << "i: " << i; + EXPECT_EQ(0, p.size()) << "i: " << i; + + // Fast path: plenty of bytes in the buffer. + foo.append(4, 0); + p = foo; + DecodeVar32(&p, &out, &error_message); + EXPECT_EQ(in, out); + EXPECT_EQ(4, p.size()); + } } TEST(VarintTest, DecodeErrors) { re2::StringPiece empty; uint32_t out; std::string error_message; - EXPECT_FALSE(DecodeVar32(&empty, &out, &error_message)); - EXPECT_EQ("buffer underrun", error_message); - re2::StringPiece partial("\x80", 1); - EXPECT_FALSE(DecodeVar32(&partial, &out, &error_message)); - EXPECT_EQ("buffer underrun", error_message); + for (auto input : + {re2::StringPiece("", 0), re2::StringPiece("\x80", 1), + re2::StringPiece("\x80\x80", 2), re2::StringPiece("\x80\x80\x80", 3), + re2::StringPiece("\x80\x80\x80\x80", 4)}) { + EXPECT_FALSE(DecodeVar32(&input, &out, &error_message)) << "input: " + << input; + EXPECT_EQ("buffer underrun", error_message); + } re2::StringPiece too_big("\x80\x80\x80\x80\x10", 5); EXPECT_FALSE(DecodeVar32(&too_big, &out, &error_message)); diff --git a/src/coding.cc b/src/coding.cc index 4b9ca10..0c024a7 100644 --- a/src/coding.cc +++ b/src/coding.cc @@ -31,6 +31,7 @@ // coding.cc: see coding.h. #include "coding.h" +#include "common.h" namespace moonfire_nvr { @@ -50,24 +51,56 @@ void AppendVar32Slow(uint32_t in, std::string *out) { bool DecodeVar32Slow(re2::StringPiece *in, uint32_t *out_p, std::string *error_message) { + // The fast path is inlined; this function is called only when + // byte 0 is present and >= 0x80. + size_t left = in->size() - 1; auto p = reinterpret_cast(in->data()); - auto end = p + in->size(); - uint32_t out = 0; - int shift = 0; - do { - if (p == end) { - *error_message = "buffer underrun"; - return false; + uint32_t v = uint32_t(p[0] & 0x7f); + size_t size = 1; + + // Aid branch prediction in two ways: + // * have a faster path which doesn't check for buffer underrun on every + // byte if there's plenty of bytes left or the last byte is not continued. + // * fully unroll the loop + if (left >= 4 || (p[left] & 0x80) == 0) { + v |= uint32_t(p[size] & 0x7f) << 7; + if (p[size++] & 0x80) { + v |= uint32_t(p[size] & 0x7f) << 14; + if (p[size++] & 0x80) { + v |= uint32_t(p[size] & 0x7f) << 21; + if (p[size++] & 0x80) { + if (UNLIKELY(p[size] & 0xf0)) { + *error_message = "integer overflow"; + return false; + } + v |= uint32_t(p[size++] & 0x7f) << 28; + } + } } - if (shift == 28 && *p & 0xf0) { - *error_message = "integer overflow"; - return false; + *out_p = v; + in->remove_prefix(size); + return true; + } + + // Slowest path. + if (LIKELY(left)) { + v |= uint32_t(p[size] & 0x7f) << 7; + if (p[size++] & 0x80 && --left > 0) { + v |= uint32_t(p[size] & 0x7f) << 14; + if (p[size++] & 0x80 && --left > 0) { + v |= uint32_t(p[size] & 0x7f) << 21; + if (p[size++] & 0x80) { + --left; + } + } } - out |= uint32_t(*p & 0x7f) << shift; - shift += 7; - } while ((*p++ & 0x80) != 0); - *out_p = out; - in->remove_prefix(reinterpret_cast(p) - in->data()); + } + if (UNLIKELY(left == 0 && p[size - 1] & 0x80)) { + *error_message = "buffer underrun"; + return false; + } + *out_p = v; + in->remove_prefix(size); return true; } diff --git a/src/common.h b/src/common.h index 72cbd8a..e5a149f 100644 --- a/src/common.h +++ b/src/common.h @@ -33,6 +33,9 @@ #ifndef MOONFIRE_NVR_COMMON_H #define MOONFIRE_NVR_COMMON_H +#define LIKELY(x) __builtin_expect((x), 1) +#define UNLIKELY(x) __builtin_expect((x), 0) + namespace moonfire_nvr { // Return value for *ForEach callbacks. diff --git a/src/filesystem.h b/src/filesystem.h index ab30b55..32ab47c 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -45,7 +45,6 @@ #include #include #include -#include #include #include "common.h" @@ -96,42 +95,6 @@ class File { virtual int Write(re2::StringPiece data, size_t *bytes_written) = 0; }; -class MockFile : public File { - public: - MOCK_CONST_METHOD0(name, const std::string &()); - MOCK_METHOD3(Access, int(const char *, int, int)); - MOCK_METHOD0(Close, int()); - - // The std::unique_ptr variants of Open are wrapped here because gmock's - // SetArgPointee doesn't work well with std::unique_ptr. - - int Open(const char *path, int flags, std::unique_ptr *f) final { - File *f_tmp = nullptr; - int ret = OpenRaw(path, flags, &f_tmp); - f->reset(f_tmp); - return ret; - } - - int Open(const char *path, int flags, mode_t mode, - std::unique_ptr *f) final { - File *f_tmp = nullptr; - int ret = OpenRaw(path, flags, mode, &f_tmp); - f->reset(f_tmp); - return ret; - } - - MOCK_METHOD3(Open, int(const char *, int, int *)); - MOCK_METHOD4(Open, int(const char *, int, mode_t, int *)); - MOCK_METHOD3(OpenRaw, int(const char *, int, File **)); - MOCK_METHOD4(OpenRaw, int(const char *, int, mode_t, File **)); - MOCK_METHOD3(Read, int(void *, size_t, size_t *)); - MOCK_METHOD1(Stat, int(struct stat *)); - MOCK_METHOD0(Sync, int()); - MOCK_METHOD1(Truncate, int(off_t)); - MOCK_METHOD1(Unlink, int(const char *)); - MOCK_METHOD2(Write, int(re2::StringPiece, size_t *)); -}; - // Interface to the local filesystem. There's typically one per program, // but it's an abstract class for testability. Thread-safe. class Filesystem { diff --git a/src/http.cc b/src/http.cc index 8df1cdc..e2a3d00 100644 --- a/src/http.cc +++ b/src/http.cc @@ -387,7 +387,11 @@ void HttpServe(const std::shared_ptr &file, evhttp_request *req) { << ": Client requested whole file of size " << file->size(); http_status = HTTP_OK; http_status_str = "OK"; + break; } + + default: + LOG(FATAL) << "unexpected range_type: " << static_cast(range_type); } // Successful reply started; add common headers and send. diff --git a/src/moonfire-db.h b/src/moonfire-db.h index 3cad69a..11d575e 100644 --- a/src/moonfire-db.h +++ b/src/moonfire-db.h @@ -61,6 +61,7 @@ #define MOONFIRE_NVR_MOONFIRE_DB_H #include +#include #include #include #include diff --git a/src/mp4.h b/src/mp4.h index 56d557b..742e104 100644 --- a/src/mp4.h +++ b/src/mp4.h @@ -36,6 +36,7 @@ #ifndef MOONFIRE_NVR_MP4_H #define MOONFIRE_NVR_MP4_H +#include #include #include diff --git a/src/recording-bench.cc b/src/recording-bench.cc new file mode 100644 index 0000000..9abc1db --- /dev/null +++ b/src/recording-bench.cc @@ -0,0 +1,66 @@ +// This file is part of Moonfire NVR, a security camera network video recorder. +// Copyright (C) 2016 Scott Lamb +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// In addition, as a special exception, the copyright holders give +// permission to link the code of portions of this program with the +// OpenSSL library under certain conditions as described in each +// individual source file, and distribute linked combinations including +// the two. +// +// You must obey the GNU General Public License in all respects for all +// of the code used other than OpenSSL. If you modify file(s) with this +// exception, you may extend this exception to your version of the +// file(s), but you are not obligated to do so. If you do not wish to do +// so, delete this exception statement from your version. If you delete +// this exception statement from all source files in the program, then +// also delete it here. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// recording-bench.cc: benchmarks of the recording.h interface. + +#include +#include + +#include "recording.h" +#include "testutil.h" + +DECLARE_bool(alsologtostderr); + +static void BM_Iterator(benchmark::State &state) { + using moonfire_nvr::ReadFileOrDie; + using moonfire_nvr::SampleIndexIterator; + // state.PauseTiming(); + std::string index = ReadFileOrDie("../src/testdata/video_sample_index.bin"); + // state.ResumeTiming(); + while (state.KeepRunning()) { + SampleIndexIterator it(index); + while (!it.done()) it.Next(); + CHECK(!it.has_error()) << it.error(); + } + state.SetBytesProcessed(int64_t(state.iterations()) * int64_t(index.size())); +} +BENCHMARK(BM_Iterator); + +int main(int argc, char **argv) { + FLAGS_alsologtostderr = true; + + // Sadly, these two flag-parsing libraries don't appear to get along. + // google::ParseCommandLineFlags(&argc, &argv, true); + benchmark::Initialize(&argc, argv); + + google::InitGoogleLogging(argv[0]); + benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/src/recording-test.cc b/src/recording-test.cc index e6d1d51..c52df0f 100644 --- a/src/recording-test.cc +++ b/src/recording-test.cc @@ -40,6 +40,7 @@ #include "recording.h" #include "string.h" +#include "testutil.h" DECLARE_bool(alsologtostderr); diff --git a/src/recording.cc b/src/recording.cc index afd26e8..323511b 100644 --- a/src/recording.cc +++ b/src/recording.cc @@ -36,6 +36,7 @@ #include #include +#include "common.h" #include "coding.h" #include "string.h" @@ -81,22 +82,23 @@ void SampleIndexEncoder::AddSample(int32_t duration_90k, int32_t bytes, void SampleIndexIterator::Next() { uint32_t raw1; uint32_t raw2; - pos_ += bytes_internal(); - if (data_.empty() || !DecodeVar32(&data_, &raw1, &error_) || - !DecodeVar32(&data_, &raw2, &error_)) { + pos_ += bytes_; + if (UNLIKELY(data_.empty()) || + UNLIKELY(!DecodeVar32(&data_, &raw1, &error_)) || + UNLIKELY(!DecodeVar32(&data_, &raw2, &error_))) { done_ = true; return; } start_90k_ += duration_90k_; int32_t duration_90k_delta = Unzigzag32(raw1 >> 1); duration_90k_ += duration_90k_delta; - if (duration_90k_ < 0) { + if (UNLIKELY(duration_90k_ < 0)) { error_ = StrCat("negative duration ", duration_90k_, " after applying delta ", duration_90k_delta); done_ = true; return; } - if (duration_90k_ == 0 && !data_.empty()) { + if (UNLIKELY(duration_90k_ == 0 && !data_.empty())) { error_ = StrCat("zero duration only allowed at end; have ", data_.size(), "bytes left."); done_ = true; @@ -104,15 +106,15 @@ void SampleIndexIterator::Next() { } is_key_ = raw1 & 0x01; int32_t bytes_delta = Unzigzag32(raw2); - if (is_key_) { - bytes_key_ += bytes_delta; + if (UNLIKELY(is_key_)) { + bytes_ = bytes_key_ += bytes_delta; } else { - bytes_nonkey_ += bytes_delta; + bytes_ = bytes_nonkey_ += bytes_delta; } - if (bytes_internal() <= 0) { - error_ = StrCat("non-positive bytes ", bytes_internal(), - " after applying delta ", bytes_delta, " to ", - (is_key_ ? "key" : "non-key"), " frame at ts ", start_90k_); + if (UNLIKELY(bytes_ <= 0)) { + error_ = StrCat("non-positive bytes ", bytes_, " after applying delta ", + bytes_delta, " to ", (is_key_ ? "key" : "non-key"), + " frame at ts ", start_90k_); done_ = true; return; } @@ -128,6 +130,7 @@ void SampleIndexIterator::Clear() { duration_90k_ = 0; bytes_key_ = 0; bytes_nonkey_ = 0; + bytes_ = 0; is_key_ = false; done_ = true; } diff --git a/src/recording.h b/src/recording.h index 620fe27..a764e4a 100644 --- a/src/recording.h +++ b/src/recording.h @@ -134,7 +134,7 @@ class SampleIndexIterator { int32_t end_90k() const { return start_90k_ + duration_90k(); } int32_t bytes() const { DCHECK(!done_); - return bytes_internal(); + return bytes_; } bool is_key() const { DCHECK(!done_); @@ -144,16 +144,12 @@ class SampleIndexIterator { private: void Clear(); - // Return the bytes taken by the current sample, or 0 after Clear(). - int64_t bytes_internal() const { - return is_key_ ? bytes_key_ : bytes_nonkey_; - } - re2::StringPiece data_; std::string error_; int64_t pos_; int32_t start_90k_; int32_t duration_90k_; + int32_t bytes_; // bytes taken by the current sample, or 0 after Clear(). int32_t bytes_key_; int32_t bytes_nonkey_; bool is_key_; diff --git a/src/sqlite.cc b/src/sqlite.cc index d3108cd..791a2ea 100644 --- a/src/sqlite.cc +++ b/src/sqlite.cc @@ -376,7 +376,7 @@ Statement Database::Prepare(re2::StringPiece sql, size_t *used, bool RunStatements(DatabaseContext *ctx, re2::StringPiece stmts, std::string *error_message) { while (true) { - size_t used; + size_t used = 0; auto stmt = ctx->db()->Prepare(stmts, &used, error_message); if (!stmt.valid()) { // Statement didn't parse. If |error_message| is empty, there are just no diff --git a/src/testdata/video_sample_index.bin b/src/testdata/video_sample_index.bin new file mode 100644 index 0000000000000000000000000000000000000000..36d943a9760899b1d944a72ce12657261e48ee3d GIT binary patch literal 6038 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 LM*gJ=mGl1x&Ld#; literal 0 HcmV?d00001 diff --git a/src/testutil.h b/src/testutil.h index cad816b..d9b936c 100644 --- a/src/testutil.h +++ b/src/testutil.h @@ -37,7 +37,9 @@ #include #include +#include "filesystem.h" #include "http.h" +#include "uuid.h" namespace moonfire_nvr { @@ -106,6 +108,47 @@ class ScopedMockLog : public google::LogSink { LogEntry pending_; }; +class MockUuidGenerator : public UuidGenerator { + public: + MOCK_METHOD0(Generate, Uuid()); +}; + +class MockFile : public File { + public: + MOCK_CONST_METHOD0(name, const std::string &()); + MOCK_METHOD3(Access, int(const char *, int, int)); + MOCK_METHOD0(Close, int()); + + // The std::unique_ptr variants of Open are wrapped here because gmock's + // SetArgPointee doesn't work well with std::unique_ptr. + + int Open(const char *path, int flags, std::unique_ptr *f) final { + File *f_tmp = nullptr; + int ret = OpenRaw(path, flags, &f_tmp); + f->reset(f_tmp); + return ret; + } + + int Open(const char *path, int flags, mode_t mode, + std::unique_ptr *f) final { + File *f_tmp = nullptr; + int ret = OpenRaw(path, flags, mode, &f_tmp); + f->reset(f_tmp); + return ret; + } + + MOCK_METHOD3(Open, int(const char *, int, int *)); + MOCK_METHOD4(Open, int(const char *, int, mode_t, int *)); + MOCK_METHOD3(OpenRaw, int(const char *, int, File **)); + MOCK_METHOD4(OpenRaw, int(const char *, int, mode_t, File **)); + MOCK_METHOD3(Read, int(void *, size_t, size_t *)); + MOCK_METHOD1(Stat, int(struct stat *)); + MOCK_METHOD0(Sync, int()); + MOCK_METHOD1(Truncate, int(off_t)); + MOCK_METHOD1(Unlink, int(const char *)); + MOCK_METHOD2(Write, int(re2::StringPiece, size_t *)); +}; + } // namespace moonfire_nvr #endif // MOONFIRE_NVR_TESTUTIL_H diff --git a/src/uuid.h b/src/uuid.h index de6865a..4c3f255 100644 --- a/src/uuid.h +++ b/src/uuid.h @@ -34,7 +34,6 @@ #ifndef MOONFIRE_NVR_UUID_H #define MOONFIRE_NVR_UUID_H -#include #include #include @@ -75,11 +74,6 @@ class UuidGenerator { virtual Uuid Generate() = 0; }; -class MockUuidGenerator : public UuidGenerator { - public: - MOCK_METHOD0(Generate, Uuid()); -}; - UuidGenerator *GetRealUuidGenerator(); } // namespace moonfire_nvr