mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-05-03 08:20:26 -04:00
254 lines
9.6 KiB
C++
254 lines
9.6 KiB
C++
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
|
// Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
//
|
|
// util_test.cc: tests of the util.h interface.
|
|
|
|
#include <gflags/gflags.h>
|
|
#include <glog/logging.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "http.h"
|
|
#include "string.h"
|
|
#include "testutil.h"
|
|
|
|
DECLARE_bool(alsologtostderr);
|
|
|
|
using moonfire_nvr::internal::ParseRangeHeader;
|
|
using moonfire_nvr::internal::RangeHeaderType;
|
|
|
|
using testing::_;
|
|
using testing::AnyNumber;
|
|
using testing::DoAll;
|
|
using testing::Return;
|
|
using testing::SetArgPointee;
|
|
|
|
namespace moonfire_nvr {
|
|
namespace {
|
|
|
|
class MockFileSlice : public FileSlice {
|
|
public:
|
|
MOCK_CONST_METHOD0(size, int64_t());
|
|
MOCK_CONST_METHOD3(AddRange, bool(ByteRange, EvBuffer *, std::string *));
|
|
};
|
|
|
|
TEST(EvBufferTest, AddFileTest) {
|
|
std::string dir = PrepareTempDirOrDie("http");
|
|
std::string foo_filename = StrCat(dir, "/foo");
|
|
WriteFileOrDie(foo_filename, "foo");
|
|
|
|
int in_fd = open(foo_filename.c_str(), O_RDONLY);
|
|
PCHECK(in_fd >= 0) << "open: " << foo_filename;
|
|
std::string error_message;
|
|
|
|
// Ensure adding the whole file succeeds.
|
|
EvBuffer buf1;
|
|
ASSERT_TRUE(buf1.AddFile(in_fd, 0, 3, &error_message)) << error_message;
|
|
in_fd = -1;
|
|
EXPECT_EQ(3u, evbuffer_get_length(buf1.get()));
|
|
|
|
// Ensure adding an empty region succeeds.
|
|
EvBuffer buf2;
|
|
ASSERT_TRUE(buf2.AddFile(in_fd, 0, 0, &error_message)) << error_message;
|
|
EXPECT_EQ(0u, evbuffer_get_length(buf2.get()));
|
|
|
|
// Ensure adding part of a file after another string succeeds.
|
|
in_fd = open(foo_filename.c_str(), O_RDONLY);
|
|
EvBuffer buf3;
|
|
buf3.Add("1234");
|
|
ASSERT_TRUE(buf3.AddFile(in_fd, 1, 2, &error_message)) << error_message;
|
|
auto size3 = evbuffer_get_length(buf3.get());
|
|
EXPECT_EQ(6u, size3);
|
|
std::string buf3_contents = std::string(
|
|
reinterpret_cast<const char *>(evbuffer_pullup(buf3.get(), size3)),
|
|
size3);
|
|
EXPECT_EQ("1234oo", buf3_contents);
|
|
}
|
|
|
|
class FileSlicesTest : public testing::Test {
|
|
protected:
|
|
FileSlicesTest() {
|
|
EXPECT_CALL(a_, size()).Times(AnyNumber()).WillRepeatedly(Return(5));
|
|
EXPECT_CALL(b_, size()).Times(AnyNumber()).WillRepeatedly(Return(13));
|
|
EXPECT_CALL(c_, size()).Times(AnyNumber()).WillRepeatedly(Return(7));
|
|
EXPECT_CALL(d_, size()).Times(AnyNumber()).WillRepeatedly(Return(17));
|
|
EXPECT_CALL(e_, size()).Times(AnyNumber()).WillRepeatedly(Return(19));
|
|
|
|
slices_.Append(&a_);
|
|
slices_.Append(&b_);
|
|
slices_.Append(&c_);
|
|
slices_.Append(&d_);
|
|
slices_.Append(&e_);
|
|
}
|
|
|
|
FileSlices slices_;
|
|
testing::StrictMock<MockFileSlice> a_;
|
|
testing::StrictMock<MockFileSlice> b_;
|
|
testing::StrictMock<MockFileSlice> c_;
|
|
testing::StrictMock<MockFileSlice> d_;
|
|
testing::StrictMock<MockFileSlice> e_;
|
|
};
|
|
|
|
TEST_F(FileSlicesTest, Size) {
|
|
EXPECT_EQ(5 + 13 + 7 + 17 + 19, slices_.size());
|
|
}
|
|
|
|
TEST_F(FileSlicesTest, ExactSlice) {
|
|
// Exactly slice b.
|
|
std::string error_message;
|
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _)).WillOnce(Return(true));
|
|
EXPECT_TRUE(slices_.AddRange(ByteRange(5, 18), nullptr, &error_message))
|
|
<< error_message;
|
|
}
|
|
|
|
TEST_F(FileSlicesTest, Offset) {
|
|
// Part of slice b, all of slice c, and part of slice d.
|
|
std::string error_message;
|
|
EXPECT_CALL(b_, AddRange(ByteRange(12, 13), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(c_, AddRange(ByteRange(0, 7), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(d_, AddRange(ByteRange(0, 1), _, _)).WillOnce(Return(true));
|
|
EXPECT_TRUE(slices_.AddRange(ByteRange(17, 26), nullptr, &error_message))
|
|
<< error_message;
|
|
}
|
|
|
|
TEST_F(FileSlicesTest, Everything) {
|
|
std::string error_message;
|
|
EXPECT_CALL(a_, AddRange(ByteRange(0, 5), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(c_, AddRange(ByteRange(0, 7), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(d_, AddRange(ByteRange(0, 17), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(e_, AddRange(ByteRange(0, 19), _, _)).WillOnce(Return(true));
|
|
EXPECT_TRUE(slices_.AddRange(ByteRange(0, 61), nullptr, &error_message))
|
|
<< error_message;
|
|
}
|
|
|
|
TEST_F(FileSlicesTest, PropagateError) {
|
|
std::string error_message;
|
|
EXPECT_CALL(a_, AddRange(ByteRange(0, 5), _, _)).WillOnce(Return(true));
|
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _))
|
|
.WillOnce(DoAll(SetArgPointee<2>("asdf"), Return(false)));
|
|
EXPECT_FALSE(slices_.AddRange(ByteRange(0, 61), nullptr, &error_message));
|
|
EXPECT_EQ("asdf", error_message);
|
|
}
|
|
|
|
// Test the specific examples enumerated in RFC 2616 section 14.35.1.
|
|
TEST(RangeHeaderTest, Rfc_2616_Section_14_35_1) {
|
|
std::vector<ByteRange> ranges;
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=0-499", 10000, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(0, 500)));
|
|
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=500-999", 10000, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(500, 1000)));
|
|
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=-500", 10000, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(9500, 10000)));
|
|
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=9500-", 10000, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(9500, 10000)));
|
|
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=0-0,-1", 10000, &ranges));
|
|
EXPECT_THAT(ranges,
|
|
testing::ElementsAre(ByteRange(0, 1), ByteRange(9999, 10000)));
|
|
|
|
// Non-canonical ranges. Possibly the point of these is that the adjacent
|
|
// and overlapping ranges are supposed to be coalesced into one? I'm not
|
|
// going to do that for now...just trying to get something working...
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=500-600,601-999", 10000, &ranges));
|
|
EXPECT_THAT(ranges,
|
|
testing::ElementsAre(ByteRange(500, 601), ByteRange(601, 1000)));
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=500-700,601-999", 10000, &ranges));
|
|
EXPECT_THAT(ranges,
|
|
testing::ElementsAre(ByteRange(500, 701), ByteRange(601, 1000)));
|
|
}
|
|
|
|
TEST(RangeHeaderTest, Satisfiability) {
|
|
std::vector<ByteRange> ranges;
|
|
EXPECT_EQ(RangeHeaderType::kNotSatisfiable,
|
|
ParseRangeHeader("bytes=10000-", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=0-499,10000-", 10000, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(0, 500)));
|
|
EXPECT_EQ(RangeHeaderType::kNotSatisfiable,
|
|
ParseRangeHeader("bytes=-1", 0, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kNotSatisfiable,
|
|
ParseRangeHeader("bytes=0-0", 0, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kNotSatisfiable,
|
|
ParseRangeHeader("bytes=0-", 0, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=0-0", 1, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(0, 1)));
|
|
EXPECT_EQ(RangeHeaderType::kSatisfiable,
|
|
ParseRangeHeader("bytes=0-", 1, &ranges));
|
|
EXPECT_THAT(ranges, testing::ElementsAre(ByteRange(0, 1)));
|
|
}
|
|
|
|
TEST(RangeHeaderTest, AbsentOrInvalid) {
|
|
std::vector<ByteRange> ranges;
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader(nullptr, 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("foo=0-499", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("foo=0-499", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("bytes=499-0", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("bytes=", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("bytes=,", 10000, &ranges));
|
|
EXPECT_EQ(RangeHeaderType::kAbsentOrInvalid,
|
|
ParseRangeHeader("bytes=-", 10000, &ranges));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace moonfire_nvr
|
|
|
|
int main(int argc, char **argv) {
|
|
FLAGS_alsologtostderr = true;
|
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
|
testing::InitGoogleTest(&argc, argv);
|
|
google::InitGoogleLogging(argv[0]);
|
|
return RUN_ALL_TESTS();
|
|
}
|