diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 8e2a5955..00000000 --- a/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -root = true - -[*.{kt,kts}] -indent_size = 2 \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index f1956d5e..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [runningcode] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 695ad1c1..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -registries: - gradle-plugin-portal: - type: maven-repository - url: https://plugins.gradle.org/m2 - username: dummy # Required by dependabot - password: dummy # Required by dependabot -updates: - - package-ecosystem: "gradle" - directory: "/" - registries: - - gradle-plugin-portal - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml deleted file mode 100644 index bbbab046..00000000 --- a/.github/workflows/gradle-build.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "CI" -on: [push, pull_request] -jobs: - validation: - name: "Building" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: gradle/actions/wrapper-validation@v5 - - name: "Set up JDK 17" - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - uses: gradle/actions/setup-gradle@v5 - name: Setup Gradle - - name: Check Fladle - run: ./gradlew assembleDebug assembleDebugAndroidTest printYml check :fladle-plugin:check - - name: Publish Snapshot - if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request' }} - env: - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_SIGNING_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }} - run: ./gradlew :fladle-plugin:publishToMavenCentral --no-configuration-cache diff --git a/.github/workflows/gradle-release.yml b/.github/workflows/gradle-release.yml deleted file mode 100644 index 6565d5bb..00000000 --- a/.github/workflows/gradle-release.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: "Release" -on: - push: - tags: - - 'v*' - workflow_dispatch: - -jobs: - release: - name: "Publish Release" - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-java@v5 - with: - java-version: "17" - distribution: "temurin" - - uses: gradle/actions/setup-gradle@v5 - - name: Publish to Maven Central - env: - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_SIGNING_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }} - run: ./gradlew :fladle-plugin:publishToMavenCentral --no-configuration-cache - - name: Publish to Gradle Plugin Portal - env: - GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} - GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.GPG_SIGNING_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_KEY_PASSWORD }} - run: ./gradlew :fladle-plugin:publishPlugins - - name: Create GitHub Release - env: - GH_TOKEN: ${{ github.token }} - run: gh release create "${{ github.ref_name }}" --generate-notes diff --git a/.github/workflows/mkdocs-deploy.yml b/.github/workflows/mkdocs-deploy.yml deleted file mode 100644 index 099a4d32..00000000 --- a/.github/workflows/mkdocs-deploy.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Publish docs via GitHub Pages -on: - push: - branches: - - master - -jobs: - build: - name: Deploy docs - runs-on: ubuntu-latest - steps: - - name: Checkout master - uses: actions/checkout@v6 - - - name: Deploy docs - uses: mhausenblas/mkdocs-deploy-gh-pages@1.26 - env: - GITHUB_TOKEN: ${{ secrets.PERSONAL_TOKEN }} - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e0717f80..00000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea -.DS_Store -build/ -/captures -.externalNativeBuild - -# Ignore service account credentials. -flank-gradle-*.json - -# Mkdocs generated -site - -# Pyhon Environment -lib -bin diff --git a/dummy_app.apk b/.nojekyll similarity index 100% rename from dummy_app.apk rename to .nojekyll diff --git a/.opensource/project.json b/.opensource/project.json deleted file mode 100644 index 3f83d482..00000000 --- a/.opensource/project.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Fladle", - "type": "library", - "platforms": [ "Android" ], - "content": "README.md", - "tabs": [ - { "title": "Documentation", "href": "https://runningcode.github.io/fladle/" } - ] -} diff --git a/404.html b/404.html new file mode 100644 index 00000000..915d158b --- /dev/null +++ b/404.html @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + + + + + + Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 23494f9c..00000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2020 Nelson Osacky - - 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 - - http://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. diff --git a/README.md b/README.md deleted file mode 100644 index c9370ec8..00000000 --- a/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Fladle - -Easily scale your Android Instrumentation tests on Firebase Test Lab with Flank. - -### Documentation is at [runningcode.github.io/fladle](https://runningcode.github.io/fladle) diff --git a/android-library-no-tests/build.gradle.kts b/android-library-no-tests/build.gradle.kts deleted file mode 100644 index 76dda11f..00000000 --- a/android-library-no-tests/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id("com.android.library") -} - -android { - compileSdk = 33 - namespace = "com.osacky.flank.gradle.sample.library" - defaultConfig { - minSdk = 23 - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -fulladleModuleConfig { - debugApk.set(rootProject.file("dummy_app.apk").path) -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.navigation.fragment.ktx) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) -} - diff --git a/android-library-no-tests/src/main/AndroidManifest.xml b/android-library-no-tests/src/main/AndroidManifest.xml deleted file mode 100644 index 8072ee00..00000000 --- a/android-library-no-tests/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt deleted file mode 100644 index 964eb395..00000000 --- a/android-library-no-tests/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.osacky.flank.gradle.sample - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } -} diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 00000000..1cf13b9f Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/javascripts/bundle.51d95adb.min.js b/assets/javascripts/bundle.51d95adb.min.js new file mode 100644 index 00000000..b20ec683 --- /dev/null +++ b/assets/javascripts/bundle.51d95adb.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(kt)for(var r of kt(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Ht((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,We=T.tagName;return!!(We==="INPUT"&&a[Ke]&&!T.readOnly||We==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function re(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,re())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Ht(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(re,z){d.append(z,re)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,re=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,re.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Ht((Lt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},re=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=re;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},We=Ke;function Ie(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(w){return typeof w}:Ie=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Ie(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Ie(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",Rt=We({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Le,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(Rt){Le=Le&&!!document.queryCommandSupported(Rt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Xe?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=Fe.EMPTY;function It(e){return e instanceof Fe||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new Fe(function(){n.currentObservers=null,De(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ne(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ne(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var On=function(e){ne(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Dt(kr(e))?e.pop():void 0}function Vt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ki();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return ln(this,arguments,function(){var r,n,o,i;return Pt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Xe(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Xe(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Xe(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(Nt(e))return Qi(e);if(pt(e))return Yi(e);if(zt(e))return Gi(e);if(qt(e))return _n(e);if(Yt(e))return Bi(e);if(Bt(e))return Ji(e)}throw Kt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):me,Oe(1),r?He(t):zn(function(){return new Xt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function fe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},re=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;re(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var We=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),We.subscribe(Ke),!u&&d>0&&(u=new et({next:function(Ie){return We.next(Ie)},error:function(Ie){v=!0,B(),p=jr(re,o,Ie),We.error(Ie)},complete:function(){h=!0,B(),p=jr(re,a),We.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),Y())}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,we),l(()=>Be(e)),N(Be(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,we),l(()=>rr(e)),N(rr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>de(e)))),N(de(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function sr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=de(e),o=bt(e);return r>=o.height-n.height-t}),Y())}var cr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return cr[e].checked}function qe(e,t){cr[e].checked!==t&&cr[e].click()}function je(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),fe());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>se(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function ur(e,t={credentials:"same-origin"}){return ve(fetch(`${e}`,t)).pipe(ce(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ur(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return ur(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Be(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Authentication

+

There are two authentication mechanisms for using Fladle.

+

It is recommended to use user authentication on local development machines to avoid sharing credentials and a service account on CI.

+

User authentication

+

Credentials are stored in ~/.flank.

+
    +
  1. ./gradlew flankAuth
  2. +
  3. Sign in to web browser.
  4. +
  5. Specify projectId in fladle configuration
  6. +
  7. ./gradlew runFlank
  8. +
+

Service account credentials

+
    +
  1. +

    Create a service account. Service accounts aren't subject to spam checks or captcha prompts, which could + otherwise block your CI builds. Create a service account with an Editor role in the + Google Cloud Platform console.

    +
  2. +
  3. +

    Enable required APIs. After logging in using the service account: In the Google Developers Console API Library + page, enable the Google Cloud Testing API and Cloud Tool Results API. To enable these APIs, type these API names into + the search box at the top of the console, and then click Enable API on the overview page for that API.

    +
  4. +
  5. +

    After creating the account go to Keys, click Add Key -> Create a new Key. Select JSON for the key type. This will download the json credentials.

    +
  6. +
  7. +

    Configure the Fladle extension serviceAccountCredentials to point to the credentials.

    +
  8. +
+

Above instructions are based on Google instruction for authenticating with CI.

+

See also Flank's instructions for authenticating with a service account.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 710a2a98..00000000 --- a/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -buildscript { - repositories { - google() - mavenCentral() - } -} - -plugins { - alias(libs.plugins.agp) apply false - alias(libs.plugins.ben.manes.versions) - id "com.osacky.fulladle" - alias(libs.plugins.kotlinter) -} - -allprojects { - apply plugin: "org.jmailen.kotlinter" -} - -fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("sample/flank-gradle-5cf02dc90531.json") -} - -tasks.wrapper.configure { - gradleVersion = '9.4.0' -} - -def isNonStable = { String version -> - def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) } - def regex = /^[0-9,.v-]+(-r)?$/ - return !stableKeyword && !(version ==~ regex) -} - -tasks.named('dependencyUpdates').configure { - // Example 1: reject all non stable versions - rejectVersionIf { - isNonStable(it.candidate.version) - } -} diff --git a/changelog/index.html b/changelog/index.html new file mode 100644 index 00000000..1abd54c4 --- /dev/null +++ b/changelog/index.html @@ -0,0 +1,1666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Changelog - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Changelog

+

Unreleased

+

0.21.0

+
    +
  • Minimum required Gradle version is now 9.1
  • +
  • Fixed support for Android Gradle Plugin version 9.0.1
  • +
+

0.20.0

+
    +
  • Botched release. Do not use.
  • +
+

0.19.0

+
    +
  • Minimum required JVM version is now 17.
  • +
  • Minimum supported Gradle version is now 7.3.
  • +
  • Fix support for Gradle 9.0.0. PR Thanks kevinguitar
  • +
+

0.18.0

+
    +
  • Use non-deprecated device models and versions in default config. PR Thanks Kaibolay
  • +
+

0.17.5

+
    +
  • Support Develocity JUnit XML Reporting PR Thanks ZacSweers
  • +
  • Min supported Gradle version is now 6.5
  • +
  • Updated Flank version to 23.10.1 +*
  • +
+

0.17.4

+
    +
  • Add support for additionalTestApks in YamlConfigWriterTask. PR. Thanks ZacSweers
  • +
  • Add abi option to support selecting a particular debug APK in projects that use ABI splits to produce multiple APKs. PR
  • +
+

0.17.2

+
    +
  • Fix configuration cache support.
  • +
+

0.17.0

+
    +
  • Fix quotes around environment variables and formatting issues.
  • +
+

0.16.3

+
    +
  • Remove deprecation warning using main in JavaExec task.
  • +
  • Conditionally declare outputs on FlankExecutionTask and mark as not up-to-date. PR
  • +
  • Fix environmentVariables not passed to flank.yml PR Thanks Sinan
  • +
+

0.16.2

+
    +
  • Fix crash accessing TestedExtension in non-android modules PR Thanks asadsalman
  • +
+

0.16.1

+
    +
  • Treat app and library modules the same in Fulladle PR Thanks asadsalman
  • +
  • Added fulladleModuleConfig to app modules PR
  • +
+
+

Breaking API Change

+

Previously, only app modules could be picked up as root-level modules in Fulladle. This has changed, now either app or library modules can be picked up (though we try app modules first). If a library module is picked up as a the root-level module, it must specifiy a debugApk through either the root fladle block or the module's own fulladleModuleConfig block.

+
+

0.16.0

+
    +
  • Allow excluding modules from Fulladle PR
  • +
  • Allow configuring extra parameters on additionalTestApks PR
  • +
+

0.15.1

+
    +
  • Allow Flank snapshot usage PR Thanks AndrewReitz
  • +
  • Fix incorrect task group name. PR
  • +
  • Snapshots are now compatible with java 8 PR
  • +
  • Configuration validation happens at task execution time. Fixes #239
  • +
  • Configure assemble dependency per variant. Fixes #233
  • +
  • Add required smartFlankGcsPath to samples Fixes #236
  • +
+

0.15.0

+
    +
  • Add support for async flag. PR.
  • +
  • Add flag for depending on assembling of debug apk and instrumentation apk automatically. dependOnAssemble PR Thanks asadsalman
  • +
  • Gradle 7.0 Compatibility
  • +
+

0.14.1

+ +

0.14.0

+ +

0.13.1

+
    +
  • Fix flankAuth task throwing exception. Fixes #195
  • +
  • Add support for newly added flank options PR#186 Thanks pawelpasterz:
      +
    • default-test-time
    • +
    • default-class-test-time
    • +
    • additional-apks
    • +
    • use-average-test-time-for-new-tests
    • +
    • disable-results-upload
    • +
    +
  • +
+

0.13.0

+ +
+

Breaking API Change

+

Use lazy properties Fixes #92 PR Thanks pawelpasterz

+
+

0.12.1

+
    +
  • Don't override debug or instrumentation apk if already specified. Fixes #172.
  • +
+

0.12.0

+
    +
  • Don't override property values in individual configurations. Fixes #158.
  • +
  • Update Flank to 20.08.3.
  • +
  • Use Github actions instead of CircleCI
  • +
  • Write Yaml file to task specific output directory and add up-to-date checks. #159 Fixes #147 Thanks CristianGM
  • +
  • Add support for full-junit-result and legacy-junit-result. #170 Fixes #157. Thanks MatthewTPage
  • +
+

0.11.0

+
    +
  • Update Flank to 20.07.0.
  • +
  • Only add additional-test-apks for modules that have tests. PR
  • +
  • Experimental configuration caching support. PR1PR2
  • +
+

0.10.2

+ +
+

Breaking API change

+

Add time units for timeout. timeoutMin has been renamed to testTimeout. PR Thanks pawelpasterz

+
+

0.10.1

+
    +
  • [Fix] Allow for specifying roboScript without specifying instrumentationApk. Fixes #128.
  • +
+

0.10.0

+ +
+

Breaking API Change

+

additionalTestApks now uses ListProperty instead of the previous Map. This is to allow for lazy configuration of the provided files.

+
+
+

Warning

+

Minimum supported Gradle version is now 5.4.

+
+

0.9.4

+ +

0.9.2

+
+

Breaking API change

+

debugApk and instrumentationApk now use Lazy Property API to avoid resolving at configuration time.

+
+

0.9.1

+
    +
  • Bugfix: ability to set flank version. PR
  • +
+
+

Breaking API Change

+

serviceAccountCredentials now uses Lazy Property API. See Configuration for details on how to set it. PR

+
+
+

Warning

+

Minimum required Gradle Version is now 5.1.

+
+
+

Warning

+

Dropped support for Flank 7.X and lower.

+
+

0.9.0

+
    +
  • Do not add flank maven repo. PR
  • +
  • Allow specifying custom flank coordinates. PR
  • +
  • Change ordering and use file provider. PR
  • +
+

0.8.1

+
    +
  • Add support for additionalTestApks. PR Thanks japplin.
  • +
  • Add support for resultsDir. PR
  • +
+

0.8.0

+
    +
  • BREAKING: devices now takes a List<Map<String, String>> instead of a List<Device>. See the [#README.md] for an example. PR Thanks zlippard.
  • +
  • Add support for keep-file-path. PR Thanks tahirhajizada.
  • +
+

0.7.0

+ +

0.6.7

+
    +
  • Allow using wildcards in debugApk or instrumentationApk path by not checking that file exists. PR
  • +
+

0.6.6

+
    +
  • Bump flank version to 7.0.0
  • +
  • Publish to mavenCentral()
  • +
+

0.6.5

+
    +
  • Add support for results-bucket gcloud config option. PR Thanks c-moss
  • +
  • Default flank version 6.2.3
  • +
  • Lower build logging level
  • +
+

0.6.4

+
    +
  • Allow setting android version number as string to allow for preview versions. PRThanks JeroenMols.
  • +
+

0.6.3

+ +

0.6.2

+ +

0.6.1

+ +

0.6.0

+
    +
  • Default flank version 5.0.1
  • +
  • Rename yaml output to match new flank version.
  • +
  • Add support for shard time.
  • +
+

0.5.2

+
    +
  • Improve error messages for missing arguments in the fladle extension.
  • +
+

0.5.1

+
    +
  • Test multipleconfig to actually write yml Thanks PR winterDroid.
  • +
  • JavaExec uses classpath instead of jar directly. Thanks PR winterDroid.
  • +
+

0.5.0

+
    +
  • Use flank as a maven artifact.
  • +
  • Group Fladle Tasks
  • +
+

0.4.1

+
    +
  • Detect AndroidX test orchestrator
  • +
  • Fix detection of debug and instrumentation apk paths.
  • +
+

0.4.0

+
    +
  • Add support for all configuration options. Thanks PR winterDroid.
  • +
+

Breaking Changes:

+

Previous users of clearPackageData = true will now need to use: +

environmentVariables = [
+  "clearPackageData": "true"
+]
+

+

0.3.8

+
    +
  • Fix broken flakyTestAttempts.
  • +
+

0.3.7

+
    +
  • Add support for flakyTestAttempts.
  • +
+

0.3.6

+
    +
  • Add support for environment variable clearPackageData. Thanks @anderssu !
  • +
+

0.3.5

+
    +
  • Automatically configure the use of test orchestrator.
  • +
+

0.3.4

+
    +
  • Add support for setting results-history-name.
  • +
  • Add support for selecting variant to test instead of apk path.
  • +
+

0.3.3

+
    +
  • Add support for setting smartFlankGcsPath
  • +
  • Capitalize task names.
  • +
+

0.3.2

+
    +
  • Actually fix gradle download task bug.
  • +
+

0.3.1

+
    +
  • Maybe fix bug similar to: https://github.com/michel-kraemer/gradle-download-task/issues/58
  • +
+

0.3.0

+
    +
  • Add support for multiple test configurations.
  • +
+

0.2.12

+
    +
  • Fix broken flankDoctor task.
  • +
+

0.2.11

+
    +
  • Add minimum Gradle version check. (4.9 is required because we use the lazy task configuration API))
  • +
+

0.2.10

+
    +
  • Add support for specifying Flank snapshot versions. See README for configuration options.
  • +
+

0.2.9

+
    +
  • Add support for multiple build targets. PR. Thanks winterDroid.
  • +
  • Add support for testShards and repeatTests flank options. See README for configuration options.
  • +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/configuration/index.html b/configuration/index.html new file mode 100644 index 00000000..a2ab0c55 --- /dev/null +++ b/configuration/index.html @@ -0,0 +1,2472 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Configuration

+

The following configuration options must be set inside the fladle block. See the sample configuration below. There is also a groovy sample and a kotlin sample.

+

serviceAccountCredentials

+
+

User Authentication is also possible. See Authentication.

+
+
+
serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json")
+
+
+
+
serviceAccountCredentials.set("project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json")
+
+
+
+

Optionally, the serviceAccountCredentials can be set with environment variables but then the projectId parameter must be set.

+

See Authentication for more info.

+

variant

+
+

Note

+

variant must be set if using buildFlavors in order to automatically configure the debugApk and testApk.

+
+

Set the variant to automatically configure for testing. A build variant is a combination of buildFlavor and buildType. +This must also be set when testing against a non-default variant. +For example: 'debug' or 'freeDebug'. +Put this inside your Fladle block.

+
+
variant = "freeDebug"
+
+
+
+
variant.set("freeDebug")
+
+
+
+

Sample Configuration

+
fladle {
+    // Required parameters
+    serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json")
+    variant = "freeDebug"
+
+    // Optional parameters
+    useOrchestrator = false
+    environmentVariables = [
+        "clearPackageData": "true"
+    ]
+    directoriesToPull = [
+        "/sdcard/screenshots"
+    ]
+    filesToDownload = [
+        ".*/screenshots/.*"
+    ]
+    testTargets = [
+        "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView"
+    ]
+    testTimeout = "15m"
+    recordVideo = false
+    performanceMetrics = false
+    devices = [
+        [ "model": "SmallPhone.arm", "version": "28" ],
+        [ "model": "MediumPhone.arm", "version": "33" ]
+    ]
+    projectId("flank-gradle")
+    flankVersion = "23.10.1"
+    debugApk = "$buildDir/outputs/apk/debug/sample-debug.apk"
+    instrumentationApk = "$buildDir/outputs/apk/androidTest/debug/sample-debug-androidTest.apk"
+    additionalTestApks = [
+        "- app:  $buildDir/outputs/apk/debug/sample-debug.apk",
+        "  test: $buildDir/outputs/apk/androidTest/debug/sample2-debug-androidTest.apk",
+        "- test: ${rootProject.buildDir}/database/module/outputs/apk/database-module-androidTest.apk"
+    ]
+    autoGoogleLogin = true
+    maxTestShards = 8 //or numUniformShards=5 cannot use both.
+    shardTime = 120
+    smartFlankGcsPath = "gs://tmp_flank/flank/test_app_android.xml"
+    configs {
+        oranges {
+            useOrchestrator.set(false)
+            testTargets.set(project.provider { [
+                "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail"
+            ] })
+        }
+    }
+    resultsBucket("my-results-bucket-name")
+    keepFilePath = true
+    runTimout = "45m"
+    ignoreFailedTests = false
+    disableSharding = false
+    smartFlankDisableUpload = false
+    testRunnerClass = "com.foo.TestRunner"
+    localResultsDir = "flank"
+    clientDetails = [
+      "key1": "value1",
+      "key2": "value2"
+    ]
+    testTargetsAlwaysRun = [
+      "com.example.TestSuite#test1",
+      "com.example.TestSuite#test2"
+    ]
+    otherFiles = [
+      "/sdcard/dir1/file1.txt": "/my/example/path/file1.txt",
+      "/sdcard/dir2/file2.txt": "/my/example/path/file2.txt"
+    ]
+    networkProfile = "LTE"
+    roboDirectives = [
+      ["click", "button1", ""],
+      ["ignore", "button2"],
+      ["text", "field1", "my text"],
+    ]
+    outputStyle = 'multi'
+    legacyJunitResult = false
+    fullJunitResult = false
+    additionalApks = [
+      "gs://path/to/app1.apk",
+      "localPath/to/app2.apk"
+    ]
+    defaultTestTime = 5.3
+    defaultClassTestTime = 180.5
+    useAverageTestTimeForNewTests = true
+    disableResultsUpload = true
+    grantPermissions = "none"
+    type = "game-loop"
+    scenarioLabels = [
+      "label1",
+      "label2" 
+    ]
+    scenarioNumbers = [ 1, 23, 52 ]
+    obbFiles = [
+      "local/file/path/test1.obb",
+      "local/file/path/test2.obb"
+    ]
+    obbNames = [
+      "patch.0300110.com.example.android.obb",
+      "patch.0300111.com.example.android.obb"
+    ]
+    testTargetsForShard = [
+      "package com.package1.for.shard1, com.package2.for.shard1",
+      "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2",
+      "class com.foo.ClassForShard3; package com.package.for.shard3"
+    ]
+    failFast = true
+    dependOnAssemble = true
+}
+
+

useOrchestrator

+

Whether or not we should use the android test orchestrator to run this tests. +Set this to true when the build.gradle file includes testOptions.execution 'ANDROID_TEST_ORCHESTRATOR'

+
+
useOrchestrator = true
+
+
+
+
useOrchestrator.set(true)
+
+
+
+

testTargets

+

Set multiple testTargets to be run by Flank. These are used to whitelist or blacklist test classes, test cases and test annotations. +See Google Cloud Firebase docs for more information.

+
+
testTargets = [
+        "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView"
+]
+
+
+
+
testTargets.set(listOf(
+        "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView"
+))
+
+
+
+

devices

+

A list of devices to run the tests against. When list is empty, a default device will be used. Each device entry is a map. The valid keys in the map are model, version, orientation, and locale. When a key is not set or is null, a default value will be used.

+
+
devices = [
+        [ "model": "MediumPhone.arm", "version": "26" ],
+        [ "model": "MediumPhone.arm", "version": "33" ]
+]
+
+
+
+
devices.set(listOf(
+    mapOf("model" to "MediumPhone.arm", "version" to "26" ),
+    mapOf("model" to "MediumPhone.arm", "version" to "33" )
+))
+
+
+
+

projectId

+

The projectId is a unique identifier which can be found in the project's URL: https://console.firebase.google.com/project/<projectId> +This is automatically discovered based on the service credential by default.

+
+
projectId = "flank-gradle"
+
+
+
+
projectId.set("flank-gradle")
+
+
+
+

flankVersion

+

Need a different Flank version? Specify it with flankVersion.

+

To use a snapshot:

+
+
flankVersion = "flank_snapshot"`
+
+
+
+
flankVersion.set("flank_snapshot")
+
+
+
+

Need more than 50 shards? Use Flank 8.1.0.

+

To use a different version:

+
+
flankVersion = "23.10.1"
+
+
+
+
flankVersion.set("23.10.1")
+
+
+
+

flankCoordinates

+

Specify custom flank maven coordinates.

+
+
flankCoordinates = "com.github.flank:flank"
+
+
+
+
flankCoordinates.set("com.github.flank:flank")
+
+
+
+

debugApk

+

This is a string representing the path to the app's debug apk. +Supports wildcard characters. +Optional, prefer to set variant.

+
+
debugApk = project.provider { "${buildDir.toString()}/outputs/apk/debug/*.apk" }
+
+
+
+
debugApk.set(project.provider { "${buildDir.toString()}/outputs/apk/debug/*.apk" })
+
+
+
+

instrumentationApk

+

This is a string representing the path to the app's instrumentaiton apk. +Supports wildcard characters. +Optional, prefer to set variant. +InstrumenationApk should not be set when using roboScript.

+
+
instrumentationApk = project.provider { "${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk" }
+
+
+
+
instrumentationApk.set(project.provider { "${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk" })
+
+
+
+

additionalTestApks

+

Paths to additional test configurations. +Order matters. A test apk is run with the nearest previous listed app apk. +For library modules, add them to the list with a - test: in front. For test apks which belong to an application module, add them with test:. +It is not required to list an app apk here. If there is no app apk listed in additionalTestApks, the test apks are run against the debugApk.

+
+
additionalTestApks.value(project.provider { [
+"- app: ../main/app/build/output/apk/debug/app.apk",
+"  test: ../main/app/build/output/apk/androidTest/debug/app-test.apk",
+"- app: ../sample/app/build/output/apk/debug/sample-app.apk",
+"  test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk",
+"- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk",
+"- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk"
+]})
+
+
+
+
additionalTestApks.value(project.provider { listOf(
+"- app: ../main/app/build/output/apk/debug/app.apk",
+"  test: ../main/app/build/output/apk/androidTest/debug/app-test.apk",
+"- app: ../sample/app/build/output/apk/debug/sample-app.apk",
+"  test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk",
+"- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk",
+"- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk"
+)})
+
+
+
+

autoGoogleLogin

+

Whether to automatically log in using a preconfigured google account. More Info

+
+
autoGoogleLogin = false
+
+
+
+
autoGoogleLogin.set(false)
+
+
+
+

environmentVariables

+

Environment variables are mirrored as extra options to the am instrument -e KEY1 VALUE1 … command and passed to your test runner (typically AndroidJUnitRunner). Examples

+
+
environmentVariables = [
+    // Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner)
+    "clearPackageData": "true"
+]
+
+
+
+
environmentVariables = mapOf(
+    // Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner)
+    "clearPackageData" to "true"
+)
+
+
+
+

testShards

+
+

Deprecated

+

Use maxTestShards instead.

+
+

The maximum number of shards. Fladle will throw an error when used together with maxTestShards or numUniformShards.

+
+
testShards = 5
+
+
+
+
testShards.set(5)
+
+
+
+

maxTestShards

+

The maximum number of shards. Fladle will throw an error when used together with testShards or numUniformShards.

+
+
maxTestShards = 8
+
+
+
+
maxTestShards.set(8)
+
+
+
+

shardTime

+

The amount of time tests within a shard should take. +When set to > 0, the shard count is dynamically set based on time up to the maximum limit defined by maxTestShards +2 minutes (120) is recommended. +default: -1 (unlimited)

+
+
shardTime = 120
+
+
+
+
shardTime.set(120)
+
+
+
+

repeatTests

+

The number of times to repeat each test. Uses Flank's default value when not specified.

+
+
repeatTests = 1
+
+
+
+
repeatTests.set(1)
+
+
+
+

configs

+

Give a name to a custom flank task and configure its options. The name is appended to the end of the flank task. For example runFlank becomes runFlank<name>.

+
+
configs {
+  repeatOneHundred {
+    // DSL sugar for container elements is missing (= syntax): https://github.com/gradle/gradle/issues/9987
+    repeatTests.set(100)
+  }
+}
+
+
+
+
configs {
+  create("repeatOneHundred") {
+    repeatTests.set(100)
+  }
+}
+
+
+
+

In the above example, the configuration is inherited from the outer fladle config but with the repeatTests property set to 100. Running runFlankRepeateOneHundred will execute this custom configuration.

+

smartFlankGcsPath

+

The cloud storage location for historical test runs. This must be set in order to use smart sharding. The amount of shards used is set by maxTestShards. The bucket (such as flank_data in the example) must already exist in order for it to be used.

+
+
smartFlankGcsPath = 'gs://flank_data/results/JUnitReport.xml'
+
+
+
+
smartFlankGcsPath.set("gs://flank_data/results/JUnitReport.xml")
+
+
+
+

resultsHistoryName

+

The history name for your test results (an arbitrary string label; default: the application's label from the APK manifest). All tests which use the same history name will have their results grouped together in the Firebase console in a time-ordered test history list.

+
+
resultsHistoryName = 'android-history'
+
+
+
+
resultsHistoryName.set("android-history")
+
+
+
+

flakyTestAttempts

+

The number of times to retry failed tests. Default is 0. Max is 10. +Setting the value to 1 will mean that test are retried once. If the test fails then succeeds after the retry the run +will be marked as "successful". The matrix with a flaky test will be marked as flaky.

+
+
flakyTestAttempts = 0
+
+
+
+
flakyTestAttempts.set(0)
+
+
+
+

abi

+

The ABI split of the application that should be tested (e.g. "x86"). Only required if the application under test uses ABI splits and the debug APK is selected automatically (via variant) instead of manually (via debugApk).

+

If the application uses ABI splits, and this property isn't specified, an arbitrary ABI split will be selected.

+
+
abi = "arm64-v8a"
+
+
+
+
abi.set("arm64-v8a")
+
+
+
+

directoriesToPull

+

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. 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.

+
+
directoriesToPull = [
+  '/sdcard/tempDir1', '/data/local/tmp/tempDir2'
+]
+
+
+
+
directoriesToPull.set(listOf(
+  "/sdcard/tempDir1", "/data/local/tmp/tempDir2"
+))
+
+
+
+

filesToDownload

+

List of regex that is matched against bucket paths (for example: 2019-01-09_00:13:06.106000_YCKl/shard_0/SmallPhone.arm-28-en-portrait/bugreport.txt) for files to be downloaded after a flank run. The results are downloaded to the APP_MODULE/build/fladle/RESULTS directory where RESULTS can be set by localResultsDir var otherwise defaulting to results/.

+
+
filesToDownload = [
+  '.*/sdcard/tempDir1/.*', '.*/data/local/tmp/tempDir2/.*'
+]
+
+
+
+
filesToDownload.set(listOf(
+  ".*/sdcard/tempDir1/.*", ".*/data/local/tmp/tempDir2/.*"
+))
+
+
+
+

testTimeout

+

The max time test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 45m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. +Examples: +* 1h -> 1 hour +* 5m -> 5 minutes +* 200s -> 200 seconds +* 100 -> 100 seconds

+
+
testTimeout = "1h"
+
+
+
+
testTimeout.set("1h")
+
+
+
+

recordVideo

+

Enable video recording during the test. Enabled by default.

+
+
recordVideo = true
+
+
+
+
recordVideo.set(true)
+
+
+
+

performanceMetrics

+

Monitor and record performance metrics: CPU, memory, network usage, and FPS (game-loop only). Enabled by default.

+
+
performanceMetrics = true
+
+
+
+
performanceMetrics.set(true)
+
+
+
+

resultsBucket

+

The name of a Google Cloud Storage bucket where raw test results will be stored.

+
+
resultsBucket = "my-gcs-bucket-name"
+
+
+
+
resultsBucket.set("my-gcs-bucket-name")
+
+
+
+

keepFilePath

+

Keeps the full path of downloaded files from a Google Cloud Storage bucket. Required when file names are not unique. Disabled by default.

+
+
keepFilePath = false
+
+
+
+
keepFilePath.set(false)
+
+
+
+

resultsDir

+

The name of a unique Google Cloud Storage object within the results bucket where raw test results will be stored. The default is a timestamp with a random suffix.

+
+
resultsDir = "result-dir-${getTimeStamp()}"
+
+
+
+
resultsDir.set("result-dir-${getTimeStamp()}")
+
+
+
+

disableSharding

+

Disables sharding. All tests will run on the same device. Useful for parameterized tests which do not support sharding. (default: false)

+
+
disableSharding = false
+
+
+
+
disableSharding.set(false)
+
+
+
+

smartFlankDisableUpload

+

Disables smart flank JUnit XML uploading. Useful for preventing timing data from being updated. (default: false) +What is Smart Flank?

+
+
smartFlankDisableUpload = false
+
+
+
+
smartFlankDisableUpload.set(false)
+
+
+
+

testRunnerClass

+

The fully-qualified Java class name of the instrumentation test runner (default: the test manifest is parsed to determine the class name).

+
+
testRunnerClass = "com.example.MyCustomTestRunner"
+
+
+
+
testRunnerClass.set("com.example.MyCustomTestRunner")
+
+
+
+

localResultsDir

+

The local directory to store the test results. Folder is DELETED before each run to ensure only artifacts from the new run are saved. This directory is relative to the working directory of Flank which is by default build/fladle or build/fladle/<flavorname>.

+
+
localResultsDir = "my-results-dir"
+
+
+
+
localResultsDir.set("my-results-dir")
+
+
+
+

testTargetsAlwaysRun

+

Always run - these tests are inserted at the beginning of every shard. Useful if you need to grant permissions or login before other tests run

+
+
testTargetsAlwaysRun = [
+  'class com.example.MyTestClass'
+]
+
+
+
+
testTargetsAlwaysRun.set(listOf(
+  "class com.example.MyTestClass"
+))
+
+
+
+
+

The flags below are only available with Flank 20.05.0 or higher.

+
+

runTimeout

+

The max time this test run can execute before it is cancelled. s (seconds), m (minutes), h (hours) suffixes are acceptable, mixes like 1h45m are currently not supported (default: unlimited). +Examples:

+
    +
  • 20, 20s -> 20 seconds
  • +
  • 30m -> 30 minutes
  • +
  • 2h -> 2 hours
  • +
+
+
runTimeout = "15m"
+
+
+
+
runTimeout.set("15m")
+
+
+
+

ignoreFailedTests

+

Always return successful task completion even when there are failed tests. Useful when parsing JUnit XML to determine failure. (default: false)

+
+
ignoreFailedTests = false
+
+
+
+
ignoreFailedTests.set(false)
+
+
+
+

numUniformShards

+

Specifies the number of shards into which you want to evenly distribute test cases. The shards are run in parallel on separate devices. For example, if your test execution contains 20 test cases and you specify four shards, each shard executes five test cases. The number of shards should be less than the total number of test cases. The number of shards specified must be >= 1 and <= 50. This option cannot be used along maxTestShards and is not compatible with smart sharding (Smart Flank). If you want to take benefits of smart sharding use maxTestShards instead. (default: null)

+
+
numUniformShards = 50
+
+
+
+
numUniformShards.set(50)
+
+
+
+

clientDetails

+

A key-value map of additional details to attach to the test matrix.(clientDetails in Google Cloud Docs) Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. When consuming the test results, such as in Cloud Functions or a CI system, these details can add additional context such as a link to the corresponding pull request. (Access Client Details). +These can be used to provide additional context about the environment where the tests are being run.

+
+
clientDetails = [
+    "test-type": "PR",
+    "build-number": "132"
+]
+
+
+
+
clientDetails.set(mapOf(
+    "test-type" to "PR",
+    "build-number" to "132"
+))
+
+
+
+

otherFiles

+

A list of device-path: file-path pairs that indicate the device paths to push files to the device before starting tests, and the paths of files to push. Device paths must be under absolute, whitelisted paths (${EXTERNAL_STORAGE}, or ${ANDROID_DATA}/local/tmp). Source file paths may be in the local filesystem or in Google Cloud Storage (gs://…).

+
+
otherFiles = [
+    "/sdcard/dir1/file1.txt": "local/file.txt",
+    "/sdcard/dir2/file2.jpg": "gs://bucket/file.jpg",
+]
+
+
+
+
otherFiles.set(mapOf(
+    "/sdcard/dir1/file1.txt" to "local/file.txt",
+    "/sdcard/dir2/file2.jpg" to "gs://bucket/file.jpg",
+))
+
+
+
+

networkProfile

+

The name of the network traffic profile, for example LTE, HSPA, etc, which consists of a set of parameters to emulate network conditions when running the test (default: no network shaping; see available profiles listed by the flank test network-profiles list command). This feature only works on physical devices.

+
+
networkProfile = "LTE"
+
+
+
+
networkProfile.set("LTE")
+
+
+
+

roboScript

+

The path to a Robo Script JSON file. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. You can guide the Robo test to perform specific actions by recording a Robo Script in Android Studio and then specifying this argument. Learn more at DOCS.

+
+
roboScript = "my-robo-script.json"
+
+
+
+
roboScript.set("my-robo-script.json")
+
+
+
+

roboDirectives

+

List of robo_directives that you can use to customize the behavior of Robo test. The type specifies the action type of the directive, which may take on values click, text or ignore. Each directive is list of String = [type, key, value]. Each key should be the Android resource name of a target UI element and each value should be the text input for that element. Values are only permitted for text type elements, so no value should be specified for click and ignore type elements.

+
+
roboDirectives = [
+    ["test, "input_resource_name", "message"],
+    ["click, "button_resource_name", ""],
+]
+
+
+
+
roboDirectives.set(listOf(
+    listOf("test", "input_resource_name", "message"),
+    listOf("click", "button_resource_name", ""),
+))
+
+
+
+

outputStyle

+

Output style of execution status. May be one of [verbose, multi, single]. +For runs with only one test execution the default value is 'verbose', in other cases 'multi' is used as the default. The output style 'multi' is not displayed correctly on consoles which don't support ANSI codes, to avoid corrupted output use single or verbose.

+

multi displays separated status for each shard execution in separated line, lines are updated over time. Similar to gradle output when running multiple tasks in parallel. Requires ANSI codes support.

+

single displays shortened status of all executions in single line. Similar to gcloud output when running with sharding. Should work on any console.

+

Default value is single.

+
+
outputSyle = "single"
+
+
+
+
outputStyle.set("single")
+
+
+
+

legacyJunitResult

+

Flank provides two ways for parsing junit xml results. +New way uses google api instead of merging xml files, but can generate slightly different output format. +This flag allows fallback for legacy xml junit results parsing

+
+
legacyJunitResult = false
+
+
+
+
legacyJunitResult.set(false)
+
+
+
+

fullJunitResult

+

Enables creating an additional local junit result on local storage with failure nodes on passed flaky tests.

+
+
fullJunitResult = false
+
+
+
+
fullJunitResult.set(false)
+
+
+
+

sanityRobo

+

Runs a sanityRobo test. +instrumentationApk, roboDirectives, roboScript and additionalTestApks must be blank or empty.

+
+
sanityRobo = true
+
+
+
+
sanityRobo.set(true)
+
+
+
+

defaultTestTime

+

Set default test time expressed in seconds, used for calculating shards. (default: 120.0s)

+
+
defaultTestTime = 1.2
+
+
+
+
defaultTestTime.set(1.2)
+
+
+
+

defaultClassTestTime

+

Set default parameterized class test time expressed in seconds, used for calculating shards. (default: 2x defaultTestTime => 240s)

+
+
defaultClassTestTime = 245.5
+
+
+
+
defaultClassTestTime.set(245,5)
+
+
+
+

additionalApks

+

A list of up to 100 additional APKs to install, in addition to those being directly tested. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation.

+
+
additionalApks = [
+  "gs://path/to/app1.apk",
+  "localPath/to/app2.apk"
+]
+
+
+
+
additionalApks.set(
+  project.provider {
+    listOf("gs://path/to/app1.apk", "localPath/to/app2.apk")
+  }
+)
+
+
+
+

useAverageTestTimeForNewTests

+

Enable using average time from previous tests duration when using SmartShard and tests did not run before. (default: false)

+
+
useAverageTestTimeForNewTests = true
+
+
+
+
useAverageTestTimeForNewTests.set(true)
+
+
+
+

disableResultsUpload

+

Disable flank results upload on gcloud storage. (default: false)

+
+
disableResultsUpload = true
+
+
+
+
disableResultsUpload.set(true)
+
+
+
+

testTargetsForShard

+

Specifies a group of packages, classes, and/or test cases to run in each shard (a group of test cases). +The shards are run in parallel on separate devices. You can use this option up to 50 times to specify multiple shards when one or more physical devices are selected, +or up to 500 times when no physical devices are selected. +Note: If you include the flags environmentVariables or testTargets when running testTargetsForShard, the flags are applied to all the shards you create. +You can also specify multiple packages, classes, or test cases in the same shard by separating each item with a comma. +To specify both package and class in the same shard, separate package and class with semi-colons.

+
+
testTargetsForShard = [
+  "package com.package1.for.shard1, com.package2.for.shard1",
+  "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2",
+  "class com.foo.ClassForShard3; package com.package.for.shard3"
+  ]
+
+
+
+
testTargetsForShard.set(
+  project.provider {
+    listOf(
+      "package com.package1.for.shard1, com.package2.for.shard1",
+      "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2",
+      "class com.foo.ClassForShard3; package com.package.for.shard3"
+    )
+  }
+)
+
+
+
+

grantPermissions

+

Whether to grant runtime permissions on the device before the test begins. By default, all permissions are granted. PERMISSIONS must be one of: all, none

+
+
grantPermissions = "none"
+
+
+
+
grantPermissions.set("none")
+
+
+
+

type

+

The type of test to run. TYPE must be one of: instrumentation, robo, game-loop. Use if you want to be sure there is only one type of tests being run (flank enables to run mixed types of test in one run).

+
+
type = "game-loop"
+
+
+
+
type.set("game-loop")
+
+
+
+

scenarioLabels

+

A list of game-loop scenario labels (default: None). Each game-loop scenario may be labeled in the APK manifest file with one or more arbitrary strings, creating logical groupings (e.g. GPU_COMPATIBILITY_TESTS). +If --scenario-numbers and --scenario-labels are specified together, Firebase Test Lab will first execute each scenario from --scenario-numbers. +It will then expand each given scenario label into a list of scenario numbers marked with that label, and execute those scenarios.

+
+
scenarioLabels = [
+  "label1",
+  "label2" 
+]
+
+
+
+
scenarioLabels.set(
+  project.provider {
+    listOf("label1", "label2")
+  }
+)
+
+
+
+

scenarioNumbers

+

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.

+
+
scenarioNumbers = [ 1, 23, 52 ]
+
+
+
+
scenarioNumbers.set(
+  project.provider {
+    listOf(1, 23, 52)
+  }
+)
+
+
+
+

obbFiles

+

A list of one or two Android OBB file names which will be copied to each test device before the tests will run (default: None). +Each OBB file name must conform to the format as specified by Android (e.g. [main|patch].0300110.com.example.android.obb) and will be installed into <shared-storage>/Android/obb/<package-name>/ on the test device.

+
+
obbFiles = [
+  "local/file/path/test1.obb",
+  "local/file/path/test2.obb"
+]
+
+
+
+
obbFiles.set(
+  project.provider {
+    listOf(
+      "local/file/path/test1.obb",
+      "local/file/path/test2.obb"
+    )
+  }
+)
+
+
+
+

obbNames

+

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 <shared-storage>/Android/obb/<package-name>/ on the device.

+
+
obbNames = [
+  "patch.0300110.com.example.android.obb",
+  "patch.0300111.com.example.android.obb"
+]
+
+
+
+
obbNames.set(
+  project.provider {
+    listOf(
+      "patch.0300110.com.example.android.obb",
+      "patch.0300111.com.example.android.obb"
+    )
+  }
+)
+
+
+
+

failFast

+

If true, only a single attempt at most will be made to run each execution/shard in the matrix. Flaky test attempts are not affected. +Normally, 2 or more attempts are made if a potential infrastructure issue is detected. +This feature is for latency sensitive workloads. The incidence of execution failures may be significantly greater for +fail-fast matrices and support is more limited because of that expectation.

+
+
failFast = true
+
+
+
+
failFast.set(true)
+
+
+
+

additionalFlankOptions

+

Appending additional option to flank root yaml. This option is useful when you would like to test options before official fladle support is added. +Multiple options are supported.

+

Single option

+
+
additionalFlankOptions = "new-property: true"
+
+
+
+
additionalFlankOptions.set("new-property: true")
+
+
+
+

Multiple options

+
+
additionalFlankOptions = """
+  new-property: true
+  other-new-property: force
+""".stripIndent()
+
+
+
+
additionalFlankOptions.set("""
+    new-property: true
+    other-new-property: force
+""".trimIndent())
+
+
+
+

additionalGcloudOptions

+

Allow appending additional config to gcloud root yaml. This option is useful when you would like to test option +before it is available on Fladle. Supports both single and multiple properties.

+

Single option

+
+
additionalGcloudOptions = "new-property: true"
+
+
+
+
additionalGcloudOptions.set("new-property: true")
+
+
+
+

Multiple options

+
+
additionalGcloudOptions = """
+    new-property: true
+    other-new-property: force
+  """.stripIndent()
+
+
+
+
additionalGcloudOptions.set("""
+    new-property: true
+    other-new-property: force
+""".trimIndent())
+
+
+
+

dependOnAssemble

+

Enable to automatically build the app and test APKs before runFlank executes. (default: false)

+
+
dependOnAssemble = true
+
+
+
+
dependOnAssemble.set(true)
+
+
+
+

async

+

Enable to return immediately after invoking tests, without waiting for results. (default: false)

+
+
async = true
+
+
+
+
async.set(true)
+
+
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/authentication.md b/docs/authentication.md deleted file mode 100644 index 82534dfc..00000000 --- a/docs/authentication.md +++ /dev/null @@ -1,40 +0,0 @@ -# Authentication - -There are two authentication mechanisms for using Fladle. - -It is recommended to use user authentication on local development machines to avoid sharing credentials and a service account on CI. - -## User authentication - -Credentials are stored in `~/.flank`. - -1. `./gradlew flankAuth` -2. Sign in to web browser. -3. Specify [projectId](../configuration/#projectid) in fladle configuration -4. `./gradlew runFlank` - -## Service account credentials - -1. **Create a service account**. Service accounts aren't subject to spam checks or captcha prompts, which could - otherwise block your CI builds. Create a service account with an **Editor** role in the - [Google Cloud Platform console]. - -2. **Enable required APIs**. After logging in using the service account: In the [Google Developers Console API Library] - page, enable the **Google Cloud Testing API** and **Cloud Tool Results API**. To enable these APIs, type these API names into - the search box at the top of the console, and then click **Enable API** on the overview page for that API. - -3. After creating the account go to `Keys`, click `Add Key` -> `Create a new Key`. Select `JSON` for the key type. This will download the json credentials. - -4. Configure the [Fladle extension serviceAccountCredentials] to point to the credentials. - -Above instructions are based on Google instruction for [authenticating with CI]. - -See also Flank's instructions for [authenticating with a service account]. - - -[google cloud platform console]: https://console.cloud.google.com/iam-admin/serviceaccounts/ -[google developers console api library]: https://console.developers.google.com/apis/library -[these steps]: https://firebase.google.com/docs/test-lab/android/continuous#requirements -[Fladle extension serviceAccountCredentials]: ../configuration/#serviceaccountcredentials -[authenticating with CI]: https://firebase.google.com/docs/test-lab/android/continuous -[authenticating with a service account]: https://flank.github.io/flank/#authenticate-with-a-service-account diff --git a/docs/changelog.md b/docs/changelog.md deleted file mode 100644 index a97f61a9..00000000 --- a/docs/changelog.md +++ /dev/null @@ -1,264 +0,0 @@ -# Changelog - -## Unreleased - -## 0.21.0 -* Minimum required Gradle version is now 9.1 -* Fixed support for Android Gradle Plugin version 9.0.1 - -## 0.20.0 -* Botched release. Do not use. - -## 0.19.0 -* Minimum required JVM version is now 17. -* Minimum supported Gradle version is now 7.3. -* Fix support for Gradle 9.0.0. [PR](https://github.com/runningcode/fladle/pull/452) Thanks [kevinguitar](https://github.com/kevinguitar) - -## 0.18.0 -* Use non-deprecated device models and versions in default config. [PR](https://github.com/runningcode/fladle/pull/446) Thanks [Kaibolay](https://github.com/kaibolay) - -## 0.17.5 -* Support Develocity JUnit XML Reporting [PR](https://github.com/runningcode/fladle/pull/386) Thanks [ZacSweers](https://github.com/ZacSweers) -* Min supported Gradle version is now 6.5 -* Updated Flank version to 23.10.1 -* -## 0.17.4 -* Add support for additionalTestApks in YamlConfigWriterTask. [PR](https://github.com/runningcode/fladle/pull/291). Thanks [ZacSweers](https://github.com/ZacSweers) -* Add `abi` option to support selecting a particular debug APK in projects that use [ABI splits](https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split) to produce multiple APKs. [PR](https://github.com/runningcode/fladle/pull/281) - -## 0.17.2 -* Fix configuration cache support. - -## 0.17.0 -* Fix quotes around environment variables and formatting issues. - -## 0.16.3 -* Remove deprecation warning using `main` in `JavaExec` task. -* Conditionally declare outputs on FlankExecutionTask and mark as not up-to-date. [PR](https://github.com/runningcode/fladle/pull/273) -* Fix [environmentVariables not passed](https://github.com/runningcode/fladle/issues/270) to flank.yml [PR](https://github.com/runningcode/fladle/pull/271) Thanks [Sinan](https://github.com/kozaxinan) - -## 0.16.2 -* Fix crash accessing TestedExtension in non-android modules [PR](https://github.com/runningcode/fladle/pull/265/files) Thanks [asadsalman](https://github.com/asadsalman) - -## 0.16.1 -* Treat app and library modules the same in Fulladle [PR](https://github.com/runningcode/fladle/pull/262) Thanks [asadsalman](https://github.com/asadsalman) -* Added fulladleModuleConfig to app modules [PR](https://github.com/runningcode/fladle/pull/260) - -!!! Warning "Breaking API Change" - Previously, only app modules could be picked up as root-level modules in Fulladle. This has changed, now either app or library modules can be picked up (though we try app modules first). If a library module is picked up as a the root-level module, it _must_ specifiy a `debugApk` through either the root `fladle` block or the module's own `fulladleModuleConfig` block. - -## 0.16.0 -* Allow excluding modules from Fulladle [PR](https://github.com/runningcode/fladle/pull/257) -* Allow configuring extra parameters on additionalTestApks [PR](https://github.com/runningcode/fladle/pull/257) - -## 0.15.1 -* Allow Flank snapshot usage [PR](https://github.com/runningcode/fladle/pull/238) Thanks [AndrewReitz](https://github.com/AndrewReitz) -* Fix incorrect task group name. [PR](https://github.com/runningcode/fladle/pull/249) -* Snapshots are now compatible with java 8 [PR](https://github.com/runningcode/fladle/pull/247) -* Configuration validation happens at task execution time. [Fixes #239](https://github.com/runningcode/fladle/issues/239) -* Configure assemble dependency per variant. [Fixes #233](https://github.com/runningcode/fladle/issues/233) -* Add required `smartFlankGcsPath` to samples [Fixes #236](https://github.com/runningcode/fladle/issues/236) - -## 0.15.0 -* Add support for [`async`](https://runningcode.github.io/fladle/configuration/#async) flag. [PR](https://github.com/runningcode/fladle/pull/228). -* Add flag for depending on assembling of debug apk and instrumentation apk automatically. `dependOnAssemble` [PR](https://github.com/runningcode/fladle/pull/223/files) Thanks [asadsalman](https://github.com/asadsalman) -* Gradle 7.0 Compatibility - -## 0.14.1 -* Support new Flank options without updating Fladle [Fixes #146](https://github.com/runningcode/fladle/issues/146) [PR](https://github.com/runningcode/fladle/pull/214) Thanks [piotradamczyk5](https://github.com/piotradamczyk5) - -## 0.14.0 -* Bump Flank version to 21.01.1 -* Add support for test-targets-for-shard [Fixes #205](https://github.com/runningcode/fladle/issues/205) -* Add support for new flank options [PR#211](https://github.com/runningcode/fladle/pull/211) Thanks [pawelpasterz](https://github.com/pawelpasterz) -* Deprecate `testShards` [Fixes #204](https://github.com/runningcode/fladle/issues/205) [PR#212](https://github.com/runningcode/fladle/pull/212) Thanks [pawelpasterz](https://github.com/pawelpasterz) -* Write test results into a config-specific directory [PR#194](https://github.com/runningcode/fladle/pull/194) Thanks [pawelpasterz](https://github.com/pawelpasterz) - -## 0.13.1 -* Fix flankAuth task throwing exception. [Fixes #195](https://github.com/runningcode/fladle/issues/195) -* Add support for newly added flank options [PR#186](https://github.com/runningcode/fladle/pull/186) Thanks [pawelpasterz](https://github.com/pawelpasterz): - * `default-test-time` - * `default-class-test-time` - * `additional-apks` - * `use-average-test-time-for-new-tests` - * `disable-results-upload` - -## 0.13.0 -* Add support for sanityRobo tests [Fixes #165](https://github.com/runningcode/fladle/issues/165) [PR](https://github.com/runningcode/fladle/pull/177) Thanks [pawelpasterz](https://github.com/pawelpasterz) -* Add support for [user authentication](../authentication). -* Bump Flank to [20.09.3](https://github.com/Flank/flank/releases/tag/v20.09.3) - -!!! Warning "Breaking API Change" - Use lazy properties [Fixes #92](https://github.com/runningcode/fladle/issues/92) [PR](https://github.com/runningcode/fladle/pull/176) Thanks [pawelpasterz](https://github.com/pawelpasterz) - -## 0.12.1 -* Don't override debug or instrumentation apk if already specified. Fixes [#172](https://github.com/runningcode/fladle/issues/172). - -## 0.12.0 -* Don't override property values in individual configurations. Fixes [#158](https://github.com/runningcode/fladle/issues/158). -* Update [Flank to 20.08.3](https://github.com/Flank/flank/releases/tag/v20.08.3). -* Use Github actions instead of CircleCI -* Write Yaml file to task specific output directory and add up-to-date checks. [#159](https://github.com/runningcode/fladle/pull/159) [Fixes #147](https://github.com/runningcode/fladle/issues/147) Thanks [CristianGM](https://github.com/CristianGM) -* Add support for `full-junit-result` and `legacy-junit-result`. [#170](https://github.com/runningcode/fladle/pull/170) Fixes [#157](https://github.com/runningcode/fladle/issues/157). Thanks [MatthewTPage](https://github.com/MatthewTPage) - -## 0.11.0 -* Update [Flank to 20.07.0](https://github.com/Flank/flank/releases/tag/v20.07.0). -* Only add `additional-test-apks` for modules that have tests. [PR](https://github.com/runningcode/fladle/pull/150) -* Experimental configuration caching support. [PR1](https://github.com/runningcode/fladle/pull/153)[PR2](https://github.com/runningcode/fladle/pull/154) - -## 0.10.2 -* Update [Flank to 20.06.2](https://github.com/Flank/flank/releases/tag/v20.06.2). -* Fix duplicated status messages in console. [PR](https://github.com/runningcode/fladle/pull/142) Thanks [francescocervone](https://github.com/francescocervone) - -!!! Warning "Breaking API change" - Add time units for timeout. `timeoutMin` has been renamed to `testTimeout`. [PR](https://github.com/runningcode/fladle/pull/137) Thanks [pawelpasterz](https://github.com/pawelpasterz) - -## 0.10.1 -* [Fix] Allow for specifying roboScript without specifying instrumentationApk. Fixes [#128](https://github.com/runningcode/fladle/issues/128). - -## 0.10.0 - -* Allow for debugging using [--dump-shards](/fladle/faq/#debugging) -* Fix naming for variant discovery of apk and instrumentation apk. Instead of chocolate-debug, variant must now be set as chocolateDebug. -* Update [Flank to 20.05.2](https://github.com/Flank/flank/releases/tag/v20.05.2). -* [Fulladle Preview](/fladle/multi-module-testing) - -!!! Warning "Breaking API Change" - [additionalTestApks](/fladle/configuration/#additionaltestapks) now uses ListProperty instead of the previous Map. This is to allow for lazy configuration of the provided files. -!!! Warning - Minimum supported Gradle version is now 5.4. - -## 0.9.4 -* Update [Flank to 20.05.1](https://github.com/Flank/flank/releases/tag/v20.05.0). Huge new release! -* Add support for new flank flags. Thanks [pawelpasterz](https://github.com/pawelpasterz) [PR](https://github.com/runningcode/fladle/pull/88) -* Use compileOnly for AGP version. - -## 0.9.2 - -!!! Warning "Breaking API change" - debugApk and instrumentationApk now use Lazy Property API to avoid resolving at configuration time. - -## 0.9.1 - -* Bugfix: ability to set flank version. [PR](https://github.com/runningcode/fladle/pull/97) - -!!! Warning "Breaking API Change" - serviceAccountCredentials now uses [Lazy Property API](https://docs.gradle.org/current/userguide/lazy_configuration.html#working_with_files_in_lazy_properties). See [Configuration](/configuration#serviceAccountCredentials) for details on how to set it. [PR](https://github.com/runningcode/fladle/pull/97) -!!! Warning - Minimum required Gradle Version is now 5.1. -!!! Warning - Dropped support for Flank 7.X and lower. - -## 0.9.0 -* Do not add flank maven repo. [PR](https://github.com/runningcode/fladle/pull/94) -* Allow specifying custom flank coordinates. [PR](https://github.com/runningcode/fladle/pull/94) -* Change ordering and use file provider. [PR](https://github.com/runningcode/fladle/pull/95) - -## 0.8.1 -* Add support for `additionalTestApks`. [PR](https://github.com/runningcode/fladle/pull/83) Thanks [japplin](https://github.com/japplin). -* Add support for `resultsDir`. [PR](https://github.com/runningcode/fladle/pull/80) - -## 0.8.0 -* BREAKING: devices now takes a `List>` instead of a `List`. See the [#README.md] for an example. [PR](https://github.com/runningcode/fladle/pull/76) Thanks [zlippard](https://github.com/zlippard). -* Add support for `keep-file-path`. [PR](https://github.com/runningcode/fladle/pull/77) Thanks [tahirhajizada](https://github.com/tahirhajizada). - -## 0.7.0 -* Add support for Flank 8 and bump version. [PR](https://github.com/runningcode/fladle/pull/75) Thanks [francescocervone](https://github.com/francescocervone) - -## 0.6.7 -* Allow using wildcards in debugApk or instrumentationApk path by not checking that file exists. [PR](https://github.com/runningcode/fladle/pull/72) - -## 0.6.6 -* Bump flank version to 7.0.0 -* Publish to mavenCentral() - -## 0.6.5 -* Add support for results-bucket gcloud config option. [PR](https://github.com/runningcode/fladle/pull/62) Thanks [c-moss](https://github.com/c-moss) -* Default flank version 6.2.3 -* Lower build logging level - -## 0.6.4 -* Allow setting android version number as string to allow for preview versions. [PR](https://github.com/runningcode/fladle/pull/590)Thanks [JeroenMols](https://github.com/JeroenMols). -## 0.6.3 -* Allow service credentials to be set [using environment variables](https://github.com/runningcode/fladle/pull/58). [Fixes #55](https://github.com/runningcode/fladle/issues/55) -* Fix not being able to [set flankVersion](https://github.com/runningcode/fladle/pull/57). [Fixes #56](https://github.com/runningcode/fladle/issues/56) - -## 0.6.2 -* [Fix shardTime config property not written to flank.yml](https://github.com/runningcode/fladle/pull/53) Thanks [nnoel-grubhub](https://github.com/nnoel-grubhub) - -## 0.6.1 -* Fix project id [spacing](https://github.com/runningcode/fladle/issues/49) Thanks [andersu](https://github.com/andersu) for reporting. - -## 0.6.0 -* Default flank version 5.0.1 -* Rename yaml output to match new flank version. -* Add support for shard time. - -## 0.5.2 -* Improve error messages for missing arguments in the fladle extension. - -## 0.5.1 -* Test multipleconfig to actually write yml Thanks [PR](https://github.com/runningcode/fladle/pull/40/) [winterDroid](https://github.com/winterDroid). -* JavaExec uses classpath instead of jar directly. Thanks [PR](https://github.com/runningcode/fladle/pull/37/) [winterDroid](https://github.com/winterDroid). - -## 0.5.0 -* Use flank as a maven artifact. -* Group Fladle Tasks - -## 0.4.1 -* Detect AndroidX test orchestrator -* Fix detection of debug and instrumentation apk paths. - -## 0.4.0 -* Add support for all configuration options. Thanks [PR](https://github.com/runningcode/fladle/pull/26/) [winterDroid](https://github.com/winterDroid). - -### Breaking Changes: -Previous users of `clearPackageData = true` will now need to use: -``` -environmentVariables = [ - "clearPackageData": "true" -] -``` - -## 0.3.8 -* Fix broken flakyTestAttempts. - -## 0.3.7 -* Add support for flakyTestAttempts. - -## 0.3.6 -* Add support for environment variable clearPackageData. Thanks @anderssu ! - -## 0.3.5 -* Automatically configure the use of test orchestrator. - -## 0.3.4 -* Add support for setting results-history-name. -* Add support for selecting variant to test instead of apk path. - -## 0.3.3 -* Add support for setting smartFlankGcsPath -* Capitalize task names. - -## 0.3.2 -* Actually fix gradle download task bug. - -## 0.3.1 -* Maybe fix bug similar to: https://github.com/michel-kraemer/gradle-download-task/issues/58 - -## 0.3.0 -* Add support for multiple test configurations. - -## 0.2.12 -* Fix broken flankDoctor task. - -## 0.2.11 -* Add minimum Gradle version check. (4.9 is required because we use the lazy task configuration API)) - -## 0.2.10 -* Add support for specifying Flank snapshot versions. See README for configuration options. - -## 0.2.9 -* Add support for multiple build targets. [PR](https://github.com/runningcode/fladle/pull/9). Thanks [winterDroid](https://github.com/winterDroid). -* Add support for testShards and repeatTests flank options. See README for configuration options. - diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index faed6ae9..00000000 --- a/docs/configuration.md +++ /dev/null @@ -1,1146 +0,0 @@ -# Configuration - -The following configuration options must be set inside the fladle block. See the [sample configuration](../configuration/#sample-configuration) below. There is also a [groovy sample](https://github.com/runningcode/fladle/blob/master/sample/build.gradle) and a [kotlin sample](https://github.com/runningcode/fladle/blob/master/sample-kotlin/build.gradle.kts). - -### serviceAccountCredentials - -!!! note "" - User Authentication is also possible. See [Authentication]. - - -=== "Groovy" - ``` groovy - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - ``` -=== "Kotlin" - ``` kotlin - serviceAccountCredentials.set("project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - ``` - -Optionally, the serviceAccountCredentials can be set with [environment variables](https://flank.github.io/flank/#authenticate-with-a-service-account) but then the projectId parameter must be set. - -See [Authentication] for more info. - -[Authentication]: ../authentication - - -### variant - -!!! note - `variant` must be set if using buildFlavors in order to automatically configure the debugApk and testApk. - -Set the variant to automatically configure for testing. A build variant is a combination of buildFlavor and buildType. -This must also be set when testing against a non-default variant. -For example: 'debug' or 'freeDebug'. -Put this inside your Fladle block. - -=== "Groovy" - ``` groovy - variant = "freeDebug" - ``` -=== "Kotlin" - ``` kotlin - variant.set("freeDebug") - ``` - -## Sample Configuration - -``` groovy -fladle { - // Required parameters - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - variant = "freeDebug" - - // Optional parameters - useOrchestrator = false - environmentVariables = [ - "clearPackageData": "true" - ] - directoriesToPull = [ - "/sdcard/screenshots" - ] - filesToDownload = [ - ".*/screenshots/.*" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - testTimeout = "15m" - recordVideo = false - performanceMetrics = false - devices = [ - [ "model": "SmallPhone.arm", "version": "28" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - projectId("flank-gradle") - flankVersion = "{{ fladle.flank_version }}" - debugApk = "$buildDir/outputs/apk/debug/sample-debug.apk" - instrumentationApk = "$buildDir/outputs/apk/androidTest/debug/sample-debug-androidTest.apk" - additionalTestApks = [ - "- app: $buildDir/outputs/apk/debug/sample-debug.apk", - " test: $buildDir/outputs/apk/androidTest/debug/sample2-debug-androidTest.apk", - "- test: ${rootProject.buildDir}/database/module/outputs/apk/database-module-androidTest.apk" - ] - autoGoogleLogin = true - maxTestShards = 8 //or numUniformShards=5 cannot use both. - shardTime = 120 - smartFlankGcsPath = "gs://tmp_flank/flank/test_app_android.xml" - configs { - oranges { - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - } - } - resultsBucket("my-results-bucket-name") - keepFilePath = true - runTimout = "45m" - ignoreFailedTests = false - disableSharding = false - smartFlankDisableUpload = false - testRunnerClass = "com.foo.TestRunner" - localResultsDir = "flank" - clientDetails = [ - "key1": "value1", - "key2": "value2" - ] - testTargetsAlwaysRun = [ - "com.example.TestSuite#test1", - "com.example.TestSuite#test2" - ] - otherFiles = [ - "/sdcard/dir1/file1.txt": "/my/example/path/file1.txt", - "/sdcard/dir2/file2.txt": "/my/example/path/file2.txt" - ] - networkProfile = "LTE" - roboDirectives = [ - ["click", "button1", ""], - ["ignore", "button2"], - ["text", "field1", "my text"], - ] - outputStyle = 'multi' - legacyJunitResult = false - fullJunitResult = false - additionalApks = [ - "gs://path/to/app1.apk", - "localPath/to/app2.apk" - ] - defaultTestTime = 5.3 - defaultClassTestTime = 180.5 - useAverageTestTimeForNewTests = true - disableResultsUpload = true - grantPermissions = "none" - type = "game-loop" - scenarioLabels = [ - "label1", - "label2" - ] - scenarioNumbers = [ 1, 23, 52 ] - obbFiles = [ - "local/file/path/test1.obb", - "local/file/path/test2.obb" - ] - obbNames = [ - "patch.0300110.com.example.android.obb", - "patch.0300111.com.example.android.obb" - ] - testTargetsForShard = [ - "package com.package1.for.shard1, com.package2.for.shard1", - "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2", - "class com.foo.ClassForShard3; package com.package.for.shard3" - ] - failFast = true - dependOnAssemble = true -} -``` - - -### useOrchestrator -Whether or not we should use the android test orchestrator to run this tests. -Set this to true when the build.gradle file includes `testOptions.execution 'ANDROID_TEST_ORCHESTRATOR'` - -=== "Groovy" - ``` groovy - useOrchestrator = true - ``` -=== "Kotlin" - ``` kotlin - useOrchestrator.set(true) - ``` - -### testTargets -Set multiple testTargets to be run by Flank. These are used to whitelist or blacklist test classes, test cases and test annotations. -See [Google Cloud Firebase docs](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run) for more information. - -=== "Groovy" - ``` groovy - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - ``` -=== "Kotlin" - ``` kotlin - testTargets.set(listOf( - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - )) - ``` - - -### devices -A list of devices to run the tests against. When list is empty, a default device will be used. Each device entry is a map. The valid keys in the map are `model`, `version`, `orientation`, and `locale`. When a key is not set or is null, a default value will be used. - -=== "Groovy" - ``` groovy - devices = [ - [ "model": "MediumPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - ``` -=== "Kotlin" - ``` kotlin - devices.set(listOf( - mapOf("model" to "MediumPhone.arm", "version" to "26" ), - mapOf("model" to "MediumPhone.arm", "version" to "33" ) - )) - ``` - -### projectId -The projectId is a unique identifier which can be found in the project's URL: `https://console.firebase.google.com/project/` -This is automatically discovered based on the service credential by default. - -=== "Groovy" - ``` groovy - projectId = "flank-gradle" - ``` -=== "Kotlin" - ``` kotlin - projectId.set("flank-gradle") - ``` - -### flankVersion -Need a different Flank version? Specify it with `flankVersion`. - -To use a snapshot: -=== "Groovy" - ``` groovy - flankVersion = "flank_snapshot"` - ``` -=== "Kotlin" - ``` kotlin - flankVersion.set("flank_snapshot") - ``` - -Need more than 50 shards? Use Flank `8.1.0`. - -To use a different version: - -=== "Groovy" - ``` groovy - flankVersion = "{{ fladle.flank_version }}" - ``` -=== "Kotlin" - ``` kotlin - flankVersion.set("{{ fladle.flank_version }}") - ``` - -### flankCoordinates -Specify custom flank maven coordinates. - -=== "Groovy" - ``` groovy - flankCoordinates = "com.github.flank:flank" - ``` -=== "Kotlin" - ``` kotlin - flankCoordinates.set("com.github.flank:flank") - ``` - -### debugApk -This is a string representing the path to the app's debug apk. -Supports wildcard characters. -Optional, prefer to set [variant](../configuration/#variant). - -=== "Groovy" - ``` groovy - debugApk = project.provider { "${buildDir.toString()}/outputs/apk/debug/*.apk" } - ``` -=== "Kotlin" - ``` kotlin - debugApk.set(project.provider { "${buildDir.toString()}/outputs/apk/debug/*.apk" }) - ``` - -### instrumentationApk -This is a string representing the path to the app's instrumentaiton apk. -Supports wildcard characters. -Optional, prefer to set [variant](../configuration/#variant). -InstrumenationApk should not be set when using [roboScript](../configuration/#roboscript). - -=== "Groovy" - ``` groovy - instrumentationApk = project.provider { "${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk" } - ``` -=== "Kotlin" - ``` kotlin - instrumentationApk.set(project.provider { "${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk" }) - ``` - -### additionalTestApks -Paths to additional test configurations. -Order matters. A test apk is run with the nearest previous listed app apk. -For library modules, add them to the list with a `- test:` in front. For test apks which belong to an application module, add them with ` test:`. -It is not required to list an app apk here. If there is no app apk listed in additionalTestApks, the test apks are run against the [debugApk](../configuration/#debugapk). - -=== "Groovy" - ```groovy - additionalTestApks.value(project.provider { [ - "- app: ../main/app/build/output/apk/debug/app.apk", - " test: ../main/app/build/output/apk/androidTest/debug/app-test.apk", - "- app: ../sample/app/build/output/apk/debug/sample-app.apk", - " test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk", - "- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk", - "- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk" - ]}) - ``` -=== "Kotlin" - ``` kotlin - additionalTestApks.value(project.provider { listOf( - "- app: ../main/app/build/output/apk/debug/app.apk", - " test: ../main/app/build/output/apk/androidTest/debug/app-test.apk", - "- app: ../sample/app/build/output/apk/debug/sample-app.apk", - " test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk", - "- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk", - "- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk" - )}) - ``` - -### autoGoogleLogin -Whether to automatically log in using a preconfigured google account. [More Info](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run#--auto-google-login) - -=== "Groovy" - ``` groovy - autoGoogleLogin = false - ``` -=== "Kotlin" - ``` kotlin - autoGoogleLogin.set(false) - ``` - -### environmentVariables -Environment variables are mirrored as extra options to the `am instrument -e KEY1 VALUE1 …` command and passed to your test runner (typically AndroidJUnitRunner). Examples - -=== "Groovy" - ``` groovy - environmentVariables = [ - // Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner) - "clearPackageData": "true" - ] - ``` -=== "Kotlin" - ``` kotlin - environmentVariables = mapOf( - // Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner) - "clearPackageData" to "true" - ) - ``` -### testShards - -!!! warning "Deprecated" - Use [`maxTestShards`](../configuration/#maxtestshards) instead. - -The maximum number of shards. Fladle will throw an error when used together with [maxTestShards](../configuration/#maxtestshards) or [numUniformShards](../configuration/#numuniformshards). - -=== "Groovy" - ``` groovy - testShards = 5 - ``` -=== "Kotlin" - ``` kotlin - testShards.set(5) - ``` - -### maxTestShards -The maximum number of shards. Fladle will throw an error when used together with [testShards](../configuration/#testshards) or [numUniformShards](../configuration/#numuniformshards). - -=== "Groovy" - ``` groovy - maxTestShards = 8 - ``` -=== "Kotlin" - ``` kotlin - maxTestShards.set(8) - ``` - -### shardTime -The amount of time tests within a shard should take. -When set to > 0, the shard count is dynamically set based on time up to the maximum limit defined by maxTestShards -2 minutes (120) is recommended. -default: -1 (unlimited) - -=== "Groovy" - ``` groovy - shardTime = 120 - ``` -=== "Kotlin" - ``` kotlin - shardTime.set(120) - ``` - -### repeatTests -The number of times to repeat each test. Uses Flank's default value when not specified. - -=== "Groovy" - ``` groovy - repeatTests = 1 - ``` -=== "Kotlin" - ``` kotlin - repeatTests.set(1) - ``` - -### configs -Give a name to a custom flank task and configure its options. The name is appended to the end of the flank task. For example `runFlank` becomes `runFlank`. - -=== "Groovy" - ``` groovy - configs { - repeatOneHundred { - // DSL sugar for container elements is missing (= syntax): https://github.com/gradle/gradle/issues/9987 - repeatTests.set(100) - } - } - ``` -=== "Kotlin" - ``` kotlin - configs { - create("repeatOneHundred") { - repeatTests.set(100) - } - } - ``` - -In the above example, the configuration is inherited from the outer fladle config but with the repeatTests property set to 100. Running `runFlankRepeateOneHundred` will execute this custom configuration. - -### smartFlankGcsPath -The cloud storage location for historical test runs. This must be set in order to use smart sharding. The amount of shards used is set by `maxTestShards`. The bucket (such as `flank_data` in the example) must already exist in order for it to be used. - -=== "Groovy" - ``` groovy - smartFlankGcsPath = 'gs://flank_data/results/JUnitReport.xml' - ``` -=== "Kotlin" - ``` kotlin - smartFlankGcsPath.set("gs://flank_data/results/JUnitReport.xml") - ``` - -### resultsHistoryName -The history name for your test results (an arbitrary string label; default: the application's label from the APK manifest). All tests which use the same history name will have their results grouped together in the Firebase console in a time-ordered test history list. - -=== "Groovy" - ``` groovy - resultsHistoryName = 'android-history' - ``` -=== "Kotlin" - ``` kotlin - resultsHistoryName.set("android-history") - ``` - -### flakyTestAttempts -The number of times to retry failed tests. Default is 0. Max is 10. -Setting the value to 1 will mean that test are retried once. If the test fails then succeeds after the retry the run -will be marked as "successful". The matrix with a flaky test will be marked as flaky. - -=== "Groovy" - ``` groovy - flakyTestAttempts = 0 - ``` -=== "Kotlin" - ``` kotlin - flakyTestAttempts.set(0) - ``` - -### abi -The [ABI](https://developer.android.com/ndk/guides/abis.html#sa) split of the application that should be tested (e.g. "x86"). Only required if the application under test uses [ABI splits](https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split) and the debug APK is selected automatically (via [variant](../configuration/#variant)) instead of manually (via [debugApk](../configuration/#debugapk)). - -If the application uses ABI splits, and this property isn't specified, an arbitrary ABI split will be selected. - -=== "Groovy" - ``` groovy - abi = "arm64-v8a" - ``` -=== "Kotlin" - ``` kotlin - abi.set("arm64-v8a") - ``` - -### directoriesToPull -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`. 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. - -=== "Groovy" - ``` groovy - directoriesToPull = [ - '/sdcard/tempDir1', '/data/local/tmp/tempDir2' - ] - ``` -=== "Kotlin" - ``` kotlin - directoriesToPull.set(listOf( - "/sdcard/tempDir1", "/data/local/tmp/tempDir2" - )) - ``` - -### filesToDownload -List of regex that is matched against bucket paths (for example: `2019-01-09_00:13:06.106000_YCKl/shard_0/SmallPhone.arm-28-en-portrait/bugreport.txt`) for files to be downloaded after a flank run. The results are downloaded to the `APP_MODULE/build/fladle/RESULTS` directory where RESULTS can be set by [`localResultsDir`](../configuration/#localresultsdir) var otherwise defaulting to `results/`. - -=== "Groovy" - ``` groovy - filesToDownload = [ - '.*/sdcard/tempDir1/.*', '.*/data/local/tmp/tempDir2/.*' - ] - ``` -=== "Kotlin" - ``` kotlin - filesToDownload.set(listOf( - ".*/sdcard/tempDir1/.*", ".*/data/local/tmp/tempDir2/.*" - )) - ``` - -### testTimeout -The max time test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 45m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. -Examples: -* 1h -> 1 hour -* 5m -> 5 minutes -* 200s -> 200 seconds -* 100 -> 100 seconds - -=== "Groovy" - ``` groovy - testTimeout = "1h" - ``` -=== "Kotlin" - ``` kotlin - testTimeout.set("1h") - ``` - -### recordVideo -Enable video recording during the test. Enabled by default. - -=== "Groovy" - ``` groovy - recordVideo = true - ``` -=== "Kotlin" - ``` kotlin - recordVideo.set(true) - ``` - -### performanceMetrics -Monitor and record performance metrics: CPU, memory, network usage, and FPS (game-loop only). Enabled by default. - -=== "Groovy" - ``` groovy - performanceMetrics = true - ``` -=== "Kotlin" - ``` kotlin - performanceMetrics.set(true) - ``` - -### resultsBucket -The name of a Google Cloud Storage bucket where raw test results will be stored. - -=== "Groovy" - ``` groovy - resultsBucket = "my-gcs-bucket-name" - ``` -=== "Kotlin" - ``` kotlin - resultsBucket.set("my-gcs-bucket-name") - ``` - -### keepFilePath -Keeps the full path of downloaded files from a Google Cloud Storage bucket. Required when file names are not unique. Disabled by default. - -=== "Groovy" - ``` groovy - keepFilePath = false - ``` -=== "Kotlin" - ``` kotlin - keepFilePath.set(false) - ``` - -### resultsDir -The name of a unique Google Cloud Storage object within the results bucket where raw test results will be stored. The default is a timestamp with a random suffix. - -=== "Groovy" - ``` groovy - resultsDir = "result-dir-${getTimeStamp()}" - ``` -=== "Kotlin" - ``` kotlin - resultsDir.set("result-dir-${getTimeStamp()}") - ``` - -### disableSharding -Disables sharding. All tests will run on the same device. Useful for parameterized tests which do not support sharding. (default: false) - -=== "Groovy" - ``` groovy - disableSharding = false - ``` -=== "Kotlin" - ``` kotlin - disableSharding.set(false) - ``` - -### smartFlankDisableUpload -Disables smart flank JUnit XML uploading. Useful for preventing timing data from being updated. (default: false) -[What is Smart Flank?](https://flank.github.io/flank/smart_flank/) - -=== "Groovy" - ``` groovy - smartFlankDisableUpload = false - ``` -=== "Kotlin" - ``` kotlin - smartFlankDisableUpload.set(false) - ``` - -### testRunnerClass -The fully-qualified Java class name of the instrumentation test runner (default: the test manifest is parsed to determine the class name). - -=== "Groovy" - ``` groovy - testRunnerClass = "com.example.MyCustomTestRunner" - ``` -=== "Kotlin" - ``` kotlin - testRunnerClass.set("com.example.MyCustomTestRunner") - ``` - -### localResultsDir -The local directory to store the test results. Folder is DELETED before each run to ensure only artifacts from the new run are saved. This directory is relative to the working directory of Flank which is by default `build/fladle` or `build/fladle/`. - -=== "Groovy" - ``` groovy - localResultsDir = "my-results-dir" - ``` -=== "Kotlin" - ``` kotlin - localResultsDir.set("my-results-dir") - ``` - -### testTargetsAlwaysRun -Always run - these tests are inserted at the beginning of every shard. Useful if you need to grant permissions or login before other tests run - -=== "Groovy" - ``` groovy - testTargetsAlwaysRun = [ - 'class com.example.MyTestClass' - ] - ``` -=== "Kotlin" - ``` kotlin - testTargetsAlwaysRun.set(listOf( - "class com.example.MyTestClass" - )) - ``` - -!!! note "" - The flags below are only available with Flank 20.05.0 or higher. - -### runTimeout -The max time this test run can execute before it is cancelled. s (seconds), m (minutes), h (hours) suffixes are acceptable, mixes like 1h45m are currently not supported (default: unlimited). -Examples: - -* 20, 20s -> 20 seconds -* 30m -> 30 minutes -* 2h -> 2 hours - -=== "Groovy" - ``` groovy - runTimeout = "15m" - ``` -=== "Kotlin" - ``` kotlin - runTimeout.set("15m") - ``` - -### ignoreFailedTests -Always return successful task completion even when there are failed tests. Useful when parsing JUnit XML to determine failure. (default: false) - -=== "Groovy" - ``` groovy - ignoreFailedTests = false - ``` -=== "Kotlin" - ``` kotlin - ignoreFailedTests.set(false) - ``` - -### numUniformShards -Specifies the number of shards into which you want to evenly distribute test cases. The shards are run in parallel on separate devices. For example, if your test execution contains 20 test cases and you specify four shards, each shard executes five test cases. The number of shards should be less than the total number of test cases. The number of shards specified must be >= 1 and <= 50. This option cannot be used along maxTestShards and is not compatible with smart sharding ([Smart Flank](https://flank.github.io/flank/smart_flank/)). If you want to take benefits of smart sharding use maxTestShards instead. (default: null) - -=== "Groovy" - ``` groovy - numUniformShards = 50 - ``` -=== "Kotlin" - ``` kotlin - numUniformShards.set(50) - ``` - -### clientDetails -A key-value map of additional details to attach to the test matrix.([clientDetails in Google Cloud Docs](https://cloud.google.com/sdk/gcloud/reference/beta/firebase/test/android/run#--client-details)) Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. When consuming the test results, such as in Cloud Functions or a CI system, these details can add additional context such as a link to the corresponding pull request. ([Access Client Details](https://firebase.google.com/docs/test-lab/extend-with-functions#access_client_details)). -These can be used to provide additional context about the environment where the tests are being run. - -=== "Groovy" - ``` groovy - clientDetails = [ - "test-type": "PR", - "build-number": "132" - ] - ``` -=== "Kotlin" - ``` kotlin - clientDetails.set(mapOf( - "test-type" to "PR", - "build-number" to "132" - )) - ``` - -### otherFiles -A list of device-path: file-path pairs that indicate the device paths to push files to the device before starting tests, and the paths of files to push. Device paths must be under absolute, whitelisted paths (${EXTERNAL_STORAGE}, or ${ANDROID_DATA}/local/tmp). Source file paths may be in the local filesystem or in Google Cloud Storage (gs://…). - -=== "Groovy" - ``` groovy - otherFiles = [ - "/sdcard/dir1/file1.txt": "local/file.txt", - "/sdcard/dir2/file2.jpg": "gs://bucket/file.jpg", - ] - ``` -=== "Kotlin" - ``` kotlin - otherFiles.set(mapOf( - "/sdcard/dir1/file1.txt" to "local/file.txt", - "/sdcard/dir2/file2.jpg" to "gs://bucket/file.jpg", - )) - ``` - -### networkProfile -The name of the network traffic profile, for example LTE, HSPA, etc, which consists of a set of parameters to emulate network conditions when running the test (default: no network shaping; see available profiles listed by the `flank test network-profiles list` command). This feature only works on physical devices. - -=== "Groovy" - ``` groovy - networkProfile = "LTE" - ``` -=== "Kotlin" - ``` kotlin - networkProfile.set("LTE") - ``` - -### roboScript -The path to a Robo Script JSON file. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. You can guide the Robo test to perform specific actions by recording a Robo Script in Android Studio and then specifying this argument. Learn more at [DOCS](https://firebase.google.com/docs/test-lab/robo-ux-test#scripting). - -=== "Groovy" - ``` groovy - roboScript = "my-robo-script.json" - ``` -=== "Kotlin" - ``` kotlin - roboScript.set("my-robo-script.json") - ``` - -### roboDirectives -List of robo_directives that you can use to customize the behavior of Robo test. The type specifies the action type of the directive, which may take on values click, text or ignore. Each directive is list of String = [type, key, value]. Each key should be the Android resource name of a target UI element and each value should be the text input for that element. Values are only permitted for text type elements, so no value should be specified for click and ignore type elements. - - -=== "Groovy" - ``` groovy - roboDirectives = [ - ["test, "input_resource_name", "message"], - ["click, "button_resource_name", ""], - ] - ``` -=== "Kotlin" - ``` kotlin - roboDirectives.set(listOf( - listOf("test", "input_resource_name", "message"), - listOf("click", "button_resource_name", ""), - )) - ``` - - -### outputStyle -Output style of execution status. May be one of [`verbose`, `multi`, `single`]. -For runs with only one test execution the default value is 'verbose', in other cases 'multi' is used as the default. The output style 'multi' is not displayed correctly on consoles which don't support ANSI codes, to avoid corrupted output use single or verbose. - -`multi` displays separated status for each shard execution in separated line, lines are updated over time. Similar to gradle output when running multiple tasks in parallel. Requires ANSI codes support. - -`single` displays shortened status of all executions in single line. Similar to gcloud output when running with sharding. Should work on any console. - -Default value is single. - -=== "Groovy" - ``` groovy - outputSyle = "single" - ``` -=== "Kotlin" - ``` kotlin - outputStyle.set("single") - ``` - -### legacyJunitResult -Flank provides two ways for parsing junit xml results. -New way uses google api instead of merging xml files, but can generate slightly different output format. -This flag allows fallback for legacy xml junit results parsing - -=== "Groovy" - ``` groovy - legacyJunitResult = false - ``` -=== "Kotlin" - ``` kotlin - legacyJunitResult.set(false) - ``` - -### fullJunitResult -Enables creating an additional local junit result on local storage with failure nodes on passed flaky tests. - -=== "Groovy" - ``` groovy - fullJunitResult = false - ``` -=== "Kotlin" - ``` kotlin - fullJunitResult.set(false) - ``` - -### sanityRobo -Runs a sanityRobo test. -`instrumentationApk`, `roboDirectives`, `roboScript` and `additionalTestApks` must be blank or empty. - -=== "Groovy" - ``` groovy - sanityRobo = true - ``` -=== "Kotlin" - ``` kotlin - sanityRobo.set(true) - ``` - -### defaultTestTime -Set default test time expressed in seconds, used for calculating shards. (default: 120.0s) - -=== "Groovy" - ``` groovy - defaultTestTime = 1.2 - ``` -=== "Kotlin" - ``` kotlin - defaultTestTime.set(1.2) - ``` - -### defaultClassTestTime -Set default parameterized class test time expressed in seconds, used for calculating shards. (default: 2x [defaultTestTime](../configuration/#defaulttesttime) => 240s) - -=== "Groovy" - ``` groovy - defaultClassTestTime = 245.5 - ``` -=== "Kotlin" - ``` kotlin - defaultClassTestTime.set(245,5) - ``` - -### additionalApks -A list of up to 100 additional APKs to install, in addition to those being directly tested. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. - -=== "Groovy" - ``` groovy - additionalApks = [ - "gs://path/to/app1.apk", - "localPath/to/app2.apk" - ] - ``` -=== "Kotlin" - ``` kotlin - additionalApks.set( - project.provider { - listOf("gs://path/to/app1.apk", "localPath/to/app2.apk") - } - ) - ``` - -### useAverageTestTimeForNewTests -Enable using average time from previous tests duration when using SmartShard and tests did not run before. (default: false) - -=== "Groovy" - ``` groovy - useAverageTestTimeForNewTests = true - ``` -=== "Kotlin" - ``` kotlin - useAverageTestTimeForNewTests.set(true) - ``` - -### disableResultsUpload -Disable flank results upload on gcloud storage. (default: false) - -=== "Groovy" - ``` groovy - disableResultsUpload = true - ``` -=== "Kotlin" - ``` kotlin - disableResultsUpload.set(true) - ``` - -### testTargetsForShard -Specifies a group of packages, classes, and/or test cases to run in each shard (a group of test cases). -The shards are run in parallel on separate devices. You can use this option up to 50 times to specify multiple shards when one or more physical devices are selected, -or up to 500 times when no physical devices are selected. -Note: If you include the flags `environmentVariables` or `testTargets` when running `testTargetsForShard`, the flags are applied to all the shards you create. -You can also specify multiple packages, classes, or test cases in the same shard by separating each item with a comma. -To specify both package and class in the same shard, separate package and class with semi-colons. - -=== "Groovy" - ``` - testTargetsForShard = [ - "package com.package1.for.shard1, com.package2.for.shard1", - "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2", - "class com.foo.ClassForShard3; package com.package.for.shard3" - ] - ``` -=== "Kotlin" - ``` - testTargetsForShard.set( - project.provider { - listOf( - "package com.package1.for.shard1, com.package2.for.shard1", - "class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2", - "class com.foo.ClassForShard3; package com.package.for.shard3" - ) - } - ) - ``` - -### grantPermissions -Whether to grant runtime permissions on the device before the test begins. By default, all permissions are granted. PERMISSIONS must be one of: all, none - -=== "Groovy" - ``` groovy - grantPermissions = "none" - ``` -=== "Kotlin" - ``` kotlin - grantPermissions.set("none") - ``` - -### type -The type of test to run. TYPE must be one of: instrumentation, robo, game-loop. Use if you want to be sure there is only one type of tests being run (flank enables to run mixed types of test in one run). - -=== "Groovy" - ``` groovy - type = "game-loop" - ``` -=== "Kotlin" - ``` kotlin - type.set("game-loop") - ``` - -### scenarioLabels -A list of game-loop scenario labels (default: None). Each game-loop scenario may be labeled in the APK manifest file with one or more arbitrary strings, creating logical groupings (e.g. GPU_COMPATIBILITY_TESTS). -If `--scenario-numbers` and `--scenario-labels` are specified together, Firebase Test Lab will first execute each scenario from `--scenario-numbers`. -It will then expand each given scenario label into a list of scenario numbers marked with that label, and execute those scenarios. - -=== "Groovy" - ```groovy - scenarioLabels = [ - "label1", - "label2" - ] - ``` -=== "Kotlin" - ```kotlin - scenarioLabels.set( - project.provider { - listOf("label1", "label2") - } - ) - ``` - -### scenarioNumbers -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. - -=== "Groovy" - ```groovy - scenarioNumbers = [ 1, 23, 52 ] - ``` -=== "Kotlin" - ```kotlin - scenarioNumbers.set( - project.provider { - listOf(1, 23, 52) - } - ) - ``` - -### obbFiles -A list of one or two Android OBB file names which will be copied to each test device before the tests will run (default: None). -Each OBB file name must conform to the format as specified by Android (e.g. `[main|patch].0300110.com.example.android.obb`) and will be installed into `/Android/obb//` on the test device. - -=== "Groovy" - ```groovy - obbFiles = [ - "local/file/path/test1.obb", - "local/file/path/test2.obb" - ] - ``` -=== "Kotlin" - ```kotlin - obbFiles.set( - project.provider { - listOf( - "local/file/path/test1.obb", - "local/file/path/test2.obb" - ) - } - ) - ``` - -### obbNames -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. - -=== "Groovy" - ```groovy - obbNames = [ - "patch.0300110.com.example.android.obb", - "patch.0300111.com.example.android.obb" - ] - ``` -=== "Kotlin" - ```kotlin - obbNames.set( - project.provider { - listOf( - "patch.0300110.com.example.android.obb", - "patch.0300111.com.example.android.obb" - ) - } - ) - ``` - -### failFast -If true, only a single attempt at most will be made to run each execution/shard in the matrix. Flaky test attempts are not affected. -Normally, 2 or more attempts are made if a potential infrastructure issue is detected. -This feature is for latency sensitive workloads. The incidence of execution failures may be significantly greater for -fail-fast matrices and support is more limited because of that expectation. - -=== "Groovy" - ```groovy - failFast = true - ``` - -=== "Kotlin" - ```kotlin - failFast.set(true) - ``` - - -### additionalFlankOptions -Appending additional option to flank root yaml. This option is useful when you would like to test options before official fladle support is added. -Multiple options are supported. - -Single option - -=== "Groovy" - ```groovy - additionalFlankOptions = "new-property: true" - ``` - -=== "Kotlin" - ```kotlin - additionalFlankOptions.set("new-property: true") - ``` - -Multiple options - -=== "Groovy" - ```groovy - additionalFlankOptions = """ - new-property: true - other-new-property: force - """.stripIndent() - ``` - -=== "Kotlin" - ```kotlin - additionalFlankOptions.set(""" - new-property: true - other-new-property: force - """.trimIndent()) - ``` - -### additionalGcloudOptions -Allow appending additional config to gcloud root yaml. This option is useful when you would like to test option -before it is available on Fladle. Supports both single and multiple properties. - - -Single option - -=== "Groovy" - ```groovy - additionalGcloudOptions = "new-property: true" - ``` - -=== "Kotlin" - ```kotlin - additionalGcloudOptions.set("new-property: true") - ``` - -Multiple options - -=== "Groovy" - ```groovy - additionalGcloudOptions = """ - new-property: true - other-new-property: force - """.stripIndent() - ``` - -=== "Kotlin" - ```kotlin - additionalGcloudOptions.set(""" - new-property: true - other-new-property: force - """.trimIndent()) - ``` - -### dependOnAssemble -Enable to automatically build the app and test APKs before `runFlank` executes. (default: `false`) - -=== "Groovy" - ``` groovy - dependOnAssemble = true - ``` -=== "Kotlin" - ``` kotlin - dependOnAssemble.set(true) - ``` - - -### async -Enable to return immediately after invoking tests, without waiting for results. (default: `false`) - -=== "Groovy" - ``` groovy - async = true - ``` -=== "Kotlin" - ``` kotlin - async.set(true) - ``` diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 5c99143e..00000000 --- a/docs/faq.md +++ /dev/null @@ -1,28 +0,0 @@ -# FAQ - -## Error APK file not found -The app APK and the instrumentation APK are expected to have already been generated before calling runFlank. To generate APKs, run `assembleDebug` and `assembleDebugAndroidTest` before running `runFlank`. - -You can also have Fladle build them for you by using the [`dependOnAssemble`](../configuration/#dependonassemble) property. - - - - -## No signature of method -If you receive an error like this, it is likely caused by invalid fladle extension confiuration. -The syntax was changed in the `0.9.X` releases in order to avoid touching files during the configuration phase. -```bash -No signature of method: flank_4vvjv7w3oopge32w1tl9cs6e4.fladle() is applicable for argument types: (flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1) values: [flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1@649a2315] - Possible solutions: file(java.lang.Object), find(), findAll(), file(java.lang.Object, org.gradle.api.PathValidation), files([Ljava.lang.Object;), findAll(groovy.lang.Closure) -``` - -If you receive a similar error, please check [configuration](../configuration#sample-configuration) for a sample configuration. - -## Debugging -`./gradlew runFlank -PdumpShards` Will dump shards and exit the process without running the tests. - -`./gradlew printYml` Will print out the current yaml configuration to be passed to Flank. - - -## More help? -Still having trouble? Check the #flank channel in the [Firebase Community Slack](https://firebase.community/) diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 36dbba57..00000000 --- a/docs/index.md +++ /dev/null @@ -1,36 +0,0 @@ -# Welcome to Fladle - -![Github Actions](https://github.com/runningcode/fladle/workflows/CI/badge.svg) - -The Gradle Plugin for Firebase Test Lab and [Flank](https://github.com/testArmada/flank). - -Fladle is the easiest way to scale your instrumentation tests using Firebase Test Lab. [Apply the gradle plugin and runFlank](quick-start)! You can easily run many hours of instrumentation tests using hundreds of devices in Test Lab and get the results in just few minutes. Fladle simplifies the configuration necessary to scale your tests using Firebase Test Lab and Flank. - -Fladle is mentioned in [Fragmented Podcast #163](https://fragmentedpodcast.com/episodes/163/). Skip to ~29 minutes. - - -## New? [_Get Started Here_](quick-start) - -### Flank -[Flank](https://github.com/testArmada/flank) is a parallel test runner for [Firebase Test Lab](https://firebase.google.com/docs/test-lab). - -Also read this [medium post](https://medium.com/walmartlabs/flank-smart-test-runner-for-firebase-cf65e1b1eca7). - -## Corporate Sponsors - -Thank you Doist for your sponsorship! - -Doist - -[Sponsor Fladle!](https://github.com/sponsors/runningcode) - -## Trusted by - -The following companies test their Android app using Fladle. - -SoundCloud Zalando Robinhood -Yelp Salesforce Dropbox -ImmoScout24 GrubHub - - -Want to appear here? [File an issue](https://github.com/runningcode/fladle/issues/new) or [open a PR](https://github.com/runningcode/fladle/edit/master/docs/index.md) diff --git a/docs/multi-module-testing.md b/docs/multi-module-testing.md deleted file mode 100644 index 5489dd64..00000000 --- a/docs/multi-module-testing.md +++ /dev/null @@ -1,125 +0,0 @@ -# Multi-module testing - -Multi module testing can be done by manually specifying [additionalTestApks](/fladle/configuration/#additionaltestapks) or applying the Fulladle plugin to automacally gather all the additional test apks. See also [this article](https://proandroiddev.com/android-code-coverage-on-firebase-test-lab-part-3-ci-cd-integration-10f729996c29) for a full setup and instructions including integration with CI. - -## Fulladle Plugin - -1. Apply the Fulladle plugin at the root of the project. - - === "Groovy" - ``` groovy - plugins { - id 'com.osacky.fulladle' version '{{ fladle.current_release }}' - } - ``` - === "Kotlin" - ``` kotlin - plugins { - id("com.osacky.fulladle") version "{{ fladle.current_release }}" - } - ``` - -2. Configure the Fladle extension. - - ===! "Groovy" - ``` groovy - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json") - } - ``` - === "Kotlin" - ``` kotlin - fladle { - serviceAccountCredentials.set(project.layout.projectDirectory.file("flank-gradle-service-account.json")) - } - ``` - - !!! Warning - If using buildFlavors or testing against a non default variant, you will need to specify the variant you want to test in the fulladleModuleConfig block. - -3. Run the tests. - First assemble all your debug apks and test apks. - ``` bash - ./gradlew assembleDebug assembleDebugAndroidTest - ``` - - !!! note - When using flavors, make sure to assemble your buildVariants as well. - - `./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest` - - Run Flank! - ``` bash - ./gradlew runFlank - ``` - -### Overriding configurations in modules -Fulladle will pick Flank configurations from the `fladle` block in the root `build.gradle` file. You may want to override some of these configurations for certain modules, you can add the following block to any Android library module to override its configurations: - - -===! "Groovy" - ``` groovy - fulladleModuleConfig { - clientDetails = [ - "test-type": "PR", - "build-number": "132" - ] - maxTestShards = 3 - environmentVariables = [ - "clearPackageData": "true" - ] - debugApk = "app.apk" - variant = "vanillaDebug" - } - ``` -=== "Kotlin" - ``` kotlin - fulladleModuleConfig { - clientDetails.set(mapOf( - "test-type" to "PR", - "build-number" to "132", - )) - maxTestShards.set(3) - environmentVariables.set(mapOf( - "clearPackageData" to "true" - )) - debugApk.set("app.apk") - variant.set("vanillaDebug") - } - ``` -All of the above configurations are optional, Flank will default to the top-level configurations if you don't override anything here. For details about these configurations, refer to [configuration docs](./configuration.md). - -#### Disabling a module -You may want to exclude a library module from testing when using Fulladle. You can do so by setting the `enabled` configuration in the module's `fulladleModuleConfig` block like so: - - -=== "Groovy" - ``` groovy - fulladleModuleConfig { - enabled = false - } - ``` -=== "Kotlin" - ``` kotlin - fulladleModuleConfig { - enabled.set(false) - } - ``` - - -### Overriding root-level config -Fulladle does not provide the ability to control which module ends up as the root-level module or as an additional module. Either one of app modules or library modules can become a root-level module. If a library module ends up as a root-level module, it needs to specify a `debugApk` in its `fladle` or `fulladleModuleConfig` block. - -The root-level configuration (e.g. `maxTestShards`) can also be overridden in the `fulladleModuleConfig` block of whatever module gets picked as the root module. - -## Troubleshooting -Fulladle might still have some rough edges, but we'd love feedback. Please join us in the [Firebase Community Slack](https://firebase.community/) with any feedback you may have. -You can also file [Fladle Github issues](https://github.com/runningcode/fladle/issues). - -When filing a bug report, please include the Flank version number, the Fladle version number and the output of the following: - -`./gradlew printYml` - -`./gradlew runFlank -PdumpShards` - - diff --git a/docs/quick-start.md b/docs/quick-start.md deleted file mode 100644 index 0d6d5fa9..00000000 --- a/docs/quick-start.md +++ /dev/null @@ -1,63 +0,0 @@ -# Quick Start - -Using Fladle takes 3 steps: - -1. Apply the Fladle plugin. Follow instructions [here](https://plugins.gradle.org/plugin/com.osacky.fladle) - - Root `build.gradle` - - === "Groovy" - ``` groovy - buildscript { - dependencies { - classpath "com.osacky.flank.gradle:fladle:{{ fladle.current_release }}" - } - } - ``` - === "Kotlin" - ``` kotlin - buildscript { - dependencies { - classpath("com.osacky.flank.gradle:fladle:{{ fladle.current_release }}") - } - } - ``` - - Application module `build.gradle` - - ===! "Groovy" - ``` groovy - apply plugin: "com.android.application" - apply plugin: "com.osacky.fladle" - ``` - - === "Kotlin" - ``` kotlin - plugins { - id ("com.android.application") - id ("com.osacky.fladle") - } - ``` - -2. Configure [Authentication using these steps.](../authentication) - - !!! Warning - If using buildFlavors or testing against a non default variant, [variant must also configured](/fladle/configuration#variant) - -3. Run your tests! - - First assemble your debug apk and test apk. - ``` bash - ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest - ``` - - !!! note - When using flavors, make sure to assemble your buildVariants. - - `./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest` - - Run Flank! - ``` bash - ./gradlew runFlank - ``` - diff --git a/docs/recipes.md b/docs/recipes.md deleted file mode 100644 index 83a7b6a8..00000000 --- a/docs/recipes.md +++ /dev/null @@ -1,103 +0,0 @@ -# Recipes - -Here are some recipes to use to achieve various goals in flank. For additional recipes or suggestions, please file an -issue on Github. - -## Smartly shard tests in 120 second shards across a maximum of 50 shards. - -This recipe will keep track of test durations automatically on firebase test lab and try to split up test runs in to 120 second shards up to maximum of 50 shards. - -=== "Groovy" - ``` groovy - fladle { - maxTestShards = 50 - shardTime = 120 - smartFlankGcsPath = "gs://fladle-results/smart-flank/JUnitReport.xml" - } - ``` -=== "Kotlin" - ``` kotlin - fladle { - maxTestShards.set(50) - shardTime.set(120) - smartFlankGcsPath.set("gs://fladle-results/smart-flank/JUnitReport.xml") - } - ``` - - -## Run different tests on different devices with different Gradle tasks. - -`./gradlew runFlankPerfTests` will execute the performance tests against a MediumPhone.arm -`./gradlew runFlankRegresssionTests` will execute the regressions tests against a SmallPhone.arm - -=== "Groovy" - ``` groovy - fladle { - configs { - perfTests { - devices.set([ - ["model" : "MediumPhone.arm", "version" : "28"], - ["model" : "MediumPhone.arm", "version" : "28"] - ]) - testTargets.set([ - "class com.sample.MyPerformanceTest" - ]) - } - regressionTests { - devices.set([ - [ "model" : "SmallPhone.arm", "version" : "28"] - ]) - testTargets.set([ - "class com.sample.MyRegressionTest" - ]) - } - } - } - ``` -=== "Kotlin" - ``` kotlin - fladle { - configs { - create("perfTests") { - devices.set(listOf( - mapOf("model" to "MediumPhone.arm", "version" to "28" ), - mapOf("model" to "MediumPhone.arm", "version" to "28") - )) - testTargets.set(listOf( - "class com.sample.MyPerformanceTest" - )) - } - create("regressionTests") { - devices.set(listOf( - mapOf("model" to "SmallPhone.arm", "version" to "28" ) - )) - testTargets.set(listOf( - "class com.sample.MyRegressionTest" - )) - } - } - } - ``` - - -## Always use the latest version of Flank - - -Use Gradle's [dynamic version syntax] to declare a dynamic version. - - -!!! warning - Dynamic versions lead to non-reproducible builds since Gradle will check for new versions periodically based on [how long versions are cached]. - -=== "Groovy" - ``` groovy - flankVersion = "23.+" - ``` -=== "Kotlin" - ``` kotlin - flankVersion.set("23.+") - ``` - - -[dynamic version syntax]: https://docs.gradle.org/current/userguide/dynamic_versions.html#sub:declaring_dependency_with_dynamic_version -[how long versions are cached]: https://docs.gradle.org/current/userguide/dynamic_versions.html#sec:controlling_dependency_caching_programmatically \ No newline at end of file diff --git a/docs/releasing.md b/docs/releasing.md deleted file mode 100644 index 10ec8351..00000000 --- a/docs/releasing.md +++ /dev/null @@ -1,68 +0,0 @@ -# Releasing - -* Create a local release branch from `master` -```bash -git checkout master -git pull -git checkout -b release_{{ fladle.next_release }} -``` - -* Update `version` in `fladle-plugin/build.gradle.kts` (remove `-SNAPSHOT`) -```kotlin -version = "{{ fladle.next_release }}" -``` - -* Update the current version and next version in `mkdocs.yml`: -``` -extra: - fladle: - release: '{{ fladle.next_release }}' - next_release: 'REPLACE_WITH_NEXT_VERSION_NUMBER' -``` - -* Take one last look -``` -git diff -``` - -* Commit all local changes -``` -git commit -am "Prepare {{ fladle.next_release }} release" -``` - -* Create a tag and push it -```bash -git tag v{{ fladle.next_release }} -git push origin v{{ fladle.next_release }} -``` - -Pushing the tag automatically triggers the [release workflow](https://github.com/runningcode/fladle/actions/workflows/gradle-release.yml) which: - -1. Publishes to Maven Central -2. Publishes to Gradle Plugin Portal -3. Creates a GitHub Release with auto-generated notes - -* Release to Maven Central - * Login to Maven Central Repository: [https://central.sonatype.com/](https://central.sonatype.com/) - * Click on **Publish** - -* Merge the release branch to master -``` -git checkout master -git pull -git merge --no-ff release_{{ fladle.next_release }} -``` -* Update `version` in `fladle-plugin/build.gradle.kts` (increase version and add `-SNAPSHOT`) -```kotlin -version = "REPLACE_WITH_NEXT_VERSION_NUMBER-SNAPSHOT" -``` - -* Commit your changes -``` -git commit -am "Prepare for next development iteration" -``` - -* Push your changes -``` -git push -``` diff --git a/docs/results.md b/docs/results.md deleted file mode 100644 index b36f6a10..00000000 --- a/docs/results.md +++ /dev/null @@ -1,11 +0,0 @@ -# Results - -By default, results are placed in the `build/fladle/results/` directly. - -A merged junit report is available in `JUnitReport.xml` - -On failures only, an html report with links to failed shards is also generated in the results directory with the name `HtmlErrorReport.html`. - -A cost report is also available `CostReport.txt`. - - diff --git a/docs/snapshots.md b/docs/snapshots.md deleted file mode 100644 index ceb72b3a..00000000 --- a/docs/snapshots.md +++ /dev/null @@ -1,47 +0,0 @@ -# Testing Snapshot Releases - -To test the Fladle snapshot release you have two options: - - -# Traditional -Root `build.gradle` -```groovy - -buildscript { - repositories { - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } - } - dependencies { - classpath "com.osacky.flank.gradle:fladle:{{ fladle.next_release }}-SNAPSHOT" - } -} -``` - -Project `build.gradle` -```groovy -apply plugin: "com.osacky.fladle" -``` - - - -# Plugin Management -`settings.gradle` -```groovy -pluginManagement { - repositories { - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } - gradlePluginPortal() - } -} -``` - -Android application `build.gradle` -```groovy -plugins { - id "com.osacky.fladle" version "{{ fladle.next_release }}-SNAPSHOT" -} -``` diff --git a/faq/index.html b/faq/index.html new file mode 100644 index 00000000..2a4f4231 --- /dev/null +++ b/faq/index.html @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + FAQ - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

FAQ

+

Error APK file not found

+

The app APK and the instrumentation APK are expected to have already been generated before calling runFlank. To generate APKs, run assembleDebug and assembleDebugAndroidTest before running runFlank.

+

You can also have Fladle build them for you by using the dependOnAssemble property.

+

No signature of method

+

If you receive an error like this, it is likely caused by invalid fladle extension confiuration. +The syntax was changed in the 0.9.X releases in order to avoid touching files during the configuration phase. +

No signature of method: flank_4vvjv7w3oopge32w1tl9cs6e4.fladle() is applicable for argument types: (flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1) values: [flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1@649a2315]
+            Possible solutions: file(java.lang.Object), find(), findAll(), file(java.lang.Object, org.gradle.api.PathValidation), files([Ljava.lang.Object;), findAll(groovy.lang.Closure)
+

+

If you receive a similar error, please check configuration for a sample configuration.

+

Debugging

+

./gradlew runFlank -PdumpShards Will dump shards and exit the process without running the tests.

+

./gradlew printYml Will print out the current yaml configuration to be passed to Flank.

+

More help?

+

Still having trouble? Check the #flank channel in the Firebase Community Slack

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/fladle-plugin/build.gradle.kts b/fladle-plugin/build.gradle.kts deleted file mode 100644 index 0f045edf..00000000 --- a/fladle-plugin/build.gradle.kts +++ /dev/null @@ -1,118 +0,0 @@ -import org.gradle.api.tasks.testing.logging.TestLogEvent - -group = "com.osacky.flank.gradle" -version = "0.21.0" -description = "Easily Scale your Android Instrumentation Tests with Firebase Test Lab with Flank" - -repositories { - google() - mavenCentral() - gradlePluginPortal() -} - -plugins { - `kotlin-dsl` - `java-gradle-plugin` - alias(libs.plugins.gradle.plugin.publish) - alias(libs.plugins.kotlinter) - alias(libs.plugins.vanniktech.publish) -} - -dependencies { - compileOnly(gradleApi()) - compileOnly(libs.agp) { - exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-embeddable") - exclude(group = "org.jetbrains.kotlin", module = "kotlin-compiler-runner") - } - compileOnly(libs.gradle.enterprise) - - // AGP must be on the runtime classpath so GradleTestKit's withPluginClasspath() - // can resolve the com.android.application and com.android.library plugins. - runtimeOnly(libs.agp) - - testImplementation(gradleTestKit()) - testImplementation(libs.junit) - testImplementation(libs.truth) -} - -gradlePlugin { - website.set("https://github.com/runningcode/fladle") - vcsUrl.set("https://github.com/runningcode/fladle") - plugins { - create("fladle") { - id = "com.osacky.fladle" - displayName = "Fladle" - description = project.description - implementationClass = "com.osacky.flank.gradle.FlankGradlePlugin" - tags.set(listOf("flank", "testing", "android", "fladle")) - } - create("fulladle") { - id = "com.osacky.fulladle" - displayName = "Fulladle" - description = project.description - implementationClass = "com.osacky.flank.gradle.FulladlePlugin" - tags.set(listOf("flank", "testing", "android", "fladle")) - } - } -} - -java { - withJavadocJar() - withSourcesJar() -} - -mavenPublishing { - publishToMavenCentral() - signAllPublications() - - pom { - name.set("Fladle") - description.set(project.description) - url.set("https://github.com/runningcode/fladle") - licenses { - license { - name.set("The Apache License, Version 2.0") - url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") - } - } - developers { - developer { - id.set("runningcode") - name.set("Nelson Osacky") - } - } - scm { - connection.set("scm:git:git://github.com/runningcode/fladle.git") - developerConnection.set("scm:git:ssh://github.com/runningcode/fladle.git") - url.set("https://github.com/runningcode/fladle") - } - } -} - -tasks.withType(Test::class.java).configureEach { - // Test fixtures are stored in here so we should re-run tests if the test projects change. - inputs.dir("src/test/resources") - testLogging { - events = setOf(TestLogEvent.SKIPPED, TestLogEvent.FAILED, TestLogEvent.PASSED) - } -} - -tasks.withType(ValidatePlugins::class.java).configureEach { - failOnWarning.set(true) - enableStricterValidation.set(true) -} - -// Ensure Java 17 Compatibility. See https://github.com/runningcode/fladle/issues/246 -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java).configureEach { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) - languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) - } -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} diff --git a/fladle-plugin/settings.gradle.kts b/fladle-plugin/settings.gradle.kts deleted file mode 100644 index 09608f5d..00000000 --- a/fladle-plugin/settings.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -rootProject.name = "fladle" - -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} - -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} \ No newline at end of file diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt deleted file mode 100644 index d6721998..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfig.kt +++ /dev/null @@ -1,484 +0,0 @@ -package com.osacky.flank.gradle - -import com.osacky.flank.gradle.validation.SinceFlank -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import kotlin.reflect.full.memberProperties - -interface FladleConfig { - // Project id is automatically discovered by default. Use this to override the project id. - @get:Input - @get:Optional - val projectId: Property - - @get:InputFile - @get:PathSensitive(PathSensitivity.RELATIVE) - @get:Optional - val serviceAccountCredentials: RegularFileProperty - - /** - * debugApk and instrumentationApk are [Property] and not [RegularFileProperty] because we support wildcard characters. - */ - @get:Input - @get:Optional - val debugApk: Property - - @get:Input - @get:Optional - val instrumentationApk: Property - - @get:Input - val sanityRobo: Property - - @get:Input - val useOrchestrator: Property - - @get:Input - val autoGoogleLogin: Property - - @get:Input - val devices: ListProperty> - - // https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run - @get:Input - val testTargets: ListProperty - - // The maximum number of shards. Value will be overwritten by [maxTestShards] if both used in configuration - @Deprecated( - message = "testShards is deprecated. Use maxTestShards instead", - replaceWith = ReplaceWith("maxTestShards"), - ) - @get:Input - @get:Optional - val testShards: Property - - /** - * The maximum number of shards - */ - @get:Input - @get:Optional - val maxTestShards: Property - - /** - * shardTime - the amount of time tests within a shard should take - * when set to > 0, the shard count is dynamically set based on time up to the maximmum limit defined by maxTestShards - * 2 minutes (120) is recommended. - * default: -1 (unlimited) - */ - @get:Input - @get:Optional - val shardTime: Property - - @get:SinceFlank("8.0.0") - @get:Input - @get:Optional - val repeatTests: Property - - @get:Input - @get:Optional - val smartFlankGcsPath: Property - - @get:Input - @get:Optional - val resultsHistoryName: Property - - @get:Input - val directoriesToPull: ListProperty - - @get:Input - val filesToDownload: ListProperty - - @get:Input - val environmentVariables: MapProperty - - @get:Input - val recordVideo: Property - - @get:Input - val performanceMetrics: Property - - // The number of times to retry failed tests. Default is 0. Max is 10. - @get:SinceFlank("8.0.0") - @get:Input - val flakyTestAttempts: Property - - // Variant to use for configuring output APK. - @get:Input - @get:Optional - val variant: Property - - /** - * ABI split to use for configuring output APK. - * - * If the application under test uses ABI splits, setting this property to a particular ABI value - * (e.g. "x86") will select the version of the APK to use. If the application uses ABI splits and - * no value is provided, an arbitrary APK will be selected. - * - * See [the Android docs for ABI splits](https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split). - */ - @get:Input - @get:Optional - val abi: Property - - @get:Input - @get:Optional - val resultsBucket: Property - - @get:SinceFlank("8.1.0", hasDefaultValue = true) - @get:Input - val keepFilePath: Property - - /** - * The name of a unique Google Cloud Storage object within the results bucket where raw test results will be stored - * (default: a timestamp with a random suffix). - */ - @get:Input - @get:Optional - val resultsDir: Property - - @get:SinceFlank("6.1.0") - @get:Input - val additionalTestApks: ListProperty - - /** - * The max time this test run can execute before it is cancelled (default: unlimited). - */ - @get:SinceFlank("20.05.0") - @get:Input - @get:Optional - val runTimeout: Property - - /** - * Terminate with exit code 0 when there are failed tests. - * Useful for Fladle and other gradle plugins that don't expect the process to have a non-zero exit code. - * The JUnit XML is used to determine failure. (default: false) - */ - @get:SinceFlank("20.05.0", hasDefaultValue = true) - @get:Input - @get:Optional - val ignoreFailedTests: Property - - /** - * Disables sharding. Useful for parameterized tests. (default: false) - */ - @get:Input - val disableSharding: Property - - /** - * Disables smart flank JUnit XML uploading. Useful for preventing timing data from being updated. (default: false) - */ - @get:Input - val smartFlankDisableUpload: Property - - /** - * The fully-qualified Java class name of the instrumentation test runner - * (default: the last name extracted from the APK manifest). - */ - @get:SinceFlank("6.2.0") - @get:Input - @get:Optional - val testRunnerClass: Property - - /** - * Local folder to store the test result. - * Folder is DELETED before each run to ensure only artifacts from the new run are saved. - */ - @get:Input - @get:Optional - val localResultsDir: Property - - /** - * Specifies the number of shards into which you want to evenly distribute test cases. - * The shards are run in parallel on separate devices. For example, - * if your test execution contains 20 test cases and you specify four shards, each shard executes five test cases. - * The number of shards should be less than the total number of test cases. - * The number of shards specified must be >= 1 and <= 50. - * This option cannot be used along max-test-shards and is not compatible with smart sharding. - * If you want to take benefits of smart sharding use max-test-shards instead. - * (default: null) - */ - @get:SinceFlank("20.05.0") - @get:Input - @get:Optional - val numUniformShards: Property - - /** - * A key-value map of additional details to attach to the test matrix. - * Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. - * When consuming the test results, such as in Cloud Functions or a CI system, - * these details can add additional context such as a link to the corresponding pull request. - */ - @get:SinceFlank("20.05.0") - @get:Input - val clientDetails: MapProperty - - /** - * Always run - these tests are inserted at the beginning of every shard - * useful if you need to grant permissions or login before other tests run - */ - @get:Input - val testTargetsAlwaysRun: ListProperty - - /** - * A list of device-path: file-path pairs that indicate the device paths to push files to the device before starting tests, and the paths of files to push. - * Device paths must be under absolute, whitelisted paths (${EXTERNAL_STORAGE}, or ${ANDROID_DATA}/local/tmp). - * Source file paths may be in the local filesystem or in Google Cloud Storage (gs://…). - */ - @get:SinceFlank("20.05.0") - @get:Input - val otherFiles: MapProperty - - /** - * The name of the network traffic profile, for example LTE, HSPA, etc, - * which consists of a set of parameters to emulate network conditions when running the test - * (default: no network shaping; see available profiles listed by the `flank test network-profiles list` command). - * This feature only works on physical devices. - */ - @get:SinceFlank("20.05.0") - @get:Input - @get:Optional - val networkProfile: Property - - /** - * The path to a Robo Script JSON file. - * The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. - * You can guide the Robo test to perform specific actions by recording a Robo Script in Android Studio and then specifying this argument. - * Learn more at [https://firebase.google.com/docs/test-lab/robo-ux-test#scripting]. - */ - @get:SinceFlank("20.05.0") - @get:Input - @get:Optional - val roboScript: Property - - /** - * List of robo_directives that you can use to customize the behavior of Robo test. - * The type specifies the action type of the directive, which may take on values click, text or ignore. - * Each directive is list of String = [type, key, value] - * Each key should be the Android resource name of a target UI element and each value should be the text input for that element. - * Values are only permitted for text type elements, so no value should be specified for click and ignore type elements. - */ - @get:SinceFlank("20.05.0") - @get:Input - val roboDirectives: ListProperty> - - /** - * The max time test execution can run before it is cancelled (default: 15m). - * It does not include any time necessary to prepare and clean up the target device. - * The maximum possible testing time is 45m on physical devices and 60m on virtual devices. - * The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. - * - * Examples: - * * 1h -> 1 hour - * * 5m -> 5 minutes - * * 200s -> 200 seconds - * * 100 -> 100 seconds - */ - @get:Input - val testTimeout: Property - - /** - * Output style of execution status. May be one of [verbose, multi, single]. - * For runs with only one test execution the default value is 'verbose', in other cases - * 'multi' is used as the default. The output style 'multi' is not displayed correctly on consoles - * which don't support ansi codes, to avoid corrupted output use single or verbose. - */ - @get:SinceFlank("20.06.0", hasDefaultValue = true) - @get:Input - val outputStyle: Property - - /** - * Flank provides two ways for parsing junit xml results. - * New way uses google api instead of merging xml files, but can generate slightly different output format. - * This flag allows fallback for legacy xml junit results parsing - */ - @get:SinceFlank("20.05.0", hasDefaultValue = true) - @get:Input - val legacyJunitResult: Property - - /** - * Enables creating an additional local junit result on local storage with failure nodes on passed flaky tests. - */ - @get:SinceFlank("20.06.0", hasDefaultValue = true) - @get:Input - val fullJunitResult: Property - - /** - * A list of up to 100 additional APKs to install, in addition to those being directly tested. - * The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. - */ - @get:SinceFlank("20.05.0") - @get:Input - @get:Optional - val additionalApks: ListProperty - - /** - * Enable using average time from previous tests duration when using SmartShard and tests did not run before. - * (default: false) - */ - @get:SinceFlank("20.08.4") - @get:Input - @get:Optional - val useAverageTestTimeForNewTests: Property - - /** - * Set default test time expressed in seconds, used for calculating shards. - * (default: 120.0s) - */ - @get:SinceFlank("20.08.4") - @get:Input - @get:Optional - val defaultTestTime: Property - - /** - * Set default parameterized class test time expressed in seconds, used for calculating shards. - * (default: 2x [defaultTestTime] => 240s) - */ - @get:SinceFlank("20.08.4") - @get:Input - @get:Optional - val defaultClassTestTime: Property - - /** - * Disable flank results upload on gcloud storage. - * (default: false) - */ - @get:SinceFlank("20.07.0") - @get:Input - @get:Optional - val disableResultsUpload: Property - - /** - * Specifies a group of packages, classes, and/or test cases to run in each shard (a group of test cases). - * The shards are run in parallel on separate devices. You can use this option up to 50 times to specify multiple shards when one or more physical devices are selected, - * or up to 500 times when no physical devices are selected. - * Note: If you include the flags environment-variable or test-targets when running test-targets-for-shard, the flags are applied to all the shards you create. - * You can also specify multiple packages, classes, or test cases in the same shard by separating each item with a comma. - * To specify both package and class in the same shard, separate package and class with semi-colons. - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val testTargetsForShard: ListProperty - - /** - * Whether to grant runtime permissions on the device before the test begins. - * By default, all permissions are granted. PERMISSIONS must be one of: all, none - * (default: all) - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val grantPermissions: Property - - /** - * The type of test to run. TYPE must be one of: instrumentation, robo, game-loop. - * Use if you want to be sure there is only one type of tests being run - * (flank enables to run mixed types of test in one run). - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val type: Property - - /** - * A list of game-loop scenario labels (default: None). Each game-loop scenario may be labeled in the - * APK manifest file with one or more arbitrary strings, creating logical groupings (e.g. GPU_COMPATIBILITY_TESTS). - * If --scenario-numbers and --scenario-labels are specified together, Firebase Test Lab will first execute each scenario from --scenario-numbers. - * It will then expand each given scenario label into a list of scenario numbers marked with that label, and execute those scenarios. - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val scenarioLabels: ListProperty - - /** - * 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. - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val scenarioNumbers: ListProperty - - /** - * A list of one or two Android OBB file names which will be copied to each test device before the tests will run (default: None). - * Each OBB file name must conform to the format as specified by Android (e.g. [main|patch].0300110.com.example.android.obb) and will be installed into /Android/obb// on the test device. - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val obbFiles: ListProperty - - /** - * 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. - */ - @get:SinceFlank("20.12.0") - @get:Input - @get:Optional - val obbNames: ListProperty - - /** - * If true, only a single attempt at most will be made to run each execution/shard in the matrix. - * Flaky test attempts are not affected. Normally, 2 or more attempts are made if a potential - * infrastructure issue is detected. This feature is for latency sensitive workloads. The - * incidence of execution failures may be significantly greater for fail-fast matrices and support - * is more limited because of that expectation. - */ - @get:SinceFlank("21.01.0") - @get:Input - @get:Optional - val failFast: Property - - /** - * Allow appending additional config to flank root yaml. This option is useful when you would like to test option - * before it is available on Fladle. Supports both single and multiple properties. - */ - @get:Input - @get:Optional - val additionalFlankOptions: Property - - /** - * When enabled, the execution of flank will depend on the Gradle tasks to assemble the debug and instrumentation APKs. - * before flank runs - */ - @get:Input - @get:Optional - val dependOnAssemble: Property - - /** - * When enabled, tests are invoked asynchronously, without waiting for results. - */ - @get:Input - @get:Optional - val async: Property - - /** - * Allow appending additional config to gcloud root yaml. This option is useful when you would like to test option - * before it is available on Fladle. Supports both single and multiple properties. - */ - @get:Input - @get:Optional - val additionalGcloudOptions: Property - - @Internal - fun getPresentProperties() = - this::class - .memberProperties - .filter { - when (val prop = it.call(this)) { - is Property<*> -> prop.isPresent - is MapProperty<*, *> -> prop.isPresent && prop.get().isNotEmpty() - is ListProperty<*> -> prop.isPresent && prop.get().isNotEmpty() - else -> false - } - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt deleted file mode 100644 index 4105cad4..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladleConfigImpl.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Internal - -data class FladleConfigImpl( - @get:Internal internal val name: String, - override val projectId: Property, - override val serviceAccountCredentials: RegularFileProperty, - override val debugApk: Property, - override val instrumentationApk: Property, - override val sanityRobo: Property, - override val useOrchestrator: Property, - override val autoGoogleLogin: Property, - override val devices: ListProperty>, - override val testTargets: ListProperty, - override val shardTime: Property, - override val testShards: Property, - override val repeatTests: Property, - override val smartFlankGcsPath: Property, - override val resultsHistoryName: Property, - override val flakyTestAttempts: Property, - override val variant: Property, - override val abi: Property, - override val directoriesToPull: ListProperty, - override val filesToDownload: ListProperty, - override val environmentVariables: MapProperty, - override val recordVideo: Property, - override val performanceMetrics: Property, - override val resultsBucket: Property, - override val keepFilePath: Property, - override val resultsDir: Property, - override val additionalTestApks: ListProperty, - override val runTimeout: Property, - override val ignoreFailedTests: Property, - override val disableSharding: Property, - override val smartFlankDisableUpload: Property, - override val testRunnerClass: Property, - override val localResultsDir: Property, - override val numUniformShards: Property, - override val clientDetails: MapProperty, - override val testTargetsAlwaysRun: ListProperty, - override val otherFiles: MapProperty, - override val networkProfile: Property, - override val roboScript: Property, - override val roboDirectives: ListProperty>, - override val testTimeout: Property, - override val outputStyle: Property, - override val legacyJunitResult: Property, - override val fullJunitResult: Property, - override val additionalApks: ListProperty, - override val defaultTestTime: Property, - override val useAverageTestTimeForNewTests: Property, - override val defaultClassTestTime: Property, - override val disableResultsUpload: Property, - override val testTargetsForShard: ListProperty, - override val grantPermissions: Property, - override val type: Property, - override val scenarioLabels: ListProperty, - override val scenarioNumbers: ListProperty, - override val obbFiles: ListProperty, - override val obbNames: ListProperty, - override val failFast: Property, - override val maxTestShards: Property, - override val additionalFlankOptions: Property, - override val additionalGcloudOptions: Property, - override val dependOnAssemble: Property, - override val async: Property, -) : FladleConfig { - /** - * Prepare config to run sanity robo. - * - * Sets [sanityRobo] property as `true`. - * - * Cleans [instrumentationApk], [additionalTestApks], [roboDirectives], [roboScript] properties. - */ - fun clearPropertiesForSanityRobo() { - sanityRobo.set(true) - additionalTestApks.empty() - // Must be set to an empty string to override `convention` value inherited from base. - instrumentationApk.set("") - roboDirectives.empty() - // Must be set to an empty string to override `convention` value inherited from base. - roboScript.set("") - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt deleted file mode 100644 index 44873cd9..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FladlePluginDelegate.kt +++ /dev/null @@ -1,315 +0,0 @@ -package com.osacky.flank.gradle - -import com.android.build.api.dsl.ApplicationExtension -import com.android.build.api.variant.ApplicationAndroidComponentsExtension -import com.android.build.api.variant.FilterConfiguration -import com.android.build.api.variant.HasAndroidTest -import com.osacky.flank.gradle.validation.checkForExclusionUsage -import com.osacky.flank.gradle.validation.validateOptionsUsed -import org.gradle.api.GradleException -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration -import org.gradle.api.plugins.BasePluginExtension -import org.gradle.api.tasks.TaskContainer -import org.gradle.kotlin.dsl.create -import org.gradle.util.GradleVersion - -class FladlePluginDelegate { - fun apply(target: Project) { - checkMinimumGradleVersion() - - // Create Configuration to store flank dependency - target.configurations.create(FLADLE_CONFIG) - - val extension = target.extensions.create("fladle", target.objects) - - target.tasks.register("flankAuth", FlankJavaExec::class.java) { - doFirst { - target.layout.fladleDir - .get() - .asFile - .mkdirs() - } - classpath = project.fladleConfig - args = listOf("auth", "login") - } - - configureTasks(target, extension) - } - - private fun checkMinimumGradleVersion() { - if (GRADLE_MIN_VERSION > GradleVersion.current()) { - throw GradleException("Fladle requires at minimum version $GRADLE_MIN_VERSION. Detected version ${GradleVersion.current()}.") - } - } - - private fun configureTasks( - project: Project, - base: FlankGradleExtension, - ) { - base.flankVersion.finalizeValueOnRead() - base.flankCoordinates.finalizeValueOnRead() - base.serviceAccountCredentials.finalizeValueOnRead() - - // Register onVariants callbacks before afterEvaluate for APK path detection - project.pluginManager.withPlugin("com.android.application") { - if (!base.debugApk.isPresent || !base.instrumentationApk.isPresent) { - findDebugAndInstrumentationApk(project, base) - } - } - - project.afterEvaluate { - // Add Flank dependency to Fladle Configuration - // Must be done afterEvaluate otherwise extension values will not be set. - project.dependencies.add(FLADLE_CONFIG, "${base.flankCoordinates.get()}:${base.flankVersion.get()}") - - tasks.apply { - createTasksForConfig(base, base, project, "") - - base.configs.forEach { config -> - createTasksForConfig(base, config, project, config.name.capitalize()) - } - } - } - } - - private fun TaskContainer.createTasksForConfig( - base: FlankGradleExtension, - config: FladleConfig, - project: Project, - name: String, - ) { - val configName = name.toLowerCase() - // we want to use default dir only if user did not set own `localResultsDir` - val useDefaultDir = config.localResultsDir.isPresent.not() - - val validateFladle = - register("validateFladleConfig$name") { - description = "Perform validation actions" - group = TASK_GROUP - doLast { - checkIfSanityAndValidateConfigs(config) - validateOptionsUsed(config = config, flank = base.flankVersion.get()) - checkForExclusionUsage(config) - } - } - - val writeConfigProps = register("writeConfigProps$name", YamlConfigWriterTask::class.java, base, config, name) - - writeConfigProps.configure { dependsOn(validateFladle) } - - register("printYml$name") { - description = "Print the flank.yml file to the console." - group = TASK_GROUP - dependsOn(writeConfigProps) - doLast { - println( - writeConfigProps - .get() - .fladleConfigFile - .get() - .asFile - .readText(), - ) - } - } - - register("flankDoctor$name", FlankJavaExec::class.java) { - if (useDefaultDir) setUpWorkingDir(configName) - description = "Finds problems with the current configuration." - classpath = project.fladleConfig - args = - listOf( - "firebase", - "test", - "android", - "doctor", - "-c", - writeConfigProps - .get() - .fladleConfigFile - .get() - .asFile.absolutePath, - ) - dependsOn(writeConfigProps) - } - - val execFlank = register("execFlank$name", FlankExecutionTask::class.java, config) - execFlank.configure { - if (useDefaultDir) setUpWorkingDir(configName) - description = "Runs instrumentation tests using flank on firebase test lab." - classpath = project.fladleConfig - args = - if (project.hasProperty("dumpShards")) { - listOf( - "firebase", - "test", - "android", - "run", - "-c", - writeConfigProps - .get() - .fladleConfigFile - .get() - .asFile.absolutePath, - "--dump-shards", - ) - } else { - listOf( - "firebase", - "test", - "android", - "run", - "-c", - writeConfigProps - .get() - .fladleConfigFile - .get() - .asFile.absolutePath, - ) - } - if (config.serviceAccountCredentials.isPresent) { - environment(mapOf("GOOGLE_APPLICATION_CREDENTIALS" to config.serviceAccountCredentials.get())) - } - dependsOn(writeConfigProps) - if (config.dependOnAssemble.isPresent && config.dependOnAssemble.get()) { - // Find assemble tasks by convention name pattern - val variantName = config.variant.orNull - if (variantName != null) { - val capitalizedVariant = variantName.capitalize() - dependsOn("assemble$capitalizedVariant") - dependsOn("assemble${capitalizedVariant}AndroidTest") - } else { - dependsOn("assembleDebug") - dependsOn("assembleDebugAndroidTest") - } - } - if (config.localResultsDir.hasValue) { - this.outputs.dir("${workingDir.path}/${config.localResultsDir.get()}") - // This task is never upToDate since it relies on network connections and firebase test lab. - this.outputs.upToDateWhen { false } - } - } - if (config.localResultsDir.hasValue && canImportReport()) { - try { - importReport(project, execFlank) - } catch (e: Exception) { - project.logger.warn(e.message) - e.printStackTrace() - } - } - - register("runFlank$name", RunFlankTask::class.java).configure { - dependsOn(execFlank) - } - } - - private fun automaticallyConfigureTestOrchestrator( - project: Project, - config: FladleConfig, - androidExtension: ApplicationExtension, - ) { - project.afterEvaluate { - val execution = androidExtension.testOptions.execution.uppercase() - val useOrchestrator = - execution == "ANDROIDX_TEST_ORCHESTRATOR" || - execution == "ANDROID_TEST_ORCHESTRATOR" - if (useOrchestrator) { - log("Automatically detected the use of Android Test Orchestrator") - config.useOrchestrator.set(true) - } - } - } - - private fun findDebugAndInstrumentationApk( - project: Project, - config: FladleConfig, - ) { - val androidExtension = - requireNotNull( - project.extensions.findByType(ApplicationExtension::class.java), - ) { "Could not find ApplicationExtension in ${project.name}" } - automaticallyConfigureTestOrchestrator(project, config, androidExtension) - - val androidComponents = - requireNotNull(project.extensions.findByType(ApplicationAndroidComponentsExtension::class.java)) { - "Could not find ApplicationAndroidComponentsExtension in ${project.name}" - } - - androidComponents.onVariants { variant -> - if (!variant.isExpectedVariant(config)) return@onVariants - val androidTest = (variant as? HasAndroidTest)?.androidTest ?: return@onVariants - - val buildType = variant.buildType ?: return@onVariants - val flavorName = variant.productFlavors.joinToString("") { it.second } - val flavorPath = variant.productFlavors.joinToString("/") { it.second } - val archivesName = - project.extensions - .getByType(BasePluginExtension::class.java) - .archivesName - .get() - val buildDir = project.layout.buildDirectory - - // Test APK path - val testApkDirPath = if (flavorPath.isNotEmpty()) "androidTest/$flavorPath/$buildType" else "androidTest/$buildType" - val testApkFileName = - if (flavorName.isNotEmpty()) { - "$archivesName-$flavorName-$buildType-androidTest.apk" - } else { - "$archivesName-$buildType-androidTest.apk" - } - val testApkPath = - buildDir - .file("outputs/apk/$testApkDirPath/$testApkFileName") - .get() - .asFile - .absolutePath - - variant.outputs.forEach { output -> - if (!output.isExpectedAbiOutput(config)) return@forEach - - val abiFilter = output.filters.firstOrNull { it.filterType == FilterConfiguration.FilterType.ABI } - val abiName = abiFilter?.identifier - - val appApkDirPath = if (flavorPath.isNotEmpty()) "$flavorPath/$buildType" else buildType - val appApkFileName = - buildString { - append(archivesName) - if (flavorName.isNotEmpty()) append("-$flavorName") - if (abiName != null) append("-$abiName") - append("-$buildType.apk") - } - val appApkPath = - buildDir - .file("outputs/apk/$appApkDirPath/$appApkFileName") - .get() - .asFile - .absolutePath - - if (!config.debugApk.isPresent) { - // Don't set debug apk if not already set. #172 - project.log("Configuring fladle.debugApk from variant ${variant.name}") - config.debugApk.set(appApkPath) - } - if (!config.roboScript.isPresent && !config.instrumentationApk.isPresent && !config.sanityRobo.get()) { - // Don't set instrumentation apk if not already set. #172 - project.log("Configuring fladle.instrumentationApk from variant ${variant.name}") - config.instrumentationApk.set(testApkPath) - } - } - } - } - - private val Project.fladleConfig: Configuration - get() = configurations.getByName(FLADLE_CONFIG) - - companion object { - val GRADLE_MIN_VERSION: GradleVersion = GradleVersion.version("9.1") - const val TASK_GROUP = "fladle" - const val FLADLE_CONFIG = "fladle" - - fun Project.log(message: String) { - logger.info("Fladle: $message") - } - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt deleted file mode 100644 index 2518ff9d..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankExecutionTask.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.file.ProjectLayout -import org.gradle.api.tasks.Nested -import org.gradle.work.DisableCachingByDefault -import javax.inject.Inject - -@DisableCachingByDefault( - because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", -) -abstract class FlankExecutionTask - @Inject - constructor( - projectLayout: ProjectLayout, - @get:Nested val config: FladleConfig, - ) : FlankJavaExec(projectLayout) { - init { - doFirst { - checkFilesExist(config) - } - } - - private fun checkFilesExist(base: FladleConfig) { - if (base.serviceAccountCredentials.isPresent) { - check( - base.serviceAccountCredentials - .get() - .asFile - .exists(), - ) { - "serviceAccountCredential file doesn't exist ${base.serviceAccountCredentials.get()}" - } - } - } - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt deleted file mode 100644 index 25b496d1..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradleExtension.kt +++ /dev/null @@ -1,229 +0,0 @@ -package com.osacky.flank.gradle - -import groovy.lang.Closure -import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.kotlin.dsl.listProperty -import org.gradle.kotlin.dsl.mapProperty -import org.gradle.kotlin.dsl.property -import javax.inject.Inject - -open class FlankGradleExtension - @Inject - constructor( - objects: ObjectFactory, - ) : FladleConfig { - companion object { - const val FLANK_VERSION = "23.10.1" - } - - @get:Input - val flankCoordinates: Property = objects.property(String::class.java).convention("com.github.flank:flank") - - override val sanityRobo: Property = objects.property().convention(false) - - @get:Input - val flankVersion: Property = objects.property(String::class.java).convention(FLANK_VERSION) - - // Project id is automatically discovered by default. Use this to override the project id. - override val projectId: Property = objects.property() - override val serviceAccountCredentials: RegularFileProperty = objects.fileProperty() - override val useOrchestrator: Property = objects.property().convention(false) - override val autoGoogleLogin: Property = objects.property().convention(false) - override val devices: ListProperty> = - objects.listProperty>().convention( - listOf(mapOf("model" to "SmallPhone.arm", "version" to "28")), - ) - - // https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run - override val testTargets: ListProperty = objects.listProperty() - - override val testShards: Property = objects.property() - override val shardTime: Property = objects.property() - override val repeatTests: Property = objects.property() - - // Shard Android tests by time using historical run data. The amount of shards used is set by `testShards`. - override val smartFlankGcsPath: Property = objects.property() - - override val resultsHistoryName: Property = objects.property() - - override val flakyTestAttempts: Property = objects.property().convention(0) - - override val variant: Property = objects.property() - - override val abi: Property = objects.property() - - /** - * debugApk and instrumentationApk are [Property] and not [RegularFileProperty] because we support wildcard characters. - */ - override val debugApk: Property = objects.property() - - override val instrumentationApk: Property = objects.property() - - override val directoriesToPull: ListProperty = objects.listProperty() - - override val filesToDownload: ListProperty = objects.listProperty() - - override val environmentVariables: MapProperty = objects.mapProperty() - - override val recordVideo: Property = objects.property().convention(true) - - override val performanceMetrics: Property = objects.property().convention(true) - - override val resultsBucket: Property = objects.property() - - override val keepFilePath: Property = objects.property().convention(false) - - override val resultsDir: Property = objects.property() - - override val additionalTestApks: ListProperty = objects.listProperty() - - override val runTimeout: Property = objects.property() - - override val ignoreFailedTests: Property = objects.property().convention(false) - - override val disableSharding: Property = objects.property().convention(false) - - override val smartFlankDisableUpload: Property = objects.property().convention(false) - - override val testRunnerClass: Property = objects.property() - - override val localResultsDir: Property = objects.property() - - override val numUniformShards: Property = objects.property() - - override val clientDetails: MapProperty = objects.mapProperty() - - override val testTargetsAlwaysRun: ListProperty = objects.listProperty() - - override val otherFiles: MapProperty = objects.mapProperty() - - override val networkProfile: Property = objects.property() - - override val roboScript: Property = objects.property() - - override val roboDirectives: ListProperty> = objects.listProperty() - - override val testTimeout: Property = objects.property().convention("15m") - - override val outputStyle: Property = objects.property().convention("single") - - override val legacyJunitResult: Property = objects.property().convention(false) - - override val fullJunitResult: Property = objects.property().convention(false) - - override val additionalApks: ListProperty = objects.listProperty() - - override val defaultTestTime: Property = objects.property() - - override val useAverageTestTimeForNewTests: Property = objects.property() - - override val defaultClassTestTime: Property = objects.property() - - override val disableResultsUpload: Property = objects.property() - - override val testTargetsForShard: ListProperty = objects.listProperty() - - override val grantPermissions: Property = objects.property() - - override val type: Property = objects.property() - - override val scenarioLabels: ListProperty = objects.listProperty() - - override val scenarioNumbers: ListProperty = objects.listProperty() - - override val obbFiles: ListProperty = objects.listProperty() - - override val obbNames: ListProperty = objects.listProperty() - - override val failFast: Property = objects.property() - - override val maxTestShards: Property = objects.property() - - override val additionalFlankOptions: Property = objects.property() - - override val additionalGcloudOptions: Property = objects.property() - - override val dependOnAssemble: Property = objects.property().convention(false) - - override val async: Property = objects.property().convention(false) - - @Internal - val configs: NamedDomainObjectContainer = - objects.domainObjectContainer(FladleConfigImpl::class.java) { - FladleConfigImpl( - name = it, - projectId = objects.property().convention(projectId), - serviceAccountCredentials = objects.fileProperty().convention(serviceAccountCredentials), - debugApk = objects.property().convention(debugApk), - instrumentationApk = objects.property().convention(instrumentationApk), - sanityRobo = objects.property().convention(sanityRobo), - useOrchestrator = objects.property().convention(useOrchestrator), - autoGoogleLogin = objects.property().convention(autoGoogleLogin), - devices = objects.listProperty>().convention(devices), - testTargets = objects.listProperty().convention(testTargets), - testShards = objects.property().convention(testShards), - shardTime = objects.property().convention(shardTime), - repeatTests = objects.property().convention(repeatTests), - smartFlankGcsPath = objects.property().convention(smartFlankGcsPath), - resultsHistoryName = objects.property().convention(resultsHistoryName), - flakyTestAttempts = objects.property().convention(flakyTestAttempts), - variant = objects.property().convention(variant), - abi = objects.property().convention(abi), - directoriesToPull = objects.listProperty().convention(directoriesToPull), - filesToDownload = objects.listProperty().convention(filesToDownload), - environmentVariables = objects.mapProperty().convention(environmentVariables), - recordVideo = objects.property().convention(recordVideo), - performanceMetrics = objects.property().convention(performanceMetrics), - resultsBucket = objects.property().convention(resultsBucket), - keepFilePath = objects.property().convention(keepFilePath), - resultsDir = objects.property().convention(resultsDir), - additionalTestApks = objects.listProperty().convention(additionalTestApks), - runTimeout = objects.property().convention(runTimeout), - ignoreFailedTests = objects.property().convention(ignoreFailedTests), - disableSharding = objects.property().convention(disableSharding), - smartFlankDisableUpload = objects.property().convention(smartFlankDisableUpload), - testRunnerClass = objects.property().convention(testRunnerClass), - localResultsDir = objects.property().convention(localResultsDir), - numUniformShards = objects.property().convention(numUniformShards), - clientDetails = objects.mapProperty().convention(clientDetails), - testTargetsAlwaysRun = objects.listProperty().convention(testTargetsAlwaysRun), - otherFiles = objects.mapProperty().convention(otherFiles), - networkProfile = objects.property().convention(networkProfile), - roboScript = objects.property().convention(roboScript), - roboDirectives = objects.listProperty>().convention(roboDirectives), - testTimeout = objects.property().convention(testTimeout), - outputStyle = objects.property().convention(outputStyle), - legacyJunitResult = objects.property().convention(legacyJunitResult), - fullJunitResult = objects.property().convention(fullJunitResult), - additionalApks = objects.listProperty().convention(additionalApks), - useAverageTestTimeForNewTests = objects.property().convention(useAverageTestTimeForNewTests), - defaultTestTime = objects.property().convention(defaultTestTime), - defaultClassTestTime = objects.property().convention(defaultClassTestTime), - disableResultsUpload = objects.property().convention(disableResultsUpload), - testTargetsForShard = objects.listProperty().convention(testTargetsForShard), - grantPermissions = objects.property().convention(grantPermissions), - type = objects.property().convention(type), - scenarioLabels = objects.listProperty().convention(scenarioLabels), - scenarioNumbers = objects.listProperty().convention(scenarioNumbers), - obbFiles = objects.listProperty().convention(obbFiles), - obbNames = objects.listProperty().convention(obbNames), - failFast = objects.property().convention(failFast), - maxTestShards = objects.property().convention(maxTestShards), - additionalFlankOptions = objects.property().convention(additionalFlankOptions), - additionalGcloudOptions = objects.property().convention(additionalGcloudOptions), - dependOnAssemble = objects.property().convention(dependOnAssemble), - async = objects.property().convention(async), - ) - } - - fun configs(closure: Closure<*>) { - configs.configure(closure) - } - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt deleted file mode 100644 index 3aa0b437..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankGradlePlugin.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.Plugin -import org.gradle.api.Project - -class FlankGradlePlugin : Plugin { - override fun apply(target: Project) { - FladlePluginDelegate().apply(target) - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt deleted file mode 100644 index 21bb73aa..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FlankJavaExec.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.file.ProjectLayout -import org.gradle.api.tasks.JavaExec -import org.gradle.work.DisableCachingByDefault -import javax.inject.Inject - -@DisableCachingByDefault( - because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", -) -abstract class FlankJavaExec - @Inject - constructor( - projectLayout: ProjectLayout, - ) : JavaExec() { - init { - group = FladlePluginDelegate.TASK_GROUP - mainClass.set("ftl.Main") - workingDir(projectLayout.fladleDir) - } - - fun setUpWorkingDir(configName: String) = workingDir(project.layout.buildDirectory.dir("fladle/$configName")) - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt deleted file mode 100644 index e6637bd1..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladleModuleExtension.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property -import org.gradle.kotlin.dsl.mapProperty -import org.gradle.kotlin.dsl.property -import javax.inject.Inject - -open class FulladleModuleExtension - @Inject - constructor( - objects: ObjectFactory, - ) { - /** - * When set to false, Fulladle will not automatically add this module to additionalTestApks. - * - * Default: true - */ - val enabled: Property = objects.property().convention(true) - - /** - * The maximum number of shards to be used for this specific test apk. - */ - val maxTestShards: Property = objects.property().convention(null as Int?) - - /** - * A key-value map of additional details to attach to the test matrix results file. - * Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. - * When consuming the test results, such as in Cloud Functions or a CI system, - * these details can add additional context such as a link to the corresponding pull request. - */ - val clientDetails: MapProperty = objects.mapProperty() - - /** - * The environment variables are mirrored as extra options to the am instrument -e KEY1 VALUE1 … command and - * passed to your test runner (typically AndroidJUnitRunner) - */ - val environmentVariables: MapProperty = objects.mapProperty() - - /** - * the app under test - */ - val debugApk: Property = objects.property().convention(null as String?) - - /** - * The variant that should be used for the specific module. If nothing is specified any variant - * can be a match. - */ - val variant: Property = objects.property().convention(null as String?) - - /** - * Variant APK info collected during configuration via onVariants callbacks. - * Used by FulladlePlugin at execution time to build YAML entries. - */ - internal val variantApks: MutableList = mutableListOf() - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt deleted file mode 100644 index 59be14be..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/FulladlePlugin.kt +++ /dev/null @@ -1,298 +0,0 @@ -package com.osacky.flank.gradle - -import com.android.build.api.variant.ApplicationAndroidComponentsExtension -import com.android.build.api.variant.FilterConfiguration -import com.android.build.api.variant.HasAndroidTest -import com.android.build.api.variant.LibraryAndroidComponentsExtension -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.plugins.BasePluginExtension -import org.gradle.kotlin.dsl.getByType - -/** - * Like the Fladle plugin, but it configures additionalTestApks for the _full_ project. Hence fulladle. - */ -class FulladlePlugin : Plugin { - override fun apply(root: Project) { - check(root.parent == null) { "Fulladle must be applied in the root project in order to configure subprojects." } - FladlePluginDelegate().apply(root) - - val flankGradleExtension = root.extensions.getByType(FlankGradleExtension::class) - - root.subprojects { - // Yuck, cross project configuration - extensions.create("fulladleModuleConfig", FulladleModuleExtension::class.java) - - // Register onVariants callbacks to capture APK info during configuration - pluginManager.withPlugin("com.android.application") { - val androidComponents = extensions.getByType(ApplicationAndroidComponentsExtension::class.java) - val ext = extensions.findByType(FulladleModuleExtension::class.java) ?: return@withPlugin - androidComponents.onVariants { variant -> - val androidTest = (variant as? HasAndroidTest)?.androidTest ?: return@onVariants - val buildType = variant.buildType ?: return@onVariants - val flavorName = variant.productFlavors.joinToString("") { it.second } - val flavorPath = variant.productFlavors.joinToString("/") { it.second } - val archivesName = extensions.getByType(BasePluginExtension::class.java).archivesName.get() - - variant.outputs.forEach { output -> - val abiFilter = output.filters.firstOrNull { it.filterType == FilterConfiguration.FilterType.ABI } - val abiName = abiFilter?.identifier - - val appApkDirPath = if (flavorPath.isNotEmpty()) "$flavorPath/$buildType" else buildType - val appApkFileName = - buildString { - append(archivesName) - if (flavorName.isNotEmpty()) append("-$flavorName") - if (abiName != null) append("-$abiName") - append("-$buildType.apk") - } - val appApkPath = - layout.buildDirectory - .file("outputs/apk/$appApkDirPath/$appApkFileName") - .get() - .asFile.absolutePath - - val testApkDirPath = - if (flavorPath.isNotEmpty()) "androidTest/$flavorPath/$buildType" else "androidTest/$buildType" - val testApkFileName = - if (flavorName.isNotEmpty()) { - "$archivesName-$flavorName-$buildType-androidTest.apk" - } else { - "$archivesName-$buildType-androidTest.apk" - } - val testApkPath = - layout.buildDirectory - .file("outputs/apk/$testApkDirPath/$testApkFileName") - .get() - .asFile.absolutePath - - ext.variantApks.add( - VariantApkInfo( - variantName = variant.name, - appApkPath = appApkPath, - testApkPath = testApkPath, - abiName = abiName, - ), - ) - } - } - } - - pluginManager.withPlugin("com.android.library") { - val androidComponents = extensions.getByType(LibraryAndroidComponentsExtension::class.java) - val ext = extensions.findByType(FulladleModuleExtension::class.java) ?: return@withPlugin - androidComponents.onVariants { variant -> - val androidTest = (variant as? HasAndroidTest)?.androidTest ?: return@onVariants - val buildType = variant.buildType ?: return@onVariants - val flavorName = variant.productFlavors.joinToString("") { it.second } - val flavorPath = variant.productFlavors.joinToString("/") { it.second } - val archivesName = extensions.getByType(BasePluginExtension::class.java).archivesName.get() - - val testApkDirPath = - if (flavorPath.isNotEmpty()) "androidTest/$flavorPath/$buildType" else "androidTest/$buildType" - val testApkFileName = - if (flavorName.isNotEmpty()) { - "$archivesName-$flavorName-$buildType-androidTest.apk" - } else { - "$archivesName-$buildType-androidTest.apk" - } - val testApkPath = - layout.buildDirectory - .file("outputs/apk/$testApkDirPath/$testApkFileName") - .get() - .asFile.absolutePath - - ext.variantApks.add( - VariantApkInfo( - variantName = variant.name, - appApkPath = null, - testApkPath = testApkPath, - abiName = null, - ), - ) - } - } - } - - val fulladleConfigureTask = - root.tasks.register("configureFulladle") { - var modulesEnabled = false - // We first configure all app modules, then configure all library modules. - // We force this order because app modules are better candidates to become - // root level test/app APKs, since they produce app APKs. - // If no app module had tests or was enabled, we will choose a library module - // to become a root level module, in which case we will have to check if it - // has its debugApk set. - doLast { - // first configure all app modules - root.subprojects { - if (!hasAndroidTest) { - return@subprojects - } - modulesEnabled = true - if (isAndroidAppModule) { - configureModule(this, flankGradleExtension) - } - } - // then configure all library modules - root.subprojects { - if (!hasAndroidTest) { - return@subprojects - } - modulesEnabled = true - if (isAndroidLibraryModule) { - configureModule(this, flankGradleExtension) - } - } - - check(modulesEnabled) { - "All modules were disabled for testing in fulladleModuleConfig or the enabled modules had no tests.\n" + - "Either re-enable modules for testing or add modules with tests." - } - } - } - - root.tasks.withType(YamlConfigWriterTask::class.java).configureEach { - dependsOn(fulladleConfigureTask) - } - - root.afterEvaluate { - // TODO add other printYml tasks from other configs - root.tasks.named("printYml").configure { - dependsOn(fulladleConfigureTask) - } - } - } -} - -fun configureModule( - project: Project, - flankGradleExtension: FlankGradleExtension, -) = project.run { - val fulladleModuleExtension = extensions.findByType(FulladleModuleExtension::class.java) ?: return - if (!hasAndroidTest) { - return - } - - // Only configure the first test variant per module. - // Does anyone test more than one variant per module? - var addedTestsForModule = false - - for (variantInfo in fulladleModuleExtension.variantApks) { - if (addedTestsForModule) break - - if (!variantInfo.isExpectedVariantInModule(fulladleModuleExtension)) continue - - // Check ABI filter against the extension - if (flankGradleExtension.abi.isPresent && variantInfo.abiName != null && variantInfo.abiName != flankGradleExtension.abi.get()) continue - if (flankGradleExtension.abi.isPresent && variantInfo.abiName == null) { - // No ABI filter on this output - it's a match (universal) - } - - val yml = StringBuilder() - // If the debugApk isn't yet set, let's use this one. - if (!flankGradleExtension.debugApk.isPresent) { - if (project.isAndroidAppModule && variantInfo.appApkPath != null) { - // app modules produce app apks that we can consume - flankGradleExtension.debugApk.set(rootProject.provider { variantInfo.appApkPath }) - } else if (project.isAndroidLibraryModule) { - // library modules do not produce an app apk and we'll use the one specified in fulladleModuleConfig block - // we need library modules to specify the app apk to test against, even if it's a dummy one - check(fulladleModuleExtension.debugApk.isPresent && fulladleModuleExtension.debugApk.orNull != null) { - "Library module ${project.path} did not specify a debug apk. Library modules do not " + - "generate a debug apk and one needs to be specified in the fulladleModuleConfig block\n" + - "This is a required parameter in FTL which remains unused for library modules under test, " + - "and you can use a dummy apk here" - } - flankGradleExtension.debugApk.set(rootProject.provider { fulladleModuleExtension.debugApk.get() }) - } - } else { - // Otherwise, let's just add it to the list. - if (project.isAndroidAppModule && variantInfo.appApkPath != null) { - yml.appendLine("- app: ${variantInfo.appApkPath}") - } else if (project.isAndroidLibraryModule) { - // app apk is not required for library modules so only use if it's explicitly specified - if (fulladleModuleExtension.debugApk.orNull != null) { - yml.appendLine("- app: ${fulladleModuleExtension.debugApk.get()}") - } - } - } - - // If the instrumentation apk isn't yet set, let's use this one. - if (!flankGradleExtension.instrumentationApk.isPresent) { - flankGradleExtension.instrumentationApk.set(rootProject.provider { variantInfo.testApkPath }) - } else { - // Otherwise, let's just add it to the list. - if (yml.isBlank()) { - // The first item in the list needs to start with a ` - `. - yml.appendLine("- test: ${variantInfo.testApkPath}") - } else { - yml.appendLine(" test: ${variantInfo.testApkPath}") - } - } - - if (yml.isEmpty()) { - // this is the root module - // should not be added as additional test apk - overrideRootLevelConfigs(flankGradleExtension, fulladleModuleExtension) - } else { - yml.appendProperty(fulladleModuleExtension.maxTestShards, " max-test-shards") - yml.appendMapProperty( - fulladleModuleExtension.clientDetails, - " client-details", - ) { appendLine(" ${it.key}: ${it.value}") } - yml.appendMapProperty( - fulladleModuleExtension.environmentVariables, - " environment-variables", - ) { appendLine(" ${it.key}: ${it.value}") } - flankGradleExtension.additionalTestApks.add(yml.toString()) - } - addedTestsForModule = true - } -} - -val Project.isAndroidAppModule - get() = plugins.hasPlugin("com.android.application") -val Project.isAndroidLibraryModule - get() = plugins.hasPlugin("com.android.library") - -// returns false if the module explicitly disabled testing or if it simply had no tests -val Project.hasAndroidTest: Boolean - get() { - if (!(isAndroidLibraryModule || isAndroidAppModule)) { - return false - } - val fulladleModuleExtension = extensions.findByType(FulladleModuleExtension::class.java) ?: return false - if (!fulladleModuleExtension.enabled.get()) { - return false - } - if (!file("$projectDir/src/androidTest").exists()) { - println("Ignoring test variants in $path: No tests in $projectDir/src/androidTest") - return false - } - return true - } - -fun overrideRootLevelConfigs( - flankGradleExtension: FlankGradleExtension, - fulladleModuleExtension: FulladleModuleExtension, -) { - // if the root module overrode any value in its fulladleModuleConfig block - // then use those values instead - val debugApk = fulladleModuleExtension.debugApk.orNull - if (debugApk != null && debugApk.isNotEmpty()) { - flankGradleExtension.debugApk.set(fulladleModuleExtension.debugApk.get()) - } - val maxTestShards = fulladleModuleExtension.maxTestShards.orNull - if (maxTestShards != null && maxTestShards > 0) { - flankGradleExtension.maxTestShards.set(fulladleModuleExtension.maxTestShards.get()) - } - val clientDetails = fulladleModuleExtension.clientDetails.orNull - if (clientDetails != null && clientDetails.size != 0) { - flankGradleExtension.clientDetails.set(fulladleModuleExtension.clientDetails.get()) - } - val env = fulladleModuleExtension.environmentVariables.orNull - if (env != null && env.size != 0) { - flankGradleExtension.environmentVariables.set(fulladleModuleExtension.environmentVariables.get()) - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt deleted file mode 100644 index f07314f2..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ImportReportDelegate.kt +++ /dev/null @@ -1,102 +0,0 @@ -@file:Suppress("DEPRECATION") - -package com.osacky.flank.gradle - -import org.gradle.api.Project -import org.gradle.api.file.Directory -import org.gradle.api.file.RegularFile -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.TaskContainer -import org.gradle.api.tasks.TaskProvider -import com.gradle.develocity.agent.gradle.test.ImportJUnitXmlReports as DevelocityImportJUnitXmlReports -import com.gradle.develocity.agent.gradle.test.JUnitXmlDialect as DevelocityJUnitXmlDialect -import com.gradle.enterprise.gradleplugin.test.ImportJUnitXmlReports as GEImportJUnitXmlReports -import com.gradle.enterprise.gradleplugin.test.JUnitXmlDialect as GEJUnitXmlDialect - -fun canImportReport(): Boolean = JUnitXmlHandler.canImport() - -fun importReport( - project: Project, - flankTaskProvider: TaskProvider, -) { - val enableTestUploads = - project.providers - .gradleProperty("com.osacky.fladle.enableTestUploads") - .getOrElse("true") - .toBoolean() - if (enableTestUploads) { - val resultsProvider: Provider = - project.layout.buildDirectory - .dir("fladle") - .flatMap { fladleDir -> - val localResultsDirProvider: Provider = - fladleDir - .dir(flankTaskProvider.flatMap { task -> task.config.localResultsDir }) - - localResultsDirProvider.map { localResultsDir -> localResultsDir.file("JUnitReport.xml") } - } - JUnitXmlHandler.get()?.register( - project.tasks, - flankTaskProvider, - resultsProvider, - ) - } -} - -/** Abstraction over Develocity and GE impls of JUnitXml reporting. */ -sealed class JUnitXmlHandler { - abstract fun register( - tasks: TaskContainer, - flankTask: TaskProvider, - reportsFile: Provider, - ) - - companion object { - private fun canImport(name: String) = - try { - Class.forName(name) - true - } catch (e: ClassNotFoundException) { - false - } - - private val canImportDevelocity get() = canImport("com.gradle.develocity.agent.gradle.test.ImportJUnitXmlReports") - - private val canImportGE get() = canImport("com.gradle.enterprise.gradleplugin.test.ImportJUnitXmlReports") - - fun canImport() = canImportDevelocity || canImportGE - - fun get() = - if (canImportDevelocity) { - DevelocityJunitXmlHandler - } else if (canImportGE) { - GEJunitXmlHandler - } else { - null - } - } - - object DevelocityJunitXmlHandler : JUnitXmlHandler() { - override fun register( - tasks: TaskContainer, - flankTask: TaskProvider, - reportsFile: Provider, - ) { - DevelocityImportJUnitXmlReports.register(tasks, flankTask, DevelocityJUnitXmlDialect.ANDROID_FIREBASE).configure { - reports.setFrom(reportsFile) - } - } - } - - object GEJunitXmlHandler : JUnitXmlHandler() { - override fun register( - tasks: TaskContainer, - flankTask: TaskProvider, - reportsFile: Provider, - ) { - GEImportJUnitXmlReports.register(tasks, flankTask, GEJUnitXmlDialect.ANDROID_FIREBASE).configure { - reports.setFrom(reportsFile) - } - } - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ProjectExtensions.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/ProjectExtensions.kt deleted file mode 100644 index a9d8ae25..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/ProjectExtensions.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.file.Directory -import org.gradle.api.file.ProjectLayout -import org.gradle.api.provider.Provider - -internal val ProjectLayout.fladleDir: Provider - get() = buildDirectory.dir("fladle") diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RequiredDeviceKeyMissingException.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/RequiredDeviceKeyMissingException.kt deleted file mode 100644 index 7682930f..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RequiredDeviceKeyMissingException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.osacky.flank.gradle - -data class RequiredDeviceKeyMissingException( - val key: String, -) : Exception("Device should have '$key' key set to a value.") diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt deleted file mode 100644 index b47c0e74..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/RunFlankTask.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.DefaultTask -import org.gradle.work.DisableCachingByDefault -import javax.inject.Inject - -@DisableCachingByDefault( - because = "Flank executions are dependent on resources such as network connection and server and therefore cannot be cached.", -) -open class RunFlankTask - @Inject - constructor() : DefaultTask() { - init { - description = "Runs instrumentation tests using flank on firebase test lab." - group = FladlePluginDelegate.TASK_GROUP - } - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt deleted file mode 100644 index b26bcd5f..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/SanityConfigValidation.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import java.lang.IllegalArgumentException - -fun checkIfSanityAndValidateConfigs(config: FladleConfig) = - when (config) { - is FlankGradleExtension -> { - config.checkAndValidateConfig { option, name -> - "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo." - } - } - - is FladleConfigImpl -> { - config.checkAndValidateConfig(config.name) { option, name -> - "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. " + - "To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" - } - } - - else -> { - throw IllegalArgumentException("Unexpected configuration when validating parameters. Did not expect: $config.") - } - } - -private fun FladleConfig.checkAndValidateConfig( - name: String = "base", - message: (String, String) -> String, -) { - if (sanityRobo.get()) { - when { - roboDirectives.hasValue -> throw IllegalStateException(message("roboDirectives", name)) - roboScript.hasValue -> throw IllegalStateException(message("roboScript", name)) - instrumentationApk.hasValue -> throw IllegalStateException(message("instrumentationApk", name)) - additionalTestApks.hasValue -> throw IllegalStateException(message("additionalTestApks", name)) - } - } -} - -val Property.hasValue - get() = orNull.isNullOrBlank().not() - -private val ListProperty.hasValue - get() = getOrElse(emptyList()).isNotEmpty() diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/VariantApkInfo.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/VariantApkInfo.kt deleted file mode 100644 index b69db0fa..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/VariantApkInfo.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.osacky.flank.gradle - -data class VariantApkInfo( - val variantName: String, - val appApkPath: String?, - val testApkPath: String, - val abiName: String?, -) { - fun isExpectedVariantInModule(config: FulladleModuleExtension): Boolean = - !config.variant.isPresent || (config.variant.isPresent && variantName.contains(config.variant.get())) -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/Variants.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/Variants.kt deleted file mode 100644 index df732443..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/Variants.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.osacky.flank.gradle - -import com.android.build.api.variant.FilterConfiguration -import com.android.build.api.variant.Variant -import com.android.build.api.variant.VariantOutput - -/** - * Returns true if this [Variant] matches the variant specified in the [config]. - * - * If no variant is specified, all variants are considered a match. - */ -fun Variant.isExpectedVariant(config: FladleConfig) = - !config.variant.isPresent || (config.variant.isPresent && config.variant.get() == this.name) - -/** - * Returns true if this [VariantOutput] matches the ABI specified in the [config]. - * - * If the config does not specify an ABI, or if the config specifies an ABI but the [VariantOutput] - * is not filtered by ABI, it is considered a match. - */ -fun VariantOutput.isExpectedAbiOutput(config: FladleConfig): Boolean { - val abiFilters = filters.filter { it.filterType == FilterConfiguration.FilterType.ABI } - return !config.abi.isPresent || - abiFilters.isEmpty() || - abiFilters.any { it.identifier == config.abi.get() } -} - -/** - * Returns true if this [Variant] matches the variant specified in the [config]. - * - * If no variant is specified, all variants are considered a match. - */ -fun Variant.isExpectedVariantInModule(config: FulladleModuleExtension) = - !config.variant.isPresent || (config.variant.isPresent && this.name.contains(config.variant.get())) diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt deleted file mode 100644 index 1d3b09e6..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlConfigWriterTask.kt +++ /dev/null @@ -1,64 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.DefaultTask -import org.gradle.api.file.ProjectLayout -import org.gradle.api.file.RegularFile -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import org.gradle.work.DisableCachingByDefault -import java.util.Locale -import javax.inject.Inject - -@DisableCachingByDefault(because = "The task writes a small file from in memory properties and does not benefit from caching.") -open class YamlConfigWriterTask - @Inject - constructor( - @get:Nested val base: FlankGradleExtension, - @get:Nested val config: FladleConfig, - @get:Input val configName: String, - projectLayout: ProjectLayout, - objects: ObjectFactory, - ) : DefaultTask() { - private val yamlWriter = YamlWriter() - - private val fladleDir = - projectLayout.fladleDir.map { - if (configName == "") { - it - } else { - it.dir(configName.toLowerCase(Locale.ROOT)) - } - } - - @get:Input - val additionalTestApks: ListProperty = - objects - .listProperty(String::class.java) - .convention(config.additionalTestApks) - - @OutputFile - val fladleConfigFile: Provider = fladleDir.map { it.file("flank.yml") } - - @Internal - override fun getDescription(): String = "Writes a flank.yml file based on the current FlankGradleExtension configuration." - - @Internal - override fun getGroup(): String = FladlePluginDelegate.TASK_GROUP - - @TaskAction - fun writeFile() { - fladleDir.get().asFile.mkdirs() - val mergedConfig = - object : FladleConfig by config { - override val additionalTestApks: ListProperty - get() = this@YamlConfigWriterTask.additionalTestApks - } - fladleConfigFile.get().asFile.writeText(yamlWriter.createConfigProps(mergedConfig, base)) - } - } diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt deleted file mode 100644 index a80543bc..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlExtensions.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property - -fun StringBuilder.appendProperty( - prop: Property, - name: String, -) { - if (prop.isPresent) appendLine(" $name: ${prop.get()}") -} - -fun StringBuilder.appendMapProperty( - prop: MapProperty, - name: String, - custom: StringBuilder.(Map.Entry) -> Unit, -) { - if (prop.isPresentAndNotEmpty) { - appendLine(" $name:") - prop.get().forEach { custom(it) } - } -} - -fun StringBuilder.appendListProperty( - prop: ListProperty, - name: String, - custom: StringBuilder.(T) -> Unit, -) { - if (prop.isPresentAndNotEmpty) { - appendLine(" $name:") - prop.get().forEach { custom(it) } - } -} - -fun StringBuilder.appendAdditionalProperty(property: Property) { - if (property.isPresent) { - property - .get() - .split("\n") - .map { " $it" } - .forEach { appendLine(it) } - } -} - -val ListProperty.isPresentAndNotEmpty - get() = isPresent && get().isNotEmpty() - -val MapProperty.isPresentAndNotEmpty - get() = isPresent && get().isNotEmpty() diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt deleted file mode 100644 index d2083f64..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/YamlWriter.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.osacky.flank.gradle - -import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting - -internal class YamlWriter { - internal fun createConfigProps( - config: FladleConfig, - base: FlankGradleExtension, - ): String { - fun Boolean.toInt() = if (this) 1 else 0 - - if (base.projectId.orNull == null) { - check(base.serviceAccountCredentials.isPresent) { - "ServiceAccountCredentials in fladle extension not set." + - "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials" - } - } - check(base.debugApk.isPresent) { "debugApk must be specified" } - if (!config.sanityRobo.get()) { - val result = - config.instrumentationApk.isPresent.toInt() + config.roboScript.hasValue.toInt() + - config.roboDirectives.isPresentAndNotEmpty.toInt() - - check(result == 1) { - val prefix = - if (result > 1) { - "Only one of instrumentationApk file, roboScript file, and robo directives must be specified." - } else { - "Must specify either a instrumentationApk file or a roboScript file or a robo directive." - } - """ - $prefix - instrumentationApk=${config.instrumentationApk.orNull} - roboScript=${config.roboScript.orNull} - roboDirective=${config.roboDirectives.orNull} - """.trimIndent() - } - } - val additionalProperties = writeAdditionalProperties(config) - val flankProperties = writeFlankProperties(config) - - return buildString { - appendLine("gcloud:") - appendLine(" app: ${config.debugApk.get()}") - if (config.instrumentationApk.hasValue) { - appendLine(" test: ${config.instrumentationApk.get()}") - } - if (config.devices.isPresentAndNotEmpty) appendLine(createDeviceString(config.devices.get())) - appendLine(additionalProperties) - append(flankProperties) - } - } - - internal fun writeFlankProperties(config: FladleConfig): String = - buildString { - appendLine("flank:") - - // Fladle will fail in configuration phase if both maxTestShards and testShards are present - appendProperty(config.maxTestShards, name = "max-test-shards") - appendProperty(config.testShards, name = "max-test-shards") - - appendProperty(config.shardTime, name = "shard-time") - appendProperty(config.repeatTests, name = "num-test-runs") - appendProperty(config.smartFlankGcsPath, name = "smart-flank-gcs-path") - appendProperty(config.projectId, name = "project") - appendProperty(config.keepFilePath, name = "keep-file-path") - appendListProperty(config.filesToDownload, name = "files-to-download") { appendLine(" - $it") } - if (!config.sanityRobo.get()) { - appendListProperty( - config.additionalTestApks, - name = "additional-app-test-apks", - ) { appendLine(" $it") } - } - appendProperty(config.runTimeout, name = "run-timeout") - appendProperty(config.ignoreFailedTests, name = "ignore-failed-tests") - appendProperty(config.disableSharding, name = "disable-sharding") - appendProperty(config.smartFlankDisableUpload, name = "smart-flank-disable-upload") - appendProperty(config.localResultsDir, name = "local-result-dir") - appendListProperty( - config.testTargetsAlwaysRun, - name = "test-targets-always-run", - ) { appendLine(" - class $it") } - appendProperty(config.legacyJunitResult, name = "legacy-junit-result") - appendProperty(config.fullJunitResult, name = "full-junit-result") - appendProperty(config.outputStyle, name = "output-style") - appendProperty(config.defaultTestTime, name = "default-test-time") - appendProperty(config.defaultClassTestTime, name = "default-class-test-time") - appendProperty( - config.useAverageTestTimeForNewTests, - name = "use-average-test-time-for-new-tests", - ) - appendProperty(config.disableResultsUpload, name = "disable-results-upload") - appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { - appendLine(" - $it") - } - appendAdditionalProperty(config.additionalFlankOptions) - } - - internal fun writeAdditionalProperties(config: FladleConfig): String = - buildString { - appendProperty(config.useOrchestrator, name = "use-orchestrator") - appendProperty(config.autoGoogleLogin, name = "auto-google-login") - appendProperty(config.recordVideo, name = "record-video") - appendProperty(config.performanceMetrics, name = "performance-metrics") - appendProperty(config.testTimeout, name = "timeout") - if (config.async.isPresent && config.async.get()) { - appendProperty(config.async, name = "async") - } - appendProperty(config.resultsHistoryName, name = "results-history-name") - appendProperty(config.resultsBucket, name = "results-bucket") - appendMapProperty(config.environmentVariables, name = "environment-variables") { - appendLine(" ${it.key}: ${it.value}") - } - appendListProperty(config.testTargets, name = "test-targets") { appendLine(" - $it") } - appendListProperty(config.directoriesToPull, name = "directories-to-pull") { appendLine(" - $it") } - appendProperty(config.flakyTestAttempts, name = "num-flaky-test-attempts") - appendProperty(config.resultsDir, name = "results-dir") - appendProperty(config.testRunnerClass, name = "test-runner-class") - appendProperty(config.numUniformShards, name = "num-uniform-shards") - appendMapProperty(config.clientDetails, name = "client-details") { appendLine(" ${it.key}: ${it.value}") } - appendMapProperty(config.otherFiles, name = "other-files") { appendLine(" ${it.key}: ${it.value}") } - appendProperty(config.networkProfile, name = "network-profile") - if (!config.sanityRobo.get()) { - if (config.roboScript.hasValue) { - appendProperty(config.roboScript, name = "robo-script") - } else { - appendListProperty(config.roboDirectives, name = "robo-directives") { - val value = - it - .getOrElse(2) { "" } - .let { stringValue -> if (stringValue.isBlank()) "\"\"" else stringValue } - appendLine(" ${it[0]}:${it[1]}: $value") - } - } - } - appendListProperty(config.additionalApks, name = "additional-apks") { appendLine(" - $it") } - appendProperty(config.grantPermissions, name = "grant-permissions") - appendProperty(config.type, name = "type") - appendListProperty(config.scenarioLabels, name = "scenario-labels") { appendLine(" - $it") } - appendListProperty(config.scenarioNumbers, name = "scenario-numbers") { appendLine(" - $it") } - appendListProperty(config.obbFiles, name = "obb-files") { appendLine(" - $it") } - appendListProperty(config.obbNames, name = "obb-names") { appendLine(" - $it") } - appendListProperty(config.testTargetsForShard, name = "test-targets-for-shard") { appendLine(" - $it") } - appendProperty(config.failFast, name = "fail-fast") - appendAdditionalProperty(config.additionalGcloudOptions) - } - - @VisibleForTesting - internal fun createDeviceString(devices: List>): String = - buildString { - appendLine(" device:") - for (device in devices) { - if (device["model"] == null) throw RequiredDeviceKeyMissingException("model") - val model = device["model"] - if (device["version"] == null) throw RequiredDeviceKeyMissingException("version") - val version = device["version"] - val orientation = device["orientation"] - val locale = device["locale"] - appendLine(" - model: $model") - appendLine(" version: $version") - orientation?.let { - appendLine(" orientation: $it") - } - locale?.let { - appendLine(" locale: $it") - } - } - } -} diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/SinceFlank.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/SinceFlank.kt deleted file mode 100644 index f35e6222..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/SinceFlank.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.osacky.flank.gradle.validation - -annotation class SinceFlank( - val version: String, - val hasDefaultValue: Boolean = false, -) diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt deleted file mode 100644 index c60daf31..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateExclusionUsage.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.osacky.flank.gradle.validation - -import com.osacky.flank.gradle.FladleConfig - -fun checkForExclusionUsage(config: FladleConfig) { - val usersProperties = - config - .getPresentProperties() - .map { it.name } - - exclusions.forEach { - if (usersProperties.contains(it.first) && usersProperties.contains(it.second)) { - throw IllegalStateException("Options ${it.first} and ${it.second} cannot be used together. Choose one of them.") - } - } -} - -private val exclusions = - listOf( - "testShards" to "maxTestShards", - "testShards" to "numUniformShards", - "maxTestShards" to "numUniformShards", - ) diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt deleted file mode 100644 index 0a48b133..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/ValidateOptions.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.osacky.flank.gradle.validation - -import com.osacky.flank.gradle.FladleConfig -import com.osacky.flank.gradle.FlankGradleExtension.Companion.FLANK_VERSION -import kotlin.reflect.full.memberProperties - -fun validateOptionsUsed( - config: FladleConfig, - flank: String, -) { - // if using snapshot version default to the latest known version of flank for validation checks - val configFlankVersion = if (flank.toLowerCase().endsWith("snapshot")) FLANK_VERSION.toVersion() else flank.toVersion() - - config - .getPresentProperties() - .mapNotNull { property -> properties[property.name]?.let { property to it } } - .forEach { (property, version) -> - if (version > configFlankVersion) { - throw IllegalStateException( - "Option ${property.name} is available since flank $version, which is higher than used $configFlankVersion", - ) - } - } -} - -private fun String.toVersion() = VersionNumber.parse(this) - -private val properties = - FladleConfig::class - .memberProperties - .asSequence() - .map { it to it.getter.annotations } - // we also need to exclude properties with default values to preserve backward compatibility - // to be fixed - .filter { it.second.any { annotation -> annotation is SinceFlank && !annotation.hasDefaultValue } } - .map { - it.first.name to - it.second - .filterIsInstance() - .first() - .version - .toVersion() - }.toMap() diff --git a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/VersionNumber.kt b/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/VersionNumber.kt deleted file mode 100644 index 90d5d9cb..00000000 --- a/fladle-plugin/src/main/java/com/osacky/flank/gradle/validation/VersionNumber.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.osacky.flank.gradle.validation - -/* -* Copyright 2012 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 -* -* http://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. -*/ - -/** - * Copied and trimmed from [org.gradle.util.VersionNumber]. - */ -class VersionNumber private constructor( - private val major: Int, - private val minor: Int, - private val micro: Int, - private val patch: Int, - private val qualifier: String?, - private val scheme: AbstractScheme, -) : Comparable { - override fun compareTo(other: VersionNumber): Int { - if (major != other.major) { - return major - other.major - } - if (minor != other.minor) { - return minor - other.minor - } - if (micro != other.micro) { - return micro - other.micro - } - if (patch != other.patch) { - return patch - other.patch - } - return qualifier - .orEmpty() - .lowercase() - .compareTo(other.qualifier.orEmpty().lowercase()) - } - - override fun equals(other: Any?): Boolean = other is VersionNumber && compareTo(other) == 0 - - override fun hashCode(): Int { - var result = major - result = 31 * result + minor - result = 31 * result + micro - result = 31 * result + patch - result = 31 * result + qualifier.hashCode() - return result - } - - override fun toString(): String = scheme.format(this) - - /** - * Returns the version number scheme. - */ - interface Scheme { - fun parse(versionString: String): VersionNumber - - fun format(versionNumber: VersionNumber): String - } - - private abstract class AbstractScheme protected constructor( - val depth: Int, - ) : Scheme { - override fun parse(versionString: String): VersionNumber { - if (versionString.isEmpty()) { - return UNKNOWN - } - val scanner = Scanner(versionString) - - if (!scanner.hasDigit()) { - return UNKNOWN - } - var minor = 0 - var micro = 0 - var patch = 0 - val major = scanner.scanDigit() - if (scanner.isSeparatorAndDigit('.')) { - scanner.skipSeparator() - minor = scanner.scanDigit() - if (scanner.isSeparatorAndDigit('.')) { - scanner.skipSeparator() - micro = scanner.scanDigit() - if (depth > 3 && scanner.isSeparatorAndDigit('.', '_')) { - scanner.skipSeparator() - patch = scanner.scanDigit() - } - } - } - - if (scanner.isEnd) { - return VersionNumber(major, minor, micro, patch, null, this) - } - - if (scanner.isQualifier) { - scanner.skipSeparator() - return VersionNumber(major, minor, micro, patch, scanner.remainder(), this) - } - - return UNKNOWN - } - - private class Scanner( - val str: String, - ) { - var pos: Int = 0 - - fun hasDigit(): Boolean = pos < str.length && Character.isDigit(str.get(pos)) - - fun isSeparatorAndDigit(vararg separators: Char): Boolean = - pos < str.length - 1 && oneOf(*separators) && Character.isDigit(str.get(pos + 1)) - - fun oneOf(vararg separators: Char): Boolean { - val current = str.get(pos) - for (separator in separators) { - if (current == separator) { - return true - } - } - return false - } - - val isQualifier: Boolean - get() = pos < str.length - 1 && oneOf('.', '-') - - fun scanDigit(): Int { - val start = pos - while (hasDigit()) { - pos++ - } - return str.substring(start, pos).toInt() - } - - val isEnd: Boolean - get() = pos == str.length - - fun skipSeparator() { - pos++ - } - - fun remainder(): String? = if (pos == str.length) null else str.substring(pos) - } - } - - private class DefaultScheme : AbstractScheme(3) { - override fun format(versionNumber: VersionNumber): String = - String.format( - VERSION_TEMPLATE, - versionNumber.major, - versionNumber.minor, - versionNumber.micro, - if (versionNumber.qualifier == null) "" else "-" + versionNumber.qualifier, - ) - - companion object { - private const val VERSION_TEMPLATE = "%d.%d.%d%s" - } - } - - companion object { - private val DEFAULT_SCHEME = DefaultScheme() - val UNKNOWN: VersionNumber = version(0) - - @JvmOverloads - fun version( - major: Int, - minor: Int = 0, - ): VersionNumber = - VersionNumber( - major = major, - minor = minor, - micro = 0, - patch = 0, - qualifier = null, - scheme = DEFAULT_SCHEME, - ) - - fun parse(versionString: String): VersionNumber = DEFAULT_SCHEME.parse(versionString) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt deleted file mode 100644 index dcb28cd3..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/MultipleConfigsTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.osacky.flank.gradle - -import com.google.common.truth.Truth.assertThat -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class MultipleConfigsTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Test - fun checkCanPrintSecondConfig() { - val file = testProjectRoot.newFile("build.gradle") - file.writeText( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "instrument.apk" - | - | testTargets.set(project.provider { ['default'] }) - | localResultsDir = 'defaultDir' - | configs { - | orange { - | testTargets.set(project.provider { ['override'] }) - | localResultsDir.set('overrideDir') - | } - | } - |} - """.trimMargin(), - ) - testProjectRoot.newFile("flank-gradle-service.json").writeText("{}") - - val result = - GradleRunner - .create() - .withPluginClasspath() - .withArguments("writeConfigPropsOrange", "--stacktrace") - .forwardOutput() - .withProjectDir(testProjectRoot.root) - .build() - - assertThat(result.output).contains("SUCCESS") - - val writtenYmlFile = testProjectRoot.root.resolve("build/fladle/orange/flank.yml") - assertThat(writtenYmlFile.readText()).contains( - """ - |gcloud: - | app: foo.apk - | test: instrument.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | test-targets: - | - override - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | local-result-dir: overrideDir - """.trimMargin(), - ) - - val regularConfig = - GradleRunner - .create() - .withPluginClasspath() - .withArguments("writeConfigProps") - .forwardOutput() - .withProjectDir(testProjectRoot.root) - .build() - - assertThat(regularConfig.output).contains("SUCCESS") - - val writtenBaseYml = testProjectRoot.root.resolve("build/fladle/flank.yml") - assertThat(writtenBaseYml.readText()).contains( - """ - |gcloud: - | app: foo.apk - | test: instrument.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | test-targets: - | - default - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | local-result-dir: defaultDir - """.trimMargin(), - ) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt deleted file mode 100644 index a7a66477..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/YamlWriterTest.kt +++ /dev/null @@ -1,1549 +0,0 @@ -package com.osacky.flank.gradle - -import com.google.common.truth.Truth.assertThat -import org.gradle.api.Project -import org.gradle.testfixtures.ProjectBuilder -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Assert.fail -import org.junit.Before -import org.junit.Test - -class YamlWriterTest { - private val yamlWriter = YamlWriter() - - private lateinit var project: Project - - @Before - fun setup() { - project = ProjectBuilder.builder().withName("project").build() - } - - @Test - fun testWriteSingleDevice() { - val devices = - listOf( - mapOf("model" to "SmallPhone.arm", "version" to "28"), - ) - val deviceString = yamlWriter.createDeviceString(devices) - val expected = - """ - | device: - | - model: SmallPhone.arm - | version: 28 - | - """.trimMargin() - assertThat(deviceString).isEqualTo(expected) - } - - @Test - fun testWriteTwoDevices() { - val devices = - listOf( - mapOf("model" to "SmallPhone.arm", "version" to "28"), - mapOf("model" to "MediumPhone.arm", "version" to "33"), - ) - val deviceString = yamlWriter.createDeviceString(devices) - val expected = - """ - | device: - | - model: SmallPhone.arm - | version: 28 - | - model: MediumPhone.arm - | version: 33 - | - """.trimMargin() - assertThat(deviceString).isEqualTo(expected) - } - - @Test - fun testWriteTwoCustomDevices() { - val devices = - listOf( - mapOf("model" to "SmallPhone.arm", "version" to "33", "orientation" to "portrait"), - mapOf("model" to "MediumPhone.arm", "orientation" to "landscape", "version" to "28"), - ) - val deviceString = yamlWriter.createDeviceString(devices) - val expected = - """ - | device: - | - model: SmallPhone.arm - | version: 33 - | orientation: portrait - | - model: MediumPhone.arm - | version: 28 - | orientation: landscape - | - """.trimMargin() - assertThat(deviceString).isEqualTo(expected) - } - - @Test - fun testWriteTwoCustomDevicesWithLocale() { - val devices = - listOf( - mapOf("model" to "SmallPhone.arm", "version" to "33", "orientation" to "portrait", "locale" to "en"), - mapOf("model" to "MediumPhone.arm", "orientation" to "landscape", "locale" to "es_ES", "version" to "28"), - ) - val deviceString = yamlWriter.createDeviceString(devices) - val expected = - """ - | device: - | - model: SmallPhone.arm - | version: 33 - | orientation: portrait - | locale: en - | - model: MediumPhone.arm - | version: 28 - | orientation: landscape - | locale: es_ES - | - """.trimMargin() - assertThat(deviceString).isEqualTo(expected) - } - - @Test - fun testThrowsExceptionWhenMissingModelKeyInDevice() { - val devices = - listOf( - mapOf("version" to "23", "orientation" to "portrait", "locale" to "en"), - ) - try { - yamlWriter.createDeviceString(devices) - fail() - } catch (expected: RequiredDeviceKeyMissingException) { - assertEquals("Device should have 'model' key set to a value.", expected.message) - } - } - - @Test - fun testThrowsExceptionWhenMissingVersionKeyInDevice() { - val devices = - listOf( - mapOf("model" to "SmallPhone.arm", "orientation" to "portrait", "locale" to "en"), - ) - try { - yamlWriter.createDeviceString(devices) - fail() - } catch (expected: RequiredDeviceKeyMissingException) { - assertEquals("Device should have 'version' key set to a value.", expected.message) - } - } - - @Test - fun verifyMissingServiceThrowsError() { - val extension = FlankGradleExtension(project.objects) - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertEquals( - "ServiceAccountCredentials in fladle extension not set." + - "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials", - expected.message, - ) - } - } - - @Test - fun verifyMissingServiceDoesntThrowErrorIfProjectIdSet() { - val extension = - emptyExtension { - projectId.set("set") - debugApk.set("path") - instrumentationApk.set("instrument") - } - val yaml = yamlWriter.createConfigProps(extension, extension) - assertThat(yaml).isEqualTo( - """ - gcloud: - app: path - test: instrument - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - project: set - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n', - ) - } - - @Test - fun verifyDebugApkThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertEquals("debugApk must be specified", expected.message) - } - } - - @Test - fun verifyNoInstrumentationApkThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertThat(expected).hasMessageThat().isEqualTo( - """ - Must specify either a instrumentationApk file or a roboScript file or a robo directive. - instrumentationApk=null - roboScript=null - roboDirective=[] - """.trimIndent(), - ) - } - } - - @Test - fun verifyInstrumentationApkAndRoboscriptThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboScript.set("foo") - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertThat(expected).hasMessageThat().isEqualTo( - """ - Only one of instrumentationApk file, roboScript file, and robo directives must be specified. - instrumentationApk=build/test/*.apk - roboScript=foo - roboDirective=[] - """.trimIndent(), - ) - } - } - - @Test - fun verifyInstrumentationApkAndRobodirectivesThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboDirectives.add(listOf("click", "resource_id")) - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertThat(expected).hasMessageThat().isEqualTo( - """ - Only one of instrumentationApk file, roboScript file, and robo directives must be specified. - instrumentationApk=build/test/*.apk - roboScript=null - roboDirective=[[click, resource_id]] - """.trimIndent(), - ) - } - } - - @Test - fun verifyRoboscriptAndRobodirectivesThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboScript.set("foo") - roboDirectives.add(listOf("click", "resource_id")) - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertThat(expected).hasMessageThat().isEqualTo( - """ - Only one of instrumentationApk file, roboScript file, and robo directives must be specified. - instrumentationApk=null - roboScript=foo - roboDirective=[[click, resource_id]] - """.trimIndent(), - ) - } - } - - @Test - fun verifyInstrumentationApkAndRoboscriptAndRobodirectivesThrowsError() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - instrumentationApk.set("build/test/*.apk") - roboScript.set("foo") - roboDirectives.add(listOf("click", "resource_id")) - } - try { - yamlWriter.createConfigProps(extension, extension) - fail() - } catch (expected: IllegalStateException) { - assertThat(expected).hasMessageThat().isEqualTo( - """ - Only one of instrumentationApk file, roboScript file, and robo directives must be specified. - instrumentationApk=build/test/*.apk - roboScript=foo - roboDirective=[[click, resource_id]] - """.trimIndent(), - ) - } - } - - @Test - fun verifyOnlyWithRoboscriptWorks() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboScript.set("foo") - } - val configProps = yamlWriter.createConfigProps(extension, extension) - assertThat(configProps).isEqualTo( - """ - gcloud: - app: path - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - robo-script: foo - - flank: - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n', - ) - } - - @Test - fun verifyOnlyWithRobodirectivesWorks() { - val extension = - emptyExtension { - serviceAccountCredentials.set(project.layout.projectDirectory.file("fake.json")) - debugApk.set("path") - roboDirectives.add(listOf("click", "resource_id")) - } - val configProps = yamlWriter.createConfigProps(extension, extension) - assertThat(configProps).isEqualTo( - """ - gcloud: - app: path - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - robo-directives: - click:resource_id: "" - - flank: - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n', - ) - } - - @Test - fun writeNoTestShards() { - val extension = - emptyExtension { - } - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeProjectIdOption() { - val extension = - emptyExtension { - projectId.set("foo") - } - - assertEquals( - "flank:\n" + - " project: foo\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeTestShardOption() { - val extension = - emptyExtension { - testShards.set(5) - } - - assertEquals( - "flank:\n" + - " max-test-shards: 5\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeShardTimeOption() { - val extension = - emptyExtension { - shardTime.set(120) - } - - assertEquals( - "flank:\n" + - " shard-time: 120\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeNoTestRepeats() { - val extension = emptyExtension {} - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeTestRepeats() { - val extension = - emptyExtension { - repeatTests.set(5) - } - - assertEquals( - "flank:\n" + - " num-test-runs: 5\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeTestShardAndRepeatOption() { - val extension = - emptyExtension { - testShards.set(5) - repeatTests.set(2) - } - - assertEquals( - "flank:\n" + - " max-test-shards: 5\n" + - " num-test-runs: 2\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeResultsHistoryName() { - val extension = - emptyExtension { - resultsHistoryName.set("androidtest") - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " results-history-name: androidtest\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeResultsBucket() { - val extension = - emptyExtension { - resultsBucket.set("fake-project.appspot.com") - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " results-bucket: fake-project.appspot.com\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeResultsDir() { - val extension = - emptyExtension { - resultsDir.set("resultsGoHere") - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " num-flaky-test-attempts: 0\n" + - " results-dir: resultsGoHere\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeTestTargetsAndResultsHistoryName() { - val extension = - emptyExtension { - resultsHistoryName.set("androidtest") - testTargets.set( - project.provider { - listOf("class com.example.Foo") - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " results-history-name: androidtest\n" + - " test-targets:\n" + - " - class com.example.Foo\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeNoTestTargets() { - val extension = emptyExtension {} - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeSingleTestTargets() { - val extension = - emptyExtension { - testTargets.set( - project.provider { - listOf("class com.example.Foo#testThing") - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " test-targets:\n" + - " - class com.example.Foo#testThing\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeMultipleTestTargets() { - val extension = - emptyExtension { - testTargets.set( - project.provider { - listOf("class com.example.Foo#testThing", "class com.example.Foo#testThing2") - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " test-targets:\n" + - " - class com.example.Foo#testThing\n" + - " - class com.example.Foo#testThing2\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeSmartFlankGcsPath() { - val extension = - emptyExtension { - smartFlankGcsPath.set("gs://test/fakepath.xml") - } - - assertEquals( - "flank:\n" + - " smart-flank-gcs-path: gs://test/fakepath.xml\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeNoDirectoriesToPull() { - val extension = - emptyExtension { - directoriesToPull.set( - project.provider { - emptyList() - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeSingleDirectoriesToPull() { - val extension = - emptyExtension { - directoriesToPull.set( - project.provider { - listOf("/sdcard/screenshots") - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " directories-to-pull:\n" + - " - /sdcard/screenshots\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeMultipleDirectoriesToPull() { - val extension = - emptyExtension { - directoriesToPull.set( - project.provider { - listOf("/sdcard/screenshots", "/sdcard/reports") - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " directories-to-pull:\n" + - " - /sdcard/screenshots\n" + - " - /sdcard/reports\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeNoFilesToDownload() { - val extension = - emptyExtension { - filesToDownload.set( - project.provider { - emptyList() - }, - ) - } - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeSingleFilesToDownload() { - val extension = - emptyExtension { - filesToDownload.set( - project.provider { - listOf(".*/screenshots/.*") - }, - ) - } - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " files-to-download:\n" + - " - .*/screenshots/.*\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeMultipleFilesToDownload() { - val extension = - emptyExtension { - filesToDownload.set( - project.provider { - listOf(".*/screenshots/.*", ".*/reports/.*") - }, - ) - } - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " files-to-download:\n" + - " - .*/screenshots/.*\n" + - " - .*/reports/.*\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeSingleEnvironmentVariables() { - val extension = - emptyExtension { - environmentVariables.set( - project.provider { - mapOf( - "listener" to "com.osacky.flank.sample.Listener", - ) - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " environment-variables:\n" + - " listener: com.osacky.flank.sample.Listener\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeMultipleEnvironmentVariables() { - val extension = - emptyExtension { - environmentVariables.set( - project.provider { - mapOf( - "clearPackageData" to "true", - "listener" to "com.osacky.flank.sample.Listener", - ) - }, - ) - } - - assertEquals( - " use-orchestrator: false\n" + - " auto-google-login: false\n" + - " record-video: true\n" + - " performance-metrics: true\n" + - " timeout: 15m\n" + - " environment-variables:\n" + - " clearPackageData: true\n" + - " listener: com.osacky.flank.sample.Listener\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeDefaultProperties() { - val extension = - emptyExtension { - useOrchestrator.set(true) - autoGoogleLogin.set(true) - recordVideo.set(false) - performanceMetrics.set(false) - testTimeout.set("45m") - } - - assertEquals( - " use-orchestrator: true\n" + - " auto-google-login: true\n" + - " record-video: false\n" + - " performance-metrics: false\n" + - " timeout: 45m\n" + - " num-flaky-test-attempts: 0\n", - yamlWriter.writeAdditionalProperties(extension), - ) - } - - @Test - fun writeNoKeepFilePath() { - val extension = emptyExtension() - - assertEquals( - "flank:\n" + - " keep-file-path: false\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - yamlWriter.writeFlankProperties(extension), - ) - } - - @Test - fun writeKeepFilePath() { - val extension = - emptyExtension { - keepFilePath.set(true) - } - - assertThat(yamlWriter.writeFlankProperties(extension)) - .isEqualTo( - "flank:\n" + - " keep-file-path: true\n" + - " ignore-failed-tests: false\n" + - " disable-sharding: false\n" + - " smart-flank-disable-upload: false\n" + - " legacy-junit-result: false\n" + - " full-junit-result: false\n" + - " output-style: single\n", - ) - } - - @Test - fun writeAdditionalTestApks() { - val extension = - emptyExtension { - debugApk.set("../orange/build/output/app.apk") - instrumentationApk.set("../orange/build/output/app-test.apk") - additionalTestApks.set( - project.provider { - listOf( - "- app: ../orange/build/output/app.apk", - " test: ../orange/build/output/app-test2.apk", - "- app: ../bob/build/output/app.apk", - " test: ../bob/build/output/app-test.apk", - "- test: ../bob/build/output/app-test2.apk", - "- test: ../bob/build/output/app-test3.apk", - ) - }, - ) - } - - assertThat( - yamlWriter.writeFlankProperties(extension), - ).isEqualTo( - """ - flank: - keep-file-path: false - additional-app-test-apks: - - app: ../orange/build/output/app.apk - test: ../orange/build/output/app-test2.apk - - app: ../bob/build/output/app.apk - test: ../bob/build/output/app-test.apk - - test: ../bob/build/output/app-test2.apk - - test: ../bob/build/output/app-test3.apk - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() + '\n', - ) - } - - @Test - fun verifyDefaultValues() { - val defaultFlankProperties = emptyExtension().toFlankProperties() - val defaultAdditionalProperties = emptyExtension().toAdditionalProperties().trimIndent() - - val expectedFlank = - """ - flank: - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent() - - val expectedAdditional = - """ - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - """.trimIndent() - - assertEquals(expectedFlank, defaultFlankProperties) - assertEquals(expectedAdditional, defaultAdditionalProperties) - } - - @Test - fun writeRunTimeout() { - val extension = - emptyExtension { - runTimeout.set("20m") - } - - assertTrue(yamlWriter.writeFlankProperties(extension).contains(" run-timeout: 20m")) - } - - @Test - fun writeIgnoreFailedTests() { - val properties = - emptyExtension { - ignoreFailedTests.set(true) - }.toFlankProperties() - - assertTrue(properties.contains(" ignore-failed-tests: true")) - } - - @Test - fun writeDisableSharding() { - val properties = - emptyExtension { - disableSharding.set(true) - }.toFlankProperties() - - assertTrue(properties.contains(" disable-sharding: true")) - } - - @Test - fun writeSmartFlankDisableUpload() { - val properties = - emptyExtension { - smartFlankDisableUpload.set(true) - }.toFlankProperties() - - assertTrue(properties.contains(" smart-flank-disable-upload: true")) - } - - @Test - fun writeTestRunnerClass() { - val properties = - emptyExtension { - testRunnerClass.set("any.class.Runner") - }.toAdditionalProperties() - - assertTrue(properties.contains(" test-runner-class: any.class.Runner")) - } - - @Test - fun writeLocalResultsDir() { - val properties = - emptyExtension { - localResultsDir.set("~/my/results/dir") - }.toFlankProperties() - - assertTrue(properties.contains(" local-result-dir: ~/my/results/dir")) - } - - @Test - fun writeNumUniformShards() { - val properties = - emptyExtension { - numUniformShards.set(20) - }.toAdditionalProperties() - - assertTrue(properties.contains(" num-uniform-shards: 20")) - } - - @Test - fun writeOutputStyle() { - val properties = - emptyExtension { - outputStyle.set("anyString") - }.toFlankProperties() - - assertTrue(properties.contains(" output-style: anyString")) - } - - @Test - fun missingOutputStyle() { - val properties = emptyExtension().toFlankProperties() - - assertTrue(properties.contains(" output-style: single")) - } - - @Test - fun writeLegacyJunitResult() { - val properties = - emptyExtension { - legacyJunitResult.set(true) - }.toFlankProperties() - - assertTrue(properties.contains(" legacy-junit-result: true")) - } - - @Test - fun missingLegacyJunitResult() { - val properties = emptyExtension().toFlankProperties() - - assertTrue(properties.contains(" legacy-junit-result: false")) - } - - @Test - fun writeFullJunitResult() { - val properties = - emptyExtension { - fullJunitResult.set(true) - }.toFlankProperties() - - assertTrue(properties.contains(" full-junit-result: true")) - } - - @Test - fun writeAsyncFlag() { - val properties = - emptyExtension { - async.set(true) - }.toAdditionalProperties() - assertTrue(properties.contains(" async: true")) - } - - @Test - fun missingFullJunitResult() { - val properties = emptyExtension().toFlankProperties() - - assertTrue(properties.contains(" full-junit-result: false")) - } - - @Test - fun writeClientDetails() { - val properties = - emptyExtension { - clientDetails.set( - project.provider { - mapOf( - "anyDetail1" to "anyValue1", - "anyDetail2" to "anyValue2", - ) - }, - ) - }.toAdditionalProperties() - - assertTrue( - properties.contains( - """ - | client-details: - | anyDetail1: anyValue1 - | anyDetail2: anyValue2 - """.trimMargin(), - ), - ) - } - - @Test - fun writeTestTargetsAlwaysRun() { - val properties = - emptyExtension { - testTargetsAlwaysRun.set( - project.provider { - listOf( - "com.example.FirstTests#test1", - "com.example.FirstTests#test2", - "com.example.FirstTests#test3", - ) - }, - ) - }.toFlankProperties() - - assertTrue( - properties.contains( - """ - | test-targets-always-run: - | - class com.example.FirstTests#test1 - | - class com.example.FirstTests#test2 - | - class com.example.FirstTests#test3 - """.trimMargin(), - ), - ) - } - - @Test - fun writeOtherFiles() { - val properties = - emptyExtension { - otherFiles.set( - project.provider { - mapOf( - "/example/path/test1" to "anyfile.txt", - "/example/path/test2" to "anyfile2.txt", - ) - }, - ) - }.toAdditionalProperties() - - assertTrue( - properties.contains( - """ - | other-files: - | /example/path/test1: anyfile.txt - | /example/path/test2: anyfile2.txt - """.trimMargin(), - ), - ) - } - - @Test - fun writeNetworkProfile() { - val properties = - emptyExtension { - networkProfile.set("LTE") - }.toAdditionalProperties() - - assertTrue(properties.contains(" network-profile: LTE")) - } - - @Test - fun writeRoboScript() { - val properties = - emptyExtension { - roboScript.set("~/my/dir/with/script.json") - }.toAdditionalProperties() - - assertTrue(properties.contains(" robo-script: ~/my/dir/with/script.json")) - } - - @Test - fun writeRoboDirectives() { - val properties = - emptyExtension { - roboDirectives.set( - project.provider { - listOf( - listOf("click", "button3"), - listOf("ignore", "button1", ""), - listOf("text", "field1", "my common text"), - ) - }, - ) - }.toAdditionalProperties() - - assertTrue( - properties.contains( - """ - | robo-directives: - | click:button3: "" - | ignore:button1: "" - | text:field1: my common text - """.trimMargin(), - ), - ) - } - - @Test - fun writeDefaultTestTime() { - val properties = - emptyExtension { - defaultTestTime.set(240.5) - }.toFlankProperties() - - assertThat(properties).contains("default-test-time: 240.5") - } - - @Test - fun writeDefaultClassTestTime() { - val properties = - emptyExtension { - defaultClassTestTime.set(681.8) - }.toFlankProperties() - - assertThat(properties).contains("default-class-test-time: 681.8") - } - - @Test - fun writeUseAverageTestTimeForNewTests() { - val properties = - emptyExtension { - useAverageTestTimeForNewTests.set(true) - }.toFlankProperties() - - assertThat(properties).contains("use-average-test-time-for-new-tests: true") - } - - @Test - fun writeAdditionalApks() { - val properties = - emptyExtension { - additionalApks.set( - project.provider { - listOf("gs://path/to/app1.apk", "localPath/to/app2.apk") - }, - ) - }.toAdditionalProperties() - - assertThat(properties).contains( - """ - | additional-apks: - | - gs://path/to/app1.apk - | - localPath/to/app2.apk - """.trimMargin(), - ) - } - - @Test - fun writeDisableResultsUpload() { - val properties = - emptyExtension { - disableResultsUpload.set(true) - }.toFlankProperties() - - assertThat(properties).contains("disable-results-upload: true") - } - - @Test - fun writeTestTargetsForShard() { - val properties = - emptyExtension { - testTargetsForShard.addAll( - "class com.example.test_app.bar.BarInstrumentedTest", - "class com.example.test_app.foo.FooInstrumentedTest", - ) - }.toFlankProperties() - - assertThat(properties).contains( - """ - | test-targets-for-shard: - | - class com.example.test_app.bar.BarInstrumentedTest - | - class com.example.test_app.foo.FooInstrumentedTest - """.trimMargin(), - ) - } - - @Test - fun writeGrantPermissions() { - val properties = - emptyExtension { - grantPermissions.set("none") - }.toAdditionalProperties() - - assertThat(properties).contains("grant-permissions: none") - } - - @Test - fun writeType() { - val properties = - emptyExtension { - type.set("game-loop") - }.toAdditionalProperties() - - assertThat(properties).contains("type: game-loop") - } - - @Test - fun writeScenarioLabels() { - val properties = - emptyExtension { - scenarioLabels.set( - project.provider { - listOf("label1", "label2") - }, - ) - }.toAdditionalProperties() - - assertThat(properties).contains( - """ - | scenario-labels: - | - label1 - | - label2 - """.trimMargin(), - ) - } - - @Test - fun writeScenarioNumbers() { - val properties = - emptyExtension { - scenarioNumbers.set( - project.provider { - listOf(1, 123, 543) - }, - ) - }.toAdditionalProperties() - - assertThat(properties).contains( - """ - | scenario-numbers: - | - 1 - | - 123 - | - 543 - """.trimMargin(), - ) - } - - @Test - fun writeObbFiles() { - val properties = - emptyExtension { - obbFiles.set( - project.provider { - listOf( - "local/file/path/test1.obb", - "local/file/path/test2.obb", - ) - }, - ) - }.toAdditionalProperties() - - assertThat(properties).contains( - """ - | obb-files: - | - local/file/path/test1.obb - | - local/file/path/test2.obb - """.trimMargin(), - ) - } - - @Test - fun writeObbNames() { - val properties = - emptyExtension { - obbNames.set( - project.provider { - listOf( - "patch.0300110.com.example.android.obb", - "patch.0300111.com.example.android.obb", - ) - }, - ) - }.toAdditionalProperties() - - assertThat(properties).contains( - """ - | obb-names: - | - patch.0300110.com.example.android.obb - | - patch.0300111.com.example.android.obb - """.trimMargin(), - ) - } - - @Test - fun writeFailFast() { - val properties = - emptyExtension { - failFast.set(true) - }.toAdditionalProperties() - - assertThat(properties).contains("fail-fast: true") - } - - @Test - fun writeMaxTestShardOption() { - val properties = - emptyExtension { - maxTestShards.set(8) - }.toFlankProperties() - - assertThat(properties).contains("max-test-shards: 8") - } - - @Test - fun writeSingleLineAdditionalFlankProperty() { - val properties = - emptyExtension { - additionalFlankOptions.set("new_version_property: test") - }.toFlankProperties() - - assertThat(properties).contains(" new_version_property: test") - } - - @Test - fun writeSingleLineAdditionalGcloudProperty() { - val properties = - emptyExtension { - additionalGcloudOptions.set("new_version_property: test") - }.toAdditionalProperties() - - assertThat(properties).contains(" new_version_property: test") - } - - @Test - fun writeMultiLineAdditionalFlankProperies() { - val properties = - emptyExtension { - additionalFlankOptions.set("new_version_property: test\nnew_version_property2: test2") - }.toFlankProperties() - - assertThat(properties).contains(" new_version_property: test") - assertThat(properties).contains(" new_version_property2: test2") - } - - @Test - fun writeMultiLineAdditionalGcloudProperties() { - val properties = - emptyExtension { - additionalGcloudOptions.set("new_version_property: test\nnew_version_property2: test2") - }.toAdditionalProperties() - - assertThat(properties).contains(" new_version_property: test") - assertThat(properties).contains(" new_version_property2: test2") - } - - private fun emptyExtension() = FlankGradleExtension(project.objects) - - private fun emptyExtension(block: FlankGradleExtension.() -> Unit) = emptyExtension().apply(block) - - private fun FlankGradleExtension.toFlankProperties() = yamlWriter.writeFlankProperties(this).trimIndent() - - private fun FlankGradleExtension.toAdditionalProperties() = yamlWriter.writeAdditionalProperties(this) -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt deleted file mode 100644 index a30daf45..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AndroidTestUtil.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.osacky.flank.gradle.integration - -/* - * Copyright (C) 2016 Square, Inc. - * - * 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 - * - * http://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. - */ -import java.io.File -import java.util.Properties - -internal fun androidHome(): String { - val env = System.getenv("ANDROID_HOME") - if (env != null) { - return env.withInvariantPathSeparators() - } - val localProp = File(File(System.getProperty("user.dir")).parentFile, "local.properties") - if (localProp.exists()) { - val prop = Properties() - localProp.inputStream().use { - prop.load(it) - } - val sdkHome = prop.getProperty("sdk.dir") - if (sdkHome != null) { - return sdkHome.withInvariantPathSeparators() - } - } - throw IllegalStateException( - "Missing 'ANDROID_HOME' environment variable or local.properties with 'sdk.dir'", - ) -} - -internal fun String.withInvariantPathSeparators() = replace("\\", "/") diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt deleted file mode 100644 index 848eb540..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/AutoConfigureFladleTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class AutoConfigureFladleTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - fun writeBuildGradle(build: String) { - val file = testProjectRoot.newFile("build.gradle") - file.writeText(build) - } - - @Test - fun testAndroidProject() { - val fixtureName = "android-project" - testProjectRoot.newFile("local.properties").writeText("sdk.dir=${androidHome()}\n") - testProjectRoot.newFile("gradle.properties").writeText("android.useAndroidX=true") - writeBuildGradle( - """ - allprojects { - repositories { - google() - mavenCentral() - } - } - """.trimIndent(), - ) - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$fixtureName' - """.trimIndent(), - ) - - testProjectRoot.setupFixture(fixtureName) - - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withArguments("assembleDebug", "assembleDebugAndroidTest", "printYml", "--stacktrace") - .build() - - assertThat(result.output).contains("BUILD SUCCESSFUL") - assertThat(result.output).containsMatch( - """ - > Task :android-project:printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - listener: com.osacky.flank.sample.Listener - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt deleted file mode 100644 index 83cbf547..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/ConfigurationCacheTest.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class ConfigurationCacheTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - fun writeBuildGradle(build: String) { - val file = testProjectRoot.newFile("build.gradle") - file.writeText(build) - } - - @Test - fun testHelp() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - """.trimMargin(), - ) - val result = configCachingRunner("help").build() - - assertThat(result.output).contains("SUCCESS") - - val secondResult = configCachingRunner("help").build() - - assertThat(secondResult.output).contains("Reusing configuration cache.") - } - - @Test - fun testPrintYml() { - writeBuildGradle( - """|plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json") - | debugApk = "debug.apk" - | instrumentationApk = "test.apk" - | localResultsDir = "foo" - |} - | - """.trimMargin(), - ) - testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{}") - val result = configCachingRunner("writeConfigProps").build() - - assertThat(result.output).contains("SUCCESS") - - val secondResult = configCachingRunner("writeConfigProps").build() - - assertThat(secondResult.output).contains("Reusing configuration cache.") - } - - @Test - fun flankDoctor() { - writeBuildGradle( - """|plugins { - | id "com.osacky.fladle" - |} - | - |repositories { - | mavenCentral() - |} - | - |fladle { - | // Flank Version is pinned at 20.08.3 because 20.08.4 introduce a backward incompatible change that causes the doctor to fail. - | // We should eventually format the version code of the device as a string but that would make Fladle backward incompatible. - | flankVersion = "20.08.3" - | serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json") - | debugApk = "debug.apk" - | instrumentationApk = "test.apk" - |} - | - """.trimMargin(), - ) - testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{}") - val result = configCachingRunner("flankDoctor").build() - - assertThat(result.output).contains("SUCCESS") - - val secondResult = configCachingRunner("flankDoctor").build() - - assertThat(secondResult.output).contains("Reusing configuration cache.") - } - - @Test - fun runFlank() { - writeBuildGradle( - """|plugins { - | id "com.osacky.fladle" - |} - | - |repositories { - | mavenCentral() - |} - | - |fladle { - | serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json") - | debugApk = "debug.apk" - | instrumentationApk = "test.apk" - | localResultsDir = "foo" - |} - | - """.trimMargin(), - ) - - val settings = testProjectRoot.newFile("settings.gradle") - settings.writeText( - """ - plugins { - id 'com.gradle.develocity' version '4.3' - } - """.trimIndent(), - ) - testProjectRoot.newFile("flank-gradle-service-account.json").writeText("{ \"project_id\": \"foo\" }") - val result = configCachingRunner("runFlank").buildAndFail() - - assertThat(result.output).contains("Error: Failed to read service account credential.") - assertThat(result.output).contains("Configuration cache entry stored.") - - val secondResult = configCachingRunner("runFlank").buildAndFail() - - assertThat(secondResult.output).contains("Error: Failed to read service account credential.") - assertThat(secondResult.output).contains("Reusing configuration cache.") - } - - private fun configCachingRunner(arg: String): GradleRunner = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .forwardOutput() - .withArguments(arg, "--configuration-cache") -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt deleted file mode 100644 index fffb40e9..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankAuthTestTask.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.junit.Ignore -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class FlankAuthTestTask { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Ignore("Flaky. See https://github.com/runningcode/fladle/issues/245") - @Test - fun testFlankAuth() { - // We set a task timeout because running flankAuth opens a link in the web browser. - // We want the task to fail from the timeout and not any other reason like a missing folder. - testProjectRoot.writeBuildDotGradle( - """plugins { - | id "com.osacky.fladle" - |} - |repositories { - | mavenCentral() - |} - | - |tasks.named("flankAuth").configure { - | timeout.set(Duration.ofSeconds(5)) - |} - | - """.trimMargin(), - ) - - val result = testProjectRoot.gradleRunner().withArguments("flankAuth").buildAndFail() - - assertThat(result.output).contains("Visit the following URL in your browser:") - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt deleted file mode 100644 index d95df33f..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FlankGradlePluginIntegrationTest.kt +++ /dev/null @@ -1,312 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class FlankGradlePluginIntegrationTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - val minSupportGradleVersion = "9.1.0" - val oldVersion = "9.0.0" - - fun writeBuildGradle(build: String) { - testProjectRoot.writeBuildDotGradle(build) - } - - @Test - fun testLowGradleVersionFailsBuild() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - """.trimMargin(), - ) - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(oldVersion) - .buildAndFail() - assertThat(result.output).contains("Fladle requires at minimum version Gradle 9.1. Detected version Gradle 9.0.0") - } - - @Test - fun testGradleNineOne() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - """.trimMargin(), - ) - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion("9.1.0") - .build() - - assertThat(result.output).contains("SUCCESS") - } - - @Test - fun testMinSupportedGradleVersionWorks() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - """.trimMargin(), - ) - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .build() - } - - @Test - fun testMissingServiceAccountWithProjectId() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | projectId = "foo-project" - | debugApk = "foo" - | instrumentationApk = "fakeInstrument.apk" - |} - """.trimMargin(), - ) - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .build() - } - - @Test - fun testMissingServiceAccountFailsBuild() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | debugApk = "foo" - |} - """.trimMargin(), - ) - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - assertThat( - result.output, - ).contains( - "ServiceAccountCredentials in fladle extension not set." + - "https://runningcode.github.io/fladle/configuration/#serviceaccountcredentials", - ) - } - - @Test - fun testMissingApkFailsBuild() { - writeBuildGradle( - """plugins { - | id "com.osacky.fladle" - |} - |fladle { - | serviceAccountCredentials = project.layout.projectDirectory.file("foo") - |} - | - """.trimMargin(), - ) - testProjectRoot.newFile("foo").writeText("{}") - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("runFlank") - .buildAndFail() - assertThat(result.output).contains("debugApk must be specified") - } - - @Test - fun testMissingInstrumentationApkFailsBuild() { - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("foo") - debugApk = "test-debug.apk" - } - """.trimIndent(), - ) - testProjectRoot.newFile("foo").writeText("{}") - val result = - GradleRunner - .create() - .withProjectDir(testProjectRoot.root) - .withPluginClasspath() - .withGradleVersion(minSupportGradleVersion) - .withArguments("runFlank") - .buildAndFail() - - assertThat(result.output).contains("Must specify either a instrumentationApk file or a roboScript file or a robo directive.") - } - - @Test - fun testSpecifyingBothInstrumentationAndRoboscriptFailsBuild() { - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboScript = "foo.script" - } - """.trimIndent(), - ) - testProjectRoot.writeEmptyServiceCredential() - val result = - testProjectRoot - .gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - - assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") - } - - @Test - fun testSpecifyingBothInstrumentationAndRobodirectiveFailsBuild() { - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent(), - ) - testProjectRoot.writeEmptyServiceCredential() - val result = - testProjectRoot - .gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - - assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") - } - - @Test - fun testSpecifyingBothRoboscriptAndRobodirectiveFailsBuild() { - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - roboScript = "foo.script" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent(), - ) - testProjectRoot.writeEmptyServiceCredential() - val result = - testProjectRoot - .gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - - assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") - } - - @Test - fun testSpecifyingInstrumentationAndRoboscriptAndRobodirectiveFailsBuild() { - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - roboScript = "foo.script" - roboDirectives = [ - ["click", "resource_id"], - ] - } - """.trimIndent(), - ) - testProjectRoot.writeEmptyServiceCredential() - val result = - testProjectRoot - .gradleRunner() - .withGradleVersion(minSupportGradleVersion) - .withArguments("printYml") - .buildAndFail() - - assertThat(result.output).contains("Only one of instrumentationApk file, roboScript file, and robo directives must be specified.") - } - - @Test - fun testGradleEightCompat() { - writeBuildGradle( - """plugins { - id "com.osacky.fladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - debugApk = "test-debug.apk" - instrumentationApk = "instrumentation-debug.apk" - configs { - fooConfig { - } - } - } - """.trimMargin(), - ) - testProjectRoot.writeEmptyServiceCredential() - val result = - testProjectRoot - .gradleRunner() - .withGradleVersion("9.1.0") - .withArguments("printYmlFooConfig") - .build() - assertThat(result.task(":printYmlFooConfig")!!.outcome).isEqualTo(TaskOutcome.SUCCESS) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt deleted file mode 100644 index 7d2a3ecf..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/FulladlePluginIntegrationTest.kt +++ /dev/null @@ -1,1029 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.io.File - -class FulladlePluginIntegrationTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - val agpDependency: String = "com.android.tools.build:gradle:9.0.1" - - fun writeBuildGradle(build: String) { - val file = testProjectRoot.newFile("build.gradle") - file.writeText(build) - } - - @Test - fun fulladleSmokeTest() { - writeBuildGradle( - """plugins { - | id "com.osacky.fulladle" - |} - """.trimMargin(), - ) - val result = - testProjectRoot - .gradleRunner() - .withArguments("help") - .build() - assertThat(result.output).contains("SUCCESS") - } - - @Test - fun fulladleWithSubmodules() { - val appFixture = "android-project" - val libraryFixture = "android-library-project" - val ignoredLibraryProject = "android-lib-ignored" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$libraryFixture' - include '$ignoredLibraryProject' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(libraryFixture) - File(testProjectRoot.root, libraryFixture).copyRecursively(testProjectRoot.newFile(ignoredLibraryProject), overwrite = true) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - // Configure second included project to ignore fulladle module - File(testProjectRoot.root, "$ignoredLibraryProject/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - // Ensure that there is only one additional test APK even though there are two library modules. - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } - - @Test - fun fulladleWithNonAndroidModule() { - val appFixture = "android-project" - val libraryFixture = "android-library-project" - val nonAndroidFixture = "lib1" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$libraryFixture' - include '$nonAndroidFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(libraryFixture) - File(testProjectRoot.root, libraryFixture).copyRecursively(testProjectRoot.newFile(nonAndroidFixture), overwrite = true) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - // Configure second included project to ignore fulladle module - File(testProjectRoot.root, "$nonAndroidFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$nonAndroidFixture/build.gradle").writeText( - """ - apply plugin: 'java-library' - - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - } - - @Test - fun fulladleWithSubmoduleOverrides() { - val appFixture = "android-project" - val appFixture2 = "android-project2" - val libraryFixture = "android-library-project" - val libraryFixture2 = "android-lib2" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' - include '$libraryFixture2' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(appFixture2) - testProjectRoot.setupFixture(libraryFixture) - File(testProjectRoot.root, libraryFixture).copyRecursively(testProjectRoot.newFile(libraryFixture2), overwrite = true) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - environmentVariables = [ - "clearPackageData": "true", - "listener": "com.osacky.flank.sample.Listener" - ] - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$libraryFixture2/build.gradle").appendText( - """ - fulladleModuleConfig { - maxTestShards = 4 - clientDetails = ["test-type": "PR","build-number": "132"] - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$libraryFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - maxTestShards = 7 - environmentVariables = [ - "clearPackageData": "false", - "listener": "com.osacky.flank.sample.Listener.Different" - ] - debugApk = "dummy_app.apk" - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - listener: com.osacky.flank.sample.Listener - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk - test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk - max-test-shards: 5 - environment-variables: - clearPackageData: false - - - test: [0-9a-zA-Z\/_]*/$libraryFixture2/build/outputs/apk/androidTest/debug/android-lib2-debug-androidTest.apk - max-test-shards: 4 - client-details: - test-type: PR - build-number: 132 - - - app: dummy_app.apk - test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - max-test-shards: 7 - environment-variables: - clearPackageData: false - listener: com.osacky.flank.sample.Listener.Different - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } - - @Test - fun fulladleWithSpecificFlavor() { - val appFixture = "android-project" - val libraryFixture = "android-library-project" - val flavourProject = "android-project-flavors" - val flavourLibrary = "android-library-project-flavors" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$libraryFixture' - include '$flavourProject' - include '$flavourLibrary' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(libraryFixture) - testProjectRoot.setupFixture(flavourProject) - testProjectRoot.setupFixture(flavourLibrary) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - // Configure flavors in project and library - File(testProjectRoot.root, "$flavourProject/build.gradle").appendText( - """ - fulladleModuleConfig { - variant = "vanillaDebug" - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$flavourLibrary/build.gradle").appendText( - """ - fulladleModuleConfig { - variant = "strawberryDebug" - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/vanilla/debug/android-project-flavors-vanilla-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/vanilla/debug/android-project-flavors-vanilla-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/strawberry/debug/android-library-project-flavors-strawberry-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } - - @Test - fun fulladleWithDefaultFlavor() { - val appFixture = "android-project" - val libraryFixture = "android-library-project" - val flavourProject = "android-project-flavors" - val flavourLibrary = "android-library-project-flavors" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$flavourProject' - include '$flavourLibrary' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(flavourProject) - testProjectRoot.setupFixture(flavourLibrary) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/chocolate/debug/android-project-flavors-chocolate-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-flavors/build/outputs/apk/androidTest/chocolate/debug/android-project-flavors-chocolate-debug-androidTest.apk - - - test: [0-9a-zA-Z\/_]*/android-library-project-flavors/build/outputs/apk/androidTest/lemon/debug/android-library-project-flavors-lemon-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } - - @Test - fun `test root level module overrides with fulladleModuleConfig`() { - val appFixture = "android-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - maxTestShards = 4 - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = true - maxTestShards = 7 - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - assertThat(result.output).doesNotContain("max-test-shards: 4") - assertThat(result.output).contains("max-test-shards: 7") - assertThat(result.output).doesNotContain("additional-app-test-apks") - } - - @Test - fun testAllModulesDisabled() { - val appFixture = "android-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .buildAndFail() - - assertThat(result.output).contains("Task :configureFulladle FAILED") - assertThat(result.output).contains("All modules were disabled for testing in fulladleModuleConfig or the enabled modules had no tests") - } - - /** - * this test has all app modules disabled - * and specifies no debugApk for library module. - * No module is appropriate for root level flank - * config, thus we have an illegal state - */ - @Test - fun `test fulladle with app modules disabled and library module without debug apk`() { - val appFixture = "android-project" - val appFixture2 = "android-project2" - val libraryFixture = "android-library-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(appFixture2) - testProjectRoot.setupFixture(libraryFixture) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .buildAndFail() - - assertThat(result.output).contains("Task :configureFulladle FAILED") - assertThat(result.output).contains( - "Library module :android-library-project did not specify a debug apk. Library modules do not " + - "generate a debug apk and one needs to be specified in the fulladleModuleConfig block", - ) - assertThat(result.output).contains( - "This is a required parameter in FTL which remains unused for library modules under test, and you can use a dummy apk here", - ) - } - - /** - * this test has all app modules disabled - * and specifies debugApk for library module. - * the library module is appropriate for - * root level flank config, thus we have SUCCESS - */ - @Test - fun `test fulladle with app modules disabled and library module with debug apk`() { - val appFixture = "android-project" - val appFixture2 = "android-project2" - val libraryFixture = "android-library-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(appFixture2) - testProjectRoot.setupFixture(libraryFixture) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - File(testProjectRoot.root, "$libraryFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - debugApk = "dummy_app.apk" - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).doesNotContain("additional-app-test-apks") - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: dummy_app.apk - test: [0-9a-zA-Z\/_]*/$libraryFixture/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - """.trimIndent(), - ) - assertThat(result.output).contains("SUCCESS") - } - - /** - * this test has only one of two app module enabled - * and specifies no debugApk for library module. - * the enabled app module is appropriate for - * root level flank config, thus we have SUCCESS - */ - @Test - fun `test fulladle with app module enabled and library module without debug apk`() { - val appFixture = "android-project" - val appFixture2 = "android-project2" - val libraryFixture = "android-library-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixture' - include '$appFixture2' - include '$libraryFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(appFixture2) - testProjectRoot.setupFixture(libraryFixture) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - - dependencies { - classpath '$agpDependency' - } - } - - plugins { - id "com.osacky.fulladle" - } - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = true - } - """.trimIndent(), - ) - - File(testProjectRoot.root, "$appFixture2/build.gradle").appendText( - """ - fulladleModuleConfig { - enabled = false - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("BUILD SUCCESSFUL") - } - - @Test - fun fulladleWithAbiSplits() { - val appFixtureWithAbiSplits = "android-project-with-abi-splits" - val appFixture = "android-project" - val appFixtureTwo = "android-project2" - val libraryFixture = "android-library-project" - testProjectRoot.newFile("settings.gradle").writeText( - """ - include '$appFixtureWithAbiSplits' - include '$appFixture' - include '$appFixtureTwo' - include '$libraryFixture' - - dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } - } - """.trimIndent(), - ) - testProjectRoot.setupFixture(appFixture) - testProjectRoot.setupFixture(appFixtureTwo) - testProjectRoot.setupFixture(libraryFixture) - File(testProjectRoot.root, appFixture).copyRecursively(testProjectRoot.newFile(appFixtureWithAbiSplits), overwrite = true) - - writeBuildGradle( - """ - buildscript { - repositories { - google() - } - dependencies { - classpath '$agpDependency' - } - } - plugins { - id "com.osacky.fulladle" - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("android-project/flank-gradle-5cf02dc90531.json") - abi = "armeabi-v7a" - } - """.trimIndent(), - ) - - // Overwrite android-project fixture to include ABI splits. - File(testProjectRoot.root, "$appFixtureWithAbiSplits/build.gradle").writeText( - """ - plugins { - id "com.android.application" - } - android { - compileSdk 33 - namespace "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 29 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - splits { - abi { - enable true - reset() - include "x86", "armeabi-v7a", "arm64-v8a", "x86_64" - universalApk false - } - } - } - """.trimIndent(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments(":printYml") - .build() - - assertThat(result.output).contains("SUCCESS") - // Ensure that: - // - Any application modules that use ABI splits use APKs for the specified split (armeabi-v7a). - // - Any application modules that don't use ABI splits are still present in additional-app-test-apks. - assertThat(result.output).containsMatch( - """ - > Task :printYml - gcloud: - app: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/debug/android-project-debug.apk - test: [0-9a-zA-Z\/_]*/android-project/build/outputs/apk/androidTest/debug/android-project-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 28 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - num-flaky-test-attempts: 0 - - flank: - keep-file-path: false - additional-app-test-apks: - - app: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/debug/android-project-with-abi-splits-armeabi-v7a-debug.apk - test: [0-9a-zA-Z\/_]*/android-project-with-abi-splits/build/outputs/apk/androidTest/debug/android-project-with-abi-splits-debug-androidTest.apk - - - app: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/debug/android-project2-debug.apk - test: [0-9a-zA-Z\/_]*/android-project2/build/outputs/apk/androidTest/debug/android-project2-debug-androidTest.apk - max-test-shards: 5 - environment-variables: - clearPackageData: false - - - test: [0-9a-zA-Z\/_]*/android-library-project/build/outputs/apk/androidTest/debug/android-library-project-debug-androidTest.apk - - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt deleted file mode 100644 index 7e661015..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityRoboTest.kt +++ /dev/null @@ -1,358 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -private fun baseConfigMessage(option: String) = "Incorrect [base] configuration. [$option] can't be used together with sanityRobo." - -private fun additionalConfigMessage( - option: String, - name: String, -) = "Incorrect [$name] configuration. [$option] can't be used together with sanityRobo. " + - "To configure sanityRobo, add clearPropertiesForSanityRobo() to the [$name] configuration" - -class SanityRoboTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Before - fun setUp() = testProjectRoot.newFile("flank-gradle-service.json").writeText("{}") - - @Test - fun `sanityRobo - should throw an error if instrumentationApk set`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | debugApk = "debug.apk" - | sanityRobo = true - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | instrumentationApk = "test.apk" - | configs { - | sanity { - | clearPropertiesForSanityRobo() - | } - | } - |} - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val result = runner.withArguments("printYml").buildAndFail() - - assertThat(result.output).contains("FAILED") - assertThat(result.output).contains(baseConfigMessage("instrumentationApk")) - - val resultSanity = runner.withArguments("printYmlSanity").build() - - assertThat(resultSanity.output).contains("SUCCESS") - } - - @Test - fun `sanityRobo - should throw an error if roboScript set`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | debugApk = "debug.apk" - | sanityRobo = true - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | roboScript = "some/path/script.json" - |} - """.trimMargin(), - ) - - val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() - - assertThat(result.output).contains("FAILED") - assertThat(result.output).contains(baseConfigMessage("roboScript")) - } - - @Test - fun `sanityRobo - should throw an error if roboDirectives set`() { - testProjectRoot.writeBuildDotGradle( - buildScript = - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | debugApk = "debug.apk" - | sanityRobo = true - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | roboDirectives = [ - | ["click", "button1", ""], - | ["ignore", "button2"], - | ["text", "field1", "my text"], - | ] - |} - """.trimMargin(), - ) - - val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() - - assertThat(result.output).contains("FAILED") - assertThat(result.output).contains(baseConfigMessage("roboDirectives")) - } - - @Test - fun `sanityRobo - should throw an error if additionalTestApks set`() { - testProjectRoot.writeBuildDotGradle( - buildScript = - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | debugApk = "debug.apk" - | sanityRobo = true - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | additionalTestApks = [ - | "- app: debug2.apk", - | " test: test2.apk", - | "- test: test3.apk" - | ] - |} - """.trimMargin(), - ) - - val result = testProjectRoot.gradleRunner().withArguments("printYml").buildAndFail() - - assertThat(result.output).contains("FAILED") - assertThat(result.output).contains(baseConfigMessage("additionalTestApks")) - } - - @Test - fun `sanityRobo - should throw an error if roboScript set (multiple config)`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | additionalTestApks = [ - | "- app: debug2.apk", - | " test: test2.apk", - | "- test: test3.apk" - | ] - | configs { - | sanity { - | clearPropertiesForSanityRobo() - | roboScript.set("path/to/script.json") - | } - | } - |} - """.trimMargin(), - ) - - val expectedMessage = additionalConfigMessage("roboScript", "sanity") - - val runner = testProjectRoot.gradleRunner() - val result = runner.withArguments("printYml").build() - - assertThat(result.output).contains("SUCCESS") - - val resultOrange = runner.withArguments("printYmlSanity").buildAndFail() - - assertThat(resultOrange.output).contains("FAILED") - assertThat(resultOrange.output).contains(expectedMessage) - } - - @Test - fun `sanityRobo - should print correct config yamls (inner config is sanity run)`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | additionalTestApks = [ - | "- app: debug2.apk", - | " test: test2.apk", - | "- test: test3.apk" - | ] - | configs { - | orange { - | clearPropertiesForSanityRobo() - | } - | } - |} - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val result = runner.withArguments("printYml").build() - - assertThat(result.output).contains("SUCCESS") - assertThat(result.output).contains( - """ - |gcloud: - | app: foo.apk - | test: test.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | additional-app-test-apks: - | - app: debug2.apk - | test: test2.apk - | - test: test3.apk - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - - val resultOrange = runner.withArguments("printYmlOrange").build() - - assertThat(resultOrange.output).contains("SUCCESS") - assertThat(resultOrange.output).contains( - """ - |gcloud: - | app: foo.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - } - - @Test - fun `sanityRobo - should print correct config yamls (base config is sanity run)`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | sanityRobo = true - | configs { - | orange { - | instrumentationApk.set("test.apk") - | additionalTestApks.set(project.provider { [ - | "- app: debug2.apk", - | " test: test2.apk", - | "- test: test3.apk" - | ] }) - | sanityRobo.set(false) - | } - | } - |} - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val result = runner.withArguments("printYml").build() - - assertThat(result.output).contains("SUCCESS") - assertThat(result.output).contains( - """ - |gcloud: - | app: foo.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - - val resultOrange = runner.withArguments("printYmlOrange").build() - - assertThat(resultOrange.output).contains("SUCCESS") - assertThat(resultOrange.output).contains( - """ - |gcloud: - | app: foo.apk - | test: test.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | additional-app-test-apks: - | - app: debug2.apk - | test: test2.apk - | - test: test3.apk - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt deleted file mode 100644 index 5db13232..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/SanityWithAutoConfigureTest.kt +++ /dev/null @@ -1,306 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -private const val COMMON_SCRIPT_PART = """ - plugins { - id 'com.android.application' - id 'com.osacky.fladle' - } - - android { - compileSdk 33 - namespace "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 29 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - } -""" - -class SanityWithAutoConfigureTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Before - fun setUp() { - testProjectRoot.newFile("flank-gradle-service.json").writeText("{}") - testProjectRoot.newFile("local.properties").writeText("sdk.dir=${androidHome()}\n") - testProjectRoot.newFile("gradle.properties").writeText("android.useAndroidX=true") - } - - @Test - fun `test auto configuration with sanityRobo set (inner config)`() { - testProjectRoot.writeBuildDotGradle( - """ - $COMMON_SCRIPT_PART - - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - useOrchestrator = true - environmentVariables = [ - "clearPackageData": "true" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - devices = [ - [ "model": "SmallPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - smartFlankGcsPath = "gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml" - configs { - sanity { - clearPropertiesForSanityRobo() - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - } - oranges { - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(6) - } - } - } - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val baseResult = runner.withArguments("printYml").build() - - assertThat(baseResult.output).contains("BUILD SUCCESSFUL") - assertThat(baseResult.output).containsMatch( - """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - - val sanityResult = runner.withArguments("printYmlSanity").build() - - assertThat(sanityResult.output).contains("BUILD SUCCESSFUL") - assertThat(sanityResult.output).containsMatch( - """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 3 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - - val orangesResult = runner.withArguments("printYmlOranges").build() - - assertThat(orangesResult.output).contains("BUILD SUCCESSFUL") - assertThat(orangesResult.output).containsMatch( - """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: [0-9a-zA-Z\/_]*/build/outputs/apk/androidTest/debug/[0-9a-zA-Z\/_]*-debug-androidTest.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 6 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } - - @Test - fun `test auto configuration with sanityRobo set (base config)`() { - testProjectRoot.writeBuildDotGradle( - """ - $COMMON_SCRIPT_PART - - fladle { - sanityRobo = true - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service.json") - useOrchestrator = true - environmentVariables = [ - "clearPackageData": "true" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - devices = [ - [ "model": "SmallPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - smartFlankGcsPath = "gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml" - configs { - oranges { - instrumentationApk.set("instrumentation-apk-not-detected-from-root.apk") - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - sanityRobo.set(false) - } - } - } - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val baseResult = runner.withArguments("printYml").build() - - assertThat(baseResult.output).contains("BUILD SUCCESSFUL") - assertThat(baseResult.output).containsMatch( - """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: true - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView - num-flaky-test-attempts: 0 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - - val orangesResult = runner.withArguments("printYmlOranges").build() - - assertThat(orangesResult.output).contains("BUILD SUCCESSFUL") - assertThat(orangesResult.output).containsMatch( - """ - gcloud: - app: [0-9a-zA-Z\/_]*/build/outputs/apk/debug/[0-9a-zA-Z\/_]*-debug.apk - test: instrumentation-apk-not-detected-from-root.apk - device: - - model: SmallPhone.arm - version: 26 - - model: MediumPhone.arm - version: 33 - - use-orchestrator: false - auto-google-login: false - record-video: true - performance-metrics: true - timeout: 15m - environment-variables: - clearPackageData: true - test-targets: - - class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail - num-flaky-test-attempts: 3 - - flank: - smart-flank-gcs-path: gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml - keep-file-path: false - ignore-failed-tests: false - disable-sharding: false - smart-flank-disable-upload: false - legacy-junit-result: false - full-junit-result: false - output-style: single - """.trimIndent(), - ) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/TestFixtures.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/TestFixtures.kt deleted file mode 100644 index 6ad7a89c..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/TestFixtures.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.osacky.flank.gradle.integration - -import org.gradle.testkit.runner.GradleRunner -import org.junit.rules.TemporaryFolder -import java.io.File - -fun TemporaryFolder.setupFixture(fixtureName: String) { - File( - this::class.java.classLoader - .getResource(fixtureName)!! - .file, - ).copyRecursively(newFile(fixtureName), true) -} - -internal fun TemporaryFolder.writeBuildDotGradle(buildScript: String) = - newFile("build.gradle") - .writeText(buildScript) - -fun TemporaryFolder.gradleRunner() = - GradleRunner - .create() - .withPluginClasspath() - .forwardOutput() - .withProjectDir(root) - -fun TemporaryFolder.writeEmptyServiceCredential() { - newFile("flank-gradle-service.json").writeText("{}") -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt deleted file mode 100644 index 29f40468..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/integration/VariantTests.kt +++ /dev/null @@ -1,205 +0,0 @@ -package com.osacky.flank.gradle.integration - -import com.google.common.truth.Truth.assertThat -import org.gradle.testkit.runner.BuildResult -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.io.File - -class VariantTests { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Test - fun testWithDependOnAssembleNoFlavors() { - val result = setUpDependOnAssemble(true, withFlavors = false) - assertThat(result.output).contains(":assembleDebug") - assertThat(result.output).contains(":assembleDebugAndroidTest") - assertThat(result.output).doesNotContain(":assembleRelease") - } - - @Test - fun testWithOutDependOnAssembleNoFlavors() { - val result = setUpDependOnAssemble(false, withFlavors = false) - assertThat(result.output).doesNotContain(":assembleDebug") - assertThat(result.output).doesNotContain(":assembleDebugAndroidTest") - assertThat(result.output).doesNotContain(":assembleRelease") - } - - @Test - fun testWithDependOnAssembleAndFlavors() { - val result = setUpDependOnAssemble(true, withFlavors = true) - assertThat(result.output).contains(":assembleChocolateDebug") - assertThat(result.output).contains(":assembleChocolateDebugAndroidTest") - assertThat(result.output).doesNotContain(":assembleChocolateRelease") - assertThat(result.output).doesNotContain(":assembleVanilla") - } - - @Test - fun testWithOutDependOnAssembleAndFlavors() { - val result = setUpDependOnAssemble(false, withFlavors = true) - assertThat(result.output).doesNotMatch(":assemble.*") - assertThat(result.output).doesNotContain(":assembleChocolateDebug") - assertThat(result.output).doesNotContain(":assembleDebug") - assertThat(result.output).doesNotContain(":assembleRelease") - assertThat(result.output).doesNotContain(":assembleDebugAndroidTest") - } - - @Test - fun testAdditionalFladleConfigForVariant() { - val result = - setUpDependOnAssemble( - dependsOnAssemble = true, - withFlavors = true, - withFladleConfig = - """ - configs { - vanilla { - variant.set("vanillaDebug") - } - } - """.trimIndent(), - withTask = "runFlankVanilla", - ) - assertThat(result.output).contains(":assembleVanillaDebug") - assertThat(result.output).contains(":assembleVanillaDebugAndroidTest") - assertThat(result.output).doesNotContain(":assembleVanillaRelease") - assertThat(result.output).doesNotContain(":assembleChocolate") - - // See #60 https://github.com/runningcode/fladle/issues/60 - // testProjectRoot.writeEmptyServiceCredential() - // val resultPrint = testProjectRoot.gradleRunner() - // .withArguments("printYmlVanilla") - // .build() - // assertThat(resultPrint.output).contains("build/outputs/apk/vanilla/debug/chocovanilla-vanilla-debug.apk") - // assertThat(resultPrint.output).contains("build/outputs/apk/androidTest/vanilla/debug/chocovanilla-vanilla-debug-androidTest.apk") - } - - @Test - fun testAbiSplits() { - val result = setUpDependOnAssemble(true, withAbiSplit = true, withTask = "printYml", dryRun = false) - assertThat(result.output).containsMatch("""\s+app: \S*-x86-debug\.apk""") - // Test APKs do not use ABI splits. - assertThat(result.output).doesNotContainMatch("""\s+test: \S*-x86-\S*androidTest\.apk""") - } - - @Test - fun testAbiSplitsWithVariants() { - val result = setUpDependOnAssemble(true, withFlavors = true, withAbiSplit = true, withTask = "printYml", dryRun = false) - assertThat(result.output).containsMatch("""\s+app: \S*-chocolate-x86-debug\.apk""") - assertThat(result.output).doesNotContainMatch("""\s+test: \S*-x86-\S*androidTest\.apk""") - } - - private fun setUpDependOnAssemble( - dependsOnAssemble: Boolean, - withFlavors: Boolean = false, - withAbiSplit: Boolean = false, - withFladleConfig: String = "", - withTask: String = "runFlank", - dryRun: Boolean = true, - ): BuildResult { - testProjectRoot.newFile("settings.gradle").writeText( - """rootProject.name = 'chocovanilla' - |include ':android-project' - """.trimMargin(), - ) - testProjectRoot.newFile("local.properties").writeText("sdk.dir=${androidHome()}\n") - testProjectRoot.newFile("gradle.properties").writeText("android.useAndroidX=true") - testProjectRoot.setupFixture("android-project") - val flavors = - if (withFlavors) { - """ - flavorDimensions "flavor" - productFlavors { - chocolate { - dimension "flavor" - } - vanilla { - dimension "flavor" - } - } - """.trimIndent() - } else { - "" - } - val abiSplits = - if (withAbiSplit) { - """ - splits { - abi { - enable true - reset() - include "x86", "x86_64" - universalApk false - } - } - """.trimIndent() - } else { - "" - } - val variant = - if (withFlavors) { - """variant = "chocolateDebug"""" - } else { - "" - } - val abi = - if (withAbiSplit) { - "abi = \"x86\"" - } else { - "" - } - writeBuildGradle( - """ - plugins { - id "com.osacky.fladle" - id "com.android.application" - } - repositories { - google() - mavenCentral() - } - android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - $flavors - $abiSplits - } - fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - dependOnAssemble = $dependsOnAssemble - $variant - $abi - $withFladleConfig - } - """.trimIndent(), - ) - - val arguments = mutableListOf(withTask) - if (dryRun) { - arguments.add("--dry-run") - } - return testProjectRoot - .gradleRunner() - .withArguments(arguments) - .build() - } - - private fun writeBuildGradle(build: String) { - // Overwrite existing build.gradle file in "android-project" directory with new text - val file = File(testProjectRoot.root, "android-project/build.gradle") - file.writeText(build) - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt deleted file mode 100644 index 4ffb9517..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateExclusionsTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.osacky.flank.gradle.validation - -import com.google.common.truth.Truth.assertThat -import com.osacky.flank.gradle.integration.gradleRunner -import com.osacky.flank.gradle.integration.writeBuildDotGradle -import com.osacky.flank.gradle.integration.writeEmptyServiceCredential -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class ValidateExclusionsTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - @Test - fun `should throw an error when mutually exclusive options used`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | testShards.set(1) - | maxTestShards.set(2) - | flankVersion.set("21.01.0") - |} - """.trimMargin(), - ) - - val result = - testProjectRoot - .gradleRunner() - .withArguments("printYml") - .buildAndFail() - - assertThat(result.output).contains("FAILED") - assertThat(result.output).contains("Options testShards and maxTestShards cannot be used together. Choose one of them.") - } - - @Test - fun `should throw an error when mutually exclusive options used -- inner config`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | testShards.set(1) - | flankVersion.set("21.01.0") - | configs { - | newSharding { - | maxTestShards.set(2) - | } - | } - |} - """.trimMargin(), - ) - - testProjectRoot.writeEmptyServiceCredential() - val runner = testProjectRoot.gradleRunner() - - runner.withArguments("printYml").build().run { - assertThat(output).contains("SUCCESS") - } - - runner.withArguments("printYmlNewSharding").buildAndFail().run { - assertThat(output).contains("FAILED") - assertThat(output).contains("Options testShards and maxTestShards cannot be used together. Choose one of them.") - } - } -} diff --git a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt b/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt deleted file mode 100644 index 0800d233..00000000 --- a/fladle-plugin/src/test/java/com/osacky/flank/gradle/validation/ValidateOptionsTest.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.osacky.flank.gradle.validation - -import com.google.common.truth.Truth.assertThat -import com.osacky.flank.gradle.FladleConfig -import com.osacky.flank.gradle.FlankGradleExtension -import com.osacky.flank.gradle.integration.gradleRunner -import com.osacky.flank.gradle.integration.writeBuildDotGradle -import org.gradle.testfixtures.ProjectBuilder -import org.junit.Assert.assertThrows -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -class ValidateOptionsTest { - @get:Rule - var testProjectRoot = TemporaryFolder() - - private val objects = - ProjectBuilder - .builder() - .withName("project") - .build() - .objects - private lateinit var config: FladleConfig - - @Before - fun setUp() { - testProjectRoot.newFile("flank-gradle-service.json").writeText("{}") - config = FlankGradleExtension(objects) - } - - @Test - fun `should throw an error when unavailable option used`() { - config.useAverageTestTimeForNewTests.set(true) - - assertThrows(IllegalStateException::class.java) { validateOptionsUsed(config, "20.05.0") }.run { - assertThat( - message, - ).containsMatch("Option useAverageTestTimeForNewTests is available since flank 20.8.4, which is higher than used 20.5.0") - } - } - - @Test - fun `should not throw an error when available option used`() { - config.testRunnerClass.set("any") - - validateOptionsUsed(config, "20.09.10") - } - - @Test - fun `ListProperty check also checks for empty list`() { - config.testTargetsForShard.set(listOf()) - validateOptionsUsed(config, "20.09.10") - } - - @Test - fun `ListProperty isPresent when set`() { - config.testTargetsForShard.set(listOf("class com.example.Bar")) - try { - validateOptionsUsed(config, "20.09.10") - } catch (e: IllegalStateException) { - assertThat( - e, - ).hasMessageThat().contains("Option testTargetsForShard is available since flank 20.12.0, which is higher than used 20.9.10") - } - } - - @Test - fun `should throw an error when unavailable option used -- multi config`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | flankVersion.set("20.05.0") - | configs { - | newNetwork { - | useAverageTestTimeForNewTests.set(true) - | } - | noSharding { - | disableSharding.set(true) - | } - | } - |} - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - - runner.withArguments("printYml").build().run { - assertThat(output).contains("SUCCESS") - } - - runner.withArguments("printYmlNewNetwork").buildAndFail().run { - assertThat(output).contains("FAILED") - assertThat(output).contains("Option useAverageTestTimeForNewTests is available since flank 20.8.4, which is higher than used 20.5.0") - } - - runner.withArguments("printYmlNoSharding").build().run { - assertThat(output).contains("SUCCESS") - } - } - - @Test - fun `should not throw an error if none unavailable option used`() { - testProjectRoot.writeBuildDotGradle( - """ - |plugins { - | id "com.osacky.fladle" - |} - | - |fladle { - | serviceAccountCredentials = layout.projectDirectory.file("flank-gradle-service.json") - | debugApk = "foo.apk" - | instrumentationApk = "test.apk" - | flankVersion.set("20.20.20") - | configs { - | noRecord { - | recordVideo.set(false) - | } - | } - |} - """.trimMargin(), - ) - - val runner = testProjectRoot.gradleRunner() - val result = runner.withArguments("printYml").build() - - assertThat(result.output).contains("BUILD SUCCESSFUL") - assertThat(result.output).contains( - """ - |gcloud: - | app: foo.apk - | test: test.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: true - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - - val resultOrange = runner.withArguments("printYmlNoRecord").build() - - assertThat(resultOrange.output).contains("BUILD SUCCESSFUL") - assertThat(resultOrange.output).contains( - """ - |gcloud: - | app: foo.apk - | test: test.apk - | device: - | - model: SmallPhone.arm - | version: 28 - | - | use-orchestrator: false - | auto-google-login: false - | record-video: false - | performance-metrics: true - | timeout: 15m - | num-flaky-test-attempts: 0 - | - |flank: - | keep-file-path: false - | ignore-failed-tests: false - | disable-sharding: false - | smart-flank-disable-upload: false - | legacy-junit-result: false - | full-junit-result: false - | output-style: single - """.trimMargin(), - ) - } -} diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle b/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle deleted file mode 100644 index 73bcc57c..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -plugins { - id 'com.android.library' -} - -android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - flavorDimensions("flavor") - - productFlavors { - create("lemon") { - dimension = "flavor" - } - create("strawberry") { - dimension = "flavor" - } - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java b/fladle-plugin/src/test/resources/android-library-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java deleted file mode 100644 index 5cce721c..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.osacky.flank.gradle.sample; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.action.ViewActions.typeText; -import java.lang.RuntimeException; - -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Rule - private final ActivityTestRule testRule = new ActivityTestRule(MainActivity.class); - - @Test - public void seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())); - } - - @Test - public void runAndFail() { - throw new RuntimeException("Test failed"); - } -} diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml deleted file mode 100644 index 2d886939..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java deleted file mode 100644 index 0b3bae41..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.osacky.flank.gradle.sample; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/layout/activity_main.xml b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/values/strings.xml b/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/fladle-plugin/src/test/resources/android-library-project-flavors/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/fladle-plugin/src/test/resources/android-library-project/build.gradle b/fladle-plugin/src/test/resources/android-library-project/build.gradle deleted file mode 100644 index 7890fc20..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -plugins { - id 'com.android.library' -} - -android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} - diff --git a/fladle-plugin/src/test/resources/android-library-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java b/fladle-plugin/src/test/resources/android-library-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java deleted file mode 100644 index 5cce721c..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.osacky.flank.gradle.sample; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.action.ViewActions.typeText; -import java.lang.RuntimeException; - -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Rule - private final ActivityTestRule testRule = new ActivityTestRule(MainActivity.class); - - @Test - public void seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())); - } - - @Test - public void runAndFail() { - throw new RuntimeException("Test failed"); - } -} diff --git a/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml deleted file mode 100644 index 2d886939..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-library-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java b/fladle-plugin/src/test/resources/android-library-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java deleted file mode 100644 index 0b3bae41..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.osacky.flank.gradle.sample; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/fladle-plugin/src/test/resources/android-library-project/src/main/res/layout/activity_main.xml b/fladle-plugin/src/test/resources/android-library-project/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-library-project/src/main/res/values/strings.xml b/fladle-plugin/src/test/resources/android-library-project/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/fladle-plugin/src/test/resources/android-library-project/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/fladle-plugin/src/test/resources/android-project-flavors/build.gradle b/fladle-plugin/src/test/resources/android-project-flavors/build.gradle deleted file mode 100644 index f9e08150..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -plugins { - id 'com.android.application' - id 'com.osacky.fladle' -} - -android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - flavorDimensions += "flavor" - - productFlavors { - create("chocolate") { - dimension = "flavor" - } - create("vanilla") { - dimension = "flavor" - } - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} - diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java b/fladle-plugin/src/test/resources/android-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java deleted file mode 100644 index 5cce721c..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.osacky.flank.gradle.sample; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.action.ViewActions.typeText; -import java.lang.RuntimeException; - -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Rule - private final ActivityTestRule testRule = new ActivityTestRule(MainActivity.class); - - @Test - public void seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())); - } - - @Test - public void runAndFail() { - throw new RuntimeException("Test failed"); - } -} diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml deleted file mode 100644 index 2d886939..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java b/fladle-plugin/src/test/resources/android-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java deleted file mode 100644 index 0b3bae41..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.osacky.flank.gradle.sample; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/layout/activity_main.xml b/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/values/strings.xml b/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/fladle-plugin/src/test/resources/android-project-flavors/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/fladle-plugin/src/test/resources/android-project/build.gradle b/fladle-plugin/src/test/resources/android-project/build.gradle deleted file mode 100644 index 3d71caaf..00000000 --- a/fladle-plugin/src/test/resources/android-project/build.gradle +++ /dev/null @@ -1,57 +0,0 @@ -plugins { - id 'com.android.application' - id 'com.osacky.fladle' -} - -android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } -} - -fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - useOrchestrator = true - environmentVariables = [ - "clearPackageData": "true", - "listener": "com.osacky.flank.sample.Listener" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - devices = [ - [ "model": "SmallPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - smartFlankGcsPath = "gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml" - configs { - oranges { - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - } - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} - diff --git a/fladle-plugin/src/test/resources/android-project/flank-gradle-5cf02dc90531.json b/fladle-plugin/src/test/resources/android-project/flank-gradle-5cf02dc90531.json deleted file mode 100644 index 9e26dfee..00000000 --- a/fladle-plugin/src/test/resources/android-project/flank-gradle-5cf02dc90531.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java b/fladle-plugin/src/test/resources/android-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java deleted file mode 100644 index 5cce721c..00000000 --- a/fladle-plugin/src/test/resources/android-project/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.osacky.flank.gradle.sample; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.action.ViewActions.typeText; -import java.lang.RuntimeException; - -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Rule - private final ActivityTestRule testRule = new ActivityTestRule(MainActivity.class); - - @Test - public void seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())); - } - - @Test - public void runAndFail() { - throw new RuntimeException("Test failed"); - } -} diff --git a/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml deleted file mode 100644 index 2d886939..00000000 --- a/fladle-plugin/src/test/resources/android-project/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java b/fladle-plugin/src/test/resources/android-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java deleted file mode 100644 index 0b3bae41..00000000 --- a/fladle-plugin/src/test/resources/android-project/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.osacky.flank.gradle.sample; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/fladle-plugin/src/test/resources/android-project/src/main/res/layout/activity_main.xml b/fladle-plugin/src/test/resources/android-project/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/fladle-plugin/src/test/resources/android-project/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project/src/main/res/values/strings.xml b/fladle-plugin/src/test/resources/android-project/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/fladle-plugin/src/test/resources/android-project/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/fladle-plugin/src/test/resources/android-project2/build.gradle b/fladle-plugin/src/test/resources/android-project2/build.gradle deleted file mode 100644 index 3cb2fa25..00000000 --- a/fladle-plugin/src/test/resources/android-project2/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -plugins { - id 'com.android.application' - id 'com.osacky.fladle' -} - -android { - compileSdk 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } -} - -fladle { - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - useOrchestrator = true - environmentVariables = [ - "clearPackageData": "true" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - devices = [ - [ "model": "SmallPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - smartFlankGcsPath = "gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml" - configs { - oranges { - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - } - } -} - -fulladleModuleConfig { - maxTestShards = 5 - environmentVariables = ["clearPackageData": "false"] -} -dependencies { - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation("androidx.navigation:navigation-fragment-ktx:2.3.0") - implementation 'androidx.constraintlayout:constraintlayout:2.0.1' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} - diff --git a/fladle-plugin/src/test/resources/android-project2/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java b/fladle-plugin/src/test/resources/android-project2/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java deleted file mode 100644 index 5cce721c..00000000 --- a/fladle-plugin/src/test/resources/android-project2/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.osacky.flank.gradle.sample; - - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.action.ViewActions.typeText; -import java.lang.RuntimeException; - -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Rule - private final ActivityTestRule testRule = new ActivityTestRule(MainActivity.class); - - @Test - public void seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())); - } - - @Test - public void runAndFail() { - throw new RuntimeException("Test failed"); - } -} diff --git a/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml b/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml deleted file mode 100644 index 2d886939..00000000 --- a/fladle-plugin/src/test/resources/android-project2/src/main/AndroidManifest.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project2/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java b/fladle-plugin/src/test/resources/android-project2/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java deleted file mode 100644 index 0b3bae41..00000000 --- a/fladle-plugin/src/test/resources/android-project2/src/main/java/com/osacky/flank/gradle/sample/MainActivity.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.osacky.flank.gradle.sample; - -import androidx.appcompat.app.AppCompatActivity; -import android.os.Bundle; - -public class MainActivity extends AppCompatActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - } -} diff --git a/fladle-plugin/src/test/resources/android-project2/src/main/res/layout/activity_main.xml b/fladle-plugin/src/test/resources/android-project2/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/fladle-plugin/src/test/resources/android-project2/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/fladle-plugin/src/test/resources/android-project2/src/main/res/values/strings.xml b/fladle-plugin/src/test/resources/android-project2/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/fladle-plugin/src/test/resources/android-project2/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index b9267d76..00000000 --- a/gradle.properties +++ /dev/null @@ -1,22 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - - -kotlin.code.style=official -org.gradle.parallel=true -org.gradle.caching=true diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index 5b343e54..00000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,13 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/0b98aec810298c2c1d7fdac5dac37910/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/23adb857f3cb3cbe28750bc7faa7abc0/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/932015f6361ccaead0c6d9b8717ed96e/redirect -toolchainVendor=JETBRAINS -toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml deleted file mode 100644 index 9c952c41..00000000 --- a/gradle/libs.versions.toml +++ /dev/null @@ -1,58 +0,0 @@ -## Generated by $ ./gradlew refreshVersionsCatalog - -[plugins] -foojay = { id = "org.gradle.toolchains.foojay-resolver-convention", version = "1.0.0"} - -ben-manes-versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } - -kotlinter = { id = "org.jmailen.kotlinter", version = "5.4.2" } - -gradle-plugin-publish = {id = "com.gradle.plugin-publish", version = "2.1.1" } - -vanniktech-publish = { id = "com.vanniktech.maven.publish", version = "0.36.0" } - -agp = { id = "com.android.application", version.ref = "agp-version"} - -[versions] - -androidx-appcompat = "1.6.1" - -androidx-constraintlayout = "2.1.4" - -androidx-navigation = "2.3.0" - -androidx-test-espresso = "3.7.0" - -androidx-test-ext-junit = "1.3.0" - -androidx-test-rules = "1.7.0" - -junit-version = "4.13.2" - -agp-version = "9.0.1" - -flank-version = "23.10.1" - -[libraries] - -appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } - -constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } - -navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "androidx-navigation" } - -espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-test-espresso" } - -androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } - -androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidx-test-rules" } - -agp = { module = "com.android.tools.build:gradle", version.ref = "agp-version" } - -flank = { module = "com.github.flank:flank", version.ref = "flank-version" } - -junit = { group = "junit", name = "junit", version.ref = "junit-version" } - -gradle-enterprise = { module = "com.gradle:develocity-gradle-plugin", version = "3.19.2" } - -truth = "com.google.truth:truth:1.4.5" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index d997cfc6..00000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index dbc3ce4a..00000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew deleted file mode 100755 index 0262dcbd..00000000 --- a/gradlew +++ /dev/null @@ -1,248 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015 the original 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. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# 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 ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - - - -# 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 - if ! command -v java >/dev/null 2>&1 - then - 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 -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# 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"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index e509b2dd..00000000 --- a/gradlew.bat +++ /dev/null @@ -1,93 +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 -@rem SPDX-License-Identifier: Apache-2.0 -@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=. -@rem This is normally unused -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% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 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! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/index.html b/index.html new file mode 100644 index 00000000..3b28cdb9 --- /dev/null +++ b/index.html @@ -0,0 +1,584 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Welcome to Fladle

+

Github Actions

+

The Gradle Plugin for Firebase Test Lab and Flank.

+

Fladle is the easiest way to scale your instrumentation tests using Firebase Test Lab. Apply the gradle plugin and runFlank! You can easily run many hours of instrumentation tests using hundreds of devices in Test Lab and get the results in just few minutes. Fladle simplifies the configuration necessary to scale your tests using Firebase Test Lab and Flank.

+

Fladle is mentioned in Fragmented Podcast #163. Skip to ~29 minutes.

+

New? Get Started Here

+

Flank

+

Flank is a parallel test runner for Firebase Test Lab.

+

Also read this medium post.

+

Corporate Sponsors

+

Thank you Doist for your sponsorship!

+

Doist

+

Sponsor Fladle!

+

Trusted by

+

The following companies test their Android app using Fladle.

+

SoundCloud Zalando Robinhood +Yelp Salesforce Dropbox +ImmoScout24 GrubHub

+

Want to appear here? File an issue or open a PR

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index be9b1e1d..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,43 +0,0 @@ -# pip install -r requirements.txt -# mkdocs serve - - -extra: - fladle: - current_release: '0.20.0' - next_release: '0.20.1' - flank_version: '23.10.1' - -site_name: Fladle -site_url: https://runningcode.github.io/fladle/ -repo_name: Fladle -repo_url: https://github.com/runningcode/fladle/ -site_description: The Gradle Plugin for Firebase Test Lab and Flank -site_author: Nelson Osacky - -nav: - - Home: index.md - - Quick Start: quick-start.md - - Authentication: authentication.md - - Configuration: configuration.md - - Recipes: recipes.md - - Changelog: changelog.md - - Results: results.md - - Multi Module Testing: multi-module-testing.md - - FAQ: faq.md - - Testing Snapshots: snapshots.md - - Releasing: releasing.md - -theme: - name: 'material' - -markdown_extensions: - - codehilite - - admonition - - pymdownx.superfences - - pymdownx.inlinehilite - - pymdownx.tabbed: - -plugins: - - search - - markdownextradata: {} diff --git a/multi-module-testing/index.html b/multi-module-testing/index.html new file mode 100644 index 00000000..2880f1f9 --- /dev/null +++ b/multi-module-testing/index.html @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Multi Module Testing - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Multi-module testing

+

Multi module testing can be done by manually specifying additionalTestApks or applying the Fulladle plugin to automacally gather all the additional test apks. See also this article for a full setup and instructions including integration with CI.

+

Fulladle Plugin

+
    +
  1. +

    Apply the Fulladle plugin at the root of the project.

    +
    +
    plugins {
    +    id 'com.osacky.fulladle' version '0.20.0'
    +}
    +
    +
    +
    +
    plugins {
    +    id("com.osacky.fulladle") version "0.20.0"
    +}
    +
    +
    +
    +
  2. +
  3. +

    Configure the Fladle extension.

    +
    +
    fladle {
    +    serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-service-account.json")
    +}
    +
    +
    +
    +
    fladle {
    +    serviceAccountCredentials.set(project.layout.projectDirectory.file("flank-gradle-service-account.json"))
    +}
    +
    +
    +
    +
    +

    Warning

    +

    If using buildFlavors or testing against a non default variant, you will need to specify the variant you want to test in the fulladleModuleConfig block.

    +
    +
  4. +
  5. +

    Run the tests. + First assemble all your debug apks and test apks. +

    ./gradlew assembleDebug assembleDebugAndroidTest
    +

    +
    +

    Note

    +

    When using flavors, make sure to assemble your buildVariants as well.

    +

    ./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest

    +
    +

    Run Flank! +

    ./gradlew runFlank
    +

    +
  6. +
+

Overriding configurations in modules

+

Fulladle will pick Flank configurations from the fladle block in the root build.gradle file. You may want to override some of these configurations for certain modules, you can add the following block to any Android library module to override its configurations:

+
+
fulladleModuleConfig {
+  clientDetails = [
+      "test-type": "PR",
+      "build-number": "132"
+  ]
+  maxTestShards = 3
+  environmentVariables = [
+      "clearPackageData": "true"
+  ]
+  debugApk = "app.apk"
+  variant = "vanillaDebug"
+}
+
+
+
+
fulladleModuleConfig {
+  clientDetails.set(mapOf(
+    "test-type" to "PR",
+    "build-number" to "132",
+  ))
+  maxTestShards.set(3)
+  environmentVariables.set(mapOf(
+    "clearPackageData" to "true"
+  ))
+  debugApk.set("app.apk")
+  variant.set("vanillaDebug")
+}
+
+
+
+

All of the above configurations are optional, Flank will default to the top-level configurations if you don't override anything here. For details about these configurations, refer to configuration docs.

+

Disabling a module

+

You may want to exclude a library module from testing when using Fulladle. You can do so by setting the enabled configuration in the module's fulladleModuleConfig block like so:

+
+
fulladleModuleConfig {
+  enabled = false
+}
+
+
+
+
fulladleModuleConfig {
+  enabled.set(false)
+}
+
+
+
+

Overriding root-level config

+

Fulladle does not provide the ability to control which module ends up as the root-level module or as an additional module. Either one of app modules or library modules can become a root-level module. If a library module ends up as a root-level module, it needs to specify a debugApk in its fladle or fulladleModuleConfig block.

+

The root-level configuration (e.g. maxTestShards) can also be overridden in the fulladleModuleConfig block of whatever module gets picked as the root module.

+

Troubleshooting

+

Fulladle might still have some rough edges, but we'd love feedback. Please join us in the Firebase Community Slack with any feedback you may have. +You can also file Fladle Github issues.

+

When filing a bug report, please include the Flank version number, the Fladle version number and the output of the following:

+

./gradlew printYml

+

./gradlew runFlank -PdumpShards

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/quick-start/index.html b/quick-start/index.html new file mode 100644 index 00000000..c2518f41 --- /dev/null +++ b/quick-start/index.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Quick Start - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Quick Start

+

Using Fladle takes 3 steps:

+
    +
  1. +

    Apply the Fladle plugin. Follow instructions here

    +

    Root build.gradle

    +
    +
    buildscript {
    +  dependencies {
    +    classpath "com.osacky.flank.gradle:fladle:0.20.0"
    +  }
    +}
    +
    +
    +
    +
    buildscript {
    +  dependencies {
    +    classpath("com.osacky.flank.gradle:fladle:0.20.0")
    +  }
    +}
    +
    +
    +
    +

    Application module build.gradle

    +
    +
    apply plugin: "com.android.application"
    +apply plugin: "com.osacky.fladle"
    +
    +
    +
    +
    plugins {
    +    id ("com.android.application")
    +    id ("com.osacky.fladle")
    +}
    +
    +
    +
    +
  2. +
  3. +

    Configure Authentication using these steps.

    +
    +

    Warning

    +

    If using buildFlavors or testing against a non default variant, variant must also configured

    +
    +
  4. +
  5. +

    Run your tests!

    +

    First assemble your debug apk and test apk. +

    ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest
    +

    +
    +

    Note

    +

    When using flavors, make sure to assemble your buildVariants.

    +

    ./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest

    +
    +

    Run Flank! +

    ./gradlew runFlank
    +

    +
  6. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/recipes/index.html b/recipes/index.html new file mode 100644 index 00000000..22650ace --- /dev/null +++ b/recipes/index.html @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Recipes - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Recipes

+

Here are some recipes to use to achieve various goals in flank. For additional recipes or suggestions, please file an +issue on Github.

+

Smartly shard tests in 120 second shards across a maximum of 50 shards.

+

This recipe will keep track of test durations automatically on firebase test lab and try to split up test runs in to 120 second shards up to maximum of 50 shards.

+
+
fladle {
+    maxTestShards = 50
+    shardTime = 120
+    smartFlankGcsPath = "gs://fladle-results/smart-flank/JUnitReport.xml"
+}
+
+
+
+
fladle {
+    maxTestShards.set(50)
+    shardTime.set(120)
+    smartFlankGcsPath.set("gs://fladle-results/smart-flank/JUnitReport.xml")
+}
+
+
+
+

Run different tests on different devices with different Gradle tasks.

+

./gradlew runFlankPerfTests will execute the performance tests against a MediumPhone.arm +./gradlew runFlankRegresssionTests will execute the regressions tests against a SmallPhone.arm

+
+
fladle {
+    configs {
+        perfTests {
+            devices.set([
+                ["model" : "MediumPhone.arm", "version" : "28"], 
+                ["model" : "MediumPhone.arm", "version" : "28"]
+            ])
+            testTargets.set([
+                    "class com.sample.MyPerformanceTest"
+            ])
+        }
+        regressionTests {
+            devices.set([
+                [ "model" : "SmallPhone.arm", "version" : "28"]
+            ])
+            testTargets.set([
+                "class com.sample.MyRegressionTest"
+            ])
+        }
+    }
+}
+
+
+
+
fladle {
+    configs {
+        create("perfTests") {
+            devices.set(listOf(
+                mapOf("model" to "MediumPhone.arm", "version" to "28" ), 
+                mapOf("model" to "MediumPhone.arm", "version" to "28")
+            ))
+            testTargets.set(listOf(
+                "class com.sample.MyPerformanceTest"
+            ))
+        }
+        create("regressionTests") {
+            devices.set(listOf(
+                mapOf("model" to "SmallPhone.arm", "version" to "28" )
+            ))
+            testTargets.set(listOf(
+                "class com.sample.MyRegressionTest"
+            ))
+        }
+    }
+}
+
+
+
+

Always use the latest version of Flank

+

Use Gradle's dynamic version syntax to declare a dynamic version.

+
+

Warning

+

Dynamic versions lead to non-reproducible builds since Gradle will check for new versions periodically based on how long versions are cached.

+
+
+
flankVersion = "23.+"
+
+
+
+
flankVersion.set("23.+")
+
+
+
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/releasing/index.html b/releasing/index.html new file mode 100644 index 00000000..74ed52ad --- /dev/null +++ b/releasing/index.html @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Releasing - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Releasing

+
    +
  • +

    Create a local release branch from master +

    git checkout master
    +git pull
    +git checkout -b release_0.20.1
    +

    +
  • +
  • +

    Update version in fladle-plugin/build.gradle.kts (remove -SNAPSHOT) +

    version = "0.20.1"
    +

    +
  • +
  • +

    Update the current version and next version in mkdocs.yml: +

    extra:
    +  fladle:
    +    release: '0.20.1'
    +    next_release: 'REPLACE_WITH_NEXT_VERSION_NUMBER'
    +

    +
  • +
  • +

    Take one last look +

    git diff
    +

    +
  • +
  • +

    Commit all local changes +

    git commit -am "Prepare 0.20.1 release"
    +

    +
  • +
  • +

    Create a tag and push it +

    git tag v0.20.1
    +git push origin v0.20.1
    +

    +
  • +
+

Pushing the tag automatically triggers the release workflow which:

+
    +
  1. Publishes to Maven Central
  2. +
  3. Publishes to Gradle Plugin Portal
  4. +
  5. +

    Creates a GitHub Release with auto-generated notes

    +
  6. +
  7. +

    Release to Maven Central

    + +
  8. +
  9. +

    Merge the release branch to master +

    git checkout master
    +git pull
    +git merge --no-ff release_0.20.1
    +

    +
  10. +
  11. +

    Update version in fladle-plugin/build.gradle.kts (increase version and add -SNAPSHOT) +

    version = "REPLACE_WITH_NEXT_VERSION_NUMBER-SNAPSHOT"
    +

    +
  12. +
  13. +

    Commit your changes +

    git commit -am "Prepare for next development iteration"
    +

    +
  14. +
  15. +

    Push your changes +

    git push
    +

    +
  16. +
+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 38c2ad7f..00000000 --- a/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended" - ] -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b9585770..00000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -mkdocs -mkdocs-material>=5.1.0 -mkdocs-markdownextradata-plugin -pymdown-extensions>=7.0 diff --git a/results/index.html b/results/index.html new file mode 100644 index 00000000..57133f48 --- /dev/null +++ b/results/index.html @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Results - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Results

+

By default, results are placed in the build/fladle/results/<matrix name> directly.

+

A merged junit report is available in JUnitReport.xml

+

On failures only, an html report with links to failed shards is also generated in the results directory with the name HtmlErrorReport.html.

+

A cost report is also available CostReport.txt.

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/sample-android-library/build.gradle.kts b/sample-android-library/build.gradle.kts deleted file mode 100644 index 63a998cb..00000000 --- a/sample-android-library/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - id("com.android.library") -} - -fulladleModuleConfig { - enabled.set(true) - clientDetails.set(mapOf( - "test-type" to "PR", - "build-number" to "132", - "module-name" to project.path, - )) - maxTestShards.set(13) - environmentVariables.set(mapOf( - "clearPackageData" to "true" - )) -} - -android { - namespace = "com.osacky.flank.gradle.sample" - compileSdk = 33 - defaultConfig { - minSdk = 23 - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.navigation.fragment.ktx) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) -} - diff --git a/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt deleted file mode 100644 index 0b6fe002..00000000 --- a/sample-android-library/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.osacky.flank.gradle.sample - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun seeView() { - assert(true) - } - - @Test - fun runAndFail(): Unit = throw RuntimeException("Test failed") -} diff --git a/sample-android-library/src/main/AndroidManifest.xml b/sample-android-library/src/main/AndroidManifest.xml deleted file mode 100644 index 8072ee00..00000000 --- a/sample-android-library/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt deleted file mode 100644 index 964eb395..00000000 --- a/sample-android-library/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.osacky.flank.gradle.sample - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } -} diff --git a/sample-flavors-kotlin/build.gradle.kts b/sample-flavors-kotlin/build.gradle.kts deleted file mode 100644 index 2b131a90..00000000 --- a/sample-flavors-kotlin/build.gradle.kts +++ /dev/null @@ -1,110 +0,0 @@ -plugins { - id ("com.android.application") - id ("com.osacky.fladle") -} - -android { - namespace = "com.osacky.flank.gradle.sample.kotlin" - compileSdk = 33 - defaultConfig { - applicationId = "com.osacky.flank.gradle.sample.kotlin" - minSdk = 23 - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" - flavorDimensions += "flavor" - - productFlavors { - create("chocolate") { - dimension = "flavor" - } - create("vanilla") { - dimension = "flavor" - } - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -androidComponents { - beforeVariants(selector().withName("vanilla")) { variantBuilder -> - variantBuilder.enable = false - } -} - -fladle { - flankVersion.set("23.10.1") - variant.set("chocolateDebug") - debugApk.set(provider { layout.buildDirectory.file("/outputs/apk/chocolate/debug/*.apk").get().toString()}) - serviceAccountCredentials.set(project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json")) - // Project Id is not needed if serviceAccountCredentials are set. -// projectId("flank-gradle") - useOrchestrator.set(true) - environmentVariables.set(project.provider { mapOf("clearPackageData" to "true") }) - testTargets.set(project.provider { listOf( - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ) }) - devices.set(project.provider { listOf( - mapOf("model" to "SmallPhone.arm", "version" to "26" ), - mapOf("model" to "MediumPhone.arm", "version" to "33" ) - ) }) - smartFlankGcsPath.set("gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml") - configs { - create("oranges") { - useOrchestrator.set(false) - testTargets.set(project.provider { listOf( - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ) }) - flakyTestAttempts.set(3) - } - create("additionalTests") { - additionalTestApks.value(project.provider { listOf( - "app: ../main/app/build/output/apk/debug/app.apk", - "test: ../main/app/build/output/apk/androidTest/debug/app-test.apk", - "app: ../sample/app/build/output/apk/debug/sample-app.apk", - "test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk", - "test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk", - "test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk" - )}) - } - } - flakyTestAttempts.set(1) - dependOnAssemble.set(true) -} - -fulladleModuleConfig { - maxTestShards.set(24) - clientDetails.set( - mapOf( - "key1" to "val1" - ) - ) - environmentVariables.set( - mapOf( - "clearPackageData" to "true" - ) - ) - debugApk.set("${rootProject.file("dummy_app.apk")}") -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.navigation.fragment.ktx) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) -} - diff --git a/sample-flavors-kotlin/flank-gradle-5cf02dc90531.json b/sample-flavors-kotlin/flank-gradle-5cf02dc90531.json deleted file mode 100644 index 9e26dfee..00000000 --- a/sample-flavors-kotlin/flank-gradle-5cf02dc90531.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt deleted file mode 100644 index efa6aae8..00000000 --- a/sample-flavors-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.osacky.flank.gradle.sample - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.ActivityTestRule -import com.osacky.flank.gradle.sample.kotlin.MainActivity -import com.osacky.flank.gradle.sample.kotlin.R -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Rule - @JvmField val testRule = ActivityTestRule(MainActivity::class.java) - - @Test - fun seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())) - } - - @Test - fun runAndFail(): Unit = throw RuntimeException("Test failed") -} diff --git a/sample-flavors-kotlin/src/main/AndroidManifest.xml b/sample-flavors-kotlin/src/main/AndroidManifest.xml deleted file mode 100644 index 12058c6f..00000000 --- a/sample-flavors-kotlin/src/main/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt b/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt deleted file mode 100644 index 90fce2f0..00000000 --- a/sample-flavors-kotlin/src/main/java/com/osacky/flank/gradle/sample/kotlin/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.osacky.flank.gradle.sample.kotlin - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} diff --git a/sample-flavors-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-flavors-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 3bb4cdbc..00000000 --- a/sample-flavors-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/sample-flavors-kotlin/src/main/res/drawable/ic_launcher_background.xml b/sample-flavors-kotlin/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index e80b3ee9..00000000 --- a/sample-flavors-kotlin/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample-flavors-kotlin/src/main/res/layout/activity_main.xml b/sample-flavors-kotlin/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/sample-flavors-kotlin/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample-flavors-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png b/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 898f3ed5..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index dffca360..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png b/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64ba76f7..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index dae5e082..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5ed4659..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14ed0af3..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b0907cac..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index d8ae0315..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2c18de9e..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index beed3cdd..00000000 Binary files a/sample-flavors-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-flavors-kotlin/src/main/res/navigation/mobile_navigation.xml b/sample-flavors-kotlin/src/main/res/navigation/mobile_navigation.xml deleted file mode 100644 index da6a276d..00000000 --- a/sample-flavors-kotlin/src/main/res/navigation/mobile_navigation.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/sample-flavors-kotlin/src/main/res/values/colors.xml b/sample-flavors-kotlin/src/main/res/values/colors.xml deleted file mode 100644 index 69b22338..00000000 --- a/sample-flavors-kotlin/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #008577 - #00574B - #D81B60 - diff --git a/sample-flavors-kotlin/src/main/res/values/strings.xml b/sample-flavors-kotlin/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/sample-flavors-kotlin/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/sample-flavors-kotlin/src/main/res/values/styles.xml b/sample-flavors-kotlin/src/main/res/values/styles.xml deleted file mode 100644 index 5885930d..00000000 --- a/sample-flavors-kotlin/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/sample-kotlin/build.gradle.kts b/sample-kotlin/build.gradle.kts deleted file mode 100644 index 5d729cec..00000000 --- a/sample-kotlin/build.gradle.kts +++ /dev/null @@ -1,87 +0,0 @@ -plugins { - id ("com.android.application") - id ("com.osacky.fladle") -} - -android { - namespace = "com.osacky.flank.gradle.sample.kotlin" - compileSdk = 33 - defaultConfig { - applicationId = "com.osacky.flank.gradle.sample.kotlin" - minSdk = 23 - versionCode = 1 - versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR" -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -fladle { - flankVersion.set("23.10.1") - // Project Id is not needed if serviceAccountCredentials are set. - projectId.set("flank-gradle") - useOrchestrator.set(true) - environmentVariables.set(project.provider { mapOf("clearPackageData" to "true") }) - maxTestShards.set(10) - testTargets.set(listOf( - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - )) - devices.set(listOf( - mapOf("model" to "SmallPhone.arm", "version" to "26" ), - mapOf("model" to "MediumPhone.arm", "version" to "33" ) - )) - smartFlankGcsPath.set("gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml") - configs { - create("oranges") { - useOrchestrator.set(false) - testTargets.set(project.provider { listOf( - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ) }) - flakyTestAttempts.set(3) - } - create("additionalTests") { - additionalTestApks.value(project.provider { listOf( - "app: ../main/app/build/output/apk/debug/app.apk", - "test: ../main/app/build/output/apk/androidTest/debug/app-test.apk", - "app: ../sample/app/build/output/apk/debug/sample-app.apk", - "test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk", - "test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk", - "test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk" - )}) - } - create("perfTests") { - devices.set(listOf(mapOf("model" to "SmallPhone.arm", "version" to "28" ), mapOf("model" to "MediumPhone.arm", "version" to "33"))) - testTargets.set(listOf( - "class com.sample.MyPerformanceTest" - )) - } - create("regressionTests") { - devices.set(listOf(mapOf("model" to "SmallPhone.arm", "version" to "28" ))) - testTargets.set(listOf( - "class com.sample.MyRegressionTest" - )) - } - } - flakyTestAttempts.set(1) -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.navigation.fragment.ktx) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) -} - diff --git a/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt deleted file mode 100644 index cec17543..00000000 --- a/sample-kotlin/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.osacky.flank.gradle.sample - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.ActivityTestRule -import com.osacky.flank.gradle.sample.kotlin.R -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Rule - @JvmField val testRule = ActivityTestRule(MainActivity::class.java) - - @Test - fun seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())) - } - - @Test - fun runAndFail(): Unit = throw RuntimeException("Test failed") -} diff --git a/sample-kotlin/src/main/AndroidManifest.xml b/sample-kotlin/src/main/AndroidManifest.xml deleted file mode 100644 index 4e2143cd..00000000 --- a/sample-kotlin/src/main/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - diff --git a/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt deleted file mode 100644 index 781d1f01..00000000 --- a/sample-kotlin/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.osacky.flank.gradle.sample - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import com.osacky.flank.gradle.sample.kotlin.R - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} diff --git a/sample-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 3bb4cdbc..00000000 --- a/sample-kotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/sample-kotlin/src/main/res/drawable/ic_launcher_background.xml b/sample-kotlin/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index e80b3ee9..00000000 --- a/sample-kotlin/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample-kotlin/src/main/res/layout/activity_main.xml b/sample-kotlin/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/sample-kotlin/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample-kotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png b/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 898f3ed5..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index dffca360..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png b/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64ba76f7..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index dae5e082..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5ed4659..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14ed0af3..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b0907cac..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index d8ae0315..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2c18de9e..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index beed3cdd..00000000 Binary files a/sample-kotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample-kotlin/src/main/res/navigation/mobile_navigation.xml b/sample-kotlin/src/main/res/navigation/mobile_navigation.xml deleted file mode 100644 index da6a276d..00000000 --- a/sample-kotlin/src/main/res/navigation/mobile_navigation.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/sample-kotlin/src/main/res/values/colors.xml b/sample-kotlin/src/main/res/values/colors.xml deleted file mode 100644 index 69b22338..00000000 --- a/sample-kotlin/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #008577 - #00574B - #D81B60 - diff --git a/sample-kotlin/src/main/res/values/strings.xml b/sample-kotlin/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/sample-kotlin/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/sample-kotlin/src/main/res/values/styles.xml b/sample-kotlin/src/main/res/values/styles.xml deleted file mode 100644 index 5885930d..00000000 --- a/sample-kotlin/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/sample-kotlin/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt b/sample-kotlin/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt deleted file mode 100644 index df5c876f..00000000 --- a/sample-kotlin/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.osacky.flank.gradle.sample - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/sample/build.gradle b/sample/build.gradle deleted file mode 100644 index 71b98804..00000000 --- a/sample/build.gradle +++ /dev/null @@ -1,105 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'com.osacky.fladle' - -android { - compileSdk = 33 - namespace = "com.osacky.flank.gradle.sample" - defaultConfig { - applicationId "com.osacky.flank.gradle.sample" - minSdk 23 - targetSdk 33 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - execution 'ANDROIDX_TEST_ORCHESTRATOR' - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -fladle { - async = true - flankVersion = "23.+" - serviceAccountCredentials = project.layout.projectDirectory.file("flank-gradle-5cf02dc90531.json") - // Project Id is not needed if serviceAccountCredentials are set. -// projectId("flank-gradle") - useOrchestrator = true - environmentVariables = [ - "clearPackageData": "true" - ] - testTargets = [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView" - ] - devices = [ - [ "model": "SmallPhone.arm", "version": "26" ], - [ "model": "MediumPhone.arm", "version": "33" ] - ] - localResultsDir = "foo" - smartFlankGcsPath = "gs://test-lab-yr9w6qsdvy45q-iurp80dm95h8a/flank/test_app_android.xml" - configs { - oranges { - // Cannot use = syntax: https://github.com/gradle/gradle/issues/9987 - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - } - additionalTests { - useOrchestrator.set(false) - testTargets.set(project.provider { [ - "class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail" - ] }) - flakyTestAttempts.set(3) - additionalTestApks.value(project.provider { [ - "app: ../main/app/build/output/apk/debug/app.apk", - "test: ../main/app/build/output/apk/androidTest/debug/app-test.apk", - "app: ../sample/app/build/output/apk/debug/sample-app.apk", - "test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk", - "test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk", - "test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk" - ]}) - } - perfTests { - devices.set([[ "model" : "SmallPhone.arm", "version" : "28" ], ["model" : "MediumPhone.arm", "version": "33"]]) - testTargets.set([ - "class com.sample.MyPerformanceTest" - ]) - } - regressionTests { - devices.set([[ "model" : "SmallPhone.arm", "version" : "28" ]]) - testTargets.set([ - "class com.sample.MyRegressionTest" - ]) - } - } - flakyTestAttempts = 1 -} - -fulladleModuleConfig { - clientDetails = [ - "test-type": "PR", - "build-number": "132" - ] -} - -dependencies { - implementation(libs.appcompat) - implementation(libs.navigation.fragment.ktx) - implementation(libs.constraintlayout) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.test.rules) - androidTestImplementation(libs.androidx.test.ext.junit) - androidTestImplementation(libs.espresso.core) -} - diff --git a/sample/flank-gradle-5cf02dc90531.json b/sample/flank-gradle-5cf02dc90531.json deleted file mode 100644 index 5122b94b..00000000 --- a/sample/flank-gradle-5cf02dc90531.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "service_account", - "project_id": "foo", - "client_id": "foo", - "client_email": "myfake@gmail.com", - "private_key": "foo", - "private_key_id": "foo" -} \ No newline at end of file diff --git a/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt b/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt deleted file mode 100644 index f71566f5..00000000 --- a/sample/src/androidTest/java/com/osacky/flank/gradle/sample/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.osacky.flank.gradle.sample - -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.assertion.ViewAssertions.matches -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.ActivityTestRule -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import java.lang.RuntimeException - -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Rule - @JvmField val testRule = ActivityTestRule(MainActivity::class.java) - - @Test - fun seeView() { - onView(withId(R.id.text_view_hello)).check(matches(isDisplayed())) - } - - @Test - fun runAndFail(): Unit = throw RuntimeException("Test failed") -} diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml deleted file mode 100644 index 95244300..00000000 --- a/sample/src/main/AndroidManifest.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt b/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt deleted file mode 100644 index bed8adcc..00000000 --- a/sample/src/main/java/com/osacky/flank/gradle/sample/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.osacky.flank.gradle.sample - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 3bb4cdbc..00000000 --- a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index e80b3ee9..00000000 --- a/sample/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml deleted file mode 100644 index 36e1986f..00000000 --- a/sample/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index bbd3e021..00000000 --- a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 898f3ed5..00000000 Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index dffca360..00000000 Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64ba76f7..00000000 Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index dae5e082..00000000 Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5ed4659..00000000 Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14ed0af3..00000000 Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index b0907cac..00000000 Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index d8ae0315..00000000 Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 2c18de9e..00000000 Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index beed3cdd..00000000 Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/sample/src/main/res/navigation/mobile_navigation.xml b/sample/src/main/res/navigation/mobile_navigation.xml deleted file mode 100644 index da6a276d..00000000 --- a/sample/src/main/res/navigation/mobile_navigation.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml deleted file mode 100644 index 69b22338..00000000 --- a/sample/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #008577 - #00574B - #D81B60 - diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml deleted file mode 100644 index 14e3fe3b..00000000 --- a/sample/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Flank Gradle Plugin Sample - diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml deleted file mode 100644 index 5885930d..00000000 --- a/sample/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/sample/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt b/sample/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt deleted file mode 100644 index df5c876f..00000000 --- a/sample/src/test/java/com/osacky/flank/gradle/sample/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.osacky.flank.gradle.sample - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 00000000..7a94e455 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome to Fladle","text":"

The Gradle Plugin for Firebase Test Lab and Flank.

Fladle is the easiest way to scale your instrumentation tests using Firebase Test Lab. Apply the gradle plugin and runFlank! You can easily run many hours of instrumentation tests using hundreds of devices in Test Lab and get the results in just few minutes. Fladle simplifies the configuration necessary to scale your tests using Firebase Test Lab and Flank.

Fladle is mentioned in Fragmented Podcast #163. Skip to ~29 minutes.

"},{"location":"#new-get-started-here","title":"New? Get Started Here","text":""},{"location":"#flank","title":"Flank","text":"

Flank is a parallel test runner for Firebase Test Lab.

Also read this medium post.

"},{"location":"#corporate-sponsors","title":"Corporate Sponsors","text":"

Thank you Doist for your sponsorship!

Sponsor Fladle!

"},{"location":"#trusted-by","title":"Trusted by","text":"

The following companies test their Android app using Fladle.

Want to appear here? File an issue or open a PR

"},{"location":"authentication/","title":"Authentication","text":"

There are two authentication mechanisms for using Fladle.

It is recommended to use user authentication on local development machines to avoid sharing credentials and a service account on CI.

"},{"location":"authentication/#user-authentication","title":"User authentication","text":"

Credentials are stored in ~/.flank.

  1. ./gradlew flankAuth
  2. Sign in to web browser.
  3. Specify projectId in fladle configuration
  4. ./gradlew runFlank
"},{"location":"authentication/#service-account-credentials","title":"Service account credentials","text":"
  1. Create a service account. Service accounts aren't subject to spam checks or captcha prompts, which could otherwise block your CI builds. Create a service account with an Editor role in the Google Cloud Platform console.

  2. Enable required APIs. After logging in using the service account: In the Google Developers Console API Library page, enable the Google Cloud Testing API and Cloud Tool Results API. To enable these APIs, type these API names into the search box at the top of the console, and then click Enable API on the overview page for that API.

  3. After creating the account go to Keys, click Add Key -> Create a new Key. Select JSON for the key type. This will download the json credentials.

  4. Configure the Fladle extension serviceAccountCredentials to point to the credentials.

Above instructions are based on Google instruction for authenticating with CI.

See also Flank's instructions for authenticating with a service account.

"},{"location":"changelog/","title":"Changelog","text":""},{"location":"changelog/#unreleased","title":"Unreleased","text":""},{"location":"changelog/#0210","title":"0.21.0","text":"
  • Minimum required Gradle version is now 9.1
  • Fixed support for Android Gradle Plugin version 9.0.1
"},{"location":"changelog/#0200","title":"0.20.0","text":"
  • Botched release. Do not use.
"},{"location":"changelog/#0190","title":"0.19.0","text":"
  • Minimum required JVM version is now 17.
  • Minimum supported Gradle version is now 7.3.
  • Fix support for Gradle 9.0.0. PR Thanks kevinguitar
"},{"location":"changelog/#0180","title":"0.18.0","text":"
  • Use non-deprecated device models and versions in default config. PR Thanks Kaibolay
"},{"location":"changelog/#0175","title":"0.17.5","text":"
  • Support Develocity JUnit XML Reporting PR Thanks ZacSweers
  • Min supported Gradle version is now 6.5
  • Updated Flank version to 23.10.1 *
"},{"location":"changelog/#0174","title":"0.17.4","text":"
  • Add support for additionalTestApks in YamlConfigWriterTask. PR. Thanks ZacSweers
  • Add abi option to support selecting a particular debug APK in projects that use ABI splits to produce multiple APKs. PR
"},{"location":"changelog/#0172","title":"0.17.2","text":"
  • Fix configuration cache support.
"},{"location":"changelog/#0170","title":"0.17.0","text":"
  • Fix quotes around environment variables and formatting issues.
"},{"location":"changelog/#0163","title":"0.16.3","text":"
  • Remove deprecation warning using main in JavaExec task.
  • Conditionally declare outputs on FlankExecutionTask and mark as not up-to-date. PR
  • Fix environmentVariables not passed to flank.yml PR Thanks Sinan
"},{"location":"changelog/#0162","title":"0.16.2","text":"
  • Fix crash accessing TestedExtension in non-android modules PR Thanks asadsalman
"},{"location":"changelog/#0161","title":"0.16.1","text":"
  • Treat app and library modules the same in Fulladle PR Thanks asadsalman
  • Added fulladleModuleConfig to app modules PR

Breaking API Change

Previously, only app modules could be picked up as root-level modules in Fulladle. This has changed, now either app or library modules can be picked up (though we try app modules first). If a library module is picked up as a the root-level module, it must specifiy a debugApk through either the root fladle block or the module's own fulladleModuleConfig block.

"},{"location":"changelog/#0160","title":"0.16.0","text":"
  • Allow excluding modules from Fulladle PR
  • Allow configuring extra parameters on additionalTestApks PR
"},{"location":"changelog/#0151","title":"0.15.1","text":"
  • Allow Flank snapshot usage PR Thanks AndrewReitz
  • Fix incorrect task group name. PR
  • Snapshots are now compatible with java 8 PR
  • Configuration validation happens at task execution time. Fixes #239
  • Configure assemble dependency per variant. Fixes #233
  • Add required smartFlankGcsPath to samples Fixes #236
"},{"location":"changelog/#0150","title":"0.15.0","text":"
  • Add support for async flag. PR.
  • Add flag for depending on assembling of debug apk and instrumentation apk automatically. dependOnAssemble PR Thanks asadsalman
  • Gradle 7.0 Compatibility
"},{"location":"changelog/#0141","title":"0.14.1","text":"
  • Support new Flank options without updating Fladle Fixes #146 PR Thanks piotradamczyk5
"},{"location":"changelog/#0140","title":"0.14.0","text":"
  • Bump Flank version to 21.01.1
  • Add support for test-targets-for-shard Fixes #205
  • Add support for new flank options PR#211 Thanks pawelpasterz
  • Deprecate testShards Fixes #204 PR#212 Thanks pawelpasterz
  • Write test results into a config-specific directory PR#194 Thanks pawelpasterz
"},{"location":"changelog/#0131","title":"0.13.1","text":"
  • Fix flankAuth task throwing exception. Fixes #195
  • Add support for newly added flank options PR#186 Thanks pawelpasterz:
    • default-test-time
    • default-class-test-time
    • additional-apks
    • use-average-test-time-for-new-tests
    • disable-results-upload
"},{"location":"changelog/#0130","title":"0.13.0","text":"
  • Add support for sanityRobo tests Fixes #165 PR Thanks pawelpasterz
  • Add support for user authentication.
  • Bump Flank to 20.09.3

Breaking API Change

Use lazy properties Fixes #92 PR Thanks pawelpasterz

"},{"location":"changelog/#0121","title":"0.12.1","text":"
  • Don't override debug or instrumentation apk if already specified. Fixes #172.
"},{"location":"changelog/#0120","title":"0.12.0","text":"
  • Don't override property values in individual configurations. Fixes #158.
  • Update Flank to 20.08.3.
  • Use Github actions instead of CircleCI
  • Write Yaml file to task specific output directory and add up-to-date checks. #159 Fixes #147 Thanks CristianGM
  • Add support for full-junit-result and legacy-junit-result. #170 Fixes #157. Thanks MatthewTPage
"},{"location":"changelog/#0110","title":"0.11.0","text":"
  • Update Flank to 20.07.0.
  • Only add additional-test-apks for modules that have tests. PR
  • Experimental configuration caching support. PR1PR2
"},{"location":"changelog/#0102","title":"0.10.2","text":"
  • Update Flank to 20.06.2.
  • Fix duplicated status messages in console. PR Thanks francescocervone

Breaking API change

Add time units for timeout. timeoutMin has been renamed to testTimeout. PR Thanks pawelpasterz

"},{"location":"changelog/#0101","title":"0.10.1","text":"
  • [Fix] Allow for specifying roboScript without specifying instrumentationApk. Fixes #128.
"},{"location":"changelog/#0100","title":"0.10.0","text":"
  • Allow for debugging using --dump-shards
  • Fix naming for variant discovery of apk and instrumentation apk. Instead of chocolate-debug, variant must now be set as chocolateDebug.
  • Update Flank to 20.05.2.
  • Fulladle Preview

Breaking API Change

additionalTestApks now uses ListProperty instead of the previous Map. This is to allow for lazy configuration of the provided files.

Warning

Minimum supported Gradle version is now 5.4.

"},{"location":"changelog/#094","title":"0.9.4","text":"
  • Update Flank to 20.05.1. Huge new release!
  • Add support for new flank flags. Thanks pawelpasterz PR
  • Use compileOnly for AGP version.
"},{"location":"changelog/#092","title":"0.9.2","text":"

Breaking API change

debugApk and instrumentationApk now use Lazy Property API to avoid resolving at configuration time.

"},{"location":"changelog/#091","title":"0.9.1","text":"
  • Bugfix: ability to set flank version. PR

Breaking API Change

serviceAccountCredentials now uses Lazy Property API. See Configuration for details on how to set it. PR

Warning

Minimum required Gradle Version is now 5.1.

Warning

Dropped support for Flank 7.X and lower.

"},{"location":"changelog/#090","title":"0.9.0","text":"
  • Do not add flank maven repo. PR
  • Allow specifying custom flank coordinates. PR
  • Change ordering and use file provider. PR
"},{"location":"changelog/#081","title":"0.8.1","text":"
  • Add support for additionalTestApks. PR Thanks japplin.
  • Add support for resultsDir. PR
"},{"location":"changelog/#080","title":"0.8.0","text":"
  • BREAKING: devices now takes a List<Map<String, String>> instead of a List<Device>. See the [#README.md] for an example. PR Thanks zlippard.
  • Add support for keep-file-path. PR Thanks tahirhajizada.
"},{"location":"changelog/#070","title":"0.7.0","text":"
  • Add support for Flank 8 and bump version. PR Thanks francescocervone
"},{"location":"changelog/#067","title":"0.6.7","text":"
  • Allow using wildcards in debugApk or instrumentationApk path by not checking that file exists. PR
"},{"location":"changelog/#066","title":"0.6.6","text":"
  • Bump flank version to 7.0.0
  • Publish to mavenCentral()
"},{"location":"changelog/#065","title":"0.6.5","text":"
  • Add support for results-bucket gcloud config option. PR Thanks c-moss
  • Default flank version 6.2.3
  • Lower build logging level
"},{"location":"changelog/#064","title":"0.6.4","text":"
  • Allow setting android version number as string to allow for preview versions. PRThanks JeroenMols.
"},{"location":"changelog/#063","title":"0.6.3","text":"
  • Allow service credentials to be set using environment variables. Fixes #55
  • Fix not being able to set flankVersion. Fixes #56
"},{"location":"changelog/#062","title":"0.6.2","text":"
  • Fix shardTime config property not written to flank.yml Thanks nnoel-grubhub
"},{"location":"changelog/#061","title":"0.6.1","text":"
  • Fix project id spacing Thanks andersu for reporting.
"},{"location":"changelog/#060","title":"0.6.0","text":"
  • Default flank version 5.0.1
  • Rename yaml output to match new flank version.
  • Add support for shard time.
"},{"location":"changelog/#052","title":"0.5.2","text":"
  • Improve error messages for missing arguments in the fladle extension.
"},{"location":"changelog/#051","title":"0.5.1","text":"
  • Test multipleconfig to actually write yml Thanks PR winterDroid.
  • JavaExec uses classpath instead of jar directly. Thanks PR winterDroid.
"},{"location":"changelog/#050","title":"0.5.0","text":"
  • Use flank as a maven artifact.
  • Group Fladle Tasks
"},{"location":"changelog/#041","title":"0.4.1","text":"
  • Detect AndroidX test orchestrator
  • Fix detection of debug and instrumentation apk paths.
"},{"location":"changelog/#040","title":"0.4.0","text":"
  • Add support for all configuration options. Thanks PR winterDroid.
"},{"location":"changelog/#breaking-changes","title":"Breaking Changes:","text":"

Previous users of clearPackageData = true will now need to use:

environmentVariables = [\n  \"clearPackageData\": \"true\"\n]\n

"},{"location":"changelog/#038","title":"0.3.8","text":"
  • Fix broken flakyTestAttempts.
"},{"location":"changelog/#037","title":"0.3.7","text":"
  • Add support for flakyTestAttempts.
"},{"location":"changelog/#036","title":"0.3.6","text":"
  • Add support for environment variable clearPackageData. Thanks @anderssu !
"},{"location":"changelog/#035","title":"0.3.5","text":"
  • Automatically configure the use of test orchestrator.
"},{"location":"changelog/#034","title":"0.3.4","text":"
  • Add support for setting results-history-name.
  • Add support for selecting variant to test instead of apk path.
"},{"location":"changelog/#033","title":"0.3.3","text":"
  • Add support for setting smartFlankGcsPath
  • Capitalize task names.
"},{"location":"changelog/#032","title":"0.3.2","text":"
  • Actually fix gradle download task bug.
"},{"location":"changelog/#031","title":"0.3.1","text":"
  • Maybe fix bug similar to: https://github.com/michel-kraemer/gradle-download-task/issues/58
"},{"location":"changelog/#030","title":"0.3.0","text":"
  • Add support for multiple test configurations.
"},{"location":"changelog/#0212","title":"0.2.12","text":"
  • Fix broken flankDoctor task.
"},{"location":"changelog/#0211","title":"0.2.11","text":"
  • Add minimum Gradle version check. (4.9 is required because we use the lazy task configuration API))
"},{"location":"changelog/#0210_1","title":"0.2.10","text":"
  • Add support for specifying Flank snapshot versions. See README for configuration options.
"},{"location":"changelog/#029","title":"0.2.9","text":"
  • Add support for multiple build targets. PR. Thanks winterDroid.
  • Add support for testShards and repeatTests flank options. See README for configuration options.
"},{"location":"configuration/","title":"Configuration","text":"

The following configuration options must be set inside the fladle block. See the sample configuration below. There is also a groovy sample and a kotlin sample.

"},{"location":"configuration/#serviceaccountcredentials","title":"serviceAccountCredentials","text":"

User Authentication is also possible. See Authentication.

Groovy
serviceAccountCredentials = project.layout.projectDirectory.file(\"flank-gradle-5cf02dc90531.json\")\n
Kotlin
serviceAccountCredentials.set(\"project.layout.projectDirectory.file(\"flank-gradle-5cf02dc90531.json\")\n

Optionally, the serviceAccountCredentials can be set with environment variables but then the projectId parameter must be set.

See Authentication for more info.

"},{"location":"configuration/#variant","title":"variant","text":"

Note

variant must be set if using buildFlavors in order to automatically configure the debugApk and testApk.

Set the variant to automatically configure for testing. A build variant is a combination of buildFlavor and buildType. This must also be set when testing against a non-default variant. For example: 'debug' or 'freeDebug'. Put this inside your Fladle block.

Groovy
variant = \"freeDebug\"\n
Kotlin
variant.set(\"freeDebug\")\n
"},{"location":"configuration/#sample-configuration","title":"Sample Configuration","text":"
fladle {\n// Required parameters\nserviceAccountCredentials = project.layout.projectDirectory.file(\"flank-gradle-5cf02dc90531.json\")\nvariant = \"freeDebug\"\n\n// Optional parameters\nuseOrchestrator = false\nenvironmentVariables = [\n\"clearPackageData\": \"true\"\n]\ndirectoriesToPull = [\n\"/sdcard/screenshots\"\n]\nfilesToDownload = [\n\".*/screenshots/.*\"\n]\ntestTargets = [\n\"class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView\"\n]\ntestTimeout = \"15m\"\nrecordVideo = false\nperformanceMetrics = false\ndevices = [\n[ \"model\": \"SmallPhone.arm\", \"version\": \"28\" ],\n[ \"model\": \"MediumPhone.arm\", \"version\": \"33\" ]\n]\nprojectId(\"flank-gradle\")\nflankVersion = \"23.10.1\"\ndebugApk = \"$buildDir/outputs/apk/debug/sample-debug.apk\"\ninstrumentationApk = \"$buildDir/outputs/apk/androidTest/debug/sample-debug-androidTest.apk\"\nadditionalTestApks = [\n\"- app:  $buildDir/outputs/apk/debug/sample-debug.apk\",\n\"  test: $buildDir/outputs/apk/androidTest/debug/sample2-debug-androidTest.apk\",\n\"- test: ${rootProject.buildDir}/database/module/outputs/apk/database-module-androidTest.apk\"\n]\nautoGoogleLogin = true\nmaxTestShards = 8 //or numUniformShards=5 cannot use both.\nshardTime = 120\nsmartFlankGcsPath = \"gs://tmp_flank/flank/test_app_android.xml\"\nconfigs {\noranges {\nuseOrchestrator.set(false)\ntestTargets.set(project.provider { [\n\"class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#runAndFail\"\n] })\n}\n}\nresultsBucket(\"my-results-bucket-name\")\nkeepFilePath = true\nrunTimout = \"45m\"\nignoreFailedTests = false\ndisableSharding = false\nsmartFlankDisableUpload = false\ntestRunnerClass = \"com.foo.TestRunner\"\nlocalResultsDir = \"flank\"\nclientDetails = [\n\"key1\": \"value1\",\n\"key2\": \"value2\"\n]\ntestTargetsAlwaysRun = [\n\"com.example.TestSuite#test1\",\n\"com.example.TestSuite#test2\"\n]\notherFiles = [\n\"/sdcard/dir1/file1.txt\": \"/my/example/path/file1.txt\",\n\"/sdcard/dir2/file2.txt\": \"/my/example/path/file2.txt\"\n]\nnetworkProfile = \"LTE\"\nroboDirectives = [\n[\"click\", \"button1\", \"\"],\n[\"ignore\", \"button2\"],\n[\"text\", \"field1\", \"my text\"],\n]\noutputStyle = 'multi'\nlegacyJunitResult = false\nfullJunitResult = false\nadditionalApks = [\n\"gs://path/to/app1.apk\",\n\"localPath/to/app2.apk\"\n]\ndefaultTestTime = 5.3\ndefaultClassTestTime = 180.5\nuseAverageTestTimeForNewTests = true\ndisableResultsUpload = true\ngrantPermissions = \"none\"\ntype = \"game-loop\"\nscenarioLabels = [\n\"label1\",\n\"label2\" ]\nscenarioNumbers = [ 1, 23, 52 ]\nobbFiles = [\n\"local/file/path/test1.obb\",\n\"local/file/path/test2.obb\"\n]\nobbNames = [\n\"patch.0300110.com.example.android.obb\",\n\"patch.0300111.com.example.android.obb\"\n]\ntestTargetsForShard = [\n\"package com.package1.for.shard1, com.package2.for.shard1\",\n\"class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2\",\n\"class com.foo.ClassForShard3; package com.package.for.shard3\"\n]\nfailFast = true\ndependOnAssemble = true\n}\n
"},{"location":"configuration/#useorchestrator","title":"useOrchestrator","text":"

Whether or not we should use the android test orchestrator to run this tests. Set this to true when the build.gradle file includes testOptions.execution 'ANDROID_TEST_ORCHESTRATOR'

Groovy
useOrchestrator = true\n
Kotlin
useOrchestrator.set(true)\n
"},{"location":"configuration/#testtargets","title":"testTargets","text":"

Set multiple testTargets to be run by Flank. These are used to whitelist or blacklist test classes, test cases and test annotations. See Google Cloud Firebase docs for more information.

Groovy
testTargets = [\n\"class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView\"\n]\n
Kotlin
testTargets.set(listOf(\n\"class com.osacky.flank.gradle.sample.ExampleInstrumentedTest#seeView\"\n))\n
"},{"location":"configuration/#devices","title":"devices","text":"

A list of devices to run the tests against. When list is empty, a default device will be used. Each device entry is a map. The valid keys in the map are model, version, orientation, and locale. When a key is not set or is null, a default value will be used.

Groovy
devices = [\n[ \"model\": \"MediumPhone.arm\", \"version\": \"26\" ],\n[ \"model\": \"MediumPhone.arm\", \"version\": \"33\" ]\n]\n
Kotlin
devices.set(listOf(\nmapOf(\"model\" to \"MediumPhone.arm\", \"version\" to \"26\" ),\nmapOf(\"model\" to \"MediumPhone.arm\", \"version\" to \"33\" )\n))\n
"},{"location":"configuration/#projectid","title":"projectId","text":"

The projectId is a unique identifier which can be found in the project's URL: https://console.firebase.google.com/project/<projectId> This is automatically discovered based on the service credential by default.

Groovy
projectId = \"flank-gradle\"\n
Kotlin
projectId.set(\"flank-gradle\")\n
"},{"location":"configuration/#flankversion","title":"flankVersion","text":"

Need a different Flank version? Specify it with flankVersion.

To use a snapshot:

Groovy
flankVersion = \"flank_snapshot\"`\n
Kotlin
flankVersion.set(\"flank_snapshot\")\n

Need more than 50 shards? Use Flank 8.1.0.

To use a different version:

Groovy
flankVersion = \"23.10.1\"\n
Kotlin
flankVersion.set(\"23.10.1\")\n
"},{"location":"configuration/#flankcoordinates","title":"flankCoordinates","text":"

Specify custom flank maven coordinates.

Groovy
flankCoordinates = \"com.github.flank:flank\"\n
Kotlin
flankCoordinates.set(\"com.github.flank:flank\")\n
"},{"location":"configuration/#debugapk","title":"debugApk","text":"

This is a string representing the path to the app's debug apk. Supports wildcard characters. Optional, prefer to set variant.

Groovy
debugApk = project.provider { \"${buildDir.toString()}/outputs/apk/debug/*.apk\" }\n
Kotlin
debugApk.set(project.provider { \"${buildDir.toString()}/outputs/apk/debug/*.apk\" })\n
"},{"location":"configuration/#instrumentationapk","title":"instrumentationApk","text":"

This is a string representing the path to the app's instrumentaiton apk. Supports wildcard characters. Optional, prefer to set variant. InstrumenationApk should not be set when using roboScript.

Groovy
instrumentationApk = project.provider { \"${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk\" }\n
Kotlin
instrumentationApk.set(project.provider { \"${buildDir.toString()}/outputs/apk/androidTest/debug/*.apk\" })\n
"},{"location":"configuration/#additionaltestapks","title":"additionalTestApks","text":"

Paths to additional test configurations. Order matters. A test apk is run with the nearest previous listed app apk. For library modules, add them to the list with a - test: in front. For test apks which belong to an application module, add them with test:. It is not required to list an app apk here. If there is no app apk listed in additionalTestApks, the test apks are run against the debugApk.

Groovy
additionalTestApks.value(project.provider { [\n\"- app: ../main/app/build/output/apk/debug/app.apk\",\n\"  test: ../main/app/build/output/apk/androidTest/debug/app-test.apk\",\n\"- app: ../sample/app/build/output/apk/debug/sample-app.apk\",\n\"  test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk\",\n\"- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk\",\n\"- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk\"\n]})\n
Kotlin
additionalTestApks.value(project.provider { listOf(\n\"- app: ../main/app/build/output/apk/debug/app.apk\",\n\"  test: ../main/app/build/output/apk/androidTest/debug/app-test.apk\",\n\"- app: ../sample/app/build/output/apk/debug/sample-app.apk\",\n\"  test: ../sample/app/build/output/apk/androidTest/debug/sample-app-test.apk\",\n\"- test: ../feature/room/build/output/apk/androidTest/debug/feature-room-test.apk\",\n\"- test: ../library/databases/build/output/apk/androidTest/debug/sample-databases-test.apk\"\n)})\n
"},{"location":"configuration/#autogooglelogin","title":"autoGoogleLogin","text":"

Whether to automatically log in using a preconfigured google account. More Info

Groovy
autoGoogleLogin = false\n
Kotlin
autoGoogleLogin.set(false)\n
"},{"location":"configuration/#environmentvariables","title":"environmentVariables","text":"

Environment variables are mirrored as extra options to the am instrument -e KEY1 VALUE1 \u2026 command and passed to your test runner (typically AndroidJUnitRunner). Examples

Groovy
environmentVariables = [\n// Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner)\n\"clearPackageData\": \"true\"\n]\n
Kotlin
environmentVariables = mapOf(\n// Whether or not to remove all shared state from your device's CPU and memory after each test. [More info](https://developer.android.com/training/testing/junit-runner)\n\"clearPackageData\" to \"true\"\n)\n
"},{"location":"configuration/#testshards","title":"testShards","text":"

Deprecated

Use maxTestShards instead.

The maximum number of shards. Fladle will throw an error when used together with maxTestShards or numUniformShards.

Groovy
testShards = 5\n
Kotlin
testShards.set(5)\n
"},{"location":"configuration/#maxtestshards","title":"maxTestShards","text":"

The maximum number of shards. Fladle will throw an error when used together with testShards or numUniformShards.

Groovy
maxTestShards = 8\n
Kotlin
maxTestShards.set(8)\n
"},{"location":"configuration/#shardtime","title":"shardTime","text":"

The amount of time tests within a shard should take. When set to > 0, the shard count is dynamically set based on time up to the maximum limit defined by maxTestShards 2 minutes (120) is recommended. default: -1 (unlimited)

Groovy
shardTime = 120\n
Kotlin
shardTime.set(120)\n
"},{"location":"configuration/#repeattests","title":"repeatTests","text":"

The number of times to repeat each test. Uses Flank's default value when not specified.

Groovy
repeatTests = 1\n
Kotlin
repeatTests.set(1)\n
"},{"location":"configuration/#configs","title":"configs","text":"

Give a name to a custom flank task and configure its options. The name is appended to the end of the flank task. For example runFlank becomes runFlank<name>.

Groovy
configs {\nrepeatOneHundred {\n// DSL sugar for container elements is missing (= syntax): https://github.com/gradle/gradle/issues/9987\nrepeatTests.set(100)\n}\n}\n
Kotlin
configs {\ncreate(\"repeatOneHundred\") {\nrepeatTests.set(100)\n}\n}\n

In the above example, the configuration is inherited from the outer fladle config but with the repeatTests property set to 100. Running runFlankRepeateOneHundred will execute this custom configuration.

"},{"location":"configuration/#smartflankgcspath","title":"smartFlankGcsPath","text":"

The cloud storage location for historical test runs. This must be set in order to use smart sharding. The amount of shards used is set by maxTestShards. The bucket (such as flank_data in the example) must already exist in order for it to be used.

Groovy
smartFlankGcsPath = 'gs://flank_data/results/JUnitReport.xml'\n
Kotlin
smartFlankGcsPath.set(\"gs://flank_data/results/JUnitReport.xml\")\n
"},{"location":"configuration/#resultshistoryname","title":"resultsHistoryName","text":"

The history name for your test results (an arbitrary string label; default: the application's label from the APK manifest). All tests which use the same history name will have their results grouped together in the Firebase console in a time-ordered test history list.

Groovy
resultsHistoryName = 'android-history'\n
Kotlin
resultsHistoryName.set(\"android-history\")\n
"},{"location":"configuration/#flakytestattempts","title":"flakyTestAttempts","text":"

The number of times to retry failed tests. Default is 0. Max is 10. Setting the value to 1 will mean that test are retried once. If the test fails then succeeds after the retry the run will be marked as \"successful\". The matrix with a flaky test will be marked as flaky.

Groovy
flakyTestAttempts = 0\n
Kotlin
flakyTestAttempts.set(0)\n
"},{"location":"configuration/#abi","title":"abi","text":"

The ABI split of the application that should be tested (e.g. \"x86\"). Only required if the application under test uses ABI splits and the debug APK is selected automatically (via variant) instead of manually (via debugApk).

If the application uses ABI splits, and this property isn't specified, an arbitrary ABI split will be selected.

Groovy
abi = \"arm64-v8a\"\n
Kotlin
abi.set(\"arm64-v8a\")\n
"},{"location":"configuration/#directoriestopull","title":"directoriesToPull","text":"

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. 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.

Groovy
directoriesToPull = [\n'/sdcard/tempDir1', '/data/local/tmp/tempDir2'\n]\n
Kotlin
directoriesToPull.set(listOf(\n\"/sdcard/tempDir1\", \"/data/local/tmp/tempDir2\"\n))\n
"},{"location":"configuration/#filestodownload","title":"filesToDownload","text":"

List of regex that is matched against bucket paths (for example: 2019-01-09_00:13:06.106000_YCKl/shard_0/SmallPhone.arm-28-en-portrait/bugreport.txt) for files to be downloaded after a flank run. The results are downloaded to the APP_MODULE/build/fladle/RESULTS directory where RESULTS can be set by localResultsDir var otherwise defaulting to results/.

Groovy
filesToDownload = [\n'.*/sdcard/tempDir1/.*', '.*/data/local/tmp/tempDir2/.*'\n]\n
Kotlin
filesToDownload.set(listOf(\n\".*/sdcard/tempDir1/.*\", \".*/data/local/tmp/tempDir2/.*\"\n))\n
"},{"location":"configuration/#testtimeout","title":"testTimeout","text":"

The max time test execution can run before it is cancelled (default: 15m). It does not include any time necessary to prepare and clean up the target device. The maximum possible testing time is 45m on physical devices and 60m on virtual devices. The TIMEOUT units can be h, m, or s. If no unit is given, seconds are assumed. Examples: * 1h -> 1 hour * 5m -> 5 minutes * 200s -> 200 seconds * 100 -> 100 seconds

Groovy
testTimeout = \"1h\"\n
Kotlin
testTimeout.set(\"1h\")\n
"},{"location":"configuration/#recordvideo","title":"recordVideo","text":"

Enable video recording during the test. Enabled by default.

Groovy
recordVideo = true\n
Kotlin
recordVideo.set(true)\n
"},{"location":"configuration/#performancemetrics","title":"performanceMetrics","text":"

Monitor and record performance metrics: CPU, memory, network usage, and FPS (game-loop only). Enabled by default.

Groovy
performanceMetrics = true\n
Kotlin
performanceMetrics.set(true)\n
"},{"location":"configuration/#resultsbucket","title":"resultsBucket","text":"

The name of a Google Cloud Storage bucket where raw test results will be stored.

Groovy
resultsBucket = \"my-gcs-bucket-name\"\n
Kotlin
resultsBucket.set(\"my-gcs-bucket-name\")\n
"},{"location":"configuration/#keepfilepath","title":"keepFilePath","text":"

Keeps the full path of downloaded files from a Google Cloud Storage bucket. Required when file names are not unique. Disabled by default.

Groovy
keepFilePath = false\n
Kotlin
keepFilePath.set(false)\n
"},{"location":"configuration/#resultsdir","title":"resultsDir","text":"

The name of a unique Google Cloud Storage object within the results bucket where raw test results will be stored. The default is a timestamp with a random suffix.

Groovy
resultsDir = \"result-dir-${getTimeStamp()}\"\n
Kotlin
resultsDir.set(\"result-dir-${getTimeStamp()}\")\n
"},{"location":"configuration/#disablesharding","title":"disableSharding","text":"

Disables sharding. All tests will run on the same device. Useful for parameterized tests which do not support sharding. (default: false)

Groovy
disableSharding = false\n
Kotlin
disableSharding.set(false)\n
"},{"location":"configuration/#smartflankdisableupload","title":"smartFlankDisableUpload","text":"

Disables smart flank JUnit XML uploading. Useful for preventing timing data from being updated. (default: false) What is Smart Flank?

Groovy
smartFlankDisableUpload = false\n
Kotlin
smartFlankDisableUpload.set(false)\n
"},{"location":"configuration/#testrunnerclass","title":"testRunnerClass","text":"

The fully-qualified Java class name of the instrumentation test runner (default: the test manifest is parsed to determine the class name).

Groovy
testRunnerClass = \"com.example.MyCustomTestRunner\"\n
Kotlin
testRunnerClass.set(\"com.example.MyCustomTestRunner\")\n
"},{"location":"configuration/#localresultsdir","title":"localResultsDir","text":"

The local directory to store the test results. Folder is DELETED before each run to ensure only artifacts from the new run are saved. This directory is relative to the working directory of Flank which is by default build/fladle or build/fladle/<flavorname>.

Groovy
localResultsDir = \"my-results-dir\"\n
Kotlin
localResultsDir.set(\"my-results-dir\")\n
"},{"location":"configuration/#testtargetsalwaysrun","title":"testTargetsAlwaysRun","text":"

Always run - these tests are inserted at the beginning of every shard. Useful if you need to grant permissions or login before other tests run

Groovy
testTargetsAlwaysRun = [\n'class com.example.MyTestClass'\n]\n
Kotlin
testTargetsAlwaysRun.set(listOf(\n\"class com.example.MyTestClass\"\n))\n

The flags below are only available with Flank 20.05.0 or higher.

"},{"location":"configuration/#runtimeout","title":"runTimeout","text":"

The max time this test run can execute before it is cancelled. s (seconds), m (minutes), h (hours) suffixes are acceptable, mixes like 1h45m are currently not supported (default: unlimited). Examples:

  • 20, 20s -> 20 seconds
  • 30m -> 30 minutes
  • 2h -> 2 hours
Groovy
runTimeout = \"15m\"\n
Kotlin
runTimeout.set(\"15m\")\n
"},{"location":"configuration/#ignorefailedtests","title":"ignoreFailedTests","text":"

Always return successful task completion even when there are failed tests. Useful when parsing JUnit XML to determine failure. (default: false)

Groovy
ignoreFailedTests = false\n
Kotlin
ignoreFailedTests.set(false)\n
"},{"location":"configuration/#numuniformshards","title":"numUniformShards","text":"

Specifies the number of shards into which you want to evenly distribute test cases. The shards are run in parallel on separate devices. For example, if your test execution contains 20 test cases and you specify four shards, each shard executes five test cases. The number of shards should be less than the total number of test cases. The number of shards specified must be >= 1 and <= 50. This option cannot be used along maxTestShards and is not compatible with smart sharding (Smart Flank). If you want to take benefits of smart sharding use maxTestShards instead. (default: null)

Groovy
numUniformShards = 50\n
Kotlin
numUniformShards.set(50)\n
"},{"location":"configuration/#clientdetails","title":"clientDetails","text":"

A key-value map of additional details to attach to the test matrix.(clientDetails in Google Cloud Docs) Arbitrary key-value pairs may be attached to a test matrix to provide additional context about the tests being run. When consuming the test results, such as in Cloud Functions or a CI system, these details can add additional context such as a link to the corresponding pull request. (Access Client Details). These can be used to provide additional context about the environment where the tests are being run.

Groovy
clientDetails = [\n\"test-type\": \"PR\",\n\"build-number\": \"132\"\n]\n
Kotlin
clientDetails.set(mapOf(\n\"test-type\" to \"PR\",\n\"build-number\" to \"132\"\n))\n
"},{"location":"configuration/#otherfiles","title":"otherFiles","text":"

A list of device-path: file-path pairs that indicate the device paths to push files to the device before starting tests, and the paths of files to push. Device paths must be under absolute, whitelisted paths (${EXTERNAL_STORAGE}, or ${ANDROID_DATA}/local/tmp). Source file paths may be in the local filesystem or in Google Cloud Storage (gs://\u2026).

Groovy
otherFiles = [\n\"/sdcard/dir1/file1.txt\": \"local/file.txt\",\n\"/sdcard/dir2/file2.jpg\": \"gs://bucket/file.jpg\",\n]\n
Kotlin
otherFiles.set(mapOf(\n\"/sdcard/dir1/file1.txt\" to \"local/file.txt\",\n\"/sdcard/dir2/file2.jpg\" to \"gs://bucket/file.jpg\",\n))\n
"},{"location":"configuration/#networkprofile","title":"networkProfile","text":"

The name of the network traffic profile, for example LTE, HSPA, etc, which consists of a set of parameters to emulate network conditions when running the test (default: no network shaping; see available profiles listed by the flank test network-profiles list command). This feature only works on physical devices.

Groovy
networkProfile = \"LTE\"\n
Kotlin
networkProfile.set(\"LTE\")\n
"},{"location":"configuration/#roboscript","title":"roboScript","text":"

The path to a Robo Script JSON file. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation. You can guide the Robo test to perform specific actions by recording a Robo Script in Android Studio and then specifying this argument. Learn more at DOCS.

Groovy
roboScript = \"my-robo-script.json\"\n
Kotlin
roboScript.set(\"my-robo-script.json\")\n
"},{"location":"configuration/#robodirectives","title":"roboDirectives","text":"

List of robo_directives that you can use to customize the behavior of Robo test. The type specifies the action type of the directive, which may take on values click, text or ignore. Each directive is list of String = [type, key, value]. Each key should be the Android resource name of a target UI element and each value should be the text input for that element. Values are only permitted for text type elements, so no value should be specified for click and ignore type elements.

Groovy
roboDirectives = [\n[\"test, \"input_resource_name\", \"message\"],\n    [\"click, \"button_resource_name\", \"\"],\n]\n
Kotlin
roboDirectives.set(listOf(\nlistOf(\"test\", \"input_resource_name\", \"message\"),\nlistOf(\"click\", \"button_resource_name\", \"\"),\n))\n
"},{"location":"configuration/#outputstyle","title":"outputStyle","text":"

Output style of execution status. May be one of [verbose, multi, single]. For runs with only one test execution the default value is 'verbose', in other cases 'multi' is used as the default. The output style 'multi' is not displayed correctly on consoles which don't support ANSI codes, to avoid corrupted output use single or verbose.

multi displays separated status for each shard execution in separated line, lines are updated over time. Similar to gradle output when running multiple tasks in parallel. Requires ANSI codes support.

single displays shortened status of all executions in single line. Similar to gcloud output when running with sharding. Should work on any console.

Default value is single.

Groovy
outputSyle = \"single\"\n
Kotlin
outputStyle.set(\"single\")\n
"},{"location":"configuration/#legacyjunitresult","title":"legacyJunitResult","text":"

Flank provides two ways for parsing junit xml results. New way uses google api instead of merging xml files, but can generate slightly different output format. This flag allows fallback for legacy xml junit results parsing

Groovy
legacyJunitResult = false\n
Kotlin
legacyJunitResult.set(false)\n
"},{"location":"configuration/#fulljunitresult","title":"fullJunitResult","text":"

Enables creating an additional local junit result on local storage with failure nodes on passed flaky tests.

Groovy
fullJunitResult = false\n
Kotlin
fullJunitResult.set(false)\n
"},{"location":"configuration/#sanityrobo","title":"sanityRobo","text":"

Runs a sanityRobo test. instrumentationApk, roboDirectives, roboScript and additionalTestApks must be blank or empty.

Groovy
sanityRobo = true\n
Kotlin
sanityRobo.set(true)\n
"},{"location":"configuration/#defaulttesttime","title":"defaultTestTime","text":"

Set default test time expressed in seconds, used for calculating shards. (default: 120.0s)

Groovy
defaultTestTime = 1.2\n
Kotlin
defaultTestTime.set(1.2)\n
"},{"location":"configuration/#defaultclasstesttime","title":"defaultClassTestTime","text":"

Set default parameterized class test time expressed in seconds, used for calculating shards. (default: 2x defaultTestTime => 240s)

Groovy
defaultClassTestTime = 245.5\n
Kotlin
defaultClassTestTime.set(245,5)\n
"},{"location":"configuration/#additionalapks","title":"additionalApks","text":"

A list of up to 100 additional APKs to install, in addition to those being directly tested. The path may be in the local filesystem or in Google Cloud Storage using gs:// notation.

Groovy
additionalApks = [\n\"gs://path/to/app1.apk\",\n\"localPath/to/app2.apk\"\n]\n
Kotlin
additionalApks.set(\nproject.provider {\nlistOf(\"gs://path/to/app1.apk\", \"localPath/to/app2.apk\")\n}\n)\n
"},{"location":"configuration/#useaveragetesttimefornewtests","title":"useAverageTestTimeForNewTests","text":"

Enable using average time from previous tests duration when using SmartShard and tests did not run before. (default: false)

Groovy
useAverageTestTimeForNewTests = true\n
Kotlin
useAverageTestTimeForNewTests.set(true)\n
"},{"location":"configuration/#disableresultsupload","title":"disableResultsUpload","text":"

Disable flank results upload on gcloud storage. (default: false)

Groovy
disableResultsUpload = true\n
Kotlin
disableResultsUpload.set(true)\n
"},{"location":"configuration/#testtargetsforshard","title":"testTargetsForShard","text":"

Specifies a group of packages, classes, and/or test cases to run in each shard (a group of test cases). The shards are run in parallel on separate devices. You can use this option up to 50 times to specify multiple shards when one or more physical devices are selected, or up to 500 times when no physical devices are selected. Note: If you include the flags environmentVariables or testTargets when running testTargetsForShard, the flags are applied to all the shards you create. You can also specify multiple packages, classes, or test cases in the same shard by separating each item with a comma. To specify both package and class in the same shard, separate package and class with semi-colons.

Groovy
testTargetsForShard = [\n  \"package com.package1.for.shard1, com.package2.for.shard1\",\n  \"class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2\",\n  \"class com.foo.ClassForShard3; package com.package.for.shard3\"\n  ]\n
Kotlin
testTargetsForShard.set(\n  project.provider {\n    listOf(\n      \"package com.package1.for.shard1, com.package2.for.shard1\",\n      \"class com.foo.ClassForShard2#testMethod1, com.foo.ClassForShard2#testMethod2\",\n      \"class com.foo.ClassForShard3; package com.package.for.shard3\"\n    )\n  }\n)\n
"},{"location":"configuration/#grantpermissions","title":"grantPermissions","text":"

Whether to grant runtime permissions on the device before the test begins. By default, all permissions are granted. PERMISSIONS must be one of: all, none

Groovy
grantPermissions = \"none\"\n
Kotlin
grantPermissions.set(\"none\")\n
"},{"location":"configuration/#type","title":"type","text":"

The type of test to run. TYPE must be one of: instrumentation, robo, game-loop. Use if you want to be sure there is only one type of tests being run (flank enables to run mixed types of test in one run).

Groovy
type = \"game-loop\"\n
Kotlin
type.set(\"game-loop\")\n
"},{"location":"configuration/#scenariolabels","title":"scenarioLabels","text":"

A list of game-loop scenario labels (default: None). Each game-loop scenario may be labeled in the APK manifest file with one or more arbitrary strings, creating logical groupings (e.g. GPU_COMPATIBILITY_TESTS). If --scenario-numbers and --scenario-labels are specified together, Firebase Test Lab will first execute each scenario from --scenario-numbers. It will then expand each given scenario label into a list of scenario numbers marked with that label, and execute those scenarios.

Groovy
scenarioLabels = [\n\"label1\",\n\"label2\" ]\n
Kotlin
scenarioLabels.set(\nproject.provider {\nlistOf(\"label1\", \"label2\")\n}\n)\n
"},{"location":"configuration/#scenarionumbers","title":"scenarioNumbers","text":"

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.

Groovy
scenarioNumbers = [ 1, 23, 52 ]\n
Kotlin
scenarioNumbers.set(\nproject.provider {\nlistOf(1, 23, 52)\n}\n)\n
"},{"location":"configuration/#obbfiles","title":"obbFiles","text":"

A list of one or two Android OBB file names which will be copied to each test device before the tests will run (default: None). Each OBB file name must conform to the format as specified by Android (e.g. [main|patch].0300110.com.example.android.obb) and will be installed into <shared-storage>/Android/obb/<package-name>/ on the test device.

Groovy
obbFiles = [\n\"local/file/path/test1.obb\",\n\"local/file/path/test2.obb\"\n]\n
Kotlin
obbFiles.set(\nproject.provider {\nlistOf(\n\"local/file/path/test1.obb\",\n\"local/file/path/test2.obb\"\n)\n}\n)\n
"},{"location":"configuration/#obbnames","title":"obbNames","text":"

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 <shared-storage>/Android/obb/<package-name>/ on the device.

Groovy
obbNames = [\n\"patch.0300110.com.example.android.obb\",\n\"patch.0300111.com.example.android.obb\"\n]\n
Kotlin
obbNames.set(\nproject.provider {\nlistOf(\n\"patch.0300110.com.example.android.obb\",\n\"patch.0300111.com.example.android.obb\"\n)\n}\n)\n
"},{"location":"configuration/#failfast","title":"failFast","text":"

If true, only a single attempt at most will be made to run each execution/shard in the matrix. Flaky test attempts are not affected. Normally, 2 or more attempts are made if a potential infrastructure issue is detected. This feature is for latency sensitive workloads. The incidence of execution failures may be significantly greater for fail-fast matrices and support is more limited because of that expectation.

Groovy
failFast = true\n
Kotlin
failFast.set(true)\n
"},{"location":"configuration/#additionalflankoptions","title":"additionalFlankOptions","text":"

Appending additional option to flank root yaml. This option is useful when you would like to test options before official fladle support is added. Multiple options are supported.

Single option

Groovy
additionalFlankOptions = \"new-property: true\"\n
Kotlin
additionalFlankOptions.set(\"new-property: true\")\n

Multiple options

Groovy
additionalFlankOptions = \"\"\"\n  new-property: true\n  other-new-property: force\n\"\"\".stripIndent()\n
Kotlin
additionalFlankOptions.set(\"\"\"\n    new-property: true\n    other-new-property: force\n\"\"\".trimIndent())\n
"},{"location":"configuration/#additionalgcloudoptions","title":"additionalGcloudOptions","text":"

Allow appending additional config to gcloud root yaml. This option is useful when you would like to test option before it is available on Fladle. Supports both single and multiple properties.

Single option

Groovy
additionalGcloudOptions = \"new-property: true\"\n
Kotlin
additionalGcloudOptions.set(\"new-property: true\")\n

Multiple options

Groovy
additionalGcloudOptions = \"\"\"\n    new-property: true\n    other-new-property: force\n  \"\"\".stripIndent()\n
Kotlin
additionalGcloudOptions.set(\"\"\"\n    new-property: true\n    other-new-property: force\n\"\"\".trimIndent())\n
"},{"location":"configuration/#dependonassemble","title":"dependOnAssemble","text":"

Enable to automatically build the app and test APKs before runFlank executes. (default: false)

Groovy
dependOnAssemble = true\n
Kotlin
dependOnAssemble.set(true)\n
"},{"location":"configuration/#async","title":"async","text":"

Enable to return immediately after invoking tests, without waiting for results. (default: false)

Groovy
async = true\n
Kotlin
async.set(true)\n
"},{"location":"faq/","title":"FAQ","text":""},{"location":"faq/#error-apk-file-not-found","title":"Error APK file not found","text":"

The app APK and the instrumentation APK are expected to have already been generated before calling runFlank. To generate APKs, run assembleDebug and assembleDebugAndroidTest before running runFlank.

You can also have Fladle build them for you by using the dependOnAssemble property.

"},{"location":"faq/#no-signature-of-method","title":"No signature of method","text":"

If you receive an error like this, it is likely caused by invalid fladle extension confiuration. The syntax was changed in the 0.9.X releases in order to avoid touching files during the configuration phase.

No signature of method: flank_4vvjv7w3oopge32w1tl9cs6e4.fladle() is applicable for argument types: (flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1) values: [flank_4vvjv7w3oopge32w1tl9cs6e4$_run_closure1@649a2315]\nPossible solutions: file(java.lang.Object), find(), findAll(), file(java.lang.Object, org.gradle.api.PathValidation), files([Ljava.lang.Object;), findAll(groovy.lang.Closure)\n

If you receive a similar error, please check configuration for a sample configuration.

"},{"location":"faq/#debugging","title":"Debugging","text":"

./gradlew runFlank -PdumpShards Will dump shards and exit the process without running the tests.

./gradlew printYml Will print out the current yaml configuration to be passed to Flank.

"},{"location":"faq/#more-help","title":"More help?","text":"

Still having trouble? Check the #flank channel in the Firebase Community Slack

"},{"location":"multi-module-testing/","title":"Multi-module testing","text":"

Multi module testing can be done by manually specifying additionalTestApks or applying the Fulladle plugin to automacally gather all the additional test apks. See also this article for a full setup and instructions including integration with CI.

"},{"location":"multi-module-testing/#fulladle-plugin","title":"Fulladle Plugin","text":"
  1. Apply the Fulladle plugin at the root of the project.

    Groovy
    plugins {\nid 'com.osacky.fulladle' version '0.20.0'\n}\n
    Kotlin
    plugins {\nid(\"com.osacky.fulladle\") version \"0.20.0\"\n}\n
  2. Configure the Fladle extension.

    Groovy
    fladle {\nserviceAccountCredentials = project.layout.projectDirectory.file(\"flank-gradle-service-account.json\")\n}\n
    Kotlin
    fladle {\nserviceAccountCredentials.set(project.layout.projectDirectory.file(\"flank-gradle-service-account.json\"))\n}\n

    Warning

    If using buildFlavors or testing against a non default variant, you will need to specify the variant you want to test in the fulladleModuleConfig block.

  3. Run the tests. First assemble all your debug apks and test apks.

    ./gradlew assembleDebug assembleDebugAndroidTest\n

    Note

    When using flavors, make sure to assemble your buildVariants as well.

    ./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest

    Run Flank!

    ./gradlew runFlank\n

"},{"location":"multi-module-testing/#overriding-configurations-in-modules","title":"Overriding configurations in modules","text":"

Fulladle will pick Flank configurations from the fladle block in the root build.gradle file. You may want to override some of these configurations for certain modules, you can add the following block to any Android library module to override its configurations:

Groovy
fulladleModuleConfig {\nclientDetails = [\n\"test-type\": \"PR\",\n\"build-number\": \"132\"\n]\nmaxTestShards = 3\nenvironmentVariables = [\n\"clearPackageData\": \"true\"\n]\ndebugApk = \"app.apk\"\nvariant = \"vanillaDebug\"\n}\n
Kotlin
fulladleModuleConfig {\nclientDetails.set(mapOf(\n\"test-type\" to \"PR\",\n\"build-number\" to \"132\",\n))\nmaxTestShards.set(3)\nenvironmentVariables.set(mapOf(\n\"clearPackageData\" to \"true\"\n))\ndebugApk.set(\"app.apk\")\nvariant.set(\"vanillaDebug\")\n}\n

All of the above configurations are optional, Flank will default to the top-level configurations if you don't override anything here. For details about these configurations, refer to configuration docs.

"},{"location":"multi-module-testing/#disabling-a-module","title":"Disabling a module","text":"

You may want to exclude a library module from testing when using Fulladle. You can do so by setting the enabled configuration in the module's fulladleModuleConfig block like so:

Groovy
fulladleModuleConfig {\nenabled = false\n}\n
Kotlin
fulladleModuleConfig {\nenabled.set(false)\n}\n
"},{"location":"multi-module-testing/#overriding-root-level-config","title":"Overriding root-level config","text":"

Fulladle does not provide the ability to control which module ends up as the root-level module or as an additional module. Either one of app modules or library modules can become a root-level module. If a library module ends up as a root-level module, it needs to specify a debugApk in its fladle or fulladleModuleConfig block.

The root-level configuration (e.g. maxTestShards) can also be overridden in the fulladleModuleConfig block of whatever module gets picked as the root module.

"},{"location":"multi-module-testing/#troubleshooting","title":"Troubleshooting","text":"

Fulladle might still have some rough edges, but we'd love feedback. Please join us in the Firebase Community Slack with any feedback you may have. You can also file Fladle Github issues.

When filing a bug report, please include the Flank version number, the Fladle version number and the output of the following:

./gradlew printYml

./gradlew runFlank -PdumpShards

"},{"location":"quick-start/","title":"Quick Start","text":"

Using Fladle takes 3 steps:

  1. Apply the Fladle plugin. Follow instructions here

    Root build.gradle

    Groovy
    buildscript {\ndependencies {\nclasspath \"com.osacky.flank.gradle:fladle:0.20.0\"\n}\n}\n
    Kotlin
    buildscript {\ndependencies {\nclasspath(\"com.osacky.flank.gradle:fladle:0.20.0\")\n}\n}\n

    Application module build.gradle

    Groovy
    apply plugin: \"com.android.application\"\napply plugin: \"com.osacky.fladle\"\n
    Kotlin
    plugins {\nid (\"com.android.application\")\nid (\"com.osacky.fladle\")\n}\n
  2. Configure Authentication using these steps.

    Warning

    If using buildFlavors or testing against a non default variant, variant must also configured

  3. Run your tests!

    First assemble your debug apk and test apk.

    ./gradlew :app:assembleDebug :app:assembleDebugAndroidTest\n

    Note

    When using flavors, make sure to assemble your buildVariants.

    ./gradlew :app:assembleFreeDebug :app:assembleFreeDebugAndroidTest

    Run Flank!

    ./gradlew runFlank\n

"},{"location":"recipes/","title":"Recipes","text":"

Here are some recipes to use to achieve various goals in flank. For additional recipes or suggestions, please file an issue on Github.

"},{"location":"recipes/#smartly-shard-tests-in-120-second-shards-across-a-maximum-of-50-shards","title":"Smartly shard tests in 120 second shards across a maximum of 50 shards.","text":"

This recipe will keep track of test durations automatically on firebase test lab and try to split up test runs in to 120 second shards up to maximum of 50 shards.

Groovy
fladle {\nmaxTestShards = 50\nshardTime = 120\nsmartFlankGcsPath = \"gs://fladle-results/smart-flank/JUnitReport.xml\"\n}\n
Kotlin
fladle {\nmaxTestShards.set(50)\nshardTime.set(120)\nsmartFlankGcsPath.set(\"gs://fladle-results/smart-flank/JUnitReport.xml\")\n}\n
"},{"location":"recipes/#run-different-tests-on-different-devices-with-different-gradle-tasks","title":"Run different tests on different devices with different Gradle tasks.","text":"

./gradlew runFlankPerfTests will execute the performance tests against a MediumPhone.arm ./gradlew runFlankRegresssionTests will execute the regressions tests against a SmallPhone.arm

Groovy
fladle {\nconfigs {\nperfTests {\ndevices.set([\n[\"model\" : \"MediumPhone.arm\", \"version\" : \"28\"], [\"model\" : \"MediumPhone.arm\", \"version\" : \"28\"]\n])\ntestTargets.set([\n\"class com.sample.MyPerformanceTest\"\n])\n}\nregressionTests {\ndevices.set([\n[ \"model\" : \"SmallPhone.arm\", \"version\" : \"28\"]\n])\ntestTargets.set([\n\"class com.sample.MyRegressionTest\"\n])\n}\n}\n}\n
Kotlin
fladle {\nconfigs {\ncreate(\"perfTests\") {\ndevices.set(listOf(\nmapOf(\"model\" to \"MediumPhone.arm\", \"version\" to \"28\" ), mapOf(\"model\" to \"MediumPhone.arm\", \"version\" to \"28\")\n))\ntestTargets.set(listOf(\n\"class com.sample.MyPerformanceTest\"\n))\n}\ncreate(\"regressionTests\") {\ndevices.set(listOf(\nmapOf(\"model\" to \"SmallPhone.arm\", \"version\" to \"28\" )\n))\ntestTargets.set(listOf(\n\"class com.sample.MyRegressionTest\"\n))\n}\n}\n}\n
"},{"location":"recipes/#always-use-the-latest-version-of-flank","title":"Always use the latest version of Flank","text":"

Use Gradle's dynamic version syntax to declare a dynamic version.

Warning

Dynamic versions lead to non-reproducible builds since Gradle will check for new versions periodically based on how long versions are cached.

Groovy
flankVersion = \"23.+\"\n
Kotlin
flankVersion.set(\"23.+\")\n
"},{"location":"releasing/","title":"Releasing","text":"
  • Create a local release branch from master

    git checkout master\ngit pull\ngit checkout -b release_0.20.1\n

  • Update version in fladle-plugin/build.gradle.kts (remove -SNAPSHOT)

    version = \"0.20.1\"\n

  • Update the current version and next version in mkdocs.yml:

    extra:\n  fladle:\n    release: '0.20.1'\n    next_release: 'REPLACE_WITH_NEXT_VERSION_NUMBER'\n

  • Take one last look

    git diff\n

  • Commit all local changes

    git commit -am \"Prepare 0.20.1 release\"\n

  • Create a tag and push it

    git tag v0.20.1\ngit push origin v0.20.1\n

Pushing the tag automatically triggers the release workflow which:

  1. Publishes to Maven Central
  2. Publishes to Gradle Plugin Portal
  3. Creates a GitHub Release with auto-generated notes

  4. Release to Maven Central

    • Login to Maven Central Repository: https://central.sonatype.com/
    • Click on Publish
  5. Merge the release branch to master

    git checkout master\ngit pull\ngit merge --no-ff release_0.20.1\n

  6. Update version in fladle-plugin/build.gradle.kts (increase version and add -SNAPSHOT)

    version = \"REPLACE_WITH_NEXT_VERSION_NUMBER-SNAPSHOT\"\n

  7. Commit your changes

    git commit -am \"Prepare for next development iteration\"\n

  8. Push your changes

    git push\n

"},{"location":"results/","title":"Results","text":"

By default, results are placed in the build/fladle/results/<matrix name> directly.

A merged junit report is available in JUnitReport.xml

On failures only, an html report with links to failed shards is also generated in the results directory with the name HtmlErrorReport.html.

A cost report is also available CostReport.txt.

"},{"location":"snapshots/","title":"Testing Snapshot Releases","text":"

To test the Fladle snapshot release you have two options:

"},{"location":"snapshots/#traditional","title":"Traditional","text":"

Root build.gradle

buildscript {\nrepositories {\nmaven {\nurl \"https://oss.sonatype.org/content/repositories/snapshots/\"\n}\n}\ndependencies {\nclasspath \"com.osacky.flank.gradle:fladle:0.20.1-SNAPSHOT\"\n}\n}\n

Project build.gradle

apply plugin: \"com.osacky.fladle\"\n

"},{"location":"snapshots/#plugin-management","title":"Plugin Management","text":"

settings.gradle

pluginManagement {\nrepositories {\nmaven {\nurl \"https://oss.sonatype.org/content/repositories/snapshots/\"\n}\ngradlePluginPortal()\n}\n}\n

Android application build.gradle

plugins {\nid \"com.osacky.fladle\" version \"0.20.1-SNAPSHOT\"\n}\n

"}]} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index e6b4450a..00000000 --- a/settings.gradle +++ /dev/null @@ -1,33 +0,0 @@ -pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } -} - -plugins { - id "org.gradle.toolchains.foojay-resolver-convention" version "1.0.0" - id "com.gradle.develocity" version "4.3.2" -} - -include ':android-library-no-tests' -include ':sample' -include ':sample-kotlin' -include ':sample-flavors-kotlin' -include ':sample-android-library' -includeBuild 'fladle-plugin' - -dependencyResolutionManagement { - repositories { - mavenCentral() - google() - } -} - -develocity { - buildScan { - termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" - termsOfUseAgree = "yes" - } -} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..0b3e1895 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,58 @@ + + + + https://runningcode.github.io/fladle/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/authentication/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/changelog/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/configuration/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/faq/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/multi-module-testing/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/quick-start/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/recipes/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/releasing/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/results/ + 2026-03-19 + daily + + + https://runningcode.github.io/fladle/snapshots/ + 2026-03-19 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 00000000..b92554c0 Binary files /dev/null and b/sitemap.xml.gz differ diff --git a/snapshots/index.html b/snapshots/index.html new file mode 100644 index 00000000..ed7ce564 --- /dev/null +++ b/snapshots/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Testing Snapshots - Fladle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Testing Snapshot Releases

+

To test the Fladle snapshot release you have two options:

+

Traditional

+

Root build.gradle +

buildscript {
+  repositories {
+    maven {
+      url "https://oss.sonatype.org/content/repositories/snapshots/"
+    }
+  }
+  dependencies {
+    classpath "com.osacky.flank.gradle:fladle:0.20.1-SNAPSHOT"
+  }
+}
+

+

Project build.gradle +

apply plugin: "com.osacky.fladle"
+

+

Plugin Management

+

settings.gradle +

pluginManagement {
+    repositories {
+        maven {
+            url "https://oss.sonatype.org/content/repositories/snapshots/"
+        }
+        gradlePluginPortal()
+    }
+}
+

+

Android application build.gradle +

plugins {
+    id "com.osacky.fladle" version "0.20.1-SNAPSHOT"
+}
+

+ + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file