diff --git a/.github/workflows/android-appcenter.yml b/.github/workflows/android-browserstack.yml similarity index 59% rename from .github/workflows/android-appcenter.yml rename to .github/workflows/android-browserstack.yml index 41dd327066..aa422776bd 100644 --- a/.github/workflows/android-appcenter.yml +++ b/.github/workflows/android-browserstack.yml @@ -1,46 +1,45 @@ -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' - 'binding/android/PorcupineTestApp/**' - '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' - 'binding/android/PorcupineTestApp/**' - 'resources/.test/**' - 'resources/audio_samples/**' defaults: run: - working-directory: binding/android/PorcupineTestApp + working-directory: binding/android/PorcupineTestApp/ jobs: build: - name: Run Android Tests on AppCenter + name: Run Android Tests on BrowserStack runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - 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 @@ -68,35 +67,34 @@ jobs: - name: Build androidTest run: ./gradlew assembleEnDebugAndroidTest - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Porcupine-Android" - --devices "Picovoice/android-min-max" - --app-path porcupine-test-app/build/outputs/apk/en/debug/porcupine-test-app-en-debug.apk - --test-series "porcupine-android" - --locale "en_US" - --build-dir porcupine-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 "Porcupine-Android" + --devices "android-min-max" + --app_path "porcupine-test-app/build/outputs/apk/en/debug/porcupine-test-app-en-debug.apk" + --test_path "porcupine-test-app/build/outputs/apk/androidTest/en/debug/porcupine-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: - uses: actions/checkout@v3 - - 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 @@ -124,12 +122,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/Porcupine-Android" - --devices "Picovoice/android-min-max" - --app-path porcupine-test-app/build/outputs/apk/en/release/porcupine-test-app-en-release.apk - --test-series "porcupine-android" - --locale "en_US" - --build-dir porcupine-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 "Porcupine-Android-Integration" + --devices "android-min-max" + --app_path "porcupine-test-app/build/outputs/apk/en/release/porcupine-test-app-en-release.apk" + --test_path "porcupine-test-app/build/outputs/apk/androidTest/en/release/porcupine-test-app-en-release-androidTest.apk" diff --git a/.github/workflows/android-perf.yml b/.github/workflows/android-perf.yml index 253e971d3c..fbe8e671b1 100644 --- a/.github/workflows/android-perf.yml +++ b/.github/workflows/android-perf.yml @@ -19,28 +19,25 @@ defaults: jobs: build: - name: Run Android Speed Tests on AppCenter + name: Run Android Speed Tests on BrowserStack runs-on: ubuntu-latest strategy: matrix: - device: [single-android, 32bit-android] + device: [ android-perf ] include: - - device: single-android + - device: android-perf performanceThresholdSec: 0.5 - - device: 32bit-android - performanceThresholdSec: 2.75 steps: - uses: actions/checkout@v3 - - 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 uses: actions/setup-java@v3 @@ -79,12 +76,12 @@ jobs: - name: Build androidTest run: ./gradlew assembleEnDebugAndroidTest -DtestBuildType=perf - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Porcupine-Android" - --devices "Picovoice/${{ matrix.device }}" - --app-path porcupine-test-app/build/outputs/apk/en/debug/porcupine-test-app-en-debug.apk - --test-series "porcupine-android" - --locale "en_US" - --build-dir porcupine-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 "Porcupine-Android-Performance" + --devices "${{ matrix.device }}" + --app_path "porcupine-test-app/build/outputs/apk/en/debug/porcupine-test-app-en-debug.apk" + --test_path "porcupine-test-app/build/outputs/apk/androidTest/en/debug/porcupine-test-app-en-debug-androidTest.apk" diff --git a/.github/workflows/ios-appcenter.yml b/.github/workflows/ios-appcenter.yml deleted file mode 100644 index 3503b7d852..0000000000 --- a/.github/workflows/ios-appcenter.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: iOS AppCenter Tests - -on: - workflow_dispatch: - push: - branches: [ master ] - paths: - - '.github/workflows/ios-appcenter.yml' - - 'binding/ios/PorcupineAppTest/**' - - 'resources/.test/**' - - 'resources/audio_samples/**' - pull_request: - branches: [ master, 'v[0-9]+.[0-9]+' ] - paths: - - '.github/workflows/ios-appcenter.yml' - - 'binding/ios/PorcupineAppTest/**' - - 'resources/.test/**' - - 'resources/audio_samples/**' - -defaults: - run: - working-directory: binding/ios/PorcupineAppTest - -jobs: - build: - name: Run iOS Tests on AppCenter - runs-on: macos-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set up Node.js LTS - uses: actions/setup-node@v3 - with: - node-version: lts/* - - - 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 - run: sed -i '.bak' 's:{TESTING_ACCESS_KEY_HERE}:${{secrets.PV_VALID_ACCESS_KEY}}:' - PorcupineAppTestUITests/BaseTest.swift - - - name: XCode Build - run: xcrun xcodebuild build-for-testing - -configuration Debug - -workspace PorcupineAppTest.xcworkspace - -sdk iphoneos - -scheme PorcupineAppTest - -derivedDataPath ddp - CODE_SIGNING_ALLOWED=NO - - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Porcupine-iOS" - --devices "Picovoice/ios-min-max" - --test-series "porcupine-ios" - --locale "en_US" - --build-dir ddp/Build/Products/Debug-iphoneos diff --git a/.github/workflows/ios-browserstack.yml b/.github/workflows/ios-browserstack.yml new file mode 100644 index 0000000000..a007d223ed --- /dev/null +++ b/.github/workflows/ios-browserstack.yml @@ -0,0 +1,83 @@ +name: iOS BrowserStack Tests + +on: + workflow_dispatch: + push: + branches: [ master ] + paths: + - '.github/workflows/ios-browserstack.yml' + - 'binding/ios/PorcupineAppTest/**' + - 'resources/.test/**' + - 'resources/audio_samples/**' + pull_request: + branches: [ master, 'v[0-9]+.[0-9]+' ] + paths: + - '.github/workflows/ios-browserstack.yml' + - 'binding/ios/PorcupineAppTest/**' + - 'resources/.test/**' + - 'resources/audio_samples/**' + +defaults: + run: + working-directory: binding/ios/PorcupineAppTest + +jobs: + build: + name: Run iOS Tests on BrowserStack + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Installing Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + - run: + pip3 install requests + + - 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: Inject AccessKey + run: sed -i '.bak' 's:{TESTING_ACCESS_KEY_HERE}:${{secrets.PV_VALID_ACCESS_KEY}}:' + PorcupineAppTestUITests/BaseTest.swift + + - name: XCode Build + run: xcrun xcodebuild build-for-testing + -configuration Debug + -project PorcupineAppTest.xcodeproj + -sdk iphoneos + -scheme PorcupineAppTest + -derivedDataPath ddp + CODE_SIGNING_ALLOWED=NO + + - name: Generating ipa + run: cd ddp/Build/Products/Debug-iphoneos/ && + mkdir Payload && + cp -r PorcupineAppTest.app Payload && + zip --symlinks -r PorcupineAppTest.ipa Payload && + rm -r Payload + + - name: Zipping Tests + run: cd ddp/Build/Products/Debug-iphoneos/ && + zip --symlinks -r PorcupineAppTestUITests.zip PorcupineAppTestUITests-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 "Porcupine-iOS" + --devices "ios-min-max" + --app_path "ddp/Build/Products/Debug-iphoneos/PorcupineAppTest.ipa" + --test_path "ddp/Build/Products/Debug-iphoneos/PorcupineAppTestUITests.zip" diff --git a/.github/workflows/ios-perf.yml b/.github/workflows/ios-perf.yml index 2951485813..e7bd0ad795 100644 --- a/.github/workflows/ios-perf.yml +++ b/.github/workflows/ios-perf.yml @@ -23,12 +23,12 @@ defaults: jobs: build: - name: Run iOS Tests on AppCenter + name: Run iOS Tests on BrowserStack runs-on: macos-latest strategy: matrix: - device: [ios-perf] + device: [ ios-perf ] include: - device: ios-perf performanceThresholdSec: 0.5 @@ -37,16 +37,12 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - 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 Cocoapods - run: gem install cocoapods - - - name: Install AppCenter CLI - run: npm install -g appcenter-cli + python-version: '3.10' + - run: + pip3 install requests - name: Make build dir run: mkdir ddp @@ -59,10 +55,7 @@ jobs: - 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}}:' PerformanceTest/PerformanceTest.swift @@ -71,23 +64,35 @@ jobs: PerformanceTest/PerformanceTest.swift - name: Inject Performance Threshold - run: sed -i '.bak' 's:{PERFORMANCE_THRESHOLD_SEC}:${{ matrix.performanceThresholdSec }}:' + run: sed -i '.bak' '1,/{PERFORMANCE_THRESHOLD_SEC}/s/{PERFORMANCE_THRESHOLD_SEC}/${{ matrix.performanceThresholdSec }}/' PerformanceTest/PerformanceTest.swift - name: XCode Build run: xcrun xcodebuild build-for-testing -configuration Debug - -workspace PorcupineAppTest.xcworkspace + -project PorcupineAppTest.xcodeproj -sdk iphoneos -scheme PerformanceTest -derivedDataPath ddp CODE_SIGNING_ALLOWED=NO - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Porcupine-iOS" - --devices "Picovoice/${{ matrix.device }}" - --test-series "porcupine-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 PorcupineAppTest.app Payload && + zip --symlinks -r PorcupineAppTest.ipa Payload && + rm -r Payload + + - name: Zipping Tests + run: cd ddp/Build/Products/Debug-iphoneos/ && + zip --symlinks -r PerformanceTest.zip PerformanceTest-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 "Porcupine-iOS-Performance" + --devices "${{ matrix.device }}" + --app_path "ddp/Build/Products/Debug-iphoneos/PorcupineAppTest.ipa" + --test_path "ddp/Build/Products/Debug-iphoneos/PerformanceTest.zip" diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/build.gradle b/binding/android/PorcupineTestApp/porcupine-test-app/build.gradle index 66b5ce7cf3..a45d2845da 100644 --- a/binding/android/PorcupineTestApp/porcupine-test-app/build.gradle +++ b/binding/android/PorcupineTestApp/porcupine-test-app/build.gradle @@ -114,10 +114,12 @@ android { androidTest { java { if (System.getProperty("testBuildType", "debug") == "perf") { - exclude "**/PorcupineTest.java" + exclude "**/StandardTests.java" + exclude "**/LanguageTests.java" exclude "**/IntegrationTest.java" } else if (System.getProperty("testBuildType", "debug") == "integ") { - exclude "**/PorcupineTest.java" + exclude "**/StandardTests.java" + exclude "**/LanguageTests.java" exclude "**/PerformanceTest.java" } else { exclude "**/IntegrationTest.java" @@ -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') } diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/BaseTest.java b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/BaseTest.java index 197358dcce..8e5aa290ec 100644 --- a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/BaseTest.java +++ b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/BaseTest.java @@ -1,5 +1,5 @@ /* - Copyright 2022 Picovoice Inc. + Copyright 2022-2025 Picovoice Inc. You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" file accompanying this source. @@ -18,12 +18,7 @@ import androidx.annotation.NonNull; 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; @@ -42,20 +37,12 @@ public class BaseTest { - @Rule - public ReportHelper reportHelper = Factory.getReportHelper(); - Context testContext; Context appContext; AssetManager assetManager; String testResourcesPath; String accessKey; - @After - public void TearDown() { - reportHelper.label("Stopping App"); - } - @Before public void Setup() throws IOException { testContext = InstrumentationRegistry.getInstrumentation().getContext(); diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/IntegrationTest.java b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/IntegrationTest.java index 015a75764c..6039d371eb 100644 --- a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/IntegrationTest.java +++ b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/IntegrationTest.java @@ -1,3 +1,15 @@ +/* + Copyright 2025 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + 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 ai.picovoice.porcupine.testapp; import static androidx.test.espresso.Espresso.onView; @@ -16,9 +28,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 +83,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 +97,6 @@ public void intentsTeardown() { Intents.release(); } - @After - public void TearDown() { - reportHelper.label("Stopping App"); - } - @Test public void testPorcupine() { onView(withId(R.id.testButton)).perform(click()); diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/LanguageTests.java b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/LanguageTests.java new file mode 100644 index 0000000000..61eb77187f --- /dev/null +++ b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/LanguageTests.java @@ -0,0 +1,123 @@ +/* + Copyright 2025 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + 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 ai.picovoice.porcupine.testapp; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.google.gson.JsonArray; +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.List; +import java.util.Objects; + +import ai.picovoice.porcupine.Porcupine; + + +@RunWith(Parameterized.class) +public class LanguageTests extends BaseTest { + + @Parameterized.Parameter(value = 0) + public String modelFile; + + @Parameterized.Parameter(value = 1) + public String[] keywordFiles; + + @Parameterized.Parameter(value = 2) + public String testAudioFile; + + @Parameterized.Parameter(value = 3) + public int[] expectedResults; + + @Parameterized.Parameters(name = "{0}") + public static Collection initParameters() throws IOException { + String testDataJsonString = getTestDataString(); + + JsonObject testDataJson = JsonParser.parseString(testDataJsonString).getAsJsonObject(); + JsonArray multipleKeywordDataJson = testDataJson.getAsJsonObject("tests").getAsJsonArray("multipleKeyword"); + + List parameters = new ArrayList<>(); + for (int i = 0; i < multipleKeywordDataJson.size(); i++) { + JsonObject testData = multipleKeywordDataJson.get(i).getAsJsonObject(); + String language = testData.get("language").getAsString(); + JsonArray keywords = testData.getAsJsonArray("wakewords"); + JsonArray groundTruthJson = testData.getAsJsonArray("groundTruth"); + + String modelFile = String.format("model_files/porcupine_params_%s.pv", language); + String[] keywordFiles = new String[keywords.size()]; + for (int j = 0; j < keywords.size(); j++) { + String keyword = keywords.get(j).getAsString(); + keywordFiles[j] = String.format("keyword_files/%s/%s_android.ppn", language, keyword); + } + String audioFile = String.format("audio_samples/multiple_keywords_%s.wav", language); + int[] groundTruth = new int[groundTruthJson.size()]; + for (int j = 0; j < groundTruthJson.size(); j++) { + groundTruth[j] = groundTruthJson.get(j).getAsInt(); + } + + if (Objects.equals(language, "en")) { + modelFile = "model_files/porcupine_params.pv"; + audioFile = "audio_samples/multiple_keywords.wav"; + } + + parameters.add(new Object[] { + modelFile, + keywordFiles, + audioFile, + groundTruth + }); + } + + return parameters; + } + + + @Test + public void testProcess() throws Exception { + String modelPath = new File(testResourcesPath, modelFile).getAbsolutePath(); + String[] keywordPaths = new String[keywordFiles.length]; + for (int i = 0; i < keywordFiles.length; i++) { + keywordPaths[i] = new File(testResourcesPath, keywordFiles[i]).getAbsolutePath(); + + } + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath) + .setKeywordPaths(keywordPaths) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + File testAudio = new File(testResourcesPath, testAudioFile); + ArrayList detectionResults = processTestAudio(p, testAudio); + p.delete(); + + assertSame(expectedResults.length, detectionResults.size()); + for (int i = 0; i < expectedResults.length; i++) { + assertSame(detectionResults.get(i), expectedResults[i]); + } + + p.delete(); + } +} diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/PorcupineTest.java b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/PorcupineTest.java deleted file mode 100644 index 05bde74629..0000000000 --- a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/PorcupineTest.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - Copyright 2022 Picovoice Inc. - - You may not use this file except in compliance with the license. A copy of the license is - located in the "LICENSE" file accompanying this source. - - 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 ai.picovoice.porcupine.testapp; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import com.google.gson.JsonArray; -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.List; -import java.util.Objects; - -import ai.picovoice.porcupine.Porcupine; -import ai.picovoice.porcupine.PorcupineException; - - -@RunWith(Enclosed.class) -public class PorcupineTest { - - public static class StandardTests extends BaseTest { - - @Test - public void testInitSuccessWithSingleBuiltIn() throws PorcupineException { - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - p.delete(); - } - - @Test - public void testInitSuccessWithMultipleBuiltIns() throws PorcupineException { - Porcupine.BuiltInKeyword[] inputBuiltInKeywords = new Porcupine.BuiltInKeyword[]{ - Porcupine.BuiltInKeyword.ALEXA, - Porcupine.BuiltInKeyword.GRASSHOPPER, - Porcupine.BuiltInKeyword.PICOVOICE, - Porcupine.BuiltInKeyword.TERMINATOR - }; - - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywords(inputBuiltInKeywords) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - p.delete(); - } - - @Test - public void testInitSuccessWithCustomArguments() throws PorcupineException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); - File modelPath = new File(testResourcesPath, "model_files/porcupine_params.pv"); - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setModelPath(modelPath.getAbsolutePath()) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setSensitivity(0.25f) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - p.delete(); - } - - @Test - public void testInitSuccessWithMultipleCustomKeywordsAndSensitivities() throws PorcupineException { - File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); - File keywordPath2 = new File(testResourcesPath, "keyword_files/en/pico_clock_android.ppn"); - String[] keywordPaths = new String[]{keywordPath.getAbsolutePath(), keywordPath2.getAbsolutePath()}; - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywordPaths(keywordPaths) - .setSensitivities(new float[]{0.35f, 0.6f}) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - p.delete(); - } - - @Test - public void testInitFailWithMismatchedLanguage() { - boolean didFail = false; - File keywordPath = new File(testResourcesPath, "keyword_files/de/ananas_android.ppn"); - File modelPath = new File(testResourcesPath, "model_files/porcupine_params.pv"); - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setModelPath(modelPath.getAbsolutePath()) - .setKeywordPath(keywordPath.getAbsolutePath()) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidModelPath() { - boolean didFail = false; - File modelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setModelPath(modelPath.getAbsolutePath()) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidSensitivity() { - boolean didFail = false; - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .setSensitivity(10) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithMismatchedSensitivityCount() { - boolean didFail = false; - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .setSensitivities(new float[]{0.4f, 0.8f}) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithNoKeywords() { - boolean didFail = false; - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithInvalidKeywordPath() { - boolean didFail = false; - File keywordPath = new File(testResourcesPath, "bad_path/bad_path.ppn"); - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithBuiltInAndCustom() { - File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); - boolean didFail = false; - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .setKeywordPath(keywordPath.getAbsolutePath()) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testInitFailWithWrongKeywordPlatform() { - boolean didFail = false; - File keywordPath = new File(testResourcesPath, "keyword_files/en/alexa_linux.ppn"); - try { - new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .build(appContext); - } catch (PorcupineException e) { - didFail = true; - } - - assertTrue(didFail); - } - - @Test - public void testProcSuccessSingleBuiltIn() throws Exception { - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .build(appContext); - - File testAudio = new File(testResourcesPath, "audio_samples/porcupine.wav"); - ArrayList detectionResults = processTestAudio(p, testAudio); - p.delete(); - - assertEquals(1, detectionResults.size()); - assertEquals(0, (int) detectionResults.get(0)); - } - - @Test - public void testProcSuccessMultipleBuiltIn() throws Exception { - Porcupine.BuiltInKeyword[] inputBuiltInKeywords = new Porcupine.BuiltInKeyword[]{ - Porcupine.BuiltInKeyword.ALEXA, - Porcupine.BuiltInKeyword.AMERICANO, - Porcupine.BuiltInKeyword.BLUEBERRY, - Porcupine.BuiltInKeyword.BUMBLEBEE, - Porcupine.BuiltInKeyword.GRAPEFRUIT, - Porcupine.BuiltInKeyword.GRASSHOPPER, - Porcupine.BuiltInKeyword.PICOVOICE, - Porcupine.BuiltInKeyword.PORCUPINE, - Porcupine.BuiltInKeyword.TERMINATOR - }; - - Porcupine.BuiltInKeyword[] expectedResults = new Porcupine.BuiltInKeyword[]{ - Porcupine.BuiltInKeyword.PORCUPINE, - Porcupine.BuiltInKeyword.ALEXA, - Porcupine.BuiltInKeyword.AMERICANO, - Porcupine.BuiltInKeyword.BLUEBERRY, - Porcupine.BuiltInKeyword.BUMBLEBEE, - Porcupine.BuiltInKeyword.GRAPEFRUIT, - Porcupine.BuiltInKeyword.GRASSHOPPER, - Porcupine.BuiltInKeyword.PICOVOICE, - Porcupine.BuiltInKeyword.PORCUPINE, - Porcupine.BuiltInKeyword.TERMINATOR - }; - - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywords(inputBuiltInKeywords) - .build(appContext); - - File testAudio = new File(testResourcesPath, "audio_samples/multiple_keywords.wav"); - ArrayList detectionResults = processTestAudio(p, testAudio); - p.delete(); - - assertSame(expectedResults.length, detectionResults.size()); - for (int i = 0; i < expectedResults.length; i++) { - Porcupine.BuiltInKeyword expectedKeyword = expectedResults[i]; - assertTrue(detectionResults.get(i) < inputBuiltInKeywords.length); - Porcupine.BuiltInKeyword keywordDetected = inputBuiltInKeywords[detectionResults.get(i)]; - assertSame(expectedKeyword, keywordDetected); - } - } - - @Test - public void testInitWithNonAsciiModelName() throws PorcupineException { - File keywordPath = new File(testResourcesPath, "keyword_files/es/murciƩlago_android.ppn"); - File modelPath = new File(testResourcesPath, "model_files/porcupine_params_es.pv"); - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setKeywordPath(keywordPath.getAbsolutePath()) - .setModelPath(modelPath.getAbsolutePath()) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - p.delete(); - } - - @Test - public void testErrorStack() { - String[] error = {}; - try { - new Porcupine.Builder() - .setAccessKey("invalid") - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .build(appContext); - } catch (PorcupineException e) { - error = e.getMessageStack(); - } - - assertTrue(0 < error.length); - assertTrue(error.length <= 8); - - try { - new Porcupine.Builder() - .setAccessKey("invalid") - .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) - .build(appContext); - } catch (PorcupineException e) { - for (int i = 0; i < error.length; i++) { - assertEquals(e.getMessageStack()[i], error[i]); - } - } - } - } - - @RunWith(Parameterized.class) - public static class LanguageTests extends BaseTest { - - @Parameterized.Parameter(value = 0) - public String modelFile; - - @Parameterized.Parameter(value = 1) - public String[] keywordFiles; - - @Parameterized.Parameter(value = 2) - public String testAudioFile; - - @Parameterized.Parameter(value = 3) - public int[] expectedResults; - - @Parameterized.Parameters(name = "{0}") - public static Collection initParameters() throws IOException { - String testDataJsonString = getTestDataString(); - - JsonObject testDataJson = JsonParser.parseString(testDataJsonString).getAsJsonObject(); - JsonArray multipleKeywordDataJson = testDataJson.getAsJsonObject("tests").getAsJsonArray("multipleKeyword"); - - List parameters = new ArrayList<>(); - for (int i = 0; i < multipleKeywordDataJson.size(); i++) { - JsonObject testData = multipleKeywordDataJson.get(i).getAsJsonObject(); - String language = testData.get("language").getAsString(); - JsonArray keywords = testData.getAsJsonArray("wakewords"); - JsonArray groundTruthJson = testData.getAsJsonArray("groundTruth"); - - String modelFile = String.format("model_files/porcupine_params_%s.pv", language); - String[] keywordFiles = new String[keywords.size()]; - for (int j = 0; j < keywords.size(); j++) { - String keyword = keywords.get(j).getAsString(); - keywordFiles[j] = String.format("keyword_files/%s/%s_android.ppn", language, keyword); - } - String audioFile = String.format("audio_samples/multiple_keywords_%s.wav", language); - int[] groundTruth = new int[groundTruthJson.size()]; - for (int j = 0; j < groundTruthJson.size(); j++) { - groundTruth[j] = groundTruthJson.get(j).getAsInt(); - } - - if (Objects.equals(language, "en")) { - modelFile = "model_files/porcupine_params.pv"; - audioFile = "audio_samples/multiple_keywords.wav"; - } - - parameters.add(new Object[] { - modelFile, - keywordFiles, - audioFile, - groundTruth - }); - } - - return parameters; - } - - - @Test - public void testProcess() throws Exception { - String modelPath = new File(testResourcesPath, modelFile).getAbsolutePath(); - String[] keywordPaths = new String[keywordFiles.length]; - for (int i = 0; i < keywordFiles.length; i++) { - keywordPaths[i] = new File(testResourcesPath, keywordFiles[i]).getAbsolutePath(); - - } - Porcupine p = new Porcupine.Builder() - .setAccessKey(accessKey) - .setModelPath(modelPath) - .setKeywordPaths(keywordPaths) - .build(appContext); - - assertTrue(p.getVersion() != null && !p.getVersion().equals("")); - assertTrue(p.getFrameLength() > 0); - assertTrue(p.getSampleRate() > 0); - - File testAudio = new File(testResourcesPath, testAudioFile); - ArrayList detectionResults = processTestAudio(p, testAudio); - p.delete(); - - assertSame(expectedResults.length, detectionResults.size()); - for (int i = 0; i < expectedResults.length; i++) { - assertSame(detectionResults.get(i), expectedResults[i]); - } - - p.delete(); - } - } -} diff --git a/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/StandardTests.java b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/StandardTests.java new file mode 100644 index 0000000000..d16c571432 --- /dev/null +++ b/binding/android/PorcupineTestApp/porcupine-test-app/src/androidTest/java/ai/picovoice/porcupine/testapp/StandardTests.java @@ -0,0 +1,338 @@ +/* + Copyright 2025 Picovoice Inc. + + You may not use this file except in compliance with the license. A copy of the license is + located in the "LICENSE" file accompanying this source. + + 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 ai.picovoice.porcupine.testapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.ArrayList; + +import ai.picovoice.porcupine.Porcupine; +import ai.picovoice.porcupine.PorcupineException; + + +@RunWith(AndroidJUnit4.class) +public class StandardTests extends BaseTest { + + @Test + public void testInitSuccessWithSingleBuiltIn() throws PorcupineException { + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + p.delete(); + } + + @Test + public void testInitSuccessWithMultipleBuiltIns() throws PorcupineException { + Porcupine.BuiltInKeyword[] inputBuiltInKeywords = new Porcupine.BuiltInKeyword[]{ + Porcupine.BuiltInKeyword.ALEXA, + Porcupine.BuiltInKeyword.GRASSHOPPER, + Porcupine.BuiltInKeyword.PICOVOICE, + Porcupine.BuiltInKeyword.TERMINATOR + }; + + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywords(inputBuiltInKeywords) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + p.delete(); + } + + @Test + public void testInitSuccessWithCustomArguments() throws PorcupineException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); + File modelPath = new File(testResourcesPath, "model_files/porcupine_params.pv"); + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath.getAbsolutePath()) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setSensitivity(0.25f) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + p.delete(); + } + + @Test + public void testInitSuccessWithMultipleCustomKeywordsAndSensitivities() throws PorcupineException { + File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); + File keywordPath2 = new File(testResourcesPath, "keyword_files/en/pico_clock_android.ppn"); + String[] keywordPaths = new String[]{keywordPath.getAbsolutePath(), keywordPath2.getAbsolutePath()}; + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywordPaths(keywordPaths) + .setSensitivities(new float[]{0.35f, 0.6f}) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + p.delete(); + } + + @Test + public void testInitFailWithMismatchedLanguage() { + boolean didFail = false; + File keywordPath = new File(testResourcesPath, "keyword_files/de/ananas_android.ppn"); + File modelPath = new File(testResourcesPath, "model_files/porcupine_params.pv"); + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath.getAbsolutePath()) + .setKeywordPath(keywordPath.getAbsolutePath()) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidModelPath() { + boolean didFail = false; + File modelPath = new File(testResourcesPath, "bad_path/bad_path.pv"); + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setModelPath(modelPath.getAbsolutePath()) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidSensitivity() { + boolean didFail = false; + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .setSensitivity(10) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithMismatchedSensitivityCount() { + boolean didFail = false; + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .setSensitivities(new float[]{0.4f, 0.8f}) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithNoKeywords() { + boolean didFail = false; + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithInvalidKeywordPath() { + boolean didFail = false; + File keywordPath = new File(testResourcesPath, "bad_path/bad_path.ppn"); + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithBuiltInAndCustom() { + File keywordPath = new File(testResourcesPath, "keyword_files/en/hey_barista_android.ppn"); + boolean didFail = false; + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .setKeywordPath(keywordPath.getAbsolutePath()) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testInitFailWithWrongKeywordPlatform() { + boolean didFail = false; + File keywordPath = new File(testResourcesPath, "keyword_files/en/alexa_linux.ppn"); + try { + new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .build(appContext); + } catch (PorcupineException e) { + didFail = true; + } + + assertTrue(didFail); + } + + @Test + public void testProcSuccessSingleBuiltIn() throws Exception { + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .build(appContext); + + File testAudio = new File(testResourcesPath, "audio_samples/porcupine.wav"); + ArrayList detectionResults = processTestAudio(p, testAudio); + p.delete(); + + assertEquals(1, detectionResults.size()); + assertEquals(0, (int) detectionResults.get(0)); + } + + @Test + public void testProcSuccessMultipleBuiltIn() throws Exception { + Porcupine.BuiltInKeyword[] inputBuiltInKeywords = new Porcupine.BuiltInKeyword[]{ + Porcupine.BuiltInKeyword.ALEXA, + Porcupine.BuiltInKeyword.AMERICANO, + Porcupine.BuiltInKeyword.BLUEBERRY, + Porcupine.BuiltInKeyword.BUMBLEBEE, + Porcupine.BuiltInKeyword.GRAPEFRUIT, + Porcupine.BuiltInKeyword.GRASSHOPPER, + Porcupine.BuiltInKeyword.PICOVOICE, + Porcupine.BuiltInKeyword.PORCUPINE, + Porcupine.BuiltInKeyword.TERMINATOR + }; + + Porcupine.BuiltInKeyword[] expectedResults = new Porcupine.BuiltInKeyword[]{ + Porcupine.BuiltInKeyword.PORCUPINE, + Porcupine.BuiltInKeyword.ALEXA, + Porcupine.BuiltInKeyword.AMERICANO, + Porcupine.BuiltInKeyword.BLUEBERRY, + Porcupine.BuiltInKeyword.BUMBLEBEE, + Porcupine.BuiltInKeyword.GRAPEFRUIT, + Porcupine.BuiltInKeyword.GRASSHOPPER, + Porcupine.BuiltInKeyword.PICOVOICE, + Porcupine.BuiltInKeyword.PORCUPINE, + Porcupine.BuiltInKeyword.TERMINATOR + }; + + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywords(inputBuiltInKeywords) + .build(appContext); + + File testAudio = new File(testResourcesPath, "audio_samples/multiple_keywords.wav"); + ArrayList detectionResults = processTestAudio(p, testAudio); + p.delete(); + + assertSame(expectedResults.length, detectionResults.size()); + for (int i = 0; i < expectedResults.length; i++) { + Porcupine.BuiltInKeyword expectedKeyword = expectedResults[i]; + assertTrue(detectionResults.get(i) < inputBuiltInKeywords.length); + Porcupine.BuiltInKeyword keywordDetected = inputBuiltInKeywords[detectionResults.get(i)]; + assertSame(expectedKeyword, keywordDetected); + } + } + + @Test + public void testInitWithNonAsciiModelName() throws PorcupineException { + File keywordPath = new File(testResourcesPath, "keyword_files/es/murciƩlago_android.ppn"); + File modelPath = new File(testResourcesPath, "model_files/porcupine_params_es.pv"); + Porcupine p = new Porcupine.Builder() + .setAccessKey(accessKey) + .setKeywordPath(keywordPath.getAbsolutePath()) + .setModelPath(modelPath.getAbsolutePath()) + .build(appContext); + + assertTrue(p.getVersion() != null && !p.getVersion().isEmpty()); + assertTrue(p.getFrameLength() > 0); + assertTrue(p.getSampleRate() > 0); + + p.delete(); + } + + @Test + public void testErrorStack() { + String[] error = {}; + try { + new Porcupine.Builder() + .setAccessKey("invalid") + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .build(appContext); + } catch (PorcupineException e) { + error = e.getMessageStack(); + } + + assertTrue(0 < error.length); + assertTrue(error.length <= 8); + + try { + new Porcupine.Builder() + .setAccessKey("invalid") + .setKeyword(Porcupine.BuiltInKeyword.PORCUPINE) + .build(appContext); + } catch (PorcupineException e) { + for (int i = 0; i < error.length; i++) { + assertEquals(e.getMessageStack()[i], error[i]); + } + } + } +} diff --git a/resources/.lint/spell-check/dict.txt b/resources/.lint/spell-check/dict.txt index ea2841c205..fea095fdf5 100644 --- a/resources/.lint/spell-check/dict.txt +++ b/resources/.lint/spell-check/dict.txt @@ -205,3 +205,4 @@ xcschemes xcshareddata xcworkspace xpresso +xcuitest diff --git a/script/automation/browserstack.py b/script/automation/browserstack.py new file mode 100644 index 0000000000..54c0e77a0d --- /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)