Skip to content

Commit bfd9f48

Browse files
committed
Add new smoke tests.
This commit adds the first batch of new tests. These tests replace the test apps for Database, Firestore, and Storage.
1 parent 4143a0a commit bfd9f48

File tree

12 files changed

+755
-0
lines changed

12 files changed

+755
-0
lines changed

smoke-tests/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Firebase Smoke Test Suite
2+
3+
This directory contains smoke tests for Firebase on Android. These tests are
4+
intended to verify the integrations between different components and versions of
5+
Firebase. The tests should not include additional overhead, such as user
6+
interfaces. However, the tests should strive to remain similar to real use
7+
cases. As such, these tests run on devices or emulators (no Robolectric). This
8+
is a work in progress, and the following list shows what is complete:
9+
10+
- [x] Create first set of tests to replace old test apps.
11+
- [ ] Reliably run smoke tests on CI.
12+
- [ ] Support version matrices.
13+
- [ ] Extend to collect system health metrics.
14+
15+
# Test Synchronization
16+
17+
Tests on devices usually involve at least two threads: the main thread and the
18+
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:
25+
26+
```java
27+
@Test
28+
public void foo() throws Exception {
29+
TaskChannel<Foo> channel = new TaskChannel<>();
30+
MainThread.run(() -> test_foo(channel));
31+
32+
Foo foo = channel.waitForSuccess();
33+
assertThat(foo)...
34+
}
35+
36+
private void test_foo(TaskChannel<Foo> channel) {
37+
...
38+
}
39+
```
40+
41+
Channels should be used to send results back to the testing thread. The
42+
`AbstractChannel` class provides the core implementation to send a failure or
43+
success value back to the test method. Failures are always exceptions, and
44+
multiple exceptions may be sent per test case. Only one success value may be
45+
sent. Failures dominate over successful values.
46+
47+
Firebase methods are often difficult to test due to their asynchronous nature.
48+
This framework provides a few subclasses of `AbstractChannel` to eliminate some
49+
boilerplate, concentrate the synchronization logic in one place, and shorten the
50+
tests to focus on the actual Firebase methods under test. The most common
51+
subclass is `TaskChannel`, and it simplifies chaining and adding listeners to
52+
Google Play Services tasks. Below is an example (Firebase methods truncated for
53+
brevity):
54+
55+
```java
56+
private void test_readAfterSignIn(TaskChannel<Document> channel) {
57+
FirebaseAuth auth = FirebaseAuth.getInstance();
58+
FirebaseDatabase db = FirebaseDatabase.getInstance();
59+
60+
channel.trapFailure(auth.signIn()).andThen(a -> {
61+
channel.sendOutcome(db.get("path/to/document"));
62+
});
63+
}
64+
```
65+
66+
Methods like `trapFailure` and `sendOutcome` automatically direct failures from
67+
tasks to the testing channel. There are also specialized channels for Firestore
68+
and Database, because they rely on custom listeners. See the source code for
69+
more details.
70+
71+
# Building the Tests
72+
73+
This Gradle project is split into flavors for each Firebase product. The Android
74+
plugin adds additional, compound flavors, such as `androidTestDatabase`. All
75+
test code lives in one of these testing variants. Common infrastructure belongs
76+
directly in `androidTest`. Do not add any source code to `main` or a non-testing
77+
variant, such as `firestore`.
78+
79+
As an exception, there is a single activity in the `main` variant. This is used
80+
to build the APK under test. However, all test logic belongs in the testing
81+
variants.

