Commit 8f858fc4 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #441 from sandstorm-io/fix-no-exceptions

Fix build with -fno-exceptions
parents dec6bafa 429d796c
......@@ -198,42 +198,48 @@ KJ_TEST("decode all types") {
auto root = message.initRoot<TestAllTypes>(); \
KJ_EXPECT_THROW_MESSAGE(errorMessage, json.decode(s, root)); \
}
#define CASE_THROW_RECOVERABLE(s, errorMessage) \
{ \
MallocMessageBuilder message; \
auto root = message.initRoot<TestAllTypes>(); \
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(errorMessage, json.decode(s, root)); \
}
CASE(R"({})", root.getBoolField() == false);
CASE(R"({"unknownField":7})", root.getBoolField() == false);
CASE(R"({"boolField":true})", root.getBoolField() == true);
CASE(R"({"int8Field":-128})", root.getInt8Field() == -128);
CASE(R"({"int8Field":"127"})", root.getInt8Field() == 127);
CASE_THROW(R"({"int8Field":"-129"})", "Value out-of-range");
CASE_THROW(R"({"int8Field":128})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int8Field":"-129"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int8Field":128})", "Value out-of-range");
CASE(R"({"int16Field":-32768})", root.getInt16Field() == -32768);
CASE(R"({"int16Field":"32767"})", root.getInt16Field() == 32767);
CASE_THROW(R"({"int16Field":"-32769"})", "Value out-of-range");
CASE_THROW(R"({"int16Field":32768})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int16Field":"-32769"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int16Field":32768})", "Value out-of-range");
CASE(R"({"int32Field":-2147483648})", root.getInt32Field() == -2147483648);
CASE(R"({"int32Field":"2147483647"})", root.getInt32Field() == 2147483647);
CASE(R"({"int64Field":-9007199254740992})", root.getInt64Field() == -9007199254740992LL);
CASE(R"({"int64Field":9007199254740991})", root.getInt64Field() == 9007199254740991LL);
CASE(R"({"int64Field":"-9223372036854775808"})", root.getInt64Field() == -9223372036854775808ULL);
CASE(R"({"int64Field":"9223372036854775807"})", root.getInt64Field() == 9223372036854775807LL);
CASE_THROW(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range");
CASE_THROW(R"({"int64Field":"9223372036854775808"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int64Field":"-9223372036854775809"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"int64Field":"9223372036854775808"})", "Value out-of-range");
CASE(R"({"uInt8Field":255})", root.getUInt8Field() == 255);
CASE(R"({"uInt8Field":"0"})", root.getUInt8Field() == 0);
CASE_THROW(R"({"uInt8Field":"256"})", "Value out-of-range");
CASE_THROW(R"({"uInt8Field":-1})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt8Field":"256"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt8Field":-1})", "Value out-of-range");
CASE(R"({"uInt16Field":65535})", root.getUInt16Field() == 65535);
CASE(R"({"uInt16Field":"0"})", root.getUInt16Field() == 0);
CASE_THROW(R"({"uInt16Field":"655356"})", "Value out-of-range");
CASE_THROW(R"({"uInt16Field":-1})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt16Field":"655356"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt16Field":-1})", "Value out-of-range");
CASE(R"({"uInt32Field":4294967295})", root.getUInt32Field() == 4294967295);
CASE(R"({"uInt32Field":"0"})", root.getUInt32Field() == 0);
CASE_THROW(R"({"uInt32Field":"42949672956"})", "Value out-of-range");
CASE_THROW(R"({"uInt32Field":-1})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt32Field":"42949672956"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt32Field":-1})", "Value out-of-range");
CASE(R"({"uInt64Field":9007199254740991})", root.getUInt64Field() == 9007199254740991ULL);
CASE(R"({"uInt64Field":"18446744073709551615"})", root.getUInt64Field() == 18446744073709551615ULL);
CASE(R"({"uInt64Field":"0"})", root.getUInt64Field() == 0);
CASE_THROW(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range");
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"18446744073709551616"})", "Value out-of-range");
CASE(R"({"float32Field":0})", root.getFloat32Field() == 0);
CASE(R"({"float32Field":4.5})", root.getFloat32Field() == 4.5);
CASE(R"({"float32Field":null})", kj::isNaN(root.getFloat32Field()));
......@@ -258,9 +264,9 @@ KJ_TEST("decode all types") {
CASE(R"({"structField":{"boolField":true}})", root.getStructField().getBoolField() == true);
CASE(R"({"enumField":"bar"})", root.getEnumField() == TestEnum::BAR);
CASE_THROW(R"({"int64Field":"177a"})", "String does not contain valid");
CASE_THROW(R"({"uInt64Field":"177a"})", "String does not contain valid");
CASE_THROW(R"({"float64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"int64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"uInt64Field":"177a"})", "String does not contain valid");
CASE_THROW_RECOVERABLE(R"({"float64Field":"177a"})", "String does not contain valid");
CASE(R"({})", root.hasBoolList() == false);
CASE(R"({"boolList":null})", root.hasBoolList() == false);
......@@ -309,6 +315,7 @@ KJ_TEST("decode all types") {
CASE(R"({"enumList":["bar"]})", root.getEnumList()[0] == TestEnum::BAR);
#undef CASE
#undef CASE_THROW
#undef CASE_THROW_RECOVERABLE
}
KJ_TEST("decode test message") {
......
......@@ -107,19 +107,19 @@ void traverseCatchingExceptions(kj::ArrayPtr<const word> data) {
// Try traversing through Checker.
kj::runCatchingExceptions([&]() {
FlatArrayMessageReader reader(data);
KJ_ASSERT(Checker::check(reader) != 0);
KJ_ASSERT(Checker::check(reader) != 0) { break; }
});
// Try traversing through AnyPointer.
kj::runCatchingExceptions([&]() {
FlatArrayMessageReader reader(data);
KJ_ASSERT(traverse(reader.getRoot<AnyPointer>()) != 0);
KJ_ASSERT(traverse(reader.getRoot<AnyPointer>()) != 0) { break; }
});
// Try counting the size..
kj::runCatchingExceptions([&]() {
FlatArrayMessageReader reader(data);
KJ_ASSERT(reader.getRoot<AnyPointer>().targetSize().wordCount != 0);
KJ_ASSERT(reader.getRoot<AnyPointer>().targetSize().wordCount != 0) { break; }
});
// Try copying into a builder, and if that works, traversing it with Checker.
......@@ -128,7 +128,7 @@ void traverseCatchingExceptions(kj::ArrayPtr<const word> data) {
FlatArrayMessageReader reader(data);
MallocMessageBuilder copyBuilder(buffer);
copyBuilder.setRoot(reader.getRoot<AnyPointer>());
KJ_ASSERT(Checker::check(copyBuilder) != 0);
KJ_ASSERT(Checker::check(copyBuilder) != 0) { break; }
});
}
......
......@@ -555,11 +555,17 @@ struct WireHelpers {
// object.
ref = pad + 1;
segment = segment->getArena()->tryGetSegment(pad->farRef.segmentId.get());
KJ_REQUIRE(segment != nullptr, "Message contains double-far pointer to unknown segment.") {
SegmentReader* newSegment = segment->getArena()->tryGetSegment(pad->farRef.segmentId.get());
KJ_REQUIRE(newSegment != nullptr,
"Message contains double-far pointer to unknown segment.") {
return nullptr;
}
KJ_REQUIRE(pad->kind() == WirePointer::FAR,
"Second word of double-far pad must be far pointer.") {
return nullptr;
}
segment = newSegment;
return segment->getStartPtr() + pad->farPositionInSegment();
} else {
return refTarget;
......@@ -1611,20 +1617,30 @@ struct WireHelpers {
}
} else {
word* ptr = followFars(ref, refTarget, segment);
char* cptr = reinterpret_cast<char*>(ptr);
byte* bptr = reinterpret_cast<byte*>(ptr);
KJ_REQUIRE(ref->kind() == WirePointer::LIST,
"Called getText{Field,Element}() but existing pointer is not a list.");
"Called getText{Field,Element}() but existing pointer is not a list.") {
goto useDefault;
}
KJ_REQUIRE(ref->listRef.elementSize() == ElementSize::BYTE,
"Called getText{Field,Element}() but existing list pointer is not byte-sized.");
"Called getText{Field,Element}() but existing list pointer is not byte-sized.") {
goto useDefault;
}
size_t size = unbound(subtractChecked(ref->listRef.elementCount() / ELEMENTS, ONE,
[]() { KJ_FAIL_REQUIRE("zero-size blob can't be text (need NUL terminator)"); }));
KJ_REQUIRE(cptr[size] == '\0', "Text blob missing NUL terminator.") {
auto maybeSize = trySubtract(ref->listRef.elementCount() * (ONE * BYTES / ELEMENTS),
ONE * BYTES);
KJ_IF_MAYBE(size, maybeSize) {
KJ_REQUIRE(*(bptr + *size) == '\0', "Text blob missing NUL terminator.") {
goto useDefault;
}
return Text::Builder(cptr, size);
return Text::Builder(reinterpret_cast<char*>(bptr), unbound(*size / BYTES));
} else {
KJ_FAIL_REQUIRE("zero-size blob can't be text (need NUL terminator)") {
goto useDefault;
};
}
}
}
......@@ -1663,6 +1679,7 @@ struct WireHelpers {
WirePointer* ref, word* refTarget, SegmentBuilder* segment, CapTableBuilder* capTable,
const void* defaultValue, BlobSize defaultSize)) {
if (ref->isNull()) {
useDefault:
if (defaultSize == ZERO * BYTES) {
return nullptr;
} else {
......@@ -1674,9 +1691,13 @@ struct WireHelpers {
word* ptr = followFars(ref, refTarget, segment);
KJ_REQUIRE(ref->kind() == WirePointer::LIST,
"Called getData{Field,Element}() but existing pointer is not a list.");
"Called getData{Field,Element}() but existing pointer is not a list.") {
goto useDefault;
}
KJ_REQUIRE(ref->listRef.elementSize() == ElementSize::BYTE,
"Called getData{Field,Element}() but existing list pointer is not byte-sized.");
"Called getData{Field,Element}() but existing list pointer is not byte-sized.") {
goto useDefault;
}
return Data::Builder(reinterpret_cast<byte*>(ptr),
unbound(ref->listRef.elementCount() / ELEMENTS));
......@@ -2653,13 +2674,13 @@ PointerType PointerReader::getPointerType() const {
WireHelpers::followFars(ptr, refTarget, sgmt);
switch(ptr->kind()) {
case WirePointer::FAR:
KJ_FAIL_ASSERT("far pointer not followed?");
KJ_FAIL_ASSERT("far pointer not followed?") { return PointerType::NULL_; }
case WirePointer::STRUCT:
return PointerType::STRUCT;
case WirePointer::LIST:
return PointerType::LIST;
case WirePointer::OTHER:
KJ_REQUIRE(ptr->isCapability(), "unknown pointer type");
KJ_REQUIRE(ptr->isCapability(), "unknown pointer type") { return PointerType::NULL_; }
return PointerType::CAPABILITY;
}
KJ_UNREACHABLE;
......
......@@ -104,7 +104,9 @@ inline KJ_CONSTEXPR() BitsPerElementTableType dataBitsPerElement(ElementSize siz
}
inline constexpr PointersPerElementN<1> pointersPerElement(ElementSize size) {
return size == ElementSize::POINTER ? ONE * POINTERS / ELEMENTS : ZERO * POINTERS / ELEMENTS;
return size == ElementSize::POINTER
? PointersPerElementN<1>(ONE * POINTERS / ELEMENTS)
: PointersPerElementN<1>(ZERO * POINTERS / ELEMENTS);
}
static constexpr BitsPerElementTableType BITS_PER_ELEMENT_INCLUDING_PONITERS_TABLE[8] = {
......
......@@ -351,13 +351,14 @@ TEST(TwoPartyNetwork, HugeMessage) {
{
auto req = client.methodWithDefaultsRequest();
req.initA(100000000); // 100 MB
KJ_EXPECT_THROW_MESSAGE("larger than the single-message size limit",
req.send().wait(ioContext.waitScope));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("larger than the single-message size limit",
req.send().ignoreResult().wait(ioContext.waitScope));
}
// Oversized response fails.
KJ_EXPECT_THROW_MESSAGE("larger than the single-message size limit",
client.getEnormousStringRequest().send().wait(ioContext.waitScope));
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("larger than the single-message size limit",
client.getEnormousStringRequest().send().ignoreResult().wait(ioContext.waitScope));
// Connection is still up.
{
......
......@@ -448,6 +448,9 @@ private:
bool isTailCall = false;
// Is this a tail call? If so, we don't expect to receive results in the `Return`.
bool skipFinish = false;
// If true, don't send a Finish message.
inline bool operator==(decltype(nullptr)) const {
return !isAwaitingReturn && selfRef == nullptr;
}
......@@ -1331,7 +1334,7 @@ private:
connectionState->questions.find(id), "Question ID no longer on table?");
// Send the "Finish" message (if the connection is not already broken).
if (connectionState->connection.is<Connected>()) {
if (connectionState->connection.is<Connected>() && !question.skipFinish) {
auto message = connectionState->connection.get<Connected>()->newOutgoingMessage(
messageSizeHint<rpc::Finish>());
auto builder = message->getBody().getAs<rpc::Message>().initFinish();
......@@ -1504,6 +1507,14 @@ private:
question.paramExports = kj::mv(exports);
question.isTailCall = isTailCall;
// Make the QuentionRef and result promise.
SendInternalResult result;
auto paf = kj::newPromiseAndFulfiller<kj::Promise<kj::Own<RpcResponse>>>();
result.questionRef = kj::refcounted<QuestionRef>(
*connectionState, questionId, kj::mv(paf.fulfiller));
question.selfRef = *result.questionRef;
result.promise = paf.promise.attach(kj::addRef(*result.questionRef));
// Finish and send.
callBuilder.setQuestionId(questionId);
if (isTailCall) {
......@@ -1514,18 +1525,13 @@ private:
callBuilder.getInterfaceId(), callBuilder.getMethodId());
message->send();
})) {
KJ_LOG(WARNING, *exception);
kj::throwRecoverableException(kj::mv(*exception));
// We can't safely throw the exception from here since we've already modified the question
// table state. We'll have to reject the promise instead.
question.isAwaitingReturn = false;
question.skipFinish = true;
result.questionRef->reject(kj::mv(*exception));
}
// Make the result promise.
SendInternalResult result;
auto paf = kj::newPromiseAndFulfiller<kj::Promise<kj::Own<RpcResponse>>>();
result.questionRef = kj::refcounted<QuestionRef>(
*connectionState, questionId, kj::mv(paf.fulfiller));
question.selfRef = *result.questionRef;
result.promise = paf.promise.attach(kj::addRef(*result.questionRef));
// Send and return.
return kj::mv(result);
}
......@@ -1819,7 +1825,6 @@ private:
KJ_CONTEXT("returning from RPC call", interfaceId, methodId);
exports = kj::downcast<RpcServerResponseImpl>(*KJ_ASSERT_NONNULL(response)).send();
})) {
KJ_LOG(WARNING, *exception);
responseSent = false;
sendErrorReturn(kj::mv(*exception));
return;
......@@ -2268,7 +2273,7 @@ private:
// Add the answer to the answer table for pipelining and send the response.
auto& answer = answers[answerId];
KJ_REQUIRE(!answer.active, "questionId is already in use") {
KJ_REQUIRE(!answer.active, "questionId is already in use", answerId) {
return;
}
......
......@@ -35,20 +35,6 @@
#include "dynamic.h"
#endif // !CAPNP_LITE
#if KJ_NO_EXCEPTIONS
#undef EXPECT_ANY_THROW
#define EXPECT_ANY_THROW(code) EXPECT_DEATH(code, ".")
#endif
#define EXPECT_NONFATAL_FAILURE(code) \
EXPECT_TRUE(kj::runCatchingExceptions([&]() { code; }) != nullptr);
#ifdef KJ_DEBUG
#define EXPECT_DEBUG_ANY_THROW EXPECT_ANY_THROW
#else
#define EXPECT_DEBUG_ANY_THROW(EXP)
#endif
// TODO(cleanup): Auto-generate stringification functions for union discriminants.
namespace capnproto_test {
namespace capnp {
......
......@@ -869,6 +869,26 @@ T Promise<T>::wait(WaitScope& waitScope) {
}
}
template <>
inline void Promise<void>::wait(WaitScope& waitScope) {
// Override <void> case to use throwRecoverableException().
_::ExceptionOr<_::Void> result;
waitImpl(kj::mv(node), result, waitScope);
if (result.value != nullptr) {
KJ_IF_MAYBE(exception, result.exception) {
throwRecoverableException(kj::mv(*exception));
}
} else KJ_IF_MAYBE(exception, result.exception) {
throwRecoverableException(kj::mv(*exception));
} else {
// Result contained neither a value nor an exception?
KJ_UNREACHABLE;
}
}
template <typename T>
ForkedPromise<T> Promise<T>::fork() {
return ForkedPromise<T>(false, refcounted<_::ForkHub<_::FixVoid<T>>>(kj::mv(node)));
......
......@@ -357,11 +357,6 @@ TEST(Async, SeparateFulfillerChained) {
EXPECT_EQ(123, pair.promise.wait(waitScope));
}
#if KJ_NO_EXCEPTIONS
#undef EXPECT_ANY_THROW
#define EXPECT_ANY_THROW(code) EXPECT_DEATH(code, ".")
#endif
TEST(Async, SeparateFulfillerDiscarded) {
EventLoop loop;
WaitScope waitScope(loop);
......
......@@ -245,13 +245,7 @@ TEST(Common, Downcast) {
EXPECT_EQ(&bar, &downcast<Bar>(foo));
#if defined(KJ_DEBUG) && !KJ_NO_RTTI
#if KJ_NO_EXCEPTIONS
#ifdef KJ_DEBUG
EXPECT_DEATH_IF_SUPPORTED(downcast<Baz>(foo), "Value cannot be downcast");
#endif
#else
EXPECT_ANY_THROW(downcast<Baz>(foo));
#endif
KJ_EXPECT_THROW_MESSAGE("Value cannot be downcast", downcast<Baz>(foo));
#endif
#if KJ_NO_RTTI
......
......@@ -98,8 +98,22 @@ private:
#define ADD_FAILURE() ::kj::AddFailureAdapter(__FILE__, __LINE__)
#if KJ_NO_EXCEPTIONS
#define EXPECT_ANY_THROW(code) \
KJ_EXPECT(::kj::_::expectFatalThrow(nullptr, nullptr, [&]() { code; }))
#else
#define EXPECT_ANY_THROW(code) \
KJ_EXPECT(::kj::runCatchingExceptions([&]() { code; }) != nullptr)
#endif
#define EXPECT_NONFATAL_FAILURE(code) \
EXPECT_TRUE(kj::runCatchingExceptions([&]() { code; }) != nullptr);
#ifdef KJ_DEBUG
#define EXPECT_DEBUG_ANY_THROW EXPECT_ANY_THROW
#else
#define EXPECT_DEBUG_ANY_THROW(EXP)
#endif
#define TEST(x, y) KJ_TEST("legacy test: " #x "/" #y)
......
......@@ -97,11 +97,7 @@ TEST(Exception, UnwindDetector) {
#if !__MINGW32__ // Inexplicably crashes when exception is thrown from constructor.
TEST(Exception, ExceptionCallbackMustBeOnStack) {
#if KJ_NO_EXCEPTIONS
EXPECT_DEATH_IF_SUPPORTED(new ExceptionCallback, "must be allocated on the stack");
#else
EXPECT_ANY_THROW(new ExceptionCallback);
#endif
KJ_EXPECT_THROW_MESSAGE("must be allocated on the stack", new ExceptionCallback);
}
#endif // !__MINGW32__
......
......@@ -42,20 +42,6 @@ inline void delay() { Sleep(10); }
inline void delay() { usleep(10000); }
#endif
#if KJ_NO_EXCEPTIONS
#undef EXPECT_ANY_THROW
#define EXPECT_ANY_THROW(code) EXPECT_DEATH(code, ".")
#define EXPECT_NONFATAL_FAILURE(code) code
#else
#define EXPECT_NONFATAL_FAILURE EXPECT_ANY_THROW
#endif
#ifdef KJ_DEBUG
#define EXPECT_DEBUG_ANY_THROW EXPECT_ANY_THROW
#else
#define EXPECT_DEBUG_ANY_THROW(EXP)
#endif
TEST(Mutex, MutexGuarded) {
MutexGuarded<uint> value(123);
......
......@@ -86,22 +86,22 @@ TEST(String, parseAs) {
EXPECT_TRUE(isNaN(StringPtr("nan").parseAs<double>()));
EXPECT_TRUE(isNaN(StringPtr("NAN").parseAs<double>()));
EXPECT_TRUE(isNaN(StringPtr("NaN").parseAs<double>()));
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<double>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<double>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("").parseAs<double>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("a").parseAs<double>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("1a").parseAs<double>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("+-1").parseAs<double>());
EXPECT_EQ(StringPtr("1").parseAs<float>(), 1.0);
EXPECT_EQ(StringPtr("1").parseAs<int64_t>(), 1);
EXPECT_EQ(StringPtr("9223372036854775807").parseAs<int64_t>(), 9223372036854775807LL);
EXPECT_EQ(StringPtr("-9223372036854775808").parseAs<int64_t>(), -9223372036854775808ULL);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("9223372036854775808").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-9223372036854775809").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<int64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("9223372036854775808").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("-9223372036854775809").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("a").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("1a").parseAs<int64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("+-1").parseAs<int64_t>());
EXPECT_EQ(StringPtr("010").parseAs<int64_t>(), 10);
EXPECT_EQ(StringPtr("0010").parseAs<int64_t>(), 10);
EXPECT_EQ(StringPtr("0x10").parseAs<int64_t>(), 16);
......@@ -113,24 +113,24 @@ TEST(String, parseAs) {
EXPECT_EQ(StringPtr("1").parseAs<uint64_t>(), 1);
EXPECT_EQ(StringPtr("0").parseAs<uint64_t>(), 0);
EXPECT_EQ(StringPtr("18446744073709551615").parseAs<uint64_t>(), 18446744073709551615ULL);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("18446744073709551616").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("a").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("1a").parseAs<uint64_t>());
KJ_EXPECT_THROW_MESSAGE("not contain valid", StringPtr("+-1").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("18446744073709551616").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("a").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("1a").parseAs<uint64_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("not contain valid", StringPtr("+-1").parseAs<uint64_t>());
EXPECT_EQ(StringPtr("1").parseAs<int32_t>(), 1);
EXPECT_EQ(StringPtr("2147483647").parseAs<int32_t>(), 2147483647);
EXPECT_EQ(StringPtr("-2147483648").parseAs<int32_t>(), -2147483648);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("2147483648").parseAs<int32_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-2147483649").parseAs<int32_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("2147483648").parseAs<int32_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("-2147483649").parseAs<int32_t>());
EXPECT_EQ(StringPtr("1").parseAs<uint32_t>(), 1);
EXPECT_EQ(StringPtr("0").parseAs<uint32_t>(), 0U);
EXPECT_EQ(StringPtr("4294967295").parseAs<uint32_t>(), 4294967295U);
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint32_t>());
KJ_EXPECT_THROW_MESSAGE("out-of-range", StringPtr("4294967296").parseAs<uint32_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("-1").parseAs<uint32_t>());
KJ_EXPECT_THROW_RECOVERABLE_MESSAGE("out-of-range", StringPtr("4294967296").parseAs<uint32_t>());
EXPECT_EQ(StringPtr("1").parseAs<int16_t>(), 1);
EXPECT_EQ(StringPtr("1").parseAs<uint16_t>(), 1);
......
......@@ -40,25 +40,26 @@ bool isHex(const char *s) {
}
long long parseSigned(const StringPtr& s, long long min, long long max) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s) { return 0; }
char *endPtr;
errno = 0;
auto value = strtoll(s.begin(), &endPtr, isHex(s.cStr()) ? 16 : 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
KJ_REQUIRE(value >= min && value <= max, "Value out-of-range", value, min, max);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s) { return 0; }
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s) { return 0; }
KJ_REQUIRE(value >= min && value <= max, "Value out-of-range", value, min, max) { return 0; }
return value;
}
unsigned long long parseUnsigned(const StringPtr& s, unsigned long long max) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s) { return 0; }
char *endPtr;
errno = 0;
auto value = strtoull(s.begin(), &endPtr, isHex(s.cStr()) ? 16 : 10);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s);
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s);
KJ_REQUIRE(value <= max, "Value out-of-range", value, max);
KJ_REQUIRE(s[0] != '-', "Value out-of-range", s); //strtoull("-1") does not fail with ERANGE
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid number", s) { return 0; }
KJ_REQUIRE(errno != ERANGE, "Value out-of-range", s) { return 0; }
KJ_REQUIRE(value <= max, "Value out-of-range", value, max) { return 0; }
//strtoull("-1") does not fail with ERANGE
KJ_REQUIRE(s[0] != '-', "Value out-of-range", s) { return 0; }
return value;
}
......@@ -75,11 +76,11 @@ T parseInteger(const StringPtr& s) {
}
double parseDouble(const StringPtr& s) {
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s);
KJ_REQUIRE(s != nullptr, "String does not contain valid number", s) { return 0; }
char *endPtr;
errno = 0;
auto value = strtod(s.begin(), &endPtr);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid floating number", s);
KJ_REQUIRE(endPtr == s.end(), "String does not contain valid floating number", s) { return 0; }
return value;
}
......
......@@ -28,6 +28,9 @@
#include <string.h>
#ifndef _WIN32
#include <sys/mman.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
namespace kj {
......@@ -59,7 +62,7 @@ TestCase::~TestCase() {
namespace _ { // private
bool hasSubstring(kj::StringPtr haystack, kj::StringPtr needle) {
bool hasSubstring(StringPtr haystack, StringPtr needle) {
// TODO(perf): This is not the best algorithm for substring matching.
if (needle.size() <= haystack.size()) {
for (size_t i = 0; i <= haystack.size() - needle.size(); i++) {
......@@ -96,6 +99,74 @@ void LogExpectation::logMessage(
// =======================================================================================
namespace {
class FatalThrowExpectation: public ExceptionCallback {
public:
FatalThrowExpectation(Maybe<Exception::Type> type,
Maybe<StringPtr> message)
: type(type), message(message) {}
virtual void onFatalException(Exception&& exception) {
KJ_IF_MAYBE(expectedType, type) {
if (exception.getType() != *expectedType) {
KJ_LOG(ERROR, "threw exception of wrong type", exception, *expectedType);
_exit(1);
}
}
KJ_IF_MAYBE(expectedSubstring, message) {
if (!hasSubstring(exception.getDescription(), *expectedSubstring)) {
KJ_LOG(ERROR, "threw exception with wrong message", exception, *expectedSubstring);
_exit(1);
}
}
_exit(0);
}
private:
Maybe<Exception::Type> type;
Maybe<StringPtr> message;
};
} // namespace
bool expectFatalThrow(kj::Maybe<Exception::Type> type, kj::Maybe<StringPtr> message,
Function<void()> code) {
#if _WIN32
// We don't support death tests on Windows due to lack of efficient fork.
return true;
#else
pid_t child;
KJ_SYSCALL(child = fork());
if (child == 0) {
KJ_DEFER(_exit(1));
FatalThrowExpectation expectation(type, message);
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
code();
})) {
KJ_LOG(ERROR, "a non-fatal exception was thrown, but we expected fatal", *e);
} else {
KJ_LOG(ERROR, "no fatal exception was thrown");
}
}
int status;
KJ_SYSCALL(waitpid(child, &status, 0));
if (WIFEXITED(status)) {
return WEXITSTATUS(status) == 0;
} else if (WIFSIGNALED(status)) {
KJ_FAIL_EXPECT("subprocess crashed without throwing exception", WTERMSIG(status));
return false;
} else {
KJ_FAIL_EXPECT("subprocess neiter excited nor crashed?", status);
return false;
}
#endif
}
// =======================================================================================
GlobFilter::GlobFilter(const char* pattern): pattern(heapString(pattern)) {}
GlobFilter::GlobFilter(ArrayPtr<const char> pattern): pattern(heapString(pattern)) {}
......
......@@ -28,6 +28,7 @@
#include "debug.h"
#include "vector.h"
#include "function.h"
namespace kj {
......@@ -74,7 +75,7 @@ private:
if (cond); else KJ_FAIL_EXPECT("failed: expected " #cond, ##__VA_ARGS__)
#endif
#define KJ_EXPECT_THROW(type, code) \
#define KJ_EXPECT_THROW_RECOVERABLE(type, code) \
do { \
KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \
KJ_EXPECT(e->getType() == ::kj::Exception::Type::type, \
......@@ -84,7 +85,7 @@ private:
} \
} while (false)
#define KJ_EXPECT_THROW_MESSAGE(message, code) \
#define KJ_EXPECT_THROW_RECOVERABLE_MESSAGE(message, code) \
do { \
KJ_IF_MAYBE(e, ::kj::runCatchingExceptions([&]() { code; })) { \
KJ_EXPECT(::kj::_::hasSubstring(e->getDescription(), message), \
......@@ -94,6 +95,20 @@ private:
} \
} while (false)
#if KJ_NO_EXCEPTIONS
#define KJ_EXPECT_THROW(type, code) \
do { \
KJ_EXPECT(::kj::_::expectFatalThrow(type, nullptr, [&]() { code; })); \
} while (false)
#define KJ_EXPECT_THROW_MESSAGE(message, code) \
do { \
KJ_EXPECT(::kj::_::expectFatalThrow(nullptr, kj::StringPtr(message), [&]() { code; })); \
} while (false)
#else
#define KJ_EXPECT_THROW KJ_EXPECT_THROW_RECOVERABLE
#define KJ_EXPECT_THROW_MESSAGE KJ_EXPECT_THROW_RECOVERABLE_MESSAGE
#endif
#define KJ_EXPECT_LOG(level, substring) \
::kj::_::LogExpectation KJ_UNIQUE_NAME(_kjLogExpectation)(::kj::LogSeverity::level, substring)
// Expects that a log message with the given level and substring text will be printed within
......@@ -105,6 +120,14 @@ namespace _ { // private
bool hasSubstring(kj::StringPtr haystack, kj::StringPtr needle);
#if KJ_NO_EXCEPTIONS
bool expectFatalThrow(Maybe<Exception::Type> type, Maybe<StringPtr> message,
Function<void()> code);
// Expects that the given code will throw a fatal exception matching the given type and/or message.
// Since exceptions are disabled, the test will fork() and run in a subprocess. On Windows, where
// fork() is not available, this always returns true.
#endif
class LogExpectation: public ExceptionCallback {
public:
LogExpectation(LogSeverity severity, StringPtr substring);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment