From 8fdaf382491d21ddc7bb39b6898cb4b1704af5d6 Mon Sep 17 00:00:00 2001 From: Artem Zinnatullin Date: Fri, 13 Nov 2015 07:19:33 +0300 Subject: [PATCH] Run tests on Android! --- .gitignore | 3 + .travis.yml | 24 ++++++-- android-tests/build.gradle | 61 +++++++++++++++++++ android-tests/src/main/AndroidManifest.xml | 4 ++ build.gradle | 41 +++++-------- gradle/build.sh | 9 +++ gradle/buildViaTravis.sh | 8 +-- gradle/runTestsOnAndroid.sh | 6 ++ rxjava/build.gradle | 43 +++++++++++++ settings.gradle | 3 +- src/test/java/rx/BackpressureTests.java | 6 +- src/test/java/rx/ObservableTests.java | 48 ++++++++++++--- .../OnSubscribeCombineLatestTest.java | 1 + .../operators/OnSubscribeRefCountTest.java | 39 ++++++++---- .../OperatorMergeMaxConcurrentTest.java | 8 ++- .../operators/OperatorReplayTest.java | 9 ++- .../rx/schedulers/ExecutorSchedulerTest.java | 34 ++++++----- 17 files changed, 272 insertions(+), 75 deletions(-) create mode 100644 android-tests/build.gradle create mode 100644 android-tests/src/main/AndroidManifest.xml create mode 100755 gradle/build.sh create mode 100755 gradle/runTestsOnAndroid.sh create mode 100644 rxjava/build.gradle diff --git a/.gitignore b/.gitignore index fb5b67685f..bbc52e2423 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ bin/ # Scala build *.cache /.nb-gradle/private/ + +# Android build local properties +local.properties \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 5c90183bda..2b772d634b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,30 @@ -language: java -jdk: -- oraclejdk7 -sudo: false # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ +sudo: false + +language: android -# script for build and release via Travis to Bintray -script: gradle/buildViaTravis.sh +android: + components: + - build-tools-23.0.1 + - android-23 + - extra-android-m2repository + - sys-img-armeabi-v7a-android-18 + +jdk: + - oraclejdk7 + +script: gradle/build.sh # cache between builds cache: directories: - $HOME/.m2 - $HOME/.gradle + env: + matrix: + - TARGET_PLATFORM=jdk + - TARGET_PLATFORM="android-18 --abi armeabi-v7a" global: - secure: YcLpYfNc/dyDON+oDvnJK5pFNhpPeJHxlAHV8JBt42e51prAl6njqrg1Qlfdp0pvBiskTPQHUxbFy9DOB1Z+43lPj5vlqz6qBgtS3vtBnsrczr+5Xx7NTdVKq6oZGl45VjfNPT7zdM6GQ5ifdzOid6kJIFu34g9JZkCzOY3BWGM= - secure: WVmfSeW1UMNdem7+X4cVDjkEkqdeNavYH4udn3bFN1IFaWdliWFp4FYVBVi+p1T/IgkRSqzoW9Bm43DABe1UMFoErFCbfd7B0Ofgb4NZAsxFgokHGVLCe6k5+rQyASseiO7k0itSj3Kq9TrDueKPhv+g+IG0w1A8yZTnXdhXHvY= diff --git a/android-tests/build.gradle b/android-tests/build.gradle new file mode 100644 index 0000000000..b336f116c2 --- /dev/null +++ b/android-tests/build.gradle @@ -0,0 +1,61 @@ +buildscript { + repositories { + jcenter() + } + + dependencies { + // Android Gradle plugin + classpath 'com.android.tools.build:gradle:1.3.1' + + // Resolves android-sdk dependencies + classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply plugin: 'android-sdk-manager' +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion '23.0.1' + + defaultConfig { + minSdkVersion 14 + testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + } + + sourceSets { + androidTest { + java.srcDir '../src/test/java' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } +} + +dependencies { + compile libraries.rxJava + + androidTestCompile libraries.mockitoCore + androidTestCompile libraries.dexMaker + androidTestCompile libraries.dexMakerDx + androidTestCompile libraries.dexMakerMockito + androidTestCompile libraries.androidSupportTestRunner +} + +afterEvaluate { + tasks.withType(com.android.build.gradle.internal.tasks.AndroidTestTask) { task -> + task.doFirst { + logging.level = LogLevel.INFO + } + task.doLast { + logging.level = LogLevel.LIFECYCLE + } + } +} \ No newline at end of file diff --git a/android-tests/src/main/AndroidManifest.xml b/android-tests/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..aff696213b --- /dev/null +++ b/android-tests/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8b934db6eb..6821635814 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,23 @@ buildscript { - repositories { jcenter() } - dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:2.2.3' } + repositories { jcenter() } + dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:2.2.3' } } -description = 'RxJava: Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.' +allprojects { + repositories { + jcenter() + } -apply plugin: 'rxjava-project' -apply plugin: 'java' - -dependencies { - testCompile 'junit:junit-dep:4.10' - testCompile 'org.mockito:mockito-core:1.8.5' -} - -javadoc { - exclude "**/rx/internal/**" + apply plugin: 'rxjava-project' } -// support for snapshot/final releases with the various branches RxJava uses -nebulaRelease { - addReleaseBranchPattern(/\d+\.\d+\.\d+/) - addReleaseBranchPattern('HEAD') -} +ext.libraries = [ + rxJava : project(':rxjava'), -if (project.hasProperty('release.useLastTag')) { - tasks.prepare.enabled = false -} - -test{ - maxHeapSize = "2g" -} + junit : 'junit:junit-dep:4.10', + mockitoCore : 'org.mockito:mockito-core:1.10.19', + dexMaker : 'com.crittercism.dexmaker:dexmaker:1.4', + dexMakerDx : 'com.crittercism.dexmaker:dexmaker-dx:1.4', + dexMakerMockito : 'com.crittercism.dexmaker:dexmaker-mockito:1.4', + androidSupportTestRunner: 'com.android.support.test:runner:0.4' +] \ No newline at end of file diff --git a/gradle/build.sh b/gradle/build.sh new file mode 100755 index 0000000000..a584540c61 --- /dev/null +++ b/gradle/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ "$TARGET_PLATFORM" == "jdk" ]; then + sh "${SCRIPTS_DIR}/buildViaTravis.sh" +elif [[ "$TARGET_PLATFORM" == android-* ]]; then + sh "${SCRIPTS_DIR}/runTestsOnAndroid.sh" +fi \ No newline at end of file diff --git a/gradle/buildViaTravis.sh b/gradle/buildViaTravis.sh index d98e5eb603..f629139dec 100755 --- a/gradle/buildViaTravis.sh +++ b/gradle/buildViaTravis.sh @@ -3,14 +3,14 @@ if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" - ./gradlew -Prelease.useLastTag=true build + ./gradlew -Prelease.useLastTag=true :rxjava:build elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' - ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot --stacktrace + ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" :rxjava:build :rxjava:snapshot --stacktrace elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' - ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final --stacktrace + ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" :rxjava:final --stacktrace else echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' - ./gradlew -Prelease.useLastTag=true build + ./gradlew -Prelease.useLastTag=true :rxjava:build fi diff --git a/gradle/runTestsOnAndroid.sh b/gradle/runTestsOnAndroid.sh new file mode 100755 index 0000000000..e437f465aa --- /dev/null +++ b/gradle/runTestsOnAndroid.sh @@ -0,0 +1,6 @@ +#!/bin/bash +echo no | eval "android create avd --force -n test -t $TARGET_PLATFORM" +emulator -avd test -no-skin -no-audio -no-window & +android-wait-for-emulator +adb shell input keyevent 82 +./gradlew connectedAndroidTest \ No newline at end of file diff --git a/rxjava/build.gradle b/rxjava/build.gradle new file mode 100644 index 0000000000..28c696a190 --- /dev/null +++ b/rxjava/build.gradle @@ -0,0 +1,43 @@ +description = 'RxJava: Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.' + +apply plugin: 'java' + +sourceCompatibility = 1.7 +targetCompatibility = 1.7 + +sourceSets { + main { + java { + srcDir '../src/main/java' + } + } + + test { + java { + srcDir '../src/test/java' + } + } +} + +dependencies { + testCompile libraries.junit + testCompile libraries.mockitoCore +} + +javadoc { + exclude '**/rx/internal/**' +} + +// support for snapshot/final releases with the various branches RxJava uses +nebulaRelease { + addReleaseBranchPattern(/\d+\.\d+\.\d+/) + addReleaseBranchPattern('HEAD') +} + +if (project.hasProperty('release.useLastTag')) { + tasks.prepare.enabled = false +} + +test { + maxHeapSize = '2g' +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index c5620daef9..659c4be943 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -rootProject.name='rxjava' +include ':rxjava' +include ':android-tests' \ No newline at end of file diff --git a/src/test/java/rx/BackpressureTests.java b/src/test/java/rx/BackpressureTests.java index e46dfebcb5..e8390fce42 100644 --- a/src/test/java/rx/BackpressureTests.java +++ b/src/test/java/rx/BackpressureTests.java @@ -470,7 +470,7 @@ public void testOnBackpressureDrop() { } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testOnBackpressureDropWithAction() { for (int i = 0; i < 100; i++) { final AtomicInteger emitCount = new AtomicInteger(); @@ -508,7 +508,7 @@ public void call(Integer integer) { } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testOnBackpressureDropSynchronous() { for (int i = 0; i < 100; i++) { int NUM = (int) (RxRingBuffer.SIZE * 1.1); // > 1 so that take doesn't prevent buffer overflow @@ -530,7 +530,7 @@ public void testOnBackpressureDropSynchronous() { } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testOnBackpressureDropSynchronousWithAction() { for (int i = 0; i < 100; i++) { final AtomicInteger dropCount = new AtomicInteger(); diff --git a/src/test/java/rx/ObservableTests.java b/src/test/java/rx/ObservableTests.java index d59e8c41a9..143bbac23e 100644 --- a/src/test/java/rx/ObservableTests.java +++ b/src/test/java/rx/ObservableTests.java @@ -35,6 +35,9 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -55,6 +58,7 @@ import rx.functions.Func2; import rx.observables.ConnectableObservable; import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; import rx.schedulers.TestScheduler; import rx.subjects.ReplaySubject; import rx.subjects.Subject; @@ -1101,19 +1105,45 @@ public String call(Integer t1) { } @Test - public void testErrorThrownIssue1685() { + public void testErrorThrownIssue1685() throws ExecutionException, InterruptedException { Subject subject = ReplaySubject.create(); - Observable.error(new RuntimeException("oops")) - .materialize() - .delay(1, TimeUnit.SECONDS) - .dematerialize() - .subscribe(subject); + ExecutorService exec = Executors.newSingleThreadExecutor(); - subject.subscribe(); - subject.materialize().toBlocking().first(); + try { + + final AtomicReference err = new AtomicReference(); + + Scheduler s = Schedulers.from(exec); + exec.submit(new Runnable() { + @Override + public void run() { + Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + err.set(e); + } + }); + } + }).get(); - System.out.println("Done"); + Observable.error(new RuntimeException("oops")) + .materialize() + .delay(1, TimeUnit.SECONDS, s) + .dematerialize() + .subscribe(subject); + + subject.subscribe(); + subject.materialize().toBlocking().first(); + + Thread.sleep(1000); // the uncaught exception comes after the terminal event reaches toBlocking + + assertNotNull("UncaughtExceptionHandler didn't get anything.", err.get()); + + System.out.println("Done"); + } finally { + exec.shutdownNow(); + } } @Test diff --git a/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java b/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java index c28606cae0..bf4af98057 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeCombineLatestTest.java @@ -818,6 +818,7 @@ public void testWithCombineLatestIssue1717() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); final int SIZE = 2000; Observable timer = Observable.interval(0, 1, TimeUnit.MILLISECONDS) + .onBackpressureBuffer() .observeOn(Schedulers.newThread()) .doOnEach(new Action1>() { diff --git a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java index fa38d2bdf1..a492052a5b 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java @@ -22,6 +22,7 @@ import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.*; @@ -528,9 +529,12 @@ public Integer call(Integer t1, Integer t2) { @Test(timeout = 10000) public void testUpstreamErrorAllowsRetry() throws InterruptedException { + final AtomicReference err1 = new AtomicReference(); + final AtomicReference err2 = new AtomicReference(); + final AtomicInteger intervalSubscribed = new AtomicInteger(); Observable interval = - Observable.interval(200,TimeUnit.MILLISECONDS) + Observable.interval(200, TimeUnit.MILLISECONDS) .doOnSubscribe( new Action0() { @Override @@ -538,7 +542,7 @@ public void call() { System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); } } - ) + ) .flatMap(new Func1>() { @Override public Observable call(Long t1) { @@ -572,26 +576,39 @@ public void call(Throwable t1) { public void call(String t1) { System.out.println("Subscriber 1: " + t1); } + }, new Action1() { + @Override + public void call(Throwable t) { + err1.set(t); + } }); Thread.sleep(100); interval - .doOnError(new Action1() { - @Override - public void call(Throwable t1) { - System.out.println("Subscriber 2 onError: " + t1); - } - }) - .retry(5) + .doOnError(new Action1() { + @Override + public void call(Throwable t1) { + System.out.println("Subscriber 2 onError: " + t1); + } + }) + .retry(5) .subscribe(new Action1() { @Override public void call(String t1) { System.out.println("Subscriber 2: " + t1); } + }, new Action1() { + @Override + public void call(Throwable t) { + err2.set(t); + } }); - + Thread.sleep(1300); - + System.out.println(intervalSubscribed.get()); assertEquals(6, intervalSubscribed.get()); + + assertNotNull("First subscriber didn't get the error", err1); + assertNotNull("Second subscriber didn't get the error", err2); } } diff --git a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java index af20d14316..4f4a05d66f 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java @@ -27,6 +27,7 @@ import rx.*; import rx.Observable; import rx.Observer; +import rx.internal.util.PlatformDependent; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; @@ -218,10 +219,15 @@ public void testSimpleAsync() { } @Test(timeout = 10000) public void testSimpleOneLessAsyncLoop() { - for (int i = 0; i < 200; i++) { + int max = 200; + if (PlatformDependent.isAndroid()) { + max = 50; + } + for (int i = 0; i < max; i++) { testSimpleOneLessAsync(); } } + @Test(timeout = 10000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); diff --git a/src/test/java/rx/internal/operators/OperatorReplayTest.java b/src/test/java/rx/internal/operators/OperatorReplayTest.java index c0ec384d84..408fbc71ba 100644 --- a/src/test/java/rx/internal/operators/OperatorReplayTest.java +++ b/src/test/java/rx/internal/operators/OperatorReplayTest.java @@ -46,6 +46,7 @@ import rx.internal.operators.OperatorReplay.BoundedReplayBuffer; import rx.internal.operators.OperatorReplay.Node; import rx.internal.operators.OperatorReplay.SizeAndTimeBoundReplayBuffer; +import rx.internal.util.PlatformDependent; import rx.observables.ConnectableObservable; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; @@ -1049,7 +1050,13 @@ public void testAsyncComeAndGo() { @Test public void testNoMissingBackpressureException() { - final int m = 4 * 1000 * 1000; + final int m; + if (PlatformDependent.isAndroid()) { + m = 500 * 1000; + } else { + m = 4 * 1000 * 1000; + } + Observable firehose = Observable.create(new OnSubscribe() { @Override public void call(Subscriber t) { diff --git a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java index ed4e03213d..09aa2bacd1 100644 --- a/src/test/java/rx/schedulers/ExecutorSchedulerTest.java +++ b/src/test/java/rx/schedulers/ExecutorSchedulerTest.java @@ -15,21 +15,30 @@ */ package rx.schedulers; -import static org.junit.Assert.*; +import org.junit.Test; -import java.lang.management.*; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; - -import rx.*; +import rx.Scheduler; import rx.Scheduler.Worker; -import rx.functions.*; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Actions; import rx.internal.schedulers.NewThreadWorker; import rx.internal.util.RxThreadFactory; import rx.schedulers.ExecutorScheduler.ExecutorSchedulerWorker; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + public class ExecutorSchedulerTest extends AbstractSchedulerConcurrencyTests { final static Executor executor = Executors.newFixedThreadPool(2, new RxThreadFactory("TestCustomPool-")); @@ -59,9 +68,8 @@ public static void testCancelledRetention(Scheduler.Worker w, boolean periodic) Thread.sleep(1000); - MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); - MemoryUsage memHeap = memoryMXBean.getHeapMemoryUsage(); - long initial = memHeap.getUsed(); + Runtime runtime = Runtime.getRuntime(); + long initial = runtime.totalMemory() - runtime.freeMemory(); System.out.printf("Starting: %.3f MB%n", initial / 1024.0 / 1024.0); @@ -92,8 +100,7 @@ public void call() { } } - memHeap = memoryMXBean.getHeapMemoryUsage(); - long after = memHeap.getUsed(); + long after = runtime.totalMemory() - runtime.freeMemory(); System.out.printf("Peak: %.3f MB%n", after / 1024.0 / 1024.0); w.unsubscribe(); @@ -106,8 +113,7 @@ public void call() { Thread.sleep(1000); - memHeap = memoryMXBean.getHeapMemoryUsage(); - long finish = memHeap.getUsed(); + long finish = runtime.totalMemory() - runtime.freeMemory(); System.out.printf("After: %.3f MB%n", finish / 1024.0 / 1024.0); if (finish > initial * 5) {