diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 0000000000..dc81115ce9 --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,88 @@ +local docker_base = 'registry.oxen.rocks/lokinet-ci-'; + +// Log a bunch of version information to make it easier for debugging +local version_info = { + name: 'Version Information', + image: docker_base + 'android', + commands: [ + 'cmake --version', + 'apt --installed list' + ] +}; + + +// Intentionally doing a depth of 2 as libSession-util has it's own submodules (and libLokinet likely will as well) +local clone_submodules = { + name: 'Clone Submodules', + image: 'drone/git', + commands: ['git fetch --tags', 'git submodule update --init --recursive --depth=2 --jobs=4'] +}; + +// cmake options for static deps mirror +local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https://oxen.rocks/deps ' else ''); + +[ + // Unit tests (PRs only) + { + kind: 'pipeline', + type: 'docker', + name: 'Unit Tests', + platform: { arch: 'amd64' }, + trigger: { event: { exclude: [ 'push' ] } }, + steps: [ + version_info, + clone_submodules, + { + name: 'Run Unit Tests', + image: docker_base + 'android', + pull: 'always', + environment: { ANDROID_HOME: '/usr/lib/android-sdk' }, + commands: [ + 'apt-get install -y ninja-build', + './gradlew testPlayDebugUnitTestCoverageReport' + ], + } + ], + }, + // Validate build artifact was created by the direct branch push (PRs only) + { + kind: 'pipeline', + type: 'docker', + name: 'Check Build Artifact Existence', + platform: { arch: 'amd64' }, + trigger: { event: { exclude: [ 'push' ] } }, + steps: [ + { + name: 'Poll for build artifact existence', + image: docker_base + 'android', + pull: 'always', + commands: [ + './scripts/drone-upload-exists.sh' + ] + } + ] + }, + // Debug APK build (non-PRs only) + { + kind: 'pipeline', + type: 'docker', + name: 'Debug APK Build', + platform: { arch: 'amd64' }, + trigger: { event: { exclude: [ 'pull_request' ] } }, + steps: [ + version_info, + clone_submodules, + { + name: 'Build and upload', + image: docker_base + 'android', + pull: 'always', + environment: { SSH_KEY: { from_secret: 'SSH_KEY' }, ANDROID_HOME: '/usr/lib/android-sdk' }, + commands: [ + 'apt-get install -y ninja-build', + './gradlew assemblePlayDebug', + './scripts/drone-static-upload.sh' + ], + } + ], + } +] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 023fc81010..be928b3933 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,7 @@ ffpr *.sh pkcs11.password app/play -app/huawei \ No newline at end of file +app/huawei + +!/scripts/drone-static-upload.sh +!/scripts/drone-upload-exists.sh \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 70ed8efa38..5002756607 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,6 +124,7 @@ android { debug { isDefault true minifyEnabled false + enableUnitTestCoverage true } } @@ -201,6 +202,27 @@ android { } } } + + task testPlayDebugUnitTestCoverageReport(type: JacocoReport, dependsOn: "testPlayDebugUnitTest") { + reports { + xml.enabled = true + } + + // Add files that should not be listed in the report (e.g. generated Files from dagger) + def fileFilter = [] + def mainSrc = "$projectDir/src/main/java" + def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/playDebug", excludes: fileFilter) + + // Compiled Kotlin class files are written into build-variant-specific subdirectories of 'build/tmp/kotlin-classes'. + classDirectories.from = files([kotlinDebugTree]) + + // To produce an accurate report, the bytecode is mapped back to the original source code. + sourceDirectories.from = files([mainSrc]) + + // Execution data generated when running the tests against classes instrumented by the JaCoCo agent. + // This is enabled with 'enableUnitTestCoverage' in the 'debug' build type. + executionData.from = "${project.buildDir}/outputs/unit_test_code_coverage/playDebugUnitTest/testPlayDebugUnitTest.exec" + } } dependencies { diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt index aecbd7e4a8..b89c62b6d5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModelTest.kt @@ -10,6 +10,7 @@ import org.hamcrest.CoreMatchers.nullValue import org.hamcrest.MatcherAssert.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mockito import org.mockito.Mockito.anyLong import org.mockito.Mockito.anySet import org.mockito.Mockito.verify @@ -49,7 +50,8 @@ class ConversationViewModelTest: BaseViewModelTest() { viewModel.saveDraft(draft) - verify(repository).saveDraft(threadId, draft) + // The above is an async process to wait 100ms to give it a chance to complete + verify(repository, Mockito.timeout(100).times(1)).saveDraft(threadId, draft) } @Test diff --git a/libsession-util/build.gradle b/libsession-util/build.gradle index 935227185f..f368f15eea 100644 --- a/libsession-util/build.gradle +++ b/libsession-util/build.gradle @@ -26,7 +26,7 @@ android { externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" - version "3.22.1" + version "3.22.1+" } } compileOptions { diff --git a/scripts/drone-static-upload.sh b/scripts/drone-static-upload.sh new file mode 100755 index 0000000000..b5c9ee83f7 --- /dev/null +++ b/scripts/drone-static-upload.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +# Script used with Drone CI to upload build artifacts (because specifying all this in +# .drone.jsonnet is too painful). + +set -o errexit + +if [ -z "$SSH_KEY" ]; then + echo -e "\n\n\n\e[31;1mUnable to upload artifact: SSH_KEY not set\e[0m" + # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds + exit 0 +fi + +echo "$SSH_KEY" >ssh_key + +set -o xtrace # Don't start tracing until *after* we write the ssh key + +chmod 600 ssh_key + +# Define the output paths +build_dir="app/build/outputs/apk/play/debug" +target_path="${build_dir}/$(ls ${build_dir} | grep -o 'session-[^[:space:]]*-universal.apk')" + +# Validate the paths exist +if [ ! -d $build_path ]; then + echo -e "\n\n\n\e[31;1mExpected a file to upload, found none\e[0m" >&2 + exit 1 +fi + +if [ -n "$DRONE_TAG" ]; then + # For a tag build use something like `session-android-v1.2.3-universal` + base="session-android-$DRONE_TAG-universal" +else + # Otherwise build a length name from the datetime and commit hash, such as: + # session-android-20200522T212342Z-04d7dcc54-universal + base="session-android-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}-universal" +fi + +# Copy over the build products +mkdir -vp "$base" +cp -av $target_path "$base" + +# tar dat shiz up yo +archive="$base.tar.xz" +tar cJvf "$archive" "$base" + +upload_to="oxen.rocks/${DRONE_REPO// /_}/${DRONE_BRANCH// /_}" + +# sftp doesn't have any equivalent to mkdir -p, so we have to split the above up into a chain of +# -mkdir a/, -mkdir a/b/, -mkdir a/b/c/, ... commands. The leading `-` allows the command to fail +# without error. +upload_dirs=(${upload_to//\// }) +put_debug= +mkdirs= +dir_tmp="" +for p in "${upload_dirs[@]}"; do + dir_tmp="$dir_tmp$p/" + mkdirs="$mkdirs +-mkdir $dir_tmp" +done + +sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@oxen.rocks <