From 1a2b459daa6fa5556b492d14c3fee62823180a65 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Tue, 14 Dec 2021 09:29:55 -0800 Subject: [PATCH] Update timeout encoding algorithm (#28346) * update timeout encoding algorithm * Automated change: Fix sanity tests * fix * review feedback Co-authored-by: ctiller --- CMakeLists.txt | 64 +++-- build_autogenerated.yaml | 19 +- .../chttp2/transport/hpack_encoder.cc | 36 ++- .../chttp2/transport/hpack_encoder.h | 6 + src/core/lib/transport/metadata_batch.h | 12 +- src/core/lib/transport/timeout_encoding.cc | 265 +++++++++++++----- src/core/lib/transport/timeout_encoding.h | 50 +++- test/core/transport/BUILD | 3 + test/core/transport/timeout_encoding_test.cc | 67 ++--- tools/run_tests/generated/tests.json | 48 ++-- 10 files changed, 380 insertions(+), 190 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 60370ff03e2..ccc78315a8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -753,7 +753,6 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_c thd_test) add_dependencies(buildtests_c threadpool_test) add_dependencies(buildtests_c time_averaged_stats_test) - add_dependencies(buildtests_c timeout_encoding_test) add_dependencies(buildtests_c timer_heap_test) add_dependencies(buildtests_c timer_list_test) add_dependencies(buildtests_c transport_security_common_api_test) @@ -992,6 +991,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx time_jump_test) endif() add_dependencies(buildtests_cxx time_util_test) + add_dependencies(buildtests_cxx timeout_encoding_test) add_dependencies(buildtests_cxx timer_test) add_dependencies(buildtests_cxx tls_certificate_verifier_test) add_dependencies(buildtests_cxx tls_security_connector_test) @@ -7382,33 +7382,6 @@ target_link_libraries(time_averaged_stats_test ) -endif() -if(gRPC_BUILD_TESTS) - -add_executable(timeout_encoding_test - test/core/transport/timeout_encoding_test.cc -) - -target_include_directories(timeout_encoding_test - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/include - ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} - ${_gRPC_RE2_INCLUDE_DIR} - ${_gRPC_SSL_INCLUDE_DIR} - ${_gRPC_UPB_GENERATED_DIR} - ${_gRPC_UPB_GRPC_GENERATED_DIR} - ${_gRPC_UPB_INCLUDE_DIR} - ${_gRPC_XXHASH_INCLUDE_DIR} - ${_gRPC_ZLIB_INCLUDE_DIR} -) - -target_link_libraries(timeout_encoding_test - ${_gRPC_ALLTARGETS_LIBRARIES} - grpc_test_util -) - - endif() if(gRPC_BUILD_TESTS) @@ -15952,6 +15925,41 @@ target_link_libraries(time_util_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(timeout_encoding_test + test/core/transport/timeout_encoding_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(timeout_encoding_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(timeout_encoding_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index a29a4d40f3f..e6af1f414c5 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -4288,15 +4288,6 @@ targets: deps: - grpc_test_util uses_polling: false -- name: timeout_encoding_test - build: test - language: c - headers: [] - src: - - test/core/transport/timeout_encoding_test.cc - deps: - - grpc_test_util - uses_polling: false - name: timer_heap_test build: test language: c @@ -7957,6 +7948,16 @@ targets: deps: - grpc_test_util uses_polling: false +- name: timeout_encoding_test + gtest: true + build: test + language: c++ + headers: [] + src: + - test/core/transport/timeout_encoding_test.cc + deps: + - grpc_test_util + uses_polling: false - name: timer_test gtest: true build: test diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc index be5b47d0037..5fc6380fa26 100644 --- a/src/core/ext/transport/chttp2/transport/hpack_encoder.cc +++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.cc @@ -725,13 +725,35 @@ void HPackCompressor::Framer::EncodeIndexedKeyWithBinaryValue( void HPackCompressor::Framer::Encode(GrpcTimeoutMetadata, grpc_millis deadline) { - char timeout_str[GRPC_HTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE]; - grpc_mdelem mdelem; - grpc_http2_encode_timeout(deadline - ExecCtx::Get()->Now(), timeout_str); - mdelem = grpc_mdelem_from_slices(GRPC_MDSTR_GRPC_TIMEOUT, - UnmanagedMemorySlice(timeout_str)); - EncodeDynamic(mdelem); - GRPC_MDELEM_UNREF(mdelem); + Timeout timeout = Timeout::FromDuration(deadline - ExecCtx::Get()->Now()); + for (auto it = compressor_->previous_timeouts_.begin(); + it != compressor_->previous_timeouts_.end(); ++it) { + double ratio = timeout.RatioVersus(it->timeout); + // If the timeout we're sending is shorter than a previous timeout, but + // within 3% of it, we'll consider sending it. + if (ratio > -3 && ratio <= 0 && + compressor_->table_.ConvertableToDynamicIndex(it->index)) { + EmitIndexed(compressor_->table_.DynamicIndex(it->index)); + // Put this timeout to the front of the queue - forces common timeouts to + // be considered earlier. + std::swap(*it, *compressor_->previous_timeouts_.begin()); + return; + } + } + // Clean out some expired timeouts. + while (!compressor_->previous_timeouts_.empty() && + !compressor_->table_.ConvertableToDynamicIndex( + compressor_->previous_timeouts_.back().index)) { + compressor_->previous_timeouts_.pop_back(); + } + Slice encoded = timeout.Encode(); + uint32_t index = compressor_->table_.AllocateIndex( + GrpcTimeoutMetadata::key().length() + encoded.length() + + hpack_constants::kEntryOverhead); + compressor_->previous_timeouts_.push_back(PreviousTimeout{timeout, index}); + EmitLitHdrWithNonBinaryStringKeyIncIdx( + StaticSlice::FromStaticString(GrpcTimeoutMetadata::key()).c_slice(), + encoded.c_slice()); } void HPackCompressor::Framer::Encode(UserAgentMetadata, const Slice& slice) { diff --git a/src/core/ext/transport/chttp2/transport/hpack_encoder.h b/src/core/ext/transport/chttp2/transport/hpack_encoder.h index 2479e79b118..c921d576154 100644 --- a/src/core/ext/transport/chttp2/transport/hpack_encoder.h +++ b/src/core/ext/transport/chttp2/transport/hpack_encoder.h @@ -288,6 +288,11 @@ class HPackCompressor { std::vector values_; }; + struct PreviousTimeout { + Timeout timeout; + uint32_t index; + }; + // entry tables for keys & elems: these tables track values that have been // seen and *may* be in the decompressor table HPackEncoderIndex elem_index_; @@ -308,6 +313,7 @@ class HPackCompressor { Slice user_agent_; SliceIndex path_index_; SliceIndex authority_index_; + std::vector previous_timeouts_; }; } // namespace grpc_core diff --git a/src/core/lib/transport/metadata_batch.h b/src/core/lib/transport/metadata_batch.h index a585e076c45..758c630ce92 100644 --- a/src/core/lib/transport/metadata_batch.h +++ b/src/core/lib/transport/metadata_batch.h @@ -88,12 +88,12 @@ struct GrpcTimeoutMetadata { using MementoType = grpc_millis; static absl::string_view key() { return "grpc-timeout"; } static MementoType ParseMemento(Slice value, MetadataParseErrorFn on_error) { - grpc_millis timeout; - if (GPR_UNLIKELY(!grpc_http2_decode_timeout(value.c_slice(), &timeout))) { + auto timeout = ParseTimeout(value); + if (!timeout.has_value()) { on_error("invalid value", value); - timeout = GRPC_MILLIS_INF_FUTURE; + return GRPC_MILLIS_INF_FUTURE; } - return timeout; + return *timeout; } static ValueType MementoToValue(MementoType timeout) { if (timeout == GRPC_MILLIS_INF_FUTURE) { @@ -102,9 +102,7 @@ struct GrpcTimeoutMetadata { return ExecCtx::Get()->Now() + timeout; } static Slice Encode(ValueType x) { - char timeout[GRPC_HTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE]; - grpc_http2_encode_timeout(x, timeout); - return Slice::FromCopiedString(timeout); + return Timeout::FromDuration(x - ExecCtx::Get()->Now()).Encode(); } static MementoType DisplayValue(MementoType x) { return x; } }; diff --git a/src/core/lib/transport/timeout_encoding.cc b/src/core/lib/transport/timeout_encoding.cc index 274006383f3..cd485d72c1e 100644 --- a/src/core/lib/transport/timeout_encoding.cc +++ b/src/core/lib/transport/timeout_encoding.cc @@ -23,83 +23,214 @@ #include #include +#include + #include "src/core/lib/gpr/string.h" -static int64_t round_up(int64_t x, int64_t divisor) { - return (x / divisor + (x % divisor != 0)) * divisor; +namespace grpc_core { + +namespace { + +int64_t DivideRoundingUp(int64_t dividend, int64_t divisor) { + return (dividend + divisor - 1) / divisor; } -/* round an integer up to the next value with three significant figures */ -static int64_t round_up_to_three_sig_figs(int64_t x) { - if (x < 1000) return x; - if (x < 10000) return round_up(x, 10); - if (x < 100000) return round_up(x, 100); - if (x < 1000000) return round_up(x, 1000); - if (x < 10000000) return round_up(x, 10000); - if (x < 100000000) return round_up(x, 100000); - if (x < 1000000000) return round_up(x, 1000000); - return round_up(x, 10000000); +constexpr int64_t kSecondsPerMinute = 60; +constexpr int64_t kMinutesPerHour = 60; +constexpr int64_t kSecondsPerHour = kSecondsPerMinute * kMinutesPerHour; +constexpr int64_t kMaxHours = 27000; + +bool IsAllSpace(const uint8_t* p, const uint8_t* end) { + while (p != end && *p == ' ') p++; + return p == end; } -/* encode our minimum viable timeout value */ -static void enc_tiny(char* buffer) { memcpy(buffer, "1n", 3); } +} // namespace -/* encode our maximum timeout value, about 1157 days */ -static void enc_huge(char* buffer) { memcpy(buffer, "99999999S", 10); } +Timeout Timeout::FromDuration(grpc_millis duration) { + return Timeout::FromMillis(duration); +} -static void enc_ext(char* buffer, int64_t value, char ext) { - int n = int64_ttoa(value, buffer); - buffer[n] = ext; - buffer[n + 1] = 0; +double Timeout::RatioVersus(Timeout other) const { + double a = AsDuration(); + double b = other.AsDuration(); + if (b == 0) { + if (a > 0) return 100; + if (a < 0) return -100; + return 0; + } + return 100 * (a / b - 1); } -static void enc_seconds(char* buffer, int64_t sec) { - sec = round_up_to_three_sig_figs(sec); - if (sec % 3600 == 0) { - enc_ext(buffer, sec / 3600, 'H'); - } else if (sec % 60 == 0) { - enc_ext(buffer, sec / 60, 'M'); - } else { - enc_ext(buffer, sec, 'S'); +grpc_millis Timeout::AsDuration() const { + grpc_millis value = value_; + switch (unit_) { + case Unit::kNanoseconds: + return 0; + case Unit::kMilliseconds: + return value; + case Unit::kTenMilliseconds: + return value * 10; + case Unit::kHundredMilliseconds: + return value * 100; + case Unit::kSeconds: + return value * 1000; + case Unit::kTenSeconds: + return value * 10000; + case Unit::kHundredSeconds: + return value * 100000; + case Unit::kMinutes: + return value * 1000 * kSecondsPerMinute; + case Unit::kTenMinutes: + return value * 10000 * kSecondsPerMinute; + case Unit::kHundredMinutes: + return value * 100000 * kSecondsPerMinute; + case Unit::kHours: + return value * 1000 * kSecondsPerHour; } } -static void enc_millis(char* buffer, int64_t x) { - x = round_up_to_three_sig_figs(x); - if (x < GPR_MS_PER_SEC) { - enc_ext(buffer, x, 'm'); +Slice Timeout::Encode() const { + char buf[10]; + char* p = buf; + uint16_t n = value_; + int digits; + if (n >= 10000) { + digits = 5; + } else if (n >= 1000) { + digits = 4; + } else if (n >= 100) { + digits = 3; + } else if (n >= 10) { + digits = 2; } else { - if (x % GPR_MS_PER_SEC == 0) { - enc_seconds(buffer, x / GPR_MS_PER_SEC); - } else { - enc_ext(buffer, x, 'm'); + digits = 1; + } + switch (digits) { + case 5: + *p++ = '0' + n / 10000; + n %= 10000; + ABSL_FALLTHROUGH_INTENDED; + case 4: + *p++ = '0' + n / 1000; + n %= 1000; + ABSL_FALLTHROUGH_INTENDED; + case 3: + *p++ = '0' + n / 100; + n %= 100; + ABSL_FALLTHROUGH_INTENDED; + case 2: + *p++ = '0' + n / 10; + n %= 10; + ABSL_FALLTHROUGH_INTENDED; + case 1: + *p++ = '0' + n; + } + switch (unit_) { + case Unit::kNanoseconds: + *p++ = 'n'; + break; + case Unit::kHundredMilliseconds: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kTenMilliseconds: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kMilliseconds: + *p++ = 'm'; + break; + case Unit::kHundredSeconds: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kTenSeconds: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kSeconds: + *p++ = 'S'; + break; + case Unit::kHundredMinutes: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kTenMinutes: + *p++ = '0'; + ABSL_FALLTHROUGH_INTENDED; + case Unit::kMinutes: + *p++ = 'M'; + break; + case Unit::kHours: + *p++ = 'H'; + break; + } + return Slice::FromCopiedBuffer(buf, p - buf); +} + +Timeout Timeout::FromMillis(int64_t millis) { + if (millis <= 0) { + return Timeout(1, Unit::kNanoseconds); + } else if (millis < 1000) { + return Timeout(millis, Unit::kMilliseconds); + } else if (millis < 10000) { + int64_t value = DivideRoundingUp(millis, 10); + if (value % 100 != 0) return Timeout(value, Unit::kTenMilliseconds); + } else if (millis < 100000) { + int64_t value = DivideRoundingUp(millis, 100); + if (value % 10 != 0) return Timeout(value, Unit::kHundredMilliseconds); + } + return Timeout::FromSeconds(DivideRoundingUp(millis, 1000)); +} + +Timeout Timeout::FromSeconds(int64_t seconds) { + GPR_DEBUG_ASSERT(seconds != 0); + if (seconds < 1000) { + if (seconds % kSecondsPerMinute != 0) { + return Timeout(seconds, Unit::kSeconds); + } + } else if (seconds < 10000) { + int64_t value = DivideRoundingUp(seconds, 10); + if ((value * 10) % kSecondsPerMinute != 0) { + return Timeout(value, Unit::kTenSeconds); + } + } else if (seconds < 100000) { + int64_t value = DivideRoundingUp(seconds, 100); + if ((value * 100) % kSecondsPerMinute != 0) { + return Timeout(value, Unit::kHundredSeconds); } } + return Timeout::FromMinutes(DivideRoundingUp(seconds, kSecondsPerMinute)); } -void grpc_http2_encode_timeout(grpc_millis timeout, char* buffer) { - const grpc_millis kMaxTimeout = 99999999000; - if (timeout <= 0) { - enc_tiny(buffer); - } else if (timeout < 1000 * GPR_MS_PER_SEC) { - enc_millis(buffer, timeout); - } else if (timeout >= kMaxTimeout) { - enc_huge(buffer); - } else { - enc_seconds(buffer, - timeout / GPR_MS_PER_SEC + (timeout % GPR_MS_PER_SEC != 0)); +Timeout Timeout::FromMinutes(int64_t minutes) { + GPR_DEBUG_ASSERT(minutes != 0); + if (minutes < 1000) { + if (minutes % kMinutesPerHour != 0) { + return Timeout(minutes, Unit::kMinutes); + } + } else if (minutes < 10000) { + int64_t value = DivideRoundingUp(minutes, 10); + if ((value * 10) % kMinutesPerHour != 0) { + return Timeout(value, Unit::kTenMinutes); + } + } else if (minutes < 100000) { + int64_t value = DivideRoundingUp(minutes, 100); + if ((value * 100) % kMinutesPerHour != 0) { + return Timeout(value, Unit::kHundredMinutes); + } } + return Timeout::FromHours(DivideRoundingUp(minutes, kMinutesPerHour)); } -static int is_all_whitespace(const char* p, const char* end) { - while (p != end && *p == ' ') p++; - return p == end; +Timeout Timeout::FromHours(int64_t hours) { + GPR_DEBUG_ASSERT(hours != 0); + if (hours < kMaxHours) { + return Timeout(hours, Unit::kHours); + } + return Timeout(kMaxHours, Unit::kHours); } -int grpc_http2_decode_timeout(const grpc_slice& text, grpc_millis* timeout) { +absl::optional ParseTimeout(const Slice& text) { grpc_millis x = 0; - const uint8_t* p = GRPC_SLICE_START_PTR(text); - const uint8_t* end = GRPC_SLICE_END_PTR(text); + const uint8_t* p = text.begin(); + const uint8_t* end = text.end(); int have_digit = 0; /* skip whitespace */ for (; p != end && *p == ' '; p++) { @@ -111,41 +242,43 @@ int grpc_http2_decode_timeout(const grpc_slice& text, grpc_millis* timeout) { /* spec allows max. 8 digits, but we allow values up to 1,000,000,000 */ if (x >= (100 * 1000 * 1000)) { if (x != (100 * 1000 * 1000) || digit != 0) { - *timeout = GRPC_MILLIS_INF_FUTURE; - return 1; + return GRPC_MILLIS_INF_FUTURE; } } x = x * 10 + digit; } - if (!have_digit) return 0; + if (!have_digit) return absl::nullopt; /* skip whitespace */ for (; p != end && *p == ' '; p++) { } - if (p == end) return 0; + if (p == end) return absl::nullopt; /* decode unit specifier */ + int64_t timeout; switch (*p) { case 'n': - *timeout = x / GPR_NS_PER_MS + (x % GPR_NS_PER_MS != 0); + timeout = x / GPR_NS_PER_MS + (x % GPR_NS_PER_MS != 0); break; case 'u': - *timeout = x / GPR_US_PER_MS + (x % GPR_US_PER_MS != 0); + timeout = x / GPR_US_PER_MS + (x % GPR_US_PER_MS != 0); break; case 'm': - *timeout = x; + timeout = x; break; case 'S': - *timeout = x * GPR_MS_PER_SEC; + timeout = x * GPR_MS_PER_SEC; break; case 'M': - *timeout = x * 60 * GPR_MS_PER_SEC; + timeout = x * 60 * GPR_MS_PER_SEC; break; case 'H': - *timeout = x * 60 * 60 * GPR_MS_PER_SEC; + timeout = x * 60 * 60 * GPR_MS_PER_SEC; break; default: - return 0; + return absl::nullopt; } p++; - return is_all_whitespace(reinterpret_cast(p), - reinterpret_cast(end)); + if (!IsAllSpace(p, end)) return absl::nullopt; + return timeout; } + +} // namespace grpc_core diff --git a/src/core/lib/transport/timeout_encoding.h b/src/core/lib/transport/timeout_encoding.h index c87ff39d491..0ffd2e82194 100644 --- a/src/core/lib/transport/timeout_encoding.h +++ b/src/core/lib/transport/timeout_encoding.h @@ -21,18 +21,48 @@ #include -#include -#include - -#include "src/core/lib/gpr/string.h" #include "src/core/lib/iomgr/exec_ctx.h" +#include "src/core/lib/slice/slice.h" + +namespace grpc_core { + +class Timeout { + public: + static Timeout FromDuration(grpc_millis duration); + + // Computes: 100 * ((this - other) / other) + double RatioVersus(Timeout other) const; + Slice Encode() const; + grpc_millis AsDuration() const; + + private: + enum class Unit : uint8_t { + kNanoseconds, + kMilliseconds, + kTenMilliseconds, + kHundredMilliseconds, + kSeconds, + kTenSeconds, + kHundredSeconds, + kMinutes, + kTenMinutes, + kHundredMinutes, + kHours, + }; + + Timeout(uint16_t value, Unit unit) : value_(value), unit_(unit) {} + + static Timeout FromMillis(int64_t millis); + static Timeout FromSeconds(int64_t seconds); + static Timeout FromMinutes(int64_t minutes); + static Timeout FromHours(int64_t hours); + + uint16_t value_; + Unit unit_; +}; -#define GRPC_HTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE 10 +absl::optional ParseTimeout(const Slice& text); -/* Encode/decode timeouts to the GRPC over HTTP/2 format; - encoding may round up arbitrarily. If the timeout is larger than about 1157 - days, it will be capped and "99999999S" will be sent on the wire. */ -void grpc_http2_encode_timeout(grpc_millis timeout, char* buffer); -int grpc_http2_decode_timeout(const grpc_slice& text, grpc_millis* timeout); +} // namespace grpc_core #endif /* GRPC_CORE_LIB_TRANSPORT_TIMEOUT_ENCODING_H */ diff --git a/test/core/transport/BUILD b/test/core/transport/BUILD index 21a7147dfd8..4a7b1f3c9eb 100644 --- a/test/core/transport/BUILD +++ b/test/core/transport/BUILD @@ -159,6 +159,9 @@ grpc_cc_test( grpc_cc_test( name = "timeout_encoding_test", srcs = ["timeout_encoding_test.cc"], + external_deps = [ + "gtest", + ], language = "C++", uses_polling = False, deps = [ diff --git a/test/core/transport/timeout_encoding_test.cc b/test/core/transport/timeout_encoding_test.cc index 5f6d38c7f87..1e1a7e9a1c6 100644 --- a/test/core/transport/timeout_encoding_test.cc +++ b/test/core/transport/timeout_encoding_test.cc @@ -23,6 +23,8 @@ #include +#include + #include "absl/strings/str_format.h" #include @@ -33,17 +35,16 @@ #include "src/core/lib/gpr/useful.h" #include "test/core/util/test_config.h" -#define LOG_TEST(x) gpr_log(GPR_INFO, "%s", x) +namespace grpc_core { +namespace { -static void assert_encodes_as(grpc_millis ts, const char* s) { - char buffer[GRPC_HTTP2_TIMEOUT_ENCODE_MIN_BUFSIZE]; - grpc_http2_encode_timeout(ts, buffer); - gpr_log(GPR_INFO, "check '%s' == '%s'", buffer, s); - GPR_ASSERT(0 == strcmp(buffer, s)); +void assert_encodes_as(grpc_millis ts, const char* s) { + EXPECT_EQ(absl::string_view(s), + Timeout::FromDuration(ts).Encode().as_string_view()) + << " ts=" << ts; } -void test_encoding(void) { - LOG_TEST("test_encoding"); +TEST(TimeoutTest, Encoding) { assert_encodes_as(-1, "1n"); assert_encodes_as(-10, "1n"); assert_encodes_as(1, "1m"); @@ -67,20 +68,11 @@ void test_encoding(void) { assert_encodes_as(10 * 60 * 60 * GPR_MS_PER_SEC, "10H"); assert_encodes_as(60 * 60 * GPR_MS_PER_SEC - 100, "1H"); assert_encodes_as(100 * 60 * 60 * GPR_MS_PER_SEC, "100H"); - assert_encodes_as(100000000000, "99999999S"); + assert_encodes_as(100000000000, "27000H"); } -static void assert_decodes_as(const char* buffer, grpc_millis expected) { - grpc_millis got; - uint32_t hash = gpr_murmur_hash3(buffer, strlen(buffer), 0); - gpr_log(GPR_INFO, "check decoding '%s' (hash=0x%x)", buffer, hash); - GPR_ASSERT(1 == grpc_http2_decode_timeout( - grpc_slice_from_static_string(buffer), &got)); - if (got != expected) { - gpr_log(GPR_ERROR, "got:'%" PRId64 "' != expected:'%" PRId64 "'", got, - expected); - abort(); - } +void assert_decodes_as(const char* buffer, grpc_millis expected) { + EXPECT_EQ(expected, ParseTimeout(Slice::FromCopiedString(buffer))); } void decode_suite(char ext, grpc_millis (*answer)(int64_t x)) { @@ -102,27 +94,26 @@ void decode_suite(char ext, grpc_millis (*answer)(int64_t x)) { } } -static grpc_millis millis_from_nanos(int64_t x) { +grpc_millis millis_from_nanos(int64_t x) { return static_cast(x / GPR_NS_PER_MS + (x % GPR_NS_PER_MS != 0)); } -static grpc_millis millis_from_micros(int64_t x) { +grpc_millis millis_from_micros(int64_t x) { return static_cast(x / GPR_US_PER_MS + (x % GPR_US_PER_MS != 0)); } -static grpc_millis millis_from_millis(int64_t x) { +grpc_millis millis_from_millis(int64_t x) { return static_cast(x); } -static grpc_millis millis_from_seconds(int64_t x) { +grpc_millis millis_from_seconds(int64_t x) { return static_cast(x * GPR_MS_PER_SEC); } -static grpc_millis millis_from_minutes(int64_t x) { +grpc_millis millis_from_minutes(int64_t x) { return static_cast(x * 60 * GPR_MS_PER_SEC); } -static grpc_millis millis_from_hours(int64_t x) { +grpc_millis millis_from_hours(int64_t x) { return static_cast(x * 3600 * GPR_MS_PER_SEC); } -void test_decoding(void) { - LOG_TEST("test_decoding"); +TEST(TimeoutTest, DecodingSucceeds) { decode_suite('n', millis_from_nanos); decode_suite('u', millis_from_micros); decode_suite('m', millis_from_millis); @@ -136,14 +127,12 @@ void test_decoding(void) { assert_decodes_as("9999999999S", GRPC_MILLIS_INF_FUTURE); } -static void assert_decoding_fails(const char* s) { - grpc_millis x; - GPR_ASSERT(0 == - grpc_http2_decode_timeout(grpc_slice_from_static_string(s), &x)); +void assert_decoding_fails(const char* s) { + EXPECT_EQ(absl::nullopt, ParseTimeout(Slice::FromCopiedString(s))) + << " s=" << s; } -void test_decoding_fails(void) { - LOG_TEST("test_decoding_fails"); +TEST(TimeoutTest, DecodingFails) { assert_decoding_fails(""); assert_decoding_fails(" "); assert_decoding_fails("x"); @@ -155,10 +144,10 @@ void test_decoding_fails(void) { assert_decoding_fails("-1u"); } +} // namespace +} // namespace grpc_core + int main(int argc, char** argv) { - grpc::testing::TestEnvironment env(argc, argv); - test_encoding(); - test_decoding(); - test_decoding_fails(); - return 0; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index bb5452a933a..6c2ee02f66e 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -2743,30 +2743,6 @@ ], "uses_polling": false }, - { - "args": [], - "benchmark": false, - "ci_platforms": [ - "linux", - "mac", - "posix", - "windows" - ], - "cpu_cost": 1.0, - "exclude_configs": [], - "exclude_iomgrs": [], - "flaky": false, - "gtest": false, - "language": "c", - "name": "timeout_encoding_test", - "platforms": [ - "linux", - "mac", - "posix", - "windows" - ], - "uses_polling": false - }, { "args": [], "benchmark": false, @@ -7051,6 +7027,30 @@ ], "uses_polling": false }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "timeout_encoding_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,