diff --git a/.gitignore b/.gitignore index 2e810308be..8754b69638 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ realm/realm-library/src/main/cpp/jni_include realm/realm-library/distribution # Cmake output realm/realm-library/.externalNativeBuild +realm/realm-library/.cxx diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb52c9f52..183cfc525a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 7.0.2(YYYY-MM-DD) + +### Enhancements +* None. + +### Fixes +* [ObjectServer] Calling `SyncManager.refreshConnections()` did not correctly refresh connections in all cases, which could delay reconnects up to 5 minutes. (Issue [#7003](https://github.com/realm/realm-java/issues/7003)) +* Upgrading the file format result did in some cases not work correctly. This could result in a number of crashes, e.g. `FORMAT_UPGRADE_REQUIRED`. (Issue [#6889](https://github.com/realm/realm-java/issues/6889), since 7.0.0) +* Bug in memory mapping management. This bug could result in multiple different asserts as well as segfaults. In many cases stack backtraces would include members of the EncyptedFileMapping near the top - even if encryption was not used at all. In other cases asserts or crashes would be in methods reading an array header or array element. In all cases the application would terminate immediately. (Issue [#3838](https://github.com/realm/realm-core/pull/3838), since 7.0.0) + +### Compatibility +* Realm Object Server: 3.23.1 or later. +* File format: Generates Realms with format v10 (Reads and upgrades all previous formats from Realm Java 2.0 and later). +* APIs are backwards compatible with all previous release of realm-java in the 7.x.y series. + +### Internal +* Upgraded to Realm Sync 5.0.15. +* Upgraded to Realm Core 6.0.17. + + ## 7.0.1(2020-07-01) ### Enhancements diff --git a/Dockerfile b/Dockerfile index fe1790d66a..4265616585 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,12 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 # Locales RUN apt-get clean && apt-get -y update && apt-get install -y locales && locale-gen en_US.UTF-8 ENV LANG "en_US.UTF-8" ENV LANGUAGE "en_US.UTF-8" ENV LC_ALL "en_US.UTF-8" +ENV TZ=Europe/Copenhagen +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Set the environment variables ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 @@ -12,29 +14,38 @@ ENV ANDROID_HOME /opt/android-sdk-linux # Need by cmake ENV ANDROID_NDK_HOME /opt/android-ndk ENV ANDROID_NDK /opt/android-ndk -ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools +ENV PATH ${PATH}:${ANDROID_HOME}/emulator:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${NDK_HOME} ENV NDK_CCACHE /usr/bin/ccache +ENV CCACHE_CPP2 yes -# The 32 bit binaries because aapt requires it -# `file` is need by the script that creates NDK toolchains # Keep the packages in alphabetical order to make it easy to avoid duplication -RUN DEBIAN_FRONTEND=noninteractive dpkg --add-architecture i386 \ +# tzdata needs to be installed first. See https://askubuntu.com/questions/909277/avoiding-user-interaction-with-tzdata-when-installing-certbot-in-a-docker-contai +# `file` is need by the Android Emulator +RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ + && apt-get install -y tzdata \ && apt-get install -y bsdmainutils \ + bridge-utils \ build-essential \ ccache \ curl \ file \ git \ - libc6:i386 \ - libgcc1:i386 \ - libncurses5:i386 \ - libstdc++6:i386 \ - libz1:i386 \ + jq \ + libc6 \ + libgcc1 \ + libglu1 \ + libncurses5 \ + libstdc++6 \ + libz1 \ + libvirt-clients \ + libvirt-daemon-system \ openjdk-8-jdk-headless \ + qemu-kvm \ s3cmd \ unzip \ + virt-manager \ wget \ zip \ && apt-get clean @@ -54,24 +65,17 @@ RUN sdkmanager --update RUN yes | sdkmanager --licenses # SDKs -# Please keep these in descending order! # The `yes` is for accepting all non-standard tool licenses. # Please keep all sections in descending order! RUN yes | sdkmanager \ - 'platform-tools' \ - 'build-tools;29.0.2' \ + 'build-tools;29.0.3' \ + 'cmake;3.6.4111459' \ + 'emulator' \ 'extras;android;m2repository' \ 'platforms;android-29' \ - 'cmake;3.6.4111459' - -# Install the NDK -RUN mkdir /opt/android-ndk-tmp && \ - cd /opt/android-ndk-tmp && \ - wget -q https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip -O android-ndk.zip && \ - unzip android-ndk.zip && \ - mv android-ndk-r21 /opt/android-ndk && \ - rm -rf /opt/android-ndk-tmp && \ - chmod -R a+rX /opt/android-ndk + 'platform-tools' \ + 'ndk;21.0.6113669' \ + 'system-images;android-29;default;x86' # Make the SDK universally writable RUN chmod -R a+rwX ${ANDROID_HOME} diff --git a/Jenkinsfile b/Jenkinsfile index 6fa2761da5..53e85b3020 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,149 +1,112 @@ #!groovy +@Library('realm-ci') _ + import groovy.json.JsonOutput -def buildSuccess = false -def rosContainer +buildSuccess = false +rosContainer = null +dockerNetworkId = UUID.randomUUID().toString() +// Branches from which we release SNAPSHOT's. Only release branches need to run on actual hardware. +releaseBranches = ['master', 'next-major', 'v10'] +// Branches that are "important", so if they do not compile they will generate a Slack notification +slackNotificationBranches = [ 'master', 'releases', 'next-major', 'v10' ] +currentBranch = env.CHANGE_BRANCH +// 'android' nodes have android devices attached and 'brix' are physical machines in Copenhagen. +nodeSelector = (releaseBranches.contains(currentBranch)) ? 'android' : 'docker-cph-03' // Switch to `brix` when all CPH nodes work: https://jira.mongodb.org/browse/RCI-14 try { - node('docker-cph-01') { + node(nodeSelector) { timeout(time: 90, unit: 'MINUTES') { // Allocate a custom workspace to avoid having % in the path (it breaks ld) ws('/tmp/realm-java') { stage('SCM') { checkout([ - $class: 'GitSCM', - branches: scm.branches, - gitTool: 'native git', - extensions: scm.extensions + [ - [$class: 'CleanCheckout'], - [$class: 'SubmoduleOption', recursiveSubmodules: true] - ], - userRemoteConfigs: scm.userRemoteConfigs - ]) + $class : 'GitSCM', + branches : scm.branches, + gitTool : 'native git', + extensions : scm.extensions + [ + [$class: 'CleanCheckout'], + [$class: 'SubmoduleOption', recursiveSubmodules: true] + ], + userRemoteConfigs: scm.userRemoteConfigs + ]) } // Toggles for PR vs. Master builds. - // For PR's, we just build for arm-v7a and run unit tests for the ObjectServer variant - // A full build is done on `master`. - // TODO Once Android emulators are available on all nodes, we can switch to x86 builds - // on PR's for even more throughput. + // - For PR's, we favor speed > absolute correctness. So we just build for x86, use an + // emulator and run unit tests for the ObjectServer variant. + // - For branches from which we make releases, we build all architectures and run tests + // on an actual device. + def useEmulator = false + def emulatorImage = "" def abiFilter = "" def instrumentationTestTarget = "connectedAndroidTest" - if (!['master', 'next-major'].contains(env.BRANCH_NAME)) { - abiFilter = "-PbuildTargetABIs=armeabi-v7a" - instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" // Run in debug more for better error reporting + def deviceSerial = "" + if (!releaseBranches.contains(currentBranch)) { + useEmulator = true + emulatorImage = "system-images;android-29;default;x86" + abiFilter = "-PbuildTargetABIs=x86" + instrumentationTestTarget = "connectedObjectServerDebugAndroidTest" + deviceSerial = "emulator-5554" } - def buildEnv - def rosEnv - stage('Docker build') { - // Docker image for build - buildEnv = docker.build 'realm-java:snapshot' - // Docker image for testing Realm Object Server - def dependProperties = readProperties file: 'dependencies.list' - def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] - withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { - rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" + try { + + def buildEnv = null + stage('Prepare Docker Images') { + buildEnv = docker.build 'realm-java:snapshot' + // Docker image for testing Realm Object Server + def dependProperties = readProperties file: 'dependencies.list' + def rosVersion = dependProperties["REALM_OBJECT_SERVER_VERSION"] + withCredentials([string(credentialsId: 'realm-sync-feature-token-enterprise', variable: 'realmFeatureToken')]) { + rosEnv = docker.build 'ros:snapshot', "--build-arg ROS_VERSION=${rosVersion} --build-arg REALM_FEATURE_TOKEN=${realmFeatureToken} tools/sync_test_server" + } + rosContainer = rosEnv.run() } - } - rosContainer = rosEnv.run() + // There is a chance that real devices are attached to the host, so if the emulator is + // running we need to make sure that ADB and tests targets the correct device. + String restrictDevice = "" + if (deviceSerial != null) { + restrictDevice = "-e ANDROID_SERIAL=${deviceSerial} " + } - try { - buildEnv.inside("-e HOME=/tmp " + + buildEnv.inside("-e HOME=/tmp " + "-e _JAVA_OPTIONS=-Duser.home=/tmp " + "--privileged " + + "-v /dev/kvm:/dev/kvm " + "-v /dev/bus/usb:/dev/bus/usb " + "-v ${env.HOME}/gradle-cache:/tmp/.gradle " + "-v ${env.HOME}/.android:/tmp/.android " + "-v ${env.HOME}/ccache:/tmp/.ccache " + + restrictDevice + "-e REALM_CORE_DOWNLOAD_DIR=/tmp/.gradle " + - "--network container:${rosContainer.id}") { - - // Lock required around all usages of Gradle as it isn't - // able to share its cache between builds. - lock("${env.NODE_NAME}-android") { - - stage('JVM tests') { - try { - withCredentials([[$class: 'FileBinding', credentialsId: 'c0cc8f9e-c3f1-4e22-b22f-6568392e26ae', variable: 'S3CFG']]) { - sh "chmod +x gradlew && ./gradlew assemble check javadoc -Ps3cfg=${env.S3CFG} ${abiFilter} --stacktrace" - } - } finally { - storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' - storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' - step([$class: 'LintPublisher']) - } - } - - stage('Realm Transformer tests') { - try { - gradle('realm-transformer', 'check') - } finally { - storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' - } - } - - stage('Static code analysis') { - try { - gradle('realm', "findbugs pmd checkstyle ${abiFilter}") - } finally { - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) - publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) - step([$class: 'CheckStylePublisher', - canComputeNew: false, - defaultEncoding: '', - healthy: '', - pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', - unHealthy: '' - ]) - } - } - - stage('Run instrumented tests') { - String backgroundPid - try { - backgroundPid = startLogCatCollector() - forwardAdbPorts() - gradle('realm', "${instrumentationTestTarget}") - } finally { - stopLogCatCollector(backgroundPid) - storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' - storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' - } - } + "--network container:${rosContainer.id} ") { - // Gradle plugin tests require that artifacts are available, so this - // step needs to be after the instrumentation tests - stage('Gradle plugin tests') { - try { - gradle('gradle-plugin', 'check --debug') - } finally { - storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' - } - } - - // TODO: add support for running monkey on the example apps - - if (['master'].contains(env.BRANCH_NAME)) { - stage('Collect metrics') { - collectAarMetrics() - } - } - - if (['master', 'next-major'].contains(env.BRANCH_NAME)) { - stage('Publish to OJO') { - withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { - sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" - } - } - } + // Lock required around all usages of Gradle as it isn't + // able to share its cache between builds. + lock("${env.NODE_NAME}-android") { + if (useEmulator) { + // TODO: We should wait until the emulator is online. For now assume it starts fast enough + // before the tests will run, since the library needs to build first. + sh """yes '\n' | avdmanager create avd -n CIEmulator -k '${emulatorImage}' --force""" + sh "adb start-server" // https://stackoverflow.com/questions/56198290/problems-with-adb-exe + // Need to go to ANDROID_HOME due to https://askubuntu.com/questions/1005944/emulator-avd-does-not-launch-the-virtual-device + sh "cd \$ANDROID_HOME/tools && emulator -avd CIEmulator -no-boot-anim -no-window -wipe-data -noaudio -partition-size 4098 &" + try { + runBuild(abiFilter, instrumentationTestTarget) + } finally { + sh "adb emu kill" } + } else { + runBuild(abiFilter, instrumentationTestTarget) } + } + } } finally { - archiveRosLog(rosContainer.id) - sh "docker logs ${rosContainer.id}" - rosContainer.stop() + archiveServerLogs(rosContainer.id) + sh "docker logs ${rosContainer.id}" + rosContainer.stop() } } } @@ -155,50 +118,139 @@ try { buildSuccess = false throw e } finally { - if (['master', 'releases', 'next-major'].contains(env.BRANCH_NAME) && !buildSuccess) { + if (slackNotificationBranches.contains(currentBranch) && !buildSuccess) { node { withCredentials([[$class: 'StringBinding', credentialsId: 'slack-java-url', variable: 'SLACK_URL']]) { def payload = JsonOutput.toJson([ - username: 'Mr. Jenkins', - icon_emoji: ':jenkins:', - attachments: [[ - 'title': "The ${env.BRANCH_NAME} branch is broken!", - 'text': "<${env.BUILD_URL}|Click here> to check the build.", - 'color': "danger" - ]] - ]) + username: 'Mr. Jenkins', + icon_emoji: ':jenkins:', + attachments: [[ + 'title': "The ${currentBranch} branch is broken!", + 'text': "<${env.BUILD_URL}|Click here> to check the build.", + 'color': "danger" + ]] + ]) sh "curl -X POST --data-urlencode \'payload=${payload}\' ${env.SLACK_URL}" } } } } +// Runs all build steps +def runBuild(abiFilter, instrumentationTestTarget) { + + stage('Build') { + sh "chmod +x gradlew && ./gradlew assemble javadoc ${abiFilter} --stacktrace" + } + + stage('Tests') { + parallel 'JVM' : { + try { + sh "chmod +x gradlew && ./gradlew check ${abiFilter} --stacktrace" + } finally { + storeJunitResults 'realm/realm-annotations-processor/build/test-results/test/TEST-*.xml' + storeJunitResults 'examples/unitTestExample/build/test-results/**/TEST-*.xml' + storeJunitResults 'realm/realm-library/build/test-results/**/TEST-*.xml' + step([$class: 'LintPublisher']) + } + }, + 'Realm Transformer' : { + try { + gradle('realm-transformer', 'check') + } finally { + storeJunitResults 'realm-transformer/build/test-results/test/TEST-*.xml' + } + }, + 'Static code analysis' : { + try { + gradle('realm', "findbugs checkstyle") // FIXME: pmd disabled: https://github.com/realm/realm-java/issues/7024 + } finally { + publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/findbugs', reportFiles: 'findbugs-output.html', reportName: 'Findbugs issues']) + // publishHTML(target: [allowMissing: false, alwaysLinkToLastBuild: false, keepAll: true, reportDir: 'realm/realm-library/build/reports/pmd', reportFiles: 'pmd.html', reportName: 'PMD Issues']) + step([$class: 'CheckStylePublisher', + canComputeNew: false, + defaultEncoding: '', + healthy: '', + pattern: 'realm/realm-library/build/reports/checkstyle/checkstyle.xml', + unHealthy: '' + ]) + } + }, + 'Instrumentation' : { + String backgroundPid + try { + backgroundPid = startLogCatCollector() + forwardAdbPorts() + gradle('realm', "${instrumentationTestTarget} ${abiFilter}") + } finally { + stopLogCatCollector(backgroundPid) + storeJunitResults 'realm/realm-library/build/outputs/androidTest-results/connected/**/TEST-*.xml' + storeJunitResults 'realm/kotlin-extensions/build/outputs/androidTest-results/connected/**/TEST-*.xml' + } + }, + 'Gradle Plugin' : { + try { + gradle('gradle-plugin', 'check --debug') + } finally { + storeJunitResults 'gradle-plugin/build/test-results/test/TEST-*.xml' + } + } + } + + // TODO: add support for running monkey on the example apps + + if (['master'].contains(currentBranch)) { + stage('Collect metrics') { + collectAarMetrics() + } + } + + if (releaseBranches.contains(currentBranch)) { + stage('Publish to OJO') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'bintray', passwordVariable: 'BINTRAY_KEY', usernameVariable: 'BINTRAY_USER']]) { + sh "chmod +x gradlew && ./gradlew -PbintrayUser=${env.BINTRAY_USER} -PbintrayKey=${env.BINTRAY_KEY} assemble ojoUpload --stacktrace" + } + } + } +} + def forwardAdbPorts() { - sh ''' adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && - adb reverse tcp:8888 tcp:8888 - ''' + sh """ adb reverse tcp:9080 tcp:9080 && adb reverse tcp:9443 tcp:9443 && + adb reverse tcp:8888 tcp:8888 && adb reverse tcp:9090 tcp:9090 + """ } -def String startLogCatCollector() { - sh '''adb logcat -c - adb logcat -v time > "logcat.txt" & - echo $! > pid - ''' - return readFile("pid").trim() +String startLogCatCollector() { + // Cancel build quickly if no device is available. The lock acquired already should + // ensure we have access to a device. If not, it is most likely a more severe problem. + timeout(time: 1, unit: 'MINUTES') { + // Need ADB as root to clear all buffers: https://stackoverflow.com/a/47686978/1389357 + sh 'adb devices' + sh """adb root + adb logcat -b all -c + adb logcat -v time > 'logcat.txt' & + echo \$! > pid + """ + return readFile("pid").trim() + } } def stopLogCatCollector(String backgroundPid) { - sh "kill ${backgroundPid}" - zip([ - 'zipFile': 'logcat.zip', - 'archive': true, - 'glob' : 'logcat.txt' - ]) - sh 'rm logcat.txt' + // The pid might not be available if the build was terminated early or stopped due to + // a build error. + if (backgroundPid != null) { + sh "kill ${backgroundPid}" + zip([ + 'zipFile': 'logcat.zip', + 'archive': true, + 'glob' : 'logcat.txt' + ]) + sh 'rm logcat.txt' + } } -def archiveRosLog(String id) { - sh "docker cp ${id}:/tmp/integration-test-command-server.log ./ros.log" +def archiveServerLogs(String rosContainerId) { + sh "docker cp ${rosContainerId}:/tmp/integration-test-command-server.log ./ros.log" zip([ 'zipFile': 'roslog.zip', 'archive': true, @@ -221,10 +273,10 @@ def getTagsString(Map tags) { def storeJunitResults(String path) { step([ - $class: 'JUnitResultArchiver', - allowEmptyResults: true, - testResults: path - ]) + $class: 'JUnitResultArchiver', + allowEmptyResults: true, + testResults: path + ]) } def collectAarMetrics() { diff --git a/dependencies.list b/dependencies.list index 579eb34917..afcf91f325 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,19 +1,18 @@ # Realm Sync release used by Realm Java (This includes Realm Core) # https://github.com/realm/realm-sync/releases -REALM_SYNC_VERSION=5.0.7 -REALM_SYNC_SHA256=239049c777e4275fe094c73ae6dfa8736ae5ca9aa821c8d58b6d5eb3d84415e0 +REALM_SYNC_VERSION=5.0.15 +REALM_SYNC_SHA256=f8d84ce3867e2d47bc6566bb9f9c35f725415f268f4136772beb4f5a041ac8d5 # Object Server Release used by Integration tests. Installed using NPM. # Use `npm view realm-object-server versions` to get a list of available versions. REALM_OBJECT_SERVER_VERSION=3.28.2 # Common Android settings across projects -GRADLE_BUILD_TOOLS=3.3.2 -ANDROID_BUILD_TOOLS=28.0.3 +GRADLE_BUILD_TOOLS=3.6.1 +ANDROID_BUILD_TOOLS=29.0.3 # Common classpath dependencies -# Gradle 5 is not supported yet: https://issuetracker.google.com/issues/126433059 -gradleVersion=4.10.1 +gradleVersion=5.6.4 ndkVersion=21.0.6113669 BUILD_INFO_EXTRACTOR_GRADLE=4.7.5 GRADLE_BINTRAY_PLUGIN=1.8.4 diff --git a/examples/build.gradle b/examples/build.gradle index 0436277664..04c40a67c5 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -1,6 +1,6 @@ def projectDependencies = new Properties() projectDependencies.load(new FileInputStream("${rootDir}/../dependencies.list")) -project.ext.sdkVersion = 27 +project.ext.sdkVersion = 29 project.ext.minSdkVersion = 16 project.ext.buildTools = projectDependencies.get("ANDROID_BUILD_TOOLS") diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/examples/gradle/wrapper/gradle-wrapper.properties +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/examples/settings.gradle b/examples/settings.gradle index 15f6d6c37d..519f5eb74d 100644 --- a/examples/settings.gradle +++ b/examples/settings.gradle @@ -12,6 +12,6 @@ include 'newsreaderExample' include 'rxJavaExample' include 'secureTokenAndroidKeyStore' include 'threadExample' -include 'unitTestExample' +// include 'unitTestExample' FIXME: https://github.com/realm/realm-java/issues/6834 include 'objectServerExample' include 'multiprocessExample' diff --git a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy index def91cc266..003b0819c7 100644 --- a/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy +++ b/gradle-plugin/src/test/groovy/io/realm/gradle/PluginTest.groovy @@ -39,7 +39,7 @@ import static org.junit.Assert.fail * Comment about the order of repositories. * The order of repositories do matter, and might need to change depending on the * version of the Build Tools being used. See e.g.: - * + * * https://stackoverflow.com/questions/55278227/android-gradle-build-error-artifacts-for-configuration-classpath/55278968#55278968 * https://stackoverflow.com/questions/52968576/could-not-find-aapt2-proto-jar-com-android-tools-buildaapt2-proto0-3-1 */ @@ -121,7 +121,9 @@ class PluginTest { void pluginAddsRightRepositories_noRepositorySet() { project.buildscript { repositories { - google() + maven { + url 'https://maven.google.com/' + } mavenCentral() jcenter() } @@ -149,7 +151,7 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('jcenter.bintray.com', project.repositories.last().url.host) } @@ -157,9 +159,11 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSet() { project.buildscript { repositories { - google() mavenCentral() jcenter() + maven { + url 'https://maven.google.com/' + } } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -168,7 +172,6 @@ class PluginTest { project.repositories { google() - mavenCentral() } def manifest = project.file("src/main/AndroidManifest.xml") @@ -190,10 +193,10 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) + assertEquals('maven.google.com', project.buildscript.repositories.last().url.host) - assertEquals(5, project.repositories.size()) - assertEquals('repo.maven.apache.org', project.repositories.last().url.host) + assertEquals(1, project.repositories.size()) + assertEquals('dl.google.com', project.repositories.last().url.host) } // Test for https://github.com/realm/realm-java/issues/6610 @@ -201,9 +204,11 @@ class PluginTest { void pluginAddsRightRepositories_withFlatDirs() { project.buildscript { repositories { - mavenCentral() - google() jcenter() + maven { + url 'https://maven.google.com/' + } + mavenCentral() } dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get("GRADLE_BUILD_TOOLS")}" @@ -214,7 +219,6 @@ class PluginTest { flatDir { dirs 'libs' } - mavenCentral() google() } @@ -237,9 +241,9 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('jcenter.bintray.com', project.buildscript.repositories.last().url.host) + assertEquals('repo.maven.apache.org', project.buildscript.repositories.last().url.host) - assertEquals(6, project.repositories.size()) + assertEquals(2, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } @@ -248,7 +252,9 @@ class PluginTest { void pluginAddsRightRepositories_withRepositoriesSetAfterPluginIsApplied() { project.buildscript { repositories { - google() + maven { + url 'https://maven.google.com/' + } mavenCentral() jcenter() } @@ -280,9 +286,9 @@ class PluginTest { project.evaluate() assertEquals(3, project.buildscript.repositories.size()) - assertEquals('dl.google.com', project.buildscript.repositories.first().url.host) + assertEquals('maven.google.com', project.buildscript.repositories.first().url.host) - assertEquals(4, project.repositories.size()) + assertEquals(1, project.repositories.size()) assertEquals('dl.google.com', project.repositories.last().url.host) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/library-build-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/library-build-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-annotations/gradle/wrapper/gradle-wrapper.properties b/realm-annotations/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm-annotations/gradle/wrapper/gradle-wrapper.properties +++ b/realm-annotations/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm-transformer/gradle/wrapper/gradle-wrapper.properties b/realm-transformer/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm-transformer/gradle/wrapper/gradle-wrapper.properties +++ b/realm-transformer/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/build.gradle b/realm/build.gradle index 673c8b0e92..eabeb14244 100644 --- a/realm/build.gradle +++ b/realm/build.gradle @@ -13,7 +13,7 @@ buildscript { dependencies { classpath "com.android.tools.build:gradle:${projectDependencies.get('GRADLE_BUILD_TOOLS')}" - classpath 'de.undercouch:gradle-download-task:3.3.0' + classpath 'de.undercouch:gradle-download-task:4.0.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.novoda:gradle-android-command-plugin:1.7.1' classpath 'com.github.skhatri:gradle-s3-plugin:1.0.4' @@ -35,7 +35,7 @@ allprojects { project.ext.set(key, val) } project.ext.minSdkVersion = 16 - project.ext.compileSdkVersion = 28 + project.ext.compileSdkVersion = 29 project.ext.buildToolsVersion = projectDependencies.get("ANDROID_BUILD_TOOLS") group = 'io.realm' version = file("${rootDir}/../version.txt").text.trim() @@ -45,3 +45,12 @@ allprojects { jcenter() } } + +// Disable JavaDoc strict mode: https://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html +if (JavaVersion.current().isJava8Compatible()) { + allprojects { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:-missing', '-quiet') + } + } +} diff --git a/realm/config/findbugs/findbugs-filter.xml b/realm/config/findbugs/findbugs-filter.xml index 5a5d451546..5c1dc1118d 100644 --- a/realm/config/findbugs/findbugs-filter.xml +++ b/realm/config/findbugs/findbugs-filter.xml @@ -4,43 +4,53 @@ In code, please prefer annotations as a way of ignoring Findbugs issues --> - - - + + + + - + - + - + - + - + - + - + - + + + - + + + - + + + + + + + + + + + diff --git a/realm/gradle/wrapper/gradle-wrapper.properties b/realm/gradle/wrapper/gradle-wrapper.properties index 4e974715fd..0ebb3108e2 100644 --- a/realm/gradle/wrapper/gradle-wrapper.properties +++ b/realm/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/realm/realm-annotations-processor/build.gradle b/realm/realm-annotations-processor/build.gradle index ae79c01a89..3d30713f96 100644 --- a/realm/realm-annotations-processor/build.gradle +++ b/realm/realm-annotations-processor/build.gradle @@ -7,16 +7,18 @@ apply plugin: 'com.jfrog.bintray' sourceCompatibility = '1.8' targetCompatibility = '1.8' +def properties = new Properties() +properties.load(new FileInputStream("${projectDir}/../../dependencies.list")) + dependencies { - compile "com.squareup:javawriter:2.5.1" - compile "io.realm:realm-annotations:${version}" - - testCompile files('../realm-library/build/intermediates/intermediate-jars/objectServer/release/classes.jar') // Java projects cannot depend on AAR files - testCompile files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it - testCompile group:'junit', name:'junit', version:'4.12' - testCompile group:'com.google.testing.compile', name:'compile-testing', version:'0.6' - testCompile files(file("${System.env.ANDROID_HOME}/platforms/android-27/android.jar")) + implementation "com.squareup:javawriter:2.5.1" + implementation "io.realm:realm-annotations:${version}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + testImplementation files('../realm-library/build/intermediates/aar_main_jar/baseRelease/classes.jar') // Java projects cannot depend on AAR files + testImplementation files("${System.properties['java.home']}/../lib/tools.jar") // This is needed otherwise compile-testing won't be able to find it + testImplementation group:'junit', name:'junit', version:'4.12' + testImplementation group:'com.google.testing.compile', name:'compile-testing', version:'0.6' + testImplementation files(file("${System.env.ANDROID_HOME}/platforms/android-29/android.jar")) } // for Ant filter diff --git a/realm/realm-library/build.gradle b/realm/realm-library/build.gradle index bc289641a1..85a27020fc 100644 --- a/realm/realm-library/build.gradle +++ b/realm/realm-library/build.gradle @@ -74,6 +74,8 @@ android { } } + ndkVersion = "21.0.6113669" + externalNativeBuild { cmake { path 'src/main/cpp/CMakeLists.txt' @@ -573,6 +575,7 @@ if (project.hasProperty('dontCleanJniFiles')) { } else { task cleanExternalBuildFiles(type: Delete) { delete project.file('.externalNativeBuild') + delete project.file('.cxx') // Clean .so files that were created by old build script (realm/realm-jni/build.gradle). delete project.file('src/main/jniLibs') } @@ -581,15 +584,19 @@ if (project.hasProperty('dontCleanJniFiles')) { project.afterEvaluate { android.libraryVariants.all { variant -> - variant.externalNativeBuildTasks[0].dependsOn(checkNdk) if (project.hasProperty('buildTargetABIs') && project.getProperty('buildTargetABIs').trim().isEmpty()) { - variant.externalNativeBuildTasks[0].enabled = false + variant.externalNativeBuildProviders[0].configure { + enabled = false + } } // all Java files must be compiled before native build + // See https://github.com/android/ndk-samples/issues/284 android.libraryVariants.all { anotherVariant -> if (variant.flavorName == anotherVariant.flavorName) { - variant.externalNativeBuildTasks[0].dependsOn("compile${anotherVariant.name.capitalize()}JavaWithJavac") + variant.externalNativeBuildProviders[0].configure { + dependsOn "compile${anotherVariant.name.capitalize()}JavaWithJavac" + } } } // as of android gradle plugin 3.0.0-alpha5, generateJsonModel* triggers native build. Java files must be compiled before them. @@ -599,34 +606,6 @@ project.afterEvaluate { } } -task checkNdk() { - doLast { - def ndkPathInEnvVariable = System.env.ANDROID_NDK_HOME - if (!ndkPathInEnvVariable) { - throw new GradleException("The environment variable 'ANDROID_NDK_HOME' must be set.") - } - checkNdk(ndkPathInEnvVariable) - - def localPropFile = rootProject.file('local.properties') - if (!localPropFile.exists()) { - // we can skip the checks since 'ANDROID_NDK_HOME' will be used instead. - } else { - def String ndkPathInLocalProperties = getValueFromPropertiesFile(localPropFile, 'ndk.dir') - if (!ndkPathInLocalProperties) { - throw new GradleException("'ndk.dir' must be set in ${localPropFile.getAbsolutePath()}.") - } - checkNdk(ndkPathInLocalProperties) - if (new File(ndkPathInLocalProperties).getCanonicalPath() - != new File(ndkPathInEnvVariable).getCanonicalPath()) { - throw new GradleException( - "The value of environment variable 'ANDROID_NDK_HOME' (${ndkPathInEnvVariable}) and" - + " 'ndk.dir' in 'local.properties' (${ndkPathInLocalProperties}) " - + ' must point the same directory.') - } - } - } -} - android.productFlavors.all { flavor -> def librarySuffix = flavor.name == 'base' ? '' : '-object-server' def userName = project.findProperty('bintrayUser') ?: 'noUser' @@ -777,26 +756,6 @@ task ojoUpload() { group = 'Publishing' } -def checkNdk(String ndkPath) { - def detectedNdkVersion - def releaseFile = new File(ndkPath, 'RELEASE.TXT') - def propertyFile = new File(ndkPath, 'source.properties') - if (releaseFile.isFile()) { - detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] - } else if (propertyFile.isFile()) { - detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') - if (detectedNdkVersion == null) { - throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") - } - } else { - throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") - } - if (detectedNdkVersion != project.ndkVersion) { - throw new GradleException("Your NDK version: ${detectedNdkVersion}." - + " Realm JNI must be compiled with version ${project.ndkVersion} of the NDK.") - } -} - static def getValueFromPropertiesFile(File propFile, String key) { if (!propFile.isFile() || !propFile.canRead()) { return null diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java index 55dba9fe68..7a904ea333 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmInterprocessTest.java @@ -126,7 +126,7 @@ public void onServiceDisconnected(ComponentName componentName) { private class InterprocessHandler extends Handler { // Timeout Watchdog. In case the service crashed or expected response is not returned. // It is very important to feed the dog after the expected message arrived. - private final int timeout = 5000; + private final static int timeout = 5000; private volatile boolean isTimeout = true; private Runnable timeoutRunnable = new Runnable() { @Override diff --git a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java index 8cf23c8fb7..92e4d208ea 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java +++ b/realm/realm-library/src/androidTest/java/io/realm/RealmMigrationTests.java @@ -648,7 +648,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { public void apply(DynamicRealmObject obj) { String fieldValue = obj.getString(MigrationPrimaryKey.FIELD_PRIMARY); if (fieldValue != null && fieldValue.length() != 0) { - obj.setInt(TEMP_FIELD_ID, Integer.valueOf(fieldValue).intValue()); + obj.setInt(TEMP_FIELD_ID, Integer.parseInt(fieldValue)); } else { // Since this cannot be accepted as proper pk value, we'll delete it. // *You can modify with some other value such as 0, but that's not @@ -695,7 +695,7 @@ public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { public void apply(DynamicRealmObject obj) { String fieldValue = obj.getString(MigrationPrimaryKey.FIELD_PRIMARY); if (fieldValue != null && fieldValue.length() != 0) { - obj.setInt(TEMP_FIELD_ID, Integer.valueOf(fieldValue)); + obj.setInt(TEMP_FIELD_ID, Integer.parseInt(fieldValue)); } else { obj.setNull(TEMP_FIELD_ID); } diff --git a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java index 45b23de504..003c8f1738 100644 --- a/realm/realm-library/src/androidTest/java/io/realm/SortTest.java +++ b/realm/realm-library/src/androidTest/java/io/realm/SortTest.java @@ -521,7 +521,7 @@ public void run() { @Override public void onChange(RealmResults element) { assertEquals(TEST_SIZE + 1, element.size()); - int i = 0; + long i = 0; for (AllTypes allTypes : element) { assertEquals(new Date(i), allTypes.getColumnDate()); i++; @@ -537,7 +537,7 @@ public void onChange(RealmResults element) { @Override public void onChange(RealmResults element) { assertEquals(TEST_SIZE + 1, element.size()); - int i = element.size() - 1; + long i = ((long) element.size()) - 1; for (AllTypes allTypes : element) { assertEquals(new Date(i), allTypes.getColumnDate()); i--; diff --git a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java b/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java deleted file mode 100644 index 8dfdc64114..0000000000 --- a/realm/realm-library/src/androidTest/java/io/realm/instrumentation/MockActivityManager.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2015 Realm Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.realm.instrumentation; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import io.realm.RealmConfiguration; - -public class MockActivityManager { - private Lifecycle instance; - private final RealmConfiguration realmConfiguration; - private final ReferenceQueue queue; - private static final Set> references = new CopyOnWriteArraySet>(); - - private MockActivityManager(RealmConfiguration realmConfiguration) { - this.realmConfiguration = realmConfiguration; - - instance = LifecycleComponentFactory.newInstance(realmConfiguration); - - this.queue = new ReferenceQueue(); - references.add(new WeakReference(instance, queue)); - - instance.onStart(); - } - - public static MockActivityManager newInstance (RealmConfiguration realmConfiguration) { - return new MockActivityManager(realmConfiguration); - } - - // simulates a configuration change, that should trigger - // to recreate the Lifecycle component - public void sendConfigurationChange () { - instance.onStop(); - // creates a new instance - instance = LifecycleComponentFactory.newInstance(realmConfiguration); - references.add(new WeakReference(instance, queue)); - - instance.onStart(); - } - - public int numberOfInstances () { - triggerGC(); - // WeakReferences are enqueued as soon as the object to which they point to becomes - // weakly reachable. - deleteWeaklyReachableReferences(); - return references.size(); - } - - // call onStop on the Activity, this help closing any open open realm - public void onStop() { - instance.onStop(); - } - - private void triggerGC () { - // From the AOSP FinalizationTest: - // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/ - // java/lang/ref/FinalizationTester.java - // System.gc() does not garbage collect every time. Runtime.gc() is - // more likely to perform a gc. - Runtime.getRuntime().gc(); - enqueueReferences(); - System.runFinalization(); - } - - private void enqueueReferences() { - // Hack. We don't have a programmatic way to wait for the reference queue daemon to move - // references to the appropriate queues. - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new AssertionError(); - } - } - - private void deleteWeaklyReachableReferences() { - Reference weakReference; - while ((weakReference = queue.poll()) != null ) { // Does not wait for a reference to become available. - references.remove(weakReference); - } - } -} diff --git a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake index ee22a08fc9..6014163b4f 100644 --- a/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake +++ b/realm/realm-library/src/main/cpp/CMake/RealmCore.cmake @@ -89,9 +89,11 @@ function(use_sync_release enable_sync sync_dist_path) # -latomic is not set by default for mips and armv5. # See https://code.google.com/p/android/issues/detail?id=182094 + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include") + list(APPEND LIB_INCLUDE_DIRS "${sync_dist_path}/include/realm") set_target_properties(lib_realm_core PROPERTIES IMPORTED_LOCATION ${core_lib_path} IMPORTED_LINK_INTERFACE_LIBRARIES atomic - INTERFACE_INCLUDE_DIRECTORIES "${sync_dist_path}/include") + INTERFACE_INCLUDE_DIRECTORIES "${LIB_INCLUDE_DIRS}") if (enable_sync) # Sync static library diff --git a/realm/realm-library/src/main/cpp/CMakeLists.txt b/realm/realm-library/src/main/cpp/CMakeLists.txt index 677d592023..cfc1d28159 100644 --- a/realm/realm-library/src/main/cpp/CMakeLists.txt +++ b/realm/realm-library/src/main/cpp/CMakeLists.txt @@ -17,6 +17,18 @@ ########################################################################### cmake_minimum_required(VERSION 3.6.0) +# loading dependencies properties +file(STRINGS "${CMAKE_SOURCE_DIR}/../../../../../dependencies.list" DEPENDENCIES) +foreach(LINE IN LISTS DEPENDENCIES) + string(REGEX MATCHALL "([^=]+)" KEY_VALUE "${LINE}") + list(LENGTH KEY_VALUE matches_count) + if(matches_count STREQUAL 2) + list(GET KEY_VALUE 0 KEY) + list(GET KEY_VALUE 1 VALUE) + set(DEP_${KEY} ${VALUE}) + endif() +endforeach() + FUNCTION(capitalizeFirstLetter var value) string(SUBSTRING ${value} 0 1 firstLetter) string(TOUPPER ${firstLetter} firstLetter) @@ -69,7 +81,7 @@ capitalizeFirstLetter(buildTypeCap "${CMAKE_BUILD_TYPE}") # Generate JNI header files. Each build has its own JNI header in its build_dir/jni_include. # WARNING: The classes_PATH is not part the public API offered by the Android Gradle Plugin # so it might change without warning when upgrading the plugin. -set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/compile${realmFlavorCap}${buildTypeCap}JavaWithJavac/classes/) +set(classes_PATH ${CMAKE_SOURCE_DIR}/../../../build/intermediates/javac/${REALM_FLAVOR}${buildTypeCap}/classes/) set(classes_LIST io.realm.RealmQuery io.realm.internal.Table io.realm.internal.CheckedRow @@ -95,7 +107,7 @@ if (build_SYNC) endif() create_javah(TARGET jni_headers CLASSES ${classes_LIST} - CLASSPATH ${classes_PATH} + CLASSPATH ${classes_PATH} $ENV{ANDROID_HOME}/platforms/android-29/android.jar OUTPUT_DIR ${jni_headers_PATH} DEPENDS ${classes_PATH} ) diff --git a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java index 0a2d95496e..e5f8255a1f 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java +++ b/realm/realm-library/src/main/java/io/realm/internal/android/AndroidCapabilities.java @@ -32,7 +32,7 @@ public class AndroidCapabilities implements Capabilities { // If set, it will treat the current looper thread as the main thread. // It is up to the caller to handle any race conditions around this. Right now only // RunInLooperThread.java does this as part of setting up the test. - @SuppressFBWarnings("MS_SHOULD_BE_FINAL") + @SuppressFBWarnings("MS_CANNOT_BE_FINAL") public static boolean EMULATE_MAIN_THREAD = false; private final Looper looper; diff --git a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java index cd1b62161b..8081650da2 100644 --- a/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java +++ b/realm/realm-library/src/main/java/io/realm/internal/async/RealmAsyncTaskImpl.java @@ -22,7 +22,7 @@ import io.realm.RealmAsyncTask; -public final class RealmAsyncTaskImpl implements RealmAsyncTask { +public class RealmAsyncTaskImpl implements RealmAsyncTask { private final Future pendingTask; private final ThreadPoolExecutor service; private volatile boolean isCancelled = false; diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java index d28319f723..3b6f7e7e78 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncManager.java @@ -535,6 +535,9 @@ private static synchronized void notifyErrorHandler(String nativeErrorCategory, private static synchronized void notifyNetworkIsBack() { try { + for (SyncSession session : sessions.values()) { + session.refreshConnection(); + } nativeReconnect(); } catch (Exception exception) { RealmLog.error(exception); diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java index 43a77d53b0..1ec529dc71 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncSession.java @@ -44,6 +44,7 @@ import io.realm.internal.Util; import io.realm.internal.android.AndroidCapabilities; import io.realm.internal.async.RealmAsyncTaskImpl; +import io.realm.internal.async.ResetableRealmAsyncTask; import io.realm.internal.network.AuthenticateResponse; import io.realm.internal.network.RealmObjectServer; import io.realm.internal.network.ExponentialBackoffTask; @@ -81,9 +82,9 @@ public class SyncSession { private final SyncConfiguration configuration; private final ErrorHandler errorHandler; - private RealmAsyncTask networkRequest; + private ResetableRealmAsyncTask networkRequest; // private RealmAsyncTask refreshTokenTask; - private RealmAsyncTask refreshTokenNetworkRequest; + private ResetableRealmAsyncTask refreshTokenNetworkRequest; private AtomicBoolean onGoingAccessTokenQuery = new AtomicBoolean(false); private volatile boolean isClosed = false; private final AtomicReference waitingForServerChanges = new AtomicReference<>(null); @@ -429,7 +430,7 @@ public synchronized void removeConnectionChangeListener(ConnectionListener liste } } - void close() { + synchronized void close() { isClosed = true; if (networkRequest != null) { networkRequest.cancel(); @@ -735,7 +736,7 @@ public interface ErrorHandler { } // Return the access token for the Realm this Session is connected to. - String getAccessToken(final RealmObjectServer authServer, String refreshToken) { + synchronized String getAccessToken(final RealmObjectServer authServer, String refreshToken) { // check first if there's a valid access_token we can return immediately if (getUser().isRealmAuthenticated(configuration)) { Token accessToken = getUser().getAccessToken(configuration); @@ -767,15 +768,16 @@ String getAccessToken(final RealmObjectServer authServer, String refreshToken) { } // Authenticate by getting access tokens for the specific Realm - private void authenticateRealm(final RealmObjectServer authServer) { + synchronized void authenticateRealm(final RealmObjectServer authServer) { if (networkRequest != null) { networkRequest.cancel(); } clearScheduledAccessTokenRefresh(); onGoingAccessTokenQuery.set(true); + final String taskName = "Session[" + configuration.getPath() + "][AuthenticateRealm]"; // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + networkRequest = new ResetableRealmAsyncTask(new ExponentialBackoffTask() { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { @@ -802,6 +804,7 @@ protected void onSuccess(AuthenticateResponse response) { onGoingAccessTokenQuery.set(false); } } + networkRequest = null; } @Override @@ -817,12 +820,17 @@ protected void onError(AuthenticateResponse response) { && !(response.getError().getException() instanceof InterruptedIOException)) { errorHandler.onError(SyncSession.this, response.getError()); } + networkRequest = null; } - }); - networkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + + @Override + protected String getName() { + return taskName; + } + }, SyncManager.NETWORK_POOL_EXECUTOR); } - private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { + private synchronized void scheduleRefreshAccessToken(final RealmObjectServer authServer, long expireDateInMs) { onGoingAccessTokenQuery.set(true); // calculate the delay time before which we should refresh the access_token, // we adjust to 10 second to proactively refresh the access_token before the session @@ -830,7 +838,7 @@ private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long long refreshAfter = expireDateInMs - System.currentTimeMillis() - REFRESH_MARGIN_DELAY; if (refreshAfter < 0) { // Token already expired - RealmLog.debug("Expires time already reached for the access token, refresh as soon as possible"); + RealmLog.debug("Session[%s]: Expires time already reached for the access token, refresh as soon as possible", configuration.getPath()); // we avoid refreshing directly to avoid an edge case where the client clock is ahead // of the server, causing all access_token received from the server to be always // expired, we will flood the server with refresh token requests then, so adding @@ -838,7 +846,7 @@ private void scheduleRefreshAccessToken(final RealmObjectServer authServer, long refreshAfter = REFRESH_MARGIN_DELAY; } - RealmLog.debug("Scheduling an access_token refresh in " + (refreshAfter) + " milliseconds"); + RealmLog.debug("Session[%s]: Schedule refresh of access token in %s sec.", configuration.getPath(), TimeUnit.SECONDS.convert(refreshAfter, TimeUnit.MILLISECONDS)); if (refreshTokenTask != null) { refreshTokenTask.cancel(); @@ -856,11 +864,11 @@ public void run() { } // Authenticate by getting access tokens for the specific Realm - private void refreshAccessToken(final RealmObjectServer authServer) { + private synchronized void refreshAccessToken(final RealmObjectServer authServer) { // Authenticate in a background thread. This allows incremental backoff and retries in a safe manner. clearScheduledAccessTokenRefresh(); - - Future task = SyncManager.NETWORK_POOL_EXECUTOR.submit(new ExponentialBackoffTask() { + final String taskName = "Session[" + configuration.getPath() + "][RefreshAccessToken]"; + refreshTokenNetworkRequest = new ResetableRealmAsyncTask(new ExponentialBackoffTask() { @Override protected AuthenticateResponse execute() { if (!isClosed && !Thread.currentThread().isInterrupted()) { @@ -873,7 +881,7 @@ protected AuthenticateResponse execute() { protected void onSuccess(AuthenticateResponse response) { synchronized (SyncSession.this) { if (!isClosed && !Thread.currentThread().isInterrupted() && !refreshTokenNetworkRequest.isCancelled()) { - RealmLog.debug("Access Token refreshed successfully, Sync URL: " + configuration.getServerUrl()); + RealmLog.debug("Session[%s]: Access Token refreshed successfully.", configuration.getPath()); SyncWorker syncWorker = response.getSyncWorker(); if (syncWorker != null) { @@ -888,6 +896,7 @@ protected void onSuccess(AuthenticateResponse response) { scheduleRefreshAccessToken(authServer, response.getAccessToken().expiresMs()); } } + refreshTokenNetworkRequest = null; } } @@ -895,19 +904,35 @@ protected void onSuccess(AuthenticateResponse response) { protected void onError(AuthenticateResponse response) { if (!isClosed && !Thread.currentThread().isInterrupted()) { onGoingAccessTokenQuery.set(false); - RealmLog.error("Unrecoverable error, while refreshing the access Token (" + response.getError().toString() + ") reschedule will not happen"); + RealmLog.debug("Session[%s]: Unrecoverable error, while refreshing the access Token. Reschedule will not happen. %s", configuration.getPath(), response.getError()); } + refreshTokenNetworkRequest = null; + } + + @Override + protected String getName() { + return taskName; } - }); - refreshTokenNetworkRequest = new RealmAsyncTaskImpl(task, SyncManager.NETWORK_POOL_EXECUTOR); + }, SyncManager.NETWORK_POOL_EXECUTOR); + } + + synchronized void refreshConnection() { + if (networkRequest != null) { + networkRequest.resetTask(); + } + if (refreshTokenNetworkRequest != null) { + refreshTokenNetworkRequest.resetTask(); + } } - void clearScheduledAccessTokenRefresh() { + synchronized void clearScheduledAccessTokenRefresh() { if (refreshTokenTask != null) { refreshTokenTask.cancel(); + refreshTokenTask = null; } if (refreshTokenNetworkRequest != null) { refreshTokenNetworkRequest.cancel(); + refreshTokenNetworkRequest = null; } onGoingAccessTokenQuery.set(false); } diff --git a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java index d8cdc0847f..a65cf03064 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java +++ b/realm/realm-library/src/objectServer/java/io/realm/SyncUser.java @@ -356,6 +356,7 @@ public void logOut() { final Token refreshTokenToBeRevoked = refreshToken; ThreadPoolExecutor networkPoolExecutor = SyncManager.NETWORK_POOL_EXECUTOR; + String taskName = "LogOutUser[" + identity + "]"; networkPoolExecutor.submit(new ExponentialBackoffTask(3) { @Override @@ -372,6 +373,11 @@ protected void onSuccess(LogoutResponse response) { protected void onError(LogoutResponse response) { RealmLog.error("Failed to log user out.\n" + response.getError().toString()); } + + @Override + protected String getName() { + return taskName; + } }); } } diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java new file mode 100644 index 0000000000..c6a36f71fd --- /dev/null +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/async/ResetableRealmAsyncTask.java @@ -0,0 +1,31 @@ +package io.realm.internal.async; + +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadPoolExecutor; + +import io.realm.internal.network.ExponentialBackoffTask; + +/** + * Wrapper class for tasks that can internally retry operations until they succeed. + * + * Using this class will automatically add the task to the ThreadPoolExecutor. + */ +public class ResetableRealmAsyncTask extends RealmAsyncTaskImpl { + + private final ExponentialBackoffTask task; + + public ResetableRealmAsyncTask(ExponentialBackoffTask pendingTask, ThreadPoolExecutor service) { + super(service.submit(pendingTask) , service); + this.task = pendingTask; + } + + /** + * If this task is currently waiting to retry. Calling this method will reset it and make it + * retry again immediately. + */ + public void resetTask() { + task.resetDelay(); + } +} diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java index 14b205cc06..e872157735 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/AuthenticateResponse.java @@ -147,7 +147,7 @@ private AuthenticateResponse(String serverResponse) { error = new ObjectServerError(ErrorCode.JSON_EXCEPTION, exceptionMessage, ex); debugMessage = String.format(Locale.US, "Error %s", error.getErrorMessage()); } - RealmLog.debug("AuthenticateResponse. " + debugMessage); + RealmLog.debug("AuthenticateResponse: %s", debugMessage); setError(error); this.accessToken = accessToken; this.refreshToken = refreshToken; diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java index f23dc5022b..92f8691415 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/ExponentialBackoffTask.java @@ -26,6 +26,8 @@ */ public abstract class ExponentialBackoffTask implements Runnable { private final int maxRetries; + private final Object sleepLock = new Object(); // Used to block the thread but still allow other thread to resume it. + private int attempt = 0; // Number of failed attempts previously made by this task. public ExponentialBackoffTask(int maxRetries) { this.maxRetries = maxRetries; @@ -62,17 +64,33 @@ protected boolean shouldAbortTask(T response) { // Callback when task has failed protected abstract void onError(T response); + // Returns the name of this task + protected abstract String getName(); + + /** + * Resets any exponential delays and retry the task immediately. + */ + public void resetDelay() { + synchronized (sleepLock) { + RealmLog.debug(getName() + " Reset delay for task."); + attempt = 0; + sleepLock.notify(); + } + } + @Override public void run() { - int attempt = 0; while (!Thread.interrupted()) { attempt++; long sleep = calculateExponentialDelay(attempt - 1, TimeUnit.MINUTES.toMillis(5)); if (sleep > 0) { + RealmLog.debug(getName() + " Delaying for " + TimeUnit.SECONDS.convert(sleep, TimeUnit.MILLISECONDS) + " sec."); try { - Thread.sleep(sleep); + synchronized (sleepLock) { + sleepLock.wait(sleep); + } } catch (InterruptedException e) { - RealmLog.debug("Incremental backoff was interrupted."); + RealmLog.debug(getName() + " Incremental backoff was interrupted."); return; // Abort if interrupted } } @@ -93,23 +111,23 @@ public void run() { private static long calculateExponentialDelay(int failedAttempts, long maxDelayInMs) { // https://en.wikipedia.org/wiki/Exponential_backoff //Attempt = FailedAttempts + 1 - //Attempt 1 0s 0s - //Attempt 2 2s 2s - //Attempt 3 4s 4s - //Attempt 4 8s 8s - //Attempt 5 16s 16s - //Attempt 6 32s 32s - //Attempt 7 64s 1m 4s - //Attempt 8 128s 2m 8s - //Attempt 9 256s 4m 16s - //Attempt 10 512 8m 32s - //Attempt 11 1024 17m 4s - //Attempt 12 2048 34m 8s - //Attempt 13 4096 1h 8m 16s - //Attempt 14 8192 2h 16m 32s - //Attempt 15 16384 4h 33m 4s + //Attempt 1 0 0s + //Attempt 2 1 1s + //Attempt 3 3 3s + //Attempt 4 7 7s + //Attempt 5 15 15s + //Attempt 6 31 31s + //Attempt 7 63 1m 3s + //Attempt 8 127 2m 7s + //Attempt 9 255 4m 15s + //Attempt 10 511 8m 31s + //Attempt 11 1023 17m 3s + //Attempt 12 2047 34m 7s + //Attempt 13 4095 1h 8m 15s + //Attempt 14 8191 2h 16m 31s + //Attempt 15 16383 4h 33m 3s double SCALE = 1.0D; // Scale the exponential backoff - double delayInMs = ((Math.pow(2.0D, failedAttempts) - 1d) / 2.0D) * 1000 * SCALE; + double delayInMs = (Math.pow(2.0D, failedAttempts) - 1.0D) * 1000.D * SCALE; // Just use maximum back-off value. We are not afraid of many threads using this value // to trigger at once. diff --git a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java index 5995ae7beb..0f87375628 100644 --- a/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java +++ b/realm/realm-library/src/syncIntegrationTest/java/io/realm/SyncedRealmIntegrationTests.java @@ -554,4 +554,29 @@ public void onChange(Progress progress) { } }); } + + // Smoke test to check that `refreshConnections` doesn't crash. + // Testing that it actually works is not feasible in a unit test. + @Test + @RunTestInLooperThread + public void refreshConnections() { + RealmLog.setLevel(LogLevel.DEBUG); + SyncManager.refreshConnections(); // No Realms + + // A single active Realm + String username = UUID.randomUUID().toString(); + String password = "password"; + SyncUser user = SyncUser.logIn(SyncCredentials.usernamePassword(username, password, true), Constants.AUTH_URL); + final SyncConfiguration config = configurationFactory.createSyncConfigurationBuilder(user, Constants.USER_REALM) + .fullSynchronization() + .schema(StringOnly.class) + .build(); + Realm realm = Realm.getInstance(config); + SyncManager.refreshConnections(); + + // A single logged out Realm + realm.close(); + SyncManager.refreshConnections(); + looperThread.testComplete(); + } } diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java index 8a7c228a5c..19101582fa 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/SyncTestUtils.java @@ -94,8 +94,11 @@ private static void deleteRosFiles() throws IOException { private static void deleteFile(File file) throws IOException { if (file.isDirectory()) { - for (File c : file.listFiles()) { - deleteFile(c); + File[] files = file.listFiles(); + if (files != null) { + for (File c : files) { + deleteFile(c); + } } } if (!file.delete()) { diff --git a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java index d39ae9dd24..504bffd43b 100644 --- a/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java +++ b/realm/realm-library/src/syncTestUtils/java/io/realm/objectserver/utils/Constants.java @@ -18,7 +18,7 @@ public class Constants { - public static String HOST = "127.0.0.1"; + public static final String HOST = "127.0.0.1"; public static final String USER_REALM = "realm://" + HOST + ":9080/~/tests"; public static final String USER_REALM_2 = "realm://" + HOST + ":9080/~/tests2"; public static final String GLOBAL_REALM = "realm://" + HOST + ":9080/tests"; diff --git a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java index 2c3fdeedd9..a1772a2f4d 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java +++ b/realm/realm-library/src/testUtils/java/io/realm/TestHelper.java @@ -47,6 +47,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import io.realm.entities.AllTypesPrimaryKey; import io.realm.entities.AnnotationIndexTypes; import io.realm.entities.BacklinksSource; @@ -146,12 +149,7 @@ public static long addRowWithValues(Table table, long[] columnKeys, Object[] val colTypes[i] = colType; if (!colType.isValid(value)) { // String representation of the provided value type. - String providedType; - if (value == null) { - providedType = "null"; - } else { - providedType = value.getClass().toString(); - } + String providedType = value.getClass().toString(); throw new IllegalArgumentException("Invalid argument no " + (i + 1) + ". Expected a value compatible with column type " + colType + ", but got " + providedType + "."); @@ -275,7 +273,7 @@ public interface AdditionalTableSetup { void execute(Table table); } - public static Table createTable(OsSharedRealm sharedRealm, String name, AdditionalTableSetup additionalSetup) { + public static Table createTable(OsSharedRealm sharedRealm, String name, @Nullable AdditionalTableSetup additionalSetup) { boolean wasInTransaction = sharedRealm.isInTransaction(); if (!wasInTransaction) { sharedRealm.beginTransaction(); @@ -465,7 +463,7 @@ public static RealmConfiguration createConfiguration(File folder, String name) { * @deprecated Use {@link TestRealmConfigurationFactory#createConfiguration(String, byte[])} instead. */ @Deprecated - public static RealmConfiguration createConfiguration(Context context, String name, byte[] key) { + public static RealmConfiguration createConfiguration(Context context, String name, @Nullable byte[] key) { return createConfiguration(context.getFilesDir(), name, key); } @@ -473,7 +471,7 @@ public static RealmConfiguration createConfiguration(Context context, String nam * @deprecated Use {@link TestRealmConfigurationFactory#createConfiguration(String, byte[])} instead. */ @Deprecated - public static RealmConfiguration createConfiguration(File dir, String name, byte[] key) { + public static RealmConfiguration createConfiguration(File dir, String name, @Nullable byte[] key) { RealmConfiguration.Builder config = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext()) .directory(dir) .name(name); @@ -1177,9 +1175,13 @@ public static void deleteRecursively(File file) { if (!file.exists()) { return; } + if (file.isDirectory()) { - for (File f : file.listFiles()) { - deleteRecursively(f); + File[] files = file.listFiles(); + for (File f : files) { + if (f != null) { + deleteRecursively(f); + } } } @@ -1202,7 +1204,12 @@ public static boolean isSelinuxEnforcing() { final BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8)); //noinspection TryFinallyCanBeTryWithResources try { - return reader.readLine().toLowerCase(Locale.ENGLISH).equals("enforcing"); + String line = reader.readLine(); + if (line != null) { + return line.toLowerCase(Locale.ENGLISH).equals("enforcing"); + } else { + return false; + } } finally { try { reader.close(); @@ -1299,4 +1306,10 @@ public static void waitForNetworkThreadExecutorToFinish() { } } + // Workaround to cheat Kotlins type system when testing interop with Java + @SuppressWarnings("TypeParameterUnusedInFormals") + public static T getNull() { + return null; + } + } diff --git a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java index 94662ecaae..0ef2c21952 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java +++ b/realm/realm-library/src/testUtils/java/io/realm/rule/TestRealmConfigurationFactory.java @@ -99,8 +99,12 @@ protected void after() { public void create() throws IOException { super.create(); tempFolder = new File(super.getRoot(), testName); - tempFolder.delete(); - tempFolder.mkdir(); + if (tempFolder.exists() && !tempFolder.delete()) { + throw new IllegalStateException("Could not delete folder: " + tempFolder.getAbsolutePath()); + } + if (!tempFolder.mkdir()) { + throw new IllegalStateException("Could not create folder: " + tempFolder.getAbsolutePath()); + } } @Override diff --git a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java index 57b0deec15..fa4c6acb56 100644 --- a/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java +++ b/realm/realm-library/src/testUtils/java/io/realm/services/RemoteTestService.java @@ -43,9 +43,9 @@ public abstract class RemoteTestService extends Service { // There is no easy way to dynamically ensure step IDs have same value for different processes. So, use the stupid // way. - private static int BASE_MSG_ID = 0; - protected static int BASE_SIMPLE_COMMIT = BASE_MSG_ID; - protected static int BASE_A_LOT_COMMITS = BASE_SIMPLE_COMMIT + 100; + private static final int BASE_MSG_ID = 0; + protected static final int BASE_SIMPLE_COMMIT = BASE_MSG_ID; + protected static final int BASE_A_LOT_COMMITS = BASE_SIMPLE_COMMIT + 100; public static abstract class Step { public final int message; @@ -81,7 +81,7 @@ private void response(String error) { public static final String BUNDLE_KEY_ERROR = "error"; @SuppressLint("UseSparseArrays") private static Map stepMap = new HashMap(); - public static RemoteTestService thiz; + static RemoteTestService thiz; private final Messenger messenger = new Messenger(new IncomingHandler()); private Messenger client; private File rootFolder; @@ -104,10 +104,12 @@ public void onCreate() { } catch (IOException e) { RealmLog.error(e); } - //noinspection ResultOfMethodCallIgnored - rootFolder.delete(); - //noinspection ResultOfMethodCallIgnored - rootFolder.mkdir(); + if (rootFolder.exists() && !rootFolder.delete()) { + throw new IllegalStateException("Could not delete folder: " + rootFolder.getAbsolutePath()); + } + if (!rootFolder.mkdir()) { + throw new IllegalStateException("Could not create folder: " + rootFolder.getAbsolutePath()); + } Realm.init(getApplicationContext()); } @@ -167,8 +169,10 @@ private void recursiveDelete(File file) { recursiveDelete(each); } } - //noinspection ResultOfMethodCallIgnored - file.delete(); + + if (!file.delete()) { + throw new IllegalStateException("Could not delete file: " + file.getAbsolutePath()); + } } public Realm getRealm() {