Skip to content

Commit 370f2a5

Browse files
committed
Add more comments and Target for trapping test failures
1 parent 567e6c5 commit 370f2a5

File tree

10 files changed

+85
-64
lines changed

10 files changed

+85
-64
lines changed

smoke-tests/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ is a work in progress, and the following list shows what is complete:
1616

1717
Tests on devices usually involve at least two threads: the main thread and the
1818
testing thread. Either thread should be sufficient for testing, but most users
19-
will likely invoke Firebase methods from the main thread. As a result, this
20-
framework provides a simple solution to easily share state between threads. Most
21-
tests will consist of two methods. Both may be present in the same file for
22-
simplicity and ease of understanding. One method will be executed by the test
23-
runner on the testing thread, while the other, is invoked via a lambda on the
24-
main thread:
19+
will likely invoke Firebase methods from the main thread. Integration tests
20+
already verify the use of the testing thread, so it is important to gain
21+
coverage from the main thread as well. Therefore, this framework provides a
22+
simple solution to easily share state between threads. Most tests will consist
23+
of two methods. Both may be present in the same file for simplicity and ease of
24+
understanding. One method will be executed by the test runner on the testing
25+
thread, while the other, is invoked via a lambda on the main thread:
2526

2627
```java
2728
@Test

smoke-tests/src/androidTest/java/com/google/firebase/testing/common/AbstractChannel.java

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
package com.google.firebase.testing.common;
1616

17-
import java.util.concurrent.CountDownLatch;
17+
import com.google.android.gms.tasks.TaskCompletionSource;
18+
import com.google.android.gms.tasks.Tasks;
19+
import java.util.concurrent.ExecutionException;
1820
import java.util.concurrent.TimeUnit;
19-
import java.util.concurrent.locks.ReentrantLock;
21+
import java.util.concurrent.TimeoutException;
2022

2123
/**
2224
* An abstract channel for sending test results across threads.
@@ -27,11 +29,21 @@
2729
*/
2830
public abstract class AbstractChannel<T> {
2931

30-
private final CountDownLatch latch = new CountDownLatch(1);
31-
private final ReentrantLock lock = new ReentrantLock();
32+
private final TaskCompletionSource<T> implementation = new TaskCompletionSource<>();
3233

33-
private Exception error = null;
34-
private T value = null;
34+
/** Runs the test target on the main thread, trapping any exception into the channel. */
35+
protected static <U extends AbstractChannel<R>, R> U runTarget(Target<U> target, U channel) {
36+
MainThread.run(
37+
() -> {
38+
try {
39+
target.run(channel);
40+
} catch (Exception ex) {
41+
channel.fail(ex);
42+
}
43+
});
44+
45+
return channel;
46+
}
3547

3648
/**
3749
* Sends a failure back to the testing thread.
@@ -42,19 +54,10 @@ public abstract class AbstractChannel<T> {
4254
* send only one value through the channel. This method is safe to invoke from any thread.
4355
*/
4456
protected void fail(Exception err) {
45-
// This is explicitly synchronized in case multiple threads are trying to send values.
46-
try {
47-
lock.lock();
48-
49-
if (error == null) {
50-
error = err;
51-
} else {
52-
error.addSuppressed(err);
53-
}
57+
boolean isSet = implementation.trySetException(err);
5458

55-
latch.countDown();
56-
} finally {
57-
lock.unlock();
59+
if (!isSet) {
60+
implementation.getTask().getException().addSuppressed(err);
5861
}
5962
}
6063

@@ -66,23 +69,7 @@ protected void fail(Exception err) {
6669
* any thread.
6770
*/
6871
protected void succeed(T val) {
69-
// This is explicitly synchronized in case multiple threads are trying to send values.
70-
try {
71-
lock.lock();
72-
73-
if (latch.getCount() == 0) {
74-
// Only a single, successful value is supported. This throws the exception on both the
75-
// testing thread and the main thread.
76-
IllegalStateException error = new IllegalStateException("Result already completed");
77-
fail(error);
78-
throw error;
79-
}
80-
81-
value = val;
82-
latch.countDown();
83-
} finally {
84-
lock.unlock();
85-
}
72+
implementation.setResult(val);
8673
}
8774

8875
/** Waits 30 seconds to receive the successful value. */
@@ -98,17 +85,13 @@ public T waitForSuccess() throws InterruptedException {
9885
* thread.
9986
*/
10087
public T waitForSuccess(long duration, TimeUnit unit) throws InterruptedException {
101-
boolean completed = latch.await(duration, unit);
102-
103-
if (!completed) {
88+
try {
89+
return Tasks.await(implementation.getTask(), duration, unit);
90+
} catch (ExecutionException ex) {
91+
throw new AssertionError("Test completed with errors", ex.getCause());
92+
} catch (TimeoutException ex) {
10493
String message = String.format("Test did not complete within %s %s", duration, unit);
105-
throw new AssertionError(message, error);
94+
throw new AssertionError(message);
10695
}
107-
108-
if (error != null) {
109-
throw new AssertionError("Test completed with errors", error);
110-
}
111-
112-
return value;
11396
}
11497
}

smoke-tests/src/androidTest/java/com/google/firebase/testing/common/MainThread.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public final class MainThread {
2929
private MainThread() {}
3030

3131
/** Runs the {@link Runnable} on the main thread. */
32-
public static void run(Runnable r) throws InterruptedException {
32+
public static void run(Runnable r) {
3333
if (handler == null) {
3434
handler = new Handler(Looper.getMainLooper());
3535
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.testing.common;
16+
17+
/**
18+
* A test target.
19+
*
20+
* <p>This interface is similar to {@link Runnable}, but this interface's {@link #run} method takes
21+
* an instance of {@link T} as input. This is intended to be a channel.
22+
*/
23+
public interface Target<T> {
24+
25+
void run(T channel);
26+
}

smoke-tests/src/androidTest/java/com/google/firebase/testing/common/TaskChannel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
*/
2626
public class TaskChannel<T> extends AbstractChannel<T> {
2727

28+
/** Runs the test on the main thread and returns the corresponding channel. */
29+
public static <U> TaskChannel<U> runWithTaskChannel(Target<TaskChannel<U>> test) {
30+
return AbstractChannel.runTarget(test, new TaskChannel<>());
31+
}
32+
2833
/**
2934
* Sends the outcome of a task over the channel.
3035
*

smoke-tests/src/androidTestDatabase/java/com/google/firebase/testing/database/DatabaseChannel.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import com.google.firebase.database.DatabaseError;
1919
import com.google.firebase.database.Query;
2020
import com.google.firebase.database.ValueEventListener;
21+
import com.google.firebase.testing.common.AbstractChannel;
22+
import com.google.firebase.testing.common.Target;
2123
import com.google.firebase.testing.common.TaskChannel;
2224

2325
/**
@@ -29,6 +31,11 @@
2931
*/
3032
public class DatabaseChannel extends TaskChannel<DataSnapshot> {
3133

34+
/** Runs the test on the main thread and returns the corresponding channel. */
35+
public static DatabaseChannel runWithDatabaseChannel(Target<DatabaseChannel> test) {
36+
return AbstractChannel.runTarget(test, new DatabaseChannel());
37+
}
38+
3239
/**
3340
* Adds a listener to the query that sends the results back to the testing thread.
3441
*

smoke-tests/src/androidTestDatabase/java/com/google/firebase/testing/database/DatabaseTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import com.google.firebase.database.DataSnapshot;
2222
import com.google.firebase.database.DatabaseReference;
2323
import com.google.firebase.database.FirebaseDatabase;
24-
import com.google.firebase.testing.common.MainThread;
2524
import org.junit.Test;
2625
import org.junit.runner.RunWith;
2726

@@ -30,9 +29,7 @@ public final class DatabaseTest {
3029

3130
@Test
3231
public void listenForUpdate() throws Exception {
33-
DatabaseChannel channel = new DatabaseChannel();
34-
35-
MainThread.run(() -> test_ListenForUpdate(channel));
32+
DatabaseChannel channel = DatabaseChannel.runWithDatabaseChannel(c -> test_ListenForUpdate(c));
3633

3734
DataSnapshot snapshot = channel.waitForSuccess();
3835
assertThat(snapshot.child("location").getValue()).isEqualTo("Google SVL");

smoke-tests/src/androidTestFirestore/java/com/google/firebase/testing/firestore/FirestoreChannel.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import com.google.firebase.firestore.DocumentReference;
1818
import com.google.firebase.firestore.DocumentSnapshot;
19+
import com.google.firebase.testing.common.AbstractChannel;
20+
import com.google.firebase.testing.common.Target;
1921
import com.google.firebase.testing.common.TaskChannel;
2022

2123
/**
@@ -27,6 +29,11 @@
2729
*/
2830
public final class FirestoreChannel extends TaskChannel<DocumentSnapshot> {
2931

32+
/** Runs the test on the main thread and returns the corresponding channel. */
33+
public static FirestoreChannel runWithFirestoreChannel(Target<FirestoreChannel> test) {
34+
return AbstractChannel.runTarget(test, new FirestoreChannel());
35+
}
36+
3037
/**
3138
* Adds a listener to the document that sends the results back to the testing thread.
3239
*

smoke-tests/src/androidTestFirestore/java/com/google/firebase/testing/firestore/FirestoreTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.google.firebase.firestore.DocumentSnapshot;
2323
import com.google.firebase.firestore.FirebaseFirestore;
2424
import com.google.firebase.firestore.FirebaseFirestoreSettings;
25-
import com.google.firebase.testing.common.MainThread;
2625
import java.util.HashMap;
2726
import org.junit.Test;
2827
import org.junit.runner.RunWith;
@@ -32,9 +31,8 @@ public final class FirestoreTest {
3231

3332
@Test
3433
public void listenForUpdate() throws Exception {
35-
FirestoreChannel channel = new FirestoreChannel();
36-
37-
MainThread.run(() -> test_ListenForUpdate(channel));
34+
FirestoreChannel channel =
35+
FirestoreChannel.runWithFirestoreChannel(c -> test_ListenForUpdate(c));
3836

3937
DocumentSnapshot snapshot = channel.waitForSuccess();
4038
assertThat(snapshot.getString("location")).isEqualTo("Google SVL");

smoke-tests/src/androidTestStorage/java/com/google/firebase/testing/storage/StorageTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.google.firebase.auth.FirebaseAuth;
2323
import com.google.firebase.storage.FirebaseStorage;
2424
import com.google.firebase.storage.StorageReference;
25-
import com.google.firebase.testing.common.MainThread;
2625
import com.google.firebase.testing.common.TaskChannel;
2726
import java.nio.charset.StandardCharsets;
2827
import org.junit.Test;
@@ -33,9 +32,7 @@ public final class StorageTest {
3332

3433
@Test
3534
public void getSet() throws Exception {
36-
TaskChannel<byte[]> channel = new TaskChannel<>();
37-
38-
MainThread.run(() -> test_GetSet(channel));
35+
TaskChannel channel = TaskChannel.runWithTaskChannel(c -> test_GetSet(c));
3936

4037
byte[] bytes = channel.waitForSuccess();
4138
String text = new String(bytes, StandardCharsets.UTF_8);

0 commit comments

Comments
 (0)