diff --git a/.gitignore b/.gitignore index e9b502be36..bdbd704dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ local.properties results xcuserdata/ test_projects/ios/*/build* +android_shards.json diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 38a627e763..0b5dea3838 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -54,6 +54,8 @@ object Dependencies { const val KOTLIN_SERIALIZATION = "org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.KOTLIN_SERIALIZATION}" //region flank-scripts + const val ARCHIVE_LIB = "org.rauschig:jarchivelib:${Versions.ARCHIVE_LIB}" + const val TUKAANI_XZ = "org.tukaani:xz:${Versions.TUKAANI_XZ}" const val CLIKT = "com.github.ajalt:clikt:${Versions.CLIKT}" const val JCABI_GITHUB = "com.jcabi:jcabi-github:${Versions.JCABI_GITHUB}" const val SLF4J_NOP = "org.slf4j:slf4j-nop:${Versions.SLF4J_NOP}" diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 837a6222c6..54625256ae 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -21,7 +21,7 @@ object Versions { const val KOTLIN = "1.4.10" // https://github.com/Kotlin/kotlinx.coroutines/releases - const val KOTLIN_COROUTINES = "1.3.9" + const val KOTLIN_COROUTINES = "1.4.0" // https://github.com/remkop/picocli/releases const val PICOCLI = "4.5.2" @@ -38,7 +38,7 @@ object Versions { const val GOOGLE_NIO = "0.121.2" // https://search.maven.org/search?q=a:google-cloud-storage%20g:com.google.cloud - const val GOOGLE_STORAGE = "1.113.1" + const val GOOGLE_STORAGE = "1.113.2" // https://github.com/google/gson/releases const val GSON = "2.8.6" @@ -79,7 +79,7 @@ object Versions { const val COMMON_TEXT = "1.9" // https://github.com/fusesource/jansi/releases - const val JANSI = "1.18" + const val JANSI = "2.0.1" // https://github.com/ben-manes/gradle-versions-plugin/releases const val BEN_MANES = "0.33.0" @@ -88,7 +88,9 @@ object Versions { const val PROGUARD = "7.0.0" // ============== flank-scripts ============== - const val KOTLIN_SERIALIZATION = "1.0.0" + const val KOTLIN_SERIALIZATION = "1.0.1" + const val ARCHIVE_LIB = "1.1.0" + const val TUKAANI_XZ = "1.0" const val FUEL = "2.3.0" const val CLIKT = "2.8.0" const val JCABI_GITHUB = "1.1.2" diff --git a/docs/index.md b/docs/index.md index b9477409b5..c9f7a721c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -145,6 +145,12 @@ gcloud: # version: 12.0 # locale: es_ES # orientation: landscape + + ## A list of paths that will be copied from the device's storage to the designated results bucket after the test + ## is complete. These must be absolute paths under /private/var/mobile/Media or /Documents + ## of the app under test. If the path is under an app's /Documents, it must be prefixed with the app's bundle id and a colon + # directories-to-pull: + # - /private/var/mobile/Media ## A list of device-path=file-path pairs that specify the paths of the test device and the files you want pushed to the device prior to testing. ## Device paths should either be under the Media shared folder (e.g. prefixed with /private/var/mobile/Media) or @@ -160,8 +166,27 @@ gcloud: # - gs://bucket/additional.ipa # - path/to/local/ipa/file.ipa + ## A list of game-loop scenario numbers which will be run as part of the test (default: all scenarios). + ## A maximum of 1024 scenarios may be specified in one test matrix, but the maximum number may also be limited by the overall test --timeout setting. + # scenario-numbers: + # - 1 + # - 2 + # - 3 + ## The type of iOS test to run. TYPE must be one of: xctest, game-loop. Default: xctest # type: xctest + + ## The path to the application archive (.ipa file) for game-loop testing. + ## The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. + ## This flag is only valid when --type=game-loop is also set + # app: + # - gs://bucket/additional.ipa OR path/to/local/ipa/file.ipa + + + ## Enables testing special app entitlements. Re-signs an app having special entitlements with a new application-identifier. + ## This currently supports testing Push Notifications (aps-environment) entitlement for up to one app in a project. + ## Note: Because this changes the app's identifier, make sure none of the resources in your zip file contain direct references to the test app's bundle id. + # test-special-entitlements: false flank: # -- FlankYml -- @@ -361,6 +386,13 @@ gcloud: # - local/file/path/test1.obb # - local/file/path/test2.obb + ## A list of game-loop scenario numbers which will be run as part of the test (default: all scenarios). + ## A maximum of 1024 scenarios may be specified in one test matrix, but the maximum number may also be limited by the overall test --timeout setting. + # scenario-numbers: + # - 1 + # - 2 + # - 3 + ## A list of OBB required filenames. OBB file name must conform to the format as specified by Android e.g. ## [main|patch].0300110.com.example.android.obb which will be installed into /Android/obb// on the device. # obb-names: diff --git a/firebase_apis/generate_java_client.bat b/firebase_apis/generate_java_client.bat new file mode 100755 index 0000000000..7b27596f9d --- /dev/null +++ b/firebase_apis/generate_java_client.bat @@ -0,0 +1,2 @@ +SET DIR=%~dp0 +%DIR%\..\..\flank-scripts\bash\flankScripts.bat shell firebase generateJavaClient diff --git a/firebase_apis/generate_java_client.sh b/firebase_apis/generate_java_client.sh index 08cc4b5f61..1ed7797d25 100755 --- a/firebase_apis/generate_java_client.sh +++ b/firebase_apis/generate_java_client.sh @@ -1,32 +1,2 @@ -#!/bin/bash - -# Note: Must have already installed google-apis-client-generator from the master branch. PIP release will not work! - -# git clone https://github.com/google/apis-client-generator.git -# xcode-select --install -# brew install python@2 -# export PATH="/usr/local/opt/python@2/libexec/bin:$PATH" -# pip install --upgrade pip setuptools -# pip install . - -# Generate only the testing library since the others are published officially already. - -# generate_library \ -# --input=./storage_v1.json \ -# --language=java \ -# --output_dir=./storage - -rm -rf "./test_api/src/" - - generate_library \ - --input=./json/testing_v1.json \ - --language=java \ - --package_path=api/services \ - --output_dir=./test_api/src/main/java - -mv ./test_api/src/main/java/pom.xml ./test_api/pom.xml - -# generate_library \ -# --input=./toolresults_v1beta3.json \ -# --language=java \ -# --output_dir=./apis/toolresults +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell firebase generateJavaClient diff --git a/firebase_apis/update_api_json.bat b/firebase_apis/update_api_json.bat new file mode 100755 index 0000000000..1de7925542 --- /dev/null +++ b/firebase_apis/update_api_json.bat @@ -0,0 +1,2 @@ +SET DIR=%~dp0 +%DIR%\..\..\flank-scripts\bash\flankScripts.bat shell firebase updateApiJson diff --git a/firebase_apis/update_api_json.sh b/firebase_apis/update_api_json.sh index f1635105cc..a07105b5b1 100755 --- a/firebase_apis/update_api_json.sh +++ b/firebase_apis/update_api_json.sh @@ -1,18 +1,2 @@ -#!/usr/bin/env bash - -# npm -g install sort-json - -# Note: API discovery JSON is out of date. Check the gcloud CLI repo for most recent JSON. -# https://github.com/bootstraponline/gcloud_cli/blob/master/google-cloud-sdk/lib/googlecloudsdk/third_party/apis/testing_v1.json - -cd json - -TOOL_RESULTS=toolresults_v1beta3.json -rm "$TOOL_RESULTS" -curl -o "$TOOL_RESULTS" https://www.googleapis.com/discovery/v1/apis/toolresults/v1beta3/rest -sort-json "$TOOL_RESULTS" - -TESTING=testing_v1.json -rm "$TESTING" -curl -o "$TESTING" https://www.googleapis.com/discovery/v1/apis/testing/v1/rest -sort-json "$TESTING" +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell firebase updateApiJson diff --git a/flank-scripts/README.md b/flank-scripts/README.md index 79c736387b..4eaaa3791a 100644 --- a/flank-scripts/README.md +++ b/flank-scripts/README.md @@ -163,3 +163,84 @@ All [testArtifacts](../flank-scripts/src/main/kotlin/flank/scripts/testartifacts #### `resolve` Automatically prepare local artifacts if needed. +### Shell + +To show all available commands for shell use: `flankScripts shell` + +Available commands are: + - `firebase` Contains all firebase commands + - `iosBuildExample` Build example ios app + - `iosBuildFtl` Build ftl ios app + - `iosRunFtlLocal` Run ftl locally ios app + - `iosUniversalFramework` Create Universal Framework + - `ops` Contains all ops command: android, ios, gp + - `updateBinaries` Update binaries used by Flank + - `buildFlank` Build Flank + +#### `firebase` + +Contains tasks related to firebase client generation. +These tasks are : + - `updateApiJson` Download file for generating client + - `generateJavaClient` Generates Java client + +##### `updateApiJson` +Download file for generating client + +##### `generateJavaClient` +Generate Java Client from json schema + +#### `iosBuildExample` +Build example ios app + +#### `iosBuildFtl` +Build ftl ios app + +#### `iosRunFtlLocal` +Run ftl locally ios app + +| Option | Description | +|-------------|--------------------------------------------------------------------------| +| --device-id | Device id. Please take it from Xcode -> Window -> Devices and Simulators | + +#### `iosUniversalFramework` + +#### `ops` +Contains tasks related to building sample apps with tests. +These tasks are : + - `go` Build go app with tests + - `ios` Build ios app with tests + - `android` Build android apks with tests + +##### `go` +Build go app with tests + +##### `build_earl_grey_example` +Build ios earl grey example app with tests + +| Option | Short option | Description | +|------------|--------------|--------------------------| +| --generate | -g | Make build | +| --copy | -c | Copy output files to tmp | + +##### `build_flank_example` +Build ios flank example app with tests + +| Option | Short option | Description | +|------------|--------------|--------------------------| +| --generate | -g | Make build | +| --copy | -c | Copy output files to tmp | + +##### `android` +Build android apks with tests + +| Option | Short option | Description | +|------------|--------------|--------------------------| +| --generate | -g | Make build | +| --copy | -c | Copy output files to tmp | + +#### `updateBinaries` +Update binaries used by Flank + +#### `buildFlank` +Build Flank test runner diff --git a/flank-scripts/bash/buildFlankScripts.bat b/flank-scripts/bash/buildFlankScripts.bat index 7c701773a2..aeeb65e9c6 100755 --- a/flank-scripts/bash/buildFlankScripts.bat +++ b/flank-scripts/bash/buildFlankScripts.bat @@ -1,4 +1,3 @@ -Rem REPLACE with #1246 SET DIR=%~dp0 SET FLANK_SCRIPTS=%DIR%\.. diff --git a/flank-scripts/bash/flankScripts.bat b/flank-scripts/bash/flankScripts.bat index 8158f96d59..bed24e6664 100755 --- a/flank-scripts/bash/flankScripts.bat +++ b/flank-scripts/bash/flankScripts.bat @@ -1,4 +1,8 @@ SET DIR=%~dp0 SET scriptsJar=%DIR%\flankScripts.jar +if not exist "%scriptsJar%" { + %DIR%\buildFlankScripts.bat +} + java -jar "%scriptsJar%" %* diff --git a/flank-scripts/build.gradle.kts b/flank-scripts/build.gradle.kts index b4f2c6c6fc..e89b908efe 100644 --- a/flank-scripts/build.gradle.kts +++ b/flank-scripts/build.gradle.kts @@ -74,6 +74,8 @@ dependencies { implementation(Dependencies.JCABI_GITHUB) implementation(Dependencies.SLF4J_NOP) implementation(Dependencies.GLASSFISH_JSON) + implementation(Dependencies.ARCHIVE_LIB) + implementation(Dependencies.TUKAANI_XZ) detektPlugins(Dependencies.DETEKT_FORMATTING) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/Main.kt b/flank-scripts/src/main/kotlin/flank/scripts/Main.kt index 082ff2dabd..81dfc4fe8f 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/Main.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/Main.kt @@ -5,6 +5,7 @@ import com.github.ajalt.clikt.core.subcommands import flank.scripts.ci.CiCommand import flank.scripts.dependencies.DependenciesCommand import flank.scripts.release.ReleaseCommand +import flank.scripts.shell.ShellCommand import flank.scripts.testartifacts.TestArtifactsCommand class Main : CliktCommand(name = "flankScripts") { @@ -17,6 +18,7 @@ fun main(args: Array) { ReleaseCommand(), CiCommand(), DependenciesCommand, - TestArtifactsCommand() + TestArtifactsCommand(), + ShellCommand ).main(args) } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptions.kt b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptions.kt index 3299901d03..817f6e2c1d 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptions.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptions.kt @@ -16,3 +16,9 @@ class BugsnagException(val body: BugSnagResponse) : FlankScriptsExceptions() { return "Error while doing Bugnsag request, because of ${body.errors.joinToString()}" } } + +class ShellCommandException(private val errorMessage: String) : FlankScriptsExceptions() { + override fun toString(): String { + return "Error while executing shell command, details: $errorMessage" + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/BuildFlank.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/BuildFlank.kt new file mode 100644 index 0000000000..c8fe356b00 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/BuildFlank.kt @@ -0,0 +1,35 @@ +package flank.scripts.shell + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.createGradleCommand +import flank.scripts.shell.utils.rootDirectoryPathString +import flank.scripts.utils.runCommand +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption + +object BuildFlankCommand : CliktCommand(name = "buildFlank", help = "Build Flank") { + override fun run() { + buildFlank() + } +} + +private fun buildFlank() { + createGradleCommand( + workingDir = rootDirectoryPathString, + "-p", rootDirectoryPathString, ":test_runner:clean", ":test_runner:assemble", ":test_runner:shadowJar" + ) + .runCommand() + + copyFlankOutputFile() +} + +private fun copyFlankOutputFile() { + val flankDirectory = Paths.get(rootDirectoryPathString, "test_runner").toString() + + Files.copy( + Paths.get(flankDirectory, "build", "libs", "flank.jar"), + Paths.get(flankDirectory, "bash", "flank.jar"), + StandardCopyOption.REPLACE_EXISTING + ) +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ShellCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ShellCommand.kt new file mode 100644 index 0000000000..69bcaf5ca2 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ShellCommand.kt @@ -0,0 +1,33 @@ +package flank.scripts.shell + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import flank.scripts.shell.firebase.FirebaseCommand +import flank.scripts.shell.ios.BuildExampleCommand +import flank.scripts.shell.ios.BuildFtlCommand +import flank.scripts.shell.ios.InstallXcPrettyCommand +import flank.scripts.shell.ios.RunFtlLocalCommand +import flank.scripts.shell.ios.SetupIosEnvCommand +import flank.scripts.shell.ios.UniversalFrameworkCommand +import flank.scripts.shell.ops.OpsCommand +import flank.scripts.shell.updatebinaries.UpdateBinariesCommand + +object ShellCommand : CliktCommand(name = "shell", help = "Task for shell commands") { + init { + subcommands( + FirebaseCommand, + BuildExampleCommand, + BuildFtlCommand, + RunFtlLocalCommand, + UniversalFrameworkCommand, + OpsCommand, + UpdateBinariesCommand, + BuildFlankCommand, + InstallXcPrettyCommand, + SetupIosEnvCommand + ) + } + + @Suppress("EmptyFunctionBlock") + override fun run() {} +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/FirebaseCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/FirebaseCommand.kt new file mode 100644 index 0000000000..80d68d6377 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/FirebaseCommand.kt @@ -0,0 +1,18 @@ +package flank.scripts.shell.firebase + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands + +object FirebaseCommand : CliktCommand(name = "firebase", help = "Contains all firebase commands") { + + init { + subcommands( + UpdateApiJsonCommand, + GenerateJavaClientCommand + ) + } + + @Suppress("EmptyFunctionBlock") + override fun run() { + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/GenerateJavaClientCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/GenerateJavaClientCommand.kt new file mode 100644 index 0000000000..ff7f1ddee5 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/GenerateJavaClientCommand.kt @@ -0,0 +1,36 @@ +package flank.scripts.shell.firebase + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.exceptions.ShellCommandException +import flank.scripts.utils.checkIfPipInstalled +import flank.scripts.utils.installClientGeneratorIfNeeded +import flank.scripts.utils.runCommand +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption + +object GenerateJavaClientCommand : CliktCommand(name = "generateJavaClient", help = "Generate Java Client") { + + override fun run() { + checkIfPipInstalled() + installClientGeneratorIfNeeded() + val apiPath = Paths.get("test_api").toString() + val outputDirectory = Paths.get(apiPath, "src", "main", "java").toString() + val testingJsonInput = Paths.get("json", "testing_v1.json").toString() + Paths.get(apiPath, "src").toFile().deleteRecursively() + + val generateLibraryCommand = "generate_library " + + "--input=$testingJsonInput " + + "--language=java " + + "--output_dir=$outputDirectory" + + val result = generateLibraryCommand.runCommand() + if (result != 0) throw ShellCommandException("Error when execute generate_library command") + + Files.move( + Paths.get(outputDirectory, "pom.xml"), + Paths.get(apiPath, "pom.xml"), + StandardCopyOption.REPLACE_EXISTING + ) + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/UpdateApiJsonCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/UpdateApiJsonCommand.kt new file mode 100644 index 0000000000..831bfa5a79 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/firebase/UpdateApiJsonCommand.kt @@ -0,0 +1,31 @@ +package flank.scripts.shell.firebase + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.currentPath +import flank.scripts.utils.downloadFile +import flank.scripts.utils.downloadSortJsonIfNeeded +import flank.scripts.utils.runCommand +import java.nio.file.Paths + +object UpdateApiJsonCommand : CliktCommand(name = "updateApiJson", help = "Download file for generating client") { + override fun run() { + val jsonDirectoryPath = Paths.get(currentPath.toString(), "json") + val testingV1Path = Paths.get(jsonDirectoryPath.toString(), "testing_v1.json").toString() + val testingV1Beta3Path = Paths.get(jsonDirectoryPath.toString(), "toolresults_v1beta3.json").toString() + + downloadFile( + "https://raw.githubusercontent.com/Flank/gcloud_cli/master/google-cloud-sdk/lib/googlecloudsdk/third_party/apis/testing_v1.json", + testingV1Path + ) + + downloadFile( + "https://raw.githubusercontent.com/Flank/gcloud_cli/master/google-cloud-sdk/lib/googlecloudsdk/third_party/apis/toolresults_v1beta3.json", + testingV1Beta3Path + ) + + downloadSortJsonIfNeeded() + + "sort-json $testingV1Path".runCommand() + "sort-json $testingV1Beta3Path".runCommand() + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildExample.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildExample.kt new file mode 100644 index 0000000000..bdbf611155 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildExample.kt @@ -0,0 +1,57 @@ + +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.currentPath +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.shell.utils.pipe +import flank.scripts.utils.archive +import flank.scripts.utils.downloadXcPrettyIfNeeded +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.stream.Collectors + +object BuildExampleCommand : CliktCommand(name = "iosBuildExample", help = "Build example ios app") { + override fun run() { + failIfWindows() + downloadXcPrettyIfNeeded() + buildExample() + } +} + +private fun buildExample() { + val dataPath: Path = Paths.get(currentPath.toString(), "dd_tmp").apply { + toFile().deleteRecursively() + } + + val xcodeCommandSwiftTests = createIosBuildCommand( + dataPath.toString(), + "./EarlGreyExample.xcworkspace", + "EarlGreyExampleSwiftTests" + ) + xcodeCommandSwiftTests pipe "xcpretty" + + val xcodeCommandTests = createIosBuildCommand(dataPath.toString(), "./EarlGreyExample.xcworkspace", "EarlGreyExampleTests") + xcodeCommandTests pipe "xcpretty" + + copyExampleOutputFiles(dataPath.toString()) +} + +private fun copyExampleOutputFiles(dataPath: String) { + val archiveFileName = "earlgrey_example.zip" + val buildProductPath = Paths.get(dataPath, "Build", "Products") + + Files.walk(Paths.get("")) + .filter { it.fileName.toString().endsWith("-iphoneos") || it.fileName.toString().endsWith(".xctestrun") } + .map { it.toFile() } + .collect(Collectors.toList()) + .archive(archiveFileName, currentPath.toFile()) + + Files.move( + Paths.get("", archiveFileName), + Paths.get(buildProductPath.toString(), archiveFileName), + StandardCopyOption.REPLACE_EXISTING + ) +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildFtl.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildFtl.kt new file mode 100644 index 0000000000..8b355490e0 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/BuildFtl.kt @@ -0,0 +1,47 @@ +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.shell.utils.pipe +import flank.scripts.utils.archive +import flank.scripts.utils.downloadXcPrettyIfNeeded +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.stream.Collectors + +object BuildFtlCommand : CliktCommand(name = "iosBuildFtl", help = "Build ftl ios app") { + override fun run() { + failIfWindows() + downloadXcPrettyIfNeeded() + buildFtl() + } +} + +private fun buildFtl() { + val dataPath = Paths.get("", "dd_tmp").apply { + toFile().deleteRecursively() + }.toString() + val xcodeCommand = createIosBuildCommand(dataPath, "./EarlGreyExample.xcworkspace", "\"EarlGreyExampleSwiftTests\"") + + xcodeCommand pipe "xcpretty" + copyFtlOutputFiles(dataPath) +} + +private fun copyFtlOutputFiles(dataPath: String) { + val archiveFileName = "earlgrey_example.zip" + val buildProductPath = Paths.get(dataPath, "Build", "Products") + val currentPath = Paths.get("") + + Files.walk(currentPath) + .filter { it.fileName.toString().endsWith("-iphoneos") || it.fileName.toString().endsWith(".xctestrun") } + .map { it.toFile() } + .collect(Collectors.toList()) + .archive(archiveFileName, currentPath.toFile()) + + Files.move( + Paths.get("", archiveFileName), + Paths.get(buildProductPath.toString(), archiveFileName), + StandardCopyOption.REPLACE_EXISTING + ) +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/InstallXcPrettyCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/InstallXcPrettyCommand.kt new file mode 100644 index 0000000000..86247fc3ab --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/InstallXcPrettyCommand.kt @@ -0,0 +1,12 @@ +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.utils.downloadXcPrettyIfNeeded + +object InstallXcPrettyCommand : CliktCommand(name = "install_xcpretty", help = "Build ios app with tests") { + override fun run() { + failIfWindows() + downloadXcPrettyIfNeeded() + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/IosBuildCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/IosBuildCommand.kt new file mode 100644 index 0000000000..947fe99784 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/IosBuildCommand.kt @@ -0,0 +1,10 @@ +package flank.scripts.shell.ios + +fun createIosBuildCommand(buildDir: String, workspace: String, scheme: String, project: String = "") = + "xcodebuild build-for-testing" + + " -allowProvisioningUpdates" + + (if (workspace.isBlank()) "" else " -workspace $workspace") + + (if (project.isBlank()) "" else " -project $project") + + " -scheme $scheme" + + " -derivedDataPath $buildDir" + + " -sdk iphoneos" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/LipoHelper.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/LipoHelper.kt new file mode 100644 index 0000000000..d442ed3176 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/LipoHelper.kt @@ -0,0 +1,6 @@ +package flank.scripts.shell.ios + +fun createLipoCommand( + outputPath: String, + vararg files: String +) = "lipo -create ${files.joinToString(" ")} -output $outputPath" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/RunFtlLocal.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/RunFtlLocal.kt new file mode 100644 index 0000000000..ff20f624ab --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/RunFtlLocal.kt @@ -0,0 +1,32 @@ +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import flank.scripts.shell.utils.currentPath +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.utils.runCommand +import java.nio.file.Path +import java.nio.file.Paths + +object RunFtlLocalCommand : CliktCommand(name = "iosRunFtlLocal", help = "Run ftl locally ios app") { + + private val deviceId by option(help = "Device id. Please take it from Xcode -> Window -> Devices and Simulators") + .required() + + override fun run() { + failIfWindows() + runFtlLocal(deviceId) + } +} + +private fun runFtlLocal(deviceId: String) { + val dataPath: Path = Paths.get(currentPath.toString(), "dd_tmp", "Build", "Products") + + val xcodeCommand = "xcodebuild test-without-building " + + " -xctestrun $dataPath/*.xctestrun " + + "-derivedDataPath $dataPath " + + "-destination 'id=$deviceId'" + + xcodeCommand.runCommand() +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/SetupIosEnvCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/SetupIosEnvCommand.kt new file mode 100644 index 0000000000..47eac9ee68 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/SetupIosEnvCommand.kt @@ -0,0 +1,17 @@ +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.ops.EARL_GREY_EXAMPLE +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.shell.utils.iOSTestProjectsPath +import flank.scripts.utils.downloadCocoaPodsIfNeeded +import flank.scripts.utils.installPods +import java.nio.file.Paths + +object SetupIosEnvCommand : CliktCommand(name = "setup_ios_env", help = "Build ios app with tests") { + override fun run() { + failIfWindows() + downloadCocoaPodsIfNeeded() + installPods(Paths.get(iOSTestProjectsPath, EARL_GREY_EXAMPLE)) + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/UniversalFramework.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/UniversalFramework.kt new file mode 100644 index 0000000000..d96be8649e --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ios/UniversalFramework.kt @@ -0,0 +1,61 @@ +package flank.scripts.shell.ios + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.currentPath +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.utils.runCommand +import java.nio.file.Paths + +object UniversalFrameworkCommand : CliktCommand(name = "iosUniversalFramework", help = "Create Universal Framework") { + override fun run() { + failIfWindows() + createUniversalFiles() + } +} + +private const val APP_FRAMEWORK_FRAMEWORK = "AppFramework.framework" +private const val APP_FRAMEWORK = "AppFramework" + +private fun createUniversalFiles() { + val comboPath = Paths.get(currentPath.toString(), "ios-frameworks").toString() + val devicePath = Paths.get(comboPath, "Debug-iphoneos").toString() + val simPath = Paths.get(comboPath, "Debug-iphonesimulator").toString() + + listOf( + "libChannelLib.a", + "libCommonLib.a", + "libeDistantObject.a", + "libTestLib.a", + "libUILib.a" + ).map { fileName -> + createLipoCommand( + outputPath = Paths.get(comboPath, fileName).toString(), + Paths.get(devicePath, fileName).toString(), Paths.get(simPath, fileName).toString() + ) + }.forEach { command -> command.runCommand() } + + copyAppFrameworkFiles(devicePath, comboPath) + + runDsym( + universalFileOutput = Paths.get(comboPath, APP_FRAMEWORK_FRAMEWORK, APP_FRAMEWORK).toString(), + comboPath = comboPath, + files = arrayOf( + Paths.get(devicePath, APP_FRAMEWORK_FRAMEWORK, APP_FRAMEWORK).toString(), + Paths.get(simPath, APP_FRAMEWORK_FRAMEWORK, APP_FRAMEWORK).toString() + ) + ) +} + +private fun copyAppFrameworkFiles(fromPath: String, toPath: String) { + Paths.get(fromPath, APP_FRAMEWORK_FRAMEWORK).toFile() + .copyRecursively(Paths.get(toPath, APP_FRAMEWORK_FRAMEWORK).toFile(), overwrite = true) +} + +private fun runDsym( + universalFileOutput: String, + comboPath: String, + files: Array +) { + createLipoCommand(universalFileOutput, *files).runCommand() + "dsymutil $universalFileOutput --out ${Paths.get(comboPath, "AppFramework.framework.dSYM")}".runCommand() +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/AndroidOpsCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/AndroidOpsCommand.kt new file mode 100644 index 0000000000..d837f45723 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/AndroidOpsCommand.kt @@ -0,0 +1,26 @@ +package flank.scripts.shell.ops + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.multiple +import com.github.ajalt.clikt.parameters.options.option + +object AndroidOpsCommand : CliktCommand(name = "android", help = "Build android apks with tests") { + + private val generate: Boolean by option(help = "Make build").flag("-g", default = true) + + private val copy: Boolean by option(help = "Copy output files to tmp").flag("-c", default = true) + + private val artifacts: List by option().multiple() + + override fun run() { + if (generate.not()) return + AndroidBuildConfiguration(artifacts, generate, copy).run { + buildBaseApk() + buildBaseTestApk() + buildDuplicatedNamesApks() + buildMultiModulesApks() + buildCucumberSampleApp() + } + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildAndroid.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildAndroid.kt new file mode 100644 index 0000000000..3bb9c033c5 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildAndroid.kt @@ -0,0 +1,118 @@ +package flank.scripts.shell.ops + +import flank.scripts.shell.utils.androidTestProjectsPath +import flank.scripts.shell.utils.createGradleCommand +import flank.scripts.shell.utils.flankFixturesTmpPath +import flank.scripts.utils.runCommand +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardCopyOption + +fun AndroidBuildConfiguration.buildBaseApk() { + if (artifacts.canExecute("buildBaseApk").not()) return + + createGradleCommand( + workingDir = androidTestProjectsPath, + options = listOf("-p", androidTestProjectsPath, "app:assemble") + ).runCommand() + + if (copy) copyBaseApk() +} + +private fun copyBaseApk() { + val outputDir = Paths.get(flankFixturesTmpPath, "apk", "app-debug.apk") + + if (!outputDir.parent.toFile().exists()) Files.createDirectories(outputDir.parent) + + val assembleDirectory = Paths.get(androidTestProjectsPath, "app", "build", "outputs", "apk", "singleSuccess", "debug", "app-single-success-debug.apk") + Files.copy(assembleDirectory, outputDir, StandardCopyOption.REPLACE_EXISTING) +} + +fun AndroidBuildConfiguration.buildBaseTestApk() { + if (artifacts.canExecute("buildBaseTestApk").not()) return + createGradleCommand( + workingDir = androidTestProjectsPath, + options = listOf("-p", androidTestProjectsPath, "app:assembleAndroidTest") + ).runCommand() + + if (copy) copyBaseTestApk() +} + +private fun copyBaseTestApk() { + val assembleDirectory = Paths.get(androidTestProjectsPath, "app", "build", "outputs", "apk", "androidTest") + assembleDirectory.toFile().findApks().forEach { + Files.copy(it.toPath(), Paths.get(flankFixturesTmpPath, "apk", it.name), StandardCopyOption.REPLACE_EXISTING) + } +} + +fun AndroidBuildConfiguration.buildDuplicatedNamesApks() { + if (artifacts.canExecute("buildDuplicatedNamesApks").not()) return + val modules = (0..3).map { "dir$it" } + + createGradleCommand( + workingDir = androidTestProjectsPath, + options = listOf("-p", androidTestProjectsPath) + modules.map { "$it:testModule:assembleAndroidTest" }.toList() + ).runCommand() + + if (copy) copyDuplicatedNamesApks() +} + +private fun copyDuplicatedNamesApks() { + val modules = (0..3).map { "dir$it" } + val outputDir = Paths.get(flankFixturesTmpPath, "apk", "duplicated_names") + if (!outputDir.toFile().exists()) Files.createDirectories(outputDir) + + modules.map { Paths.get(androidTestProjectsPath, it, "testModule", "build", "outputs", "apk").toFile() } + .flatMap { it.findApks().toList() } + .forEachIndexed { index, file -> + file.copyApkToDirectory(Paths.get(outputDir.toString(), modules[index], file.name)) + } +} + +private fun File.copyApkToDirectory(output: Path): Path = toPath().let { sourceFile -> + if (!output.parent.toFile().exists()) Files.createDirectories(output.parent) + Files.copy(sourceFile, output, StandardCopyOption.REPLACE_EXISTING) +} + +fun AndroidBuildConfiguration.buildMultiModulesApks() { + if (artifacts.canExecute("buildMultiModulesApks").not()) return + createGradleCommand( + workingDir = androidTestProjectsPath, + options = listOf("-p", androidTestProjectsPath, + ":multi-modules:multiapp:assemble") + (1..20).map { ":multi-modules:testModule$it:assembleAndroidTest" }).runCommand() + + if (copy) copyMultiModulesApks() +} + +private fun copyMultiModulesApks() { + val outputDir = Paths.get(flankFixturesTmpPath, "apk", "multi-modules").toString() + Paths.get(androidTestProjectsPath, "multi-modules").toFile().findApks() + .forEach { it.copyApkToDirectory(Paths.get(outputDir, it.name)) } +} + +fun AndroidBuildConfiguration.buildCucumberSampleApp() { + if (artifacts.canExecute("buildMultiModulesApks").not()) return + createGradleCommand( + workingDir = androidTestProjectsPath, + options = listOf("-p", androidTestProjectsPath, "cucumber_sample_app:cukeulator:assembleDebug", ":cucumber_sample_app:cukeulator:assembleAndroidTest") + ).runCommand() + + if (copy) copyCucumberSampleApp() +} + +private fun copyCucumberSampleApp() { + val outputDir = Paths.get(flankFixturesTmpPath, "apk", "cucumber_sample_app").toString() + Paths.get(androidTestProjectsPath, "cucumber_sample_app").toFile().findApks().copyApksToPath(outputDir) +} + +private fun File.findApks() = walk().filter { it.extension == "apk" } + +private fun Sequence.copyApksToPath(outputDirectory: String) = forEach { + it.copyApkToDirectory(Paths.get(outputDirectory, it.name)) +} + +private fun List.canExecute(actionName: String) = isEmpty() || any { it.toLowerCase() == actionName.toLowerCase() } + +data class AndroidBuildConfiguration(val artifacts: List, val generate: Boolean, val copy: Boolean) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildEarlGreyExampleCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildEarlGreyExampleCommand.kt new file mode 100644 index 0000000000..8f92a88303 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildEarlGreyExampleCommand.kt @@ -0,0 +1,35 @@ +package flank.scripts.shell.ops + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.shell.utils.iOSTestProjectsPath +import java.nio.file.Paths + +object BuildEarlGreyExampleCommand : CliktCommand(name = "build_earl_grey_example", help = "Build ios earl grey example app with tests") { + + private val generate: Boolean? by option(help = "Make build").flag("-g", default = true) + + private val copy: Boolean? by option(help = "Copy output files to tmp").flag("-c", default = true) + + override fun run() { + failIfWindows() + + IosBuildConfiguration( + projectPath = Paths.get(iOSTestProjectsPath, EARL_GREY_EXAMPLE).toString(), + projectName = EARL_GREY_EXAMPLE, + buildConfigurations = listOf( + IosTestBuildConfiguration(EARL_GREY_EXAMPLE_SWIFT_TESTS, "swift"), + IosTestBuildConfiguration(EARL_GREY_EXAMPLE_TESTS, "objective_c") + ), + useWorkspace = true, + generate = generate ?: true, + copy = copy ?: true + ).generateIos() + } +} + +const val EARL_GREY_EXAMPLE = "EarlGreyExample" +private const val EARL_GREY_EXAMPLE_TESTS = "EarlGreyExampleTests" +private const val EARL_GREY_EXAMPLE_SWIFT_TESTS = "EarlGreyExampleSwiftTests" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildFlankExampleCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildFlankExampleCommand.kt new file mode 100644 index 0000000000..5cd940f831 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildFlankExampleCommand.kt @@ -0,0 +1,32 @@ +package flank.scripts.shell.ops + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import flank.scripts.shell.utils.failIfWindows +import flank.scripts.shell.utils.iOSTestProjectsPath +import java.nio.file.Paths + +object BuildFlankExampleCommand : CliktCommand(name = "build_flank_example", help = "Build ios flank example app with tests") { + + private val generate: Boolean? by option(help = "Make build").flag("-g", default = true) + + private val copy: Boolean? by option(help = "Copy output files to tmp").flag("-c", default = true) + + override fun run() { + failIfWindows() + + IosBuildConfiguration( + projectPath = Paths.get(iOSTestProjectsPath, FLANK_EXAMPLE).toString(), + projectName = FLANK_EXAMPLE, + buildConfigurations = listOf( + IosTestBuildConfiguration(FLANK_EXAMPLE, "tests"), + ), + useWorkspace = false, + generate = generate ?: true, + copy = copy ?: true + ).generateIos() + } +} + +private const val FLANK_EXAMPLE = "FlankExample" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildIos.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildIos.kt new file mode 100644 index 0000000000..d10e9ccc33 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/BuildIos.kt @@ -0,0 +1,99 @@ +package flank.scripts.shell.ops + +import flank.scripts.shell.ios.createIosBuildCommand +import flank.scripts.shell.utils.flankFixturesIosTmpPath +import flank.scripts.shell.utils.pipe +import flank.scripts.utils.archive +import flank.scripts.utils.downloadCocoaPodsIfNeeded +import flank.scripts.utils.downloadXcPrettyIfNeeded +import flank.scripts.utils.installPods +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +fun IosBuildConfiguration.generateIos() { + downloadCocoaPodsIfNeeded() + installPods(Paths.get(projectPath)) + downloadXcPrettyIfNeeded() + if (generate) buildEarlGreyExample() +} + +private fun IosBuildConfiguration.buildEarlGreyExample() = Paths.get(projectPath, "Build") + .runBuilds(this) + .resolve("Products") + .apply { renameXctestFiles().filterFilesToCopy().archiveProject(projectName).copyIosProductFiles(projectName) } + .copyTestFiles(this) + +private fun Path.runBuilds(configuration: IosBuildConfiguration) = apply { + toFile().deleteRecursively() + val parent = toFile().parent + val workspace = + if (configuration.useWorkspace) Paths.get(parent, configuration.workspaceName).toString() + else "" + + val project = if (configuration.useWorkspace) "" + else Paths.get(parent, "${configuration.projectName}.xcodeproj").toString() + configuration.buildConfigurations.forEach { + val buildCommand = createIosBuildCommand( + parent, + workspace, + scheme = it.scheme, + project, + ) + buildCommand pipe "xcpretty" + } +} + +private fun Path.renameXctestFiles() = apply { + toFile().walk().filter { it.extension == "xctestrun" }.forEach { + it.reduceTestFileName() + } +} + +private fun Sequence.archiveProject(projectName: String) = also { + it.toList().archive("$projectName.zip", File(flankFixturesIosTmpPath, projectName)) +} + +private fun File.reduceTestFileName() = + renameTo(toPath().parent.resolve(name.reduceTestFileName()).toFile()) + +private fun String.reduceTestFileName() = + "_.*xctestrun".toRegex().replace(this, ".xctestrun") + +private fun Path.filterFilesToCopy() = + toFile().walk().filter { it.nameWithoutExtension.endsWith("-iphoneos") || it.extension == "xctestrun" } + +private fun Sequence.copyIosProductFiles(projectName: String) = forEach { + if (it.isDirectory) it.copyRecursively(Paths.get(flankFixturesIosTmpPath, projectName, it.name).toFile(), overwrite = true) + else it.copyTo(Paths.get(flankFixturesIosTmpPath, projectName, it.name).toFile(), overwrite = true) +} + +private fun Path.copyTestFiles(configuration: IosBuildConfiguration) = toString().takeIf { configuration.copy }?.let { productsDirectory -> + val appDirectory = Paths.get(productsDirectory, "Debug-iphoneos").toFile().findTestDirectories() + appDirectory.forEach { + it.walk().filter { it.isFile && it.extension == "" }.forEach { testFile -> + configuration.copyTestFile(testFile) + } + } +} + +private fun IosBuildConfiguration.copyTestFile( + fileToCopy: File, +) = + fileToCopy.copyTo(Paths.get(flankFixturesIosTmpPath, projectName, fileToCopy.name).toFile(), true) + +private fun File.findTestDirectories() = walk().filter { it.isDirectory && it.name.endsWith(".xctest") } + +data class IosBuildConfiguration( + val projectPath: String, + val projectName: String, + val buildConfigurations: List, + val useWorkspace: Boolean = false, + val generate: Boolean = true, + val copy: Boolean = true +) + +data class IosTestBuildConfiguration(val scheme: String, val outputDirectoryName: String) + +private val IosBuildConfiguration.workspaceName + get() = "$projectName.xcworkspace" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOS.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOS.kt new file mode 100644 index 0000000000..a68d806cfa --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOS.kt @@ -0,0 +1,11 @@ +package flank.scripts.shell.ops + +enum class GoOS( + val goName: String, + val directory: String, + val extension: String = "" +) { + LINUX("linux", "bin/linux"), + MAC("darwin", "bin/mac"), + WINDOWS("windows", "bin/win", ".exe"), +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOpsCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOpsCommand.kt new file mode 100644 index 0000000000..761ca0703e --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/GoOpsCommand.kt @@ -0,0 +1,36 @@ +package flank.scripts.shell.ops + +import com.github.ajalt.clikt.core.CliktCommand +import flank.scripts.shell.utils.flankFixturesTmpPath +import flank.scripts.shell.utils.testProjectsPath +import flank.scripts.utils.runCommand +import java.nio.file.Path +import java.nio.file.Paths + +object GoOpsCommand : CliktCommand(name = "go", help = "Build go app with tests") { + override fun run() { + generateGoArtifacts() + } + + private fun generateGoArtifacts() { + val goHelloBinDirectoryPath = Paths.get(testProjectsPath, "gohello", "bin").apply { + toFile().deleteRecursively() + } + GoOS.values().forEach { createExecutable(it, goHelloBinDirectoryPath) } + } + + private fun createExecutable(os: GoOS, goHelloBinDirectoryPath: Path) { + Paths.get(goHelloBinDirectoryPath.toString(), *os.directory.split('/').toTypedArray()) + .toFile() + .mkdirs() + "go build -o ${os.createOutputPathForBinary()}".runCommand( + environmentVariables = mapOf( + "GOOS" to os.goName, + "GOARCH" to "amd64" + ) + ) + } + + private fun GoOS.createOutputPathForBinary() = + Paths.get(flankFixturesTmpPath, "gohello", directory, "gohello$extension") +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/OpsCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/OpsCommand.kt new file mode 100644 index 0000000000..c13d24ab81 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/ops/OpsCommand.kt @@ -0,0 +1,18 @@ +package flank.scripts.shell.ops + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands + +object OpsCommand : CliktCommand(name = "ops", help = "Contains all ops command: android, ios, gp") { + init { + subcommands( + AndroidOpsCommand, + BuildEarlGreyExampleCommand, + BuildFlankExampleCommand, + GoOpsCommand + ) + } + + @Suppress("EmptyFunctionBlock") + override fun run() {} +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateAtomic.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateAtomic.kt new file mode 100644 index 0000000000..e4ef86440b --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateAtomic.kt @@ -0,0 +1,52 @@ +package flank.scripts.shell.updatebinaries + +import flank.scripts.utils.downloadFile +import flank.scripts.utils.extract +import java.nio.file.Files +import java.nio.file.Paths +import java.util.stream.Collectors + +private val currentPath = Paths.get("") +private val atomicPath = Paths.get(currentPath.toString(), "libatomic") + +fun updateAtomic() { + val atomicDeb = Paths.get(atomicPath.toString(), "libatomic.deb").toFile() + val atomicDataTarXz = Paths.get(atomicPath.toString(), "data.tar.xz").toFile() + + if (atomicDeb.exists()) { + println("Atomic exists") + } else { + println("Downloading Atomic...") + atomicPath.toFile().mkdirs() + downloadFile( + srcUrl = "http://mirrors.kernel.org/ubuntu/pool/main/g/gcc-8/libatomic1_8-20180414-1ubuntu2_amd64.deb", + destinationPath = atomicDeb.toString() + ) + } + + atomicDeb.extract(atomicPath.toFile(), "ar") + atomicDataTarXz.extract(atomicPath.toFile(), "tar", "xz") + findAndCopyAtomicLicense() + findAndCopyAtomicFiles() + atomicPath.toFile().deleteRecursively() +} + +private fun findAndCopyAtomicLicense() { + val licenseOutputFile = Paths.get(currentPath.toString(), "libatomic.txt").toFile() + + downloadFile( + "http://changelogs.ubuntu.com/changelogs/pool/main/g/gcc-8/gcc-8_8-20180414-1ubuntu2/copyright", + licenseOutputFile.toString() + ) +} + +private fun findAndCopyAtomicFiles() { + println("Copying atomic files ...") + val list = Files.walk(atomicPath) + .filter { it.toString().endsWith("libatomic.so.1") || it.toString().endsWith("libatomic.so.1.2.0") } + .collect(Collectors.toList()) + + list.forEach { + it.toFile().copyTo(Paths.get(currentPath.toString(), it.fileName.toString()).toFile(), true) + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateBinariesCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateBinariesCommand.kt new file mode 100644 index 0000000000..b42fe638ad --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateBinariesCommand.kt @@ -0,0 +1,21 @@ +package flank.scripts.shell.updatebinaries + +import com.github.ajalt.clikt.core.CliktCommand +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +object UpdateBinariesCommand : CliktCommand(name = "updateBinaries", help = "Update binaries used by Flank") { + + override fun run() { + runBlocking { + listOf( + launch { updateAtomic() }, + launch { updateLlvm() }, + launch { updateSwift() } + ).joinAll() + + println("Binaries updated") + } + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateLlvm.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateLlvm.kt new file mode 100644 index 0000000000..4e652d8312 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateLlvm.kt @@ -0,0 +1,78 @@ +package flank.scripts.shell.updatebinaries + +import flank.scripts.utils.downloadFile +import flank.scripts.utils.extract +import flank.scripts.utils.isWindows +import java.nio.file.Files +import java.nio.file.Paths + +private val currentPath = Paths.get("") +private val llvmPath = Paths.get(currentPath.toString(), "llvm") + +fun updateLlvm() = if (isWindows) updateLlvmWindows() else updateLlvmNonWindows() + +private fun updateLlvmWindows() { + println(" Will be available after #1134") + /* + TODO finish this in #1134 + val llvmExe = Paths.get(llvmPath.toString(), "LLVM-win64.exe") + if (llvmExe.toFile().exists()) { + println("LLVM exists") + } else { + println("Downloading Windows LLVM...") + llvmPath.toFile().mkdirs() + downloadFile( + srcUrl = "https://releases.llvm.org/8.0.0/LLVM-8.0.0-win64.exe", + destinationPath = llvmExe.toString() + ) + } + + llvmExe.toFile().extract(llvmPath.toFile(), "zip", "xz") + findAndCopyLlvmLicense() + findAndCopyLlvmNmFile() + llvmPath.toFile().deleteRecursively()*/ +} + +private fun updateLlvmNonWindows() { + val llvmTarXz = Paths.get(llvmPath.toString(), "llvm.tar.xz") + + if (llvmTarXz.toFile().exists()) { + println("LLVM exists") + } else { + println("Downloading LLVM...") + llvmPath.toFile().mkdirs() + downloadFile( + srcUrl = "http://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz", + destinationPath = llvmTarXz.toString() + ) + } + + llvmTarXz.toFile().extract(llvmPath.toFile(), "tar", "xz") + findAndCopyLlvmLicense() + findAndCopyLlvmNmFile() + llvmPath.toFile().deleteRecursively() +} + +private fun findAndCopyLlvmLicense() { + val licensePathSuffix = Paths.get("include", "llvm", "Support", "LICENSE.TXT").toString() + val licenseOutputFile = Paths.get(currentPath.toString(), "llvm.txt").toFile() + + println("Copying license ...") + Files.walk(llvmPath) + .filter { it.toString().endsWith(licensePathSuffix) } + .findFirst() + .takeIf { it.isPresent } + ?.run { get().toFile().copyTo(licenseOutputFile, overwrite = true) } +} + +private fun findAndCopyLlvmNmFile() { + val llvmNmSuffix = Paths.get("bin", "llvm-nm").toString() + val llvmNmOutputFile = Paths.get(currentPath.toString(), "nm").toFile() + + println("Copying llvm nm ...") + Files.walk(llvmPath) + .filter { it.toString().endsWith(llvmNmSuffix) } + .findFirst() + .takeIf { it.isPresent } + ?.run { get().toFile().copyTo(llvmNmOutputFile, overwrite = true) } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateSwift.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateSwift.kt new file mode 100644 index 0000000000..fe109b2734 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/updatebinaries/UpdateSwift.kt @@ -0,0 +1,79 @@ +package flank.scripts.shell.updatebinaries + +import flank.scripts.utils.downloadFile +import flank.scripts.utils.extract +import flank.scripts.utils.isWindows +import java.nio.file.Files +import java.nio.file.Paths + +private val currentPath = Paths.get("") +private val swiftPath = Paths.get(currentPath.toString(), "swift") + +fun updateSwift() = if (isWindows) updateSwiftWindows() else updateSwiftOther() + +private fun updateSwiftWindows() { + println(" Will be available after #1134") + /* + TODO finish this in #1134 + val swiftExe = Paths.get(swiftPath.toString(), "swift.exe") + + if (swiftExe.toFile().exists()) { + println("Swift exists") + } else { + println("Downloading swift for Windows") + swiftPath.toFile().mkdirs() + downloadFile( + srcUrl = "https://swift.org/builds/swift-5.3-release/windows10/swift-5.3-RELEASE/swift-5.3-RELEASE-windows10.exe", + destinationPath = swiftExe.toString() + ) + } + + swiftExe.toFile().extract(swiftPath.toFile(), "zip", "gz") + findAndCopySwiftLicense() + findAndCopySwiftDemangleFile() + swiftPath.toFile().deleteRecursively()*/ +} + +private fun updateSwiftOther() { + val swiftTarGz = Paths.get(swiftPath.toString(), "swift.tar.gz") + + if (swiftTarGz.toFile().exists()) { + println("Swift exists") + } else { + println("Downloading swift") + swiftPath.toFile().mkdirs() + downloadFile( + srcUrl = "https://swift.org/builds/swift-5.0.1-release/ubuntu1604/swift-5.0.1-RELEASE/swift-5.0.1-RELEASE-ubuntu16.04.tar.gz", + destinationPath = swiftTarGz.toString() + ) + } + + swiftTarGz.toFile().extract(swiftPath.toFile(), "tar", "gz") + findAndCopySwiftLicense() + findAndCopySwiftDemangleFile() + swiftPath.toFile().deleteRecursively() +} + +private fun findAndCopySwiftLicense() { + val licenseFileSuffix = Paths.get("usr", "share", "swift", "LICENSE.txt").toString() + val licenseOutputFile = Paths.get(currentPath.toString(), "swift.txt").toFile() + + println("Copying license ...") + Files.walk(swiftPath) + .filter { it.toString().endsWith(licenseFileSuffix) } + .findFirst() + .takeIf { it.isPresent } + ?.run { get().toFile().copyTo(licenseOutputFile, overwrite = true) } +} + +private fun findAndCopySwiftDemangleFile() { + val switftDemangleFileSuffix = Paths.get("usr", "bin", "swift-demangle").toString() + val switftDemangleOutputFile = Paths.get(currentPath.toString(), "swift-demangle").toFile() + + println("Copying swift-demangle ...") + Files.walk(swiftPath) + .filter { it.toString().endsWith(switftDemangleFileSuffix) } + .findFirst() + .takeIf { it.isPresent } + ?.run { get().toFile().copyTo(switftDemangleOutputFile, overwrite = true) } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/FastFailForWindows.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/FastFailForWindows.kt new file mode 100644 index 0000000000..2aec5874d0 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/FastFailForWindows.kt @@ -0,0 +1,11 @@ +package flank.scripts.shell.utils + +import flank.scripts.utils.isWindows +import kotlin.system.exitProcess + +fun failIfWindows() { + if (isWindows) { + println("This script does not work on Windows") + exitProcess(1) + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/GradleCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/GradleCommand.kt new file mode 100644 index 0000000000..c4021fe718 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/GradleCommand.kt @@ -0,0 +1,17 @@ +package flank.scripts.shell.utils + +import flank.scripts.utils.isWindows +import java.nio.file.Paths + +fun createGradleCommand( + workingDir: String, + vararg options: String +) = createGradleCommand(workingDir, options.asList()) + +fun createGradleCommand( + workingDir: String, + options: List +) = "${Paths.get(workingDir, gradleExecutable)} ${options.joinToString(" ")}" + +private val gradleExecutable: String + get() = if (isWindows) "gradlew.bat" else "./gradlew" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/PathHelper.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/PathHelper.kt new file mode 100644 index 0000000000..3fe4748e95 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/PathHelper.kt @@ -0,0 +1,22 @@ +package flank.scripts.shell.utils + +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.Path + +val currentPath = Paths.get("") +val rootDirectoryPath = goToRoot(currentPath) +val rootDirectoryPathString = rootDirectoryPath.toString() + +fun goToRoot(startPath: Path): Path = + if (startPath.isRoot()) startPath.toAbsolutePath() else goToRoot(startPath.toAbsolutePath().parent) + +fun Path.isRoot() = Files.exists(Paths.get(toString(), "settings.gradle.kts")) + +val testProjectsPath = Paths.get(rootDirectoryPathString, "test_projects").toString() +val androidTestProjectsPath = Paths.get(testProjectsPath, "android").toString() +val iOSTestProjectsPath = Paths.get(testProjectsPath, "ios").toString() +val flankFixturesTmpPath = + Paths.get(rootDirectoryPathString, "test_runner", "src", "test", "kotlin", "ftl", "fixtures", "tmp").toString() +val flankFixturesIosTmpPath = + Paths.get(flankFixturesTmpPath, "ios").toString() diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/ShellHelper.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/ShellHelper.kt new file mode 100644 index 0000000000..f086d9c36a --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/ShellHelper.kt @@ -0,0 +1,12 @@ +package flank.scripts.shell.utils + +import flank.scripts.utils.isWindows +import flank.scripts.utils.runCommand + +infix fun String.pipe(command: String) { + if (isWindows) { + listOf("cmd", "/C", "$this | $command").runCommand() + } else { + listOf("/bin/bash", "-c", "$this | $command").runCommand() + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/TestFilters.kt b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/TestFilters.kt new file mode 100644 index 0000000000..7a18faeeea --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/shell/utils/TestFilters.kt @@ -0,0 +1,28 @@ +package flank.scripts.shell.utils + +private const val PACKAGE = "com.example.test_app" +private const val PACKAGE_FOO = "com.example.test_app.foo" +private const val PACKAGE_BAR = "com.example.test_app.bar" + +private const val ANNOTATION = "$PACKAGE.Annotation" + +private const val CLASS = "$PACKAGE.InstrumentedTest" +private const val CLASS_FOO = "$PACKAGE_FOO.FooInstrumentedTest" +private const val CLASS_BAR = "$PACKAGE_BAR.BarInstrumentedTest" + +private const val METHOD_1 = "$CLASS#test1" +private const val METHOD_FOO = "$CLASS_FOO#testFoo" + +private const val RUNNER = "com.example.test_app.test/androidx.test.runner.AndroidJUnitRunner" + +val runAllTestsShellCommand: String + get() = "adb shell am instrument -r -w \$@ $RUNNER" + +val filterPackageBarClassFooMethod1Command: String + get() = "$runAllTestsShellCommand -e package $PACKAGE_BAR -e class $CLASS_FOO -e class $METHOD_1" + +val filterAnnotationMethodFooCommand: String + get() = "$runAllTestsShellCommand -e annotation $ANNOTATION -e class $METHOD_FOO" + +val filterNotPackageFooNotClassBarCommand: String + get() = "$runAllTestsShellCommand -e notPackage $PACKAGE_FOO -e notClass $CLASS_BAR" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/testartifacts/core/DownloadFixtures.kt b/flank-scripts/src/main/kotlin/flank/scripts/testartifacts/core/DownloadFixtures.kt index 1291654cf6..3186f43c75 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/testartifacts/core/DownloadFixtures.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/testartifacts/core/DownloadFixtures.kt @@ -2,7 +2,7 @@ package flank.scripts.testartifacts.core import com.jcabi.github.Release import flank.scripts.github.getRelease -import flank.scripts.utils.download +import flank.scripts.utils.downloadFile import java.io.File fun Context.downloadFixtures( @@ -28,7 +28,7 @@ private fun Context.downloadFixtures( ).fullName ).run { if (exists() && overwrite) delete() - if (!exists()) download(url, absolutePath).also { println("OK") } + if (!exists()) downloadFile(url, absolutePath).also { println("OK") } else println("ABORTED (already exists)") } } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/Archive.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/Archive.kt new file mode 100644 index 0000000000..ab71d3db41 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/Archive.kt @@ -0,0 +1,56 @@ +package flank.scripts.utils + +import org.rauschig.jarchivelib.ArchiveFormat +import org.rauschig.jarchivelib.ArchiverFactory +import org.rauschig.jarchivelib.CompressionType +import java.io.File + +fun File.extract( + destination: File, + archiveFormat: ArchiveFormat = ArchiveFormat.AR, + compressFormat: CompressionType? = null +) { + println("Unpacking...$name") + val archiver = if (compressFormat != null) { + ArchiverFactory.createArchiver(archiveFormat, compressFormat) + } else { + ArchiverFactory.createArchiver(archiveFormat) + } + runCatching { + archiver.extract(this, destination) + }.onFailure { + println("There was an error when unpacking $name - $it") + } +} + +fun File.extract( + destination: File, + archiveFormat: String, + compressFormat: String? = null +) { + println("Unpacking...$name") + val archiver = if (compressFormat != null) { + ArchiverFactory.createArchiver(archiveFormat, compressFormat) + } else { + ArchiverFactory.createArchiver(archiveFormat) + } + runCatching { + archiver.extract(this, destination) + }.onFailure { + println("There was an error when unpacking $name - $it") + } +} + +fun List.archive( + destinationFileName: String, + destinationDirectory: File, + archiveFormat: ArchiveFormat = ArchiveFormat.ZIP +) { + println("Packing...$destinationFileName") + val archiver = ArchiverFactory.createArchiver(archiveFormat) + runCatching { + archiver.create(destinationFileName, destinationDirectory, *toTypedArray()) + }.onFailure { + println("There was an error when packing ${destinationDirectory.absolutePath}${File.separator}$destinationFileName - $it") + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/Download.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/Download.kt index e21c666720..ddc4686644 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/utils/Download.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/Download.kt @@ -3,11 +3,11 @@ package flank.scripts.utils import com.github.kittinunf.fuel.Fuel import java.io.File -fun download( +fun downloadFile( srcUrl: String, - dstFile: String + destinationPath: String ) { Fuel.download(srcUrl) - .fileDestination { _, _ -> File(dstFile) } + .fileDestination { _, _ -> File(destinationPath) } .responseString() } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/DownloadSoftware.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/DownloadSoftware.kt new file mode 100644 index 0000000000..ca67f9a98d --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/DownloadSoftware.kt @@ -0,0 +1,30 @@ +package flank.scripts.utils + +import java.nio.file.Path + +fun downloadXcPrettyIfNeeded() { + "xcpretty".checkAndInstallIfNeed("gem install xcpretty") +} + +fun downloadCocoaPodsIfNeeded() { + "xcpretty".checkAndInstallIfNeed("gem install cocoapods -v 1.9.3") +} + +fun installPods(path: Path) { + "pod install --project-directory=$path --verbose".runCommand() +} + +fun checkIfPipInstalled() { + "pip".commandInstalledOr { + println("You need pip fot this script. To install it follow https://pip.pypa.io/en/stable/installing/") + } +} + +fun downloadSortJsonIfNeeded() { + "sort-json".checkAndInstallIfNeed("npm -g install sort-json") +} + +fun installClientGeneratorIfNeeded() { + val generateLibraryCheckCommand = (if (isWindows) "where " else "command -v ") + "generate_library" + generateLibraryCheckCommand.checkAndInstallIfNeed("pip install google-apis-client-generator") +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/Env.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/Env.kt index f865ca96dc..68ec960e1d 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/utils/Env.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/Env.kt @@ -2,3 +2,5 @@ package flank.scripts.utils fun getEnv(key: String): String = System.getenv(key) ?: throw RuntimeException("Environment variable '$key' not found!") + +val isWindows: Boolean = System.getProperty("os.name").startsWith("Windows") diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/ShellExecute.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/ShellExecute.kt index b8e1403308..f9e6f953ef 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/utils/ShellExecute.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/ShellExecute.kt @@ -2,8 +2,12 @@ package flank.scripts.utils import java.io.File -fun List.runCommand(retryCount: Int = 0, fileForOutput: File? = null) = - ProcessBuilder(this).apply { +fun List.runCommand( + retryCount: Int = 0, + fileForOutput: File? = null, + environmentVariables: Map = mapOf() +) = ProcessBuilder(this).apply { + environment().putAll(environmentVariables) if (fileForOutput != null) { redirectOutput(fileForOutput) redirectError(fileForOutput) @@ -14,8 +18,11 @@ fun List.runCommand(retryCount: Int = 0, fileForOutput: File? = null) = } .startWithRetry(retryCount) -fun String.runCommand(retryCount: Int = 0, fileForOutput: File? = null) = - split(" ").toList().runCommand(retryCount, fileForOutput) +fun String.runCommand( + retryCount: Int = 0, + fileForOutput: File? = null, + environmentVariables: Map = mapOf() +) = split(" ").toList().runCommand(retryCount, fileForOutput, environmentVariables) fun String.runForOutput(retryCount: Int = 0): String = File .createTempFile(hashCode().toString(), "") @@ -24,6 +31,16 @@ fun String.runForOutput(retryCount: Int = 0): String = File file.readText() } +fun String.checkCommandExists() = (if (isWindows) "where " else "command -v ").plus(this).runCommand() == 0 + +fun String.checkAndInstallIfNeed(installCommand: String) = checkCommandExists().takeUnless { it }?.let { + installCommand.runCommand() +} + +fun String.commandInstalledOr(orAction: () -> Unit) = checkCommandExists().takeUnless { it }?.let { + orAction() +} + internal fun ProcessBuilder.startWithRetry(retryCount: Int): Int { var retryTries = 0 var processResponse: Int diff --git a/test_projects/android/build.bat b/test_projects/android/build.bat new file mode 100644 index 0000000000..15e97ba4d8 --- /dev/null +++ b/test_projects/android/build.bat @@ -0,0 +1,2 @@ +SET DIR=%~dp0 +%DIR%\..\..\flank-scripts\bash\flankScripts.bat shell ops android --copy --generate diff --git a/test_projects/android/ops.sh b/test_projects/android/ops.sh index 00636bef56..ce68785836 100755 --- a/test_projects/android/ops.sh +++ b/test_projects/android/ops.sh @@ -1,136 +1,26 @@ #!/usr/bin/env bash +DIR=`dirname "$BASH_SOURCE"` function base_app_apk() { - local dir=$TEST_PROJECTS_ANDROID - local outputDir="$FLANK_FIXTURES_TMP/apk" - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - "$dir/gradlew" -p "$dir" app:assemble - ;; - - '--copy' | '-c') - local apkIn="$dir/app/build/outputs/apk/singleSuccess/debug/app-single-success-debug.apk" - local apkOut="$outputDir/app-debug.apk" - - mkdir -p "$outputDir" - cp "$apkIn" "$apkOut" - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops android --copy --generate --artifacts=buildBaseApk } # depends on base_app_apk function base_test_apks() { - local dir=$TEST_PROJECTS_ANDROID - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - "$dir/gradlew" -p "$dir" app:assembleAndroidTest - ;; - - '--copy' | '-c') - local outputDir="$FLANK_FIXTURES_TMP/apk" - - mkdir -p "$outputDir" - cp "$dir"/app/build/outputs/apk/androidTest/**/debug/*.apk "$outputDir/" - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops android --copy --generate --artifacts=buildBaseTestApk } # depends on base_app_apk function duplicated_names_apks() { - local dir=$TEST_PROJECTS_ANDROID - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - "$dir/gradlew" -p "$dir" \ - dir0:testModule:assembleAndroidTest \ - dir1:testModule:assembleAndroidTest \ - dir2:testModule:assembleAndroidTest \ - dir3:testModule:assembleAndroidTest - ;; - - '--copy' | '-c') - local outputDir="$FLANK_FIXTURES_TMP/apk" - local testIn="$dir/app/build/outputs/apk/androidTest/**/debug/*.apk" - - mkdir -p "$outputDir" - local dir=$(dirname "${BASH_SOURCE[0]-$0}") - local outputDir="$FLANK_FIXTURES_TMP/apk/duplicated_names/" - - for index in 0 1 2 3; do - moduleName="dir$index" - apkDir="$outputDir/$moduleName/" - mkdir -p "$apkDir" - cp "$dir/$moduleName"/testModule/build/outputs/apk/**/debug/*.apk "$apkDir" - done - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops android --copy --generate --artifacts=buildDuplicatedNamesApks } function multi_module_apks() { - local dir=$TEST_PROJECTS_ANDROID - local outputDir="$FLANK_FIXTURES_TMP/apk/multi-modules/" - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - "$dir/gradlew" -p "$dir" \ - :multi-modules:multiapp:assemble \ - :multi-modules:testModule1:assembleAndroidTest \ - :multi-modules:testModule2:assembleAndroidTest \ - :multi-modules:testModule3:assembleAndroidTest \ - :multi-modules:testModule4:assembleAndroidTest \ - :multi-modules:testModule5:assembleAndroidTest \ - :multi-modules:testModule6:assembleAndroidTest \ - :multi-modules:testModule7:assembleAndroidTest \ - :multi-modules:testModule8:assembleAndroidTest \ - :multi-modules:testModule9:assembleAndroidTest \ - :multi-modules:testModule10:assembleAndroidTest \ - :multi-modules:testModule11:assembleAndroidTest \ - :multi-modules:testModule12:assembleAndroidTest \ - :multi-modules:testModule13:assembleAndroidTest \ - :multi-modules:testModule14:assembleAndroidTest \ - :multi-modules:testModule15:assembleAndroidTest \ - :multi-modules:testModule16:assembleAndroidTest \ - :multi-modules:testModule17:assembleAndroidTest \ - :multi-modules:testModule18:assembleAndroidTest \ - :multi-modules:testModule19:assembleAndroidTest \ - :multi-modules:testModule20:assembleAndroidTest - ;; - - '--copy' | '-c') - mkdir -p "$outputDir" - find "$dir/multi-modules" -type f -name "*.apk" -exec cp {} "$outputDir" \; - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops android --copy --generate --artifacts=buildMultiModulesApks } function cucumber_sample_app() { - local dir=$TEST_PROJECTS_ANDROID - local outputDir="$FLANK_FIXTURES_TMP/apk/cucumber_sample_app/" - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - "$dir/gradlew" -p "$dir" \ - :cucumber_sample_app:cukeulator:assemble \ - :cucumber_sample_app:cukeulator:assembleAndroidTest - ;; - - '--copy' | '-c') - mkdir -p "$outputDir" - find "$dir/cucumber_sample_app" -type f -name "*.apk" -exec cp {} "$outputDir" \; - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops android --copy --generate --artifacts=buildCucumberSampleApp } echo "Android test projects ops loaded" diff --git a/test_projects/gohello/build.bat b/test_projects/gohello/build.bat new file mode 100644 index 0000000000..2955f93675 --- /dev/null +++ b/test_projects/gohello/build.bat @@ -0,0 +1,2 @@ +SET DIR=%~dp0 +%DIR%\..\..\flank-scripts\bash\flankScripts.bat shell ops go diff --git a/test_projects/gohello/build.sh b/test_projects/gohello/build.sh index a7e383a006..33510f858f 100755 --- a/test_projects/gohello/build.sh +++ b/test_projects/gohello/build.sh @@ -1,10 +1,2 @@ -#!/bin/bash - -rm -rf "./bin/" -mkdir -p bin/win -mkdir -p bin/linux -mkdir -p bin/mac - -GOOS=windows GOARCH=amd64 go build -o ./bin/win/gohello.exe -GOOS=linux GOARCH=amd64 go build -o ./bin/linux/gohello -GOOS=darwin GOARCH=amd64 go build -o ./bin/mac/gohello +DIR=`dirname "$BASH_SOURCE"` +$DIR/../../flank-scripts/bash/flankScripts shell ops go diff --git a/test_projects/ios/EarlGreyExample/EarlGreyExample.xcodeproj/project.pbxproj b/test_projects/ios/EarlGreyExample/EarlGreyExample.xcodeproj/project.pbxproj index 07123bd199..f43ad0c808 100644 --- a/test_projects/ios/EarlGreyExample/EarlGreyExample.xcodeproj/project.pbxproj +++ b/test_projects/ios/EarlGreyExample/EarlGreyExample.xcodeproj/project.pbxproj @@ -283,18 +283,18 @@ TargetAttributes = { 2CB7314C1C29E54A00CF35C1 = { CreatedOnToolsVersion = 7.2; - DevelopmentTeam = AD2V26JBWL; + DevelopmentTeam = L2UF9MLSM6; LastSwiftMigration = 0800; TestTargetID = 5F5A53781ADE67D500F81DF0; }; 5F5A53781ADE67D500F81DF0 = { CreatedOnToolsVersion = 6.3; - DevelopmentTeam = AD2V26JBWL; + DevelopmentTeam = L2UF9MLSM6; LastSwiftMigration = 0800; }; 5FDE05571B0DAA090037B82F = { CreatedOnToolsVersion = 6.3.2; - DevelopmentTeam = AD2V26JBWL; + DevelopmentTeam = L2UF9MLSM6; TestTargetID = 5F5A53781ADE67D500F81DF0; }; }; @@ -485,7 +485,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = NO; - DEVELOPMENT_TEAM = AD2V26JBWL; + DEVELOPMENT_TEAM = L2UF9MLSM6; ENABLE_TESTABILITY = YES; INFOPLIST_FILE = EarlGreyExampleSwiftTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -619,7 +619,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = AD2V26JBWL; + DEVELOPMENT_TEAM = L2UF9MLSM6; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = "$(SRCROOT)/EarlGreyExample/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -666,7 +666,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = NO; - DEVELOPMENT_TEAM = AD2V26JBWL; + DEVELOPMENT_TEAM = L2UF9MLSM6; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( diff --git a/test_projects/ios/EarlGreyExample/EarlGreyExampleSwift/ViewController.swift b/test_projects/ios/EarlGreyExample/EarlGreyExampleSwift/ViewController.swift index 1c7e517b7a..51da069885 100644 --- a/test_projects/ios/EarlGreyExample/EarlGreyExampleSwift/ViewController.swift +++ b/test_projects/ios/EarlGreyExample/EarlGreyExampleSwift/ViewController.swift @@ -20,9 +20,27 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour var tableItems = (1...50).map { $0 } + func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + override func viewDidLoad() { super.viewDidLoad() - + + let str = "viewLoaded" + let outputDirectory = getDocumentsDirectory().appendingPathComponent("output") + + do { + try FileManager.default.createDirectory(atPath: outputDirectory.path, withIntermediateDirectories: true, attributes: nil) + + let filename = outputDirectory.appendingPathComponent("test.txt") + + try str.write(to: filename, atomically: true, encoding: String.Encoding.utf8) + + } catch { + print("error on create test file") + } // Create the send message view to contain one of the two send buttons let sendMessageView = SendMessageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200)) sendMessageView.translatesAutoresizingMaskIntoConstraints = false diff --git a/test_projects/ios/EarlGreyExample/build_example.sh b/test_projects/ios/EarlGreyExample/build_example.sh index df03cd26e0..54ce63a89e 100755 --- a/test_projects/ios/EarlGreyExample/build_example.sh +++ b/test_projects/ios/EarlGreyExample/build_example.sh @@ -1,33 +1,2 @@ -#!/bin/bash - -set -euxo pipefail - -if ! [ -x "$(command -v xcpretty)" ]; then - gem install xcpretty -fi - -DD="dd_tmp" -ZIP="earlgrey_example.zip" - -rm -rf "$DD" - -xcodebuild build-for-testing \ - -allowProvisioningUpdates \ - -workspace ./EarlGreyExample.xcworkspace \ - -scheme "EarlGreyExampleSwiftTests" \ - -derivedDataPath "$DD" \ - -sdk iphoneos \ - | xcpretty - -xcodebuild build-for-testing \ - -allowProvisioningUpdates \ - -workspace ./EarlGreyExample.xcworkspace \ - -scheme "EarlGreyExampleTests" \ - -derivedDataPath "$DD" \ - -sdk iphoneos \ - | xcpretty - -pushd "$DD/Build/Products" -zip -r "$ZIP" *-iphoneos *.xctestrun -popd -mv "$DD/Build/Products/$ZIP" . +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell iosBuildExample diff --git a/test_projects/ios/EarlGreyExample/build_ftl.sh b/test_projects/ios/EarlGreyExample/build_ftl.sh index 36c2dd7a32..f2cdb7f3c3 100755 --- a/test_projects/ios/EarlGreyExample/build_ftl.sh +++ b/test_projects/ios/EarlGreyExample/build_ftl.sh @@ -1,26 +1,2 @@ -#!/bin/bash - -set -euxo pipefail - -if ! [ -x "$(command -v xcpretty)" ]; then - gem install xcpretty -fi - -DD="dd_tmp" -SCHEME="EarlGreyExampleSwiftTests" -ZIP="ios_earlgrey2.zip" - -rm -rf "$DD" - -xcodebuild build-for-testing \ - -allowProvisioningUpdates \ - -workspace ./EarlGreyExample.xcworkspace \ - -scheme "$SCHEME" \ - -derivedDataPath "$DD" \ - -sdk iphoneos \ - | xcpretty - -pushd "$DD/Build/Products" -zip -r "$ZIP" *-iphoneos *.xctestrun -popd -mv "$DD/Build/Products/$ZIP" . +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell iosBuildFtl diff --git a/test_projects/ios/EarlGreyExample/ops.sh b/test_projects/ios/EarlGreyExample/ops.sh index 9ea3d2d552..77f108e8bf 100644 --- a/test_projects/ios/EarlGreyExample/ops.sh +++ b/test_projects/ios/EarlGreyExample/ops.sh @@ -1,78 +1,19 @@ #!/usr/bin/env bash -EARL_GREY_EXAMPLE="$TEST_PROJECTS_IOS/EarlGreyExample" - function setup_ios_env() { - if ! [ -x "$(command -v xcpretty)" ]; then - gem install cocoapods -v 1.9.3 - fi - (cd "$EARL_GREY_EXAMPLE" && pod install) + $DIR/../../flank-scripts/bash/flankScripts shell setup_ios_env } function install_xcpretty() { - if ! [ -x "$(command -v xcpretty)" ]; then - gem install xcpretty - fi + $DIR/../../flank-scripts/bash/flankScripts shell install_xcpretty } function universal_framework() { - "$EARL_GREY_EXAMPLE/universal_framework.sh" + $DIR/../../flank-scripts/bash/flankScripts shell iosUniversalFramework } function earl_grey_example() { - local dir=$EARL_GREY_EXAMPLE - local buildDir="$dir/build" - - for arg in "$@"; do case "$arg" in - - '--generate' | '-g') - - install_xcpretty - - rm -rf "$buildDir" - - xcodebuild build-for-testing \ - -allowProvisioningUpdates \ - -workspace "$dir/EarlGreyExample.xcworkspace" \ - -scheme "EarlGreyExampleSwiftTests" \ - -derivedDataPath "$buildDir" \ - -sdk iphoneos | - xcpretty - - xcodebuild build-for-testing \ - -allowProvisioningUpdates \ - -workspace "$dir/EarlGreyExample.xcworkspace" \ - -scheme "EarlGreyExampleTests" \ - -derivedDataPath "$buildDir" \ - -sdk iphoneos | - xcpretty - ;; - - '--copy' | '-c') - - local productsDir="$dir/build/Build/Products" - - # xcodebuild generates .xctestrun files names in format: PROJECTNAME_platform_version_architecture.xctestrun, code below removes "_platform_version_architecture" part - mv -f "$productsDir/EarlGreyExampleSwiftTests"*.xctestrun "$productsDir/EarlGreyExampleSwiftTests.xctestrun" - mv -f "$productsDir/EarlGreyExampleTests"*.xctestrun "$productsDir/EarlGreyExampleTests.xctestrun" - - mkdir -p "$FLANK_FIXTURES_TMP/ios/earl_grey_example/objc/" - mkdir -p "$FLANK_FIXTURES_TMP/ios/earl_grey_example/swift/" - - cp -Rf "$productsDir"/*-iphoneos "$FLANK_FIXTURES_TMP/ios/earl_grey_example/" - - cp "$productsDir"/*.xctestrun "$FLANK_FIXTURES_TMP/ios/earl_grey_example/" - - cp \ - "$productsDir/Debug-iphoneos/EarlGreyExampleSwift.app/PlugIns/EarlGreyExampleTests.xctest/EarlGreyExampleTests" \ - "$FLANK_FIXTURES_TMP/ios/earl_grey_example/objc/" - - cp \ - "$productsDir/Debug-iphoneos/EarlGreyExampleSwift.app/PlugIns/EarlGreyExampleSwiftTests.xctest/EarlGreyExampleSwiftTests" \ - "$FLANK_FIXTURES_TMP/ios/earl_grey_example/swift/" - ;; - - esac done + $DIR/../../flank-scripts/bash/flankScripts shell ops ios --copy --generate } echo "iOS EarlGreyExample test projects ops loaded" diff --git a/test_projects/ios/EarlGreyExample/run_ftl_local.sh b/test_projects/ios/EarlGreyExample/run_ftl_local.sh index 600117bfb9..c81d9a56e5 100755 --- a/test_projects/ios/EarlGreyExample/run_ftl_local.sh +++ b/test_projects/ios/EarlGreyExample/run_ftl_local.sh @@ -1,15 +1,2 @@ -#!/bin/bash - -set -euxo pipefail - -DD="dd_tmp" -SCHEME="appUITests" -ZIP="ios_earlgrey2.zip" - -# Firebase test lab runs using -xctestrun -xcodebuild test-without-building \ - -xctestrun $DD/Build/Products/*.xctestrun \ - -derivedDataPath "$DD" \ - -destination 'id=ADD_YOUR_ID_HERE' - -# get device identifier in Xcode -> Window -> Devices and Simulators +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell iosRunFtlLocal diff --git a/test_projects/ios/EarlGreyExample/universal_framework.sh b/test_projects/ios/EarlGreyExample/universal_framework.sh index f703753799..cec365d10a 100755 --- a/test_projects/ios/EarlGreyExample/universal_framework.sh +++ b/test_projects/ios/EarlGreyExample/universal_framework.sh @@ -1,26 +1,2 @@ -#!/bin/bash - -set -euxo pipefail - -COMBO="./ios-frameworks" -DEVICE="$COMBO/Debug-iphoneos" -SIM="$COMBO/Debug-iphonesimulator" - -lipo -create $DEVICE/libChannelLib.a $SIM/libChannelLib.a -output $COMBO/libChannelLib.a -lipo -create $DEVICE/libCommonLib.a $SIM/libCommonLib.a -output $COMBO/libCommonLib.a -lipo -create $DEVICE/libeDistantObject.a $SIM/libeDistantObject.a -output $COMBO/libeDistantObject.a -lipo -create $DEVICE/libTestLib.a $SIM/libTestLib.a -output $COMBO/libTestLib.a -lipo -create $DEVICE/libUILib.a $SIM/libUILib.a -output $COMBO/libUILib.a - -cp -RL $DEVICE/AppFramework.framework $COMBO/AppFramework.framework -DEVICE_FRAMEWORK="$DEVICE/AppFramework.framework/AppFramework" -SIM_FRAMEWORK="$SIM/AppFramework.framework/AppFramework" -UNI_FRAMEWORK="$COMBO/AppFramework.framework/AppFramework" - -lipo -create \ - "$DEVICE_FRAMEWORK" \ - "$SIM_FRAMEWORK" \ - -output "$UNI_FRAMEWORK" - -dsymutil "$UNI_FRAMEWORK" \ - --out "$COMBO/AppFramework.framework.dSYM" +DIR=`dirname "$BASH_SOURCE"` +$DIR/../flank-scripts/bash/flankScripts shell iosUniversalFramework diff --git a/test_projects/ios/FlankExample/FlankExample/ViewController.swift b/test_projects/ios/FlankExample/FlankExample/ViewController.swift index 46087b0f6b..c2e96cf14a 100644 --- a/test_projects/ios/FlankExample/FlankExample/ViewController.swift +++ b/test_projects/ios/FlankExample/FlankExample/ViewController.swift @@ -10,9 +10,28 @@ import UIKit class ViewController: UIViewController { + func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. + + + let str = "viewLoaded" + let outputDirectory = getDocumentsDirectory().appendingPathComponent("output") + + do { + try FileManager.default.createDirectory(atPath: outputDirectory.path, withIntermediateDirectories: true, attributes: nil) + let filename = outputDirectory.appendingPathComponent("test.txt") + + try str.write(to: filename, atomically: true, encoding: String.Encoding.utf8) + + } catch { + print("error on create test file") + } } } diff --git a/test_projects/ops.bat b/test_projects/ops.bat new file mode 100644 index 0000000000..f1c35124ac --- /dev/null +++ b/test_projects/ops.bat @@ -0,0 +1,18 @@ +@ECHO OFF +SET ARG=%1 +if "%ARG%" == "android" ( + CALL ../flank-scripts/bash/flankScripts.bat shell ops android --copy --generate +) + +if "%ARG%" == "ios" ( + echo iOS Build on windows not supported +) + +if "%ARG%" == "go" ( + CALL ../flank-scripts/bash/flankScripts.bat shell ops go +) + +if "%ARG%" == "all" ( + CALL ../flank-scripts/bash/flankScripts.bat shell ops android --copy --generate + CALL ../flank-scripts/bash/flankScripts.bat shell ops go +) diff --git a/test_projects/ops.sh b/test_projects/ops.sh index 9a1e7914e0..f9ab388618 100755 --- a/test_projects/ops.sh +++ b/test_projects/ops.sh @@ -1,33 +1,27 @@ #!/usr/bin/env bash -TEST_PROJECTS_ANDROID="$TEST_PROJECTS/android" -TEST_PROJECTS_IOS="$TEST_PROJECTS/ios" - -. "$TEST_PROJECTS_ANDROID/ops.sh" -. "$TEST_PROJECTS_IOS/EarlGreyExample/ops.sh" -. "$TEST_PROJECTS_IOS/FlankExample/ops.sh" +DIR=`dirname "$BASH_SOURCE"` function update_test_artifacts() { for arg in "$@"; do case "$arg" in android) - base_app_apk --generate --copy - base_test_apks --generate --copy + $DIR/../flank-scripts/bash/flankScripts shell ops android --copy --generate ;; ios) - setup_ios_env - earl_grey_example --generate --copy - flank_ios_example --generate --copy + $DIR/../flank-scripts/bash/flankScripts shell ops ios --copy --generate ;; go) - cp -R "$FLANK_ROOT/test_projects/gohello" "$FLANK_FIXTURES_TMP/" + $DIR/../flank-scripts/bash/flankScripts shell ops go --copy --generate ;; all) - update_test_artifacts android ios go + $DIR/../flank-scripts/bash/flankScripts shell ops android --copy --generate + $DIR/../flank-scripts/bash/flankScripts shell ops ios --copy --generate + $DIR/../flank-scripts/bash/flankScripts shell ops go --copy --generate ;; esac done diff --git a/test_runner/bash/update_flank.bat b/test_runner/bash/update_flank.bat index 6f4d73a8c5..6a5e4d0489 100644 --- a/test_runner/bash/update_flank.bat +++ b/test_runner/bash/update_flank.bat @@ -1,7 +1,2 @@ - -for %%F in (%filename%) do set dirname=%%~dpF -echo "%dirname%" -cd .. -cd .. -call gradlew.bat clean assemble shadowjar -cd test_runner\bash +SET DIR=%~dp0 +%DIR%\..\..\flank-scripts\bash\flankScripts.bat shell buildFlank diff --git a/test_runner/bash/update_flank.sh b/test_runner/bash/update_flank.sh index 471ab34ed6..9090fbd33c 100755 --- a/test_runner/bash/update_flank.sh +++ b/test_runner/bash/update_flank.sh @@ -1,9 +1,3 @@ -#!/usr/bin/env bash DIR=`dirname "$BASH_SOURCE"` - -FLANK="$DIR/.." - -"$FLANK/../gradlew" -p "$FLANK" clean assemble shadowJar - -cp "$FLANK"/build/libs/flank.jar "$DIR/flank.jar" +$DIR/../../flank-scripts/bash/flankScripts shell buildFlank diff --git a/test_runner/flank.ios.yml b/test_runner/flank.ios.yml index 6c3a2cfb25..1da5dd0cab 100644 --- a/test_runner/flank.ios.yml +++ b/test_runner/flank.ios.yml @@ -77,6 +77,12 @@ gcloud: # locale: es_ES # orientation: landscape + ## A list of paths that will be copied from the device's storage to the designated results bucket after the test + ## is complete. These must be absolute paths under /private/var/mobile/Media or /Documents + ## of the app under test. If the path is under an app's /Documents, it must be prefixed with the app's bundle id and a colon + # directories-to-pull: + # - /private/var/mobile/Media + ## A list of device-path=file-path pairs that specify the paths of the test device and the files you want pushed to the device prior to testing. ## Device paths should either be under the Media shared folder (e.g. prefixed with /private/var/mobile/Media) or ## within the documents directory of the filesystem of an app under test (e.g. /Documents). Device paths to app @@ -91,9 +97,27 @@ gcloud: # - gs://bucket/additional.ipa # - path/to/local/ipa/file.ipa + ## A list of game-loop scenario numbers which will be run as part of the test (default: all scenarios). + ## A maximum of 1024 scenarios may be specified in one test matrix, but the maximum number may also be limited by the overall test --timeout setting. + # scenario-numbers: + # - 1 + # - 2 + # - 3 + + ## The path to the application archive (.ipa file) for game-loop testing. + ## The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. + ## This flag is only valid when --type=game-loop is also set + # app: + # - gs://bucket/additional.ipa OR path/to/local/ipa/file.ipa + ## The type of iOS test to run. TYPE must be one of: xctest, game-loop. Default: xctest # type: xctest + ## Enables testing special app entitlements. Re-signs an app having special entitlements with a new application-identifier. + ## This currently supports testing Push Notifications (aps-environment) entitlement for up to one app in a project. + ## Note: Because this changes the app's identifier, make sure none of the resources in your zip file contain direct references to the test app's bundle id. + # test-special-entitlements: false + flank: # -- FlankYml -- @@ -131,7 +155,7 @@ flank: ## Default: 240.0 # default-class-test-time: 30 -## Disables sharding. Useful for parameterized tests. + ## Disables sharding. Useful for parameterized tests. # disable-sharding: false ## always run - these tests are inserted at the beginning of every shard diff --git a/test_runner/src/main/kotlin/ftl/args/AndroidArgs.kt b/test_runner/src/main/kotlin/ftl/args/AndroidArgs.kt index 0476fd8395..7a0f60eda4 100644 --- a/test_runner/src/main/kotlin/ftl/args/AndroidArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/AndroidArgs.kt @@ -13,9 +13,7 @@ data class AndroidArgs( val roboDirectives: List, val roboScript: String?, val environmentVariables: Map, // should not be printed, becuase could contains sensitive informations - val directoriesToPull: List, val grantPermissions: String?, - val scenarioNumbers: List, val scenarioLabels: List, val obbFiles: List, val obbNames: List, diff --git a/test_runner/src/main/kotlin/ftl/args/CommonArgs.kt b/test_runner/src/main/kotlin/ftl/args/CommonArgs.kt index 94f5110cbe..ee87d6b555 100644 --- a/test_runner/src/main/kotlin/ftl/args/CommonArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/CommonArgs.kt @@ -20,6 +20,8 @@ data class CommonArgs( override val networkProfile: String?, override val otherFiles: Map, override val type: Type?, + override val directoriesToPull: List, + override val scenarioNumbers: List, // flank override val project: String, diff --git a/test_runner/src/main/kotlin/ftl/args/CreateAndroidArgs.kt b/test_runner/src/main/kotlin/ftl/args/CreateAndroidArgs.kt index 0b9d3de164..88e86cdaf1 100644 --- a/test_runner/src/main/kotlin/ftl/args/CreateAndroidArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/CreateAndroidArgs.kt @@ -4,6 +4,7 @@ import ftl.args.yml.AppTestPair import ftl.config.AndroidConfig import ftl.config.android.AndroidFlankConfig import ftl.config.android.AndroidGcloudConfig +import ftl.util.require fun createAndroidArgs( config: AndroidConfig? = null, @@ -16,16 +17,15 @@ fun createAndroidArgs( // gcloud appApk = gcloud.app?.normalizeFilePath(), testApk = gcloud.test?.normalizeFilePath(), - useOrchestrator = gcloud.useOrchestrator!!, - testTargets = gcloud.testTargets!!.filterNotNull(), + useOrchestrator = gcloud::useOrchestrator.require(), + testTargets = gcloud::testTargets.require().filterNotNull(), testRunnerClass = gcloud.testRunnerClass, - roboDirectives = gcloud.roboDirectives!!.parseRoboDirectives(), - performanceMetrics = gcloud.performanceMetrics!!, + roboDirectives = gcloud::roboDirectives.require().parseRoboDirectives(), + performanceMetrics = gcloud::performanceMetrics.require(), numUniformShards = gcloud.numUniformShards, - environmentVariables = gcloud.environmentVariables!!, - directoriesToPull = gcloud.directoriesToPull!!, - autoGoogleLogin = gcloud.autoGoogleLogin!!, - additionalApks = gcloud.additionalApks!!.map { it.normalizeFilePath() }, + environmentVariables = gcloud::environmentVariables.require(), + autoGoogleLogin = gcloud::autoGoogleLogin.require(), + additionalApks = gcloud::additionalApks.require().map { it.normalizeFilePath() }, roboScript = gcloud.roboScript?.normalizeFilePath(), // flank @@ -36,11 +36,10 @@ fun createAndroidArgs( environmentVariables = env ) } ?: emptyList(), - useLegacyJUnitResult = flank.useLegacyJUnitResult!!, - scenarioLabels = gcloud.scenarioLabels!!, + useLegacyJUnitResult = flank::useLegacyJUnitResult.require(), + scenarioLabels = gcloud::scenarioLabels.require(), obfuscateDumpShards = obfuscate, - obbFiles = gcloud.obbfiles!!, - obbNames = gcloud.obbnames!!, - scenarioNumbers = gcloud.scenarioNumbers!!, + obbFiles = gcloud::obbfiles.require(), + obbNames = gcloud::obbnames.require(), grantPermissions = gcloud.grantPermissions ) diff --git a/test_runner/src/main/kotlin/ftl/args/CreateCommonArgs.kt b/test_runner/src/main/kotlin/ftl/args/CreateCommonArgs.kt index 5c3665e909..50f42e1f30 100644 --- a/test_runner/src/main/kotlin/ftl/args/CreateCommonArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/CreateCommonArgs.kt @@ -4,6 +4,7 @@ import ftl.args.yml.toType import ftl.config.CommonConfig import ftl.run.status.OutputStyle import ftl.run.status.asOutputStyle +import ftl.util.require import ftl.util.uniqueObjectName fun CommonConfig.createCommonArgs( @@ -12,42 +13,44 @@ fun CommonConfig.createCommonArgs( data = data, // gcloud - devices = gcloud.devices!!, + devices = gcloud::devices.require(), resultsBucket = ArgsHelper.createGcsBucket( - projectId = flank.project!!, - bucket = gcloud.resultsBucket!! + projectId = flank::project.require(), + bucket = gcloud::resultsBucket.require() ), resultsDir = gcloud.resultsDir ?: uniqueObjectName(), - recordVideo = gcloud.recordVideo!!, - testTimeout = gcloud.timeout!!, - async = gcloud.async!!, + recordVideo = gcloud::recordVideo.require(), + testTimeout = gcloud::timeout.require(), + async = gcloud::async.require(), resultsHistoryName = gcloud.resultsHistoryName, - flakyTestAttempts = gcloud.flakyTestAttempts!!, + flakyTestAttempts = gcloud::flakyTestAttempts.require(), networkProfile = gcloud.networkProfile, clientDetails = gcloud.clientDetails, - otherFiles = gcloud.otherFiles!!.mapValues { (_, path) -> path.normalizeFilePath() }, + directoriesToPull = gcloud::directoriesToPull.require(), + otherFiles = gcloud::otherFiles.require().mapValues { (_, path) -> path.normalizeFilePath() }, + scenarioNumbers = gcloud::scenarioNumbers.require(), type = gcloud.type?.toType(), // flank - maxTestShards = flank.maxTestShards!!, - shardTime = flank.shardTime!!, - repeatTests = flank.repeatTests!!, - smartFlankGcsPath = flank.smartFlankGcsPath!!, - smartFlankDisableUpload = flank.smartFlankDisableUpload!!, - testTargetsAlwaysRun = flank.testTargetsAlwaysRun!!, - runTimeout = flank.runTimeout!!, - fullJUnitResult = flank.fullJUnitResult!!, - project = flank.project!!, + maxTestShards = flank::maxTestShards.require(), + shardTime = flank::shardTime.require(), + repeatTests = flank::repeatTests.require(), + smartFlankGcsPath = flank::smartFlankGcsPath.require(), + smartFlankDisableUpload = flank::smartFlankDisableUpload.require(), + testTargetsAlwaysRun = flank::testTargetsAlwaysRun.require(), + runTimeout = flank::runTimeout.require(), + fullJUnitResult = flank::fullJUnitResult.require(), + project = flank::project.require(), outputStyle = outputStyle, - keepFilePath = flank.keepFilePath!!, - ignoreFailedTests = flank.ignoreFailedTests!!, - filesToDownload = flank.filesToDownload!!, - disableSharding = flank.disableSharding!!, - localResultDir = flank.localResultsDir!!, - disableResultsUpload = flank.disableResultsUpload!!, - defaultTestTime = flank.defaultTestTime!!, - defaultClassTestTime = flank.defaultClassTestTime!!, - useAverageTestTimeForNewTests = flank.useAverageTestTimeForNewTests!! + keepFilePath = flank::keepFilePath.require(), + ignoreFailedTests = flank::ignoreFailedTests.require(), + filesToDownload = flank::filesToDownload.require(), + disableSharding = flank::disableSharding.require(), + localResultDir = flank::localResultsDir.require(), + disableResultsUpload = flank::disableResultsUpload.require(), + defaultTestTime = flank::defaultTestTime.require(), + defaultClassTestTime = flank::defaultClassTestTime.require(), + useAverageTestTimeForNewTests = flank::useAverageTestTimeForNewTests.require() ).apply { ArgsHelper.createJunitBucket(project, smartFlankGcsPath) } @@ -64,4 +67,4 @@ private val CommonConfig.defaultOutputStyle private val CommonConfig.hasMultipleExecutions get() = gcloud.flakyTestAttempts!! > 0 || - (!flank.disableSharding!! && flank.maxTestShards!! > 0) + (!flank.disableSharding!! && flank.maxTestShards!! > 1) diff --git a/test_runner/src/main/kotlin/ftl/args/CreateIosArgs.kt b/test_runner/src/main/kotlin/ftl/args/CreateIosArgs.kt index 6a5b1aac14..2aef0d2101 100644 --- a/test_runner/src/main/kotlin/ftl/args/CreateIosArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/CreateIosArgs.kt @@ -4,6 +4,7 @@ import ftl.args.yml.Type import ftl.config.IosConfig import ftl.config.ios.IosFlankConfig import ftl.config.ios.IosGcloudConfig +import ftl.util.require fun createIosArgs( config: IosConfig, @@ -28,9 +29,11 @@ private fun createIosArgs( xctestrunZip = gcloud.test?.normalizeFilePath().orEmpty(), xctestrunFile = gcloud.xctestrunFile?.normalizeFilePath().orEmpty(), xcodeVersion = gcloud.xcodeVersion, - additionalIpas = gcloud.additionalIpas!!.map { it.normalizeFilePath() }, + additionalIpas = gcloud::additionalIpas.require().map { it.normalizeFilePath() }, testTargets = flank.testTargets?.filterNotNull().orEmpty(), - obfuscateDumpShards = obfuscate + obfuscateDumpShards = obfuscate, + app = gcloud.app?.normalizeFilePath().orEmpty(), + testSpecialEntitlements = gcloud.testSpecialEntitlements ?: false ) private fun convertToShardCount(inputValue: Int) = diff --git a/test_runner/src/main/kotlin/ftl/args/IArgs.kt b/test_runner/src/main/kotlin/ftl/args/IArgs.kt index e98c5fa2fe..da03e39e2c 100644 --- a/test_runner/src/main/kotlin/ftl/args/IArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/IArgs.kt @@ -24,7 +24,9 @@ interface IArgs { val resultsHistoryName: String? val flakyTestAttempts: Int val otherFiles: Map + val scenarioNumbers: List val type: Type? get() = null + val directoriesToPull: List // FlankYml val maxTestShards: Int diff --git a/test_runner/src/main/kotlin/ftl/args/IosArgs.kt b/test_runner/src/main/kotlin/ftl/args/IosArgs.kt index c85b505887..f5141f42ae 100644 --- a/test_runner/src/main/kotlin/ftl/args/IosArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/IosArgs.kt @@ -14,7 +14,9 @@ data class IosArgs( val xcodeVersion: String?, val testTargets: List, val obfuscateDumpShards: Boolean, - val additionalIpas: List + val additionalIpas: List, + val app: String, + val testSpecialEntitlements: Boolean? ) : IArgs by commonArgs { override val useLegacyJUnitResult = true @@ -40,9 +42,13 @@ IosArgs xcode-version: $xcodeVersion device:${ArgsToString.objectsToString(devices)} num-flaky-test-attempts: $flakyTestAttempts + directories-to-pull: ${ArgsToString.listToString(directoriesToPull)} other-files: ${ArgsToString.mapToString(otherFiles)} additional-ipas: ${ArgsToString.listToString(additionalIpas)} + scenario-numbers: ${ArgsToString.listToString(scenarioNumbers)} type: ${type?.ymlName} + app: $app + test-special-entitlements: $testSpecialEntitlements flank: max-test-shards: $maxTestShards diff --git a/test_runner/src/main/kotlin/ftl/args/ValidateAndroidArgs.kt b/test_runner/src/main/kotlin/ftl/args/ValidateAndroidArgs.kt index 15e4f05d4a..ca974c8b22 100644 --- a/test_runner/src/main/kotlin/ftl/args/ValidateAndroidArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/ValidateAndroidArgs.kt @@ -56,9 +56,10 @@ private fun AndroidArgs.assertLabelContent() { else -> obbFiles.forEach { ArgsHelper.assertFileExists(it, " (obb file)") } } - if (scenarioNumbers.isNotEmpty() && (type == null || type != Type.GAMELOOP)) + if (scenarioNumbers.isNotEmpty() && (type != Type.GAMELOOP)) throw FlankConfigurationError("Scenario numbers defined but Type is not Game-loop.") scenarioNumbers.forEach { it.toIntOrNull() ?: throw FlankConfigurationError("Invalid scenario number provided - $it") } + if (scenarioNumbers.size > 1024) throw FlankConfigurationError("There cannot be more than 1024 Scenario numbers") } private const val MAX_OBB_FILES = 2 diff --git a/test_runner/src/main/kotlin/ftl/args/ValidateIosArgs.kt b/test_runner/src/main/kotlin/ftl/args/ValidateIosArgs.kt index 0f3191f54d..52e74363b8 100644 --- a/test_runner/src/main/kotlin/ftl/args/ValidateIosArgs.kt +++ b/test_runner/src/main/kotlin/ftl/args/ValidateIosArgs.kt @@ -15,6 +15,23 @@ fun IosArgs.validate() = apply { checkResultsDirUnique() assertAdditionalIpas() validType() + assertGameloop() +} + +private fun IosArgs.assertGameloop() { + validateApp() + validateScenarioNumbers() +} + +private fun IosArgs.validateApp() { + if (app.isNotEmpty() && type != Type.GAMELOOP) throw FlankConfigurationError("App cannot be defined if type is not equal to game-loop (IOS)") +} + +private fun IosArgs.validateScenarioNumbers() { + if (scenarioNumbers.isNotEmpty() && (type != Type.GAMELOOP)) + throw FlankConfigurationError("Scenario numbers defined but Type is not Game-loop.") + scenarioNumbers.forEach { it.toIntOrNull() ?: throw FlankConfigurationError("Invalid scenario number provided - $it") } + if (scenarioNumbers.size > 1024) throw FlankConfigurationError("There cannot be more than 1024 Scenario numbers") } fun IosArgs.validateRefresh() = apply { diff --git a/test_runner/src/main/kotlin/ftl/config/android/AndroidGcloudConfig.kt b/test_runner/src/main/kotlin/ftl/config/android/AndroidGcloudConfig.kt index e5aa19c973..6de48aad1f 100644 --- a/test_runner/src/main/kotlin/ftl/config/android/AndroidGcloudConfig.kt +++ b/test_runner/src/main/kotlin/ftl/config/android/AndroidGcloudConfig.kt @@ -102,29 +102,6 @@ data class AndroidGcloudConfig @JsonIgnore constructor( @set:JsonProperty("grant-permissions") var grantPermissions: String? by data - @set:CommandLine.Option( - names = ["--directories-to-pull"], - split = ",", - description = ["A list of paths that will be copied from the device's " + - "storage to the designated results bucket after the test is complete. These must be absolute paths under " + - "/sdcard or /data/local/tmp (for example, --directories-to-pull /sdcard/tempDir1,/data/local/tmp/tempDir2). " + - "Path names are restricted to the characters a-zA-Z0-9_-./+. The paths /sdcard and /data will be made available " + - "and treated as implicit path substitutions. E.g. if /sdcard on a particular device does not map to external " + - "storage, the system will replace it with the external storage path prefix for that device."] - ) - @set:JsonProperty("directories-to-pull") - var directoriesToPull: List? by data - - @set:CommandLine.Option( - names = ["--scenario-numbers"], - split = ",", - description = ["A list of game-loop scenario numbers which will be run as part of the test (default: all scenarios). " + - "A maximum of 1024 scenarios may be specified in one test matrix, " + - "but the maximum number may also be limited by the overall test --timeout setting."] - ) - @set:JsonProperty("scenario-numbers") - var scenarioNumbers: List? by data - @set:CommandLine.Option( names = ["--scenario-labels"], split = ",", @@ -249,8 +226,6 @@ data class AndroidGcloudConfig @JsonIgnore constructor( useOrchestrator = true environmentVariables = emptyMap() grantPermissions = FlankDefaults.GRANT_PERMISSIONS_ALL - directoriesToPull = emptyList() - scenarioNumbers = emptyList() scenarioLabels = emptyList() obbfiles = emptyList() obbnames = emptyList() diff --git a/test_runner/src/main/kotlin/ftl/config/common/CommonGcloudConfig.kt b/test_runner/src/main/kotlin/ftl/config/common/CommonGcloudConfig.kt index 3afb361ce5..acce84442e 100644 --- a/test_runner/src/main/kotlin/ftl/config/common/CommonGcloudConfig.kt +++ b/test_runner/src/main/kotlin/ftl/config/common/CommonGcloudConfig.kt @@ -120,6 +120,21 @@ data class CommonGcloudConfig @JsonIgnore constructor( @set:JsonProperty("num-flaky-test-attempts") var flakyTestAttempts: Int? by data + @set:CommandLine.Option( + names = ["--directories-to-pull"], + split = ",", + description = ["A list of paths that will be copied from the device's " + + "storage to the designated results bucket after the test is complete. For Android devices these must be absolute paths under " + + "/sdcard or /data/local/tmp (for example, --directories-to-pull /sdcard/tempDir1,/data/local/tmp/tempDir2). " + + "Path names are restricted to the characters a-zA-Z0-9_-./+. The paths /sdcard and /data will be made available " + + "and treated as implicit path substitutions. E.g. if /sdcard on a particular device does not map to external " + + "storage, the system will replace it with the external storage path prefix for that device. " + + "For iOS devices these must be absolute paths under /private/var/mobile/Media or /Documents " + + "of the app under test. If the path is under an app's /Documents, it must be prefixed with the app's bundle id and a colon"] + ) + @set:JsonProperty("directories-to-pull") + var directoriesToPull: List? by data + @set:CommandLine.Option( names = ["--other-files"], split = ",", @@ -130,6 +145,16 @@ data class CommonGcloudConfig @JsonIgnore constructor( @set:JsonProperty("other-files") var otherFiles: Map? by data + @set:CommandLine.Option( + names = ["--scenario-numbers"], + split = ",", + description = ["A list of game-loop scenario numbers which will be run as part of the test (default: all scenarios). " + + "A maximum of 1024 scenarios may be specified in one test matrix, " + + "but the maximum number may also be limited by the overall test --timeout setting."] + ) + @set:JsonProperty("scenario-numbers") + var scenarioNumbers: List? by data + @set:CommandLine.Option( names = ["--type"], description = ["The type of test to run. TYPE must be one of: instrumentation, robo, xctest, game-loop."] @@ -160,8 +185,10 @@ data class CommonGcloudConfig @JsonIgnore constructor( clientDetails = null networkProfile = null devices = listOf(defaultDevice(android)) + directoriesToPull = emptyList() otherFiles = emptyMap() type = null + scenarioNumbers = emptyList() } } } diff --git a/test_runner/src/main/kotlin/ftl/config/ios/IosGcloudConfig.kt b/test_runner/src/main/kotlin/ftl/config/ios/IosGcloudConfig.kt index 9b8cf1e7bd..642b3edb02 100644 --- a/test_runner/src/main/kotlin/ftl/config/ios/IosGcloudConfig.kt +++ b/test_runner/src/main/kotlin/ftl/config/ios/IosGcloudConfig.kt @@ -58,6 +58,26 @@ data class IosGcloudConfig @JsonIgnore constructor( @set:JsonProperty("additional-ipas") var additionalIpas: List? by data + @set:CommandLine.Option( + names = ["--app"], + description = ["The path to the application archive (.ipa file) for game-loop testing. " + + "The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. " + + "This flag is only valid when --type=game-loop is also set"] + ) + @set:JsonProperty("app") + var app: String? by data + + @set:CommandLine.Option( + names = ["--test-special-entitlements"], + description = ["Enables testing special app entitlements. Re-signs an app having special entitlements with a new" + + " application-identifier. This currently supports testing Push Notifications (aps-environment) entitlement " + + "for up to one app in a project.\n" + + "Note: Because this changes the app's identifier, make sure none of the resources in your zip file contain " + + "direct references to the test app's bundle id."] + ) + @set:JsonProperty("test-special-entitlements") + var testSpecialEntitlements: Boolean? by data + constructor() : this(mutableMapOf().withDefault { null }) companion object : IYmlKeys { @@ -73,6 +93,8 @@ data class IosGcloudConfig @JsonIgnore constructor( xctestrunFile = null xcodeVersion = null additionalIpas = emptyList() + app = null + testSpecialEntitlements = false } } } diff --git a/test_runner/src/main/kotlin/ftl/gc/GcIosTestMatrix.kt b/test_runner/src/main/kotlin/ftl/gc/GcIosTestMatrix.kt index 0ae83f4a52..5bafc39e60 100644 --- a/test_runner/src/main/kotlin/ftl/gc/GcIosTestMatrix.kt +++ b/test_runner/src/main/kotlin/ftl/gc/GcIosTestMatrix.kt @@ -16,6 +16,7 @@ import com.google.api.services.testing.model.ToolResultsHistory import ftl.args.IosArgs import ftl.gc.android.mapGcsPathsToFileReference import ftl.gc.android.mapToIosDeviceFiles +import ftl.gc.android.toIosDeviceFile import ftl.ios.Xctestrun import ftl.ios.Xctestrun.toByteArray import ftl.run.exception.FlankGeneralError @@ -64,11 +65,13 @@ object GcIosTestMatrix { .setTestsZip(FileReference().setGcsPath(testZipGcsPath)) .setXctestrun(FileReference().setGcsPath(xctestrunFileGcsPath)) .setXcodeVersion(args.xcodeVersion) + .setTestSpecialEntitlements(args.testSpecialEntitlements) val iOSTestSetup = IosTestSetup() .setNetworkProfile(args.networkProfile) .setPushFiles(otherFiles.mapToIosDeviceFiles()) .setAdditionalIpas(additionalIpasGcsPaths.mapGcsPathsToFileReference()) + .setPullDirectories(args.directoriesToPull.toIosDeviceFiles()) val testTimeoutSeconds = timeoutToSeconds(args.testTimeout) @@ -98,3 +101,5 @@ object GcIosTestMatrix { } } } + +private fun List.toIosDeviceFiles() = map { path -> toIosDeviceFile(path) } diff --git a/test_runner/src/main/kotlin/ftl/gc/android/Utils.kt b/test_runner/src/main/kotlin/ftl/gc/android/Utils.kt index 827d3f549f..61e4e59de2 100644 --- a/test_runner/src/main/kotlin/ftl/gc/android/Utils.kt +++ b/test_runner/src/main/kotlin/ftl/gc/android/Utils.kt @@ -32,16 +32,15 @@ internal fun Map.mapToDeviceObbFiles(obbnames: List): Li } } -internal fun Map.mapToIosDeviceFiles(): List = - map { (testDevicePath, gcsFilePath) -> - IosDeviceFile().apply { - if (testDevicePath.contains(":")) { - val (bundleIdSeparated, pathSeparated) = testDevicePath.split(":") - bundleId = bundleIdSeparated - devicePath = pathSeparated - } else { - devicePath = testDevicePath - } - content = FileReference().setGcsPath(gcsFilePath) - } +internal fun Map.mapToIosDeviceFiles(): List = map { (testDevicePath, gcsFilePath) -> toIosDeviceFile(testDevicePath, gcsFilePath) } + +internal fun toIosDeviceFile(testDevicePath: String, gcsFilePath: String = "") = IosDeviceFile().apply { + if (testDevicePath.contains(":")) { + val (bundleIdSeparated, pathSeparated) = testDevicePath.split(":") + bundleId = bundleIdSeparated + devicePath = pathSeparated + } else { + devicePath = testDevicePath } + content = FileReference().setGcsPath(gcsFilePath) +} diff --git a/test_runner/src/main/kotlin/ftl/run/DumpShards.kt b/test_runner/src/main/kotlin/ftl/run/DumpShards.kt index 791af1d992..840625208a 100644 --- a/test_runner/src/main/kotlin/ftl/run/DumpShards.kt +++ b/test_runner/src/main/kotlin/ftl/run/DumpShards.kt @@ -23,7 +23,7 @@ suspend fun dumpShards( saveShardChunks( shardFilePath = shardFilePath, shards = shards, - size = shards.size, + size = shards.flatMap { it.value.shards.values }.count(), obfuscatedOutput = args.obfuscateDumpShards ) } diff --git a/test_runner/src/main/kotlin/ftl/util/Utils.kt b/test_runner/src/main/kotlin/ftl/util/Utils.kt index 009adbed1d..5690f9edf5 100644 --- a/test_runner/src/main/kotlin/ftl/util/Utils.kt +++ b/test_runner/src/main/kotlin/ftl/util/Utils.kt @@ -2,6 +2,7 @@ package ftl.util +import com.fasterxml.jackson.annotation.JsonProperty import ftl.run.exception.FlankGeneralError import java.io.InputStream import java.io.StringWriter @@ -12,6 +13,7 @@ import java.time.ZoneOffset import java.time.format.DateTimeFormatter import java.util.Random import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty import kotlin.reflect.KProperty fun String.trimStartLine(): String { @@ -132,3 +134,12 @@ fun , T> mutableMapProperty( override fun setValue(thisRef: R, property: KProperty<*>, value: T) = thisRef.set(name ?: property.name, value as Any) } + +/** + * Used to validate values from yml config file. + * Should be used only on properties with [JsonProperty] annotation. + */ +fun KMutableProperty.require() = + getter.call() ?: throw FlankGeneralError( + "Invalid value for [${setter.annotations.filterIsInstance().first().value}]: no argument value found" + ) diff --git a/test_runner/src/test/kotlin/Debug.kt b/test_runner/src/test/kotlin/Debug.kt index 75785a5f1a..55c8631030 100644 --- a/test_runner/src/test/kotlin/Debug.kt +++ b/test_runner/src/test/kotlin/Debug.kt @@ -24,7 +24,7 @@ fun main() { // "--debug", "firebase", "test", - "android", + "ios", "run", // "--dry", // "--dump-shards", diff --git a/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt b/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt index fc26a3e798..aa3071888b 100644 --- a/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt +++ b/test_runner/src/test/kotlin/ftl/args/AndroidArgsTest.kt @@ -440,7 +440,7 @@ AndroidArgs run-timeout: -1 legacy-junit-result: true ignore-failed-tests: false - output-style: multi + output-style: verbose disable-results-upload: false default-class-test-time: 240.0 """.trimIndent(), @@ -488,7 +488,7 @@ AndroidArgs assert(testTargetsAlwaysRun, empty) assert(disableSharding, false) assert(runTimeout, "-1") - assert(outputStyle, OutputStyle.Multi) + assert(outputStyle, OutputStyle.Verbose) assert(fullJUnitResult, false) } } @@ -1199,17 +1199,17 @@ AndroidArgs @Test fun `cli output-style`() { val cli = AndroidRunCommand() - CommandLine(cli).parseArgs("--output-style=verbose") + CommandLine(cli).parseArgs("--output-style=multi") val yaml = """ gcloud: app: $appApk test: $testApk """ - assertThat(AndroidArgs.load(yaml).validate().outputStyle).isEqualTo(OutputStyle.Multi) + assertThat(AndroidArgs.load(yaml).validate().outputStyle).isEqualTo(OutputStyle.Verbose) val args = AndroidArgs.load(yaml, cli).validate() - assertThat(args.outputStyle).isEqualTo(OutputStyle.Verbose) + assertThat(args.outputStyle).isEqualTo(OutputStyle.Multi) } @Test(expected = FlankConfigurationError::class) @@ -1928,6 +1928,7 @@ AndroidArgs """.trimIndent() AndroidArgs.load(yaml).validate() } + @Test(expected = FlankGeneralError::class) fun `should throw exception if incorrect type requested`() { val yaml = """ @@ -2305,6 +2306,37 @@ AndroidArgs """.trimIndent() AndroidArgs.load(yaml).validate() } + + @Test + fun `should throw exception if incorrect (empty) value used in yml config file`() { + assertThrowsWithMessage( + clazz = FlankGeneralError::class, + message = "Invalid value for [test-targets]: no argument value found" + ) { + AndroidArgs.load( + """ + gcloud: + app: any/path.apk + test-targets: + """.trimIndent() + ) + } + } + + @Test + fun `should not throw exception if incorrect (empty) value used in yml config file is overwriten with command line`() { + val cli = AndroidRunCommand() + CommandLine(cli).parseArgs("--legacy-junit-result") + + AndroidArgs.load( + """ + gcloud: + app: any/path.apk + flank: + legacy-junit-result: + """.trimIndent(), cli + ) + } } private fun AndroidArgs.Companion.load(yamlData: String, cli: AndroidRunCommand? = null): AndroidArgs = diff --git a/test_runner/src/test/kotlin/ftl/args/IosArgsTest.kt b/test_runner/src/test/kotlin/ftl/args/IosArgsTest.kt index dd94ebad87..78d11468d8 100644 --- a/test_runner/src/test/kotlin/ftl/args/IosArgsTest.kt +++ b/test_runner/src/test/kotlin/ftl/args/IosArgsTest.kt @@ -78,6 +78,7 @@ class IosArgsTest { orientation: default num-flaky-test-attempts: 4 type: xctest + test-special-entitlements: true flank: max-test-shards: 7 @@ -235,13 +236,17 @@ IosArgs locale: c orientation: default num-flaky-test-attempts: 4 + directories-to-pull: other-files: com.my.app:/Documents/file.txt: local/file.txt /private/var/mobile/Media/file.jpg: gs://bucket/file.jpg additional-ipas: - $testIpa1 - $testIpa2 + scenario-numbers: type: xctest + app: + test-special-entitlements: true flank: max-test-shards: 7 @@ -299,9 +304,13 @@ IosArgs locale: en orientation: portrait num-flaky-test-attempts: 0 + directories-to-pull: other-files: additional-ipas: + scenario-numbers: type: xctest + app: + test-special-entitlements: false flank: max-test-shards: 1 @@ -322,7 +331,7 @@ IosArgs local-result-dir: results run-timeout: -1 ignore-failed-tests: false - output-style: multi + output-style: verbose disable-results-upload: false default-class-test-time: 240.0 """.trimIndent(), @@ -854,17 +863,17 @@ IosArgs @Test fun `cli output-style`() { val cli = IosRunCommand() - CommandLine(cli).parseArgs("--output-style=verbose") + CommandLine(cli).parseArgs("--output-style=multi") val yaml = """ gcloud: test: $testPath xctestrun-file: $testPath """ - assertThat(IosArgs.load(yaml).outputStyle).isEqualTo(OutputStyle.Multi) + assertThat(IosArgs.load(yaml).outputStyle).isEqualTo(OutputStyle.Verbose) val args = IosArgs.load(yaml, cli) - assertThat(args.outputStyle).isEqualTo(OutputStyle.Verbose) + assertThat(args.outputStyle).isEqualTo(OutputStyle.Multi) } private fun getValidTestsSample() = listOf( @@ -1127,6 +1136,87 @@ IosArgs assertFalse(systemOutRule.log.contains("WARNING: Google cloud storage result directory should be unique, otherwise results from multiple test matrices will be overwritten or intermingled")) } } + + @Test + fun `should not throw exception if game-loop is provided and nothing else`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + type: game-loop + """.trimIndent() + IosArgs.load(yaml).validate() + } + + @Test(expected = FlankConfigurationError::class) + fun `should throw exception if game-loop is not provided and scenario numbers are`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + scenario-numbers: + - 1 + - 2 + """.trimIndent() + IosArgs.load(yaml).validate() + } + + @Test + fun `should not throw exception if game-loop is provided and scenario numbers are`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + type: game-loop + scenario-numbers: + - 1 + - 2 + """.trimIndent() + IosArgs.load(yaml).validate() + } + + @Test(expected = FlankConfigurationError::class) + fun `should throw exception if invalid scenario numbers are provided`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + type: game-loop + scenario-numbers: + - error1 + - error2 + """.trimIndent() + IosArgs.load(yaml).validate() + } + + @Test(expected = FlankConfigurationError::class) + fun `should throw exception if app provided but not type equals gameloop`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + app: $testPath + """.trimIndent() + IosArgs.load(yaml).validate() + } + + @Test + fun `should not throw exception if app provided and type equals gameloop`() { + val yaml = """ + gcloud: + test: $testPath + xctestrun-file: $testPath + results-dir: test + type: game-loop + app: $testPath + """.trimIndent() + IosArgs.load(yaml).validate() + } } private fun IosArgs.Companion.load(yamlData: String, cli: IosRunCommand? = null): IosArgs = diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt index a23f0b56d1..14e23f7405 100644 --- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt +++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/android/AndroidRunCommandTest.kt @@ -80,7 +80,6 @@ class AndroidRunCommandTest { assertThat(cmd.config.platform.gcloud.numUniformShards).isNull() assertThat(cmd.config.platform.gcloud.testRunnerClass).isNull() assertThat(cmd.config.platform.gcloud.environmentVariables).isNull() - assertThat(cmd.config.platform.gcloud.directoriesToPull).isNull() assertThat(cmd.config.common.gcloud.otherFiles).isNull() assertThat(cmd.config.common.gcloud.devices).isNull() assertThat(cmd.config.common.gcloud.resultsBucket).isNull() @@ -98,6 +97,7 @@ class AndroidRunCommandTest { assertThat(cmd.config.common.flank.filesToDownload).isNull() assertThat(cmd.config.common.gcloud.resultsDir).isNull() assertThat(cmd.config.common.gcloud.flakyTestAttempts).isNull() + assertThat(cmd.config.common.gcloud.directoriesToPull).isNull() assertThat(cmd.config.common.flank.disableSharding).isNull() assertThat(cmd.config.common.flank.localResultsDir).isNull() assertThat(cmd.config.common.flank.smartFlankDisableUpload).isNull() @@ -219,7 +219,7 @@ class AndroidRunCommandTest { val cmd = AndroidRunCommand() CommandLine(cmd).parseArgs("--directories-to-pull=a,b") - assertThat(cmd.config.platform.gcloud.directoriesToPull).hasSize(2) + assertThat(cmd.config.common.gcloud.directoriesToPull).hasSize(2) } @Test diff --git a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt index 264cb75667..32d69dc69a 100644 --- a/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt +++ b/test_runner/src/test/kotlin/ftl/cli/firebase/test/ios/IosRunCommandTest.kt @@ -90,6 +90,7 @@ class IosRunCommandTest { assertThat(cmd.config.common.gcloud.devices).isNull() assertThat(cmd.config.common.gcloud.resultsDir).isNull() assertThat(cmd.config.common.gcloud.flakyTestAttempts).isNull() + assertThat(cmd.config.common.gcloud.directoriesToPull).isNull() assertThat(cmd.config.common.flank.localResultsDir).isNull() assertThat(cmd.config.common.flank.smartFlankDisableUpload).isNull() assertThat(cmd.config.common.flank.smartFlankGcsPath).isNull() @@ -397,4 +398,12 @@ class IosRunCommandTest { assertThat(cmd.config.common.gcloud.type).isEqualTo("a") } + + @Test + fun `should properly parse test-special-entitlements`() { + val cmd = IosRunCommand() + CommandLine(cmd).parseArgs("--test-special-entitlements") + + assertThat(cmd.config.platform.gcloud.testSpecialEntitlements).isEqualTo(true) + } } diff --git a/test_runner/src/test/kotlin/ftl/gc/GcIosTestMatrixTest.kt b/test_runner/src/test/kotlin/ftl/gc/GcIosTestMatrixTest.kt index 6c52717f9b..e8da56641c 100644 --- a/test_runner/src/test/kotlin/ftl/gc/GcIosTestMatrixTest.kt +++ b/test_runner/src/test/kotlin/ftl/gc/GcIosTestMatrixTest.kt @@ -2,10 +2,10 @@ package ftl.gc import com.dd.plist.NSDictionary import com.google.api.services.testing.model.IosDeviceList -import ftl.shard.Chunk import ftl.args.IosArgs import ftl.config.FtlConstants.isWindows import ftl.ios.FIXTURES_PATH +import ftl.shard.Chunk import ftl.shard.TestMethod import ftl.test.util.FlankTestRunner import ftl.util.ShardCounter @@ -165,4 +165,32 @@ class GcIosTestMatrixTest { val expected = emptyList() assertEquals(expected, iosArgs.additionalIpas) } + + @Test + fun `should fill directoriesToPull`() { + val iosArgs = IosArgs.load(StringReader(""" + gcloud: + test: ./test_runner/src/test/kotlin/ftl/fixtures/tmp/earlgrey_example.zip + xctestrun-file: ./test_runner/src/test/kotlin/ftl/fixtures/tmp/EarlGreyExampleSwiftTests_iphoneos13.4-arm64e.xctestrun + results-dir: test_dir + directories-to-pull: + - test/test/test + """.trimIndent())) + + val expected = listOf("test/test/test") + assertEquals(expected, iosArgs.directoriesToPull) + } + + @Test + fun `should not fill directoriesToPull`() { + val iosArgs = IosArgs.load(StringReader(""" + gcloud: + test: ./test_runner/src/test/kotlin/ftl/fixtures/tmp/earlgrey_example.zip + xctestrun-file: ./test_runner/src/test/kotlin/ftl/fixtures/tmp/EarlGreyExampleSwiftTests_iphoneos13.4-arm64e.xctestrun + results-dir: test_dir + """.trimIndent())) + + val expected = emptyList() + assertEquals(expected, iosArgs.directoriesToPull) + } } diff --git a/test_runner/src/test/kotlin/ftl/run/DumpShardsKtTest.kt b/test_runner/src/test/kotlin/ftl/run/DumpShardsKtTest.kt index 88ddb733aa..2e1ad7b0d1 100644 --- a/test_runner/src/test/kotlin/ftl/run/DumpShardsKtTest.kt +++ b/test_runner/src/test/kotlin/ftl/run/DumpShardsKtTest.kt @@ -16,10 +16,19 @@ import org.junit.Assert.assertNotEquals import org.junit.Test import org.junit.runner.RunWith import java.io.File +import org.junit.After +import org.junit.Rule +import org.junit.contrib.java.lang.system.SystemOutRule @RunWith(FlankTestRunner::class) class DumpShardsKtTest { + @get:Rule + val output: SystemOutRule = SystemOutRule().enableLog().muteForSuccessfulTests() + + @After + fun tearDown() = output.clearLog() + @Test fun `dump shards android`() { // given @@ -75,6 +84,7 @@ class DumpShardsKtTest { // then assertEqualsIgnoreNewlineStyle(expected, actual) + assertThat(output.log).contains("Saved 3 shards to $TEST_SHARD_FILE") } @Test @@ -132,6 +142,7 @@ class DumpShardsKtTest { // then assertNotEquals(notExpected, actual) assertThat(notExpected.split(System.lineSeparator()).size).isEqualTo(actual.split(System.lineSeparator()).size) // same line count + assertThat(output.log).contains("Saved 3 shards to $TEST_SHARD_FILE") } @Test @@ -161,6 +172,7 @@ class DumpShardsKtTest { // then assertEquals(expected, actual) + assertThat(output.log).contains("Saved 2 shards to $TEST_SHARD_FILE") } @Test @@ -192,6 +204,7 @@ class DumpShardsKtTest { // then assertNotEquals(notExpected, actual) assertThat(notExpected.split(System.lineSeparator()).size).isEqualTo(actual.split(System.lineSeparator()).size) // same line count + assertThat(output.log).contains("Saved 2 shards to $TEST_SHARD_FILE") } } diff --git a/test_runner/src/test/kotlin/ftl/run/status/MultiLinePrinterTest.kt b/test_runner/src/test/kotlin/ftl/run/status/MultiLinePrinterTest.kt index 3a8e1c4e98..a94dd4b01b 100644 --- a/test_runner/src/test/kotlin/ftl/run/status/MultiLinePrinterTest.kt +++ b/test_runner/src/test/kotlin/ftl/run/status/MultiLinePrinterTest.kt @@ -6,6 +6,7 @@ import io.mockk.every import io.mockk.spyk import io.mockk.verify import org.fusesource.jansi.Ansi +import org.fusesource.jansi.AnsiConsole import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test @@ -21,6 +22,7 @@ class MultiLinePrinterTest { // given val ansi = spyk(Ansi.ansi()) { every { this@spyk.toString() } returns "" } val printChanges = MultiLinePrinter { ansi } + AnsiConsole.systemUninstall() // when printChanges(changes1)