From 253d7076fc19c7380b3f58b598eaca1b076bec74 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Wed, 29 Sep 2021 18:13:13 -0700 Subject: [PATCH] Chunked vector type (#27517) * chunked vector type * Automated change: Fix sanity tests * compile fix Co-authored-by: ctiller --- BUILD | 10 + CMakeLists.txt | 103 +++++++++ build_autogenerated.yaml | 118 ++++++++++ src/core/lib/gprpp/chunked_vector.h | 207 ++++++++++++++++++ test/core/gprpp/BUILD | 29 +++ ...h-a0868ce3a0f76feefcc715148ed9e71fa0738c2a | 14 ++ test/core/gprpp/chunked_vector_fuzzer.cc | 153 +++++++++++++ test/core/gprpp/chunked_vector_fuzzer.proto | 50 +++++ test/core/gprpp/chunked_vector_test.cc | 169 ++++++++++++++ tools/run_tests/generated/tests.json | 24 ++ 10 files changed, 877 insertions(+) create mode 100644 src/core/lib/gprpp/chunked_vector.h create mode 100644 test/core/gprpp/chunked_vector_corpora/crash-a0868ce3a0f76feefcc715148ed9e71fa0738c2a create mode 100644 test/core/gprpp/chunked_vector_fuzzer.cc create mode 100644 test/core/gprpp/chunked_vector_fuzzer.proto create mode 100644 test/core/gprpp/chunked_vector_test.cc diff --git a/BUILD b/BUILD index 353c0274773..8b0d85dbf32 100644 --- a/BUILD +++ b/BUILD @@ -749,6 +749,16 @@ grpc_cc_library( deps = ["gpr_platform"], ) +grpc_cc_library( + name = "chunked_vector", + hdrs = ["src/core/lib/gprpp/chunked_vector.h"], + external_deps = ["absl/utility"], + deps = [ + # TODO(ctiller): weaken this to just arena when that splits into its own target + "gpr_base", + ], +) + grpc_cc_library( name = "capture", external_deps = ["absl/utility"], diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a2f9e6cc11..38f0363914c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -834,6 +834,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx channelz_registry_test) add_dependencies(buildtests_cxx channelz_service_test) add_dependencies(buildtests_cxx channelz_test) + add_dependencies(buildtests_cxx chunked_vector_test) add_dependencies(buildtests_cxx cli_call_test) add_dependencies(buildtests_cxx client_callback_end2end_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) @@ -9928,6 +9929,108 @@ target_link_libraries(channelz_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(chunked_vector_test + src/core/ext/upb-generated/google/api/annotations.upb.c + src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.c + src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.c + src/core/ext/upb-generated/google/api/http.upb.c + src/core/ext/upb-generated/google/protobuf/any.upb.c + src/core/ext/upb-generated/google/protobuf/duration.upb.c + src/core/ext/upb-generated/google/protobuf/empty.upb.c + src/core/ext/upb-generated/google/protobuf/struct.upb.c + src/core/ext/upb-generated/google/protobuf/timestamp.upb.c + src/core/ext/upb-generated/google/protobuf/wrappers.upb.c + src/core/ext/upb-generated/google/rpc/status.upb.c + src/core/lib/gpr/alloc.cc + src/core/lib/gpr/atm.cc + src/core/lib/gpr/cpu_iphone.cc + src/core/lib/gpr/cpu_linux.cc + src/core/lib/gpr/cpu_posix.cc + src/core/lib/gpr/cpu_windows.cc + src/core/lib/gpr/env_linux.cc + src/core/lib/gpr/env_posix.cc + src/core/lib/gpr/env_windows.cc + src/core/lib/gpr/log.cc + src/core/lib/gpr/log_android.cc + src/core/lib/gpr/log_linux.cc + src/core/lib/gpr/log_posix.cc + src/core/lib/gpr/log_windows.cc + src/core/lib/gpr/murmur_hash.cc + src/core/lib/gpr/string.cc + src/core/lib/gpr/string_posix.cc + src/core/lib/gpr/string_util_windows.cc + src/core/lib/gpr/string_windows.cc + src/core/lib/gpr/sync.cc + src/core/lib/gpr/sync_abseil.cc + src/core/lib/gpr/sync_posix.cc + src/core/lib/gpr/sync_windows.cc + src/core/lib/gpr/time.cc + src/core/lib/gpr/time_posix.cc + src/core/lib/gpr/time_precise.cc + src/core/lib/gpr/time_windows.cc + src/core/lib/gpr/tmpfile_msys.cc + src/core/lib/gpr/tmpfile_posix.cc + src/core/lib/gpr/tmpfile_windows.cc + src/core/lib/gpr/wrap_memcpy.cc + src/core/lib/gprpp/arena.cc + src/core/lib/gprpp/examine_stack.cc + src/core/lib/gprpp/fork.cc + src/core/lib/gprpp/global_config_env.cc + src/core/lib/gprpp/host_port.cc + src/core/lib/gprpp/mpscq.cc + src/core/lib/gprpp/stat_posix.cc + src/core/lib/gprpp/stat_windows.cc + src/core/lib/gprpp/status_helper.cc + src/core/lib/gprpp/thd_posix.cc + src/core/lib/gprpp/thd_windows.cc + src/core/lib/gprpp/time_util.cc + src/core/lib/profiling/basic_timers.cc + src/core/lib/profiling/stap_timers.cc + test/core/gprpp/chunked_vector_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(chunked_vector_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(chunked_vector_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::base + absl::core_headers + absl::memory + absl::status + absl::cord + absl::str_format + absl::strings + absl::synchronization + absl::time + absl::optional + absl::utility + upb +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 497f6ee7d12..e39714a47d7 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -5228,6 +5228,124 @@ targets: deps: - grpc++ - grpc_test_util +- name: chunked_vector_test + gtest: true + build: test + language: c++ + headers: + - src/core/ext/upb-generated/google/api/annotations.upb.h + - src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.h + - src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.h + - src/core/ext/upb-generated/google/api/http.upb.h + - src/core/ext/upb-generated/google/protobuf/any.upb.h + - src/core/ext/upb-generated/google/protobuf/duration.upb.h + - src/core/ext/upb-generated/google/protobuf/empty.upb.h + - src/core/ext/upb-generated/google/protobuf/struct.upb.h + - src/core/ext/upb-generated/google/protobuf/timestamp.upb.h + - src/core/ext/upb-generated/google/protobuf/wrappers.upb.h + - src/core/ext/upb-generated/google/rpc/status.upb.h + - src/core/lib/gpr/alloc.h + - src/core/lib/gpr/env.h + - src/core/lib/gpr/murmur_hash.h + - src/core/lib/gpr/spinlock.h + - src/core/lib/gpr/string.h + - src/core/lib/gpr/string_windows.h + - src/core/lib/gpr/time_precise.h + - src/core/lib/gpr/tls.h + - src/core/lib/gpr/tmpfile.h + - src/core/lib/gpr/useful.h + - src/core/lib/gprpp/arena.h + - src/core/lib/gprpp/chunked_vector.h + - src/core/lib/gprpp/construct_destruct.h + - src/core/lib/gprpp/debug_location.h + - src/core/lib/gprpp/examine_stack.h + - src/core/lib/gprpp/fork.h + - src/core/lib/gprpp/global_config.h + - src/core/lib/gprpp/global_config_custom.h + - src/core/lib/gprpp/global_config_env.h + - src/core/lib/gprpp/global_config_generic.h + - src/core/lib/gprpp/host_port.h + - src/core/lib/gprpp/manual_constructor.h + - src/core/lib/gprpp/memory.h + - src/core/lib/gprpp/mpscq.h + - src/core/lib/gprpp/stat.h + - src/core/lib/gprpp/status_helper.h + - src/core/lib/gprpp/sync.h + - src/core/lib/gprpp/thd.h + - src/core/lib/gprpp/time_util.h + - src/core/lib/profiling/timers.h + src: + - src/core/ext/upb-generated/google/api/annotations.upb.c + - src/core/ext/upb-generated/google/api/expr/v1alpha1/checked.upb.c + - src/core/ext/upb-generated/google/api/expr/v1alpha1/syntax.upb.c + - src/core/ext/upb-generated/google/api/http.upb.c + - src/core/ext/upb-generated/google/protobuf/any.upb.c + - src/core/ext/upb-generated/google/protobuf/duration.upb.c + - src/core/ext/upb-generated/google/protobuf/empty.upb.c + - src/core/ext/upb-generated/google/protobuf/struct.upb.c + - src/core/ext/upb-generated/google/protobuf/timestamp.upb.c + - src/core/ext/upb-generated/google/protobuf/wrappers.upb.c + - src/core/ext/upb-generated/google/rpc/status.upb.c + - src/core/lib/gpr/alloc.cc + - src/core/lib/gpr/atm.cc + - src/core/lib/gpr/cpu_iphone.cc + - src/core/lib/gpr/cpu_linux.cc + - src/core/lib/gpr/cpu_posix.cc + - src/core/lib/gpr/cpu_windows.cc + - src/core/lib/gpr/env_linux.cc + - src/core/lib/gpr/env_posix.cc + - src/core/lib/gpr/env_windows.cc + - src/core/lib/gpr/log.cc + - src/core/lib/gpr/log_android.cc + - src/core/lib/gpr/log_linux.cc + - src/core/lib/gpr/log_posix.cc + - src/core/lib/gpr/log_windows.cc + - src/core/lib/gpr/murmur_hash.cc + - src/core/lib/gpr/string.cc + - src/core/lib/gpr/string_posix.cc + - src/core/lib/gpr/string_util_windows.cc + - src/core/lib/gpr/string_windows.cc + - src/core/lib/gpr/sync.cc + - src/core/lib/gpr/sync_abseil.cc + - src/core/lib/gpr/sync_posix.cc + - src/core/lib/gpr/sync_windows.cc + - src/core/lib/gpr/time.cc + - src/core/lib/gpr/time_posix.cc + - src/core/lib/gpr/time_precise.cc + - src/core/lib/gpr/time_windows.cc + - src/core/lib/gpr/tmpfile_msys.cc + - src/core/lib/gpr/tmpfile_posix.cc + - src/core/lib/gpr/tmpfile_windows.cc + - src/core/lib/gpr/wrap_memcpy.cc + - src/core/lib/gprpp/arena.cc + - src/core/lib/gprpp/examine_stack.cc + - src/core/lib/gprpp/fork.cc + - src/core/lib/gprpp/global_config_env.cc + - src/core/lib/gprpp/host_port.cc + - src/core/lib/gprpp/mpscq.cc + - src/core/lib/gprpp/stat_posix.cc + - src/core/lib/gprpp/stat_windows.cc + - src/core/lib/gprpp/status_helper.cc + - src/core/lib/gprpp/thd_posix.cc + - src/core/lib/gprpp/thd_windows.cc + - src/core/lib/gprpp/time_util.cc + - src/core/lib/profiling/basic_timers.cc + - src/core/lib/profiling/stap_timers.cc + - test/core/gprpp/chunked_vector_test.cc + deps: + - absl/base:base + - absl/base:core_headers + - absl/memory:memory + - absl/status:status + - absl/strings:cord + - absl/strings:str_format + - absl/strings:strings + - absl/synchronization:synchronization + - absl/time:time + - absl/types:optional + - absl/utility:utility + - upb + uses_polling: false - name: cli_call_test gtest: true build: test diff --git a/src/core/lib/gprpp/chunked_vector.h b/src/core/lib/gprpp/chunked_vector.h new file mode 100644 index 00000000000..2080655d0a6 --- /dev/null +++ b/src/core/lib/gprpp/chunked_vector.h @@ -0,0 +1,207 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H +#define GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H + +#include + +#include "src/core/lib/gprpp/arena.h" +#include "src/core/lib/gprpp/manual_constructor.h" + +namespace grpc_core { + +// Arena-friendly vector type. +// This "vector" allocates non-contiguous runs of kChunkSize T's at a time. +// Expectation is that most usage will fit in one chunk, sometimes two will be +// needed, and very rarely three. Appending is constant time, calculating the +// size is O(n_chunks). +template +class ChunkedVector { + private: + // One chunk of allocated memory. + struct Chunk { + Chunk() = default; + Chunk* next = nullptr; + size_t count = 0; + ManualConstructor data[kChunkSize]; + }; + + public: + explicit ChunkedVector(Arena* arena) : arena_(arena) {} + template + ChunkedVector(Arena* arena, Iterator begin, Iterator end) : arena_(arena) { + for (; begin != end; ++begin) { + EmplaceBack(*begin); + } + } + ChunkedVector(const ChunkedVector& other) + : ChunkedVector(other.arena_, other.begin(), other.end()) {} + ChunkedVector& operator=(ChunkedVector other) { + Swap(&other); + return *this; + } + ChunkedVector(ChunkedVector&& other) noexcept + : arena_(other.arena_), first_(other.first_), append_(other.append_) { + other.first_ = nullptr; + other.append_ = nullptr; + } + ChunkedVector& operator=(ChunkedVector&& other) noexcept { + Swap(&other); + return *this; + } + ~ChunkedVector() { Clear(); } + void Swap(ChunkedVector* other) { + std::swap(other->arena_, arena_); + std::swap(other->first_, first_); + std::swap(other->append_, append_); + } + + // Append a new element to the end of the vector. + template + void EmplaceBack(Args&&... args) { + AppendSlot()->Init(std::forward(args)...); + } + + // Remove the last element and return it. + T PopBack() { + GPR_ASSERT(append_ != nullptr); + if (append_->count == 0) { + GPR_ASSERT(first_ != append_); + Chunk* chunk = first_; + while (chunk->next != append_) { + chunk = chunk->next; + } + append_ = chunk; + } + const auto last = append_->count - 1; + T result = std::move(*append_->data[last]); + append_->data[last].Destroy(); + append_->count = last; + return result; + } + + void Clear() { + Chunk* chunk = first_; + while (chunk != nullptr && chunk->count != 0) { + for (size_t i = 0; i < chunk->count; i++) { + chunk->data[i].Destroy(); + } + chunk->count = 0; + chunk = chunk->next; + } + append_ = first_; + } + + // Forward-only iterator. + class ForwardIterator { + public: + ForwardIterator(Chunk* chunk, size_t n) : chunk_(chunk), n_(n) {} + + T& operator*() const { return *chunk_->data[n_]; } + T* operator->() const { return &*chunk_->data[n_]; } + ForwardIterator& operator++() { + ++n_; + if (n_ == chunk_->count) { + chunk_ = chunk_->next; + n_ = 0; + } + return *this; + } + bool operator==(const ForwardIterator& other) const { + return chunk_ == other.chunk_ && n_ == other.n_; + } + bool operator!=(const ForwardIterator& other) const { + return !(*this == other); + } + + private: + Chunk* chunk_; + size_t n_; + }; + + // Const Forward-only iterator. + class ConstForwardIterator { + public: + ConstForwardIterator(const Chunk* chunk, size_t n) : chunk_(chunk), n_(n) {} + + const T& operator*() const { return *chunk_->data[n_]; } + const T* operator->() const { return &*chunk_->data[n_]; } + ConstForwardIterator& operator++() { + ++n_; + if (n_ == chunk_->count) { + chunk_ = chunk_->next; + n_ = 0; + } + return *this; + } + bool operator==(const ConstForwardIterator& other) const { + return chunk_ == other.chunk_ && n_ == other.n_; + } + bool operator!=(const ConstForwardIterator& other) const { + return !(*this == other); + } + + private: + const Chunk* chunk_; + size_t n_; + }; + + ForwardIterator begin() { + if (first_ != nullptr && first_->count == 0) return end(); + return ForwardIterator(first_, 0); + } + ForwardIterator end() { return ForwardIterator(nullptr, 0); } + + ConstForwardIterator begin() const { + if (first_ != nullptr && first_->count == 0) return cend(); + return ConstForwardIterator(first_, 0); + } + ConstForwardIterator end() const { return ConstForwardIterator(nullptr, 0); } + + ConstForwardIterator cbegin() const { return begin(); } + ConstForwardIterator cend() const { return end(); } + + // Count the number of elements in the vector. + size_t size() const { + size_t n = 0; + for (const Chunk* chunk = first_; chunk != nullptr; chunk = chunk->next) { + n += chunk->count; + } + return n; + } + + private: + ManualConstructor* AppendSlot() { + if (append_ == nullptr) { + GPR_ASSERT(first_ == nullptr); + first_ = arena_->New(); + append_ = first_; + } else if (append_->count == kChunkSize) { + if (append_->next == nullptr) { + append_->next = arena_->New(); + } + append_ = append_->next; + } + return &append_->data[append_->count++]; + } + + Arena* arena_; + Chunk* first_ = nullptr; + Chunk* append_ = nullptr; +}; + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_GPRPP_CHUNKED_VECTOR_H diff --git a/test/core/gprpp/BUILD b/test/core/gprpp/BUILD index 54013578633..a4957c67f00 100644 --- a/test/core/gprpp/BUILD +++ b/test/core/gprpp/BUILD @@ -19,6 +19,8 @@ licenses(["notice"]) # Apache v2 grpc_package(name = "test/core/gprpp") +load("//test/core/util:grpc_fuzzer.bzl", "grpc_proto_fuzzer") + grpc_cc_test( name = "examine_stack_test", srcs = ["examine_stack_test.cc"], @@ -283,3 +285,30 @@ grpc_cc_test( "//test/core/util:grpc_suppressions", ], ) + +grpc_cc_test( + name = "chunked_vector_test", + srcs = ["chunked_vector_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:chunked_vector", + "//:gpr_base", + "//test/core/util:grpc_suppressions", + ], +) + +grpc_proto_fuzzer( + name = "chunked_vector_fuzzer", + srcs = ["chunked_vector_fuzzer.cc"], + corpus = "chunked_vector_corpora", + language = "C++", + proto = "chunked_vector_fuzzer.proto", + tags = ["no_windows"], + uses_polling = False, + deps = [ + "//:chunked_vector", + "//test/core/util:grpc_test_util", + ], +) diff --git a/test/core/gprpp/chunked_vector_corpora/crash-a0868ce3a0f76feefcc715148ed9e71fa0738c2a b/test/core/gprpp/chunked_vector_corpora/crash-a0868ce3a0f76feefcc715148ed9e71fa0738c2a new file mode 100644 index 00000000000..f8a94dba216 --- /dev/null +++ b/test/core/gprpp/chunked_vector_corpora/crash-a0868ce3a0f76feefcc715148ed9e71fa0738c2a @@ -0,0 +1,14 @@ +actions { + move { + from: 875700282 + to: 3473408 + } +} +actions { + emplace_back { + } +} +actions { + clear { + } +} diff --git a/test/core/gprpp/chunked_vector_fuzzer.cc b/test/core/gprpp/chunked_vector_fuzzer.cc new file mode 100644 index 00000000000..67cc8552127 --- /dev/null +++ b/test/core/gprpp/chunked_vector_fuzzer.cc @@ -0,0 +1,153 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "src/core/lib/gprpp/chunked_vector.h" +#include "src/libfuzzer/libfuzzer_macro.h" +#include "test/core/gprpp/chunked_vector_fuzzer.pb.h" + +bool squelch = true; +bool leak_check = true; + +static constexpr size_t kChunkSize = 17; +using IntHdl = std::shared_ptr; + +namespace grpc_core { +struct Comparison { + explicit Comparison(Arena* arena) : chunked(arena) {} + + ChunkedVector chunked; + std::vector std; + + // Check that both chunked and std are equivalent. + void AssertOk() const { + GPR_ASSERT(std.size() == chunked.size()); + auto it_chunked = chunked.cbegin(); + auto it_std = std.cbegin(); + while (it_std != std.cend()) { + GPR_ASSERT(**it_std == **it_chunked); + ++it_chunked; + ++it_std; + } + GPR_ASSERT(it_chunked == chunked.cend()); + } +}; + +class Fuzzer { + public: + Fuzzer() : arena_(Arena::Create(128)) {} + ~Fuzzer() { + vectors_.clear(); + arena_->Destroy(); + } + + void Act(const chunked_vector_fuzzer::Action& action) { + switch (action.action_type_case()) { + case chunked_vector_fuzzer::Action::kEmplaceBack: { + // Add some value to the back of a comparison, assert that both vectors + // are equivalent. + auto* c = Mutate(action.emplace_back().vector()); + c->chunked.EmplaceBack( + std::make_shared(action.emplace_back().value())); + c->std.emplace_back( + std::make_shared(action.emplace_back().value())); + c->AssertOk(); + } break; + case chunked_vector_fuzzer::Action::kPopBack: { + // Remove some value to the back of a comparison, assert that both + // vectors are equivalent. + auto* c = Mutate(action.pop_back().vector()); + if (c->chunked.size() > 0) { + c->chunked.PopBack(); + c->std.pop_back(); + c->AssertOk(); + } + } break; + case chunked_vector_fuzzer::Action::kCopy: { + // Copy one vector into another, assert both everything stays + // equivalent. + auto it_from = vectors_.find(action.copy().from()); + if (it_from == vectors_.end()) { + it_from = + vectors_.emplace(action.copy().from(), Comparison(arena_)).first; + } + auto it_to = vectors_.find(action.copy().to()); + if (it_to == vectors_.end()) { + it_to = vectors_.emplace(action.copy().to(), it_from->second).first; + } else { + it_to->second = it_from->second; + } + it_from->second.AssertOk(); + it_to->second.AssertOk(); + } break; + case chunked_vector_fuzzer::Action::kMove: { + // Move one vector into another, assert both everything stays + // equivalent. + auto it_from = vectors_.find(action.move().from()); + if (it_from == vectors_.end()) { + it_from = + vectors_.emplace(action.move().from(), Comparison(arena_)).first; + } + auto it_to = vectors_.find(action.move().to()); + if (it_to == vectors_.end()) { + it_to = + vectors_.emplace(action.move().to(), std::move(it_from->second)) + .first; + } else { + it_to->second = it_from->second; + } + it_from->second.AssertOk(); + it_to->second.AssertOk(); + } break; + case chunked_vector_fuzzer::Action::kClear: { + // Clear a vector, assert that both underlying vectors are equivalent. + auto* c = Mutate(action.clear().vector()); + c->chunked.Clear(); + c->std.clear(); + c->AssertOk(); + } break; + case chunked_vector_fuzzer::Action::kSwap: { + // Swap two vectors, assert that both underlying vectors are equivalent. + auto* from = Mutate(action.swap().from()); + auto* to = Mutate(action.swap().to()); + from->chunked.Swap(&to->chunked); + from->std.swap(to->std); + from->AssertOk(); + } break; + case chunked_vector_fuzzer::Action::ACTION_TYPE_NOT_SET: + break; + } + } + + private: + Comparison* Mutate(int index) { + auto it = vectors_.find(index); + if (it != vectors_.end()) { + return &it->second; + } + return &vectors_.emplace(index, Comparison(arena_)).first->second; + } + + Arena* arena_; + std::map vectors_; +}; +} // namespace grpc_core + +DEFINE_PROTO_FUZZER(const chunked_vector_fuzzer::Msg& msg) { + grpc_core::Fuzzer fuzzer; + for (int i = 0; i < msg.actions_size(); i++) { + fuzzer.Act(msg.actions(i)); + } +} diff --git a/test/core/gprpp/chunked_vector_fuzzer.proto b/test/core/gprpp/chunked_vector_fuzzer.proto new file mode 100644 index 00000000000..236b464412e --- /dev/null +++ b/test/core/gprpp/chunked_vector_fuzzer.proto @@ -0,0 +1,50 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +package chunked_vector_fuzzer; + +message EmplaceBack { + int32 vector = 1; + int32 value = 2; +} + +message PopBack { + int32 vector = 1; + int32 value = 2; +} + +message Copy { + int32 from = 1; + int32 to = 2; +} + +message ClearVector { + int32 vector = 1; +} + +message Action { + oneof action_type { + EmplaceBack emplace_back = 1; + PopBack pop_back = 2; + Copy copy = 3; + Copy move = 4; + ClearVector clear = 5; + Copy swap = 6; + } +} + +message Msg { + repeated Action actions = 1; +} diff --git a/test/core/gprpp/chunked_vector_test.cc b/test/core/gprpp/chunked_vector_test.cc new file mode 100644 index 00000000000..21f84565590 --- /dev/null +++ b/test/core/gprpp/chunked_vector_test.cc @@ -0,0 +1,169 @@ +// Copyright 2021 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/core/lib/gprpp/chunked_vector.h" + +#include + +namespace grpc_core { +namespace testing { + +static constexpr size_t kInitialArenaSize = 1024; +static constexpr size_t kChunkSize = 3; + +TEST(ChunkedVector, Noop) { + auto* arena = Arena::Create(kInitialArenaSize); + { + ChunkedVector v(arena); + EXPECT_EQ(0, v.size()); + } + arena->Destroy(); +} + +TEST(ChunkedVector, Stack) { + auto* arena = Arena::Create(kInitialArenaSize); + { + ChunkedVector v(arena); + + // Populate 2 chunks of memory, and 2/3 of a final chunk. + EXPECT_EQ(0, v.size()); + v.EmplaceBack(1); + EXPECT_EQ(1, v.size()); + v.EmplaceBack(2); + EXPECT_EQ(2, v.size()); + v.EmplaceBack(3); + EXPECT_EQ(3, v.size()); + v.EmplaceBack(4); + EXPECT_EQ(4, v.size()); + v.EmplaceBack(5); + EXPECT_EQ(5, v.size()); + v.EmplaceBack(6); + EXPECT_EQ(6, v.size()); + v.EmplaceBack(7); + EXPECT_EQ(7, v.size()); + v.EmplaceBack(8); + EXPECT_EQ(8, v.size()); + + // Now pop all of them out and check the expected ordering. + EXPECT_EQ(8, v.PopBack()); + EXPECT_EQ(7, v.size()); + EXPECT_EQ(7, v.PopBack()); + EXPECT_EQ(6, v.size()); + EXPECT_EQ(6, v.PopBack()); + EXPECT_EQ(5, v.size()); + EXPECT_EQ(5, v.PopBack()); + EXPECT_EQ(4, v.size()); + EXPECT_EQ(4, v.PopBack()); + EXPECT_EQ(3, v.size()); + EXPECT_EQ(3, v.PopBack()); + EXPECT_EQ(2, v.size()); + EXPECT_EQ(2, v.PopBack()); + EXPECT_EQ(1, v.size()); + EXPECT_EQ(1, v.PopBack()); + EXPECT_EQ(0, v.size()); + } + arena->Destroy(); +} + +TEST(ChunkedVector, Iterate) { + auto* arena = Arena::Create(kInitialArenaSize); + { + ChunkedVector v(arena); + v.EmplaceBack(1); + v.EmplaceBack(2); + v.EmplaceBack(3); + v.EmplaceBack(4); + v.EmplaceBack(5); + v.EmplaceBack(6); + v.EmplaceBack(7); + v.EmplaceBack(8); + + auto it = v.begin(); + EXPECT_EQ(1, *it); + ++it; + EXPECT_EQ(2, *it); + ++it; + EXPECT_EQ(3, *it); + ++it; + EXPECT_EQ(4, *it); + ++it; + EXPECT_EQ(5, *it); + ++it; + EXPECT_EQ(6, *it); + ++it; + EXPECT_EQ(7, *it); + ++it; + EXPECT_EQ(8, *it); + ++it; + EXPECT_EQ(v.end(), it); + } + arena->Destroy(); +} + +TEST(ChunkedVector, ConstIterate) { + auto* arena = Arena::Create(kInitialArenaSize); + { + ChunkedVector v(arena); + v.EmplaceBack(1); + v.EmplaceBack(2); + v.EmplaceBack(3); + v.EmplaceBack(4); + v.EmplaceBack(5); + v.EmplaceBack(6); + v.EmplaceBack(7); + v.EmplaceBack(8); + + auto it = v.cbegin(); + EXPECT_EQ(1, *it); + ++it; + EXPECT_EQ(2, *it); + ++it; + EXPECT_EQ(3, *it); + ++it; + EXPECT_EQ(4, *it); + ++it; + EXPECT_EQ(5, *it); + ++it; + EXPECT_EQ(6, *it); + ++it; + EXPECT_EQ(7, *it); + ++it; + EXPECT_EQ(8, *it); + ++it; + EXPECT_EQ(v.cend(), it); + } + arena->Destroy(); +} + +TEST(ChunkedVector, Clear) { + auto* arena = Arena::Create(kInitialArenaSize); + { + ChunkedVector v(arena); + v.EmplaceBack(1); + EXPECT_EQ(v.size(), 1); + v.Clear(); + EXPECT_EQ(v.size(), 0); + EXPECT_EQ(v.begin(), v.end()); + } + arena->Destroy(); +} + +} // namespace testing + +} // namespace grpc_core + +int main(int argc, char** argv) { + ::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 47edfb04aa4..d50029cde79 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -4197,6 +4197,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "chunked_vector_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,