smoke-tests/build.gradle

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
buildscript {
16+
repositories {
17+
google()
18+
jcenter()
19+
}
20+
21+
dependencies {
22+
classpath "com.android.tools.build:gradle:3.3.2"
23+
classpath "com.google.gms:google-services:4.1.0"
24+
}
25+
}
26+
27+
apply plugin: "com.android.application"
28+
29+
android {
30+
compileSdkVersion 24
31+
32+
compileOptions {
33+
sourceCompatibility JavaVersion.VERSION_1_8
34+
targetCompatibility JavaVersion.VERSION_1_8
35+
}
36+
37+
defaultConfig {
38+
minSdkVersion 16
39+
multiDexEnabled true
40+
// applicationId "com.google.firebase.testing.common"
41+
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42+
}
43+
44+
flavorDimensions "systemUnderTest"
45+
46+
productFlavors {
47+
database {
48+
//applicationId "com.google.firebase.testing.database"
49+
// applicationId "com.google.firebase.testapps.database"
50+
dimension "systemUnderTest"
51+
}
52+
53+
firestore {
54+
// applicationId "com.google.firebase.testing.firestore"
55+
dimension "systemUnderTest"
56+
}
57+
58+
storage {
59+
// applicationId "com.google.firebase.testing.storage"
60+
dimension "systemUnderTest"
61+
}
62+
}
63+
}
64+
65+
repositories {
66+
google()
67+
jcenter()
68+
}
69+
70+
dependencies {
71+
// Common
72+
implementation "com.android.support.test:runner:1.0.2"
73+
implementation "com.google.android.gms:play-services-tasks:16.0.1"
74+
implementation "com.google.firebase:firebase-core:16.0.7"
75+
implementation "com.google.truth:truth:0.43"
76+
77+
androidTestImplementation "com.android.support.test:runner:1.0.2"
78+
androidTestImplementation "com.google.android.gms:play-services-tasks:16.0.1"
79+
androidTestImplementation "com.google.firebase:firebase-core:16.0.7"
80+
androidTestImplementation "com.google.truth:truth:0.43"
81+
androidTestImplementation "junit:junit:4.12"
82+
83+
// Database
84+
databaseImplementation "com.google.firebase:firebase-auth:16.1.0"
85+
databaseImplementation "com.google.firebase:firebase-database:16.1.0"
86+
87+
androidTestDatabaseImplementation "com.google.firebase:firebase-auth:16.1.0"
88+
androidTestDatabaseImplementation "com.google.firebase:firebase-database:16.1.0"
89+
90+
// Firestore
91+
firestoreImplementation "com.google.firebase:firebase-auth:16.1.0"
92+
firestoreImplementation "com.google.firebase:firebase-firestore:18.1.0"
93+
94+
androidTestFirestoreImplementation "com.google.firebase:firebase-auth:16.1.0"
95+
androidTestFirestoreImplementation "com.google.firebase:firebase-firestore:18.1.0"
96+
97+
// Storage
98+
storageImplementation "com.google.firebase:firebase-auth:16.1.0"
99+
storageImplementation "com.google.firebase:firebase-storage:16.1.0"
100+
101+
androidTestStorageImplementation "com.google.firebase:firebase-auth:16.1.0"
102+
androidTestStorageImplementation "com.google.firebase:firebase-storage:16.1.0"
103+
}
104+
105+
apply plugin: "com.google.gms.google-services"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
import java.util.concurrent.CountDownLatch;
18+
import java.util.concurrent.TimeUnit;
19+
import java.util.concurrent.locks.ReentrantLock;
20+
21+
/**
22+
* An abstract channel for sending test results across threads.
23+
*
24+
* <p>This enables test code to run on the main thread and signal the test thread when to stop
25+
* blocking. Tests may send multiple errors that will then be thrown on the test thread. However, a
26+
* test may only send success once. After this is done, nothing else can be sent.
27+
*/
28+
public abstract class AbstractChannel<T> {
29+
30+
private final CountDownLatch latch = new CountDownLatch(1);
31+
private final ReentrantLock lock = new ReentrantLock();
32+
33+
private Exception error = null;
34+
private T value = null;
35+
36+
/**
37+
* Sends a failure back to the testing thread.
38+
*
39+
* <p>This method will always send an exception to the testing thread. If an exception has already
40+
* been sent, this method will chain the new exception to the previous. If a successful value has
41+
* already been sent, this method will override it with the failure. Note, it is recommended to
42+
* send only one value through the channel. This method is safe to invoke from any thread.
43+
*/
44+
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+
}
54+
55+
latch.countDown();
56+
} finally {
57+
lock.unlock();
58+
}
59+
}
60+
61+
/**
62+
* Sends a successful value back to the testing thread.
63+
*
64+
* <p>This method will only send the value if no value has been sent. It is an error to invoke
65+
* this method multiple times or after invoking {@link #fail}. This method is safe to invoke from
66+
* any thread.
67+
*/
68+
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+
}
86+
}
87+
88+
/** Waits 30 seconds to receive the successful value. */
89+
public T waitForSuccess() throws InterruptedException {
90+
return waitForSuccess(30, TimeUnit.SECONDS);
91+
}
92+
93+
/**
94+
* Waits for up to the request time for the sending thread to send a successful value.
95+
*
96+
* <p>If the sender does not send success within the specified time, this method throws an {@link
97+
* AssertionError} and chains any received errors to it. This method is safe to invoke from any
98+
* thread.
99+
*/
100+
public T waitForSuccess(long duration, TimeUnit unit) throws InterruptedException {
101+
boolean completed = latch.await(duration, unit);
102+
103+
if (!completed) {
104+
String message = String.format("Test did not complete within %s %s", duration, unit);
105+
throw new AssertionError(message, error);
106+
}
107+
108+
if (error != null) {
109+
throw new AssertionError("Test completed with errors", error);
110+
}
111+
112+
return value;
113+
}
114+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
import android.os.Handler;
18+
import android.os.Looper;
19+
20+
/**
21+
* Convenience class for interacting with Android.
22+
*
23+
* <p>For now, this only consists of the {@link #run} method.
24+
*/
25+
public final class MainThread {
26+
27+
private static Handler handler = null;
28+
29+
private MainThread() {}
30+
31+
/** Runs the {@link Runnable} on the main thread. */
32+
public static void run(Runnable r) throws InterruptedException {
33+
if (handler == null) {
34+
handler = new Handler(Looper.getMainLooper());
35+
}
36+
37+
handler.post(r);
38+
}
39+
}

0 commit comments

Comments
 (0)