diff --git a/README.md b/README.md index db2a4433..b82e4233 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,19 @@ This is the Stackdriver metadata agent. $ cd src $ make -j10 +# Testing + +1. Run all tests: + + $ cd src + $ make test + +2. Run individual tests: + + $ cd test + $ make + $ ./ + # Packaging 1. Build the DEB package: diff --git a/src/.gitignore b/src/.gitignore index 7ab262d8..4dee5759 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,4 +1,4 @@ -metadatad init-submodules build-cpp-netlib build-yaml-cpp +metadatad diff --git a/src/Makefile b/src/Makefile index da123cd7..afc41b43 100644 --- a/src/Makefile +++ b/src/Makefile @@ -60,6 +60,9 @@ INSTALL_DATA=$(INSTALL) -m 644 metadatad: $(LIBS) $(OBJS) $(CXX) -o $@ $(LDFLAGS) $^ $(LDLIBS) +test: + cd ../test && $(MAKE) test + $(OBJS): init-submodules install: metadatad diff --git a/src/time.cc b/src/time.cc index 9f60454c..687fad8c 100644 --- a/src/time.cc +++ b/src/time.cc @@ -16,6 +16,8 @@ #include "time.h" +#include +#include #include #include #include @@ -31,8 +33,11 @@ namespace { // See http://stackoverflow.com/questions/4137748. int UTCOffset() { const std::time_t local = std::time(nullptr); - const std::time_t utc = std::mktime(std::gmtime(&local)); - return utc - local; + std::tm utc_time = safe_gmtime(&local); + // Since we're only using this for conversion to UTC, always turn off DST. + utc_time.tm_isdst = 0; + const std::time_t utc = std::mktime(&utc_time); + return local - utc; } const int kUtcOffset = UTCOffset(); @@ -57,17 +62,36 @@ std::string ToString(const std::chrono::system_clock::time_point& t) { std::chrono::system_clock::time_point FromString(const std::string& s) { std::tm tm; - const char* end = strptime(s.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); - if (end == nullptr || end - s.c_str() != s.find('.')) { - // TODO + char* const end = strptime(s.c_str(), "%Y-%m-%dT%H:%M:%S", &tm); + if (end == nullptr || !std::isdigit(*(end - 2))) { + // TODO: Invalid time format. return std::chrono::system_clock::time_point(); } char* zone; - long ns = std::strtol(end + 1, &zone, 10); - if (zone <= end + 1 || *zone != 'Z' || *(zone+1) != '\0') { - // TODO + if (*end == '.') { + (void)std::strtol(end + 1, &zone, 10); + if (zone <= end + 1) { + // TODO: Missing nanoseconds. + return std::chrono::system_clock::time_point(); + } + } else { + zone = end; + } + if (*zone != 'Z' || *(zone+1) != '\0') { + // TODO: Invalid timezone. + return std::chrono::system_clock::time_point(); + } + char* d_end; + double seconds = std::strtod(end - 2, &d_end); + if (d_end != zone) { + // TODO: Internal error. return std::chrono::system_clock::time_point(); } + static_assert(sizeof(long) == 8, "long is too small"); + // Truncate to 9 digits by rounding to 10 and discarding the last one. + long ns = std::lround((seconds - tm.tm_sec) * 10000000000) / 10; + // Our UTC offset constant assumes no DST. + tm.tm_isdst = 0; const std::time_t local_time = std::mktime(&tm); const std::time_t utc_time = local_time + kUtcOffset; std::chrono::system_clock::time_point sec = diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..d16f2f56 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +init-submodules +*_unittest diff --git a/test/Makefile b/test/Makefile index 5b33fc2a..efea1665 100644 --- a/test/Makefile +++ b/test/Makefile @@ -21,8 +21,9 @@ TEST_DIR=. TEST_SOURCES=$(wildcard $(TEST_DIR)/*_unittest.cc) TEST_OBJS=$(TEST_SOURCES:$(TEST_DIR)/%.cc=%.o) TESTS=\ + base64_unittest \ format_unittest \ - base64_unittest + time_unittest GTEST_LIB=gtest_lib.a @@ -47,7 +48,6 @@ init-submodules: git submodule update --init $(GTEST_MODULE) touch init-submodules - $(SRC_DIR)/%.o: $(SRC_DIR)/%.cc cd $(SRC_DIR) && $(MAKE) $(@:$(SRC_DIR)/%=%) @@ -67,4 +67,7 @@ format_unittest: $(GTEST_LIB) format_unittest.o $(SRC_DIR)/format.o base64_unittest: $(GTEST_LIB) base64_unittest.o $(SRC_DIR)/base64.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ +time_unittest: $(GTEST_LIB) time_unittest.o $(SRC_DIR)/time.o + $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ + .PHONY: all test clean purge diff --git a/test/time_unittest.cc b/test/time_unittest.cc new file mode 100644 index 00000000..cc478b1a --- /dev/null +++ b/test/time_unittest.cc @@ -0,0 +1,160 @@ +#include "../src/time.h" +#include "gtest/gtest.h" + +using namespace google; + +namespace { + +TEST(TimeTest, EpochToString) { + const std::chrono::system_clock::time_point epoch; + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(epoch) + ); +} + +TEST(TimeTest, RoundtripViaTimePoint) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.678901234Z"); + EXPECT_EQ( + "2018-03-03T01:23:45.678901234Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, RoundtripViaString) { + const std::chrono::system_clock::time_point t = + std::chrono::system_clock::now(); + EXPECT_EQ( + t, + rfc3339::FromString(rfc3339::ToString(t)) + ); +} + +TEST(TimeTest, FromStringNoNanos) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45Z"); + EXPECT_EQ( + "2018-03-03T01:23:45.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringFewerDigits) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.6789Z"); + EXPECT_EQ( + "2018-03-03T01:23:45.678900000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringMoreDigits) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.67890123456789Z"); + EXPECT_EQ( + "2018-03-03T01:23:45.678901234Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringLargeNanos) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.9876543210987Z"); + EXPECT_EQ( + "2018-03-03T01:23:45.987654321Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringPositiveNanos) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.+678901234Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringNegativeNanos) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45.-678901234Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringPositiveSeconds) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:+4.567890123Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringNegativeSeconds) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:-4.567890123Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringHexSeconds) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:0x45.678901234Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringInfSeconds) { + const std::chrono::system_clock::time_point t1 = + rfc3339::FromString("2018-03-03T01:23:+infZ"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t1) + ); + const std::chrono::system_clock::time_point t2 = + rfc3339::FromString("2018-03-03T01:23:-infZ"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t2) + ); + const std::chrono::system_clock::time_point t3 = + rfc3339::FromString("2018-03-03T01:23:+nanZ"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t3) + ); + const std::chrono::system_clock::time_point t4 = + rfc3339::FromString("2018-03-03T01:23:-nanZ"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t4) + ); +} + +TEST(TimeTest, FromStringTooManySeconds) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:1045.678901234Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +TEST(TimeTest, FromStringScientificSeconds) { + const std::chrono::system_clock::time_point t = + rfc3339::FromString("2018-03-03T01:23:45e+0Z"); + EXPECT_EQ( + "1970-01-01T00:00:00.000000000Z", + rfc3339::ToString(t) + ); +} + +}