Browse Source

Merge pull request #1672 from owncloud/automatic_tests

Added setup for automated tests, and first sample tests.
David A. Velasco 9 years ago
parent
commit
81218b9945

+ 32 - 10
.travis.yml

@@ -2,14 +2,36 @@ sudo: false
 language: android
 android:
   components:
-    - platform-tools
-    - tools
-    - build-tools-23.0.3
-    - android-23
-    - android-22
-    - android-19
-    - extra-android-m2repository
+# first 'tools' updates SDK tools 'til last version ** in remote repository number 10 **
+  - tools
+# second 'tools' updates SDK tools 'til last version ** in remote repository number 11 ** (current last one)
+  - tools
+  - platform-tools
+  - build-tools-23.0.3
+  - android-23
+  - extra-android-m2repository
+  - sys-img-armeabi-v7a-android-23
+before_install:
+  - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI -c 20M
+  - emulator -avd test -no-skin -no-audio -no-window &
+  - chmod +x ./wait_for_emulator.sh
+  - ./wait_for_emulator.sh
 script:
-  - ./gradlew clean
-  - ./gradlew build
-  
+# build app and assemble APK, in debug mode
+  - ./gradlew assembleDebug
+# run all the local unit tests of app module
+  - ./gradlew :testDebug
+# run all the instrumented tests of app module - DISABLED until we get an stable setup for Espresso in Travis
+# - ./gradlew connectedDebugAndroidTest --info
+# install app, then assemble and install instrumented tests of app module
+  - ./gradlew :installDebug
+  - ./gradlew :installDebugAndroidTest
+# run sample instrumented unit test
+  - adb shell am instrument -w -e debug false -e class com.owncloud.android.datamodel.OCFileUnitTest com.owncloud.android.test/android.support.test.runner.AndroidJUnitRunner
+env:
+  global:
+  - secure: h4Y7ZvgbvOj5T71ubRcw3Fy3KXF8qHugRFLGK3q2R9YuRsDAf8XH+Y/UiXyH8sac2QSj7Zlny1kA1DEJgwhTXs9wsAVKVJCQNTJGEvhm/4uQgPeNMzMv07Lqe8V+KUBsFH5qhfPJO357ERW0k2f2qljoLSHtHStclt7iGvFdynA=
+  - secure: o9L6lXWpXowhQSdiUSmajliBUkQ6n7NrBUqhC09lqe7yXSGhEsgGRXqHoT3q2B4uIqGSiLCa9HQbW0dfDQCs+pADmzHIl3zbTViR88TSaIhOiTrqMUUl5iaO++pneZ2TzgU9bbGHbl6Ixjc6iALH2+F7P+RUM6vLTNPcfnCJa3g=
+  - secure: ydxZrS7+1ht3p1tC6DE9W9bjLQGjMkwFBwyhNmcdEM6538kN8ZCBQe7NxSsCrC1nSDBLQ17Cziv0XJHl/pCfPrbkrPPgVFpjDfPeqC2zcGcCNcFQUEylXCvQ4uRU2hKL6dPqCsOQ57Pv3qwpPoprl/usoN5Wh8V7BKplU88ZaYM=
+  matrix:
+  - ANDROID_TARGET=android-23 ANDROID_ABI=armeabi-v7a

+ 32 - 0
androidTest/AndroidManifest.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ownCloud Android client application
+
+  Copyright (C) 2012-2016 ownCloud Inc.
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License version 2,
+  as published by the Free Software Foundation.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<!--
+    UI Automator requires Android 4.3 (API level 18) or higher.
+
+    So this AndroidManifest will be merged with the normal one
+    (not requiring to change the minSdk) and used for UI Automator tests, and does not affect
+    the normal release/debug builds.
+-->
+
+<manifest
+    package="${applicationId}.test"
+    xmlns:tools="http://schemas.android.com/tools">
+    <uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>
+</manifest>

+ 139 - 0
androidTest/java/com/owncloud/android/authentication/AuthenticatorActivityTest.java

