diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e7d9b10cbbc2210e2db04ff498f4665902f7e5b..f5008b878a490f09bb33f14a0008f935fee63404 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,16 +2,23 @@ version: 2.1 orbs: android: circleci/android@0.2.1 sonarcloud: sonarsource/sonarcloud@1.0.2 + +####################### +# Commands section +# For code reuse. +####################### commands: install-ndk: android/install-ndk restore-android-build-cache: android/restore-build-cache save-android-build-cache: android/save-build-cache scan-sonar: sonarcloud/scan + restore-gradle-cache: description: "Restore gradle caches" steps: - restore_cache: key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} + save-gradle-cache: description: "Save gradle caches" steps: @@ -19,17 +26,7 @@ commands: paths: - ~/.gradle key: jars-{{ checksum "build.gradle" }}-{{ checksum "Corona-Warn-App/build.gradle" }}-{{ checksum "Server-Protocol-Buffer/build.gradle" }} - require-version-bump: - description: "Require version bump for binary assembling" - steps: - - run: - name: "Check if assemble required" - command: | - last_commit=$(git log -1 --pretty=%B) - if [[ $last_commit != *"Version bump"* ]]; then - circleci-agent step halt - echo "Skipping job" - fi + run-gradle-cmd: description: "Running gradle command with environment options" parameters: @@ -41,10 +38,19 @@ commands: steps: - run: name: << parameters.desc >> - command: ./gradlew << parameters.cmd >> + command: > + ./gradlew -PdisablePreDex + << parameters.cmd >> + no_output_timeout: 30m environment: - JVM_OPTS: -Xmx2048m - GRADLE_OPTS: -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError -Dorg.gradle.caching=true -Dorg.gradle.configureondemand=true -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false + JVM_OPTS: -Xmx4096m + GRADLE_OPTS: > + -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError + -Dorg.gradle.caching=true + -Dorg.gradle.configureondemand=true + -Dkotlin.compiler.execution.strategy=in-process + -Dkotlin.incremental=false + run-gradle-cmd-test-splitting: description: "Running gradle command with environment options and test splitting" parameters: @@ -59,13 +65,135 @@ commands: command: circleci tests glob "**/test*/**/*.kt" | circleci tests split | xargs -n 1 echo - run: name: << parameters.desc >> - command: ./gradlew << parameters.cmd >> -i -PtestFilter="`circleci tests glob "**/test*/**/*.kt" | circleci tests split`" + command: > + ./gradlew -PdisablePreDex + << parameters.cmd >> + -i -PtestFilter="`circleci tests glob "**/test*/**/*.kt" | circleci tests split`" environment: - JVM_OPTS: -Xmx2048m - GRADLE_OPTS: -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError -Dorg.gradle.caching=true -Dorg.gradle.configureondemand=true -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false + JVM_OPTS: -Xmx4096m + GRADLE_OPTS: > + -Xmx1536m -XX:+HeapDumpOnOutOfMemoryError + -Dorg.gradle.caching=true + -Dorg.gradle.configureondemand=true + -Dkotlin.compiler.execution.strategy=in-process + -Dkotlin.incremental=false + + skip-for-external-pull-requests: + description: "Skip for external pull requests due to missing access to secrets." + steps: + - run: + name: Early return if this build is from a forked PR + command: | + if [ -n "$CIRCLE_PR_NUMBER" ]; then + echo "Nothing to do for forked PRs, so marking this step successful" + circleci step halt + fi + + setup-android-macos: + description: "Setup Android environment on macOS executor" + steps: + - restore_cache: + key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }} + - run: + name: Set ANDROID_SDK_ROOT environment variable + command: echo 'export ANDROID_SDK_ROOT=$HOME/android-sdk' >> $BASH_ENV + - run: + name: Install Android SDK + command: | + sh .circleci/install-android-sdk.sh + echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH' >> $BASH_ENV + echo 'export PATH=$ANDROID_SDK_ROOT/cmdline-tools/latest:$PATH' >> $BASH_ENV + echo 'export PATH=$ANDROID_SDK_ROOT/platform-tools:$PATH' >> $BASH_ENV + echo 'export PATH=$ANDROID_SDK_ROOT/emulator:$PATH' >> $BASH_ENV + echo 'export PATH=$ANDROID_SDK_ROOT/build-tools/29.0.3:$PATH' >> $BASH_ENV + source $BASH_ENV + sdkmanager --list + - save_cache: + key: android-sdk-v1-{{ arch }}-{{ checksum ".circleci/install-android-sdk.sh" }} + paths: + - /Users/distiller/android-sdk + + run-emulator-for-api: + parameters: + apilevel: + type: integer + description: "Setup and start emulator for API<< parameters.apilevel >>" + steps: + - run: + name: Create emulator AVD + command: > + echo "no" | avdmanager --verbose create avd --force + --name "emulator_API<< parameters.apilevel >>" + --package "system-images;android-<< parameters.apilevel >>;google_apis;x86_64" + - run: + name: Configure emulator settings + command: | + cd $HOME/.android/avd/emulator_API<< parameters.apilevel >>.avd + sed -i '' -e 's/hw.lcd.density=[0-9]*/hw.lcd.density=560/g' config.ini + sed -i '' -e 's/hw.lcd.height=[0-9]*/hw.lcd.height=2880/g' config.ini + sed -i '' -e 's/hw.lcd.width=[0-9]*/hw.lcd.width=1440/g' config.ini + sed -i '' -e 's/hw.ramSize=[0-9]*/hw.ramSize=1536/g' config.ini + - run: + name: Check host state + command: | + emulator -accel-check + emulator -version + - run: + name: Start emulator AVD + command: > + emulator @emulator_API<< parameters.apilevel >> + -no-window + -no-audio + -no-boot-anim + -memory 2048 + -nojni + background: true + no_output_timeout: 60m + - run: + name: Wait for emulator + command: | + adb wait-for-device shell 'while [[ -z $(getprop dev.bootcomplete) ]]; do sleep 1; done;' + adb devices + sleep 5 + - run: + name: Disable animations + command: | + adb shell settings put global window_animation_scale 0 + adb shell settings put global transition_animation_scale 0 + adb shell settings put global animator_duration_scale 0 + + kill-all-emulators: + description: "Kill all emulators" + steps: + - run: + name: Kill all emulators + command: | + adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done + sleep 2 + adb kill-server + sleep 2 + + compress-path: + parameters: + input: + type: string + output: + type: string + description: "Compress << parameters.input >> to << parameters.output >>" + steps: + - run: + name: Compress files + command: > + zip -r + << parameters.output >> + << parameters.input >> +####################### +# Jobs section +# Tasks that get executed +####################### jobs: - quick_build_device_release_no_tests: + detekt: executor: android/android resource_class: large working_directory: ~/project @@ -73,7 +201,21 @@ jobs: - checkout - restore-gradle-cache - restore-android-build-cache - - require-version-bump + - run-gradle-cmd: + desc: Detekt check + cmd: ":Corona-Warn-App:detekt" + - store_artifacts: + path: Corona-Warn-App/build/reports + destination: reports + + quick_build_device_release_no_tests: + executor: android/android + resource_class: xlarge + working_directory: ~/project + steps: + - checkout + - restore-gradle-cache + - restore-android-build-cache - run-gradle-cmd: desc: Quick Build cmd: "assembleDeviceRelease" @@ -82,15 +224,15 @@ jobs: - store_artifacts: path: Corona-Warn-App/build/reports destination: reports + quick_build_device_for_testers_release_no_tests: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project steps: - checkout - restore-gradle-cache - restore-android-build-cache - - require-version-bump - run-gradle-cmd: desc: Quick Build cmd: ":Corona-Warn-App:assembleDeviceForTestersRelease" @@ -99,9 +241,10 @@ jobs: - store_artifacts: path: Corona-Warn-App/build/reports destination: reports - device_release_unit_tests: + + unit_tests_device_release: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project parallelism: 3 steps: @@ -113,18 +256,22 @@ jobs: cmd: ":Corona-Warn-App:testDeviceReleaseUnitTest -i" - save-gradle-cache - save-android-build-cache - - store_artifacts: - path: Corona-Warn-App/build/reports - destination: reports - store_test_results: path: Corona-Warn-App/build/test-results - persist_to_workspace: root: /home/circleci paths: - ./project - device_for_testers_release_unit_tests: + - compress-path: + input: ./Corona-Warn-App/build/reports + output: /tmp/unit_tests_device_release.zip + - store_artifacts: + path: /tmp/unit_tests_device_release.zip + destination: zips/unit_tests_device_release.zip + + unit_tests_device_for_testers_release: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project parallelism: 3 steps: @@ -136,14 +283,19 @@ jobs: cmd: ":Corona-Warn-App:testDeviceForTestersReleaseUnitTest" - save-gradle-cache - save-android-build-cache - - store_artifacts: - path: Corona-Warn-App/build/reports - destination: reports - store_test_results: path: Corona-Warn-App/build/test-results + - compress-path: + input: ./Corona-Warn-App/build/reports + output: /tmp/unit_tests_device_for_testers_release.zip + - store_artifacts: + path: /tmp/unit_tests_device_for_testers_release.zip + destination: zips/unit_tests_device_for_testers_release.zip + + lint_device_release_check: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project steps: - checkout @@ -155,23 +307,10 @@ jobs: - store_artifacts: path: Corona-Warn-App/build/reports destination: reports - ktlint_device_release_check: - executor: android/android - resource_class: medium - working_directory: ~/project - steps: - - checkout - - restore-gradle-cache - - restore-android-build-cache - - run-gradle-cmd: - desc: Ktlint check deviceRelease - cmd: ":Corona-Warn-App:ktlintDeviceReleaseCheck" - - store_artifacts: - path: Corona-Warn-App/build/reports - destination: reports + lint_device_for_testers_release_check: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project steps: - checkout @@ -183,7 +322,8 @@ jobs: - store_artifacts: path: Corona-Warn-App/build/reports destination: reports - ktlint_device_for_testers_release_check: + + ktlint_device_release_check: executor: android/android resource_class: medium working_directory: ~/project @@ -192,12 +332,13 @@ jobs: - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: - desc: Ktlint check deviceForTestersRelease - cmd: ":Corona-Warn-App:ktlintDeviceForTestersReleaseCheck" + desc: Ktlint check deviceRelease + cmd: ":Corona-Warn-App:ktlintDeviceReleaseCheck" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports - detekt: + + ktlint_device_for_testers_release_check: executor: android/android resource_class: medium working_directory: ~/project @@ -206,16 +347,18 @@ jobs: - restore-gradle-cache - restore-android-build-cache - run-gradle-cmd: - desc: Detekt check - cmd: ":Corona-Warn-App:detekt" + desc: Ktlint check deviceForTestersRelease + cmd: ":Corona-Warn-App:ktlintDeviceForTestersReleaseCheck" - store_artifacts: path: Corona-Warn-App/build/reports destination: reports + run_sonar: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project steps: + - skip-for-external-pull-requests - attach_workspace: at: /home/circleci - restore-gradle-cache @@ -224,9 +367,10 @@ jobs: desc: JaCoCo report cmd: ":Corona-Warn-App:jacocoTestReportDeviceRelease -i" - scan-sonar + quick_build_device_for_testers_signed: executor: android/android - resource_class: large + resource_class: xlarge working_directory: ~/project steps: - checkout @@ -276,22 +420,101 @@ jobs: curl --location --request POST $tsystems_upload_url \ --header "Authorization: Bearer $tsystems_upload_bearer" \ --form "file=@${fileName}" \ + + instrumentation_tests_device: + macos: + xcode: "12.3.0" + resource_class: large + steps: + - checkout + - setup-android-macos + - run-gradle-cmd: + desc: Build Apks for instrumentation test + cmd: > + :Corona-Warn-App:assembleDeviceDebug + :Corona-Warn-App:assembleDeviceDebugAndroidTest + - run-emulator-for-api: + apilevel: 29 + - run-gradle-cmd: + desc: Run instrumentation tests on device + cmd: > + :Corona-Warn-App:connectedDeviceDebugAndroidTest + --info --continue + -Pandroid.testInstrumentationRunnerArguments.notAnnotation=testhelpers.Screenshot + -Pandroid.testInstrumentationRunnerArguments.clearPackageData=true + - kill-all-emulators + - store_test_results: + path: Corona-Warn-App/build/outputs/androidTest-results + - compress-path: + input: ./Corona-Warn-App/build/reports + output: /tmp/instrumentation_tests_device.zip + - store_artifacts: + path: /tmp/instrumentation_tests_device.zip + destination: zips/instrumentation_tests_device.zip + + device_screenshots: + macos: + xcode: "12.3.0" + resource_class: large + steps: + - checkout + - restore_cache: + key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }} + - run: bundle check || bundle install --path vendor/bundle + - save_cache: + key: gem-cache-v1-{{ arch }}-{{ checksum "Gemfile.lock" }} + paths: + - vendor/bundle + - setup-android-macos + - run-gradle-cmd: + desc: Build APKs for screenshots + cmd: > + :Corona-Warn-App:assembleDebug + :Corona-Warn-App:assembleAndroidTest + - run-emulator-for-api: + apilevel: 29 + - run: + name: Run fastlane screengrab + command: | + for i in {1..5}; do bundle exec fastlane screengrab && break || sleep 15; done + no_output_timeout: 30m + - kill-all-emulators + - store_artifacts: + path: fastlane/metadata/android + destination: screenshots + - compress-path: + input: ./fastlane/metadata/android + output: /tmp/device_screenshots.zip + - store_artifacts: + path: /tmp/device_screenshots.zip + destination: zips/device_screenshots.zip + +####################### +# Workflow section +# Job execution orders +####################### workflows: version: 2 - quick_build: + check_buildtype_device: jobs: - - quick_build_device_release_no_tests - - quick_build_device_for_testers_release_no_tests - - device_release_unit_tests - - device_for_testers_release_unit_tests + - detekt - lint_device_release_check - - lint_device_for_testers_release_check - ktlint_device_release_check - - ktlint_device_for_testers_release_check - - detekt + - quick_build_device_release_no_tests + - unit_tests_device_release - run_sonar: requires: - - device_release_unit_tests + - unit_tests_device_release + - instrumentation_tests_device + + check_buildtype_device_for_testers: + jobs: + - detekt + - lint_device_for_testers_release_check + - ktlint_device_for_testers_release_check + - quick_build_device_for_testers_release_no_tests + - unit_tests_device_for_testers_release + signed_build: jobs: - quick_build_device_for_testers_signed: @@ -301,3 +524,6 @@ workflows: - /^v.*/ branches: ignore: /.*/ + - device_screenshots: + requires: + - quick_build_device_for_testers_signed \ No newline at end of file diff --git a/.circleci/install-android-sdk.sh b/.circleci/install-android-sdk.sh new file mode 100644 index 0000000000000000000000000000000000000000..fc79de5a31dc8b7add7729e6ee24dd44a0328b4e --- /dev/null +++ b/.circleci/install-android-sdk.sh @@ -0,0 +1,29 @@ +if [ -d $ANDROID_SDK_ROOT ] +then + echo "Directory $ANDROID_SDK_ROOT already exists so we're skipping the install. If you'd like to install fresh tools, edit this script to invalidate the CI cache..." + exit 0 +fi + +mkdir -p $ANDROID_SDK_ROOT +cd $ANDROID_SDK_ROOT +curl https://dl.google.com/android/repository/commandlinetools-mac-6858069_latest.zip -o cmdline-tools.zip + +unzip cmdline-tools.zip -d $ANDROID_SDK_ROOT/cmdline-tools +mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest + +mkdir -p "$ANDROID_SDK_ROOT/licenses" + +echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_SDK_ROOT/licenses/android-sdk-license" +echo "84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_SDK_ROOT/licenses/android-sdk-preview-license" +echo "d975f751698a77b662f1254ddbeed3901e976f5a" > "$ANDROID_SDK_ROOT/licenses/intel-android-extra-license" + +SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + +$SDKMANAGER "platform-tools" +$SDKMANAGER "platforms;android-29" +$SDKMANAGER "build-tools;29.0.3" +$SDKMANAGER "ndk-bundle" +$SDKMANAGER "system-images;android-29;google_apis;x86_64" +$SDKMANAGER "emulator" + +echo "y" | sudo $SDKMANAGER --install "ndk;21.2.6472646" --sdk_root=${ANDROID_SDK_ROOT} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71358b8a592ff73b6228ba865bc47a4f2a729215..0eb1ef447ed64c495b50fd45a097b692724f0d92 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ /test_environments.json *.jks /keystore.properties +.local/* fastlane/metadata diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 81c5fa348604877b1342e01047824d5587104f79..199d127a55d9684d74d99e7d1f167b47699fbf6d 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -196,6 +196,12 @@ android { includeAndroidResources = true returnDefaultValues = true } + + // Using orchestration toegether with mockk on x86 (32bit) emulator images crashes + // Leaving this in here as reminder + // https://github.com/android/android-test/issues/352 + // https://github.com/mockk/mockk/issues/466 + // execution 'ANDROIDX_TEST_ORCHESTRATOR' } kapt { @@ -317,9 +323,11 @@ dependencies { implementation "com.google.dagger:dagger-android:$dagger_version" implementation "com.google.dagger:dagger-android-support:$dagger_version" kapt "com.google.dagger:dagger-compiler:$dagger_version" - kapt "com.google.dagger:dagger-android-processor:$dagger_version" kaptTest "com.google.dagger:dagger-compiler:$dagger_version" kaptAndroidTest "com.google.dagger:dagger-compiler:$dagger_version" + kapt "com.google.dagger:dagger-android-processor:$dagger_version" + kaptTest "com.google.dagger:dagger-android-processor:$dagger_version" + kaptAndroidTest "com.google.dagger:dagger-android-processor:$dagger_version" def assisted_injection_version = "0.6.0" compileOnly "com.squareup.inject:assisted-inject-annotations-dagger2:$assisted_injection_version" diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt index 767da20af5015aaaa451a2c27e9fda0033fe4c7a..910abff2693ecc7e8a709d9b9d2fe60101b09484 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/DiaryData.kt @@ -14,22 +14,26 @@ object DiaryData { val LIST_ITEMS = listOf( ListItem.Data( R.drawable.ic_contact_diary_person_item, - "Max Mustermann" + "Max Mustermann", + ListItem.Type.PERSON ), ListItem.Data( R.drawable.ic_contact_diary_person_item, - "Erika Mustermann" + "Erika Mustermann", + ListItem.Type.PERSON ), ListItem.Data( R.drawable.ic_contact_diary_location, - "Fitnessstudio" + "Fitnessstudio", + ListItem.Type.LOCATION ), ListItem.Data( R.drawable.ic_contact_diary_location, - "Supermarket" + "Supermarket", + ListItem.Type.LOCATION ) ) diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/util/security/DBPasswordTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/util/security/DBPasswordTest.kt index 7d63879062b7de71dfc19b743aca4c3be1ceba73..0858f69d10ab54a797c310fd2871e79ac5f1f671 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/util/security/DBPasswordTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/util/security/DBPasswordTest.kt @@ -2,13 +2,26 @@ package de.rki.coronawarnapp.util.security import android.content.Context import androidx.test.core.app.ApplicationProvider +import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.storage.AppDatabase import de.rki.coronawarnapp.storage.tracing.TracingIntervalEntity +import de.rki.coronawarnapp.storage.tracing.TracingIntervalRepository +import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.di.ApplicationComponent import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockkObject import kotlinx.coroutines.runBlocking import net.sqlcipher.database.SQLiteException import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.not +import org.junit.After import org.junit.Assert.assertThat import org.junit.Assert.assertTrue import org.junit.Before @@ -19,6 +32,11 @@ import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class DBPasswordTest { + @MockK lateinit var applicationComponent: ApplicationComponent + @MockK lateinit var encryptedSharedPreferencesFactory: EncryptedPreferencesFactory + @MockK lateinit var errorResetTool: EncryptionErrorResetTool + @MockK lateinit var keyCacheRepository: KeyCacheRepository + private val appContext: Context get() = ApplicationProvider.getApplicationContext() @@ -27,10 +45,29 @@ class DBPasswordTest { @Before fun setUp() { + MockKAnnotations.init(this) + mockkObject(AppInjector) + every { AppInjector.component } returns applicationComponent + + encryptedSharedPreferencesFactory = EncryptedPreferencesFactory(appContext) + every { applicationComponent.encryptedPreferencesFactory } returns encryptedSharedPreferencesFactory + every { applicationComponent.errorResetTool } returns errorResetTool + every { applicationComponent.keyCacheRepository } returns keyCacheRepository.apply { + coEvery { keyCacheRepository.clear() } just Runs + } + + mockkObject(TracingIntervalRepository) + every { TracingIntervalRepository.resetInstance() } just Runs + clearSharedPreferences() AppDatabase.reset(appContext) } + @After + fun teardown() { + clearAllMocks() + } + @Test fun generatesPassphraseInCorrectLength() { val passphrase = SecurityHelper.getDBPassword()