blob: 79660da3c433613d31cb3abd19266cb75bd3c83c [file] [log] [blame]
/*
* Copyright 2015 Facebook, Inc.
*
* 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 <gtest/gtest.h>
#include <stdexcept>
#include <folly/ExceptionWrapper.h>
#include <folly/Conv.h>
using namespace folly;
// Tests that when we call throwException, the proper type is thrown (derived)
TEST(ExceptionWrapper, throw_test) {
std::runtime_error e("payload");
auto ew = make_exception_wrapper<std::runtime_error>(e);
std::vector<exception_wrapper> container;
container.push_back(ew);
try {
container[0].throwException();
} catch (std::runtime_error& e) {
std::string expected = "payload";
std::string actual = e.what();
EXPECT_EQ(expected, actual);
}
}
TEST(ExceptionWrapper, members) {
auto ew = exception_wrapper();
EXPECT_FALSE(bool(ew));
EXPECT_EQ(ew.what(), "");
EXPECT_EQ(ew.class_name(), "");
ew = make_exception_wrapper<std::runtime_error>("payload");
EXPECT_TRUE(bool(ew));
EXPECT_EQ(ew.what(), "std::runtime_error: payload");
EXPECT_EQ(ew.class_name(), "std::runtime_error");
}
TEST(ExceptionWrapper, equals) {
std::runtime_error e("payload");
auto ew1 = make_exception_wrapper<std::runtime_error>(e);
auto ew2 = ew1;
EXPECT_EQ(ew1, ew2);
auto ew3 = try_and_catch<std::exception>([&]() {
throw std::runtime_error("payload");
});
auto ew4 = try_and_catch<std::exception>([&]() {
ew3.throwException();
});
EXPECT_EQ(ew3, ew4);
}
TEST(ExceptionWrapper, not_equals) {
std::runtime_error e1("payload");
std::runtime_error e2("payload");
auto ew1 = make_exception_wrapper<std::runtime_error>(e1);
auto ew2 = make_exception_wrapper<std::runtime_error>(e2);
EXPECT_NE(ew1, ew2);
auto ew3 = make_exception_wrapper<std::runtime_error>(e1);
auto ew4 = make_exception_wrapper<std::runtime_error>(e1);
EXPECT_NE(ew3, ew4);
auto ew5 = try_and_catch<std::exception>([&]() {
throw e1;
});
auto ew6 = try_and_catch<std::exception>([&]() {
throw e1;
});
EXPECT_NE(ew5, ew6);
}
TEST(ExceptionWrapper, try_and_catch_test) {
std::string expected = "payload";
// Catch rightmost matching exception type
exception_wrapper ew = try_and_catch<std::exception, std::runtime_error>(
[=]() {
throw std::runtime_error(expected);
});
EXPECT_TRUE(bool(ew));
EXPECT_TRUE(ew.getCopied());
EXPECT_EQ(ew.what(), "std::runtime_error: payload");
EXPECT_EQ(ew.class_name(), "std::runtime_error");
auto rep = ew.is_compatible_with<std::runtime_error>();
EXPECT_TRUE(rep);
// Changing order is like catching in wrong order. Beware of this in your
// code.
auto ew2 = try_and_catch<std::runtime_error, std::exception>([=]() {
throw std::runtime_error(expected);
});
EXPECT_TRUE(bool(ew2));
// We are catching a std::exception, not std::runtime_error.
EXPECT_FALSE(ew2.getCopied());
// But, we can still get the actual type if we want it.
rep = ew2.is_compatible_with<std::runtime_error>();
EXPECT_TRUE(rep);
// Catches even if not rightmost.
auto ew3 = try_and_catch<std::exception, std::runtime_error>([]() {
throw std::exception();
});
EXPECT_TRUE(bool(ew3));
EXPECT_EQ(ew3.what(), "std::exception: std::exception");
EXPECT_EQ(ew3.class_name(), "std::exception");
rep = ew3.is_compatible_with<std::runtime_error>();
EXPECT_FALSE(rep);
// If does not catch, throws.
EXPECT_THROW(
try_and_catch<std::runtime_error>([]() {
throw std::exception();
}),
std::exception);
}
class AbstractIntException : public std::exception {
public:
virtual int getInt() const = 0;
};
class IntException : public AbstractIntException {
public:
explicit IntException(int i)
: i_(i) {}
int getInt() const override { return i_; }
const char* what() const noexcept override {
what_ = folly::to<std::string>("int == ", i_);
return what_.c_str();
}
private:
int i_;
mutable std::string what_;
};
TEST(ExceptionWrapper, with_exception_test) {
int expected = 23;
// This works, and doesn't slice.
exception_wrapper ew = try_and_catch<std::exception, std::runtime_error>(
[=]() {
throw IntException(expected);
});
EXPECT_TRUE(bool(ew));
EXPECT_EQ(ew.what(), "IntException: int == 23");
EXPECT_EQ(ew.class_name(), "IntException");
ew.with_exception<IntException>([&](const IntException& ie) {
EXPECT_EQ(ie.getInt(), expected);
});
// I can try_and_catch a non-copyable base class. This will use
// std::exception_ptr internally.
exception_wrapper ew2 = try_and_catch<AbstractIntException>(
[=]() {
throw IntException(expected);
});
EXPECT_TRUE(bool(ew2));
EXPECT_EQ(ew2.what(), "IntException: int == 23");
EXPECT_EQ(ew2.class_name(), "IntException");
ew2.with_exception<AbstractIntException>([&](AbstractIntException& ie) {
EXPECT_EQ(ie.getInt(), expected);
#if defined __clang__ && (__clang_major__ > 3 || __clang_minor__ >= 6)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunevaluated-expression"
#endif
EXPECT_EQ(typeid(ie), typeid(IntException));
#if defined __clang__ && (__clang_major__ > 3 || __clang_minor__ >= 6)
# pragma clang diagnostic pop
#endif
});
// Test with const this. If this compiles and does not crash due to
// infinite loop when it runs, it succeeds.
const exception_wrapper& cew = ew;
cew.with_exception<IntException>([&](const IntException& ie) {
SUCCEED();
});
// This won't even compile. You can't use a function which takes a
// non-const reference with a const exception_wrapper.
/*
cew.with_exception<IntException>([&](IntException& ie) {
SUCCEED();
});
*/
}
TEST(ExceptionWrapper, non_std_exception_test) {
int expected = 17;
exception_wrapper ew = try_and_catch<std::exception, int>(
[=]() {
throw expected;
});
EXPECT_TRUE(bool(ew));
EXPECT_FALSE(ew.is_compatible_with<std::exception>());
EXPECT_EQ(ew.what(), "int");
EXPECT_EQ(ew.class_name(), "int");
// non-std::exception types are supported, but the only way to
// access their value is to explicity rethrow and catch it.
try {
ew.throwException();
} catch /* nolint */ (int& i) {
EXPECT_EQ(i, expected);
}
}
TEST(ExceptionWrapper, exceptionStr) {
auto ew = make_exception_wrapper<std::runtime_error>("argh");
EXPECT_EQ("std::runtime_error: argh", exceptionStr(ew));
}
namespace {
class TestException : public std::exception { };
void testEW(const exception_wrapper& ew) {
EXPECT_THROW(ew.throwException(), TestException);
}
} // namespace
TEST(ExceptionWrapper, implicitConstruction) {
// Try with both lvalue and rvalue references
TestException e;
testEW(e);
testEW(TestException());
}