Skip to content
This repository was archived by the owner on Aug 19, 2019. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <test_name>
$ ./<test_name>

# Packaging

1. Build the DEB package:
Expand Down
2 changes: 1 addition & 1 deletion src/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
metadatad
init-submodules
build-cpp-netlib
build-yaml-cpp
metadatad
3 changes: 3 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 32 additions & 8 deletions src/time.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#include "time.h"

#include <cctype>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <mutex>
Expand All @@ -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();
Expand All @@ -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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not safe to assume 0 for nanoseconds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on whether we treat a trailing decimal point with no digits after as valid input or an error. As I read the spec, a decimal point must be followed by at least one digit (search for time-fraction).

}
} 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example with a 32-bit compiler. I'd rather fail compilation if someone ever tries to build for Amazon's 32-bit AMI or Windows than silently overflow.

// 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 =
Expand Down
2 changes: 2 additions & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
init-submodules
*_unittest
7 changes: 5 additions & 2 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)/%=%)

Expand All @@ -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
160 changes: 160 additions & 0 deletions test/time_unittest.cc
Original file line number Diff line number Diff line change
@@ -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)
);
}

}