Skip to content

Commit fae8b6e

Browse files
authored
Tie up the loose ends from moving Firestore's integration tests into integration_test_internal (#351)
1 parent 431eccf commit fae8b6e

22 files changed

+164
-1829
lines changed

firestore/integration_test_internal/src/android/jni_runnable_android_test.cc

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "firestore/src/android/jni_runnable_android.h"
22

3+
#include "app/memory/atomic.h"
4+
#include "app/src/mutex.h"
35
#include "firestore/src/jni/declaration.h"
46
#include "firestore/src/jni/object.h"
57
#include "firestore/src/jni/ownership.h"
@@ -18,6 +20,7 @@ using jni::Global;
1820
using jni::Local;
1921
using jni::Method;
2022
using jni::Object;
23+
using jni::StaticField;
2124
using jni::StaticMethod;
2225
using jni::Task;
2326
using jni::Throwable;
@@ -27,14 +30,18 @@ Method<Object> kLooperGetThread("getThread", "()Ljava/lang/Thread;");
2730
Method<void> kRunnableRun("run", "()V");
2831
StaticMethod<Object> kCurrentThread("currentThread", "()Ljava/lang/Thread;");
2932
Method<jlong> kThreadGetId("getId", "()J");
33+
Method<Object> kThreadGetState("getState", "()Ljava/lang/Thread$State;");
34+
StaticField<Object> kThreadStateBlocked("BLOCKED", "Ljava/lang/Thread$State;");
3035

3136
class JniRunnableTest : public FirestoreAndroidIntegrationTest {
3237
public:
3338
void SetUp() override {
3439
FirestoreAndroidIntegrationTest::SetUp();
3540
loader().LoadClass("android/os/Looper", kGetMainLooper, kLooperGetThread);
3641
loader().LoadClass("java/lang/Runnable", kRunnableRun);
37-
loader().LoadClass("java/lang/Thread", kCurrentThread, kThreadGetId);
42+
loader().LoadClass("java/lang/Thread", kCurrentThread, kThreadGetId,
43+
kThreadGetState);
44+
loader().LoadClass("java/lang/Thread$State", kThreadStateBlocked);
3845
ASSERT_TRUE(loader().ok());
3946
}
4047
};
@@ -56,6 +63,16 @@ jlong GetMainThreadId(Env& env) {
5663
return env.Call(main_thread, kThreadGetId);
5764
}
5865