@@ -0,0 +1,139 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.authentication;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import static org.junit.Assert.assertTrue;
+import com.owncloud.android.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+import android.app.Activity;
+
+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.typeText;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static org.hamcrest.Matchers.not;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AuthenticatorActivityTest {
+
+    public static final String EXTRA_ACTION = "ACTION";
+    public static final String EXTRA_ACCOUNT = "ACCOUNT";
+
+    private static final int WAIT_LOGIN = 5000;
+
+    private static final String ERROR_MESSAGE = "Activity not finished";
+    private static final String RESULT_CODE = "mResultCode";
+
+
+    @Rule
+    public ActivityTestRule<AuthenticatorActivity> mActivityRule = new ActivityTestRule<AuthenticatorActivity>(
+            AuthenticatorActivity.class){
+        @Override
+        protected Intent getActivityIntent() {
+
+            Context targetContext = InstrumentationRegistry.getInstrumentation()
+                    .getTargetContext();
+            Intent result = new Intent(targetContext, AuthenticatorActivity.class);
+            result.putExtra(EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE);
+            result.putExtra(EXTRA_ACCOUNT, "");
+            return result;
+        }
+    };
+
+    @Before
+    public void init(){
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        /*Point[] coordinates = new Point[4];
+        coordinates[0] = new Point(248, 1020);
+        coordinates[1] = new Point(248, 429);
+        coordinates[2] = new Point(796, 1020);
+        coordinates[3] = new Point(796, 429);*/
+        try {
+            if (!uiDevice.isScreenOn()) {
+                uiDevice.wakeUp();
+                //uiDevice.swipe(coordinates, 10);
+            }
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void check_login()
+        throws InterruptedException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
+        Bundle arguments = InstrumentationRegistry.getArguments();
+
+        // Get values passed
+        String testUser = arguments.getString("TEST_USER");
+        String testPassword = arguments.getString("TEST_PASSWORD");
+        String testServerURL = arguments.getString("TEST_SERVER_URL");
+
+        // Check that login button is disabled
+        onView(withId(R.id.buttonOK))
+                .check(matches(not(isEnabled())));
+
+        // Type server url
+        onView(withId(R.id.hostUrlInput))
+                .perform(typeText(testServerURL), closeSoftKeyboard());
+        onView(withId(R.id.account_username)).perform(click());
+
+        // Type user
+        onView(withId(R.id.account_username))
+                .perform(typeText(testUser), closeSoftKeyboard());
+
+        // Type user pass
+        onView(withId(R.id.account_password))
+                .perform(typeText(testPassword), closeSoftKeyboard());
+        onView(withId(R.id.buttonOK)).perform(click());
+
+        // Check that the Activity ends after clicking
+
+        Thread.sleep(WAIT_LOGIN);
+        Field f = Activity.class.getDeclaredField(RESULT_CODE);
+        f.setAccessible(true);
+        int mResultCode = f.getInt(mActivityRule.getActivity());
+
+        assertTrue(ERROR_MESSAGE, mResultCode == Activity.RESULT_OK);
+
+    }
+}

+ 135 - 0
androidTest/java/com/owncloud/android/datamodel/OCFileUnitTest.java

@@ -0,0 +1,135 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+package com.owncloud.android.datamodel;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+
+/**
+ * Instrumented unit test, to be run in an Android emulator or device.
+ *
+ * At the moment, it's a sample to validate the automatic test environment, in the scope of instrumented unit tests.
+ *
+ * Don't take it as an example of completeness.
+ *
+ * See http://developer.android.com/intl/es/training/testing/unit-testing/instrumented-unit-tests.html .
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class OCFileUnitTest {
+
+    private final static String PATH = "/path/to/a/file.txt";
+    private static final long ID = 12345L;
+    private static final long PARENT_ID = 567890L;
+    private static final String STORAGE_PATH = "/mnt/sd/localpath/to/a/file.txt";
+    private static final String MIME_TYPE = "text/plain";
+    private static final long FILE_LENGTH = 9876543210L;
+    private static final long CREATION_TIMESTAMP = 8765432109L;
+    private static final long MODIFICATION_TIMESTAMP = 7654321098L;
+    private static final long MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA = 6543210987L;
+    private static final long LAST_SYNC_DATE_FOR_PROPERTIES = 5432109876L;
+    private static final long LAST_SYNC_DATE_FOR_DATA = 4321098765L;
+    private static final String ETAG = "adshfas98ferqw8f9yu2";
+    private static final String PUBLIC_LINK = "https://fake.url.net/owncloud/987427448712984sdas29";
+    private static final String PERMISSIONS = "SRKNVD";
+    private static final String REMOTE_ID = "jadñgiadf8203:9jrp98v2mn3er2089fh";
+    private static final String ETAG_IN_CONFLICT = "2adshfas98ferqw8f9yu";
+
+    private OCFile mFile;
+
+    @Before
+    public void createDefaultOCFile() {
+        mFile = new OCFile(PATH);
+    }
+
+
+    @Test
+    public void writeThenReadAsParcelable() {
+
+        // Set up mFile with not-default values
+        mFile.setFileId(ID);
+        mFile.setParentId(PARENT_ID);
+        mFile.setStoragePath(STORAGE_PATH);
+        mFile.setMimetype(MIME_TYPE);
+        mFile.setFileLength(FILE_LENGTH);
+        mFile.setCreationTimestamp(CREATION_TIMESTAMP);
+        mFile.setModificationTimestamp(MODIFICATION_TIMESTAMP);
+        mFile.setModificationTimestampAtLastSyncForData(MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA);
+        mFile.setLastSyncDateForProperties(LAST_SYNC_DATE_FOR_PROPERTIES);
+        mFile.setLastSyncDateForData(LAST_SYNC_DATE_FOR_DATA);
+        mFile.setFavorite(true);
+        mFile.setEtag(ETAG);
+        mFile.setShareViaLink(true);
+        mFile.setShareWithSharee(true);
+        mFile.setPublicLink(PUBLIC_LINK);
+        mFile.setPermissions(PERMISSIONS);
+        mFile.setRemoteId(REMOTE_ID);
+        mFile.setNeedsUpdateThumbnail(true);
+        mFile.setDownloading(true);
+        mFile.setEtagInConflict(ETAG_IN_CONFLICT);
+
+
+        // Write the file data in a Parcel
+        Parcel parcel = Parcel.obtain();
+        mFile.writeToParcel(parcel, mFile.describeContents());
+
+        // Read the data from the parcel
+        parcel.setDataPosition(0);
+        OCFile fileReadFromParcel = OCFile.CREATOR.createFromParcel(parcel);
+
+        // Verify that the received data are correct
+        assertThat(fileReadFromParcel.getRemotePath(), is(PATH));
+        assertThat(fileReadFromParcel.getFileId(), is(ID));
+        assertThat(fileReadFromParcel.getParentId(), is(PARENT_ID));
+        assertThat(fileReadFromParcel.getStoragePath(), is(STORAGE_PATH));
+        assertThat(fileReadFromParcel.getMimetype(), is(MIME_TYPE));
+        assertThat(fileReadFromParcel.getFileLength(), is(FILE_LENGTH));
+        assertThat(fileReadFromParcel.getCreationTimestamp(), is(CREATION_TIMESTAMP));
+        assertThat(fileReadFromParcel.getModificationTimestamp(), is(MODIFICATION_TIMESTAMP));
+        assertThat(
+            fileReadFromParcel.getModificationTimestampAtLastSyncForData(),
+            is(MODIFICATION_TIMESTAMP_AT_LAST_SYNC_FOR_DATA)
+        );
+        assertThat(fileReadFromParcel.getLastSyncDateForProperties(), is(LAST_SYNC_DATE_FOR_PROPERTIES));
+        assertThat(fileReadFromParcel.getLastSyncDateForData(), is(LAST_SYNC_DATE_FOR_DATA));
+        assertThat(fileReadFromParcel.isFavorite(), is(true));
+        assertThat(fileReadFromParcel.getEtag(), is(ETAG));
+        assertThat(fileReadFromParcel.isSharedViaLink(), is(true));
+        assertThat(fileReadFromParcel.isSharedWithSharee(), is(true));
+        assertThat(fileReadFromParcel.getPublicLink(), is(PUBLIC_LINK));
+        assertThat(fileReadFromParcel.getPermissions(), is(PERMISSIONS));
+        assertThat(fileReadFromParcel.getRemoteId(), is(REMOTE_ID));
+        assertThat(fileReadFromParcel.needsUpdateThumbnail(), is(true));
+        assertThat(fileReadFromParcel.isDownloading(), is(true));
+        assertThat(fileReadFromParcel.getEtagInConflict(), is(ETAG_IN_CONFLICT));
+
+    }
+}

+ 157 - 0
androidTest/java/com/owncloud/android/uiautomator/InitialTest.java

@@ -0,0 +1,157 @@
+/**
+ * ownCloud Android client application
+ * <p/>
+ * Copyright (C) 2015 ownCloud Inc.
+ * <p/>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ * <p/>
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * <p/>
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.uiautomator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * UI Automator tests
+ */
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 18)
+public class InitialTest {
+
+    private static final String OWNCLOUD_APP_PACKAGE = "com.owncloud.android";
+    private static final String ANDROID_SETTINGS_PACKAGE = "com.android.settings";
+    private static final String SETTINGS_DATA_USAGE_OPTION = "Data usage";
+
+    private static final int LAUNCH_TIMEOUT = 5000;
+
+    private UiDevice mDevice;
+
+    @Before
+    public void initializeDevice() {
+        // Initialize UiDevice instance
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+    }
+
+    @Test
+    public void checkPreconditions() {
+        assertThat(mDevice, notNullValue());
+    }
+
+    /**
+     * Start owncloud app
+     */
+    @Test
+    public void startAppFromHomeScreen() {
+        // Perform a short press on the HOME button
+        mDevice.pressHome();
+
+        // Wait for launcher
+        final String launcherPackage = getLauncherPackageName();
+        assertThat(launcherPackage, notNullValue());
+        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
+
+        // Launch the app
+        Context context = InstrumentationRegistry.getContext();
+        final Intent intent = context.getPackageManager()
+                .getLaunchIntentForPackage(OWNCLOUD_APP_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        context.startActivity(intent);
+
+        // Wait for the app to appear
+        mDevice.wait(Until.hasObject(By.pkg(OWNCLOUD_APP_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
+    }
+
+    /**
+     * Start Settings app
+     *
+     * @throws UiObjectNotFoundException
+     */
+    @Test
+    public void startSettingsFromHomeScreen() throws UiObjectNotFoundException {
+
+        mDevice.pressHome();
+
+        // Wait for launcher
+        final String launcherPackage = getLauncherPackageName();
+        assertThat(launcherPackage, notNullValue());
+        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
+
+        // Launch the app
+        Context context = InstrumentationRegistry.getContext();
+        final Intent intent = context.getPackageManager()
+                .getLaunchIntentForPackage(ANDROID_SETTINGS_PACKAGE);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        context.startActivity(intent);
+
+        clickByText(SETTINGS_DATA_USAGE_OPTION);
+
+    }
+
+    /**
+     * Uses package manager to find the package name of the device launcher. Usually this package
+     * is "com.android.launcher" but can be different at times. This is a generic solution which
+     * works on all platforms.`
+     */
+    private String getLauncherPackageName() {
+        // Create launcher Intent
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+
+        // Use PackageManager to get the launcher package name
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        return resolveInfo.activityInfo.packageName;
+    }
+
+    /**
+     * Helper to click on objects that match the content-description text
+     *
+     * @param text
+     * @throws UiObjectNotFoundException
+     */
+    private void clickByDescription(String text) throws UiObjectNotFoundException {
+        UiObject obj = new UiObject(new UiSelector().description(text));
+        obj.clickAndWaitForNewWindow();
+    }
+
+    /**
+     * Helper to click on object that match the text value
+     *
+     * @param text
+     * @throws UiObjectNotFoundException
+     */
+    private void clickByText(String text) throws UiObjectNotFoundException {
+        UiObject obj = new UiObject(new UiSelector().text(text));
+        obj.clickAndWaitForNewWindow();
+    }
+}

+ 55 - 9
build.gradle

@@ -1,3 +1,10 @@
+// Gradle build file
+//
+// This project was started in Eclipse and later moved to Android Studio. In the transition, both IDEs were supported.
+// Due to this, the files layout is not the usual in new projects created with Android Studio / gradle. This file
+// merges declarations usually split in two separates build.gradle file, one for global settings of the project in
+// its root folder, another one for the app module in subfolder of root.
+
 buildscript {
     repositories {
         mavenCentral()
@@ -22,6 +29,7 @@ repositories {
 }
 
 dependencies {
+    /// dependencies for app building
     compile name: 'touch-image-view'
     compile project(':owncloud-android-library')
     compile "com.android.support:support-v4:${supportLibraryVersion}"
@@ -29,12 +37,50 @@ dependencies {
     compile 'com.jakewharton:disklrucache:2.0.2'
     compile "com.android.support:appcompat-v7:${supportLibraryVersion}"
     compile 'com.getbase:floatingactionbutton:1.10.1'
+
+
+    /// dependencies for local unit tests
+    testCompile 'junit:junit:4.12'
+    testCompile 'org.mockito:mockito-core:1.10.19'
+
+
+    /// dependencies for instrumented tests
+    // JUnit4 Rules
+    androidTestCompile 'com.android.support.test:rules:0.5'
+
+    // Android JUnit Runner
+    androidTestCompile 'com.android.support.test:runner:0.5'
+
+    // Espresso core
+    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
+
+    // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
+    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
+
+}
+
+tasks.withType(Test) {
+    /// increased logging for tests
+    testLogging {
+        events "passed", "skipped", "failed"
+    }
 }
 
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.3"
 
+    defaultConfig {
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+        // arguments to be passed to functional tests
+        testInstrumentationRunnerArgument "TEST_USER", "\"$System.env.OCTEST_APP_USERNAME\""
+        testInstrumentationRunnerArgument "TEST_PASSWORD", "\"$System.env.OCTEST_APP_PASSWORD\""
+        testInstrumentationRunnerArgument "TEST_SERVER_URL", "\"$System.env.OCTEST_SERVER_BASE_URL\""
+    }
+
+    // adapt structure from Eclipse to Gradle/Android Studio expectations;
+    // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
     sourceSets {
         main {
             manifest.srcFile 'AndroidManifest.xml'
@@ -43,11 +89,16 @@ android {
             aidl.srcDirs = ['src']
             renderscript.srcDirs = ['src']
             res.srcDirs = ['res']
-            assets.srcDirs = ['res']
+            assets.srcDirs = ['assets']
         }
 
-        // Move the tests to tests/java, tests/res, etc...
-        instrumentTest.setRoot('tests')
+
+        // move whole local unit tests structure as a whole from src/test/* to test/*
+        test.setRoot('test')
+
+        // move whole instrumented tests structure as a whole from src/androidTest/* to androidTest/*
+        androidTest.setRoot('androidTest')
+
 
         // Move the build types to build-types/<type>
         // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
@@ -71,10 +122,5 @@ android {
     packagingOptions {
         exclude 'META-INF/LICENSE.txt'
     }
-}
-
-
-
-
-
 
+}

+ 79 - 0
test/java/com/owncloud/android/utils/ErrorMessageAdapterUnitTest.java

@@ -0,0 +1,79 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2016 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+package com.owncloud.android.utils;
+
+import android.content.res.Resources;
+
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RemoveFileOperation;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.mockito.Mockito.when;
+
+/**
+ * Local unit test, to be run out of Android emulator or device.
+ *
+ * At the moment, it's a sample to validate the automatic test environment, in the scope of local unit tests with
+ * mock Android dependencies.
+ *
+ * Don't take it as an example of completeness.
+ *
+ * See http://developer.android.com/intl/es/training/testing/unit-testing/local-unit-tests.html .
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ErrorMessageAdapterUnitTest {
+
+    private final static String MOCK_FORBIDDEN_PERMISSIONS = "You do not have permission %s";
+    private final static String MOCK_TO_DELETE = "to delete this file";
+    private final static String PATH_TO_DELETE = "/path/to/a.file";
+    private final static String EXPECTED_ERROR_MESSAGE = "You do not have permission to delete this file";
+
+    @Mock
+    Resources mMockResources;
+
+    @Test
+    public void getErrorCauseMessageForForbiddenRemoval() {
+        // Given a mocked set of resources passed to the object under test...
+        when(mMockResources.getString(R.string.forbidden_permissions))
+            .thenReturn(MOCK_FORBIDDEN_PERMISSIONS);
+        when(mMockResources.getString(R.string.forbidden_permissions_delete))
+            .thenReturn(MOCK_TO_DELETE);
+
+        // ... when method under test is called ...
+        String errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
+            new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN),
+            new RemoveFileOperation(PATH_TO_DELETE, false),
+            mMockResources
+        );
+
+        // ... then the result should be the expected one.
+        assertThat(errorMessage, is(EXPECTED_ERROR_MESSAGE));
+
+    }
+}

+ 19 - 0
wait_for_emulator.sh

@@ -0,0 +1,19 @@
+#!/bin/bash
+
+bootanim=""
+failcounter=0
+checkcounter=0
+until [[ "$bootanim" =~ "stopped" ]]; do
+   bootanim=`adb -e shell getprop init.svc.bootanim 2>&1`
+   echo "($checkcounter) $bootanim"
+   if [[ "$bootanim" =~ "not found" ]]; then
+      let "failcounter += 1"
+      if [[ $failcounter -gt 30 ]]; then
+        echo "Failed to start emulator"
+        exit 1
+      fi
+   fi
+   let "checkcounter += 1"
+   sleep 10
+done
+echo "Done"