├── .editorconfig ├── .github ├── config.yml ├── stale.yml └── workflows │ ├── PR.yml │ ├── master.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── gradle.properties ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Ci.kt │ ├── kotest-conventions.gradle.kts │ └── versionCatalog.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotest-assertions-arrow-fx-coroutines ├── build.gradle.kts ├── gradle.properties └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── kotest │ │ └── assertions │ │ └── arrow │ │ └── fx │ │ └── coroutines │ │ ├── ExitCase.kt │ │ ├── ProjectResource.kt │ │ ├── Resource.kt │ │ ├── ResourceExtension.kt │ │ └── shouldBe.kt │ └── commonTest │ └── kotlin │ └── io │ └── kotest │ └── assertions │ └── arrow │ └── fx │ └── coroutines │ ├── ExitCaseTest.kt │ ├── ResourceExtensionLeafTest.kt │ ├── ResourceExtensionRootTest.kt │ ├── ResourceExtensionSpecTest.kt │ └── ResourceUnderTest.kt ├── kotest-assertions-arrow ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── kotest │ │ └── assertions │ │ └── arrow │ │ ├── Utils.kt │ │ └── core │ │ ├── Either.kt │ │ ├── Ior.kt │ │ ├── Nel.kt │ │ ├── Nelnspectors.kt │ │ ├── Option.kt │ │ └── Raise.kt │ └── commonTest │ └── kotlin │ └── io │ └── kotest │ └── assertions │ └── arrow │ └── core │ ├── EitherMatchers.kt │ ├── IorMatchers.kt │ ├── NelMatchers.kt │ ├── OptionMatchers.kt │ └── RaiseMatchers.kt ├── kotest-property-arrow-optics ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── kotest │ │ └── property │ │ └── arrow │ │ └── optics │ │ ├── IsoLaws.kt │ │ ├── LensLaws.kt │ │ ├── OptionalLaws.kt │ │ ├── PrismLaws.kt │ │ └── TraversalLaws.kt │ └── commonTest │ └── kotlin │ └── io │ └── kotest │ └── property │ └── arrow │ └── optics │ └── TraversalTests.kt ├── kotest-property-arrow ├── build.gradle.kts └── src │ ├── commonMain │ └── kotlin │ │ └── io │ │ └── kotest │ │ └── property │ │ └── arrow │ │ ├── core │ │ ├── Either.kt │ │ ├── Function.kt │ │ ├── Ior.kt │ │ ├── Nel.kt │ │ ├── Option.kt │ │ ├── SemiringLaws.kt │ │ └── Tuple.kt │ │ └── laws │ │ ├── Law.kt │ │ └── Util.kt │ └── commonTest │ └── kotlin │ └── io │ └── kotest │ └── property │ └── arrow │ └── core │ ├── EitherTests.kt │ └── IorTests.kt ├── kotlin-js-store └── yarn.lock ├── publish-mpp.gradle.kts ├── renovate.json ├── settings.gradle.kts └── signing-pom-details.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | indent_style = space 6 | indent_size = 2 7 | 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.gradle.kts] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | todo: 2 | keyword: "// todo" 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/PR.yml: -------------------------------------------------------------------------------- 1 | name: PR-Test 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - '*.md' 7 | - '*.yml' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout the repo 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup JDK 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '8' 21 | distribution: 'temurin' 22 | 23 | - name: Run tests 24 | run: ./gradlew check 25 | 26 | - name: Bundle the build report 27 | if: failure() 28 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 29 | 30 | - name: Upload the build report 31 | if: failure() 32 | uses: actions/upload-artifact@master 33 | with: 34 | name: error-report 35 | path: build-reports.zip 36 | 37 | env: 38 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 39 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: master 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*.md' 7 | - '*.yml' 8 | branches: 9 | - master 10 | 11 | env: 12 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 13 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 14 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 15 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 16 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 17 | 18 | jobs: 19 | linux: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout the repo 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup JDK 28 | uses: actions/setup-java@v4 29 | with: 30 | java-version: '11' 31 | distribution: 'temurin' 32 | 33 | - name: Run tests 34 | run: ./gradlew check --scan 35 | 36 | - name: publish snapshots 37 | run: ./gradlew publish 38 | 39 | - name: Bundle the build report 40 | if: failure() 41 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 42 | 43 | - name: Upload the build report 44 | if: failure() 45 | uses: actions/upload-artifact@master 46 | with: 47 | name: error-report 48 | path: build-reports.zips 49 | 50 | macos: 51 | runs-on: macos-latest 52 | steps: 53 | - name: Checkout the repo 54 | uses: actions/checkout@v4 55 | with: 56 | fetch-depth: 0 57 | 58 | - name: Setup JDK 59 | uses: actions/setup-java@v4 60 | with: 61 | java-version: '11' 62 | distribution: 'temurin' 63 | 64 | - name: Run macos tests 65 | run: ./gradlew macosX64Test macosArm64Test iosX64Test iosSimulatorArm64Test tvosX64Test tvosSimulatorArm64Test watchosX64Test watchosSimulatorArm64Test --scan 66 | 67 | - name: publish macos snapshots 68 | run: | 69 | ./gradlew publishMacosX64PublicationToDeployRepository publishMacosArm64PublicationToDeployRepository publishIosArm64PublicationToDeployRepository publishIosX64PublicationToDeployRepository \ 70 | publishTvosArm64PublicationToDeployRepository publishTvosX64PublicationToDeployRepository publishTvosSimulatorArm64PublicationToDeployRepository publishWatchosArm32PublicationToDeployRepository publishIosSimulatorArm64PublicationToDeployRepository \ 71 | publishWatchosArm64PublicationToDeployRepository publishWatchosX64PublicationToDeployRepository publishWatchosSimulatorArm64PublicationToDeployRepository 72 | 73 | - name: Bundle the build report 74 | if: failure() 75 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 76 | 77 | - name: Upload the build report 78 | if: failure() 79 | uses: actions/upload-artifact@master 80 | with: 81 | name: error-report 82 | path: build-reports.zip 83 | 84 | windows: 85 | runs-on: windows-latest 86 | steps: 87 | - name: Checkout the repo 88 | uses: actions/checkout@v4 89 | with: 90 | fetch-depth: 0 91 | 92 | - name: Setup JDK 93 | uses: actions/setup-java@v4 94 | with: 95 | java-version: '11' 96 | distribution: 'temurin' 97 | 98 | - name: Run tests 99 | run: ./gradlew mingwX64Test --scan 100 | 101 | - name: publish mingw64 snapshot 102 | run: ./gradlew publishMingwX64PublicationToDeployRepository 103 | 104 | - name: Bundle the build report 105 | if: failure() 106 | run: find . -type d -name 'reports' | zip -@ -r build-reports.zip 107 | 108 | - name: Upload the build report 109 | if: failure() 110 | uses: actions/upload-artifact@master 111 | with: 112 | name: error-report 113 | path: build-reports.zip 114 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "The release version" 8 | required: true 9 | branch: 10 | description: "The branch to release from" 11 | required: true 12 | default: 'master' 13 | 14 | 15 | env: 16 | GRADLE_OPTS: -Dorg.gradle.configureondemand=true -Dorg.gradle.parallel=false -Dkotlin.incremental=false -Dorg.gradle.jvmargs="-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" 17 | RELEASE_VERSION: ${{ github.event.inputs.version }} 18 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 19 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 20 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} 21 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} 22 | 23 | jobs: 24 | linux: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - name: Checkout the repo 29 | uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | ref: ${{ github.event.inputs.branch }} 33 | 34 | - name: Setup JDK 35 | uses: actions/setup-java@v4 36 | with: 37 | java-version: '11' 38 | distribution: 'temurin' 39 | 40 | - name: deploy to sonatype 41 | run: ./gradlew publish 42 | 43 | macos: 44 | runs-on: macos-latest 45 | 46 | steps: 47 | - name: Checkout the repo 48 | uses: actions/checkout@v4 49 | with: 50 | fetch-depth: 0 51 | ref: ${{ github.event.inputs.branch }} 52 | 53 | - name: Setup JDK 54 | uses: actions/setup-java@v4 55 | with: 56 | java-version: '11' 57 | distribution: 'temurin' 58 | 59 | - name: deploy to sonatype 60 | run: | 61 | ./gradlew publishMacosX64PublicationToDeployRepository publishMacosArm64PublicationToDeployRepository publishIosArm64PublicationToDeployRepository publishIosX64PublicationToDeployRepository \ 62 | publishTvosArm64PublicationToDeployRepository publishTvosX64PublicationToDeployRepository publishTvosSimulatorArm64PublicationToDeployRepository publishWatchosArm32PublicationToDeployRepository publishIosSimulatorArm64PublicationToDeployRepository \ 63 | publishWatchosArm64PublicationToDeployRepository publishWatchosX64PublicationToDeployRepository publishWatchosSimulatorArm64PublicationToDeployRepository 64 | 65 | windows: 66 | runs-on: windows-latest 67 | 68 | steps: 69 | - name: Checkout the repo 70 | uses: actions/checkout@v4 71 | with: 72 | fetch-depth: 0 73 | ref: ${{ github.event.inputs.branch }} 74 | 75 | - name: Setup JDK 76 | uses: actions/setup-java@v4 77 | with: 78 | java-version: '11' 79 | distribution: 'temurin' 80 | 81 | - name: deploy to sonatype 82 | run: ./gradlew publishMingwX64PublicationToDeployRepository 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | .idea 4 | *.iml 5 | **/.kotlintest/ 6 | .gradle 7 | 8 | target/ 9 | build/ 10 | /build/ 11 | out/ 12 | lib_managed/ 13 | src_managed/ 14 | project/boot/ 15 | project/plugins/project/ 16 | credentials.sbt 17 | /.idea/ 18 | .DS_Store 19 | .kotlin 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotest-extensions-arrow 2 | 3 | ## Moved to main kotest repo 4 | 5 | Kotest extension for [Arrow](https://arrow-kt.io/). 6 | 7 | See [docs](https://kotest.io/docs/assertions/arrow.html). 8 | 9 | Please create issues on the main kotest [board](https://github.com/kotest/kotest/issues). 10 | 11 | [![Build Status](https://github.com/kotest/kotest-extensions-arrow/workflows/master/badge.svg)](https://github.com/kotest/kotest-extensions-arrow/actions) 12 | [](http://search.maven.org/#search|ga|1|kotest-assertions-arrow) 13 | ![GitHub](https://img.shields.io/github/license/kotest/kotest-extensions-arrow) 14 | [![kotest @ kotlinlang.slack.com](https://img.shields.io/static/v1?label=kotlinlang&message=kotest&color=blue&logo=slack)](https://kotlinlang.slack.com/archives/CT0G9SD7Z) 15 | [](https://s01.oss.sonatype.org/content/repositories/snapshots/io/kotest/extensions/kotest-assertions-arrow/) 16 | 17 | ## How to use it 18 | 19 | ```kotlin 20 | depedencies { 21 | implementation("io.kotest.extensions:kotest-assertions-arrow:") 22 | implementation("io.kotest.extensions:kotest-assertions-arrow-fx-coroutines:") 23 | } 24 | ``` 25 | 26 | for property-based testing: 27 | 28 | ```kotlin 29 | dependencies { 30 | implementation("io.kotest.extensions:kotest-property-arrow:") 31 | // optional: the following includes optics related Laws 32 | implementation("io.kotest.extensions:kotest-property-arrow-optics:") 33 | } 34 | ``` 35 | 36 | to depend on snapshot releases add the snapshot repository url: 37 | 38 | ```kotlin 39 | repositories { 40 | //... 41 | maven { 42 | url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") 43 | } 44 | } 45 | ``` 46 | 47 | Note: 48 | Please add `io.arrow-kt:arrow-core:arrow-version`, `io.arrow-kt:arrow-fx-coroutines:arrow-version` or `io.arrow-kt:arrow-optics:arrow-version`, if they're missing in your classpath. 49 | Otherwise, it will lead to unresolved Reference errors. 50 | In the form of: "Cannot access class `arrow.core.Either` Check your module classpath for missing or conflicting dependencies." 51 | The project is not shipping the arrow jars because this leads to dependency conflicts and further adjusting the dependency graph. 52 | 53 | ## Changelog 54 | 55 | ### 2.0.0 56 | 57 | * Release for Kotlin 2.1.0 and Arrow 2.0 58 | 59 | ### 1.4.0 60 | 61 | * Release for Kotlin 1.8 and Kotest 5.7 and Arrow 1.2 62 | 63 | ### 1.3.3 64 | 65 | * Release for arrow 1.1.5 66 | 67 | ### 1.3.2 68 | 69 | * Release for arrow 1.1.4 70 | 71 | ### 1.3.1 72 | 73 | * Added `Ior` matchers. 74 | 75 | ### 1.3.0 76 | 77 | - Update to kotest 5.5.4 78 | - Add assertion module for arrow-fx-coroutines with combinators related to `Resource` and `ExitCase` 79 | - Add `Either.rethrow` 80 | 81 | ### 1.2.5 82 | 83 | - Upgrade to 5.2.3 and update kotlinx-coroutines to 1.6.1 84 | 85 | ### 1.2.4 86 | 87 | - Upgrade to 5.2.1 and restores compatibilty with 5.2.X series https://github.com/kotest/kotest-extensions-arrow/pull/149 88 | 89 | ### 1.2.3 90 | 91 | - fix linking error in native platforms [#140](https://github.com/kotest/kotest-extensions-arrow/issues/140) 92 | 93 | ### 1.2.2 94 | 95 | * update kotest to 5.1.0 96 | * update kotlin to 1.6.10 97 | * publish missing multiplatform targets affecting `1.2.1` and `1.2.0` 98 | 99 | ### 1.2.1 100 | 101 | * Added Arb.valid and Arb.invalid 102 | * Added Arb.nel(arb, range) - a variant of Arb.nel(arb) that accepts a range parameter 103 | 104 | ### 1.2.0 105 | 106 | * Upgrade to Arrow 1.0.1 107 | * Multiplatform artifacts for `kotest-assertions-arrow`, `kotest-property-arrow` and `kotest-property-arrow-optics` 108 | * `#2670` Replace explicit usage of eq with should be from kotest assertion core 109 | * `testLaws` has `RootScope` as a receiver instead of `RootContext` 110 | 111 | ### 1.1.1 112 | 113 | * removes deprecated members in `kotest-assertions-arrow` 114 | 115 | ### 1.1.0 116 | 117 | **Note that from this release, the minimium requirements are Kotest 5.0+ and Kotlin 1.6** 118 | 119 | * Update to Arrow 1.0.0 120 | * fix Java 1.8 compatibility [#2437](https://github.com/kotest/kotest/issues/2437) 121 | * Added `kotest-property-arrow` and `kotest-property-arrow-optics` for property-based testing with Arrow 122 | * includes deprecation cycle of 1.0.3 123 | * remove dependency to kotlinX-coroutines and kotest-property in `kotest-assertions-arrow` 124 | 125 | 126 | ### 1.0.3 127 | 128 | * Update to Arrow 0.13.2 129 | * Added a deprecation cycle for previous descriptors in 1.0.2 in favor of smart-casted variants in `io.kotest.assertions.arrow.core` 130 | * Reorg existing functions to `io.kotest.assertions.arrow.core` 131 | * Not leaking the arrow dependency into the runtime of users 132 | * Added Arb, Arb> 133 | 134 | ### 1.0.2 135 | 136 | * Updated to arrow 0.13.1 137 | * Updated option to support nullables 138 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | repositories { 2 | mavenCentral() 3 | gradlePluginPortal() 4 | } 5 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.`kotlin-dsl` 2 | 3 | repositories { 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | 8 | plugins { 9 | `kotlin-dsl` 10 | } 11 | 12 | dependencies { 13 | implementation(libs.kotlin.gradle.plugin) 14 | implementation(libs.animalsniffer) 15 | } 16 | -------------------------------------------------------------------------------- /buildSrc/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.mpp.stability.nowarn=true 2 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | versionCatalogs { 3 | create("libs") { 4 | from(files("../gradle/libs.versions.toml")) 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Ci.kt: -------------------------------------------------------------------------------- 1 | object Ci { 2 | 3 | // this is the version used for building snapshots 4 | // .GITHUB_RUN_NUMBER-snapshot will be appended 5 | private const val snapshotBase = "1.4.0" 6 | 7 | private val githubRunNumber = System.getenv("GITHUB_RUN_NUMBER") 8 | 9 | private val snapshotVersion = when (githubRunNumber) { 10 | null -> "$snapshotBase-LOCAL" 11 | else -> "$snapshotBase.${githubRunNumber}-SNAPSHOT" 12 | } 13 | 14 | private val releaseVersion = System.getenv("RELEASE_VERSION") 15 | 16 | val isRelease = releaseVersion != null 17 | val version = releaseVersion ?: snapshotVersion 18 | } 19 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/kotest-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.api.tasks.testing.Test 2 | import org.gradle.api.tasks.testing.logging.TestExceptionFormat 3 | import org.gradle.api.tasks.testing.logging.TestLogEvent 4 | import org.gradle.kotlin.dsl.named 5 | 6 | plugins { 7 | kotlin("multiplatform") 8 | id("ru.vyarus.animalsniffer") 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | gradlePluginPortal() 14 | } 15 | 16 | group = "io.kotest.extensions" 17 | version = Ci.version 18 | 19 | kotlin { 20 | explicitApi() 21 | 22 | metadata() 23 | 24 | jvm { 25 | compilations.all { 26 | kotlinOptions.jvmTarget = "1.8" 27 | } 28 | } 29 | 30 | js(IR) { 31 | browser() 32 | nodejs() 33 | } 34 | 35 | linuxX64() 36 | 37 | mingwX64() 38 | 39 | iosArm64() 40 | iosSimulatorArm64() 41 | iosX64() 42 | macosArm64() 43 | macosX64() 44 | tvosArm64() 45 | tvosSimulatorArm64() 46 | tvosX64() 47 | watchosArm32() 48 | watchosArm64() 49 | watchosSimulatorArm64() 50 | watchosX64() 51 | 52 | applyDefaultHierarchyTemplate() 53 | 54 | sourceSets { 55 | val kotestVersion = resolveVersion("kotest") 56 | 57 | val jvmTest by getting { 58 | dependencies { 59 | implementation("io.kotest:kotest-runner-junit5:$kotestVersion") 60 | } 61 | } 62 | 63 | all { 64 | languageSettings.optIn("kotlin.RequiresOptIn") 65 | } 66 | } 67 | } 68 | 69 | animalsniffer { 70 | ignore = listOf("java.lang.*") 71 | } 72 | 73 | tasks.named("jvmTest") { 74 | useJUnitPlatform() 75 | maxParallelForks = Runtime.getRuntime().availableProcessors() 76 | testLogging { 77 | showExceptions = true 78 | showStandardStreams = true 79 | events = setOf( 80 | TestLogEvent.FAILED, 81 | TestLogEvent.PASSED 82 | ) 83 | exceptionFormat = TestExceptionFormat.FULL 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/versionCatalog.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Project 2 | import org.gradle.api.artifacts.VersionCatalogsExtension 3 | import org.gradle.kotlin.dsl.getByType 4 | 5 | // Workaround for https://github.com/gradle/gradle/issues/15383 6 | fun Project.resolveVersion(name: String) = 7 | extensions.getByType() 8 | .named("libs") 9 | .findVersion(name) 10 | .get().requiredVersion 11 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #https://github.com/gradle/gradle/issues/11308 2 | org.gradle.internal.publish.checksums.insecure=true 3 | systemProp.org.gradle.internal.publish.checksums.insecure=true 4 | org.gradle.parallel=false 5 | kotlin.mpp.stability.nowarn=true 6 | org.gradle.daemon=true 7 | org.gradle.jvmargs=-Xmx3G -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | kotlin.native.ignoreIncorrectDependencies=true 9 | kotlin.native.ignoreDisabledTargets=true 10 | kotlin.native.cacheKind.macosX64=none 11 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | arrow = "2.0.1" 3 | kotlin = "2.1.0" 4 | kotest = "5.8.1" 5 | kotlinx-coroutines = "1.6.4" 6 | animalsniffer = "1.7.2" 7 | kotlinBinaryCompatibilityValidator = "0.14.0" 8 | 9 | [libraries] 10 | kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } 11 | kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } 12 | kotest-framework-engine = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } 13 | kotest-framework-api = { module = "io.kotest:kotest-framework-api", version.ref = "kotest" } 14 | kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } 15 | 16 | kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } 17 | 18 | arrow-fx-coroutines = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } 19 | arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } 20 | arrow-optics = { module = "io.arrow-kt:arrow-optics", version.ref = "arrow" } 21 | arrow-functions = { module = "io.arrow-kt:arrow-functions", version.ref = "arrow" } 22 | 23 | # Gradle plugins used in buildSrc 24 | kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 25 | animalsniffer = { module = "ru.vyarus:gradle-animalsniffer-plugin", version.ref = "animalsniffer" } 26 | 27 | [plugins] 28 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 29 | 30 | kotlin-binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinBinaryCompatibilityValidator" } 31 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kotest/kotest-extensions-arrow/cf140a405e6724b09dd49985476ef75bc7af7357/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotest-conventions") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain { 8 | dependencies { 9 | compileOnly(libs.arrow.fx.coroutines) 10 | api(projects.kotestAssertionsArrow) 11 | api(libs.kotlinx.coroutines.core) 12 | implementation(libs.kotest.assertions.core) 13 | implementation(libs.kotest.framework.engine) 14 | implementation(libs.kotest.property) 15 | } 16 | } 17 | 18 | commonTest { 19 | dependencies { 20 | implementation(libs.arrow.fx.coroutines) 21 | } 22 | } 23 | 24 | jsMain { 25 | dependencies { 26 | api(libs.arrow.fx.coroutines) 27 | } 28 | } 29 | 30 | nativeMain { 31 | dependencies { 32 | implementation(libs.arrow.fx.coroutines) 33 | } 34 | } 35 | } 36 | } 37 | 38 | apply(from = "../publish-mpp.gradle.kts") 39 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.native.binary.memoryModel=experimental 2 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonMain/kotlin/io/kotest/assertions/arrow/fx/coroutines/ExitCase.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.ExitCase 4 | import kotlin.contracts.ExperimentalContracts 5 | import kotlin.contracts.contract 6 | import kotlinx.coroutines.CancellationException 7 | 8 | @OptIn(ExperimentalContracts::class) 9 | public fun ExitCase.shouldBeCancelled( 10 | failureMessage: (ExitCase) -> String = { "Expected ExitCase.Cancelled, but found $it" } 11 | ): ExitCase.Cancelled { 12 | contract { 13 | returns() implies (this@shouldBeCancelled is ExitCase.Cancelled) 14 | } 15 | return when (this) { 16 | is ExitCase.Completed -> throw AssertionError(failureMessage(this)) 17 | is ExitCase.Cancelled -> this 18 | is ExitCase.Failure -> throw AssertionError(failureMessage(this)) 19 | } 20 | } 21 | 22 | @OptIn(ExperimentalContracts::class) 23 | public fun ExitCase.shouldBeCancelled(cancelled: CancellationException): ExitCase.Cancelled { 24 | contract { 25 | returns() implies (this@shouldBeCancelled is ExitCase.Cancelled) 26 | } 27 | return shouldBeCancelled().also { 28 | exception shouldBe cancelled 29 | } 30 | } 31 | 32 | @OptIn(ExperimentalContracts::class) 33 | public fun ExitCase.shouldBeCompleted( 34 | failureMessage: (ExitCase) -> String = { "Expected ExitCase.Completed, but found $it" } 35 | ): ExitCase.Completed { 36 | contract { 37 | returns() implies (this@shouldBeCompleted is ExitCase.Completed) 38 | } 39 | return when (this) { 40 | is ExitCase.Completed -> this 41 | is ExitCase.Cancelled -> throw AssertionError(failureMessage(this)) 42 | is ExitCase.Failure -> throw AssertionError(failureMessage(this)) 43 | } 44 | } 45 | 46 | @OptIn(ExperimentalContracts::class) 47 | public fun ExitCase.shouldBeFailure( 48 | failureMessage: (ExitCase) -> String = { "Expected ExitCase.Failure, but found $it" } 49 | ): ExitCase.Failure { 50 | contract { 51 | returns() implies (this@shouldBeFailure is ExitCase.Failure) 52 | } 53 | return when (this) { 54 | is ExitCase.Completed -> throw AssertionError(failureMessage(this)) 55 | is ExitCase.Cancelled -> throw AssertionError(failureMessage(this)) 56 | is ExitCase.Failure -> this 57 | } 58 | } 59 | 60 | @OptIn(ExperimentalContracts::class) 61 | public fun ExitCase.shouldBeFailure(throwable: Throwable): ExitCase.Failure { 62 | contract { 63 | returns() implies (this@shouldBeFailure is ExitCase.Failure) 64 | } 65 | return shouldBeFailure().also { 66 | failure shouldBe throwable 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonMain/kotlin/io/kotest/assertions/arrow/fx/coroutines/ProjectResource.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.Resource 4 | import io.kotest.core.extensions.LazyMaterialized 5 | import io.kotest.core.listeners.ProjectListener 6 | 7 | /** 8 | * Useful to register [Resource] on a Project wide configuration. 9 | * 10 | * For example: 11 | * 12 | * ```kotlin 13 | * object ProjectConfig : AbstractProjectConfig() { 14 | * val hikari: ProjectResource = 15 | * ProjectResource(Resource.fromCloseable { HikariDataSource(...) }) 16 | * 17 | * override fun extensions(): List = listOf(hikari) 18 | * } 19 | * 20 | * class MySpec : StringSpec({ 21 | * "my test" { 22 | * val dataSource: HikariDataSource = ProjectConfig.hikari.get() 23 | * } 24 | * }) 25 | * ``` 26 | */ 27 | public class ProjectResource private constructor( 28 | public val resource: Resource, 29 | private val default: ResourceLazyMaterialized 30 | ) : ProjectListener, LazyMaterialized by default { 31 | 32 | public constructor(resource: Resource) : this(resource, ResourceLazyMaterialized(resource)) 33 | 34 | override suspend fun beforeProject() { 35 | default.init() 36 | } 37 | 38 | override suspend fun afterProject() { 39 | default.release() 40 | default.close() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonMain/kotlin/io/kotest/assertions/arrow/fx/coroutines/Resource.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.Resource 4 | import arrow.fx.coroutines.resourceScope 5 | 6 | public suspend infix fun Resource.shouldBeResource(a: A): A = 7 | resourceScope { 8 | bind() shouldBe a 9 | } 10 | 11 | public suspend infix fun Resource.shouldBeResource(expected: Resource): A = 12 | resourceScope { 13 | bind() shouldBe expected.bind() 14 | } 15 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonMain/kotlin/io/kotest/assertions/arrow/fx/coroutines/ResourceExtension.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.* 4 | import io.kotest.core.extensions.LazyMaterialized 5 | import io.kotest.core.extensions.LazyMountableExtension 6 | import io.kotest.core.listeners.AfterSpecListener 7 | import io.kotest.core.listeners.TestListener 8 | import io.kotest.core.spec.Spec 9 | import io.kotest.core.test.TestCase 10 | import io.kotest.core.test.TestResult 11 | import io.kotest.core.test.TestType 12 | import io.kotest.core.test.isRootTest 13 | import io.kotest.mpp.atomics.AtomicReference 14 | import kotlinx.coroutines.CompletableDeferred 15 | import kotlinx.coroutines.DelicateCoroutinesApi 16 | 17 | public enum class LifecycleMode { 18 | Spec, EveryTest, Leaf, Root 19 | } 20 | 21 | /** Turns a [Resource] into a [LazyMountableExtension] as a [ResourceExtension] */ 22 | public fun Resource.extension(lifecycleMode: LifecycleMode = LifecycleMode.Spec): ResourceExtension = 23 | ResourceExtension(this, lifecycleMode) 24 | 25 | /** 26 | * Ensures that all finalizers of [resource] are invoked for the specified [LifecycleMode]. 27 | * Installing this [LazyMountableExtension] returns a [LazyMaterialized] 28 | * from which you can extract initialized [A] using [LazyMaterialized.get]. 29 | * 30 | * ```kotlin 31 | * class Connection(val name: String) { 32 | * fun open() = println("Open $name") 33 | * fun compute() = "Result of $name" 34 | * fun release(ex: ExitCase) = println("Closing $name with $ex") 35 | * } 36 | * 37 | * fun connection(name: String): Resource = 38 | * Resource({ Connection(name).also { it.open() } }, { conn, ex -> conn.release(ex) }) 39 | * 40 | * class ResourceSpec : StringSpec({ 41 | * val conn: TestResource = 42 | * install(ResourceExtension(connection("DB"))) 43 | * 44 | * "get" { 45 | * conn.get().compute() shouldBe "Result of DB" 46 | * } 47 | * 48 | * afterSpec { 49 | * shouldThrow { 50 | * conn.get() 51 | * }.message shouldBe "Resource already closed and cannot be re-opened." 52 | * } 53 | * }) 54 | * ``` 55 | * 56 | * `conn` cannot be used during or after `afterSpec` since at that point the resource is already released, 57 | * and it will result in [IllegalStateException] 58 | * 59 | * @param lifecycleMode allows installing the [Resource] for other scopes besides the default [LifecycleMode.Spec]. 60 | */ 61 | public class ResourceExtension( 62 | public val resource: Resource, public val lifecycleMode: LifecycleMode = LifecycleMode.Spec, 63 | ) : LazyMountableExtension, TestListener, AfterSpecListener { 64 | 65 | private var underlying: ResourceLazyMaterialized? = null 66 | 67 | override fun mount(configure: A.() -> Unit): LazyMaterialized = 68 | ResourceLazyMaterialized(resource, configure).also { 69 | underlying = it 70 | } 71 | 72 | override suspend fun beforeSpec(spec: Spec) { 73 | super.beforeSpec(spec) 74 | if (lifecycleMode == LifecycleMode.Spec) { 75 | underlying?.init() 76 | } 77 | } 78 | 79 | override suspend fun afterSpec(spec: Spec) { 80 | if (lifecycleMode == LifecycleMode.Spec) { 81 | underlying?.release() 82 | } 83 | underlying?.close() 84 | } 85 | 86 | override suspend fun beforeAny(testCase: TestCase) { 87 | val every = lifecycleMode == LifecycleMode.EveryTest 88 | val root = lifecycleMode == LifecycleMode.Root && testCase.isRootTest() 89 | val leaf = lifecycleMode == LifecycleMode.Leaf && testCase.type == TestType.Test 90 | if (every || root || leaf) { 91 | underlying?.init() 92 | } 93 | } 94 | 95 | override suspend fun afterAny(testCase: TestCase, result: TestResult) { 96 | val every = lifecycleMode == LifecycleMode.EveryTest 97 | val root = lifecycleMode == LifecycleMode.Root && testCase.isRootTest() 98 | val leaf = lifecycleMode == LifecycleMode.Leaf && testCase.type == TestType.Test 99 | if (every || root || leaf) { 100 | underlying?.release() 101 | } 102 | } 103 | } 104 | 105 | internal class ResourceLazyMaterialized( 106 | private val resource: Resource, 107 | private val configure: A.() -> Unit = {}, 108 | ) : LazyMaterialized { 109 | 110 | sealed interface State { 111 | data object Empty : State 112 | data object Closed : State 113 | data class Loading( 114 | val acquiring: CompletableDeferred = CompletableDeferred(), 115 | val finalizers: CompletableDeferred Unit> = CompletableDeferred(), 116 | ) : State 117 | 118 | data class Done(val value: A, val finalizers: suspend (ExitCase) -> Unit) : State 119 | } 120 | 121 | private val state = AtomicReference>(State.Empty) 122 | 123 | override suspend fun get(): A = init() 124 | 125 | @OptIn(DelicateCoroutinesApi::class) 126 | tailrec suspend fun init(): A = when (val current = state.value) { 127 | is State.Done -> current.value 128 | is State.Loading -> current.acquiring.await() 129 | is State.Closed -> throw IllegalStateException("Resource already closed and cannot be re-opened.") 130 | State.Empty -> { 131 | val loading = State.Loading() 132 | if (state.compareAndSet(State.Empty, loading)) { 133 | val (res, fin) = resource.allocate() 134 | .let { (a, finalizer) -> 135 | @Suppress("NAME_SHADOWING") 136 | val finalizer: suspend (ExitCase) -> Unit = { finalizer(it) } 137 | Pair(a, finalizer) 138 | } 139 | state.value = State.Done(res, fin) 140 | loading.acquiring.complete(res.also(configure)) 141 | loading.finalizers.complete(fin) 142 | res 143 | } else init() 144 | } 145 | } 146 | 147 | tailrec suspend fun release(): Unit = when (val current = state.value) { 148 | State.Empty -> Unit 149 | is State.Done -> if (state.compareAndSet(current, State.Empty)) current.finalizers(ExitCase.Completed) 150 | else release() 151 | 152 | is State.Loading -> if (state.compareAndSet(current, State.Empty)) current.finalizers.await() 153 | .invoke(ExitCase.Completed) 154 | else release() 155 | 156 | State.Closed -> Unit 157 | } 158 | 159 | fun close() { 160 | state.value = State.Closed 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonMain/kotlin/io/kotest/assertions/arrow/fx/coroutines/shouldBe.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import io.kotest.matchers.shouldBe as coreShouldBe 4 | 5 | internal infix fun A.shouldBe(a: A): A = 6 | also { 7 | this coreShouldBe a 8 | } 9 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonTest/kotlin/io/kotest/assertions/arrow/fx/coroutines/ExitCaseTest.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.ExitCase 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.property.Arb 6 | import io.kotest.property.arbitrary.map 7 | import io.kotest.property.arbitrary.string 8 | import io.kotest.property.checkAll 9 | import kotlinx.coroutines.CancellationException 10 | 11 | class ExitCaseTest : StringSpec({ 12 | "shouldBeCancelled" { 13 | ExitCase.Cancelled(CancellationException("")).shouldBeCancelled() 14 | } 15 | 16 | "shouldBeCancelled(e)" { 17 | checkAll(Arb.string().map { CancellationException(it) }) { e -> 18 | ExitCase.Cancelled(e).shouldBeCancelled(e) 19 | } 20 | } 21 | 22 | "shouldBeCompleted" { 23 | ExitCase.Completed.shouldBeCompleted() 24 | } 25 | 26 | "shouldBeFailure" { 27 | checkAll(Arb.string().map(::RuntimeException)) { e -> 28 | ExitCase.Failure(e).shouldBeFailure() 29 | } 30 | } 31 | 32 | "shouldBeFailure(e)" { 33 | checkAll(Arb.string().map(::RuntimeException)) { e -> 34 | ExitCase.Failure(e).shouldBeFailure(e) 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonTest/kotlin/io/kotest/assertions/arrow/fx/coroutines/ResourceExtensionLeafTest.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import io.kotest.core.extensions.install 4 | import io.kotest.core.spec.style.DescribeSpec 5 | 6 | class ResourceExtensionLeafTest : DescribeSpec({ 7 | val sub = ResourceUnderTest() 8 | 9 | install(ResourceExtension(sub.asResource(), LifecycleMode.Leaf)) { 10 | configure() 11 | } 12 | 13 | describe("context") { 14 | sub.isOpen shouldBe false 15 | sub.isConfigured shouldBe false 16 | sub.count shouldBe 0 17 | 18 | it("should initialize per leaf") { 19 | sub.isOpen shouldBe true 20 | sub.isConfigured shouldBe true 21 | sub.count shouldBe 1 22 | } 23 | 24 | it("this test should have a new container") { 25 | sub.isConfigured shouldBe true 26 | sub.count shouldBe 2 27 | } 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonTest/kotlin/io/kotest/assertions/arrow/fx/coroutines/ResourceExtensionRootTest.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import io.kotest.core.extensions.install 4 | import io.kotest.core.spec.style.FunSpec 5 | 6 | class ResourceExtensionRootTest : FunSpec({ 7 | val sub = ResourceUnderTest() 8 | 9 | install(ResourceExtension(sub.asResource(), LifecycleMode.Root)) { 10 | configure() 11 | } 12 | 13 | beforeSpec { 14 | sub.count shouldBe 0 15 | sub.isConfigured shouldBe false 16 | sub.isOpen shouldBe false 17 | sub.isReleased shouldBe false 18 | } 19 | 20 | context("initializes once for container") { 21 | test("should initialize per root") { 22 | sub.count shouldBe 1 23 | sub.isConfigured shouldBe true 24 | sub.isOpen shouldBe true 25 | sub.isReleased shouldBe false 26 | } 27 | 28 | test("should not have re-initialized") { 29 | sub.count shouldBe 1 30 | } 31 | } 32 | 33 | test("this root test should have a different container") { 34 | sub.count shouldBe 2 35 | sub.isConfigured shouldBe true 36 | sub.isOpen shouldBe true 37 | sub.isReleased shouldBe false 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonTest/kotlin/io/kotest/assertions/arrow/fx/coroutines/ResourceExtensionSpecTest.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import io.kotest.assertions.throwables.shouldThrow 4 | import io.kotest.core.extensions.install 5 | import io.kotest.core.spec.style.FunSpec 6 | 7 | class ResourceExtensionSpecTest : FunSpec({ 8 | 9 | val sub = ResourceUnderTest() 10 | 11 | val res = install(sub.asResource().extension()) { 12 | configure() 13 | } 14 | 15 | test("acquired but not released") { 16 | res.get() shouldBe sub 17 | sub.isOpen shouldBe true 18 | sub.isConfigured shouldBe true 19 | } 20 | 21 | afterSpec { 22 | sub.isReleased shouldBe true 23 | shouldThrow { 24 | res.get() 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /kotest-assertions-arrow-fx-coroutines/src/commonTest/kotlin/io/kotest/assertions/arrow/fx/coroutines/ResourceUnderTest.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.fx.coroutines 2 | 3 | import arrow.fx.coroutines.Resource 4 | import arrow.fx.coroutines.resource 5 | 6 | class ResourceUnderTest { 7 | var count: Int = 0 8 | var isOpen: Boolean = false 9 | private set 10 | var isReleased: Boolean = false 11 | private set 12 | var isConfigured: Boolean = false 13 | private set 14 | 15 | fun configure(): Unit { 16 | isConfigured = true 17 | } 18 | 19 | fun asResource(): Resource = resource { 20 | install( 21 | acquire = { 22 | isReleased = false 23 | isOpen = true 24 | count++ 25 | this@ResourceUnderTest 26 | }, 27 | release = { res, _ -> 28 | res.isOpen = false 29 | res.isConfigured = false 30 | res.isReleased = true 31 | } 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotest-conventions") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain { 8 | dependencies { 9 | compileOnly(libs.arrow.core) 10 | implementation(libs.kotest.assertions.core) 11 | } 12 | } 13 | 14 | commonTest { 15 | dependencies { 16 | implementation(libs.arrow.core) 17 | implementation(libs.kotest.framework.engine) 18 | implementation(libs.kotest.property) 19 | } 20 | } 21 | 22 | jsMain { 23 | dependencies { 24 | api(libs.arrow.core) 25 | } 26 | } 27 | 28 | nativeMain { 29 | dependencies { 30 | implementation(libs.arrow.core) 31 | } 32 | } 33 | } 34 | } 35 | 36 | apply(from = "../publish-mpp.gradle.kts") 37 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow 2 | 3 | import io.kotest.matchers.shouldBe as coreShouldBe 4 | import io.kotest.matchers.shouldNotBe as coreShouldNotBe 5 | 6 | internal infix fun A.shouldBe(a: A): A { 7 | this coreShouldBe a 8 | return this 9 | } 10 | 11 | 12 | internal infix fun A.shouldNotBe(a: A?): A { 13 | this coreShouldNotBe a 14 | return this 15 | } 16 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Either.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.Either 4 | import arrow.core.Either.Left 5 | import arrow.core.Either.Right 6 | import arrow.core.identity 7 | import io.kotest.assertions.arrow.shouldBe 8 | import io.kotest.assertions.arrow.shouldNotBe 9 | import kotlin.contracts.ExperimentalContracts 10 | import kotlin.contracts.contract 11 | 12 | /** 13 | * smart casts to [Either.Right] and fails with [failureMessage] otherwise. 14 | * ```kotlin 15 | * import arrow.core.Either.Right 16 | * import arrow.core.Either 17 | * import shouldBeRight 18 | * 19 | * fun main() { 20 | * //sampleStart 21 | * fun squared(i: Int): Int = i * i 22 | * val result = squared(5) 23 | * val either = Either.conditionally(result == 25, { result }, { 25 }) 24 | * val a = either.shouldBeRight { "5 * 5 == 25 but was $it " } 25 | * val smartCasted: Right = either 26 | * //sampleEnd 27 | * println(smartCasted) 28 | * } 29 | * ``` 30 | */ 31 | @OptIn(ExperimentalContracts::class) 32 | public fun Either.shouldBeRight(failureMessage: (A) -> String = { "Expected Either.Right, but found Either.Left with value $it" }): B { 33 | contract { 34 | returns() implies (this@shouldBeRight is Right) 35 | } 36 | return when (this) { 37 | is Right -> value 38 | is Left -> throw AssertionError(failureMessage(value)) 39 | } 40 | } 41 | 42 | public infix fun Either.shouldBeRight(b: B): B = 43 | shouldBeRight().shouldBe(b) 44 | 45 | public infix fun Either.shouldNotBeRight(b: B): B = 46 | shouldBeRight().shouldNotBe(b) 47 | 48 | /** 49 | * smart casts to [Either.Left] and fails with [failureMessage] otherwise. 50 | * ```kotlin 51 | * import arrow.core.Either.Left 52 | * import arrow.core.Either 53 | * import shouldBeLeft 54 | * 55 | * fun main() { 56 | * //sampleStart 57 | * val either = Either.conditionally(false, { "Always false" }, { throw RuntimeException("Will never execute") }) 58 | * val a = either.shouldBeLeft() 59 | * val smartCasted: Left = either 60 | * //sampleEnd 61 | * println(smartCasted) 62 | * } 63 | * ``` 64 | */ 65 | @OptIn(ExperimentalContracts::class) 66 | public fun Either.shouldBeLeft(failureMessage: (B) -> String = { "Expected Either.Left, but found Either.Right with value $it" }): A { 67 | contract { 68 | returns() implies (this@shouldBeLeft is Left) 69 | } 70 | return when (this) { 71 | is Left -> value 72 | is Right -> throw AssertionError(failureMessage(value)) 73 | } 74 | } 75 | 76 | public infix fun Either.shouldBeLeft(a: A): A = 77 | shouldBeLeft().shouldBe(a) 78 | 79 | public infix fun Either.shouldNotBeLeft(a: A): A = 80 | shouldBeLeft().shouldNotBe(a) 81 | 82 | /** for testing success & error scenarios with an [Either] generator **/ 83 | public fun Either.rethrow(): A = 84 | fold({ throw it }, ::identity) 85 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Ior.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.Ior 4 | import arrow.core.Ior.Left 5 | import arrow.core.Ior.Right 6 | import arrow.core.Ior.Both 7 | import arrow.core.identity 8 | import io.kotest.assertions.arrow.shouldBe 9 | import io.kotest.assertions.arrow.shouldNotBe 10 | import kotlin.contracts.ExperimentalContracts 11 | import kotlin.contracts.contract 12 | import io.kotest.matchers.Matcher 13 | import io.kotest.matchers.MatcherResult 14 | import io.kotest.matchers.should 15 | import io.kotest.matchers.shouldNot 16 | 17 | /** 18 | * smart casts to [Ior.Right] and fails with [failureMessage] otherwise. 19 | */ 20 | @OptIn(ExperimentalContracts::class) 21 | public fun Ior.shouldBeRight(failureMessage: (Ior) -> String = { "Expected Ior.Right, but found ${it::class.simpleName}" }): B { 22 | contract { 23 | returns() implies (this@shouldBeRight is Right) 24 | } 25 | return when (this) { 26 | is Right -> value 27 | else -> throw AssertionError(failureMessage(this)) 28 | } 29 | } 30 | 31 | public infix fun Ior.shouldBeRight(b: B): B = 32 | shouldBeRight().shouldBe(b) 33 | 34 | public infix fun Ior.shouldNotBeRight(b: B): B = 35 | shouldBeRight().shouldNotBe(b) 36 | 37 | /** 38 | * smart casts to [Ior.Left] and fails with [failureMessage] otherwise. 39 | */ 40 | @OptIn(ExperimentalContracts::class) 41 | public fun Ior.shouldBeLeft(failureMessage: (Ior) -> String = { "Expected Ior.Left, but found ${it::class.simpleName}" }): A { 42 | contract { 43 | returns() implies (this@shouldBeLeft is Left) 44 | } 45 | return when (this) { 46 | is Left -> value 47 | else -> throw AssertionError(failureMessage(this)) 48 | } 49 | } 50 | 51 | public infix fun Ior.shouldBeLeft(a: A): A = 52 | shouldBeLeft().shouldBe(a) 53 | 54 | public infix fun Ior.shouldNotBeLeft(a: A): A = 55 | shouldBeLeft().shouldNotBe(a) 56 | 57 | public fun beBoth(): Matcher> = Matcher { 58 | ior -> 59 | MatcherResult( 60 | ior is Both, 61 | { "Expected ior to be a Both, but was: ${ior::class.simpleName}" }, 62 | { "Expected ior to not be a Both." } 63 | ) 64 | } 65 | 66 | /** 67 | * smart casts to [Ior.Both] 68 | */ 69 | @OptIn(ExperimentalContracts::class) 70 | public fun Ior.shouldBeBoth(): Ior.Both { 71 | contract { 72 | returns() implies (this@shouldBeBoth is Ior.Both) 73 | } 74 | 75 | this should beBoth() 76 | 77 | return this as Ior.Both 78 | } 79 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Nel.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.NonEmptyList 4 | import io.kotest.matchers.collections.shouldBeSorted 5 | import io.kotest.matchers.collections.shouldNotBeSorted 6 | import io.kotest.matchers.collections.shouldBeUnique 7 | import io.kotest.matchers.collections.shouldContain 8 | import io.kotest.matchers.collections.shouldContainAll 9 | import io.kotest.matchers.collections.shouldContainDuplicates 10 | import io.kotest.matchers.collections.shouldContainNoNulls 11 | import io.kotest.matchers.collections.shouldContainNull 12 | import io.kotest.matchers.collections.shouldContainOnlyNulls 13 | import io.kotest.matchers.collections.shouldHaveElementAt 14 | import io.kotest.matchers.collections.shouldHaveSingleElement 15 | import io.kotest.matchers.collections.shouldHaveSize 16 | import io.kotest.matchers.collections.shouldNotHaveSize 17 | import io.kotest.matchers.collections.shouldNotBeUnique 18 | import io.kotest.matchers.collections.shouldNotContain 19 | import io.kotest.matchers.collections.shouldNotContainAll 20 | import io.kotest.matchers.collections.shouldNotContainDuplicates 21 | import io.kotest.matchers.collections.shouldNotContainNoNulls 22 | import io.kotest.matchers.collections.shouldNotContainNull 23 | import io.kotest.matchers.collections.shouldNotContainOnlyNulls 24 | import io.kotest.matchers.collections.shouldNotHaveElementAt 25 | import io.kotest.matchers.collections.shouldNotHaveSingleElement 26 | 27 | public fun NonEmptyList.shouldContainOnlyNulls(): NonEmptyList = 28 | apply { all.shouldContainOnlyNulls() } 29 | 30 | public fun NonEmptyList.shouldNotContainOnlyNulls(): NonEmptyList = 31 | apply { all.shouldNotContainOnlyNulls() } 32 | 33 | public fun NonEmptyList.shouldContainNull(): NonEmptyList = 34 | apply { all.shouldContainNull() } 35 | 36 | public fun NonEmptyList.shouldNotContainNull(): NonEmptyList = 37 | apply { all.shouldNotContainNull() } 38 | 39 | public fun NonEmptyList.shouldHaveElementAt(index: Int, element: A): Unit = 40 | all.shouldHaveElementAt(index, element) 41 | 42 | public fun NonEmptyList.shouldNotHaveElementAt(index: Int, element: A): Unit = 43 | all.shouldNotHaveElementAt(index, element) 44 | 45 | public fun NonEmptyList.shouldContainNoNulls(): NonEmptyList = 46 | apply { all.shouldContainNoNulls() } 47 | 48 | public fun NonEmptyList.shouldNotContainNoNulls(): NonEmptyList = 49 | apply { all.shouldNotContainNoNulls() } 50 | 51 | public infix fun NonEmptyList.shouldContain(a: A): Unit { 52 | all.shouldContain(a) 53 | } 54 | 55 | public infix fun NonEmptyList.shouldNotContain(a: A): Unit { 56 | all.shouldNotContain(a) 57 | } 58 | 59 | public fun NonEmptyList.shouldBeUnique(): NonEmptyList = 60 | apply { all.shouldBeUnique() } 61 | 62 | public fun NonEmptyList.shouldNotBeUnique(): NonEmptyList = 63 | apply { all.shouldNotBeUnique() } 64 | 65 | public fun NonEmptyList.shouldContainDuplicates(): NonEmptyList = 66 | apply { all.shouldContainDuplicates() } 67 | 68 | public fun NonEmptyList.shouldNotContainDuplicates(): NonEmptyList = 69 | apply { all.shouldNotContainDuplicates() } 70 | 71 | public fun NonEmptyList.shouldContainAll(vararg ts: A): Unit = 72 | all.shouldContainAll(*ts) 73 | 74 | public fun NonEmptyList.shouldNotContainAll(vararg ts: A): Unit = 75 | all.shouldNotContainAll(*ts) 76 | 77 | public infix fun NonEmptyList.shouldContainAll(ts: List): Unit = 78 | all.shouldContainAll(ts) 79 | 80 | public infix fun NonEmptyList.shouldNotContainAll(ts: List): Unit = 81 | all.shouldNotContainAll(ts) 82 | 83 | public infix fun NonEmptyList.shouldHaveSize(size: Int): NonEmptyList = 84 | apply { all.shouldHaveSize(size) } 85 | 86 | public infix fun NonEmptyList.shouldNotHaveSize(size: Int): NonEmptyList = 87 | apply { all.shouldNotHaveSize(size) } 88 | 89 | public infix fun NonEmptyList.shouldHaveSingleElement(a: A): Unit = 90 | all.shouldHaveSingleElement(a) 91 | 92 | public infix fun NonEmptyList.shouldNotHaveSingleElement(a: A): Unit = 93 | all.shouldNotHaveSingleElement(a) 94 | 95 | public fun > NonEmptyList.shouldBeSorted(): NonEmptyList = 96 | apply { all.shouldBeSorted() } 97 | 98 | public fun > NonEmptyList.shouldNotBeSorted(): NonEmptyList = 99 | apply { all.shouldNotBeSorted() } 100 | 101 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Nelnspectors.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.NonEmptyList 4 | import io.kotest.inspectors.forAll 5 | import io.kotest.inspectors.forAny 6 | import io.kotest.inspectors.forAtLeast 7 | import io.kotest.inspectors.forAtLeastOne 8 | import io.kotest.inspectors.forAtMost 9 | import io.kotest.inspectors.forAtMostOne 10 | import io.kotest.inspectors.forExactly 11 | import io.kotest.inspectors.forNone 12 | import io.kotest.inspectors.forOne 13 | import io.kotest.inspectors.forSome 14 | 15 | @Deprecated( 16 | "use Kotest's Collection.forAll.", 17 | ReplaceWith("forAll", "io.kotest.inspectors.forAll") 18 | ) 19 | public fun NonEmptyList.forAll(f: (A) -> Unit): Unit { 20 | all.forAll(f) 21 | } 22 | 23 | @Deprecated( 24 | "use Kotest's Collection.forOne.", 25 | ReplaceWith("forOne", "io.kotest.inspectors.forOne") 26 | ) 27 | public fun NonEmptyList.forOne(f: (A) -> Unit): Unit { 28 | all.forOne(f) 29 | } 30 | 31 | @Deprecated( 32 | "use Kotest's Collection.forExactly.", 33 | ReplaceWith("forExactly", "io.kotest.inspectors.forExactly") 34 | ) 35 | public fun NonEmptyList.forExactly(k: Int, f: (A) -> Unit): Unit { 36 | all.forExactly(k, f) 37 | } 38 | 39 | @Deprecated( 40 | "use Kotest's Collection.forSome.", 41 | ReplaceWith("forSome", "io.kotest.inspectors.forSome") 42 | ) 43 | public fun NonEmptyList.forSome(f: (A) -> Unit): Unit { 44 | all.forSome(f) 45 | } 46 | 47 | @Deprecated( 48 | "use Kotest's Collection.forAny.", 49 | ReplaceWith("forAny", "io.kotest.inspectors.forAny") 50 | ) 51 | public fun NonEmptyList.forAny(f: (A) -> Unit): Unit { 52 | all.forAny(f) 53 | } 54 | 55 | @Deprecated( 56 | "use Kotest's Collection.forAtLeastOne.", 57 | ReplaceWith("forAtLeastOne", "io.kotest.inspectors.forAtLeastOne") 58 | ) 59 | public fun NonEmptyList.forAtLeastOne(f: (A) -> Unit): Unit { 60 | all.forAtLeastOne(f) 61 | } 62 | 63 | @Deprecated( 64 | "use Kotest's Collection.forAtLeast.", 65 | ReplaceWith("forAtLeast", "io.kotest.inspectors.forAtLeast") 66 | ) 67 | public fun NonEmptyList.forAtLeast(k: Int, f: (A) -> Unit): Unit { 68 | all.forAtLeast(k, f) 69 | } 70 | 71 | @Deprecated( 72 | "use Kotest's Collection.forAtMostOne.", 73 | ReplaceWith("forAtMostOne", "io.kotest.inspectors.forAtMostOne") 74 | ) 75 | public fun NonEmptyList.forAtMostOne(f: (A) -> Unit): Unit { 76 | all.forAtMostOne(f) 77 | } 78 | 79 | @Deprecated( 80 | "use Kotest's Collection.forAtMost.", 81 | ReplaceWith("forAtMost", "io.kotest.inspectors.forAtMost") 82 | ) 83 | public fun NonEmptyList.forAtMost(k: Int, f: (A) -> Unit): Unit { 84 | all.forAtMost(k, f) 85 | } 86 | 87 | @Deprecated( 88 | "use Kotest's Collection.forNone.", 89 | ReplaceWith("forNone", "io.kotest.inspectors.forNone") 90 | ) 91 | public fun NonEmptyList.forNone(f: (A) -> Unit): Unit { 92 | all.forNone(f) 93 | } 94 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Option.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.None 4 | import arrow.core.Option 5 | import arrow.core.Some 6 | import io.kotest.assertions.arrow.shouldBe 7 | import io.kotest.assertions.arrow.shouldNotBe 8 | import kotlin.contracts.ExperimentalContracts 9 | import kotlin.contracts.contract 10 | 11 | /** 12 | * smart casts to [Some] and fails with [failureMessage] otherwise. 13 | * ```kotlin 14 | * import arrow.core.Option 15 | * import arrow.core.Some 16 | * import shouldBeSome 17 | * 18 | * fun main() { 19 | * //sampleStart 20 | * val list = listOf("4", "5", "6") 21 | * val option = Option.fromNullable(list.getOrNull(2)) 22 | * val element = option.shouldBeSome() 23 | * val smartCasted: Some = option 24 | * //sampleEnd 25 | * println(smartCasted) 26 | * } 27 | * ``` 28 | */ 29 | @OptIn(ExperimentalContracts::class) 30 | public fun Option.shouldBeSome(failureMessage: () -> String = { "Expected Some, but found None" }): A { 31 | contract { 32 | returns() implies (this@shouldBeSome is Some) 33 | } 34 | return when (this) { 35 | None -> throw AssertionError(failureMessage()) 36 | is Some -> value 37 | } 38 | } 39 | 40 | public infix fun Option.shouldBeSome(a: A): A = 41 | shouldBeSome().shouldBe(a) 42 | 43 | public infix fun Option.shouldNotBeSome(a: A): A = 44 | shouldBeSome().shouldNotBe(a) 45 | 46 | /** 47 | * smart casts to [None] and fails with [failureMessage] otherwise. 48 | * ```kotlin 49 | * import arrow.core.Option 50 | * import arrow.core.None 51 | * import shouldBeNone 52 | * 53 | * fun main() { 54 | * //sampleStart 55 | * val list = listOf("4", "5", "6") 56 | * val option = Option.fromNullable(list.getOrNull(5)) 57 | * val element = option.shouldBeNone() 58 | * val smartCasted: None = option 59 | * //sampleEnd 60 | * println(smartCasted) 61 | * } 62 | * ``` 63 | */ 64 | @OptIn(ExperimentalContracts::class) 65 | public fun Option.shouldBeNone(failureMessage: (Some) -> String = { "Expected None, but found Some with value ${it.value}" }): None { 66 | contract { 67 | returns() implies (this@shouldBeNone is None) 68 | } 69 | return when (this) { 70 | None -> None 71 | is Some -> throw AssertionError(failureMessage(this)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonMain/kotlin/io/kotest/assertions/arrow/core/Raise.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.raise.Raise 4 | import arrow.core.raise.recover 5 | import io.kotest.assertions.failure 6 | import io.kotest.assertions.print.print 7 | 8 | /** 9 | * Verifies if a block of code will raise a specified type of [T] (or subclasses). 10 | * 11 | * This function will include subclasses of [T]. For example, if you test for [CharSequence] and the code raises 12 | * [String], the test will pass. 13 | * 14 | * ```kotlin 15 | * val raised: String = shouldRaise { 16 | * raise("failed") 17 | * } 18 | * ``` 19 | */ 20 | public inline fun shouldRaise(block: Raise.() -> Any?): T { 21 | val expectedRaiseClass = T::class 22 | return recover({ 23 | block(this) 24 | throw failure("Expected to raise ${expectedRaiseClass.simpleName} but nothing was raised.") 25 | }) { raised -> 26 | when (raised) { 27 | is T -> raised 28 | null -> throw failure("Expected to raise ${expectedRaiseClass.simpleName} but was raised instead.") 29 | else -> throw failure("Expected to raise ${expectedRaiseClass.simpleName} but ${raised::class.simpleName} was raised instead.") 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * Verifies that a block of code will not raise anything. 36 | * 37 | * ```kotlin 38 | * val raised: String = shouldNotRaise { 39 | * raise("failed") // fails 40 | * } 41 | * ``` 42 | */ 43 | public inline fun shouldNotRaise(block: Raise.() -> T): T { 44 | return recover(block) { raised -> 45 | throw failure("No raise expected, but ${raised.print().value} was raised.") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonTest/kotlin/io/kotest/assertions/arrow/core/EitherMatchers.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.Either 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.property.Arb 6 | import io.kotest.property.arbitrary.bind 7 | import io.kotest.property.arbitrary.filter 8 | import io.kotest.property.arbitrary.int 9 | import io.kotest.property.checkAll 10 | 11 | class EitherMatchers : StringSpec({ 12 | "shouldBeRight"{ 13 | checkAll(Arb.int()) { i -> 14 | Either.Right(i) shouldBeRight i 15 | } 16 | } 17 | 18 | "shouldNotBeRight" { 19 | checkAll( 20 | Arb.bind(Arb.int(), Arb.int(), ::Pair) 21 | .filter { (a, b) -> a != b } 22 | ) { (a, b) -> 23 | Either.Right(a) shouldNotBeRight b 24 | } 25 | } 26 | 27 | "shouldBeLeft"{ 28 | checkAll(Arb.int()) { i -> 29 | Either.Left(i) shouldBeLeft i 30 | } 31 | } 32 | 33 | "shouldNotBeLeft" { 34 | checkAll( 35 | Arb.bind(Arb.int(), Arb.int(), ::Pair) 36 | .filter { (a, b) -> a != b } 37 | ) { (a, b) -> 38 | Either.Left(a) shouldNotBeLeft b 39 | } 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonTest/kotlin/io/kotest/assertions/arrow/core/IorMatchers.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.Ior 4 | import io.kotest.core.spec.style.StringSpec 5 | import io.kotest.property.Arb 6 | import io.kotest.property.arbitrary.bind 7 | import io.kotest.property.arbitrary.filter 8 | import io.kotest.property.arbitrary.int 9 | import io.kotest.property.arbitrary.string 10 | import io.kotest.property.checkAll 11 | import io.kotest.matchers.shouldBe 12 | 13 | class IorMatchers : StringSpec({ 14 | "shouldBeRight"{ 15 | checkAll(Arb.int()) { i -> 16 | Ior.Right(i) shouldBeRight i 17 | } 18 | } 19 | 20 | "shouldNotBeRight" { 21 | checkAll( 22 | Arb.bind(Arb.int(), Arb.int(), ::Pair) 23 | .filter { (a, b) -> a != b } 24 | ) { (a, b) -> 25 | Ior.Right(a) shouldNotBeRight b 26 | } 27 | } 28 | 29 | "shouldBeLeft"{ 30 | checkAll(Arb.int()) { i -> 31 | Ior.Left(i) shouldBeLeft i 32 | } 33 | } 34 | 35 | "shouldNotBeLeft" { 36 | checkAll( 37 | Arb.bind(Arb.int(), Arb.int(), ::Pair) 38 | .filter { (a, b) -> a != b } 39 | ) { (a, b) -> 40 | Ior.Left(a) shouldNotBeLeft b 41 | } 42 | } 43 | 44 | "shouldBeBoth" { 45 | checkAll(Arb.int(), Arb.string()) { i,j -> 46 | val ior = Ior.Both(i,j) 47 | ior.shouldBeBoth() 48 | ior.leftValue shouldBe i 49 | ior.rightValue shouldBe j 50 | } 51 | } 52 | }) 53 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonTest/kotlin/io/kotest/assertions/arrow/core/NelMatchers.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.NonEmptyList 4 | import io.kotest.assertions.throwables.shouldThrow 5 | import io.kotest.core.spec.style.StringSpec 6 | 7 | class NelMatchers : StringSpec({ 8 | "containNull: a nel contains at least one null" { 9 | NonEmptyList(1, listOf(2, null)).shouldContainNull() 10 | NonEmptyList(null, listOf()).shouldContainNull() 11 | NonEmptyList(1, listOf(2)).shouldNotContainNull() 12 | NonEmptyList(1, listOf()).shouldNotContainNull() 13 | } 14 | 15 | "haveElementAt: a nel contains an element at the right position" { 16 | NonEmptyList(1, listOf(2, null)).shouldHaveElementAt(1, 2) 17 | NonEmptyList(1, listOf(2, null)).shouldNotHaveElementAt(0, 42) 18 | } 19 | 20 | "a collection is sorted" { 21 | NonEmptyList(1, listOf(2, 3, 4)).shouldBeSorted() 22 | shouldThrow { 23 | NonEmptyList(2, listOf(1)).shouldBeSorted() 24 | } 25 | } 26 | 27 | "a collection is not sorted" { 28 | NonEmptyList(3, listOf(2, 1, 4)).shouldNotBeSorted() 29 | NonEmptyList(5, listOf(2, 3, 4)).shouldNotBeSorted() 30 | } 31 | 32 | "haveDuplicates: a collection is unique or not" { 33 | NonEmptyList(1, listOf(2, 3, 3)).shouldContainDuplicates() 34 | NonEmptyList(1, listOf(2, 3, 4)).shouldNotContainDuplicates() 35 | } 36 | 37 | "beUnique: a collection is unique or not" { 38 | NonEmptyList(1, listOf(2, 3, 4)).shouldBeUnique() 39 | NonEmptyList(1, listOf(2, 3, 3)).shouldNotBeUnique() 40 | } 41 | 42 | "a collection contains a single given element" { 43 | NonEmptyList(1, listOf()) shouldHaveSingleElement 1 44 | shouldThrow { 45 | NonEmptyList(1, listOf()) shouldHaveSingleElement 2 46 | } 47 | shouldThrow { 48 | NonEmptyList(1, listOf(2)) shouldHaveSingleElement 2 49 | } 50 | 51 | NonEmptyList(1, listOf()) shouldHaveSingleElement 1 52 | } 53 | 54 | "a collection does not contain a single element" { 55 | NonEmptyList(1, listOf(2)) shouldNotHaveSingleElement 1 56 | } 57 | 58 | "a collection contains an element" { 59 | NonEmptyList(1, listOf(2, 3)) shouldContain 2 60 | NonEmptyList(1, listOf(2, 3)) shouldNotContain 4 61 | shouldThrow { 62 | NonEmptyList(1, listOf(2, 3)) shouldContain 4 63 | } 64 | } 65 | 66 | "a collection has a certain size" { 67 | NonEmptyList(1, listOf(2, 3)) shouldHaveSize 3 68 | NonEmptyList(1, listOf(2, 3)) shouldNotHaveSize 2 69 | shouldThrow { 70 | NonEmptyList(1, listOf(2, 3)) shouldHaveSize 2 71 | } 72 | } 73 | 74 | "a collection contains zero nulls" { 75 | NonEmptyList(1, listOf(2, 3)).shouldContainNoNulls() 76 | NonEmptyList(null, listOf(null, null)).shouldNotContainNoNulls() 77 | NonEmptyList(1, listOf(null, null)).shouldNotContainNoNulls() 78 | } 79 | 80 | "a collection contains only nulls" { 81 | NonEmptyList(null, listOf(null, null)).shouldContainOnlyNulls() 82 | NonEmptyList(1, listOf(null, null)).shouldNotContainOnlyNulls() 83 | NonEmptyList(1, listOf(2, 3)).shouldNotContainOnlyNulls() 84 | } 85 | 86 | "a collection contains all the elements but in any order" { 87 | val col = NonEmptyList(1, listOf(2, 3, 4, 5)) 88 | 89 | col.shouldContainAll(1, 2, 3) 90 | col.shouldContainAll(3, 2, 1) 91 | col.shouldContainAll(5, 1) 92 | col.shouldContainAll(1, 5) 93 | col.shouldContainAll(1) 94 | col.shouldContainAll(5) 95 | 96 | col.shouldContainAll(1, 2, 3, 4) 97 | col.shouldContainAll(1, 2, 3, 4, 5) 98 | col.shouldContainAll(3, 2, 1) 99 | col.shouldContainAll(5, 4, 3, 2, 1) 100 | col shouldContainAll listOf(1, 2, 3, 4) 101 | 102 | shouldThrow { 103 | col.shouldContainAll(1, 2, 6) 104 | } 105 | 106 | shouldThrow { 107 | col.shouldContainAll(6) 108 | } 109 | 110 | shouldThrow { 111 | col.shouldContainAll(0, 1, 2) 112 | } 113 | 114 | shouldThrow { 115 | col.shouldContainAll(3, 2, 0) 116 | } 117 | } 118 | 119 | "a collection shouldNot containAll elements" { 120 | val col = NonEmptyList(1, listOf(2, 3, 4, 5)) 121 | 122 | col.shouldNotContainAll(99, 88, 77) 123 | col shouldNotContainAll listOf(99, 88, 77) 124 | } 125 | }) 126 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonTest/kotlin/io/kotest/assertions/arrow/core/OptionMatchers.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.Option 4 | import arrow.core.Some 5 | import io.kotest.assertions.throwables.shouldThrow 6 | import io.kotest.core.spec.style.StringSpec 7 | import io.kotest.matchers.shouldBe 8 | import io.kotest.matchers.shouldNotBe 9 | import io.kotest.property.Arb 10 | import io.kotest.property.arbitrary.bind 11 | import io.kotest.property.arbitrary.filter 12 | import io.kotest.property.arbitrary.int 13 | import io.kotest.property.checkAll 14 | 15 | class OptionMatchers : StringSpec({ 16 | "Option.shouldBeSome" { 17 | checkAll(Arb.int()) { i -> 18 | Option(i) shouldBeSome i 19 | } 20 | } 21 | 22 | "shouldNotBeSome" { 23 | checkAll( 24 | Arb.bind(Arb.int(), Arb.int(), ::Pair) 25 | .filter { (a, b) -> a != b } 26 | ) { (a, b) -> 27 | Some(a) shouldNotBeSome b 28 | } 29 | } 30 | 31 | "Option shouldBe some(value)" { 32 | shouldThrow { 33 | Option.fromNullable(null) shouldBeSome "foo" 34 | }.message shouldBe "Expected Some, but found None" 35 | 36 | shouldThrow { 37 | Option.fromNullable("boo") shouldBeSome "foo" 38 | }.message shouldBe "expected:<\"foo\"> but was:<\"boo\">" 39 | 40 | val some = 41 | Option.fromNullable("foo") shouldBeSome "foo" 42 | 43 | shouldThrow { 44 | some shouldNotBe "foo" 45 | } 46 | } 47 | 48 | "Option shouldBe none()" { 49 | shouldThrow { 50 | Option.fromNullable("foo").shouldBeNone() 51 | }.message shouldBe "Expected None, but found Some with value foo" 52 | 53 | Option.fromNullable(null).shouldBeNone() 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /kotest-assertions-arrow/src/commonTest/kotlin/io/kotest/assertions/arrow/core/RaiseMatchers.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.assertions.arrow.core 2 | 3 | import arrow.core.raise.Raise 4 | import io.kotest.assertions.arrow.shouldBe 5 | import io.kotest.assertions.throwables.shouldThrowWithMessage 6 | import io.kotest.core.spec.style.StringSpec 7 | 8 | class RaiseMatchers : StringSpec({ 9 | "shouldRaise: specific type" { 10 | val raised = shouldRaise { 11 | raise("failed") 12 | } 13 | raised shouldBe "failed" 14 | } 15 | 16 | "shouldRaise: subtype" { 17 | val raised = shouldRaise { 18 | raise("failed") 19 | } 20 | raised shouldBe "failed" 21 | } 22 | 23 | "shouldRaise: nullable type" { 24 | val raised = shouldRaise { 25 | raise(null) 26 | } 27 | raised shouldBe null 28 | } 29 | 30 | "shouldRaise: fail if null is raised when not expected" { 31 | shouldThrowWithMessage("Expected to raise String but was raised instead.") { 32 | shouldRaise { 33 | raise(null) 34 | } 35 | } 36 | } 37 | 38 | "shouldRaise: fail if expected raise type differs from actual" { 39 | shouldThrowWithMessage("Expected to raise Int but String was raised instead.") { 40 | shouldRaise { 41 | raise("failed") 42 | } 43 | } 44 | } 45 | 46 | "shouldRaise: fail if nothing is raised" { 47 | shouldThrowWithMessage("Expected to raise Int but nothing was raised.") { 48 | shouldRaise { 49 | 42 50 | } 51 | } 52 | } 53 | 54 | "shouldNotRaise" { 55 | val res = shouldNotRaise { 56 | 42 57 | } 58 | res shouldBe 42 59 | } 60 | 61 | "shouldNotRaise: fail if something is raised" { 62 | shouldThrowWithMessage("No raise expected, but \"failed\" was raised.") { 63 | shouldNotRaise { 64 | raise("failed") 65 | } 66 | } 67 | } 68 | 69 | "shouldNotRaise: fail if null was raised" { 70 | shouldThrowWithMessage("No raise expected, but was raised.") { 71 | shouldNotRaise { 72 | raise(null) 73 | } 74 | } 75 | } 76 | 77 | "shouldNotRaise: allows suspend call in block" { 78 | val res = shouldNotRaise { 79 | suspend { 42 }() 80 | } 81 | res shouldBe 42 82 | } 83 | 84 | "shouldRaise: allows suspend call in block" { 85 | val res = shouldRaise { 86 | raise(suspend { 42 }()) 87 | } 88 | res shouldBe 42 89 | } 90 | 91 | "shouldNotRaise: callable from non-suspend" { 92 | fun test() = shouldNotRaise { "success" } 93 | test() shouldBe "success" 94 | } 95 | 96 | "shouldRaise: callable from non-suspend" { 97 | fun test() = shouldRaise { raise("failed") } 98 | test() shouldBe "failed" 99 | } 100 | }) 101 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotest-conventions") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain { 8 | dependencies { 9 | compileOnly(libs.arrow.optics) 10 | implementation(libs.arrow.functions) 11 | implementation(libs.kotest.assertions.core) 12 | implementation(libs.kotest.framework.api) 13 | implementation(libs.kotest.property) 14 | implementation(libs.kotlinx.coroutines.core) 15 | api(projects.kotestPropertyArrow) 16 | } 17 | } 18 | 19 | commonTest { 20 | dependencies { 21 | implementation(projects.kotestAssertionsArrow) 22 | implementation(libs.arrow.optics) 23 | implementation(libs.kotest.framework.api) 24 | implementation(libs.kotest.framework.engine) 25 | implementation(libs.kotest.property) 26 | implementation(libs.kotlinx.coroutines.core) 27 | } 28 | } 29 | 30 | jsMain { 31 | dependencies { 32 | api(libs.arrow.optics) 33 | } 34 | } 35 | 36 | nativeMain { 37 | dependencies { 38 | implementation(libs.arrow.optics) 39 | } 40 | } 41 | } 42 | } 43 | 44 | apply(from = "../publish-mpp.gradle.kts") 45 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonMain/kotlin/io/kotest/property/arrow/optics/IsoLaws.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.kotest.property.arrow.optics 4 | 5 | import arrow.core.compose 6 | import arrow.core.identity 7 | import arrow.optics.Iso 8 | import io.kotest.property.Arb 9 | import io.kotest.property.PropertyContext 10 | import io.kotest.property.arrow.laws.Law 11 | import io.kotest.property.arrow.laws.equalUnderTheLaw 12 | import io.kotest.property.checkAll 13 | 14 | public object IsoLaws { 15 | 16 | public fun laws( 17 | iso: Iso, 18 | aGen: Arb, 19 | bGen: Arb, 20 | funcGen: Arb<(B) -> B>, 21 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 22 | eqb: (B, B) -> Boolean = { a, b -> a == b } 23 | ): List = 24 | listOf( 25 | Law("Iso Law: round trip one way") { iso.roundTripOneWay(aGen, eqa) }, 26 | Law("Iso Law: round trip other way") { iso.roundTripOtherWay(bGen, eqb) }, 27 | Law("Iso Law: modify identity is identity") { iso.modifyIdentity(aGen, eqa) }, 28 | Law("Iso Law: compose modify") { iso.composeModify(aGen, funcGen, eqa) }, 29 | Law("Iso Law: consitent set with modify") { iso.consistentSetModify(aGen, bGen, eqa) } 30 | ) 31 | 32 | public suspend fun Iso.roundTripOneWay(aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 33 | checkAll(aGen) { a -> 34 | reverseGet(get(a)).equalUnderTheLaw(a, eq) 35 | } 36 | 37 | public suspend fun Iso.roundTripOtherWay(bGen: Arb, eq: (B, B) -> Boolean): PropertyContext = 38 | checkAll(bGen) { b -> 39 | get(reverseGet(b)).equalUnderTheLaw(b, eq) 40 | } 41 | 42 | public suspend fun Iso.modifyIdentity(aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 43 | checkAll(aGen) { a -> 44 | modify(a, ::identity).equalUnderTheLaw(a, eq) 45 | } 46 | 47 | public suspend fun Iso.composeModify(aGen: Arb, funcGen: Arb<(B) -> B>, eq: (A, A) -> Boolean): PropertyContext = 48 | checkAll(aGen, funcGen, funcGen) { a, f, g -> 49 | modify(modify(a, f), g).equalUnderTheLaw(modify(a, g compose f), eq) 50 | } 51 | 52 | public suspend fun Iso.consistentSetModify(aGen: Arb, bGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 53 | checkAll(aGen, bGen) { a, b -> 54 | set(b).equalUnderTheLaw(modify(a) { b }, eq) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonMain/kotlin/io/kotest/property/arrow/optics/LensLaws.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.kotest.property.arrow.optics 4 | 5 | import arrow.core.compose 6 | import arrow.core.identity 7 | import arrow.optics.Lens 8 | import io.kotest.property.Arb 9 | import io.kotest.property.PropertyContext 10 | import io.kotest.property.arbitrary.constant 11 | import io.kotest.property.arrow.laws.Law 12 | import io.kotest.property.arrow.laws.equalUnderTheLaw 13 | import io.kotest.property.checkAll 14 | 15 | public object LensLaws { 16 | 17 | public fun laws( 18 | lensGen: Arb>, 19 | aGen: Arb, 20 | bGen: Arb, 21 | funcGen: Arb<(B) -> B>, 22 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 23 | eqb: (B, B) -> Boolean = { a, b -> a == b } 24 | ): List = 25 | listOf( 26 | Law("Lens law: get set") { lensGetSet(lensGen, aGen, eqa) }, 27 | Law("Lens law: set get") { lensSetGet(lensGen, aGen, bGen, eqb) }, 28 | Law("Lens law: is set idempotent") { lensSetIdempotent(lensGen, aGen, bGen, eqa) }, 29 | Law("Lens law: modify identity") { lensModifyIdentity(lensGen, aGen, eqa) }, 30 | Law("Lens law: compose modify") { lensComposeModify(lensGen, aGen, funcGen, eqa) }, 31 | Law("Lens law: consistent set modify") { lensConsistentSetModify(lensGen, aGen, bGen, eqa) } 32 | ) 33 | 34 | /** 35 | * Warning: Use only when a `Gen.constant()` applies 36 | */ 37 | public fun laws( 38 | lens: Lens, 39 | aGen: Arb, 40 | bGen: Arb, 41 | funcGen: Arb<(B) -> B>, 42 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 43 | eqb: (B, B) -> Boolean = { a, b -> a == b } 44 | ): List = laws(Arb.constant(lens), aGen, bGen, funcGen, eqa, eqb) 45 | 46 | public suspend fun lensGetSet(lensGen: Arb>, aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 47 | checkAll(lensGen, aGen) { lens, a -> 48 | lens.run { 49 | set(a, get(a)).equalUnderTheLaw(a, eq) 50 | } 51 | } 52 | 53 | public suspend fun lensSetGet( 54 | lensGen: Arb>, 55 | aGen: Arb, 56 | bGen: Arb, 57 | eq: (B, B) -> Boolean 58 | ): PropertyContext = 59 | checkAll(lensGen, aGen, bGen) { lens, a, b -> 60 | lens.run { 61 | get(set(a, b)).equalUnderTheLaw(b, eq) 62 | } 63 | } 64 | 65 | public suspend fun lensSetIdempotent( 66 | lensGen: Arb>, 67 | aGen: Arb, 68 | bGen: Arb, 69 | eq: (A, A) -> Boolean 70 | ): PropertyContext = 71 | checkAll(lensGen, aGen, bGen) { lens, a, b -> 72 | lens.run { 73 | set(set(a, b), b).equalUnderTheLaw(set(a, b), eq) 74 | } 75 | } 76 | 77 | public suspend fun lensModifyIdentity( 78 | lensGen: Arb>, 79 | aGen: Arb, 80 | eq: (A, A) -> Boolean 81 | ): PropertyContext = 82 | checkAll(lensGen, aGen) { lens, a -> 83 | lens.run { 84 | modify(a, ::identity).equalUnderTheLaw(a, eq) 85 | } 86 | } 87 | 88 | public suspend fun lensComposeModify( 89 | lensGen: Arb>, 90 | aGen: Arb, 91 | funcGen: Arb<(B) -> B>, 92 | eq: (A, A) -> Boolean 93 | ): PropertyContext = 94 | checkAll(lensGen, aGen, funcGen, funcGen) { lens, a, f, g -> 95 | lens.run { 96 | modify(modify(a, f), g).equalUnderTheLaw(modify(a, g compose f), eq) 97 | } 98 | } 99 | 100 | public suspend fun lensConsistentSetModify( 101 | lensGen: Arb>, 102 | aGen: Arb, 103 | bGen: Arb, 104 | eq: (A, A) -> Boolean 105 | ): PropertyContext = 106 | checkAll(lensGen, aGen, bGen) { lens, a, b -> 107 | lens.run { 108 | set(a, b).equalUnderTheLaw(modify(a) { b }, eq) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonMain/kotlin/io/kotest/property/arrow/optics/OptionalLaws.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.kotest.property.arrow.optics 4 | 5 | import arrow.core.compose 6 | import arrow.core.identity 7 | import arrow.optics.Optional 8 | import io.kotest.property.Arb 9 | import io.kotest.property.PropertyContext 10 | import io.kotest.property.arbitrary.constant 11 | import io.kotest.property.arrow.laws.Law 12 | import io.kotest.property.arrow.laws.equalUnderTheLaw 13 | import io.kotest.property.checkAll 14 | 15 | public object OptionalLaws { 16 | 17 | public fun laws( 18 | optionalGen: Arb>, 19 | aGen: Arb, 20 | bGen: Arb, 21 | funcGen: Arb<(B) -> B>, 22 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 23 | eqb: (B?, B?) -> Boolean = { a, b -> a == b } 24 | ): List = listOf( 25 | Law("Optional Law: set what you get") { getOptionSet(optionalGen, aGen, eqa) }, 26 | Law("Optional Law: set what you get") { setGetOption(optionalGen, aGen, bGen, eqb) }, 27 | Law("Optional Law: set is idempotent") { setIdempotent(optionalGen, aGen, bGen, eqa) }, 28 | Law("Optional Law: modify identity = identity") { modifyIdentity(optionalGen, aGen, eqa) }, 29 | Law("Optional Law: compose modify") { composeModify(optionalGen, aGen, funcGen, eqa) }, 30 | Law("Optional Law: consistent set with modify") { consistentSetModify(optionalGen, aGen, bGen, eqa) } 31 | ) 32 | 33 | /** 34 | * Warning: Use only when a `Gen.constant()` applies 35 | */ 36 | public fun laws( 37 | optional: Optional, 38 | aGen: Arb, 39 | bGen: Arb, 40 | funcGen: Arb<(B) -> B>, 41 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 42 | eqb: (B?, B?) -> Boolean = { a, b -> a == b } 43 | ): List = laws(Arb.constant(optional), aGen, bGen, funcGen, eqa, eqb) 44 | 45 | public suspend fun getOptionSet( 46 | optionalGen: Arb>, 47 | aGen: Arb, 48 | eq: (A, A) -> Boolean 49 | ): PropertyContext = 50 | checkAll(optionalGen, aGen) { optional, a -> 51 | optional.run { 52 | getOrModify(a).fold(::identity) { set(a, it) } 53 | .equalUnderTheLaw(a, eq) 54 | } 55 | } 56 | 57 | public suspend fun setGetOption( 58 | optionalGen: Arb>, 59 | aGen: Arb, 60 | bGen: Arb, 61 | eq: (B?, B?) -> Boolean 62 | ): PropertyContext = 63 | checkAll(optionalGen, aGen, bGen) { optional, a, b -> 64 | optional.run { 65 | getOrNull(set(a, b)).equalUnderTheLaw(getOrNull(a)?.let { b }) { a, b -> eq(a, b) } 66 | } 67 | } 68 | 69 | public suspend fun setIdempotent( 70 | optionalGen: Arb>, 71 | aGen: Arb, 72 | bGen: Arb, 73 | eq: (A, A) -> Boolean 74 | ): PropertyContext = 75 | checkAll(optionalGen, aGen, bGen) { optional, a, b -> 76 | optional.run { 77 | set(set(a, b), b).equalUnderTheLaw(set(a, b), eq) 78 | } 79 | } 80 | 81 | public suspend fun modifyIdentity( 82 | optionalGen: Arb>, 83 | aGen: Arb, 84 | eq: (A, A) -> Boolean 85 | ): PropertyContext = 86 | checkAll(optionalGen, aGen) { optional, a -> 87 | optional.run { 88 | modify(a, ::identity).equalUnderTheLaw(a, eq) 89 | } 90 | } 91 | 92 | public suspend fun composeModify( 93 | optionalGen: Arb>, 94 | aGen: Arb, 95 | funcGen: Arb<(B) -> B>, 96 | eq: (A, A) -> Boolean 97 | ): PropertyContext = 98 | checkAll(optionalGen, aGen, funcGen, funcGen) { optional, a, f, g -> 99 | optional.run { 100 | modify(modify(a, f), g).equalUnderTheLaw(modify(a, g compose f), eq) 101 | } 102 | } 103 | 104 | public suspend fun consistentSetModify( 105 | optionalGen: Arb>, 106 | aGen: Arb, 107 | bGen: Arb, 108 | eq: (A, A) -> Boolean 109 | ): PropertyContext = 110 | checkAll(optionalGen, aGen, bGen) { optional, a, b -> 111 | optional.run { 112 | set(a, b).equalUnderTheLaw(modify(a) { b }, eq) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonMain/kotlin/io/kotest/property/arrow/optics/PrismLaws.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.kotest.property.arrow.optics 4 | 5 | import arrow.core.compose 6 | import arrow.core.identity 7 | import arrow.optics.Prism 8 | import io.kotest.property.Arb 9 | import io.kotest.property.PropertyContext 10 | import io.kotest.property.arrow.laws.Law 11 | import io.kotest.property.arrow.laws.equalUnderTheLaw 12 | import io.kotest.property.checkAll 13 | 14 | public object PrismLaws { 15 | 16 | public fun laws( 17 | prism: Prism, 18 | aGen: Arb, 19 | bGen: Arb, 20 | funcGen: Arb<(B) -> B>, 21 | eqa: (A, A) -> Boolean = { a, b -> a == b }, 22 | eqb: (B?, B?) -> Boolean = { a, b -> a == b } 23 | ): List = listOf( 24 | Law("Prism law: partial round trip one way") { prism.partialRoundTripOneWay(aGen, eqa) }, 25 | Law("Prism law: round trip other way") { prism.roundTripOtherWay(bGen, eqb) }, 26 | Law("Prism law: modify identity") { prism.modifyIdentity(aGen, eqa) }, 27 | Law("Prism law: compose modify") { prism.composeModify(aGen, funcGen, eqa) }, 28 | Law("Prism law: consistent set modify") { prism.consistentSetModify(aGen, bGen, eqa) } 29 | ) 30 | 31 | public suspend fun Prism.partialRoundTripOneWay(aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 32 | checkAll(aGen) { a -> 33 | getOrModify(a).fold(::identity, ::reverseGet) 34 | .equalUnderTheLaw(a, eq) 35 | } 36 | 37 | public suspend fun Prism.roundTripOtherWay(bGen: Arb, eq: (B?, B?) -> Boolean): PropertyContext = 38 | checkAll(bGen) { b -> 39 | getOrNull(reverseGet(b)) 40 | .equalUnderTheLaw(b, eq) 41 | } 42 | 43 | public suspend fun Prism.modifyIdentity(aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 44 | checkAll(aGen) { a -> 45 | modify(a, ::identity).equalUnderTheLaw(a, eq) 46 | } 47 | 48 | public suspend fun Prism.composeModify(aGen: Arb, funcGen: Arb<(B) -> B>, eq: (A, A) -> Boolean): PropertyContext = 49 | checkAll(aGen, funcGen, funcGen) { a, f, g -> 50 | modify(modify(a, f), g).equalUnderTheLaw(modify(a, g compose f), eq) 51 | } 52 | 53 | public suspend fun Prism.consistentSetModify(aGen: Arb, bGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 54 | checkAll(aGen, bGen) { a, b -> 55 | set(a, b).equalUnderTheLaw(modify(a) { b }, eq) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonMain/kotlin/io/kotest/property/arrow/optics/TraversalLaws.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MemberVisibilityCanBePrivate", "unused") 2 | 3 | package io.kotest.property.arrow.optics 4 | 5 | import arrow.core.compose 6 | import arrow.core.identity 7 | import arrow.optics.Traversal 8 | import io.kotest.property.Arb 9 | import io.kotest.property.PropertyContext 10 | import io.kotest.property.arrow.laws.Law 11 | import io.kotest.property.arrow.laws.equalUnderTheLaw 12 | import io.kotest.property.checkAll 13 | import kotlin.math.max 14 | 15 | public object TraversalLaws { 16 | 17 | public fun laws( 18 | traversal: Traversal, 19 | aGen: Arb, 20 | bGen: Arb, 21 | funcGen: Arb<(B) -> B>, 22 | eq: (A, A) -> Boolean = { a, b -> a == b } 23 | ): List = listOf( 24 | Law("Traversal law: set is idempotent") { traversal.setIdempotent(aGen, bGen, eq) }, 25 | Law("Traversal law: modify identity") { traversal.modifyIdentity(aGen, eq) }, 26 | Law("Traversal law: compose modify") { traversal.composeModify(aGen, funcGen, eq) } 27 | ) 28 | 29 | public suspend fun Traversal.setIdempotent(aGen: Arb, bGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 30 | checkAll(max(aGen.minIterations(), bGen.minIterations()), aGen, bGen) { a, b -> 31 | set(set(a, b), b) 32 | .equalUnderTheLaw(set(a, b), eq) 33 | } 34 | 35 | public suspend fun Traversal.modifyIdentity(aGen: Arb, eq: (A, A) -> Boolean): PropertyContext = 36 | checkAll(aGen.minIterations(), aGen) { a -> 37 | modify(a, ::identity).equalUnderTheLaw(a, eq) 38 | } 39 | 40 | public suspend fun Traversal.composeModify( 41 | aGen: Arb, 42 | funcGen: Arb<(B) -> B>, 43 | eq: (A, A) -> Boolean 44 | ): PropertyContext = 45 | checkAll( 46 | max(max(aGen.minIterations(), funcGen.minIterations()), funcGen.minIterations()), 47 | aGen, 48 | funcGen, 49 | funcGen 50 | ) { a, f, g -> 51 | modify(modify(a, f), g) 52 | .equalUnderTheLaw(modify(a, g compose f), eq) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /kotest-property-arrow-optics/src/commonTest/kotlin/io/kotest/property/arrow/optics/TraversalTests.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.optics 2 | 3 | import arrow.optics.Optional 4 | import arrow.optics.Traversal 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.property.Arb 7 | import io.kotest.property.arbitrary.boolean 8 | import io.kotest.property.arbitrary.int 9 | import io.kotest.property.arbitrary.list 10 | import io.kotest.property.arbitrary.pair 11 | import io.kotest.property.arbitrary.string 12 | import io.kotest.property.arrow.core.either 13 | import io.kotest.property.arrow.core.functionAToB 14 | import io.kotest.property.arrow.core.tuple9 15 | import io.kotest.property.arrow.laws.testLaws 16 | 17 | class TraversalTests : StringSpec({ 18 | testLaws( 19 | "Traversal Laws for Optional", 20 | TraversalLaws.laws( 21 | traversal = Optional.listHead(), 22 | aGen = Arb.list(Arb.int()), 23 | bGen = Arb.int(), 24 | funcGen = Arb.functionAToB(Arb.int()), 25 | ), 26 | TraversalLaws.laws( 27 | traversal = Traversal.either(), 28 | aGen = Arb.either(Arb.string(), Arb.int()), 29 | bGen = Arb.int(), 30 | funcGen = Arb.functionAToB(Arb.int()), 31 | ), 32 | TraversalLaws.laws( 33 | traversal = Traversal.pair(), 34 | aGen = Arb.pair(Arb.boolean(), Arb.boolean()), 35 | bGen = Arb.boolean(), 36 | funcGen = Arb.functionAToB(Arb.boolean()), 37 | ), 38 | TraversalLaws.laws( 39 | traversal = Traversal.tuple9(), 40 | aGen = Arb.tuple9( 41 | Arb.boolean(), 42 | Arb.boolean(), 43 | Arb.boolean(), 44 | Arb.boolean(), 45 | Arb.boolean(), 46 | Arb.boolean(), 47 | Arb.boolean(), 48 | Arb.boolean(), 49 | Arb.boolean() 50 | ), 51 | bGen = Arb.boolean(), 52 | funcGen = Arb.functionAToB(Arb.boolean()), 53 | ) 54 | ) 55 | }) 56 | -------------------------------------------------------------------------------- /kotest-property-arrow/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotest-conventions") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain { 8 | dependencies { 9 | compileOnly(libs.arrow.core) 10 | implementation(libs.kotest.assertions.core) 11 | implementation(libs.kotest.framework.api) 12 | implementation(libs.kotest.property) 13 | } 14 | } 15 | 16 | commonTest { 17 | dependencies { 18 | implementation(projects.kotestAssertionsArrow) 19 | implementation(libs.arrow.core) 20 | implementation(libs.kotest.framework.engine) 21 | implementation(libs.kotest.property) 22 | implementation(libs.kotlinx.coroutines.core) 23 | } 24 | } 25 | 26 | jsMain { 27 | dependencies { 28 | api(libs.arrow.core) 29 | } 30 | } 31 | 32 | nativeMain { 33 | dependencies { 34 | implementation(libs.arrow.core) 35 | } 36 | } 37 | } 38 | } 39 | 40 | apply(from = "../publish-mpp.gradle.kts") 41 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Either.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import arrow.core.Either 4 | import io.kotest.property.Arb 5 | import io.kotest.property.arbitrary.choice 6 | import io.kotest.property.arbitrary.map 7 | 8 | public fun Arb.Companion.either(left: Arb, right: Arb): Arb> = 9 | choice(left.map { Either.Left(it) }, right.map { Either.Right(it) }) 10 | 11 | public fun Arb.or(arbB: Arb): Arb> = 12 | Arb.either(this, arbB) 13 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Function.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import io.kotest.property.Arb 4 | import io.kotest.property.arbitrary.map 5 | 6 | public fun Arb.Companion.functionAToB(arb: Arb): Arb<(A) -> B> = 7 | arb.map { b: B -> { _: A -> b } } 8 | 9 | public fun Arb.Companion.functionAAToA(arb: Arb): Arb<(A, A) -> A> = 10 | arb.map { a: A -> { _: A, _: A -> a } } 11 | 12 | public fun Arb.Companion.functionBAToB(arb: Arb): Arb<(B, A) -> B> = 13 | arb.map { b: B -> { _: B, _: A -> b } } 14 | 15 | public fun Arb.Companion.functionABToB(arb: Arb): Arb<(A, B) -> B> = 16 | arb.map { b: B -> { _: A, _: B -> b } } 17 | 18 | public fun Arb.Companion.functionToA(arb: Arb): Arb<() -> A> = 19 | arb.map { a: A -> { a } } 20 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Ior.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import arrow.core.Ior 4 | import io.kotest.property.Arb 5 | import io.kotest.property.arbitrary.choice 6 | import io.kotest.property.arbitrary.map 7 | import io.kotest.property.arbitrary.bind 8 | 9 | 10 | public fun Arb.Companion.ior(left: Arb, right: Arb): Arb> = 11 | Arb.choice(left.map { Ior.Left(it) }, 12 | Arb.bind(left, right) { a, b -> Ior.Both(a, b) }, 13 | right.map { Ior.Right(it) }) 14 | 15 | public fun Arb.alignWith(arbB: Arb): Arb> = 16 | Arb.ior(this, arbB) 17 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Nel.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import arrow.core.NonEmptyList 4 | import arrow.core.toNonEmptyListOrNull 5 | import io.kotest.property.Arb 6 | import io.kotest.property.arbitrary.filter 7 | import io.kotest.property.arbitrary.list 8 | import io.kotest.property.arbitrary.map 9 | 10 | public fun Arb.Companion.nel(a: Arb): Arb> = nonEmptyList(a) 11 | 12 | public fun Arb.Companion.nonEmptyList(a: Arb): Arb> = 13 | list(a) 14 | .filter(List::isNotEmpty) 15 | .map { it.toNonEmptyListOrNull()!! } 16 | 17 | public fun Arb.Companion.nel(a: Arb, size: IntRange): Arb> = nonEmptyList(a, size) 18 | 19 | public fun Arb.Companion.nonEmptyList(a: Arb, size: IntRange): Arb> = 20 | list(a, size) 21 | .filter(List::isNotEmpty) 22 | .map { it.toNonEmptyListOrNull()!! } 23 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Option.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import arrow.core.Option 4 | import arrow.core.toOption 5 | import io.kotest.property.Arb 6 | import io.kotest.property.arbitrary.map 7 | import io.kotest.property.arbitrary.orNull 8 | 9 | public fun Arb.Companion.option(arb: Arb): Arb> = 10 | arb.orNull().map { it.toOption() } 11 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/SemiringLaws.kt: -------------------------------------------------------------------------------- 1 | //@file:Suppress("unused") 2 | // 3 | //package io.kotest.property.arrow.core 4 | // 5 | //import arrow.typeclasses.Semiring 6 | //import io.kotest.property.Arb 7 | //import io.kotest.property.checkAll 8 | //import io.kotest.matchers.shouldBe 9 | //import io.kotest.property.PropertyContext 10 | //import io.kotest.property.arrow.laws.Law 11 | //import io.kotest.property.arrow.laws.equalUnderTheLaw 12 | // 13 | //public object SemiringLaws { 14 | // 15 | // public fun laws(SG: Semiring, GEN: Arb, eq: (F, F) -> Boolean = { a, b -> a == b }): List = 16 | // listOf( 17 | // Law("Semiring: Additive commutativity") { SG.semiringAdditiveCommutativity(GEN, eq) }, 18 | // Law("Semiring: Additive left identity") { SG.semiringAdditiveLeftIdentity(GEN, eq) }, 19 | // Law("Semiring: Additive right identity") { SG.semiringAdditiveRightIdentity(GEN, eq) }, 20 | // Law("Semiring: Additive associativity") { SG.semiringAdditiveAssociativity(GEN, eq) }, 21 | // Law("Semiring: Multiplicative commutativity") { SG.semiringMultiplicativeCommutativity(GEN, eq) }, 22 | // Law("Semiring: Multiplicative left identity") { SG.semiringMultiplicativeLeftIdentity(GEN, eq) }, 23 | // Law("Semiring: Multiplicative right identity") { SG.semiringMultiplicativeRightIdentity(GEN, eq) }, 24 | // Law("Semiring: Multiplicative associativity") { SG.semiringMultiplicativeAssociativity(GEN, eq) }, 25 | // Law("Semiring: Right distributivity") { SG.semiringRightDistributivity(GEN, eq) }, 26 | // Law("Semiring: Left distributivity") { SG.semiringLeftDistributivity(GEN, eq) }, 27 | // Law("Semiring: Multiplicative left absorption") { SG.semiringMultiplicativeLeftAbsorption(GEN, eq) }, 28 | // Law("Semiring: Multiplicative right absorption") { SG.semiringMultiplicativeRightAbsorption(GEN, eq) }, 29 | // Law("Semiring: times is derived") { SG.timesIsDerived(GEN, eq) }, 30 | // Law("Semiring: plus is derived") { SG.plusIsDerived(GEN, eq) }, 31 | // Law("Semiring: maybeCombineAddition is derived") { SG.maybeCombineAdditionIsDerived(GEN, eq) }, 32 | // Law("Semiring: maybeCombineAddition left null") { SG.maybeCombineAdditionLeftNull(GEN, eq) }, 33 | // Law("Semiring: maybeCombineAddition right null") { SG.maybeCombineAdditionRightNull(GEN, eq) }, 34 | // Law("Semiring: maybeCombineAddition both null") { SG.maybeCombineAdditionBothNull(eq) }, 35 | // Law("Semiring: maybeCombineMultiplicate is derived") { SG.maybeCombineMultiplicateIsDerived(GEN, eq) }, 36 | // Law("Semiring: maybeCombineMultiplicate left null") { SG.maybeCombineMultiplicateLeftNull(GEN, eq) }, 37 | // Law("Semiring: maybeCombineMultiplicate right null") { SG.maybeCombineMultiplicateRightNull(GEN, eq) }, 38 | // Law("Semiring: maybeCombineMultiplicate both null") { SG.maybeCombineMultiplicateBothNull(eq) } 39 | // ) 40 | // 41 | // // a + b = b + a 42 | // private suspend fun Semiring.semiringAdditiveCommutativity(GEN: Arb, eq: (F, F) -> Boolean) = 43 | // checkAll(GEN, GEN) { a, b -> 44 | // a.combine(b).equalUnderTheLaw(b.combine(a), eq) 45 | // } 46 | // 47 | // // 0 + a = a 48 | // private suspend fun Semiring.semiringAdditiveLeftIdentity(GEN: Arb, eq: (F, F) -> Boolean) = 49 | // checkAll(GEN) { A -> 50 | // (zero().combine(A)).equalUnderTheLaw(A, eq) 51 | // } 52 | // 53 | // // a + 0 = a 54 | // private suspend fun Semiring.semiringAdditiveRightIdentity(GEN: Arb, eq: (F, F) -> Boolean) = 55 | // checkAll(GEN) { A -> 56 | // A.combine(zero()).equalUnderTheLaw(A, eq) 57 | // } 58 | // 59 | // // a + (b + c) = (a + b) + c 60 | // private suspend fun Semiring.semiringAdditiveAssociativity(GEN: Arb, eq: (F, F) -> Boolean) = 61 | // checkAll(GEN, GEN, GEN) { A, B, C -> 62 | // A.combine(B.combine(C)).equalUnderTheLaw((A.combine(B)).combine(C), eq) 63 | // } 64 | // 65 | // // a · b = b · a 66 | // private suspend fun Semiring.semiringMultiplicativeCommutativity(GEN: Arb, eq: (F, F) -> Boolean) = 67 | // checkAll(GEN, GEN) { a, b -> 68 | // a.combineMultiplicate(b).equalUnderTheLaw(b.combineMultiplicate(a), eq) 69 | // } 70 | // 71 | // // 1 · a = a 72 | // private suspend fun Semiring.semiringMultiplicativeLeftIdentity(GEN: Arb, eq: (F, F) -> Boolean) = 73 | // checkAll(GEN) { A -> 74 | // (one().combineMultiplicate(A)).equalUnderTheLaw(A, eq) 75 | // } 76 | // 77 | // // a · 1 = a 78 | // private suspend fun Semiring.semiringMultiplicativeRightIdentity(GEN: Arb, eq: (F, F) -> Boolean) = 79 | // checkAll(GEN) { A -> 80 | // A.combineMultiplicate(one()).equalUnderTheLaw(A, eq) 81 | // } 82 | // 83 | // // a · (b · c) = (a · b) · c 84 | // private suspend fun Semiring.semiringMultiplicativeAssociativity(GEN: Arb, eq: (F, F) -> Boolean) = 85 | // checkAll(GEN, GEN, GEN) { A, B, C -> 86 | // A.combineMultiplicate(B.combineMultiplicate(C)).equalUnderTheLaw((B.combineMultiplicate(A)).combineMultiplicate(C), eq) 87 | // } 88 | // 89 | // // (a + b) · c = a · c + b · c 90 | // private suspend fun Semiring.semiringRightDistributivity(GEN: Arb, eq: (F, F) -> Boolean) = 91 | // checkAll(GEN, GEN, GEN) { A, B, C -> 92 | // (A.combine(B)).combineMultiplicate(C).equalUnderTheLaw((A.combineMultiplicate(C)).combine(B.combineMultiplicate(C)), eq) 93 | // } 94 | // 95 | // // a · (b + c) = a · b + a · c 96 | // private suspend fun Semiring.semiringLeftDistributivity(GEN: Arb, eq: (F, F) -> Boolean) = 97 | // checkAll(GEN, GEN, GEN) { A, B, C -> 98 | // A.combineMultiplicate(B.combine(C)).equalUnderTheLaw((A.combineMultiplicate(B)).combine(A.combineMultiplicate(C)), eq) 99 | // } 100 | // 101 | // // 0 · a = 0 102 | // private suspend fun Semiring.semiringMultiplicativeLeftAbsorption(GEN: Arb, eq: (F, F) -> Boolean) = 103 | // checkAll(GEN) { A -> 104 | // (zero().combineMultiplicate(A)).equalUnderTheLaw(zero(), eq) 105 | // } 106 | // 107 | // // a · 0 = 0 108 | // private suspend fun Semiring.semiringMultiplicativeRightAbsorption(GEN: Arb, eq: (F, F) -> Boolean) = 109 | // checkAll(GEN) { A -> 110 | // A.combineMultiplicate(zero()).equalUnderTheLaw(zero(), eq) 111 | // } 112 | // 113 | // private suspend fun Semiring.timesIsDerived(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 114 | // checkAll(GEN, GEN) { A, B -> 115 | // A.times(B).equalUnderTheLaw(A.combineMultiplicate(B), eq) 116 | // } 117 | // 118 | // private suspend fun Semiring.plusIsDerived(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 119 | // checkAll(GEN, GEN) { A, B -> 120 | // A.plus(B).equalUnderTheLaw(A.combine(B), eq) 121 | // } 122 | // 123 | // private suspend fun Semiring.maybeCombineAdditionIsDerived(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 124 | // checkAll(GEN, GEN) { A, B -> 125 | // A.maybeCombineAddition(B).equalUnderTheLaw(A.combine(B), eq) 126 | // } 127 | // 128 | // private suspend fun Semiring.maybeCombineAdditionLeftNull(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 129 | // checkAll(GEN) { A -> 130 | // null.maybeCombineAddition(A).equalUnderTheLaw(zero(), eq) 131 | // } 132 | // 133 | // private suspend fun Semiring.maybeCombineAdditionRightNull(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 134 | // checkAll(GEN) { A -> 135 | // A.maybeCombineAddition(null).equalUnderTheLaw(A, eq) 136 | // } 137 | // 138 | // private fun Semiring.maybeCombineAdditionBothNull(eq: (F, F) -> Boolean): Unit { 139 | // null.maybeCombineAddition(null).equalUnderTheLaw(zero(), eq) shouldBe true 140 | // } 141 | // 142 | // private suspend fun Semiring.maybeCombineMultiplicateIsDerived(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 143 | // checkAll(GEN, GEN) { A, B -> 144 | // A.maybeCombineMultiplicate(B).equalUnderTheLaw(A.combineMultiplicate(B), eq) 145 | // } 146 | // 147 | // private suspend fun Semiring.maybeCombineMultiplicateLeftNull(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 148 | // checkAll(GEN) { A -> 149 | // null.maybeCombineMultiplicate(A).equalUnderTheLaw(one(), eq) 150 | // } 151 | // 152 | // private suspend fun Semiring.maybeCombineMultiplicateRightNull(GEN: Arb, eq: (F, F) -> Boolean): PropertyContext = 153 | // checkAll(GEN) { A -> 154 | // A.maybeCombineMultiplicate(null).equalUnderTheLaw(A, eq) 155 | // } 156 | // 157 | // private fun Semiring.maybeCombineMultiplicateBothNull(eq: (F, F) -> Boolean): Unit { 158 | // null.maybeCombineMultiplicate(null).equalUnderTheLaw(one(), eq) shouldBe true 159 | // } 160 | //} 161 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/core/Tuple.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import arrow.core.Tuple4 4 | import arrow.core.Tuple5 5 | import arrow.core.Tuple6 6 | import arrow.core.Tuple7 7 | import arrow.core.Tuple8 8 | import arrow.core.Tuple9 9 | import io.kotest.property.Arb 10 | import io.kotest.property.arbitrary.bind 11 | 12 | public fun Arb.Companion.tuple4( 13 | arbA: Arb, 14 | arbB: Arb, 15 | arbC: Arb, 16 | arbD: Arb 17 | ): Arb> = 18 | Arb.bind(arbA, arbB, arbC, arbD, ::Tuple4) 19 | 20 | public fun Arb.Companion.tuple5( 21 | arbA: Arb, 22 | arbB: Arb, 23 | arbC: Arb, 24 | arbD: Arb, 25 | arbE: Arb 26 | ): Arb> = 27 | Arb.bind(arbA, arbB, arbC, arbD, arbE, ::Tuple5) 28 | 29 | public fun Arb.Companion.tuple6( 30 | arbA: Arb, 31 | arbB: Arb, 32 | arbC: Arb, 33 | arbD: Arb, 34 | arbE: Arb, 35 | arbF: Arb 36 | ): Arb> = 37 | Arb.bind(arbA, arbB, arbC, arbD, arbE, arbF, ::Tuple6) 38 | 39 | public fun Arb.Companion.tuple7( 40 | arbA: Arb, 41 | arbB: Arb, 42 | arbC: Arb, 43 | arbD: Arb, 44 | arbE: Arb, 45 | arbF: Arb, 46 | arbG: Arb 47 | ): Arb> = 48 | Arb.bind(arbA, arbB, arbC, arbD, arbE, arbF, arbG, ::Tuple7) 49 | 50 | public fun Arb.Companion.tuple8( 51 | arbA: Arb, 52 | arbB: Arb, 53 | arbC: Arb, 54 | arbD: Arb, 55 | arbE: Arb, 56 | arbF: Arb, 57 | arbG: Arb, 58 | arbH: Arb 59 | ): Arb> = 60 | Arb.bind( 61 | Arb.tuple7(arbA, arbB, arbC, arbD, arbE, arbF, arbG), 62 | arbH 63 | ) { (a, b, c, d, e, f, g), h -> 64 | Tuple8(a, b, c, d, e, f, g, h) 65 | } 66 | 67 | public fun Arb.Companion.tuple9( 68 | arbA: Arb, 69 | arbB: Arb, 70 | arbC: Arb, 71 | arbD: Arb, 72 | arbE: Arb, 73 | arbF: Arb, 74 | arbG: Arb, 75 | arbH: Arb, 76 | arbI: Arb 77 | ): Arb> = 78 | Arb.bind( 79 | Arb.tuple8(arbA, arbB, arbC, arbD, arbE, arbF, arbG, arbH), 80 | arbI 81 | ) { (a, b, c, d, e, f, g, h), i -> 82 | Tuple9(a, b, c, d, e, f, g, h, i) 83 | } 84 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/laws/Law.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.laws 2 | 3 | import io.kotest.assertions.fail 4 | import io.kotest.core.names.TestName 5 | import io.kotest.core.spec.style.scopes.RootScope 6 | import io.kotest.core.spec.style.scopes.addTest 7 | import io.kotest.core.test.TestScope 8 | 9 | public data class Law(val name: String, val test: suspend TestScope.() -> Unit) 10 | 11 | public fun A.equalUnderTheLaw(other: A, f: (A, A) -> Boolean = { a, b -> a == b }): Boolean = 12 | if (f(this, other)) true else fail("Found $this but expected: $other") 13 | 14 | public fun RootScope.testLaws(vararg laws: List): Unit = 15 | laws 16 | .flatMap { list: List -> list.asIterable() } 17 | .distinctBy { law: Law -> law.name } 18 | .forEach { law -> 19 | addTest(TestName(law.name), disabled = false, config = null) { law.test(this) } 20 | } 21 | 22 | public fun RootScope.testLaws(prefix: String, vararg laws: List): Unit = 23 | laws 24 | .flatMap { list: List -> list.asIterable() } 25 | .distinctBy { law: Law -> law.name } 26 | .forEach { law: Law -> 27 | addTest(TestName(prefix, law.name, true), disabled = false, config = null) { law.test(this) } 28 | } 29 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonMain/kotlin/io/kotest/property/arrow/laws/Util.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.laws 2 | 3 | import io.kotest.matchers.shouldBe as coreShouldBe 4 | 5 | internal infix fun A.shouldBe(a: A): A { 6 | this coreShouldBe a 7 | return this 8 | } 9 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonTest/kotlin/io/kotest/property/arrow/core/EitherTests.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import io.kotest.assertions.arrow.core.shouldBeLeft 4 | import io.kotest.assertions.arrow.core.shouldBeRight 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.property.Arb 7 | import io.kotest.property.arbitrary.int 8 | import io.kotest.property.arbitrary.string 9 | import io.kotest.property.arrow.laws.shouldBe 10 | import io.kotest.property.checkAll 11 | 12 | class EitherTests : StringSpec({ 13 | "shouldBeRight shouldBeLeft" { 14 | checkAll(Arb.either(Arb.string(), Arb.int())) { 15 | if (it.isRight()) it.shouldBeRight() shouldBe it.value else it.shouldBeLeft() shouldBe it.value 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /kotest-property-arrow/src/commonTest/kotlin/io/kotest/property/arrow/core/IorTests.kt: -------------------------------------------------------------------------------- 1 | package io.kotest.property.arrow.core 2 | 3 | import io.kotest.matchers.booleans.shouldBeTrue 4 | import io.kotest.assertions.assertSoftly 5 | import io.kotest.core.spec.style.StringSpec 6 | import io.kotest.property.Arb 7 | import io.kotest.property.arbitrary.int 8 | import io.kotest.property.arbitrary.string 9 | import io.kotest.property.arbitrary.list 10 | import io.kotest.property.arbitrary.next 11 | import io.kotest.inspectors.forAtLeastOne 12 | 13 | class IorTests : StringSpec({ 14 | "Arb.ior should generate Left, Right & Both" { 15 | assertSoftly(Arb.list(Arb.ior(Arb.string(), Arb.int()), 100..120).next()) { 16 | forAtLeastOne { 17 | it.isRight().shouldBeTrue() 18 | } 19 | forAtLeastOne { 20 | it.isBoth().shouldBeTrue() 21 | } 22 | forAtLeastOne { 23 | it.isLeft().shouldBeTrue() 24 | } 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /publish-mpp.gradle.kts: -------------------------------------------------------------------------------- 1 | apply(from = "$rootDir/signing-pom-details.gradle.kts") 2 | 3 | 4 | val javadoc by tasks.creating(Jar::class) { 5 | group = "build" 6 | description = "Assembles Javadoc jar file from for publishing" 7 | archiveClassifier.set("javadoc") 8 | } 9 | 10 | val javadocJar by tasks.creating(Jar::class) { 11 | group = JavaBasePlugin.DOCUMENTATION_GROUP 12 | description = "Assembles java doc to jar" 13 | archiveClassifier.set("javadoc") 14 | from(javadoc) 15 | } 16 | 17 | fun Project.publishing(action: PublishingExtension.() -> Unit) = 18 | configure(action) 19 | 20 | val publications: PublicationContainer = (extensions.getByName("publishing") as PublishingExtension).publications 21 | 22 | publishing { 23 | publications.withType().forEach { 24 | it.apply { 25 | //if (Ci.isRelease) 26 | artifact(javadocJar) 27 | } 28 | } 29 | } 30 | 31 | //region Fix Gradle error Reason: Task uses this output of task without declaring an explicit or implicit dependency. 32 | // https://github.com/gradle/gradle/issues/26091 33 | tasks.withType().configureEach { 34 | val signingTasks = tasks.withType() 35 | mustRunAfter(signingTasks) 36 | } 37 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", "github>kotest/renovate-config" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | rootProject.name = "kotest-extensions-arrow" 3 | 4 | pluginManagement { 5 | repositories { 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | include("kotest-assertions-arrow") 12 | include("kotest-assertions-arrow-fx-coroutines") 13 | include("kotest-property-arrow") 14 | include("kotest-property-arrow-optics") 15 | -------------------------------------------------------------------------------- /signing-pom-details.gradle.kts: -------------------------------------------------------------------------------- 1 | apply(plugin = "maven-publish") 2 | apply(plugin = "signing") 3 | 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | 9 | val signingKey: String? by project 10 | val signingPassword: String? by project 11 | 12 | fun Project.publishing(action: PublishingExtension.() -> Unit) = 13 | configure(action) 14 | 15 | fun Project.signing(configure: SigningExtension.() -> Unit): Unit = 16 | configure(configure) 17 | 18 | val publications: PublicationContainer = (extensions.getByName("publishing") as PublishingExtension).publications 19 | 20 | signing { 21 | useGpgCmd() 22 | if (signingKey != null && signingPassword != null) { 23 | @Suppress("UnstableApiUsage") 24 | useInMemoryPgpKeys(signingKey, signingPassword) 25 | } 26 | if (Ci.isRelease) { 27 | sign(publications) 28 | } 29 | } 30 | 31 | publishing { 32 | repositories { 33 | maven { 34 | val releasesRepoUrl = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 35 | val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") 36 | name = "deploy" 37 | url = if (Ci.isRelease) releasesRepoUrl else snapshotsRepoUrl 38 | credentials { 39 | username = java.lang.System.getenv("OSSRH_USERNAME") ?: "" 40 | password = java.lang.System.getenv("OSSRH_PASSWORD") ?: "" 41 | } 42 | } 43 | } 44 | 45 | publications.withType().forEach { 46 | it.apply { 47 | //if (Ci.isRelease) 48 | pom { 49 | name.set("Kotest") 50 | description.set("Kotlin Test Framework") 51 | url.set("https://github.com/kotest/kotest-extensions-arrow") 52 | 53 | scm { 54 | connection.set("scm:git:https://github.com/kotest/kotest-extensions-arrow/") 55 | developerConnection.set("scm:git:https://github.com/sksamuel/") 56 | url.set("https://github.com/kotest/kotest-extensions-arrow/") 57 | } 58 | 59 | licenses { 60 | license { 61 | name.set("Apache-2.0") 62 | url.set("https://opensource.org/licenses/Apache-2.0") 63 | } 64 | } 65 | 66 | developers { 67 | developer { 68 | id.set("sksamuel") 69 | name.set("Stephen Samuel") 70 | email.set("sam@sksamuel.com") 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------