Skip to content

Commit 727355e

Browse files
authored
Merge pull request #411 from Microsoft/develop
v0.7.0
2 parents 7d118f3 + 847fda6 commit 727355e

File tree

48 files changed

+2440
-722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2440
-722
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ captures/
4242

4343
# Mac files
4444
.DS_Store
45+
46+
# Google credentials files
47+
google-services.json

README.md

Lines changed: 20 additions & 289 deletions
Large diffs are not rendered by default.

apps/build.gradle

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,50 @@ subprojects {
1515
}
1616
}
1717
}
18-
}
18+
19+
/*
20+
* Create a project dependency for assembleDebug and assembleRelease tasks with sub projects.
21+
* The project doesn't know about assembleDebug and assembleRelease tasks, this is a workaround
22+
* until Build beacon supports custom gradle tasks.
23+
*/
24+
tasks.all {
25+
if (it.name == 'assembleDebug') {
26+
parent.assembleDebug.dependsOn it
27+
} else if (it.name == 'assembleRelease') {
28+
parent.assembleRelease.dependsOn it
29+
}
30+
}
31+
}
32+
33+
// Task to copy an APK from sub projects to current project.
34+
def copyApkToProjectBuildTask(variantName) {
35+
subprojects.android.applicationVariants.each { applicationVariant ->
36+
applicationVariant.each { variant ->
37+
38+
// Copy only one APK to current project
39+
if (variant.name.contains(variantName) && variant.name.contains('projectDependency')) {
40+
variant.outputs.outputFile.each { File file ->
41+
copy {
42+
from "${file.absolutePath}"
43+
into "${buildDir}/outputs/apk/"
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
51+
task assembleDebug() {
52+
doLast {
53+
copyApkToProjectBuildTask('Debug');
54+
}
55+
}
56+
57+
task assembleRelease() {
58+
doLast {
59+
copyApkToProjectBuildTask('Release');
60+
}
61+
}
62+
63+
// Empty task with task dependencies.
64+
task assemble(dependsOn: [assembleDebug, assembleRelease]) {}

apps/sasquatch/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ dependencies {
3030
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-analytics:${version}"
3131
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-crashes:${version}"
3232
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-distribute:${version}"
33+
34+
/* Force usage this version of support annotations to avoid conflict. */
35+
androidTestCompile "com.android.support:support-annotations:${rootProject.ext.supportLibVersion}"
36+
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
37+
compile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'
3338
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.microsoft.azure.mobile.sasquatch.activities;
2+
3+
4+
import android.support.test.espresso.Espresso;
5+
import android.support.test.rule.ActivityTestRule;
6+
7+
import com.microsoft.azure.mobile.Constants;
8+
import com.microsoft.azure.mobile.sasquatch.R;
9+
10+
import org.junit.Rule;
11+
import org.junit.Test;
12+
13+
import static android.support.test.espresso.Espresso.onView;
14+
import static android.support.test.espresso.action.ViewActions.click;
15+
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
16+
import static android.support.test.espresso.action.ViewActions.replaceText;
17+
import static android.support.test.espresso.assertion.ViewAssertions.matches;
18+
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
19+
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
20+
import static android.support.test.espresso.matcher.ViewMatchers.withChild;
21+
import static android.support.test.espresso.matcher.ViewMatchers.withId;
22+
import static android.support.test.espresso.matcher.ViewMatchers.withText;
23+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.CHECK_DELAY;
24+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.TOAST_DELAY;
25+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.onToast;
26+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.waitFor;
27+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.withContainsText;
28+
import static org.hamcrest.Matchers.allOf;
29+
import static org.hamcrest.Matchers.anyOf;
30+
31+
@SuppressWarnings("unused")
32+
public class AnalyticsTest {
33+
34+
@Rule
35+
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
36+
37+
@Test
38+
public void sendEventTest() throws InterruptedException {
39+
40+
/* Send event. */
41+
onView(allOf(
42+
withChild(withText(R.string.title_event)),
43+
withChild(withText(R.string.description_event))))
44+
.perform(click());
45+
onView(withId(R.id.name)).perform(replaceText("test"), closeSoftKeyboard());
46+
onView(withText(R.string.send)).perform(click());
47+
48+
/* Check toasts. */
49+
waitFor(onToast(mActivityTestRule.getActivity(),
50+
withText(R.string.event_before_sending)), Constants.DEFAULT_TRIGGER_INTERVAL + CHECK_DELAY)
51+
.check(matches(isDisplayed()));
52+
waitAnalytics();
53+
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
54+
withContainsText(R.string.event_sent_succeeded),
55+
withContainsText(R.string.event_sent_failed))), TOAST_DELAY)
56+
.check(matches(isDisplayed()));
57+
}
58+
59+
@Test
60+
public void sendPageTest() throws InterruptedException {
61+
62+
/* Send page. */
63+
onView(allOf(
64+
withChild(withText(R.string.title_page)),
65+
withChild(withText(R.string.description_page))))
66+
.perform(click());
67+
onView(withId(R.id.name)).perform(replaceText("test"), closeSoftKeyboard());
68+
onView(withText(R.string.send)).perform(click());
69+
70+
/* Check toasts. */
71+
waitFor(onToast(mActivityTestRule.getActivity(), withText(R.string.page_before_sending)), Constants.DEFAULT_TRIGGER_INTERVAL + CHECK_DELAY)
72+
.check(matches(isDisplayed()));
73+
waitAnalytics();
74+
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
75+
withContainsText(R.string.page_sent_succeeded),
76+
withContainsText(R.string.page_sent_failed))), TOAST_DELAY)
77+
.check(matches(isDisplayed()));
78+
}
79+
80+
private void waitAnalytics() {
81+
Espresso.registerIdlingResources(MainActivity.analyticsIdlingResource);
82+
onView(isRoot()).perform(waitFor(CHECK_DELAY));
83+
Espresso.unregisterIdlingResources(MainActivity.analyticsIdlingResource);
84+
}
85+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package com.microsoft.azure.mobile.sasquatch.activities;
2+
3+
import android.content.Context;
4+
import android.content.Intent;
5+
import android.support.annotation.StringRes;
6+
import android.support.test.espresso.Espresso;
7+
import android.support.test.espresso.EspressoException;
8+
import android.support.test.espresso.FailureHandler;
9+
import android.support.test.espresso.ViewInteraction;
10+
import android.support.test.espresso.matcher.BoundedMatcher;
11+
import android.support.test.rule.ActivityTestRule;
12+
import android.support.v4.app.ActivityCompat;
13+
import android.support.v4.content.IntentCompat;
14+
import android.view.View;
15+
16+
import com.microsoft.azure.mobile.Constants;
17+
import com.microsoft.azure.mobile.MobileCenter;
18+
import com.microsoft.azure.mobile.crashes.Crashes;
19+
import com.microsoft.azure.mobile.crashes.CrashesPrivateHelper;
20+
import com.microsoft.azure.mobile.crashes.utils.ErrorLogHelper;
21+
import com.microsoft.azure.mobile.sasquatch.R;
22+
import com.microsoft.azure.mobile.utils.storage.StorageHelper;
23+
24+
import org.hamcrest.Description;
25+
import org.hamcrest.Matcher;
26+
import org.junit.After;
27+
import org.junit.Before;
28+
import org.junit.Rule;
29+
import org.junit.Test;
30+
31+
import java.io.File;
32+
import java.lang.reflect.Method;
33+
34+
import static android.support.test.InstrumentationRegistry.getInstrumentation;
35+
import static android.support.test.espresso.Espresso.onData;
36+
import static android.support.test.espresso.Espresso.onView;
37+
import static android.support.test.espresso.action.ViewActions.click;
38+
import static android.support.test.espresso.assertion.ViewAssertions.matches;
39+
import static android.support.test.espresso.matcher.RootMatchers.isDialog;
40+
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
41+
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
42+
import static android.support.test.espresso.matcher.ViewMatchers.withChild;
43+
import static android.support.test.espresso.matcher.ViewMatchers.withText;
44+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.CHECK_DELAY;
45+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.TOAST_DELAY;
46+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.onToast;
47+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.waitFor;
48+
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.withContainsText;
49+
import static org.hamcrest.Matchers.allOf;
50+
import static org.hamcrest.Matchers.anyOf;
51+
import static org.hamcrest.Matchers.instanceOf;
52+
import static org.junit.Assert.assertTrue;
53+
54+
@SuppressWarnings("unused")
55+
public class CrashesTest {
56+
57+
@Rule
58+
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class, true, false);
59+
60+
private Context mContext;
61+
62+
@Before
63+
public void setUp() throws Exception {
64+
mContext = getInstrumentation().getTargetContext();
65+
66+
/* Clear preferences. */
67+
StorageHelper.initialize(mContext);
68+
StorageHelper.PreferencesStorage.clear();
69+
70+
/* Clear crashes. */
71+
Constants.loadFromContext(mContext);
72+
for (File logFile : ErrorLogHelper.getErrorStorageDirectory().listFiles()) {
73+
assertTrue(logFile.delete());
74+
}
75+
76+
/* Launch main activity and go to setting page. Required to properly initialize. */
77+
mActivityTestRule.launchActivity(new Intent());
78+
79+
/* Register IdlingResource */
80+
Espresso.registerIdlingResources(MainActivity.crashesIdlingResource);
81+
}
82+
83+
@After
84+
public final void tearDown() {
85+
86+
/* Unregister IdlingResource */
87+
Espresso.unregisterIdlingResources(MainActivity.crashesIdlingResource);
88+
}
89+
90+
@Test
91+
public void testCrashTest() throws InterruptedException {
92+
crashTest(R.string.title_test_crash);
93+
}
94+
95+
@Test
96+
public void divideByZeroTest() throws InterruptedException {
97+
crashTest(R.string.title_crash_divide_by_0);
98+
}
99+
100+
@Test
101+
public void uiCrashTest() throws InterruptedException {
102+
crashTest(R.string.title_test_ui_crash);
103+
}
104+
105+
@Test
106+
public void variableMessageTest() throws InterruptedException {
107+
crashTest(R.string.title_variable_message);
108+
}
109+
110+
/**
111+
* Crash and sending report test.
112+
* <p>
113+
* We can't truly restart application in tests, so some kind of crashes can't be tested by this method.
114+
* Out of memory or stack overflow - crash the test process;
115+
* UI states errors - problems with restart activity;
116+
* <p>
117+
* Also to avoid flakiness, please setup your test environment
118+
* (https://google.github.io/android-testing-support-library/docs/espresso/setup/index.html#setup-your-test-environment).
119+
* On your device, under Settings->Developer options disable the following 3 settings:
120+
* - Window animation scale
121+
* - Transition animation scale
122+
* - Animator duration scale
123+
*
124+
* @param titleId Title string resource to find list item.
125+
* @throws InterruptedException If the current thread is interrupted.
126+
*/
127+
private void crashTest(@StringRes int titleId) throws InterruptedException {
128+
129+
/* Crash. */
130+
onView(allOf(
131+
withChild(withText(R.string.title_crashes)),
132+
withChild(withText(R.string.description_crashes))))
133+
.perform(click());
134+
135+
onCrash(titleId)
136+
.withFailureHandler(new CrashFailureHandler())
137+
.perform(click());
138+
139+
/* Check error report. */
140+
assertTrue(Crashes.hasCrashedInLastSession());
141+
142+
/* Send report. */
143+
waitFor(onView(withText(R.string.crash_confirmation_dialog_send_button))
144+
.inRoot(isDialog()), 1000)
145+
.perform(click());
146+
147+
/* Check toasts. */
148+
waitFor(onToast(mActivityTestRule.getActivity(),
149+
withText(R.string.crash_before_sending)), CHECK_DELAY)
150+
.check(matches(isDisplayed()));
151+
onView(isRoot()).perform(waitFor(CHECK_DELAY));
152+
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
153+
withContainsText(R.string.crash_sent_succeeded),
154+
withText(R.string.crash_sent_failed))), TOAST_DELAY)
155+
.check(matches(isDisplayed()));
156+
onView(isRoot()).perform(waitFor(TOAST_DELAY));
157+
}
158+
159+
private ViewInteraction onCrash(@StringRes int titleId) {
160+
return onData(allOf(instanceOf(CrashActivity.Crash.class), withCrashTitle(titleId)))
161+
.perform();
162+
}
163+
164+
@SuppressWarnings("rawtypes")
165+
private static Matcher<Object> withCrashTitle(@StringRes final int titleId) {
166+
return new BoundedMatcher<Object, CrashActivity.Crash>(CrashActivity.Crash.class) {
167+
168+
@Override
169+
public boolean matchesSafely(CrashActivity.Crash map) {
170+
return map.title == titleId;
171+
}
172+
173+
@Override
174+
public void describeTo(Description description) {
175+
description.appendText("with item title from resource id: ");
176+
description.appendValue(titleId);
177+
}
178+
};
179+
}
180+
181+
private class CrashFailureHandler implements FailureHandler {
182+
183+
@Override
184+
public void handle(Throwable error, Matcher<View> viewMatcher) {
185+
Throwable uncaughtException = error instanceof EspressoException ? error.getCause() : error;
186+
187+
/* Save exception. */
188+
CrashesPrivateHelper.saveUncaughtException(mContext.getMainLooper().getThread(), uncaughtException);
189+
190+
/* Relaunch. */
191+
ActivityCompat.finishAffinity(mActivityTestRule.getActivity());
192+
unsetInstance(MobileCenter.class);
193+
unsetInstance(Crashes.class);
194+
Intent intent = new Intent();
195+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
196+
mActivityTestRule.launchActivity(intent);
197+
}
198+
199+
@SuppressWarnings("unchecked")
200+
private void unsetInstance(Class clazz) {
201+
try {
202+
Method m = clazz.getDeclaredMethod("unsetInstance");
203+
m.setAccessible(true);
204+
m.invoke(null);
205+
} catch (Exception e) {
206+
e.printStackTrace();
207+
}
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)