blob: 69f194eaa24012adfc9bd2a565a74c0aea328cf8 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 <memory>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "bvar/detail/exposed_ref.h"
#include "bvar/mvariable.h"
#include "bvar/variable.h"
namespace {
using bvar::detail::ScopedExposedRef;
using bvar::detail::make_exposed_ref;
static_assert(!std::is_copy_constructible<ScopedExposedRef<int>>::value,
"ScopedExposedRef must not duplicate ownership");
static_assert(!std::is_copy_assignable<ScopedExposedRef<int>>::value,
"ScopedExposedRef must not duplicate ownership");
TEST(ScopedExposedRefTest, scope_exit_and_early_return) {
std::string value = "value";
auto ref = make_exposed_ref(&value);
{
ScopedExposedRef<std::string> first(ref);
EXPECT_EQ(&value, first.get());
EXPECT_EQ(value.size(), first->size());
const auto read = [&]() {
ScopedExposedRef<std::string> second(ref);
EXPECT_EQ(2, ref->_nref);
return second->size();
};
EXPECT_EQ(value.size(), read());
EXPECT_EQ(1, ref->_nref);
}
EXPECT_EQ(0, ref->_nref);
}
TEST(ScopedExposedRefTest, keeps_handle_alive) {
int value = 42;
auto ref = make_exposed_ref(&value);
std::weak_ptr<bvar::detail::ExposedRef<int>> weak_ref = ref;
{
ScopedExposedRef<int> reader(ref);
ref.reset();
EXPECT_FALSE(weak_ref.expired());
EXPECT_EQ(&value, reader.get());
}
EXPECT_TRUE(weak_ref.expired());
}
TEST(ScopedExposedRefTest, reset_and_hidden_handle) {
int value = 42;
auto ref = make_exposed_ref(&value);
auto hidden_ref = make_exposed_ref(&value);
hidden_ref->hide_and_wait();
{
ScopedExposedRef<int> reader;
EXPECT_TRUE(reader == nullptr);
EXPECT_EQ(nullptr, reader.get());
reader.reset();
reader.reset(ref);
EXPECT_TRUE(reader != nullptr);
EXPECT_EQ(1, ref->_nref);
reader.reset(ref);
EXPECT_EQ(1, ref->_nref);
reader.reset(hidden_ref);
EXPECT_TRUE(reader == nullptr);
EXPECT_EQ(0, ref->_nref);
EXPECT_EQ(0, hidden_ref->_nref);
reader.reset();
reader.reset();
}
EXPECT_EQ(0, hidden_ref->_nref);
}
TEST(ScopedExposedRefTest, move_transfers_ownership) {
int first_value = 1;
int second_value = 2;
auto first_ref = make_exposed_ref(&first_value);
auto second_ref = make_exposed_ref(&second_value);
{
ScopedExposedRef<int> first(first_ref);
ScopedExposedRef<int> moved(std::move(first));
EXPECT_TRUE(first == nullptr);
EXPECT_EQ(&first_value, moved.get());
EXPECT_EQ(1, first_ref->_nref);
ScopedExposedRef<int> second(second_ref);
second = std::move(moved);
EXPECT_TRUE(moved == nullptr);
EXPECT_EQ(&first_value, second.get());
EXPECT_EQ(1, first_ref->_nref);
EXPECT_EQ(0, second_ref->_nref);
auto* self = &second;
second = std::move(*self);
EXPECT_EQ(&first_value, second.get());
EXPECT_EQ(1, first_ref->_nref);
// hide_all() stores guards in a vector, which may move them again.
std::vector<ScopedExposedRef<int>> readers;
readers.push_back(std::move(second));
readers.emplace_back(first_ref);
EXPECT_TRUE(second == nullptr);
EXPECT_EQ(2, first_ref->_nref);
}
EXPECT_EQ(0, first_ref->_nref);
EXPECT_EQ(0, second_ref->_nref);
}
#if GTEST_HAS_EXCEPTIONS
struct CallbackError {};
// There are no concurrent readers in these tests. Drain leaked references on
// failure so a regression reports an assertion instead of hanging in hide().
template <typename T>
void expect_no_readers(const bvar::detail::SharedExposedRef<T>& ref) {
EXPECT_EQ(0, ref->_nref);
while (ref->_nref > 0) {
ref->release();
}
}
class ThrowingVariable : public bvar::Variable {
public:
~ThrowingVariable() override { hide(); }
void describe(std::ostream&, bool) const override {
throw CallbackError();
}
int describe_series(std::ostream&,
const bvar::SeriesOptions&) const override {
throw CallbackError();
}
#ifdef BAIDU_INTERNAL
void get_value(boost::any*) const override { throw CallbackError(); }
#endif
};
class ThrowingMVariable : public bvar::MVariableBase {
public:
~ThrowingMVariable() override { hide(); }
void describe(std::ostream&) override { throw CallbackError(); }
size_t dump(bvar::Dumper*, const bvar::DumpOptions*) override {
throw CallbackError();
}
};
class DiscardingDumper : public bvar::Dumper {
public:
bool dump(const std::string&, const butil::StringPiece&) override {
return true;
}
};
TEST(ScopedExposedRefTest, variable_callbacks_release_on_exception) {
ThrowingVariable var;
ASSERT_EQ(0, var.expose("scoped_ref_throwing_variable"));
std::ostringstream os;
EXPECT_THROW(bvar::Variable::describe_exposed(var.name(), os),
CallbackError);
expect_no_readers(var._ref);
bvar::SeriesOptions series_options;
EXPECT_THROW(bvar::Variable::describe_series_exposed(
var.name(), os, series_options), CallbackError);
expect_no_readers(var._ref);
DiscardingDumper dumper;
bvar::DumpOptions dump_options;
dump_options.white_wildcards = var.name();
EXPECT_THROW(bvar::Variable::dump_exposed(&dumper, &dump_options),
CallbackError);
expect_no_readers(var._ref);
#ifdef BAIDU_INTERNAL
boost::any value;
EXPECT_THROW(bvar::Variable::get_exposed(var.name(), &value),
CallbackError);
expect_no_readers(var._ref);
#endif
EXPECT_TRUE(var.hide());
}
TEST(ScopedExposedRefTest, mvariable_callbacks_release_on_exception) {
ThrowingMVariable var;
ASSERT_EQ(0, var.expose("scoped_ref_throwing_mvariable"));
std::ostringstream os;
EXPECT_THROW(bvar::MVariableBase::describe_exposed(var.name(), os),
CallbackError);
expect_no_readers(var._ref);
DiscardingDumper dumper;
EXPECT_THROW(bvar::MVariableBase::dump_exposed(&dumper, nullptr),
CallbackError);
expect_no_readers(var._ref);
EXPECT_TRUE(var.hide());
}
#endif // GTEST_HAS_EXCEPTIONS
} // namespace