diff --git a/.github/workflows/flank-scripts-macos_workflow.yml b/.github/workflows/flank-scripts-macos_workflow.yml index 78423bb097..1a32343f18 100644 --- a/.github/workflows/flank-scripts-macos_workflow.yml +++ b/.github/workflows/flank-scripts-macos_workflow.yml @@ -25,5 +25,4 @@ jobs: - name: Gradle check uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./flank-scripts/gradlew" - arguments: "-p flank-scripts check" + arguments: ":flank-scripts:check" diff --git a/.github/workflows/macos_workflow.yml b/.github/workflows/macos_workflow.yml index e08a717ee9..ff2abefa3b 100644 --- a/.github/workflows/macos_workflow.yml +++ b/.github/workflows/macos_workflow.yml @@ -39,8 +39,7 @@ jobs: - name: Gradle clean build uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner clean build" + arguments: "clean build" - name: Prepare Google Service Account env: @@ -48,16 +47,14 @@ jobs: run: | GCLOUD_DIR="$HOME/.config/gcloud/" mkdir -p "$GCLOUD_DIR" - echo "$GCLOUD_KEY" | base64 --decode > "$GCLOUD_DIR/application_default_credentials.json" - + echo "$GCLOUD_KEY" | base64 --decode > "$GCLOUD_DIR/application_default_credentials.json" + - name: Gradle Integration Tests Android uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./integration_tests/gradlew" - arguments: "--info -p integration_tests test --tests IntegrationTests.shouldMatchAndroidSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_android.yml" - + arguments: "--info :integration_tests:test --tests IntegrationTests.shouldMatchAndroidSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_android.yml" + - name: Gradle Integration Tests iOS uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./integration_tests/gradlew" - arguments: "--info -p integration_tests test --tests IntegrationTests.shouldMatchIosSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_ios.yml" + arguments: "--info :integration_tests:test --tests IntegrationTests.shouldMatchIosSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_ios.yml" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ddeb121be8..1517fb7b33 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,14 +63,12 @@ jobs: - name: Gradle Build Flank uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner clean build shadowJar" - + arguments: "clean test_runner:build test_runner:shadowJar" + - name: Gradle Upload to bintray uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner bintrayUpload -PJFROG_API_KEY=${{ secrets.JFROG_API_KEY }} -PJFROG_USER=${{ secrets.JFROG_USER }}" + arguments: "test_runner:bintrayUpload -PJFROG_API_KEY=${{ secrets.JFROG_API_KEY }} -PJFROG_USER=${{ secrets.JFROG_USER }}" - name: Authenticate to hub run: | @@ -107,8 +105,7 @@ jobs: uses: eskatos/gradle-command-action@v1 if: startsWith(github.ref, 'refs/tags/v') with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner publish -PGITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" + arguments: "test_runner:publish -PGITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" - name: Post Message of Flank Release uses: Flank/flank@V1.1-action diff --git a/.github/workflows/release_notes_generation.yml b/.github/workflows/release_notes_generation.yml index ea0a1a1839..333956480d 100644 --- a/.github/workflows/release_notes_generation.yml +++ b/.github/workflows/release_notes_generation.yml @@ -18,8 +18,7 @@ jobs: - name: Generate documentation uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner processCliAsciiDoc" + arguments: "test_runner:processCliAsciiDoc" - name: Gradle Build flankScripts and add it to PATH run: | diff --git a/.github/workflows/ubuntu-workflow.yml b/.github/workflows/ubuntu-workflow.yml index bc803c0ef9..fcaa0574fa 100644 --- a/.github/workflows/ubuntu-workflow.yml +++ b/.github/workflows/ubuntu-workflow.yml @@ -32,8 +32,7 @@ jobs: - name: Gradle clean build uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./test_runner/gradlew" - arguments: "-p test_runner clean build" + arguments: "clean build" - name: Prepare Google Service Account env: @@ -46,13 +45,10 @@ jobs: - name: Gradle Integration Tests Android uses: eskatos/gradle-command-action@v1 with: - gradle-executable: "./integration_tests/gradlew" - arguments: "--info -p integration_tests test --tests IntegrationTests.shouldMatchAndroidSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_android.yml" - + arguments: "--info :integration_tests:test --tests IntegrationTests.shouldMatchAndroidSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_android.yml" - name: Gradle Integration Tests iOS uses: eskatos/gradle-command-action@v1 env: GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} with: - gradle-executable: "./integration_tests/gradlew" - arguments: "--info -p integration_tests test --tests IntegrationTests.shouldMatchIosSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_ios.yml" + arguments: "--info :integration_tests:test --tests IntegrationTests.shouldMatchIosSuccessExitCodeAndPattern -Dflank-path=../test_runner/build/libs/flank.jar -Dyml-path=./src/test/resources/flank_ios.yml" diff --git a/README.md b/README.md index 27832cca24..8b54a2b9a3 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,8 @@ app, test, and xctestrun-file support `~`, environment variables, and globs (*, Run `test_runner/flank.ios.yml` with flank to verify iOS execution is working. -- `cd test_runner/` -- `./gradlew clean build shadowJar` -- `java -jar ./build/libs/flank-*.jar firebase test ios run` +- `./gradlew clean test_runner:build test_runner:shadowJar` +- `java -jar ./test_runner/build/libs/flank-*.jar firebase test ios run` ```yaml # gcloud args match the official gcloud cli @@ -226,9 +225,8 @@ flank: Run `test_runner/flank.yml` with flank to verify Android execution is working. -- `cd test_runner/` -- `./gradlew clean build shadowJar` -- `java -jar ./build/libs/flank-*.jar firebase test android run` +- `./gradlew clean test_runner:build test_runner:shadowJar` +- `java -jar ./test_runner/build/libs/flank-*.jar firebase test android run` ```yaml # gcloud args match the official gcloud cli diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..8da4b43342 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,11 @@ + + +// Fix Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.hash.Hashing.crc32c()Lcom/google/common/hash/HashFunction; +// https://stackoverflow.com/a/45286710 +configurations.all { + resolutionStrategy { + force("com.google.guava:guava:25.1-jre") + force(Libs.KOTLIN_REFLECT) + exclude(group = "com.google.guava", module = "guava-jdk5") + } +} diff --git a/flank-scripts/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts similarity index 100% rename from flank-scripts/buildSrc/build.gradle.kts rename to buildSrc/build.gradle.kts diff --git a/test_runner/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt similarity index 85% rename from test_runner/buildSrc/src/main/kotlin/Deps.kt rename to buildSrc/src/main/kotlin/Deps.kt index fc5223b3d5..ac61f20904 100644 --- a/test_runner/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -83,6 +83,12 @@ object Versions { // https://github.com/ben-manes/gradle-versions-plugin/releases const val BEN_MANES = "0.28.0" + + + // ============== flank-scripts ============== + const val KOTLINX_SERIALIZATION = "1.0.0-RC" + const val FUEL = "2.2.3" + const val CLIKT = "2.8.0" } object Libs { @@ -136,4 +142,23 @@ object Libs { //endregion const val COMMON_TEXT = "org.apache.commons:commons-text:${Versions.COMMON_TEXT}" + const val KOTLIN_SERIALIZATION = "org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.KOTLINX_SERIALIZATION}" + + //region flank-scripts + const val PLUGIN_SHADOW_JAR = "com.github.johnrengelman.shadow" + const val CLIKT = "com.github.ajalt:clikt:${Versions.CLIKT}" + const val DETEKT_PLUGIN = "io.gitlab.arturbosch.detekt" + //endregion + + object Fuel { + const val CORE = "com.github.kittinunf.fuel:fuel:${Versions.FUEL}" + const val COROUTINES = "com.github.kittinunf.fuel:fuel-coroutines:${Versions.FUEL}" + const val KOTLINX_SERIALIZATION = "com.github.kittinunf.fuel:fuel-kotlinx-serialization:${Versions.FUEL}" + } +} + +object Kotlin { + const val PLUGIN_JVM = "jvm" + const val PLUGIN_SERIALIZATION = "plugin.serialization" } + diff --git a/flank-scripts/README.md b/flank-scripts/README.md index 1f94154d7a..625d538fc7 100644 --- a/flank-scripts/README.md +++ b/flank-scripts/README.md @@ -7,7 +7,7 @@ This repository contains helper scripts for developing flank. For now, it contai To build flank-scripts: 1. Run script `buildFlankScripts.sh` in `flank-scripts/bash/` directory -2. Run command `flank-scripts/gradlew clean assemble shadowJar` and manual copy file from `/flank-scripts/build/libs/flankScripts.jar` to `flank-scripts/bash/` +2. Run command `./gradlew clean flank-scripts:assemble flank-scripts:shadowJar` and manual copy file from `/flank-scripts/build/libs/flankScripts.jar` to `flank-scripts/bash/` 3. You could always run/build it from Intellij IDEA ### Usage diff --git a/flank-scripts/bash/buildFlankScripts.sh b/flank-scripts/bash/buildFlankScripts.sh index e199f6bb26..55f04197bd 100755 --- a/flank-scripts/bash/buildFlankScripts.sh +++ b/flank-scripts/bash/buildFlankScripts.sh @@ -4,5 +4,5 @@ DIR=`dirname "$BASH_SOURCE"` FLANK_SCRIPTS="$DIR/.." -"$FLANK_SCRIPTS/gradlew" -p "$FLANK_SCRIPTS" clean assemble shadowJar +"$FLANK_SCRIPTS/../gradlew" flank-scripts:clean flank-scripts:assemble flank-scripts:shadowJar cp "$FLANK_SCRIPTS"/build/libs/flankScripts.jar "$DIR/flankScripts.jar" diff --git a/flank-scripts/build.gradle.kts b/flank-scripts/build.gradle.kts index 641f1ff651..60ad2d01ee 100644 --- a/flank-scripts/build.gradle.kts +++ b/flank-scripts/build.gradle.kts @@ -2,10 +2,10 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { application - kotlin(Kotlin.PLUGIN_JVM) version Versions.KOTLIN_VERSION - kotlin(Kotlin.PLUGIN_SERIALIZATION) version Versions.KOTLIN_VERSION - id(PLUGIN_SHADOW_JAR) version Versions.SHADOW_JAR - id(DETEKT_PLUGIN) version Versions.DETEKT + kotlin(Kotlin.PLUGIN_JVM) version Versions.KOTLIN + kotlin(Kotlin.PLUGIN_SERIALIZATION) version Versions.KOTLIN + id(Libs.PLUGIN_SHADOW_JAR) version Versions.SHADOW + id(Libs.DETEKT_PLUGIN) version Versions.DETEKT } val artifactID = "flankScripts" @@ -51,17 +51,17 @@ detekt { tasks["check"].dependsOn(tasks["detekt"]) dependencies { - implementation(kotlin("stdlib")) - implementation(Kotlin.KOTLIN_SERIALIZATION) - implementation(Fuel.CORE) - implementation(Fuel.KOTLINX_SERIALIZATION) - implementation(Fuel.COROUTINES) - implementation(CLIKT) + implementation(kotlin("stdlib", org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)) // or "stdlib-jdk8" + implementation(Libs.KOTLIN_SERIALIZATION) + implementation(Libs.Fuel.CORE) + implementation(Libs.Fuel.KOTLINX_SERIALIZATION) + implementation(Libs.Fuel.COROUTINES) + implementation(Libs.CLIKT) - detektPlugins(DETEKT_FORMATTING) + detektPlugins(Libs.DETEKT_FORMATTING) - testImplementation(JUNIT) - testImplementation(MOCKK) - testImplementation(TRUTH) - testImplementation(SYSTEM_RULES) + testImplementation(Libs.JUNIT) + testImplementation(Libs.MOCKK) + testImplementation(Libs.TRUTH) + testImplementation(Libs.SYSTEM_RULES) } diff --git a/flank-scripts/buildSrc/src/main/kotlin/Dependencies.kt b/flank-scripts/buildSrc/src/main/kotlin/Dependencies.kt deleted file mode 100644 index 72a7de03f1..0000000000 --- a/flank-scripts/buildSrc/src/main/kotlin/Dependencies.kt +++ /dev/null @@ -1,21 +0,0 @@ -object Kotlin { - const val PLUGIN_JVM = "jvm" - const val PLUGIN_SERIALIZATION = "plugin.serialization" - const val KOTLIN_SERIALIZATION = "org.jetbrains.kotlinx:kotlinx-serialization-runtime:${Versions.KOTLINX_SERIALIZATION}" -} - -const val PLUGIN_SHADOW_JAR = "com.github.johnrengelman.shadow" -const val CLIKT = "com.github.ajalt:clikt:${Versions.CLIKT}" - -object Fuel { - const val CORE = "com.github.kittinunf.fuel:fuel:${Versions.FUEL}" - const val COROUTINES = "com.github.kittinunf.fuel:fuel-coroutines:${Versions.FUEL}" - const val KOTLINX_SERIALIZATION = "com.github.kittinunf.fuel:fuel-kotlinx-serialization:${Versions.FUEL}" -} - -const val TRUTH = "com.google.truth:truth:${Versions.TRUTH}" -const val MOCKK = "io.mockk:mockk:${Versions.MOCKK}" -const val JUNIT = "junit:junit:${Versions.JUNIT}" -const val SYSTEM_RULES = "com.github.stefanbirkner:system-rules:${Versions.SYSTEM_RULES}" -const val DETEKT_PLUGIN = "io.gitlab.arturbosch.detekt" -const val DETEKT_FORMATTING = "io.gitlab.arturbosch.detekt:detekt-formatting:${Versions.DETEKT}" diff --git a/flank-scripts/buildSrc/src/main/kotlin/Versions.kt b/flank-scripts/buildSrc/src/main/kotlin/Versions.kt deleted file mode 100644 index ff03eb79fb..0000000000 --- a/flank-scripts/buildSrc/src/main/kotlin/Versions.kt +++ /dev/null @@ -1,17 +0,0 @@ -object Versions { - const val KOTLIN_VERSION = "1.3.72" - const val SHADOW_JAR = "6.0.0" - const val KOTLINX_SERIALIZATION = "0.20.0" - const val FUEL = "2.2.3" - const val CLIKT = "2.8.0" - // https://github.com/google/truth/releases - const val TRUTH = "1.0" - // https://github.com/mockk/mockk - const val MOCKK = "1.10.0" - const val JUNIT = "4.13" - // https://github.com/stefanbirkner/system-rules/releases - const val SYSTEM_RULES = "1.19.0" - - // https://github.com/detekt/detekt/releases - const val DETEKT = "1.1.0" // version must be same as flank cause they share config with each other -} diff --git a/flank-scripts/gradlew b/flank-scripts/gradlew deleted file mode 100755 index fbd7c51583..0000000000 --- a/flank-scripts/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/flank-scripts/settings.gradle.kts b/flank-scripts/settings.gradle.kts deleted file mode 100644 index 5432991247..0000000000 --- a/flank-scripts/settings.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = "flank-scripts" diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GithubPullRequest.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GithubPullRequest.kt index 6605c68371..658c284e98 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GithubPullRequest.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/GithubPullRequest.kt @@ -1,10 +1,10 @@ package flank.scripts.ci.releasenotes import com.github.kittinunf.fuel.core.ResponseDeserializable -import flank.scripts.utils.toObject import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.list +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json @Serializable data class GithubPullRequest( @@ -22,6 +22,6 @@ data class GithubUser( object GithubPullRequestDeserializer : ResponseDeserializable> { override fun deserialize(content: String): List { - return content.toObject(GithubPullRequest.serializer().list) + return Json.decodeFromString(content) } } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt index 1ddb69e6ad..9401da86e5 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/ci/releasenotes/ReleaseNotesWithType.kt @@ -8,11 +8,11 @@ typealias ReleaseNotesWithType = Map> fun ReleaseNotesWithType.asString(headerTag: String) = StringBuilder(headerTag.markdownH2()) - .appendln() + .appendLine() .apply { this@asString.forEach { (type, messages) -> - appendln(type.markdownH3()) - messages.forEach { appendln(it) } + appendLine(type.markdownH3()) + messages.forEach { appendLine(it) } } } .toString() diff --git a/flank-scripts/src/main/kotlin/flank/scripts/utils/Serialization.kt b/flank-scripts/src/main/kotlin/flank/scripts/utils/Serialization.kt index 160e0b35ce..45d2d7bc9b 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/utils/Serialization.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/utils/Serialization.kt @@ -3,10 +3,9 @@ package flank.scripts.utils import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonConfiguration -private val json by lazy { Json(JsonConfiguration.Stable.copy(ignoreUnknownKeys = true)) } +fun T.toJson(serializationStrategy: SerializationStrategy) = + Json.encodeToString(serializationStrategy, this) -fun T.toJson(serializationStrategy: SerializationStrategy) = json.stringify(serializationStrategy, this) - -fun String.toObject(deserializationStrategy: DeserializationStrategy) = json.parse(deserializationStrategy, this) +fun String.toObject(deserializationStrategy: DeserializationStrategy) = + Json.decodeFromString(deserializationStrategy, this) diff --git a/flank-scripts/src/test/kotlin/flank/scripts/FuelTestRunner.kt b/flank-scripts/src/test/kotlin/flank/scripts/FuelTestRunner.kt index 2db96c8973..be2472b457 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/FuelTestRunner.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/FuelTestRunner.kt @@ -11,7 +11,8 @@ import flank.scripts.release.updatebugsnag.BugSnagRequest import flank.scripts.release.updatebugsnag.BugSnagResponse import flank.scripts.utils.toJson import flank.scripts.utils.toObject -import kotlinx.serialization.builtins.list +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.junit.runners.BlockJUnit4ClassRunner import org.junit.runners.model.Statement @@ -29,7 +30,8 @@ class FuelTestRunner(klass: Class<*>) : BlockJUnit4ClassRunner(klass) { return when { url == "https://api.github.com/repos/Flank/flank/git/refs/tags/success" -> request.buildResponse("", 200) url == "https://api.github.com/repos/flank/flank/releases/latest" && request.headers["Authorization"].contains("token success") -> request.buildResponse(GitHubRelease("v20.08.0").toJson(GitHubRelease.serializer()), 200) - url == "https://api.github.com/repos/flank/flank/commits/success/pulls" -> request.buildResponse(githubPullRequestTest.toJson(GithubPullRequest.serializer().list), 200) + url == "https://api.github.com/repos/flank/flank/commits/success/pulls" -> request.buildResponse( + Json.encodeToString(githubPullRequestTest), 200) request.isFailedGithubRequest() -> request.buildResponse(githubErrorBody, 422) url == "https://build.bugsnag.com/" -> request.handleBugsnagResponse() else -> Response(request.url) diff --git a/flank-scripts/src/test/kotlin/flank/scripts/ci/nexttag/NextReleaseTagGeneratorTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/ci/nexttag/NextReleaseTagGeneratorTest.kt index f62360863b..6318754229 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/ci/nexttag/NextReleaseTagGeneratorTest.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/ci/nexttag/NextReleaseTagGeneratorTest.kt @@ -3,8 +3,9 @@ package flank.scripts.ci.nexttag import com.google.common.truth.Truth.assertThat import io.mockk.every import io.mockk.mockkStatic -import java.time.LocalDate +import org.junit.BeforeClass import org.junit.Test +import java.time.LocalDate class NextReleaseTagGeneratorTest { @@ -23,4 +24,38 @@ class NextReleaseTagGeneratorTest { assertThat(generateNextReleaseTag("v20.08.1")).isEqualTo("v20.09.0") } } + + companion object { + @JvmStatic + @BeforeClass + fun setUp() { + runCatching { + mockkStatic(LocalDate::class) { + // Mockk probably has a bug because sometimes first call of + // every { LocalDate.now() } is failing with strange error: + /* + every/verify {} block were run several times. Recorded calls count differ between runs +Round 1: class java.time.LocalDate.of(-999999999, 1, 1), class java.time.LocalDate.of(999999999, 12, 31), class java.time.LocalDate.now() +Round 2: class java.time.LocalDate.now() +io.mockk.MockKException: every/verify {} block were run several times. Recorded calls count differ between runs +Round 1: class java.time.LocalDate.of(-999999999, 1, 1), class java.time.LocalDate.of(999999999, 12, 31), class java.time.LocalDate.now() +Round 2: class java.time.LocalDate.now() + at io.mockk.impl.recording.SignatureMatcherDetector$detect$1.invoke(SignatureMatcherDetector.kt:25) + at io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:86) + at io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39) + at io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31) + at io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50) + at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:59) + at io.mockk.impl.eval.EveryBlockEvaluator.every(EveryBlockEvaluator.kt:30) + at io.mockk.MockKDsl.internalEvery(API.kt:92) + at io.mockk.MockKKt.every(MockK.kt:98) + at flank.scripts.ci.nexttag.NextReleaseTagGeneratorTest.Should start new tag for new month(NextReleaseTagGeneratorTest.kt:23) + ... + */ + // simple workaround is to call `every` before test class + every { LocalDate.now() } returns LocalDate.of(1, 1, 1) + } + } + } + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..bb8b2fc26b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/test_runner/gradlew b/gradlew similarity index 100% rename from test_runner/gradlew rename to gradlew diff --git a/flank-scripts/gradlew.bat b/gradlew.bat similarity index 96% rename from flank-scripts/gradlew.bat rename to gradlew.bat index 5093609d51..9618d8d960 100644 --- a/flank-scripts/gradlew.bat +++ b/gradlew.bat @@ -29,9 +29,6 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -84,7 +81,6 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% diff --git a/integration_tests/build.gradle.kts b/integration_tests/build.gradle.kts index f83c11acb4..b6dfa0ad38 100644 --- a/integration_tests/build.gradle.kts +++ b/integration_tests/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.backend.common.onlyIf + plugins { java kotlin("jvm") version Versions.KOTLIN @@ -34,6 +36,9 @@ dependencies { } tasks.test { + onlyIf { + System.getProperty("flank-path", "").isNotBlank() + } systemProperty("flank-path", System.getProperty("flank-path")) systemProperty("yml-path", System.getProperty("yml-path")) systemProperty("run-params", System.getProperty("run-params")) diff --git a/integration_tests/src/test/kotlin/IntegrationTests.kt b/integration_tests/src/test/kotlin/IntegrationTests.kt index b98373eea2..45d9ed9d60 100644 --- a/integration_tests/src/test/kotlin/IntegrationTests.kt +++ b/integration_tests/src/test/kotlin/IntegrationTests.kt @@ -1,4 +1,5 @@ -import org.junit.Assert +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Test import utils.toStringMap @@ -10,7 +11,7 @@ class IntegrationTests { val actual = FlankCommand(testParameters.flankPath, testParameters.ymlPath, testParameters.runParams).run(testParameters.workingDirectory) - Assert.assertEquals( + assertEquals( "Expected exit code is: ${testParameters.expectedOutputCode} but actual: ${actual.exitCode}, output:\n${actual.output}", testParameters.expectedOutputCode, actual.exitCode @@ -18,7 +19,7 @@ class IntegrationTests { val expectedOutput = testParameters.outputPattern.toRegex( setOf(RegexOption.DOT_MATCHES_ALL) ) - Assert.assertTrue( + assertTrue( "Output don't match pattern, actual output: ${actual.output}", expectedOutput.find(actual.output)?.value.orEmpty().isNotBlank() ) @@ -30,7 +31,7 @@ class IntegrationTests { val actual = FlankCommand(testParameters.flankPath, testParameters.ymlPath, testParameters.runParams).run(testParameters.workingDirectory) - Assert.assertEquals( + assertEquals( "Expected exit code is: ${testParameters.expectedOutputCode} but actual: ${actual.exitCode}, output:\n${actual.output}", testParameters.expectedOutputCode, actual.exitCode @@ -38,7 +39,7 @@ class IntegrationTests { val expectedOutput = testParameters.outputPattern.toRegex( setOf(RegexOption.DOT_MATCHES_ALL) ) - Assert.assertTrue( + assertTrue( "Output don't match pattern, actual output: ${actual.output}", expectedOutput.find(actual.output)?.value.orEmpty().isNotBlank() ) diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..6001f6acf8 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +rootProject.name = "flank" + +include( + ":test_runner", + ":firebase_apis:test_api", + ":flank-scripts", + ":integration_tests" +) diff --git a/test_runner/bash/update_flank.sh b/test_runner/bash/update_flank.sh index 13bf93f105..af076f6152 100755 --- a/test_runner/bash/update_flank.sh +++ b/test_runner/bash/update_flank.sh @@ -4,6 +4,6 @@ DIR=`dirname "$BASH_SOURCE"` FLANK="$DIR/.." -"$FLANK/gradlew" -p "$FLANK" clean assemble shadowJar +"$FLANK/../gradlew" test_runner:clean test_runner:assemble test_runner:shadowJar cp "$FLANK"/build/libs/flank.jar "$DIR/flank.jar" diff --git a/test_runner/build.gradle.kts b/test_runner/build.gradle.kts index b8b03a6332..ad3ac09ba6 100644 --- a/test_runner/build.gradle.kts +++ b/test_runner/build.gradle.kts @@ -27,10 +27,24 @@ shadowJar.apply { archiveClassifier.set("") archiveBaseName.set(artifactID) mergeServiceFiles() + minimize { + exclude(dependency(Libs.KOTLIN_REFLECT)) + exclude(dependency(Libs.JACKSON_XML)) + exclude(dependency(Libs.JACKSON_DATABIND)) + exclude(dependency(Libs.JACKSON_KOTLIN)) + exclude(dependency(Libs.JACKSON_YAML)) + exclude(dependency(Libs.GSON)) + } @Suppress("UnstableApiUsage") manifest { attributes(mapOf("Main-Class" to "ftl.Main")) } + dependencies { + exclude(dependency(Libs.TRUTH)) + exclude(dependency(Libs.MOCKK)) + exclude(dependency(Libs.JUNIT)) + exclude(dependency(Libs.DETEKT_FORMATTING)) + } } // https://bintray.com/flank/maven @@ -226,7 +240,7 @@ dependencies { // NOTE: iOS support isn't in the public artifact. Use testing jar generated from the private gcloud CLI json // https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.google.apis%22%20AND%20a%3A%22google-api-services-testing%22 // compile("com.google.apis:google-api-services-testing:v1-rev30-1.23.0") - implementation(project(":test_api")) + implementation(project(":firebase_apis:test_api")) implementation(Libs.JSOUP) implementation(Libs.OKHTTP) @@ -243,16 +257,6 @@ dependencies { implementation(Libs.JANSI) } -// Fix Exception in thread "main" java.lang.NoSuchMethodError: com.google.common.hash.Hashing.crc32c()Lcom/google/common/hash/HashFunction; -// https://stackoverflow.com/a/45286710 -configurations.all { - resolutionStrategy { - force("com.google.guava:guava:25.1-jre") - force(Libs.KOTLIN_REFLECT) - exclude(group = "com.google.guava", module = "guava-jdk5") - } -} - tasks.withType { kotlinOptions.jvmTarget = "1.8" } diff --git a/test_runner/buildSrc/build.gradle.kts b/test_runner/buildSrc/build.gradle.kts deleted file mode 100644 index 5a41975548..0000000000 --- a/test_runner/buildSrc/build.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -repositories { - jcenter() -} - -plugins { - `kotlin-dsl` -} diff --git a/test_runner/gradle/wrapper/gradle-wrapper.properties b/test_runner/gradle/wrapper/gradle-wrapper.properties index 186b71557c..ac33e9944a 100644 --- a/test_runner/gradle/wrapper/gradle-wrapper.properties +++ b/test_runner/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/test_runner/gradlew.bat b/test_runner/gradlew.bat deleted file mode 100644 index 9109989e3c..0000000000 --- a/test_runner/gradlew.bat +++ /dev/null @@ -1,103 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/test_runner/settings.gradle.kts b/test_runner/settings.gradle.kts deleted file mode 100644 index c51eaea2b3..0000000000 --- a/test_runner/settings.gradle.kts +++ /dev/null @@ -1,7 +0,0 @@ -rootProject.name = "flank" - -include(":test_api") -project(":test_api").projectDir = File(rootProject.projectDir, "../firebase_apis/test_api") - -include(":integration_tests") -project(":integration_tests").projectDir = File(rootProject.projectDir, "integration_tests") diff --git a/test_runner/src/main/kotlin/ftl/json/OutcomeDetailsFormatter.kt b/test_runner/src/main/kotlin/ftl/json/OutcomeDetailsFormatter.kt index d7a2a4ff9c..992e71fe8e 100644 --- a/test_runner/src/main/kotlin/ftl/json/OutcomeDetailsFormatter.kt +++ b/test_runner/src/main/kotlin/ftl/json/OutcomeDetailsFormatter.kt @@ -14,11 +14,10 @@ import ftl.util.StepOutcome.success import ftl.util.StepOutcome.unset internal fun Outcome?.getDetails( - testSuiteOverviewData: TestSuiteOverviewData? + testSuiteOverviewData: TestSuiteOverviewData?, + isRoboTest: Boolean = false ): String = when (this?.summary) { - success, flaky -> testSuiteOverviewData - ?.getSuccessOutcomeDetails(successDetail?.otherNativeCrash ?: false) - ?: "Unknown outcome" + success, flaky -> if (isRoboTest) "---" else getSuccessOutcomeDetails(testSuiteOverviewData) failure -> failureDetail.getFailureOutcomeDetails(testSuiteOverviewData) inconclusive -> inconclusiveDetail.formatOutcomeDetails() skipped -> skippedDetail.formatOutcomeDetails() @@ -26,6 +25,9 @@ internal fun Outcome?.getDetails( else -> "Unknown outcome" } +private fun Outcome?.getSuccessOutcomeDetails(data: TestSuiteOverviewData?) = + data?.getSuccessOutcomeDetails(this?.successDetail?.otherNativeCrash ?: false) ?: "Unknown outcome" + private fun TestSuiteOverviewData.getSuccessOutcomeDetails( otherNativeCrash: Boolean ) = StringBuilder("$successCount test cases passed").apply { @@ -43,6 +45,7 @@ internal fun FailureDetail?.getFailureOutcomeDetails(testSuiteOverviewData: Test crashed == true -> "Application crashed" timedOut == true -> "Test timed out" notInstalled == true -> "App failed to install" + failedRoboscript == true -> "Test failed to run" else -> testSuiteOverviewData?.buildFailureOutcomeDetailsSummary() ?: "Unknown failure" } + this?.takeIf { it.otherNativeCrash ?: false }?.let { NATIVE_CRASH_MESSAGE }.orEmpty() diff --git a/test_runner/src/main/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummary.kt b/test_runner/src/main/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummary.kt index b05f4c17df..d24bf2baf2 100644 --- a/test_runner/src/main/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummary.kt +++ b/test_runner/src/main/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummary.kt @@ -2,15 +2,16 @@ package ftl.reports.outcome import com.google.api.services.toolresults.model.Environment -fun TestOutcomeContext.createMatrixOutcomeSummary(): Pair> = - steps.calculateAndroidBillableMinutes(projectId, testTimeout) to - if (environments.hasOutcome()) - environments.createMatrixOutcomeSummaryUsingEnvironments() - else { - if (steps.isEmpty()) println("No test results found, something went wrong. Try re-running the tests.") - steps.createMatrixOutcomeSummaryUsingSteps() - } +fun TestOutcomeContext.createMatrixOutcomeSummary() = billableMinutes() to outcomeSummary() -private fun List.hasOutcome() = isNotEmpty() && any { env -> - env.environmentResult?.outcome?.summary == null -}.not() +private fun TestOutcomeContext.billableMinutes() = steps.calculateAndroidBillableMinutes(projectId, testTimeout) + +private fun TestOutcomeContext.outcomeSummary() = + if (environments.hasOutcome()) + createMatrixOutcomeSummaryUsingEnvironments() + else { + if (steps.isEmpty()) println("No test results found, something went wrong. Try re-running the tests.") + createMatrixOutcomeSummaryUsingSteps() + } + +private fun List.hasOutcome() = isNotEmpty() && all { it.environmentResult?.outcome?.summary != null } diff --git a/test_runner/src/main/kotlin/ftl/reports/outcome/CreateTestSuiteOverviewData.kt b/test_runner/src/main/kotlin/ftl/reports/outcome/CreateTestSuiteOverviewData.kt index a8a708ea21..fbc8fab1ed 100644 --- a/test_runner/src/main/kotlin/ftl/reports/outcome/CreateTestSuiteOverviewData.kt +++ b/test_runner/src/main/kotlin/ftl/reports/outcome/CreateTestSuiteOverviewData.kt @@ -23,7 +23,7 @@ internal fun List.createTestSuiteOverviewData(): TestSuiteOverviewData = t .groupBy(Step::axisValue) .values .map { it.mapToTestSuiteOverviews().foldTestSuiteOverviewData() } - .fold(TestSuiteOverviewData()) { acc, data -> acc + data } // Fixme https://github.com/Flank/flank/issues/983 + .fold(TestSuiteOverviewData()) { acc, data -> acc + data } private fun Step.isPrimaryStep() = multiStep?.primaryStep?.rollUp != null || multiStep == null diff --git a/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcome.kt b/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcome.kt index 85a2247dea..98850b4ac1 100644 --- a/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcome.kt +++ b/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcome.kt @@ -12,29 +12,33 @@ data class TestOutcome( val details: String = "", ) -fun List.createMatrixOutcomeSummaryUsingEnvironments(): List = - map(Environment::getTestOutcome) - -private fun Environment.getTestOutcome( - outcome: Outcome? = environmentResult?.outcome -) = TestOutcome( - device = axisValue(), - outcome = outcome?.summary ?: UNKNOWN_OUTCOME, - details = outcome.getDetails(createTestSuiteOverviewData()), -) - -fun List.createMatrixOutcomeSummaryUsingSteps() = groupBy(Step::axisValue).map { (device, steps) -> - steps.getTestOutcome(device) -} - -private fun List.getTestOutcome( - deviceModel: String, - outcome: Outcome? = getOutcomeFromSteps(), -) = TestOutcome( - device = deviceModel, - outcome = outcome?.summary ?: UNKNOWN_OUTCOME, - details = outcome.getDetails(createTestSuiteOverviewData()) -) +fun TestOutcomeContext.createMatrixOutcomeSummaryUsingEnvironments(): List = environments + .map { environment -> + TestOutcome( + device = environment.axisValue(), + outcome = environment.outcomeSummary, + details = environment.getOutcomeDetails(isRoboTest) + ) + } + +private val Environment.outcomeSummary + get() = environmentResult?.outcome?.summary ?: UNKNOWN_OUTCOME + +private fun Environment.getOutcomeDetails(isRoboTest: Boolean) = environmentResult?.outcome.getDetails(createTestSuiteOverviewData(), isRoboTest) + +fun TestOutcomeContext.createMatrixOutcomeSummaryUsingSteps() = steps + .groupBy(Step::axisValue) + .map { (device, steps) -> + TestOutcome( + device = device, + outcome = steps.getOutcomeSummary(), + details = steps.getOutcomeDetails(isRoboTest) + ) + } + +private fun List.getOutcomeSummary() = getOutcomeFromSteps()?.summary ?: UNKNOWN_OUTCOME + +private fun List.getOutcomeDetails(isRoboTest: Boolean) = getOutcomeFromSteps().getDetails(createTestSuiteOverviewData(), isRoboTest) private fun List.getOutcomeFromSteps(): Outcome? = maxByOrNull { StepOutcome.order.indexOf(it.outcome?.summary) diff --git a/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcomeContext.kt b/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcomeContext.kt index 3d63a4477a..d81d6031d5 100644 --- a/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcomeContext.kt +++ b/test_runner/src/main/kotlin/ftl/reports/outcome/TestOutcomeContext.kt @@ -15,7 +15,8 @@ data class TestOutcomeContext( val projectId: String, val environments: List, val steps: List, - val testTimeout: Long + val testTimeout: Long, + val isRoboTest: Boolean ) fun TestMatrix.fetchTestOutcomeContext() = getToolResultsIds().let { ids -> @@ -24,7 +25,8 @@ fun TestMatrix.fetchTestOutcomeContext() = getToolResultsIds().let { ids -> matrixId = testMatrixId, environments = GcToolResults.listAllEnvironments(ids), steps = GcToolResults.listAllSteps(ids), - testTimeout = testTimeout() + testTimeout = testTimeout(), + isRoboTest = isRoboTest() ) } @@ -44,3 +46,5 @@ private fun TestMatrix.testTimeout() = timeoutToSeconds( ?.testTimeout ?: "0s" ) + +private fun TestMatrix.isRoboTest() = testExecutions.orEmpty().any { it?.testSpecification?.androidRoboTest != null } diff --git a/test_runner/src/main/kotlin/ftl/run/platform/common/BeforeRunMessage.kt b/test_runner/src/main/kotlin/ftl/run/platform/common/BeforeRunMessage.kt index 977ce2db19..865bf4740c 100644 --- a/test_runner/src/main/kotlin/ftl/run/platform/common/BeforeRunMessage.kt +++ b/test_runner/src/main/kotlin/ftl/run/platform/common/BeforeRunMessage.kt @@ -11,7 +11,7 @@ internal fun beforeRunMessage(args: IArgs, testShardChunks: List): String val (classesCount, testsCount) = testShardChunks.partitionedTestCases.testAndClassesCount val result = StringBuilder() - val testString = if (testsCount > 0) "$testsCount test${s(testsCount)}" else "" + val testString = if (testsCount == 0 && classesCount != 0) "" else "$testsCount test${s(testsCount)}" val classString = if (classesCount > 0) "$classesCount parameterized class${es(classesCount)}" else "" result.appendLine( diff --git a/test_runner/src/test/kotlin/ftl/json/OutcomeDetailsFormatterTest.kt b/test_runner/src/test/kotlin/ftl/json/OutcomeDetailsFormatterTest.kt index c14c86c063..b3774e5d52 100644 --- a/test_runner/src/test/kotlin/ftl/json/OutcomeDetailsFormatterTest.kt +++ b/test_runner/src/test/kotlin/ftl/json/OutcomeDetailsFormatterTest.kt @@ -6,11 +6,16 @@ import ftl.reports.api.data.TestSuiteOverviewData import ftl.util.StepOutcome import io.mockk.every import io.mockk.mockk +import io.mockk.unmockkAll +import org.junit.After import org.junit.Assert.assertEquals import org.junit.Test internal class OutcomeDetailsFormatterTest { + @After + fun tearDown() = unmockkAll() + @Test fun `should return correct outcome details for success`() { // given @@ -21,8 +26,8 @@ internal class OutcomeDetailsFormatterTest { val testSuiteOverviewData = TestSuiteOverviewData(12, 0, 0, 3, 2, 0.0, 0.0) val successCount = with(testSuiteOverviewData) { total - errors - failures - flakes - skipped } val expectedMessage = "$successCount test cases passed, " + - "${testSuiteOverviewData.skipped} skipped, " + - "${testSuiteOverviewData.flakes} flaky" + "${testSuiteOverviewData.skipped} skipped, " + + "${testSuiteOverviewData.flakes} flaky" // when val result = mockedOutcome.getDetails(testSuiteOverviewData) @@ -41,9 +46,9 @@ internal class OutcomeDetailsFormatterTest { val testSuiteOverviewData = TestSuiteOverviewData(12, 0, 0, 3, 2, 0.0, 0.0) val successCount = with(testSuiteOverviewData) { total - errors - failures - flakes - skipped } val expectedMessage = "$successCount test cases passed, " + - "${testSuiteOverviewData.skipped} skipped, " + - "${testSuiteOverviewData.flakes} flaky" + - " (Native crash)" + "${testSuiteOverviewData.skipped} skipped, " + + "${testSuiteOverviewData.flakes} flaky" + + " (Native crash)" // when val result = mockedOutcome.getDetails(testSuiteOverviewData) @@ -57,19 +62,14 @@ internal class OutcomeDetailsFormatterTest { // given val mockedOutcome = mockk { every { summary } returns StepOutcome.failure - every { failureDetail } returns mockk { - every { crashed } returns false - every { timedOut } returns false - every { notInstalled } returns false - every { otherNativeCrash } returns false - } + every { failureDetail } returns mockk(relaxed = true) {} } val testSuiteOverviewData = TestSuiteOverviewData(12, 3, 3, 3, 2, 0.0, 0.0) val expectedMessage = "${testSuiteOverviewData.failures} test cases failed, " + - "${testSuiteOverviewData.errors} errors, " + - "1 passed, " + - "${testSuiteOverviewData.skipped} skipped, " + - "${testSuiteOverviewData.flakes} flaky" + "${testSuiteOverviewData.errors} errors, " + + "1 passed, " + + "${testSuiteOverviewData.skipped} skipped, " + + "${testSuiteOverviewData.flakes} flaky" // when val result = mockedOutcome.getDetails(testSuiteOverviewData) @@ -146,12 +146,7 @@ internal class OutcomeDetailsFormatterTest { // given val mockedOutcome = mockk { every { summary } returns StepOutcome.failure - every { failureDetail } returns mockk { - every { crashed } returns false - every { timedOut } returns false - every { notInstalled } returns false - every { otherNativeCrash } returns false - } + every { failureDetail } returns mockk(relaxed = true) {} } val expectedMessage = "Unknown failure" @@ -354,4 +349,16 @@ internal class OutcomeDetailsFormatterTest { otherNativeCrash = null }.getFailureOutcomeDetails(null) } + + @Test + fun `should print message for failed robo test`() { + val mockedOutcome = mockk { + every { summary } returns StepOutcome.failure + every { failureDetail } returns FailureDetail().apply { failedRoboscript = true } + } + + val result = mockedOutcome.getDetails(null, true) + + assertEquals("Test failed to run", result) + } } diff --git a/test_runner/src/test/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummaryKtTest.kt b/test_runner/src/test/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummaryKtTest.kt new file mode 100644 index 0000000000..0b2851e8c7 --- /dev/null +++ b/test_runner/src/test/kotlin/ftl/reports/outcome/CreateMatrixOutcomeSummaryKtTest.kt @@ -0,0 +1,126 @@ +package ftl.reports.outcome + +import com.google.api.services.toolresults.model.Environment +import com.google.api.services.toolresults.model.Step +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import kotlin.reflect.full.createInstance +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class CreateMatrixOutcomeSummaryKtTest { + + @Before + fun setUp() { + mockkStatic("ftl.reports.outcome.UtilKt") + mockkStatic("ftl.reports.outcome.BillableMinutesKt") + } + + @After + fun tearDown() = unmockkAll() + + @Test + fun `should create TestOutcome list for success robo test - from environments`() { + val env: Environment = make { + environmentResult = make { + outcome = make { summary = "success" } + } + } + every { env.axisValue() } returns "anyDevice" + val context = TestOutcomeContext( + matrixId = "anyMatrix", + projectId = "anyProject", + testTimeout = Long.MAX_VALUE, + steps = emptyList(), + isRoboTest = true, + environments = listOf(env) + ) + + val (_, result) = context.createMatrixOutcomeSummary() + + assertEquals("---", result[0].details) + assertEquals("anyDevice", result[0].device) + assertEquals("success", result[0].outcome) + } + + @Test + fun `should create TestOutcome list for failed robo test - from environments`() { + val env: Environment = make { + environmentResult = make { + outcome = make { + summary = "failure" + failureDetail = make { failedRoboscript = true } + } + } + } + every { env.axisValue() } returns "anyDevice" + val context = TestOutcomeContext( + matrixId = "anyMatrix", + projectId = "anyProject", + testTimeout = Long.MAX_VALUE, + steps = emptyList(), + isRoboTest = true, + environments = listOf(env) + ) + + val (_, result) = context.createMatrixOutcomeSummary() + + assertEquals("Test failed to run", result[0].details) + assertEquals("anyDevice", result[0].device) + assertEquals("failure", result[0].outcome) + } + + @Test + fun `should create TestOutcome list for success robo test - from steps`() { + val steps: List = listOf(make { + outcome = make { summary = "success" } + }) + every { steps[0].axisValue() } returns "anyDevice" + every { steps.calculateAndroidBillableMinutes(any(), any()) } returns make() + val context = TestOutcomeContext( + matrixId = "anyMatrix", + projectId = "anyProject", + testTimeout = Long.MAX_VALUE, + steps = steps, + isRoboTest = true, + environments = emptyList() + ) + + val (_, result) = context.createMatrixOutcomeSummary() + + assertEquals("---", result[0].details) + assertEquals("anyDevice", result[0].device) + assertEquals("success", result[0].outcome) + } + + @Test + fun `should create TestOutcome list for failed robo test - from steps`() { + val steps: List = listOf(make { + outcome = make { + summary = "failure" + failureDetail = make { failedRoboscript = true } + } + }) + every { steps[0].axisValue() } returns "anyDevice" + every { steps.calculateAndroidBillableMinutes(any(), any()) } returns make() + val context = TestOutcomeContext( + matrixId = "anyMatrix", + projectId = "anyProject", + testTimeout = Long.MAX_VALUE, + steps = steps, + isRoboTest = true, + environments = emptyList() + ) + + val (_, result) = context.createMatrixOutcomeSummary() + + assertEquals("Test failed to run", result[0].details) + assertEquals("anyDevice", result[0].device) + assertEquals("failure", result[0].outcome) + } +} + +private inline fun make(block: T.() -> Unit = {}): T = T::class.createInstance().apply(block)