Skip to content

Commit

Permalink
Merge pull request #411 from Microsoft/develop
Browse files Browse the repository at this point in the history
v0.7.0
  • Loading branch information
guperrot authored Apr 26, 2017
2 parents 7d118f3 + 847fda6 commit 727355e
Show file tree
Hide file tree
Showing 48 changed files with 2,440 additions and 722 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ captures/

# Mac files
.DS_Store

# Google credentials files
google-services.json
309 changes: 20 additions & 289 deletions README.md

Large diffs are not rendered by default.

48 changes: 47 additions & 1 deletion apps/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,50 @@ subprojects {
}
}
}
}

/*
* Create a project dependency for assembleDebug and assembleRelease tasks with sub projects.
* The project doesn't know about assembleDebug and assembleRelease tasks, this is a workaround
* until Build beacon supports custom gradle tasks.
*/
tasks.all {
if (it.name == 'assembleDebug') {
parent.assembleDebug.dependsOn it
} else if (it.name == 'assembleRelease') {
parent.assembleRelease.dependsOn it
}
}
}

// Task to copy an APK from sub projects to current project.
def copyApkToProjectBuildTask(variantName) {
subprojects.android.applicationVariants.each { applicationVariant ->
applicationVariant.each { variant ->

// Copy only one APK to current project
if (variant.name.contains(variantName) && variant.name.contains('projectDependency')) {
variant.outputs.outputFile.each { File file ->
copy {
from "${file.absolutePath}"
into "${buildDir}/outputs/apk/"
}
}
}
}
}
}

task assembleDebug() {
doLast {
copyApkToProjectBuildTask('Debug');
}
}

task assembleRelease() {
doLast {
copyApkToProjectBuildTask('Release');
}
}

// Empty task with task dependencies.
task assemble(dependsOn: [assembleDebug, assembleRelease]) {}
5 changes: 5 additions & 0 deletions apps/sasquatch/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ dependencies {
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-analytics:${version}"
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-crashes:${version}"
jcenterDependencyCompile "com.microsoft.azure.mobile:mobile-center-distribute:${version}"

/* Force usage this version of support annotations to avoid conflict. */
androidTestCompile "com.android.support:support-annotations:${rootProject.ext.supportLibVersion}"
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
compile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.microsoft.azure.mobile.sasquatch.activities;


import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;

import com.microsoft.azure.mobile.Constants;
import com.microsoft.azure.mobile.sasquatch.R;

import org.junit.Rule;
import org.junit.Test;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withChild;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.CHECK_DELAY;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.TOAST_DELAY;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.onToast;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.waitFor;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.withContainsText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;

@SuppressWarnings("unused")
public class AnalyticsTest {

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

@Test
public void sendEventTest() throws InterruptedException {

/* Send event. */
onView(allOf(
withChild(withText(R.string.title_event)),
withChild(withText(R.string.description_event))))
.perform(click());
onView(withId(R.id.name)).perform(replaceText("test"), closeSoftKeyboard());
onView(withText(R.string.send)).perform(click());

/* Check toasts. */
waitFor(onToast(mActivityTestRule.getActivity(),
withText(R.string.event_before_sending)), Constants.DEFAULT_TRIGGER_INTERVAL + CHECK_DELAY)
.check(matches(isDisplayed()));
waitAnalytics();
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
withContainsText(R.string.event_sent_succeeded),
withContainsText(R.string.event_sent_failed))), TOAST_DELAY)
.check(matches(isDisplayed()));
}

@Test
public void sendPageTest() throws InterruptedException {

/* Send page. */
onView(allOf(
withChild(withText(R.string.title_page)),
withChild(withText(R.string.description_page))))
.perform(click());
onView(withId(R.id.name)).perform(replaceText("test"), closeSoftKeyboard());
onView(withText(R.string.send)).perform(click());

/* Check toasts. */
waitFor(onToast(mActivityTestRule.getActivity(), withText(R.string.page_before_sending)), Constants.DEFAULT_TRIGGER_INTERVAL + CHECK_DELAY)
.check(matches(isDisplayed()));
waitAnalytics();
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
withContainsText(R.string.page_sent_succeeded),
withContainsText(R.string.page_sent_failed))), TOAST_DELAY)
.check(matches(isDisplayed()));
}