66+
/**
67+
* Returns whether or not the given thread is in the "blocked" state.
68+
* See java.lang.Thread.State.BLOCKED.
69+
*/
70+
bool IsThreadBlocked(Env& env, Object& thread) {
71+
Local<Object> actual_state = env.Call(thread, kThreadGetState);
72+
Local<Object> expected_state = env.Get(kThreadStateBlocked);
73+
return Object::Equals(env, expected_state, actual_state);
74+
}
75+
5976
TEST_F(JniRunnableTest, JavaRunCallsCppRun) {
6077
Env env;
6178
bool invoked = false;
@@ -145,6 +162,27 @@ TEST_F(JniRunnableTest, DetachDetachesEvenIfAnExceptionIsPending) {
145162
EXPECT_TRUE(env.ok());
146163
}
147164

165+
// Verify that b/181129657 does not regress; that is, calling `Detach()` from
166+
// `Run()` should not deadlock.
167+
TEST_F(JniRunnableTest, DetachCanBeCalledFromRun) {
168+
Env env;
169+
int run_count = 0;
170+
auto runnable = MakeJniRunnable(env, [&run_count](JniRunnableBase& runnable) {
171+
++run_count;
172+
Env env;
173+
runnable.Detach(env);
174+
});
175+
Local<Object> java_runnable = runnable.GetJavaRunnable();
176+
177+
// Call `run()` twice to verify that the call to `Detach()` successfully
178+
// detaches and the second `run()` invocation does not call C++ `Run()`.
179+
env.Call(java_runnable, kRunnableRun);
180+
env.Call(java_runnable, kRunnableRun);
181+
182+
EXPECT_TRUE(env.ok());
183+
EXPECT_EQ(run_count, 1);
184+
}
185+
148186
TEST_F(JniRunnableTest, DestructionCausesJavaRunToDoNothing) {
149187
Env env;
150188
bool invoked = false;
@@ -191,29 +229,21 @@ TEST_F(JniRunnableTest, RunOnMainThreadTaskFailsIfRunThrowsException) {
191229
}
192230

193231
TEST_F(JniRunnableTest, RunOnMainThreadRunsSynchronouslyFromMainThread) {
194-
class ChainedMainThreadJniRunnable : public JniRunnableBase {
195-
public:
196-
using JniRunnableBase::JniRunnableBase;
197-
198-
void Run() override {
199-
Env env;
200-
EXPECT_EQ(GetCurrentThreadId(env), GetMainThreadId(env));
201-
if (is_nested_call_) {
202-
return;
203-
}
204-
is_nested_call_ = true;
205-
Local<Task> task = RunOnMainThread(env);
206-
EXPECT_TRUE(task.IsComplete(env));
207-
EXPECT_TRUE(task.IsSuccessful(env));
208-
is_nested_call_ = false;
209-
}
210-
211-
private:
212-
bool is_nested_call_ = false;
213-
};
214-
215232
Env env;
216-
ChainedMainThreadJniRunnable runnable(env);
233+
bool is_recursive_call = false;
234+
auto runnable =
235+
MakeJniRunnable(env, [&is_recursive_call](JniRunnableBase& runnable) {
236+
Env env;
237+
EXPECT_EQ(GetCurrentThreadId(env), GetMainThreadId(env));
238+
if (is_recursive_call) {
239+
return;
240+
}
241+
is_recursive_call = true;
242+
Local<Task> task = runnable.RunOnMainThread(env);
243+
EXPECT_TRUE(task.IsComplete(env));
244+
EXPECT_TRUE(task.IsSuccessful(env));
245+
is_recursive_call = false;
246+
});
217247

218248
Local<Task> task = runnable.RunOnMainThread(env);
219249

@@ -252,6 +282,59 @@ TEST_F(JniRunnableTest, RunOnNewThreadTaskFailsIfRunThrowsException) {
252282
EXPECT_TRUE(env.IsSameObject(exception, thrown_exception));
253283
}
254284

285+
TEST_F(JniRunnableTest, DetachReturnsAfterLastRunOnAnotherThreadCompletes) {
286+
Env env;
287+
compat::Atomic<int32_t> runnable1_run_invoke_count;
288+
runnable1_run_invoke_count.store(0);
289+
Mutex detach_thread_mutex;
290+
Global<Object> detach_thread;
291+
292+
auto runnable1 = MakeJniRunnable(
293+
env, [&runnable1_run_invoke_count, &detach_thread, &detach_thread_mutex] {
294+
runnable1_run_invoke_count.fetch_add(1);
295+
Env env;
296+
// Wait for `detach()` to be called and start blocking; then, return to
297+
// allow `detach()` to unblock and do its job.
298+
while (env.ok()) {
299+
MutexLock lock(detach_thread_mutex);
300+
if (detach_thread && IsThreadBlocked(env, detach_thread)) {
301+
break;
302+
}
303+
}
304+
EXPECT_TRUE(env.ok()) << "IsThreadBlocked() failed with an exception";
305+
});
306+
307+
auto runnable2 =
308+
MakeJniRunnable(env, [&runnable1, &detach_thread, &detach_thread_mutex] {
309+
Env env;
310+
{
311+
MutexLock lock(detach_thread_mutex);
312+
detach_thread = env.Call(kCurrentThread);
313+
}
314+
runnable1.Detach(env);
315+
EXPECT_TRUE(env.ok()) << "Detach() failed with an exception";
316+
});
317+
318+
// Wait for the `runnable1.Run()` to start to ensure that the lock is held.
319+
Local<Task> task1 = runnable1.RunOnNewThread(env);
320+
while (true) {
321+
if (runnable1_run_invoke_count.load() != 0) {
322+
break;
323+
}
324+
}
325+
326+
// Start a new thread to call `runnable1.Detach()`.
327+
Local<Task> task2 = runnable2.RunOnNewThread(env);
328+
329+
Await(env, task1);
330+
Await(env, task2);
331+
332+
// Invoke `run()` again and ensure that `Detach()` successfully did its job;
333+
// that is, verify that `Run()` is not invoked.
334+
env.Call(runnable1.GetJavaRunnable(), kRunnableRun);
335+
EXPECT_EQ(runnable1_run_invoke_count.load(), 1);
336+
}
337+
255338
} // namespace
256339
} // namespace firestore
257340
} // namespace firebase

