diff --git a/.github/workflows/android-appcenter.yml b/.github/workflows/android-browserstack.yml similarity index 60% rename from .github/workflows/android-appcenter.yml rename to .github/workflows/android-browserstack.yml index d0cbc773..7804ab41 100644 --- a/.github/workflows/android-appcenter.yml +++ b/.github/workflows/android-browserstack.yml @@ -1,17 +1,17 @@ -name: Android AppCenter Tests +name: Android BrowserStack Tests on: workflow_dispatch: push: branches: [ main ] paths: - - '.github/workflows/android-appcenter.yml' + - '.github/workflows/android-browserstack.yml' - 'binding/android/OrcaTestApp/**' - 'resources/.test/**' pull_request: branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - - '.github/workflows/android-appcenter.yml' + - '.github/workflows/android-browserstack.yml' - 'binding/android/OrcaTestApp/**' - 'resources/.test/**' @@ -21,24 +21,23 @@ defaults: 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 @@ -66,35 +65,34 @@ jobs: - name: Build androidTest run: ./gradlew assembleAndroidTest - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Orca-Android" - --devices "Picovoice/android-min-max" - --app-path orca-test-app/build/outputs/apk/debug/orca-test-app-debug.apk - --test-series "orca-android" - --locale "en_US" - --build-dir orca-test-app/build/outputs/apk/androidTest/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 "Orca-Android" + --devices "android-min-max" + --app_path "orca-test-app/build/outputs/apk/debug/orca-test-app-debug.apk" + --test_path "orca-test-app/build/outputs/apk/androidTest/debug/orca-test-app-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 @@ -122,12 +120,12 @@ jobs: - name: Build androidTest run: ./gradlew assembleReleaseAndroidTest -DtestBuildType=integ - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Orca-Android" - --devices "Picovoice/android-min-max" - --app-path orca-test-app/build/outputs/apk/release/orca-test-app-release.apk - --test-series "orca-android" - --locale "en_US" - --build-dir orca-test-app/build/outputs/apk/androidTest/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 "Orca-Android-Integration" + --devices "android-min-max" + --app_path "orca-test-app/build/outputs/apk/release/orca-test-app-release.apk" + --test_path "orca-test-app/build/outputs/apk/androidTest/release/orca-test-app-release-androidTest.apk" diff --git a/.github/workflows/android-demos.yml b/.github/workflows/android-demos.yml index b9d02ded..eac9105c 100644 --- a/.github/workflows/android-demos.yml +++ b/.github/workflows/android-demos.yml @@ -25,10 +25,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/android-perf.yml b/.github/workflows/android-perf.yml index 8fae2bcd..ccc61ae8 100644 --- a/.github/workflows/android-perf.yml +++ b/.github/workflows/android-perf.yml @@ -21,33 +21,30 @@ 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 procPerformanceThresholdSec: 3.0 - - device: 32bit-android - procPerformanceThresholdSec: 19.0 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 @@ -84,12 +81,12 @@ jobs: - name: Build androidTest run: ./gradlew assembleAndroidTest -DtestBuildType=perf - - name: Run tests on AppCenter - run: appcenter test run espresso - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Orca-Android" - --devices "Picovoice/${{ matrix.device }}" - --app-path orca-test-app/build/outputs/apk/debug/orca-test-app-debug.apk - --test-series "orca-android" - --locale "en_US" - --build-dir orca-test-app/build/outputs/apk/androidTest/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 "Orca-Android-Performance" + --devices "${{ matrix.device }}" + --app_path "orca-test-app/build/outputs/apk/debug/orca-test-app-debug.apk" + --test_path "orca-test-app/build/outputs/apk/androidTest/debug/orca-test-app-debug-androidTest.apk" diff --git a/.github/workflows/ios-appcenter.yml b/.github/workflows/ios-browserstack.yml similarity index 50% rename from .github/workflows/ios-appcenter.yml rename to .github/workflows/ios-browserstack.yml index 7a3318ee..37f23baa 100644 --- a/.github/workflows/ios-appcenter.yml +++ b/.github/workflows/ios-browserstack.yml @@ -1,17 +1,17 @@ -name: iOS AppCenter Tests +name: iOS BrowserStack Tests on: workflow_dispatch: push: branches: [ main ] paths: - - '.github/workflows/ios-appcenter.yml' + - '.github/workflows/ios-browserstack.yml' - 'binding/ios/OrcaAppTest/**' - 'resources/.test/**' pull_request: branches: [ main, 'v[0-9]+.[0-9]+' ] paths: - - '.github/workflows/ios-appcenter.yml' + - '.github/workflows/ios-browserstack.yml' - 'binding/ios/OrcaAppTest/**' - 'resources/.test/**' @@ -21,24 +21,23 @@ defaults: jobs: build: - name: Run iOS Tests on AppCenter + name: Run iOS Tests on BrowserStack runs-on: macos-latest steps: - 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/* + 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 @@ -53,7 +52,7 @@ jobs: - 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}}:' OrcaAppTestUITests/BaseTest.swift @@ -66,11 +65,23 @@ jobs: -derivedDataPath ddp CODE_SIGNING_ALLOWED=NO - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Orca-iOS" - --devices "Picovoice/ios-min-max" - --test-series "orca-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 OrcaAppTest.app Payload && + zip --symlinks -r OrcaAppTest.ipa Payload && + rm -r Payload + + - name: Zipping Tests + run: cd ddp/Build/Products/Debug-iphoneos/ && + zip --symlinks -r OrcaAppTestUITests.zip OrcaAppTestUITests-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 "Orca-iOS" + --devices "ios-min-max" + --app_path "ddp/Build/Products/Debug-iphoneos/OrcaAppTest.ipa" + --test_path "ddp/Build/Products/Debug-iphoneos/OrcaAppTestUITests.zip" diff --git a/.github/workflows/ios-demos.yml b/.github/workflows/ios-demos.yml index 0dd22f3b..3e6e3b13 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 diff --git a/.github/workflows/ios-perf.yml b/.github/workflows/ios-perf.yml index 98c3df9a..22be7334 100644 --- a/.github/workflows/ios-perf.yml +++ b/.github/workflows/ios-perf.yml @@ -21,7 +21,7 @@ defaults: jobs: build: - name: Run iOS Tests on AppCenter + name: Run iOS Tests on BrowserStack runs-on: macos-latest strategy: @@ -35,32 +35,26 @@ 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/* + 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}}:' OrcaAppTestUITests/BaseTest.swift @@ -69,7 +63,8 @@ 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 @@ -81,11 +76,23 @@ jobs: -derivedDataPath ddp CODE_SIGNING_ALLOWED=NO - - name: Run Tests on AppCenter - run: appcenter test run xcuitest - --token ${{secrets.APPCENTERAPITOKEN}} - --app "Picovoice/Orca-iOS" - --devices "Picovoice/${{ matrix.device }}" - --test-series "orca-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 OrcaAppTest.app Payload && + zip --symlinks -r OrcaAppTest.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 "Orca-iOS-Performance" + --devices "${{ matrix.device }}" + --app_path "ddp/Build/Products/Debug-iphoneos/OrcaAppTest.ipa" + --test_path "ddp/Build/Products/Debug-iphoneos/PerformanceTest.zip" diff --git a/binding/android/OrcaTestApp/build.gradle b/binding/android/OrcaTestApp/build.gradle index 56f710be..45cb5448 100644 --- a/binding/android/OrcaTestApp/build.gradle +++ b/binding/android/OrcaTestApp/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.2.2' } } diff --git a/binding/android/OrcaTestApp/gradle.properties b/binding/android/OrcaTestApp/gradle.properties index c09e1e3b..683c3de0 100644 --- a/binding/android/OrcaTestApp/gradle.properties +++ b/binding/android/OrcaTestApp/gradle.properties @@ -15,3 +15,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/binding/android/OrcaTestApp/gradle/wrapper/gradle-wrapper.properties b/binding/android/OrcaTestApp/gradle/wrapper/gradle-wrapper.properties index a30f51aa..f92acbcc 100644 --- a/binding/android/OrcaTestApp/gradle/wrapper/gradle-wrapper.properties +++ b/binding/android/OrcaTestApp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Feb 09 14:21:53 PST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/binding/android/OrcaTestApp/orca-test-app/build.gradle b/binding/android/OrcaTestApp/orca-test-app/build.gradle index 41631a5b..9d292b84 100644 --- a/binding/android/OrcaTestApp/orca-test-app/build.gradle +++ b/binding/android/OrcaTestApp/orca-test-app/build.gradle @@ -80,10 +80,12 @@ android { androidTest { java { if (System.getProperty("testBuildType", "debug") == "perf") { - exclude "**/OrcaTest.java" + exclude "**/StandardTests.java" + exclude "**/ModelTests.java" exclude "**/IntegrationTest.java" } else if (System.getProperty("testBuildType", "debug") == "integ") { - exclude "**/OrcaTest.java" + exclude "**/StandardTests.java" + exclude "**/ModelTests.java" exclude "**/PerformanceTest.java" } else { exclude "**/IntegrationTest.java" @@ -99,6 +101,7 @@ android { lint { abortOnError false } + namespace 'ai.picovoice.orca.testapp' } dependencies { @@ -106,6 +109,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.8.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:orca-android:1.0.0' // Espresso UI Testing @@ -113,7 +118,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') androidTestImplementation('ai.picovoice:orca-android:1.0.0') } @@ -121,4 +125,6 @@ dependencies { afterEvaluate { tasks."mergeDebugAssets".dependsOn "copyParams" tasks."mergeReleaseAssets".dependsOn "copyParams" + tasks."generateReleaseLintVitalReportModel".dependsOn "copyParams" + tasks."lintVitalAnalyzeRelease".dependsOn "copyParams" } diff --git a/binding/android/OrcaTestApp/orca-test-app/proguard-rules.pro b/binding/android/OrcaTestApp/orca-test-app/proguard-rules.pro index 158caf35..280693da 100644 --- a/binding/android/OrcaTestApp/orca-test-app/proguard-rules.pro +++ b/binding/android/OrcaTestApp/orca-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/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/BaseTest.java b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/BaseTest.java index 23bac17e..54729cf9 100644 --- a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/BaseTest.java +++ b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/BaseTest.java @@ -19,12 +19,8 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -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; @@ -48,9 +44,6 @@ public class BaseTest { - @Rule - public ReportHelper reportHelper = Factory.getReportHelper(); - Context testContext; Context appContext; AssetManager assetManager; @@ -77,12 +70,6 @@ public void Setup() throws Exception { accessKey = appContext.getString(R.string.pvTestingAccessKey); } - @After - public void TearDown() { - // DO NOT REMOVE - this is required for AppCenter testing - reportHelper.label("Stopping App"); - } - public static String[] getModelFiles() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); String resPath = new File( diff --git a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/IntegrationTest.java b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/IntegrationTest.java index 51dbb490..2f6375f9 100644 --- a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/IntegrationTest.java +++ b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/IntegrationTest.java @@ -28,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; @@ -88,9 +85,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); @@ -105,12 +99,6 @@ public void intentsTeardown() { Intents.release(); } - @After - public void TearDown() { - // DO NOT REMOVE - this is required for AppCenter testing - reportHelper.label("Stopping App"); - } - @Test public void testOrca() { onView(withId(R.id.testButton)).perform(click()); diff --git a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/ModelTests.java b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/ModelTests.java new file mode 100644 index 00000000..fca62b1a --- /dev/null +++ b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/ModelTests.java @@ -0,0 +1,350 @@ +/* + Copyright 2024 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.orca.testapp; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.io.File; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import ai.picovoice.orca.Orca; +import ai.picovoice.orca.OrcaAudio; +import ai.picovoice.orca.OrcaException; +import ai.picovoice.orca.OrcaInvalidArgumentException; +import ai.picovoice.orca.OrcaSynthesizeParams; +import ai.picovoice.orca.OrcaWord; +import ai.picovoice.orca.OrcaPhoneme; + +@RunWith(Parameterized.class) +public class ModelTests extends BaseTest { + + @Parameterized.Parameter(value = 0) + public String modelFile; + + @Parameterized.Parameters(name = "{0}") + public static Collection initParameters() { + List parameters = new ArrayList<>(); + for (String modelFile : getModelFiles()) { + parameters.add(new Object[]{modelFile}); + } + return parameters; + } + + String text; + String textNoPunctuation; + String textCustomPronunciation; + String textAlignment; + static JsonArray textInvalid; + + long randomState; + static JsonArray alignments; + + String modelFileUsed; + String EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER = "female"; + + Orca orca; + + @Before + public void Setup() throws Exception { + super.Setup(); + + final JsonObject testSentences = testJson.getAsJsonObject("test_sentences"); + text = testSentences.get("text").getAsString(); + textNoPunctuation = testSentences.get("text_no_punctuation").getAsString(); + textCustomPronunciation = testSentences.get("text_custom_pronunciation").getAsString(); + textAlignment = testSentences.get("text_alignment").getAsString(); + textInvalid = testSentences.get("text_invalid").getAsJsonArray(); + + randomState = testJson.get("random_state").getAsLong(); + alignments = testJson.getAsJsonArray("alignments"); + + modelFileUsed = modelFile.contains("female") ? "female" : "male"; + + orca = new Orca.Builder() + .setAccessKey(accessKey) + .setModelPath(modelFile) + .build(appContext); + } + + @After + public void TearDown() { + if (orca != null) { + orca.delete(); + } + } + + @Test + public void testVersion() { + final String version = orca.getVersion(); + assertNotNull(version); + assertNotEquals(version, ""); + } + + @Test + public void testSampleRate() { + assertTrue(orca.getSampleRate() > 0); + } + + @Test + public void testMaxCharacterLimit() { + assertTrue(orca.getMaxCharacterLimit() > 0); + } + + @Test + public void testValidCharacters() { + String[] characters = orca.getValidCharacters(); + assertTrue(characters.length > 0); + assertTrue(Arrays.asList(characters).contains(",")); + } + + @Test + public void testStreaming() throws Exception { + Orca.OrcaStream orcaStream = orca.streamOpen( + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + + short[] fullPcm = new short[0]; + for (char c : text.toCharArray()) { + short[] pcm = orcaStream.synthesize(String.valueOf(c)); + if (pcm != null && pcm.length > 0) { + fullPcm = concatArrays(fullPcm, pcm); + } + } + + short[] flushedPcm = orcaStream.flush(); + if (flushedPcm != null && flushedPcm.length > 0) { + fullPcm = concatArrays(fullPcm, flushedPcm); + } + + orcaStream.close(); + short[] testFilePcm = readAudioFile(String.format( + "%s/wav/orca_params_%s_stream.wav", testResourcesPath, modelFileUsed)); + + compareArrays(fullPcm, testFilePcm, 1); + } + + @Test + public void testSynthesize() throws Exception { + final OrcaAudio pcm = orca.synthesize( + text, + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + + short[] testFilePcm = readAudioFile(String.format( + "%s/wav/orca_params_%s_single.wav", testResourcesPath, modelFileUsed)); + + compareArrays(pcm.getPcm(), testFilePcm, 1); + } + + @Test + public void testSynthesizeToFile() throws Exception { + final File outputFile = new File( + appContext.getFilesDir(), + "text.wav"); + orca.synthesizeToFile( + text, + outputFile.getAbsolutePath(), + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + + short[] outputFilePcm = readAudioFile(outputFile.getAbsolutePath()); + short[] testFilePcm = readAudioFile(String.format( + "%s/wav/orca_params_%s_single.wav", testResourcesPath, modelFileUsed)); + + compareArrays(outputFilePcm, testFilePcm, 1); + outputFile.delete(); + } + + @Test + public void testSynthesizeNoPronunciation() throws OrcaException { + final OrcaAudio result = orca.synthesize( + textNoPunctuation, + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + assertTrue(result.getPcm().length > 0); + } + + @Test + public void testSynthesizeCustomPronunciation() throws OrcaException { + final OrcaAudio result = orca.synthesize( + textCustomPronunciation, + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + assertTrue(result.getPcm().length > 0); + } + + @Test + public void testSynthesizeAlignment() throws OrcaException { + final OrcaAudio result = orca.synthesize( + textAlignment, + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + final OrcaWord[] synthesizeTestData = new OrcaWord[alignments.size()]; + for (int i = 0; i < alignments.size(); i++) { + final JsonObject testData = alignments.get(i).getAsJsonObject(); + final String word = testData.get("word").getAsString(); + final float startSec = testData.get("start_sec").getAsFloat(); + final float endSec = testData.get("end_sec").getAsFloat(); + final JsonArray phonemesJson = testData.getAsJsonArray("phonemes"); + final OrcaPhoneme[] phonemes = new OrcaPhoneme[phonemesJson.size()]; + for (int j = 0; j < phonemesJson.size(); j++) { + final JsonObject phonemeJson = phonemesJson.get(j).getAsJsonObject(); + phonemes[j] = new OrcaPhoneme( + phonemeJson.get("phoneme").getAsString(), + phonemeJson.get("start_sec").getAsFloat(), + phonemeJson.get("end_sec").getAsFloat()); + } + synthesizeTestData[i] = new OrcaWord( + word, + startSec, + endSec, + phonemes); + } + validateMetadata( + result.getWordArray(), + synthesizeTestData, + Objects.equals(modelFileUsed, EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER)); + } + + @Test + public void testSynthesizeToFileAlignment() throws OrcaException { + final File outputFile = new File( + appContext.getFilesDir(), + "text.wav"); + OrcaWord[] result = orca.synthesizeToFile( + textAlignment, + outputFile.getAbsolutePath(), + new OrcaSynthesizeParams.Builder() + .setRandomState(randomState) + .build()); + outputFile.delete(); + + final OrcaWord[] synthesizeTestData = new OrcaWord[alignments.size()]; + for (int i = 0; i < alignments.size(); i++) { + final JsonObject testData = alignments.get(i).getAsJsonObject(); + final String word = testData.get("word").getAsString(); + final float startSec = testData.get("start_sec").getAsFloat(); + final float endSec = testData.get("end_sec").getAsFloat(); + final JsonArray phonemesJson = testData.getAsJsonArray("phonemes"); + final OrcaPhoneme[] phonemes = new OrcaPhoneme[phonemesJson.size()]; + for (int j = 0; j < phonemesJson.size(); j++) { + final JsonObject phonemeJson = phonemesJson.get(j).getAsJsonObject(); + phonemes[j] = new OrcaPhoneme( + phonemeJson.get("phoneme").getAsString(), + phonemeJson.get("start_sec").getAsFloat(), + phonemeJson.get("end_sec").getAsFloat()); + } + synthesizeTestData[i] = new OrcaWord( + word, + startSec, + endSec, + phonemes); + } + validateMetadata( + result, + synthesizeTestData, + Objects.equals(modelFileUsed, EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER)); + } + + @Test + public void testSynthesizeSpeechRate() throws OrcaException { + final OrcaAudio slow = orca.synthesize( + textCustomPronunciation, + new OrcaSynthesizeParams.Builder() + .setSpeechRate(0.7f) + .setRandomState(randomState) + .build()); + assertTrue(slow.getPcm().length > 0); + + final OrcaAudio fast = orca.synthesize( + textCustomPronunciation, + new OrcaSynthesizeParams.Builder() + .setSpeechRate(1.3f) + .setRandomState(randomState) + .build()); + assertTrue(slow.getPcm().length > 0); + assertTrue(fast.getPcm().length < slow.getPcm().length); + + try { + orca.synthesize( + textCustomPronunciation, + new OrcaSynthesizeParams.Builder() + .setSpeechRate(9999f) + .setRandomState(randomState) + .build()); + fail(); + } catch (OrcaInvalidArgumentException e) { + assertNotNull(e); + } + } + + @Test + public void testSynthesizeRandomState() throws OrcaException { + final OrcaAudio randomState1 = orca.synthesize( + text, + new OrcaSynthesizeParams.Builder() + .setRandomState(1) + .build()); + assertTrue(randomState1.getPcm().length > 0); + assertTrue(randomState1.getWordArray().length > 0); + + final OrcaAudio randomState2 = orca.synthesize( + text, + new OrcaSynthesizeParams.Builder() + .setRandomState(2) + .build()); + assertTrue(randomState2.getPcm().length > 0); + assertTrue(randomState2.getWordArray().length > 0); + + assertNotEquals(randomState1, randomState2); + assertNotEquals(randomState1.getWordArray(), randomState2.getWordArray()); + + final OrcaAudio randomStateNull = orca.synthesize( + text, + new OrcaSynthesizeParams.Builder() + .build()); + assertTrue(randomStateNull.getPcm().length > 0); + assertTrue(randomStateNull.getWordArray().length > 0); + + final OrcaAudio randomStateMaxValue = orca.synthesize( + text, + new OrcaSynthesizeParams.Builder() + .setRandomState(Long.MAX_VALUE) + .build()); + assertTrue(randomStateMaxValue.getPcm().length > 0); + assertTrue(randomStateMaxValue.getWordArray().length > 0); + } +} diff --git a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/OrcaTest.java b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/OrcaTest.java deleted file mode 100644 index 158fe2ed..00000000 --- a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/OrcaTest.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - Copyright 2024 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.orca.testapp; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import org.junit.After; -import org.junit.Before; -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.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -import ai.picovoice.orca.Orca; -import ai.picovoice.orca.OrcaAudio; -import ai.picovoice.orca.OrcaException; -import ai.picovoice.orca.OrcaInvalidArgumentException; -import ai.picovoice.orca.OrcaSynthesizeParams; -import ai.picovoice.orca.OrcaWord; -import ai.picovoice.orca.OrcaPhoneme; - -@RunWith(Enclosed.class) -public class OrcaTest { - - public static class StandardTests extends BaseTest { - - String[] modelFiles; - - @Before - public void Setup() throws Exception { - super.Setup(); - modelFiles = getModelFiles(); - } - - @Test - public void testErrorStack() { - String[] error = {}; - try { - new Orca.Builder() - .setAccessKey("invalid") - .setModelPath(modelFiles[0]) - .build(appContext); - } catch (OrcaException e) { - error = e.getMessageStack(); - } - - assertTrue(0 < error.length); - assertTrue(error.length <= 8); - - try { - new Orca.Builder() - .setAccessKey("invalid") - .setModelPath(modelFiles[0]) - .build(appContext); - } catch (OrcaException e) { - for (int i = 0; i < error.length; i++) { - assertEquals(e.getMessageStack()[i], error[i]); - } - } - } - } - - @RunWith(Parameterized.class) - public static class ModelTests extends BaseTest { - - @Parameterized.Parameter(value = 0) - public String modelFile; - - @Parameterized.Parameters(name = "{0}") - public static Collection initParameters() { - List parameters = new ArrayList<>(); - for (String modelFile : getModelFiles()) { - parameters.add(new Object[]{modelFile}); - } - return parameters; - } - - String text; - String textNoPunctuation; - String textCustomPronunciation; - String textAlignment; - static JsonArray textInvalid; - - long randomState; - static JsonArray alignments; - - String modelFileUsed; - String EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER = "female"; - - Orca orca; - - @Before - public void Setup() throws Exception { - super.Setup(); - - final JsonObject testSentences = testJson.getAsJsonObject("test_sentences"); - text = testSentences.get("text").getAsString(); - textNoPunctuation = testSentences.get("text_no_punctuation").getAsString(); - textCustomPronunciation = testSentences.get("text_custom_pronunciation").getAsString(); - textAlignment = testSentences.get("text_alignment").getAsString(); - textInvalid = testSentences.get("text_invalid").getAsJsonArray(); - - randomState = testJson.get("random_state").getAsLong(); - alignments = testJson.getAsJsonArray("alignments"); - - modelFileUsed = modelFile.contains("female") ? "female" : "male"; - - orca = new Orca.Builder() - .setAccessKey(accessKey) - .setModelPath(modelFile) - .build(appContext); - } - - @After - public void TearDown() { - if (orca != null) { - orca.delete(); - } - } - - @Test - public void testVersion() { - final String version = orca.getVersion(); - assertNotNull(version); - assertNotEquals(version, ""); - } - - @Test - public void testSampleRate() { - assertTrue(orca.getSampleRate() > 0); - } - - @Test - public void testMaxCharacterLimit() { - assertTrue(orca.getMaxCharacterLimit() > 0); - } - - @Test - public void testValidCharacters() { - String[] characters = orca.getValidCharacters(); - assertTrue(characters.length > 0); - assertTrue(Arrays.asList(characters).contains(",")); - } - - @Test - public void testStreaming() throws Exception { - Orca.OrcaStream orcaStream = orca.streamOpen( - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - - short[] fullPcm = new short[0]; - for (char c : text.toCharArray()) { - short[] pcm = orcaStream.synthesize(String.valueOf(c)); - if (pcm != null && pcm.length > 0) { - fullPcm = concatArrays(fullPcm, pcm); - } - } - - short[] flushedPcm = orcaStream.flush(); - if (flushedPcm != null && flushedPcm.length > 0) { - fullPcm = concatArrays(fullPcm, flushedPcm); - } - - orcaStream.close(); - short[] testFilePcm = readAudioFile(String.format( - "%s/wav/orca_params_%s_stream.wav", testResourcesPath, modelFileUsed)); - - compareArrays(fullPcm, testFilePcm, 1); - } - - @Test - public void testSynthesize() throws Exception { - final OrcaAudio pcm = orca.synthesize( - text, - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - - short[] testFilePcm = readAudioFile(String.format( - "%s/wav/orca_params_%s_single.wav", testResourcesPath, modelFileUsed)); - - compareArrays(pcm.getPcm(), testFilePcm, 1); - } - - @Test - public void testSynthesizeToFile() throws Exception { - final File outputFile = new File( - appContext.getFilesDir(), - "text.wav"); - orca.synthesizeToFile( - text, - outputFile.getAbsolutePath(), - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - - short[] outputFilePcm = readAudioFile(outputFile.getAbsolutePath()); - short[] testFilePcm = readAudioFile(String.format( - "%s/wav/orca_params_%s_single.wav", testResourcesPath, modelFileUsed)); - - compareArrays(outputFilePcm, testFilePcm, 1); - outputFile.delete(); - } - - @Test - public void testSynthesizeNoPronunciation() throws OrcaException { - final OrcaAudio result = orca.synthesize( - textNoPunctuation, - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - assertTrue(result.getPcm().length > 0); - } - - @Test - public void testSynthesizeCustomPronunciation() throws OrcaException { - final OrcaAudio result = orca.synthesize( - textCustomPronunciation, - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - assertTrue(result.getPcm().length > 0); - } - - @Test - public void testSynthesizeAlignment() throws OrcaException { - final OrcaAudio result = orca.synthesize( - textAlignment, - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - final OrcaWord[] synthesizeTestData = new OrcaWord[alignments.size()]; - for (int i = 0; i < alignments.size(); i++) { - final JsonObject testData = alignments.get(i).getAsJsonObject(); - final String word = testData.get("word").getAsString(); - final float startSec = testData.get("start_sec").getAsFloat(); - final float endSec = testData.get("end_sec").getAsFloat(); - final JsonArray phonemesJson = testData.getAsJsonArray("phonemes"); - final OrcaPhoneme[] phonemes = new OrcaPhoneme[phonemesJson.size()]; - for (int j = 0; j < phonemesJson.size(); j++) { - final JsonObject phonemeJson = phonemesJson.get(j).getAsJsonObject(); - phonemes[j] = new OrcaPhoneme( - phonemeJson.get("phoneme").getAsString(), - phonemeJson.get("start_sec").getAsFloat(), - phonemeJson.get("end_sec").getAsFloat()); - } - synthesizeTestData[i] = new OrcaWord( - word, - startSec, - endSec, - phonemes); - } - validateMetadata( - result.getWordArray(), - synthesizeTestData, - Objects.equals(modelFileUsed, EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER)); - } - - @Test - public void testSynthesizeToFileAlignment() throws OrcaException { - final File outputFile = new File( - appContext.getFilesDir(), - "text.wav"); - OrcaWord[] result = orca.synthesizeToFile( - textAlignment, - outputFile.getAbsolutePath(), - new OrcaSynthesizeParams.Builder() - .setRandomState(randomState) - .build()); - outputFile.delete(); - - final OrcaWord[] synthesizeTestData = new OrcaWord[alignments.size()]; - for (int i = 0; i < alignments.size(); i++) { - final JsonObject testData = alignments.get(i).getAsJsonObject(); - final String word = testData.get("word").getAsString(); - final float startSec = testData.get("start_sec").getAsFloat(); - final float endSec = testData.get("end_sec").getAsFloat(); - final JsonArray phonemesJson = testData.getAsJsonArray("phonemes"); - final OrcaPhoneme[] phonemes = new OrcaPhoneme[phonemesJson.size()]; - for (int j = 0; j < phonemesJson.size(); j++) { - final JsonObject phonemeJson = phonemesJson.get(j).getAsJsonObject(); - phonemes[j] = new OrcaPhoneme( - phonemeJson.get("phoneme").getAsString(), - phonemeJson.get("start_sec").getAsFloat(), - phonemeJson.get("end_sec").getAsFloat()); - } - synthesizeTestData[i] = new OrcaWord( - word, - startSec, - endSec, - phonemes); - } - validateMetadata( - result, - synthesizeTestData, - Objects.equals(modelFileUsed, EXACT_ALIGNMENT_TEST_MODEL_IDENTIFIER)); - } - - @Test - public void testSynthesizeSpeechRate() throws OrcaException { - final OrcaAudio slow = orca.synthesize( - textCustomPronunciation, - new OrcaSynthesizeParams.Builder() - .setSpeechRate(0.7f) - .setRandomState(randomState) - .build()); - assertTrue(slow.getPcm().length > 0); - - final OrcaAudio fast = orca.synthesize( - textCustomPronunciation, - new OrcaSynthesizeParams.Builder() - .setSpeechRate(1.3f) - .setRandomState(randomState) - .build()); - assertTrue(slow.getPcm().length > 0); - assertTrue(fast.getPcm().length < slow.getPcm().length); - - try { - orca.synthesize( - textCustomPronunciation, - new OrcaSynthesizeParams.Builder() - .setSpeechRate(9999f) - .setRandomState(randomState) - .build()); - fail(); - } catch (OrcaInvalidArgumentException e) { - assertNotNull(e); - } - } - - @Test - public void testSynthesizeRandomState() throws OrcaException { - final OrcaAudio randomState1 = orca.synthesize( - text, - new OrcaSynthesizeParams.Builder() - .setRandomState(1) - .build()); - assertTrue(randomState1.getPcm().length > 0); - assertTrue(randomState1.getWordArray().length > 0); - - final OrcaAudio randomState2 = orca.synthesize( - text, - new OrcaSynthesizeParams.Builder() - .setRandomState(2) - .build()); - assertTrue(randomState2.getPcm().length > 0); - assertTrue(randomState2.getWordArray().length > 0); - - assertNotEquals(randomState1, randomState2); - assertNotEquals(randomState1.getWordArray(), randomState2.getWordArray()); - - final OrcaAudio randomStateNull = orca.synthesize( - text, - new OrcaSynthesizeParams.Builder() - .build()); - assertTrue(randomStateNull.getPcm().length > 0); - assertTrue(randomStateNull.getWordArray().length > 0); - - final OrcaAudio randomStateMaxValue = orca.synthesize( - text, - new OrcaSynthesizeParams.Builder() - .setRandomState(Long.MAX_VALUE) - .build()); - assertTrue(randomStateMaxValue.getPcm().length > 0); - assertTrue(randomStateMaxValue.getWordArray().length > 0); - } - } -} diff --git a/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/StandardTests.java b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/StandardTests.java new file mode 100644 index 00000000..2b288cbb --- /dev/null +++ b/binding/android/OrcaTestApp/orca-test-app/src/androidTest/java/ai/picovoice/orca/testapp/StandardTests.java @@ -0,0 +1,64 @@ +/* + Copyright 2024 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.orca.testapp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import ai.picovoice.orca.Orca; +import ai.picovoice.orca.OrcaException; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +@RunWith(AndroidJUnit4.class) +public class StandardTests extends BaseTest { + + String[] modelFiles; + + @Before + public void Setup() throws Exception { + super.Setup(); + modelFiles = getModelFiles(); + } + + @Test + public void testErrorStack() { + String[] error = {}; + try { + new Orca.Builder() + .setAccessKey("invalid") + .setModelPath(modelFiles[0]) + .build(appContext); + } catch (OrcaException e) { + error = e.getMessageStack(); + } + + assertTrue(0 < error.length); + assertTrue(error.length <= 8); + + try { + new Orca.Builder() + .setAccessKey("invalid") + .setModelPath(modelFiles[0]) + .build(appContext); + } catch (OrcaException e) { + for (int i = 0; i < error.length; i++) { + assertEquals(e.getMessageStack()[i], error[i]); + } + } + } +} diff --git a/binding/android/OrcaTestApp/orca-test-app/src/main/AndroidManifest.xml b/binding/android/OrcaTestApp/orca-test-app/src/main/AndroidManifest.xml index 5e6691a5..89d1e9ba 100644 --- a/binding/android/OrcaTestApp/orca-test-app/src/main/AndroidManifest.xml +++ b/binding/android/OrcaTestApp/orca-test-app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/demo/ios/OrcaDemo/Podfile.lock b/demo/ios/OrcaDemo/Podfile.lock index b8768fc3..6489899b 100644 --- a/demo/ios/OrcaDemo/Podfile.lock +++ b/demo/ios/OrcaDemo/Podfile.lock @@ -13,4 +13,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: cfe48de1582b7218aa5c16f59fe450987cdec387 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/resources/.lint/spell-check/dict.txt b/resources/.lint/spell-check/dict.txt index 469917ff..4f06a9cb 100644 --- a/resources/.lint/spell-check/dict.txt +++ b/resources/.lint/spell-check/dict.txt @@ -41,6 +41,7 @@ wavefile Sevilla editdistance pvleopard +xcuitest xcworkspace sounddevice tiktoken diff --git a/script/automation/browserstack.py b/script/automation/browserstack.py new file mode 100644 index 00000000..7e0cd25b --- /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 Pro-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)