Skip to content

Firestore: add helper functions for integration testing Java exceptions in Android #1346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,66 @@ void FirestoreAndroidIntegrationTest::SetUp() {
void FirestoreAndroidIntegrationTest::TearDown() {
// Fail the test if there is a pending Java exception. Clear the pending
// exception as well so that it doesn't bleed into the next test.
FailTestIfPendingException();

// Perform the tear-down steps of the superclass.
FirestoreIntegrationTest::TearDown();
}

void FirestoreAndroidIntegrationTest::FailTestIfPendingException() {
Env env;
Local<Throwable> pending_exception = env.ClearExceptionOccurred();
EXPECT_FALSE(pending_exception)
<< "Test completed with a pending Java exception: "
<< pending_exception.ToString(env);
if (!pending_exception) {
return;
}

// Ignore the exception if it was thrown by the last ThrowException() call.
if (env.IsSameObject(pending_exception, last_thrown_exception_)) {
return;
}

// Fail the test since the test completed with a pending exception.
std::string pending_exception_as_string = pending_exception.ToString(env);
env.ExceptionClear();
FirestoreIntegrationTest::TearDown();
FAIL() << "Test completed with a pending Java exception: "
<< pending_exception_as_string;
}

Local<Throwable> FirestoreAndroidIntegrationTest::CreateException(Env& env) {
return CreateException(env,
"Test exception created by "
"FirestoreAndroidIntegrationTest::CreateException()");
}

Local<Throwable> FirestoreAndroidIntegrationTest::CreateException(
Env& env, const std::string& message) {
ExceptionClearGuard block(env);
ExceptionClearGuard exception_clear_guard(env);
Local<String> java_message = env.NewStringUtf(message);
return env.New(kExceptionConstructor, java_message);
}

Local<Throwable> FirestoreAndroidIntegrationTest::ThrowException(Env& env) {
return ThrowException(env,
"Test exception thrown by "
"FirestoreAndroidIntegrationTest::ThrowException()");
}

Local<Throwable> FirestoreAndroidIntegrationTest::ThrowException(
Env& env, const std::string& message) {
if (!env.ok()) {
ADD_FAILURE() << "ThrowException() invoked while there is already a "
"pending exception";
return {};
}
Local<Throwable> exception = CreateException(env, message);

// Silently discard this exception if the test ends with it still pending.
last_thrown_exception_ = exception;

env.Throw(exception);
return exception;
}