private void waitAnalytics() {
Espresso.registerIdlingResources(MainActivity.analyticsIdlingResource);
onView(isRoot()).perform(waitFor(CHECK_DELAY));
Espresso.unregisterIdlingResources(MainActivity.analyticsIdlingResource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.microsoft.azure.mobile.sasquatch.activities;

import android.content.Context;
import android.content.Intent;
import android.support.annotation.StringRes;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.EspressoException;
import android.support.test.espresso.FailureHandler;
import android.support.test.espresso.ViewInteraction;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.rule.ActivityTestRule;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.IntentCompat;
import android.view.View;

import com.microsoft.azure.mobile.Constants;
import com.microsoft.azure.mobile.MobileCenter;
import com.microsoft.azure.mobile.crashes.Crashes;
import com.microsoft.azure.mobile.crashes.CrashesPrivateHelper;
import com.microsoft.azure.mobile.crashes.utils.ErrorLogHelper;
import com.microsoft.azure.mobile.sasquatch.R;
import com.microsoft.azure.mobile.utils.storage.StorageHelper;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.io.File;
import java.lang.reflect.Method;

import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.isDialog;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withChild;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.CHECK_DELAY;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.TOAST_DELAY;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.onToast;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.waitFor;
import static com.microsoft.azure.mobile.sasquatch.activities.utils.EspressoUtils.withContainsText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertTrue;

@SuppressWarnings("unused")
public class CrashesTest {

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class, true, false);

private Context mContext;

@Before
public void setUp() throws Exception {
mContext = getInstrumentation().getTargetContext();

/* Clear preferences. */
StorageHelper.initialize(mContext);
StorageHelper.PreferencesStorage.clear();

/* Clear crashes. */
Constants.loadFromContext(mContext);
for (File logFile : ErrorLogHelper.getErrorStorageDirectory().listFiles()) {
assertTrue(logFile.delete());
}

/* Launch main activity and go to setting page. Required to properly initialize. */
mActivityTestRule.launchActivity(new Intent());

/* Register IdlingResource */
Espresso.registerIdlingResources(MainActivity.crashesIdlingResource);
}

@After
public final void tearDown() {

/* Unregister IdlingResource */
Espresso.unregisterIdlingResources(MainActivity.crashesIdlingResource);
}

@Test
public void testCrashTest() throws InterruptedException {
crashTest(R.string.title_test_crash);
}

@Test
public void divideByZeroTest() throws InterruptedException {
crashTest(R.string.title_crash_divide_by_0);
}

@Test
public void uiCrashTest() throws InterruptedException {
crashTest(R.string.title_test_ui_crash);
}

@Test
public void variableMessageTest() throws InterruptedException {
crashTest(R.string.title_variable_message);
}

/**
* Crash and sending report test.
* <p>
* We can't truly restart application in tests, so some kind of crashes can't be tested by this method.
* Out of memory or stack overflow - crash the test process;
* UI states errors - problems with restart activity;
* <p>
* Also to avoid flakiness, please setup your test environment
* (https://google.github.io/android-testing-support-library/docs/espresso/setup/index.html#setup-your-test-environment).
* On your device, under Settings->Developer options disable the following 3 settings:
* - Window animation scale
* - Transition animation scale
* - Animator duration scale
*
* @param titleId Title string resource to find list item.
* @throws InterruptedException If the current thread is interrupted.
*/
private void crashTest(@StringRes int titleId) throws InterruptedException {

/* Crash. */
onView(allOf(
withChild(withText(R.string.title_crashes)),
withChild(withText(R.string.description_crashes))))
.perform(click());

onCrash(titleId)
.withFailureHandler(new CrashFailureHandler())
.perform(click());

/* Check error report. */
assertTrue(Crashes.hasCrashedInLastSession());

/* Send report. */
waitFor(onView(withText(R.string.crash_confirmation_dialog_send_button))
.inRoot(isDialog()), 1000)
.perform(click());

/* Check toasts. */
waitFor(onToast(mActivityTestRule.getActivity(),
withText(R.string.crash_before_sending)), CHECK_DELAY)
.check(matches(isDisplayed()));
onView(isRoot()).perform(waitFor(CHECK_DELAY));
waitFor(onToast(mActivityTestRule.getActivity(), anyOf(
withContainsText(R.string.crash_sent_succeeded),
withText(R.string.crash_sent_failed))), TOAST_DELAY)
.check(matches(isDisplayed()));
onView(isRoot()).perform(waitFor(TOAST_DELAY));
}

private ViewInteraction onCrash(@StringRes int titleId) {
return onData(allOf(instanceOf(CrashActivity.Crash.class), withCrashTitle(titleId)))
.perform();
}

@SuppressWarnings("rawtypes")
private static Matcher<Object> withCrashTitle(@StringRes final int titleId) {
return new BoundedMatcher<Object, CrashActivity.Crash>(CrashActivity.Crash.class) {

@Override
public boolean matchesSafely(CrashActivity.Crash map) {
return map.title == titleId;
}

@Override
public void describeTo(Description description) {
description.appendText("with item title from resource id: ");
description.appendValue(titleId);
}
};
}

private class CrashFailureHandler implements FailureHandler {

@Override
public void handle(Throwable error, Matcher<View> viewMatcher) {
Throwable uncaughtException = error instanceof EspressoException ? error.getCause() : error;

/* Save exception. */
CrashesPrivateHelper.saveUncaughtException(mContext.getMainLooper().getThread(), uncaughtException);

/* Relaunch. */
ActivityCompat.finishAffinity(mActivityTestRule.getActivity());
unsetInstance(MobileCenter.class);
unsetInstance(Crashes.class);
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
mActivityTestRule.launchActivity(intent);
}

@SuppressWarnings("unchecked")
private void unsetInstance(Class clazz) {
try {
Method m = clazz.getDeclaredMethod("unsetInstance");
m.setAccessible(true);
m.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Loading

0 comments on commit 727355e

Please sign in to comment.