firestore/integration_test_internal/src/android/promise_android_test.cc

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,14 @@ namespace {
4141

4242
class PromiseTest : public FirestoreAndroidIntegrationTest {
4343
public:
44-
PromiseTest() : promises_(GetFirestoreInternal(TestFirestore())) {
44+
PromiseTest() : promises_(GetFirestoreInternal(TestFirestore())) {}
45+
46+
void SetUp() override {
47+
FirestoreAndroidIntegrationTest::SetUp();
4548
jni::Env env = GetEnv();
46-
// TODO(b/183294303): Uncomment this initialization code when
47-
// these tests are fixed on Android.
48-
//
49-
// Note: This usage of jni::Loader may be broken because this is
50-
// running in a background thread. Requires further investigation.
51-
//
52-
// cancellation_token_source_ = CancellationTokenSource::Create(env);
53-
// task_completion_source_ = TaskCompletionSource::Create(
54-
// env, cancellation_token_source_.GetToken(env));
49+
cancellation_token_source_ = CancellationTokenSource::Create(env);
50+
task_completion_source_ = TaskCompletionSource::Create(
51+
env, cancellation_token_source_.GetToken(env));
5552
}
5653

5754
// An enum of asynchronous functions to use in tests, as required by
@@ -223,8 +220,6 @@ class TestVoidCompletion : public TestCompletionBase<void, void> {
223220
};
224221

225222
TEST_F(PromiseTest, FutureVoidShouldSucceedWhenTaskSucceeds) {
226-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
227-
228223
jni::Env env = GetEnv();
229224
auto future = promises().NewFuture<void>(env, AsyncFn::kFn, GetTask());
230225
EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending);
@@ -238,8 +233,6 @@ TEST_F(PromiseTest, FutureVoidShouldSucceedWhenTaskSucceeds) {
238233
}
239234

240235
TEST_F(PromiseTest, FutureNonVoidShouldSucceedWhenTaskSucceeds) {
241-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
242-
243236
jni::Env env = GetEnv();
244237
auto future =
245238
promises().NewFuture<std::string, int>(env, AsyncFn::kFn, GetTask());
@@ -254,8 +247,6 @@ TEST_F(PromiseTest, FutureNonVoidShouldSucceedWhenTaskSucceeds) {
254247
}
255248

256249
TEST_F(PromiseTest, FutureVoidShouldFailWhenTaskFails) {
257-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
258-
259250
jni::Env env = GetEnv();
260251
auto future = promises().NewFuture<void>(env, AsyncFn::kFn, GetTask());
261252
EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending);
@@ -270,8 +261,6 @@ TEST_F(PromiseTest, FutureVoidShouldFailWhenTaskFails) {
270261
}
271262

272263
TEST_F(PromiseTest, FutureNonVoidShouldFailWhenTaskFails) {
273-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
274-
275264
jni::Env env = GetEnv();
276265
auto future =
277266
promises().NewFuture<std::string, int>(env, AsyncFn::kFn, GetTask());
@@ -287,8 +276,6 @@ TEST_F(PromiseTest, FutureNonVoidShouldFailWhenTaskFails) {
287276
}
288277

289278
TEST_F(PromiseTest, FutureVoidShouldCancelWhenTaskCancels) {
290-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
291-
292279
jni::Env env = GetEnv();
293280
auto future = promises().NewFuture<void>(env, AsyncFn::kFn, GetTask());
294281
EXPECT_EQ(future.status(), FutureStatus::kFutureStatusPending);
@@ -303,8 +290,6 @@ TEST_F(PromiseTest, FutureVoidShouldCancelWhenTaskCancels) {
303290
}
304291

305292
TEST_F(PromiseTest, FutureNonVoidShouldCancelWhenTaskCancels) {
306-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
307-
308293
jni::Env env = GetEnv();
309294
auto future =
310295
promises().NewFuture<std::string, int>(env, AsyncFn::kFn, GetTask());
@@ -320,8 +305,6 @@ TEST_F(PromiseTest, FutureNonVoidShouldCancelWhenTaskCancels) {
320305
}
321306

322307
TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskSucceeds) {
323-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
324-
325308
jni::Env env = GetEnv();
326309
TestVoidCompletion completion;
327310
auto future = promises().NewFuture<void, void>(env, AsyncFn::kFn, GetTask(),
@@ -338,8 +321,6 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskSucceeds) {
338321
}
339322

340323
TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskSucceeds) {
341-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
342-
343324
jni::Env env = GetEnv();
344325
TestCompletion<std::string, int> completion;
345326
auto future = promises().NewFuture<std::string, int>(env, AsyncFn::kFn,
@@ -356,8 +337,6 @@ TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskSucceeds) {
356337
}
357338

358339
TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskFails) {
359-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
360-
361340
jni::Env env = GetEnv();
362341
TestVoidCompletion completion;
363342
auto future = promises().NewFuture<void, void>(env, AsyncFn::kFn, GetTask(),
@@ -374,8 +353,6 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskFails) {
374353
}
375354

376355
TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskFails) {
377-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
378-
379356
jni::Env env = GetEnv();
380357
TestCompletion<std::string, int> completion;
381358
auto future = promises().NewFuture<std::string, int>(env, AsyncFn::kFn,
@@ -392,8 +369,6 @@ TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskFails) {
392369
}
393370

394371
TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskCancels) {
395-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
396-
397372
jni::Env env = GetEnv();
398373
TestVoidCompletion completion;
399374
auto future = promises().NewFuture<void, void>(env, AsyncFn::kFn, GetTask(),
@@ -410,8 +385,6 @@ TEST_F(PromiseTest, FutureVoidShouldCallCompletionWhenTaskCancels) {
410385
}
411386

412387
TEST_F(PromiseTest, FutureNonVoidShouldCallCompletionWhenTaskCancels) {
413-
SKIP_TEST_ON_ANDROID; // TODO(b/183294303): Fix this test on Android.
414-
415388
jni::Env env = GetEnv();
416389
TestCompletion<std::string, int> completion;
417390
auto future = promises().NewFuture<std::string, int>(env, AsyncFn::kFn,

firestore/integration_test_internal/src/firestore_integration_test.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Firestore* FirestoreIntegrationTest::TestFirestore(
114114
const std::string& name) const {
115115
for (const auto& entry : firestores_) {
116116
const FirestoreInfo& firestore_info = entry.second;
117-
if (firestore_info.cached() && firestore_info.name() == name) {
117+
if (firestore_info.name() == name) {
118118
return firestore_info.firestore();
119119
}
120120
}
@@ -141,6 +141,14 @@ void FirestoreIntegrationTest::DeleteFirestore(Firestore* firestore) {
141141
firestores_.erase(found);
142142
}
143143

144+
void FirestoreIntegrationTest::DisownFirestore(Firestore* firestore) {
145+
auto found = firestores_.find(firestore);
146+
FIREBASE_ASSERT_MESSAGE(found != firestores_.end(),
147+
"The given Firestore was not found.");
148+
found->second.ReleaseFirestore();
149+
firestores_.erase(found);
150+
}
151+
144152
void FirestoreIntegrationTest::DeleteApp(App* app) {
145153
auto found = apps_.find(app);
146154
FIREBASE_ASSERT_MESSAGE(found != apps_.end(), "The given App was not found.");

0 commit comments

Comments
 (0)