From 0cad87869df92eec55ee9efe39a8a1e018a1433d Mon Sep 17 00:00:00 2001 From: Albert Ho <82767499+albho@users.noreply.github.com> Date: Fri, 29 Nov 2024 13:43:24 -0800 Subject: [PATCH] Migrate mobile testing to BrowserStack (#877) --- ...appcenter.yml => android-browserstack.yml} | 76 ++- .github/workflows/android-demos.yml | 8 +- ...ios-appcenter.yml => ios-browserstack.yml} | 54 +- .github/workflows/ios-demos.yml | 9 - resources/.lint/spell-check/dict.txt | 1 + script/automation/browserstack.py | 136 +++++ sdk/android/PicovoiceTestApp/build.gradle | 6 +- .../PicovoiceTestApp/gradle.properties | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../picovoice-test-app/build.gradle | 17 +- .../picovoice-test-app/proguard-rules.pro | 6 +- .../picovoice/picovoice/testapp/BaseTest.java | 18 - .../picovoice/testapp/IntegrationTest.java | 11 - .../picovoice/testapp/LanguageTests.java | 140 +++++ .../picovoice/testapp/PicovoiceTest.java | 493 ------------------ .../picovoice/testapp/StandardTests.java | 359 +++++++++++++ .../src/main/AndroidManifest.xml | 3 +- .../PicovoiceAppTest/copy_test_resources.sh | 3 - 18 files changed, 736 insertions(+), 611 deletions(-) rename .github/workflows/{android-appcenter.yml => android-browserstack.yml} (61%) rename .github/workflows/{ios-appcenter.yml => ios-browserstack.yml} (52%) create mode 100644 script/automation/browserstack.py create mode 100644 sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/LanguageTests.java delete mode 100644 sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/PicovoiceTest.java create mode 100644 sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/StandardTests.java diff --git a/.github/workflows/android-appcenter.yml b/.github/workflows/android-browserstack.yml similarity index 61% rename from .github/workflows/android-appcenter.yml rename to .github/workflows/android-browserstack.yml index ad4e75404..604ceeafb 100644 --- a/.github/workflows/android-appcenter.yml +++ b/.github/workflows/android-browserstack.yml @@ -1,18 +1,18 @@ -name: Android AppCenter Tests +name: Android BrowserStack Tests on: workflow_dispatch: push: branches: [ master ] paths: - - '.github/workflows/android-appcenter.yml' + - '.github/workflows/android-browserstack.yml' - 'sdk/android/PicovoiceTestApp/**' - 'resources/.test/**' - 'resources/audio_samples/**' pull_request: branches: [ master, 'v[0-9]+.[0-9]+' ] paths: - - '.github/workflows/android-appcenter.yml' + - '.github/workflows/android-browserstack.yml' - 'sdk/android/PicovoiceTestApp/**' - 'resources/.test/**' - 'resources/audio_samples/**' @@ -23,7 +23,7 @@ defaults: jobs: build: - name: Run Android Tests on AppCenter + name: Run Android Tests on BrowserStack runs-on: ubuntu-latest steps: @@ -32,18 +32,17 @@ jobs: with: submodules: recursive - - name: Set up Node.js LTS - uses: actions/setup-node@v3 + - name: Installing Python + uses: actions/setup-python@v5 with: - node-version: lts/* + python-version: '3.10' + - run: + pip3 install requests - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Copy test_resources @@ -71,18 +70,18 @@ jobs: - name: Build androidTest run: ./gradlew assembleEnDebugAndroidTest - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Picovoice-Android-Activity" - --devices "Picovoice/android-min-max" - --app-path picovoice-test-app/build/outputs/apk/en/debug/picovoice-test-app-en-debug.apk - --test-series "porcupine-android" - --locale "en_US" - --build-dir picovoice-test-app/build/outputs/apk/androidTest/en/debug + - name: Run tests on BrowserStack + run: python3 ../../../script/automation/browserstack.py + --type espresso + --username "${{secrets.BROWSERSTACK_USERNAME}}" + --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" + --project_name "Picovoice-Android" + --devices "android-min-max" + --app_path "picovoice-test-app/build/outputs/apk/en/debug/picovoice-test-app-en-debug.apk" + --test_path "picovoice-test-app/build/outputs/apk/androidTest/en/debug/picovoice-test-app-en-debug-androidTest.apk" build-integ: - name: Run Android Integration Tests on AppCenter + name: Run Android Integration Tests on BrowserStack runs-on: ubuntu-latest steps: @@ -91,18 +90,17 @@ jobs: with: submodules: recursive - - name: Set up Node.js LTS - uses: actions/setup-node@v3 + - name: Installing Python + uses: actions/setup-python@v5 with: - node-version: lts/* - - - name: Install AppCenter CLI - run: npm install -g appcenter-cli + python-version: '3.10' + - run: + pip3 install requests - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Copy test_resources @@ -130,12 +128,12 @@ jobs: - name: Build androidTest run: ./gradlew assembleEnReleaseAndroidTest -DtestBuildType=integ - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Picovoice-Android-Activity" - --devices "Picovoice/android-min-max" - --app-path picovoice-test-app/build/outputs/apk/en/release/picovoice-test-app-en-release.apk - --test-series "picovoice-android" - --locale "en_US" - --build-dir picovoice-test-app/build/outputs/apk/androidTest/en/release + - name: Run tests on BrowserStack + run: python3 ../../../script/automation/browserstack.py + --type espresso + --username "${{secrets.BROWSERSTACK_USERNAME}}" + --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" + --project_name "Picovoice-Android-Integration" + --devices "android-min-max" + --app_path "picovoice-test-app/build/outputs/apk/en/release/picovoice-test-app-en-release.apk" + --test_path "picovoice-test-app/build/outputs/apk/androidTest/en/release/picovoice-test-app-en-release-androidTest.apk" diff --git a/.github/workflows/android-demos.yml b/.github/workflows/android-demos.yml index 0479a91b3..612370b80 100644 --- a/.github/workflows/android-demos.yml +++ b/.github/workflows/android-demos.yml @@ -27,10 +27,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Build English @@ -48,10 +48,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Build diff --git a/.github/workflows/ios-appcenter.yml b/.github/workflows/ios-browserstack.yml similarity index 52% rename from .github/workflows/ios-appcenter.yml rename to .github/workflows/ios-browserstack.yml index fa99703c7..7d77b6341 100644 --- a/.github/workflows/ios-appcenter.yml +++ b/.github/workflows/ios-browserstack.yml @@ -1,11 +1,11 @@ -name: iOS AppCenter Tests +name: iOS BrowserStack Tests on: workflow_dispatch: push: branches: [ master ] paths: - - '.github/workflows/ios-appcenter.yml' + - '.github/workflows/ios-browserstack.yml' - 'sdk/ios/PicovoiceAppTest/**' - 'resources/audio_samples/**' - 'resources/porcupine' @@ -14,7 +14,7 @@ on: pull_request: branches: [ master, 'v[0-9]+.[0-9]+' ] paths: - - '.github/workflows/ios-appcenter.yml' + - '.github/workflows/ios-browserstack.yml' - 'sdk/ios/PicovoiceAppTest/**' - 'resources/audio_samples/**' - 'resources/porcupine' @@ -27,7 +27,7 @@ defaults: jobs: build: - name: Run iOS Tests on AppCenter + name: Run iOS Tests on BrowserStack runs-on: macos-latest steps: @@ -36,32 +36,26 @@ jobs: with: submodules: recursive - - name: Set up Node.js LTS - uses: actions/setup-node@v3 + - name: Installing Python + uses: actions/setup-python@v5 with: - node-version: lts/* + python-version: '3.10' + - run: + pip3 install requests - name: Install Cocoapods run: gem install cocoapods - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - name: Make build dir run: mkdir ddp - - name: Install resource script dependency - run: | - brew update - brew install convmv - - name: Copy test_resources run: ./copy_test_resources.sh - name: Run Cocoapods run: pod install - - name: Inject AppID + - name: Inject AccessKey run: sed -i '.bak' 's:{TESTING_ACCESS_KEY_HERE}:${{secrets.PV_VALID_ACCESS_KEY}}:' PicovoiceAppTestUITests/BaseTest.swift @@ -74,11 +68,23 @@ jobs: -derivedDataPath ddp CODE_SIGNING_ALLOWED=NO - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Picovoice-iOS" - --devices "Picovoice/ios-min-max" - --test-series "picovoice-ios" - --locale "en_US" - --build-dir ddp/Build/Products/Debug-iphoneos + - name: Generating ipa + run: cd ddp/Build/Products/Debug-iphoneos/ && + mkdir Payload && + cp -r PicovoiceAppTest.app Payload && + zip --symlinks -r PicovoiceAppTest.ipa Payload && + rm -r Payload + + - name: Zipping Tests + run: cd ddp/Build/Products/Debug-iphoneos/ && + zip --symlinks -r PicovoiceAppTestUITests.zip PicovoiceAppTestUITests-Runner.app + + - name: Run tests on BrowserStack + run: python3 ../../../script/automation/browserstack.py + --type xcuitest + --username "${{secrets.BROWSERSTACK_USERNAME}}" + --access_key "${{secrets.BROWSERSTACK_ACCESS_KEY}}" + --project_name "Picovoice-iOS" + --devices "ios-min-max" + --app_path "ddp/Build/Products/Debug-iphoneos/PicovoiceAppTest.ipa" + --test_path "ddp/Build/Products/Debug-iphoneos/PicovoiceAppTestUITests.zip" diff --git a/.github/workflows/ios-demos.yml b/.github/workflows/ios-demos.yml index 65133601b..35bc4e6fd 100644 --- a/.github/workflows/ios-demos.yml +++ b/.github/workflows/ios-demos.yml @@ -34,9 +34,6 @@ jobs: - name: Install Cocoapods run: gem install cocoapods - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - name: Make build dir run: mkdir ddp @@ -81,9 +78,6 @@ jobs: - name: Install Cocoapods run: gem install cocoapods - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - name: Make build dir run: mkdir ddp @@ -119,9 +113,6 @@ jobs: - name: Install Cocoapods run: gem install cocoapods - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - name: Make build dir run: mkdir ddp diff --git a/resources/.lint/spell-check/dict.txt b/resources/.lint/spell-check/dict.txt index 428a05b26..f222a7689 100644 --- a/resources/.lint/spell-check/dict.txt +++ b/resources/.lint/spell-check/dict.txt @@ -304,6 +304,7 @@ webvp weiß Wohnzimmer xcframework +xcuitest xcworkspace xcodeproj xcshareddata diff --git a/script/automation/browserstack.py b/script/automation/browserstack.py new file mode 100644 index 000000000..54c0e77a0 --- /dev/null +++ b/script/automation/browserstack.py @@ -0,0 +1,136 @@ +import argparse +import requests +import time + +APP_URI = 'https://api-cloud.browserstack.com/app-automate/{}/v2/app' +TEST_URI = 'https://api-cloud.browserstack.com/app-automate/{}/v2/test-suite' +BUILD_URI = 'https://api-cloud.browserstack.com/app-automate/{}/v2/build' +STATUS_URI = 'https://api-cloud.browserstack.com/app-automate/{}/v2/builds/{}' + +devices_dict = { + 'android-min-max': [ + 'Samsung Galaxy S8-7.0', + 'Samsung Galaxy M52-11.0', + 'Google Pixel 9-15.0' + ], + 'android-perf': [ + 'Google Pixel 6 Pro-15.0' + ], + 'ios-min-max': [ + 'iPhone SE 2022-15', + 'iPhone 14 Plus-16', + 'iPhone 14-18' + ], + 'ios-perf': [ + 'iPhone 13-18', + ] +} + + +def main(args: argparse.Namespace) -> None: + app_files = { + 'file': open(args.app_path, 'rb') + } + + app_response = requests.post( + APP_URI.format(args.type), + files=app_files, + auth=(args.username, args.access_key) + ) + app_response_json = app_response.json() + + if not app_response.ok: + print('App Upload Failed', app_response_json) + exit(1) + + test_files = { + 'file': open(args.test_path, 'rb') + } + test_response = requests.post( + TEST_URI.format(args.type), + files=test_files, + auth=(args.username, args.access_key) + ) + test_response_json = test_response.json() + + if not test_response.ok: + print('Test Upload Failed', test_response_json) + exit(1) + + build_headers = { + 'Content-Type': 'application/json' + } + build_data = { + 'app': app_response_json['app_url'], + 'testSuite': test_response_json['test_suite_url'], + 'project': args.project_name, + 'devices': devices_dict[args.devices], + 'deviceLogs': True + } + + while True: + build_response = requests.post( + BUILD_URI.format(args.type), + headers=build_headers, + json=build_data, + auth=(args.username, args.access_key) + ) + if (build_response is not None and 'message' in build_response.json() and '[BROWSERSTACK_ALL_PARALLELS_IN_USE]' + in build_response.json()['message']): + print('Parallel threads limit reached. Waiting...', flush=True) + time.sleep(60) + else: + break + + if build_response is None: + print('Build Failed') + exit(1) + + build_response_json = build_response.json() + + if not build_response.ok: + print('Build Failed', build_response.json()) + exit(1) + + if build_response_json['message'] != 'Success': + print('Build Unsuccessful') + exit(1) + + print( + 'View build results at https://app-automate.browserstack.com/dashboard/v2/builds/{}' + .format(build_response_json['build_id'])) + + while True: + time.sleep(60) + status_response = requests.get( + STATUS_URI.format(args.type, build_response_json['build_id']), + auth=(args.username, args.access_key) + ) + status_response_json = status_response.json() + status = status_response_json['status'] + + if not status_response.ok: + print('Status Request Failed', status_response_json) + exit(1) + + if status != 'queued' and status != 'running': + break + + print('Status:', status) + if status != 'passed': + exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--type', choices=['espresso', 'xcuitest'], required=True) + parser.add_argument('--username', required=True) + parser.add_argument('--access_key', required=True) + + parser.add_argument('--project_name', required=True) + parser.add_argument('--devices', choices=devices_dict.keys(), required=True) + parser.add_argument('--app_path', required=True) + parser.add_argument('--test_path', required=True) + args = parser.parse_args() + + main(args) diff --git a/sdk/android/PicovoiceTestApp/build.gradle b/sdk/android/PicovoiceTestApp/build.gradle index b82865ec6..9967371fc 100644 --- a/sdk/android/PicovoiceTestApp/build.gradle +++ b/sdk/android/PicovoiceTestApp/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. ext { - defaultTargetSdkVersion = 31 + defaultTargetSdkVersion = 33 } buildscript { @@ -9,7 +9,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:8.2.2' } } @@ -20,6 +20,6 @@ allprojects { } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } diff --git a/sdk/android/PicovoiceTestApp/gradle.properties b/sdk/android/PicovoiceTestApp/gradle.properties index c52ac9b79..091e018b3 100644 --- a/sdk/android/PicovoiceTestApp/gradle.properties +++ b/sdk/android/PicovoiceTestApp/gradle.properties @@ -16,4 +16,7 @@ org.gradle.jvmargs=-Xmx2048m # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/sdk/android/PicovoiceTestApp/gradle/wrapper/gradle-wrapper.properties b/sdk/android/PicovoiceTestApp/gradle/wrapper/gradle-wrapper.properties index f3b860b96..e43611f06 100644 --- a/sdk/android/PicovoiceTestApp/gradle/wrapper/gradle-wrapper.properties +++ b/sdk/android/PicovoiceTestApp/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/build.gradle b/sdk/android/PicovoiceTestApp/picovoice-test-app/build.gradle index 10b598dc3..2f1ca86a3 100644 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/build.gradle +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/build.gradle @@ -24,7 +24,7 @@ if (rootProject.file("local.properties").exists()) { } android { - compileSdkVersion defaultTargetSdkVersion + compileSdk defaultTargetSdkVersion defaultConfig { applicationId "ai.picovoice.picovoice.testapp" @@ -130,12 +130,14 @@ android { } } } + namespace 'ai.picovoice.picovoice.testapp' } dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.google.code.gson:gson:2.10' + implementation 'com.google.errorprone:error_prone_annotations:2.36.0' implementation 'ai.picovoice:picovoice-android:3.0.1' // Espresso UI Testing @@ -143,7 +145,6 @@ dependencies { androidTestImplementation('androidx.test.espresso:espresso-core:3.2.0', { exclude group: 'com.android.support', module: 'support-annotations' }) - androidTestImplementation('com.microsoft.appcenter:espresso-test-extension:1.4') androidTestImplementation('androidx.test.espresso:espresso-intents:3.5.1') } @@ -165,5 +166,17 @@ afterEvaluate { tasks."merge${flavor.name.capitalize()}DebugAssets".dependsOn "${flavor.name}CopyAudio" tasks."merge${flavor.name.capitalize()}ReleaseAssets".dependsOn "${flavor.name}CopyAudio" + + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyAudio" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyContext" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyRhinoParams" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyPorcupineParams" + tasks."generate${flavor.name.capitalize()}ReleaseLintVitalReportModel".dependsOn "${flavor.name}CopyWakeword" + + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyAudio" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyContext" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyRhinoParams" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyPorcupineParams" + tasks."lintVitalAnalyze${flavor.name.capitalize()}Release".dependsOn "${flavor.name}CopyWakeword" } } diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/proguard-rules.pro b/sdk/android/PicovoiceTestApp/picovoice-test-app/proguard-rules.pro index 158caf35e..280693dab 100644 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/proguard-rules.pro +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/proguard-rules.pro @@ -20,4 +20,8 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class com.google.** { *; } --keep class com.microsoft.** { *; } \ No newline at end of file +-keep class com.microsoft.** { *; } + +-dontwarn com.google.errorprone.annotations.CheckReturnValue +-dontwarn com.google.errorprone.annotations.MustBeClosed +-dontwarn javax.lang.model.element.Modifier \ No newline at end of file diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/BaseTest.java b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/BaseTest.java index 5ee88674d..323f9288f 100644 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/BaseTest.java +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/BaseTest.java @@ -7,12 +7,7 @@ import androidx.test.platform.app.InstrumentationRegistry; -import com.microsoft.appcenter.espresso.Factory; -import com.microsoft.appcenter.espresso.ReportHelper; - -import org.junit.After; import org.junit.Before; -import org.junit.Rule; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -33,9 +28,6 @@ public class BaseTest { - @Rule - public ReportHelper reportHelper = Factory.getReportHelper(); - boolean isWakeWordDetected = false; PicovoiceWakeWordCallback wakeWordCallback = new PicovoiceWakeWordCallback() { @Override @@ -59,16 +51,6 @@ public void invoke(RhinoInference inference) { String testResourcesPath; String accessKey; - @After - public void TearDown() { - isWakeWordDetected = false; - inferenceResult = null; - if (picovoice != null) { - picovoice.delete(); - } - reportHelper.label("Stopping App"); - } - @Before public void Setup() throws IOException { testContext = InstrumentationRegistry.getInstrumentation().getContext(); diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/IntegrationTest.java b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/IntegrationTest.java index 261ea3d2b..d4317c9dd 100644 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/IntegrationTest.java +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/IntegrationTest.java @@ -16,9 +16,6 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.microsoft.appcenter.espresso.Factory; -import com.microsoft.appcenter.espresso.ReportHelper; - import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; @@ -74,9 +71,6 @@ public void perform(UiController uiController, View view) { @RunWith(AndroidJUnit4.class) public class IntegrationTest { - @Rule - public ReportHelper reportHelper = Factory.getReportHelper(); - @Rule public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class); @@ -91,11 +85,6 @@ public void intentsTeardown() { Intents.release(); } - @After - public void TearDown() { - reportHelper.label("Stopping App"); - } - @Test public void testPicovoice() { onView(withId(R.id.testButton)).perform(click()); diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/LanguageTests.java b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/LanguageTests.java new file mode 100644 index 000000000..9dd1290ce --- /dev/null +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/LanguageTests.java @@ -0,0 +1,140 @@ +package ai.picovoice.picovoice.testapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import ai.picovoice.picovoice.Picovoice; + +@RunWith(Parameterized.class) +public class LanguageTests extends BaseTest { + + @Parameterized.Parameter(value = 0) + public String porcupineModelFile; + + @Parameterized.Parameter(value = 1) + public String rhinoModelFile; + + @Parameterized.Parameter(value = 2) + public String keywordFile; + + @Parameterized.Parameter(value = 3) + public String contextFile; + + @Parameterized.Parameter(value = 4) + public String testAudioFile; + + @Parameterized.Parameter(value = 5) + public String expectedIntent; + + @Parameterized.Parameter(value = 6) + public Map expectedSlots; + + @Parameterized.Parameters(name = "{4}") + public static Collection initParameters() throws IOException { + String testDataJsonString = getTestDataString(); + + JsonObject testDataJson = JsonParser.parseString(testDataJsonString).getAsJsonObject(); + JsonArray testParametersJson = testDataJson.getAsJsonObject("tests").getAsJsonArray("parameters"); + + List parameters = new ArrayList<>(); + for (int i = 0; i < testParametersJson.size(); i++) { + JsonObject testData = testParametersJson.get(i).getAsJsonObject(); + String language = testData.get("language").getAsString(); + String wakeword = testData.get("wakeword").getAsString(); + String contextName = testData.get("context_name").getAsString(); + String audioFilename = testData.get("audio_file").getAsString(); + JsonObject inferenceJson = testData.getAsJsonObject("inference"); + + String porcupineModelFile = String.format("porcupine_model_files/porcupine_params_%s.pv", language); + String rhinoModelFile = String.format("rhino_model_files/rhino_params_%s.pv", language); + String keywordFile = String.format("keyword_files/%s/%s_android.ppn", language, wakeword); + String contextFile = String.format("context_files/%s/%s_android.rhn", language, contextName); + String audioFile = String.format("audio_samples/%s", audioFilename); + + String intent = inferenceJson.get("intent").getAsString(); + HashMap slots = new HashMap(); + for (Map.Entry entry : inferenceJson.getAsJsonObject("slots").asMap().entrySet()) { + slots.put(entry.getKey(), entry.getValue().getAsString()); + } + + if (Objects.equals(language, "en")) { + porcupineModelFile = "porcupine_model_files/porcupine_params.pv"; + rhinoModelFile = "rhino_model_files/rhino_params.pv"; + } + + parameters.add(new Object[] { + porcupineModelFile, + rhinoModelFile, + keywordFile, + contextFile, + audioFile, + intent, + slots, + }); + } + + return parameters; + } + + @Test + public void testProcess() throws Exception { + + String porcupineModelPath = new File(testResourcesPath, porcupineModelFile).getAbsolutePath(); + String rhinoModelPath = new File(testResourcesPath, rhinoModelFile).getAbsolutePath(); + String keywordPath = new File(testResourcesPath, keywordFile).getAbsolutePath(); + String contextPath = new File(testResourcesPath, contextFile).getAbsolutePath(); + + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setPorcupineModelPath(porcupineModelPath) + .setRhinoModelPath(rhinoModelPath) + .setKeywordPath(keywordPath) + .setContextPath(contextPath) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + File testAudio = new File(testResourcesPath, testAudioFile); + + processTestAudio(picovoice, testAudio); + Thread.sleep(500); + + assertTrue(isWakeWordDetected); + assertNotNull(inferenceResult); + assertTrue(inferenceResult.getIsUnderstood()); + assertEquals(expectedIntent, inferenceResult.getIntent()); + assertEquals(expectedSlots, inferenceResult.getSlots()); + + isWakeWordDetected = false; + inferenceResult = null; + + // test again + processTestAudio(picovoice, testAudio); + Thread.sleep(500); + + assertTrue(isWakeWordDetected); + assertNotNull(inferenceResult); + assertTrue(inferenceResult.getIsUnderstood()); + assertEquals(expectedIntent, inferenceResult.getIntent()); + assertEquals(expectedSlots, inferenceResult.getSlots()); + } +} diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/PicovoiceTest.java b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/PicovoiceTest.java deleted file mode 100644 index 5e85c7614..000000000 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/PicovoiceTest.java +++ /dev/null @@ -1,493 +0,0 @@ -package ai.picovoice.picovoice.testapp; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import ai.picovoice.picovoice.Picovoice; -import ai.picovoice.picovoice.PicovoiceException; - -@RunWith(Enclosed.class) -public class PicovoiceTest { - - public static class StandardTests extends BaseTest { - - @Test - public void testInitSuccessSimple() throws PicovoiceException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - assertTrue(picovoice.getVersion() != null && !picovoice.getVersion().equals("")); - assertTrue(picovoice.getFrameLength() > 0); - assertTrue(picovoice.getSampleRate() > 0); - assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); - } - - @Test - public void testInitSuccessCustomModelPaths() throws PicovoiceException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - File porcupineModelPath = new File(testResourcesPath, "porcupine_model_files/porcupine_params.pv"); - File rhinoModelPath = new File(testResourcesPath, "rhino_model_files/rhino_params.pv"); - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) - .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); - } - - @Test - public void testInitSuccessCustomSensitivities() throws PicovoiceException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setPorcupineSensitivity(0.7f) - .setRhinoSensitivity(0.35f) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); - } - - @Test - public void testInitSuccessCustomEndpointSettings() throws PicovoiceException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setEndpointDurationSec(3.0f) - .setRequireEndpoint(false) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); - } - - @Test - public void testInitFailWithMismatchedPorcupineLanguage() { - File keywordPath = new File(testResourcesPath, "keyword_files/fr/framboise_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithMismatchedRhinoLanguage() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/de/beleuchtung_android.rhn"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidKeywordPath() { - File keywordPath = new File(testResourcesPath, "bad_path/bad_path.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidContextPath() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "bad_path/bad_path.rhn"); - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidPorcupineModelPath() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - File porcupineModelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidRhinoModelPath() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - File rhinoModelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidPorcupineSensitivity() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setPorcupineSensitivity(10) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidRhinoSensitivity() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setRhinoSensitivity(-1) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithWrongPorcupinePlatform() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/alexa_linux.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithWrongRhinoPlatform() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_linux.rhn"); - - boolean didFail = false; - try { - new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - } catch (PicovoiceException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitWithNonAsciiModelName() throws PicovoiceException { - File keywordPath = new File(testResourcesPath, "keyword_files/es/murciélago_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/es/iluminación_inteligente_android.rhn"); - File porcupineModelPath = new File(testResourcesPath, "porcupine_model_files/porcupine_params_es.pv"); - File rhinoModelPath = new File(testResourcesPath, "rhino_model_files/rhino_params_es.pv"); - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) - .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); - } - - @Test - public void testReset() throws Exception { - File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); - File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); - - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setContextPath(contextPath.getAbsolutePath()) - .setWakeWordCallback(() -> { - try { - isWakeWordDetected = true; - picovoice.reset(); - } catch (PicovoiceException e) { - assertNull(e); - } - }) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - File testAudio = new File(testResourcesPath, "audio_samples/picovoice-coffee.wav"); - - inferenceResult = null; - processTestAudio(picovoice, testAudio); - Thread.sleep(500); - - assertTrue(isWakeWordDetected); - assertNull(inferenceResult); - } - } - - @RunWith(Parameterized.class) - public static class LanguageTests extends BaseTest { - - @Parameterized.Parameter(value = 0) - public String porcupineModelFile; - - @Parameterized.Parameter(value = 1) - public String rhinoModelFile; - - @Parameterized.Parameter(value = 2) - public String keywordFile; - - @Parameterized.Parameter(value = 3) - public String contextFile; - - @Parameterized.Parameter(value = 4) - public String testAudioFile; - - @Parameterized.Parameter(value = 5) - public String expectedIntent; - - @Parameterized.Parameter(value = 6) - public Map expectedSlots; - - @Parameterized.Parameters(name = "{4}") - public static Collection initParameters() throws IOException { - String testDataJsonString = getTestDataString(); - - JsonObject testDataJson = JsonParser.parseString(testDataJsonString).getAsJsonObject(); - JsonArray testParametersJson = testDataJson.getAsJsonObject("tests").getAsJsonArray("parameters"); - - List parameters = new ArrayList<>(); - for (int i = 0; i < testParametersJson.size(); i++) { - JsonObject testData = testParametersJson.get(i).getAsJsonObject(); - String language = testData.get("language").getAsString(); - String wakeword = testData.get("wakeword").getAsString(); - String contextName = testData.get("context_name").getAsString(); - String audioFilename = testData.get("audio_file").getAsString(); - JsonObject inferenceJson = testData.getAsJsonObject("inference"); - - String porcupineModelFile = String.format("porcupine_model_files/porcupine_params_%s.pv", language); - String rhinoModelFile = String.format("rhino_model_files/rhino_params_%s.pv", language); - String keywordFile = String.format("keyword_files/%s/%s_android.ppn", language, wakeword); - String contextFile = String.format("context_files/%s/%s_android.rhn", language, contextName); - String audioFile = String.format("audio_samples/%s", audioFilename); - - String intent = inferenceJson.get("intent").getAsString(); - HashMap slots = new HashMap(); - for (Map.Entry entry : inferenceJson.getAsJsonObject("slots").asMap().entrySet()) { - slots.put(entry.getKey(), entry.getValue().getAsString()); - } - - if (Objects.equals(language, "en")) { - porcupineModelFile = "porcupine_model_files/porcupine_params.pv"; - rhinoModelFile = "rhino_model_files/rhino_params.pv"; - } - - parameters.add(new Object[] { - porcupineModelFile, - rhinoModelFile, - keywordFile, - contextFile, - audioFile, - intent, - slots, - }); - } - - return parameters; - } - - @Test - public void testProcess() throws Exception { - - String porcupineModelPath = new File(testResourcesPath, porcupineModelFile).getAbsolutePath(); - String rhinoModelPath = new File(testResourcesPath, rhinoModelFile).getAbsolutePath(); - String keywordPath = new File(testResourcesPath, keywordFile).getAbsolutePath(); - String contextPath = new File(testResourcesPath, contextFile).getAbsolutePath(); - - picovoice = new Picovoice.Builder() - .setAccessKey(accessKey) - .setPorcupineModelPath(porcupineModelPath) - .setRhinoModelPath(rhinoModelPath) - .setKeywordPath(keywordPath) - .setContextPath(contextPath) - .setWakeWordCallback(wakeWordCallback) - .setInferenceCallback(inferenceCallback) - .build(appContext); - - File testAudio = new File(testResourcesPath, testAudioFile); - - processTestAudio(picovoice, testAudio); - Thread.sleep(500); - - assertTrue(isWakeWordDetected); - assertNotNull(inferenceResult); - assertTrue(inferenceResult.getIsUnderstood()); - assertEquals(expectedIntent, inferenceResult.getIntent()); - assertEquals(expectedSlots, inferenceResult.getSlots()); - - isWakeWordDetected = false; - inferenceResult = null; - - // test again - processTestAudio(picovoice, testAudio); - Thread.sleep(500); - - assertTrue(isWakeWordDetected); - assertNotNull(inferenceResult); - assertTrue(inferenceResult.getIsUnderstood()); - assertEquals(expectedIntent, inferenceResult.getIntent()); - assertEquals(expectedSlots, inferenceResult.getSlots()); - } - } -} diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/StandardTests.java b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/StandardTests.java new file mode 100644 index 000000000..2669cebd3 --- /dev/null +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/androidTest/java/ai/picovoice/picovoice/testapp/StandardTests.java @@ -0,0 +1,359 @@ +package ai.picovoice.picovoice.testapp; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +import ai.picovoice.picovoice.Picovoice; +import ai.picovoice.picovoice.PicovoiceException; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +@RunWith(AndroidJUnit4.class) +public class StandardTests extends BaseTest { + @Test + public void testInitSuccessSimple() throws PicovoiceException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + assertTrue(picovoice.getVersion() != null && !picovoice.getVersion().equals("")); + assertTrue(picovoice.getFrameLength() > 0); + assertTrue(picovoice.getSampleRate() > 0); + assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); + } + + @Test + public void testInitSuccessCustomModelPaths() throws PicovoiceException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + File porcupineModelPath = new File(testResourcesPath, "porcupine_model_files/porcupine_params.pv"); + File rhinoModelPath = new File(testResourcesPath, "rhino_model_files/rhino_params.pv"); + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) + .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); + } + + @Test + public void testInitSuccessCustomSensitivities() throws PicovoiceException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setPorcupineSensitivity(0.7f) + .setRhinoSensitivity(0.35f) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); + } + + @Test + public void testInitSuccessCustomEndpointSettings() throws PicovoiceException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setEndpointDurationSec(3.0f) + .setRequireEndpoint(false) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); + } + + @Test + public void testInitFailWithMismatchedPorcupineLanguage() { + File keywordPath = new File(testResourcesPath, "keyword_files/fr/framboise_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithMismatchedRhinoLanguage() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/de/beleuchtung_android.rhn"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidKeywordPath() { + File keywordPath = new File(testResourcesPath, "bad_path/bad_path.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidContextPath() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "bad_path/bad_path.rhn"); + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidPorcupineModelPath() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + File porcupineModelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidRhinoModelPath() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + File rhinoModelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidPorcupineSensitivity() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setPorcupineSensitivity(10) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidRhinoSensitivity() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setRhinoSensitivity(-1) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithWrongPorcupinePlatform() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/alexa_linux.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithWrongRhinoPlatform() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_linux.rhn"); + + boolean didFail = false; + try { + new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + } catch (PicovoiceException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitWithNonAsciiModelName() throws PicovoiceException { + File keywordPath = new File(testResourcesPath, "keyword_files/es/murciélago_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/es/iluminación_inteligente_android.rhn"); + File porcupineModelPath = new File(testResourcesPath, "porcupine_model_files/porcupine_params_es.pv"); + File rhinoModelPath = new File(testResourcesPath, "rhino_model_files/rhino_params_es.pv"); + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setPorcupineModelPath(porcupineModelPath.getAbsolutePath()) + .setRhinoModelPath(rhinoModelPath.getAbsolutePath()) + .setWakeWordCallback(wakeWordCallback) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + assertTrue(picovoice.getContextInformation() != null && !picovoice.getContextInformation().equals("")); + } + + @Test + public void testReset() throws Exception { + File keywordPath = new File(testResourcesPath, "keyword_files/en/picovoice_android.ppn"); + File contextPath = new File(testResourcesPath, "context_files/en/coffee_maker_android.rhn"); + + picovoice = new Picovoice.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setContextPath(contextPath.getAbsolutePath()) + .setWakeWordCallback(() -> { + try { + isWakeWordDetected = true; + picovoice.reset(); + } catch (PicovoiceException e) { + assertNull(e); + } + }) + .setInferenceCallback(inferenceCallback) + .build(appContext); + + File testAudio = new File(testResourcesPath, "audio_samples/picovoice-coffee.wav"); + + inferenceResult = null; + processTestAudio(picovoice, testAudio); + Thread.sleep(500); + + assertTrue(isWakeWordDetected); + assertNull(inferenceResult); + } +} diff --git a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/main/AndroidManifest.xml b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/main/AndroidManifest.xml index 45c0f2f96..41d2b1dad 100644 --- a/sdk/android/PicovoiceTestApp/picovoice-test-app/src/main/AndroidManifest.xml +++ b/sdk/android/PicovoiceTestApp/picovoice-test-app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/sdk/ios/PicovoiceAppTest/copy_test_resources.sh b/sdk/ios/PicovoiceAppTest/copy_test_resources.sh index d1a2247fa..f495e3987 100755 --- a/sdk/ios/PicovoiceAppTest/copy_test_resources.sh +++ b/sdk/ios/PicovoiceAppTest/copy_test_resources.sh @@ -50,6 +50,3 @@ cp ${RHINO_LIB_DIR}/common/*.pv ${ASSETS_DIR}/model_files echo "Copying test data file..." cp ${PICOVOICE_RESOURCE_DIR}/.test/test_data.json ${ASSETS_DIR} - -echo "Fixing filename encodings for Appcenter compatibility" -convmv --notest -f utf8 -t utf8 --nfd -r ${ASSETS_DIR}