void FirestoreAndroidIntegrationTest::Await(Env& env, const Task& task) {
int cycles = kTimeOutMillis / kCheckIntervalMillis;
while (env.ok() && !task.IsComplete(env)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,26 @@ MATCHER_P(JavaEq,
return jni::Object::Equals(env, object, arg);
}

/**
* A gmock matcher that compares two Java objects for equality using the ==
* operator; that is, that they both refer to the _same_ Java object.
*
* Example:
*
* jni::Env env;
* jni::Local<jni::String> object1 = env.NewStringUtf("string");
* jni::Local<jni::String> object2 = object1;
* EXPECT_THAT(object1, RefersToSameJavaObjectAs(object2));
*/
MATCHER_P(RefersToSameJavaObjectAs,
object,
std::string("is ") + (negation ? "not " : "") +
" referring to the same object as " + ToDebugString(object)) {
jni::Env env;
jni::ExceptionClearGuard block(env);
return env.IsSameObject(arg, object);
}

/** Adds Android-specific functionality to `FirestoreIntegrationTest`. */
class FirestoreAndroidIntegrationTest : public FirestoreIntegrationTest {
public:
Expand All @@ -85,9 +105,17 @@ class FirestoreAndroidIntegrationTest : public FirestoreIntegrationTest {

jni::Loader& loader() { return loader_; }

/** Creates and returns a new Java `Exception` object with a message. */
jni::Local<jni::Throwable> CreateException(jni::Env& env,
const std::string& message);
/** Creates and returns a new Java `Exception` with a default message. */
static jni::Local<jni::Throwable> CreateException(jni::Env&);
/** Creates and returns a new Java `Exception` with the given message. */
static jni::Local<jni::Throwable> CreateException(jni::Env&,
const std::string& message);

/** Throws a Java `Exception` object with a default message. */
jni::Local<jni::Throwable> ThrowException(jni::Env&);
/** Throws a Java `Exception` object with the given message. */
jni::Local<jni::Throwable> ThrowException(jni::Env&,
const std::string& message);

// Bring definitions of `Await()` from the superclass into this class so that
// the definition below *overloads* instead of *hides* them.
Expand All @@ -97,7 +125,10 @@ class FirestoreAndroidIntegrationTest : public FirestoreIntegrationTest {
static void Await(jni::Env& env, const jni::Task& task);

private:
void FailTestIfPendingException();

jni::Loader loader_;
jni::Global<jni::Throwable> last_thrown_exception_;
};

} // namespace firestore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ namespace {

using jni::ArrayList;
using jni::Env;
using jni::Global;
using jni::Local;
using jni::Object;
using jni::String;
using jni::Throwable;

using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::StrEq;

TEST_F(FirestoreAndroidIntegrationTest, ToDebugStringWithNonNull) {
Env env;
Expand All @@ -57,7 +61,7 @@ TEST_F(FirestoreAndroidIntegrationTest,
ToDebugStringWithPendingExceptionAndNonNullObject) {
Env env;
Local<String> object = env.NewStringUtf("Test Value");
env.Throw(CreateException(env, "forced exception"));
ThrowException(env);
ASSERT_FALSE(env.ok());

std::string debug_string = ToDebugString(object);
Expand All @@ -70,7 +74,7 @@ TEST_F(FirestoreAndroidIntegrationTest,
ToDebugStringWithPendingExceptionAndNullObject) {
Env env;
Object null_reference;
env.Throw(CreateException(env, "forced exception"));
ThrowException(env);
ASSERT_FALSE(env.ok());

std::string debug_string = ToDebugString(null_reference);
Expand Down Expand Up @@ -125,6 +129,85 @@ TEST_F(FirestoreAndroidIntegrationTest,
EXPECT_THAT(list_object, Not(JavaEq(string_object)));
}

TEST_F(FirestoreAndroidIntegrationTest,
RefersToSameJavaObjectAsShouldReturnTrueForSameObjects) {
Env env;
Local<String> object1 = env.NewStringUtf("string");
Global<String> object2 = object1;

EXPECT_THAT(object1, RefersToSameJavaObjectAs(object2));
}

TEST_F(FirestoreAndroidIntegrationTest,
RefersToSameJavaObjectAsShouldReturnTrueForTwoNullReferences) {
Local<Object> null_reference1;
Local<Object> null_reference2;

EXPECT_THAT(null_reference1, RefersToSameJavaObjectAs(null_reference2));
}

TEST_F(FirestoreAndroidIntegrationTest,
RefersToSameJavaObjectAsShouldReturnFalseForDistinctObjects) {
Env env;
Local<String> object1 = env.NewStringUtf("test string");
Local<String> object2 = env.NewStringUtf("test string");
ASSERT_FALSE(env.IsSameObject(object1, object2));

EXPECT_THAT(object1, Not(RefersToSameJavaObjectAs(object2)));
}

TEST_F(FirestoreAndroidIntegrationTest,
RefersToSameJavaObjectAsShouldReturnFalseIfExactlyOneObjectIsNull) {
Env env;
Local<String> null_reference;
Local<String> non_null_reference = env.NewStringUtf("string2");

EXPECT_THAT(null_reference,
Not(RefersToSameJavaObjectAs(non_null_reference)));
EXPECT_THAT(non_null_reference,
Not(RefersToSameJavaObjectAs(null_reference)));
}

TEST_F(FirestoreAndroidIntegrationTest,
ThrowExceptionWithNoMessageShouldSetPendingExceptionWithAMessage) {
Env env;
Local<Throwable> throw_exception_return_value = ThrowException(env);
Local<Throwable> actually_thrown_exception = env.ClearExceptionOccurred();
ASSERT_TRUE(actually_thrown_exception);
EXPECT_THAT(actually_thrown_exception,
RefersToSameJavaObjectAs(throw_exception_return_value));
EXPECT_THAT(actually_thrown_exception.GetMessage(env), Not(IsEmpty()));
}

TEST_F(FirestoreAndroidIntegrationTest,
ThrowExceptionWithAMessageShouldSetPendingExceptionWithTheGivenMessage) {
Env env;
Local<Throwable> throw_exception_return_value =
ThrowException(env, "my test message");
Local<Throwable> actually_thrown_exception = env.ClearExceptionOccurred();
ASSERT_TRUE(actually_thrown_exception);
EXPECT_THAT(actually_thrown_exception,
RefersToSameJavaObjectAs(throw_exception_return_value));
EXPECT_THAT(actually_thrown_exception.GetMessage(env),
StrEq("my test message"));
}

TEST_F(FirestoreAndroidIntegrationTest,
CreateExceptionWithNoMessageShouldReturnAnExceptionWithAMessage) {
Env env;
Local<Throwable> exception = CreateException(env);
ASSERT_TRUE(exception);
EXPECT_THAT(exception.GetMessage(env), Not(IsEmpty()));
}

TEST_F(FirestoreAndroidIntegrationTest,
CreateExceptionWithAMessageShouldReturnAnExceptionWithTheGivenMessage) {
Env env;
Local<Throwable> exception = CreateException(env, "my test message");
ASSERT_TRUE(exception);
EXPECT_THAT(exception.GetMessage(env), StrEq("my test message"));
}

} // namespace
} // namespace firestore
} // namespace firebase
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ TEST_F(JniRunnableTest, JavaRunCallsCppRunOncePerInvocation) {

TEST_F(JniRunnableTest, JavaRunPropagatesExceptions) {
Env env;
Local<Throwable> exception = CreateException(env, "Forced exception");
Local<Throwable> exception = CreateException(env);
auto runnable = MakeJniRunnable(env, [exception] {
Env env;
env.Throw(exception);
Expand Down Expand Up @@ -166,7 +166,7 @@ TEST_F(JniRunnableTest, DetachDetachesEvenIfAnExceptionIsPending) {
bool invoked = false;
auto runnable = MakeJniRunnable(env, [&invoked] { invoked = true; });
Local<Object> java_runnable = runnable.GetJavaRunnable();
Local<Throwable> exception = CreateException(env, "Forced exception");
Local<Throwable> exception = CreateException(env);
env.Throw(exception);
EXPECT_FALSE(env.ok());

Expand Down Expand Up @@ -230,7 +230,7 @@ TEST_F(JniRunnableTest, RunOnMainThreadRunsOnTheMainThread) {

TEST_F(JniRunnableTest, RunOnMainThreadTaskFailsIfRunThrowsException) {
Env env;
Global<Throwable> exception = CreateException(env, "Forced exception");
Global<Throwable> exception = CreateException(env);
auto runnable = MakeJniRunnable(env, [exception] {
Env env;
env.Throw(exception);
Expand Down Expand Up @@ -284,7 +284,7 @@ TEST_F(JniRunnableTest, RunOnNewThreadRunsOnANonMainThread) {

TEST_F(JniRunnableTest, RunOnNewThreadTaskFailsIfRunThrowsException) {
Env env;
Global<Throwable> exception = CreateException(env, "Forced exception");
Global<Throwable> exception = CreateException(env);
auto runnable = MakeJniRunnable(env, [exception] {
Env env;
env.Throw(exception);
Expand Down
4 changes: 2 additions & 2 deletions firestore/integration_test_internal/src/jni/task_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class TaskTest : public FirestoreAndroidIntegrationTest {
}

Local<Task> CreateFailedTask(Env& env) {
auto exception = CreateException(env, "Test Exception");
auto exception = CreateException(env);
return CreateFailedTask(env, exception);
}

Expand Down Expand Up @@ -86,7 +86,7 @@ TEST_F(TaskTest, GetResultShouldReturnTheResult) {

TEST_F(TaskTest, GetExceptionShouldReturnTheException) {
Env env;
Local<Throwable> exception = CreateException(env, "Test Exception");
Local<Throwable> exception = CreateException(env);
Local<Task> task = CreateFailedTask(env, exception);

Local<Throwable> actual_exception = task.GetException(env);
Expand Down