├── .github └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── .scalafmt.conf ├── COPYING ├── LICENSE ├── README.md ├── build.sbt ├── core └── shared │ └── src │ └── main │ └── scala │ └── org │ └── scalacheck │ └── effect │ └── PropF.scala ├── munit └── shared │ └── src │ ├── main │ └── scala │ │ └── munit │ │ └── ScalaCheckEffectSuite.scala │ └── test │ └── scala │ └── munit │ ├── Example.scala │ └── ScalaCheckEffectSuiteSuite.scala └── project ├── build.properties └── plugins.sbt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**', '!update/**', '!pr/**'] 13 | push: 14 | branches: ['**', '!update/**', '!pr/**'] 15 | tags: [v*] 16 | 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | 21 | concurrency: 22 | group: ${{ github.workflow }} @ ${{ github.ref }} 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | build: 27 | name: Test 28 | strategy: 29 | matrix: 30 | os: [ubuntu-22.04] 31 | scala: [3, 2.12, 2.13] 32 | java: [temurin@8] 33 | project: [rootJS, rootJVM, rootNative] 34 | runs-on: ${{ matrix.os }} 35 | timeout-minutes: 60 36 | steps: 37 | - name: Checkout current branch (full) 38 | uses: actions/checkout@v4 39 | with: 40 | fetch-depth: 0 41 | 42 | - name: Setup sbt 43 | uses: sbt/setup-sbt@v1 44 | 45 | - name: Setup Java (temurin@8) 46 | id: setup-java-temurin-8 47 | if: matrix.java == 'temurin@8' 48 | uses: actions/setup-java@v4 49 | with: 50 | distribution: temurin 51 | java-version: 8 52 | cache: sbt 53 | 54 | - name: sbt update 55 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 56 | run: sbt +update 57 | 58 | - name: Check that workflows are up to date 59 | run: sbt githubWorkflowCheck 60 | 61 | - name: Check headers and formatting 62 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 63 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck 64 | 65 | - name: scalaJSLink 66 | if: matrix.project == 'rootJS' 67 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult 68 | 69 | - name: nativeLink 70 | if: matrix.project == 'rootNative' 71 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink 72 | 73 | - name: Test 74 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test 75 | 76 | - name: Check binary compatibility 77 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 78 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues 79 | 80 | - name: Generate API documentation 81 | if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' 82 | run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc 83 | 84 | - name: Make target directories 85 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 86 | run: mkdir -p munit/jvm/target core/native/target core/js/target munit/native/target core/jvm/target munit/js/target project/target 87 | 88 | - name: Compress target directories 89 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 90 | run: tar cf targets.tar munit/jvm/target core/native/target core/js/target munit/native/target core/jvm/target munit/js/target project/target 91 | 92 | - name: Upload target directories 93 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 94 | uses: actions/upload-artifact@v4 95 | with: 96 | name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.project }} 97 | path: targets.tar 98 | 99 | publish: 100 | name: Publish Artifacts 101 | needs: [build] 102 | if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') 103 | strategy: 104 | matrix: 105 | os: [ubuntu-22.04] 106 | java: [temurin@8] 107 | runs-on: ${{ matrix.os }} 108 | steps: 109 | - name: Checkout current branch (full) 110 | uses: actions/checkout@v4 111 | with: 112 | fetch-depth: 0 113 | 114 | - name: Setup sbt 115 | uses: sbt/setup-sbt@v1 116 | 117 | - name: Setup Java (temurin@8) 118 | id: setup-java-temurin-8 119 | if: matrix.java == 'temurin@8' 120 | uses: actions/setup-java@v4 121 | with: 122 | distribution: temurin 123 | java-version: 8 124 | cache: sbt 125 | 126 | - name: sbt update 127 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 128 | run: sbt +update 129 | 130 | - name: Download target directories (3, rootJS) 131 | uses: actions/download-artifact@v4 132 | with: 133 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS 134 | 135 | - name: Inflate target directories (3, rootJS) 136 | run: | 137 | tar xf targets.tar 138 | rm targets.tar 139 | 140 | - name: Download target directories (3, rootJVM) 141 | uses: actions/download-artifact@v4 142 | with: 143 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM 144 | 145 | - name: Inflate target directories (3, rootJVM) 146 | run: | 147 | tar xf targets.tar 148 | rm targets.tar 149 | 150 | - name: Download target directories (3, rootNative) 151 | uses: actions/download-artifact@v4 152 | with: 153 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative 154 | 155 | - name: Inflate target directories (3, rootNative) 156 | run: | 157 | tar xf targets.tar 158 | rm targets.tar 159 | 160 | - name: Download target directories (2.12, rootJS) 161 | uses: actions/download-artifact@v4 162 | with: 163 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS 164 | 165 | - name: Inflate target directories (2.12, rootJS) 166 | run: | 167 | tar xf targets.tar 168 | rm targets.tar 169 | 170 | - name: Download target directories (2.12, rootJVM) 171 | uses: actions/download-artifact@v4 172 | with: 173 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM 174 | 175 | - name: Inflate target directories (2.12, rootJVM) 176 | run: | 177 | tar xf targets.tar 178 | rm targets.tar 179 | 180 | - name: Download target directories (2.12, rootNative) 181 | uses: actions/download-artifact@v4 182 | with: 183 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootNative 184 | 185 | - name: Inflate target directories (2.12, rootNative) 186 | run: | 187 | tar xf targets.tar 188 | rm targets.tar 189 | 190 | - name: Download target directories (2.13, rootJS) 191 | uses: actions/download-artifact@v4 192 | with: 193 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS 194 | 195 | - name: Inflate target directories (2.13, rootJS) 196 | run: | 197 | tar xf targets.tar 198 | rm targets.tar 199 | 200 | - name: Download target directories (2.13, rootJVM) 201 | uses: actions/download-artifact@v4 202 | with: 203 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM 204 | 205 | - name: Inflate target directories (2.13, rootJVM) 206 | run: | 207 | tar xf targets.tar 208 | rm targets.tar 209 | 210 | - name: Download target directories (2.13, rootNative) 211 | uses: actions/download-artifact@v4 212 | with: 213 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootNative 214 | 215 | - name: Inflate target directories (2.13, rootNative) 216 | run: | 217 | tar xf targets.tar 218 | rm targets.tar 219 | 220 | - name: Import signing key 221 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == '' 222 | env: 223 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 224 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 225 | run: echo $PGP_SECRET | base64 -d -i - | gpg --import 226 | 227 | - name: Import signing key and strip passphrase 228 | if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE != '' 229 | env: 230 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 231 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 232 | run: | 233 | echo "$PGP_SECRET" | base64 -d -i - > /tmp/signing-key.gpg 234 | echo "$PGP_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg 235 | (echo "$PGP_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1) 236 | 237 | - name: Publish 238 | env: 239 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 240 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 241 | SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }} 242 | run: sbt tlCiRelease 243 | 244 | dependency-submission: 245 | name: Submit Dependencies 246 | if: github.event.repository.fork == false && github.event_name != 'pull_request' 247 | strategy: 248 | matrix: 249 | os: [ubuntu-22.04] 250 | java: [temurin@8] 251 | runs-on: ${{ matrix.os }} 252 | steps: 253 | - name: Checkout current branch (full) 254 | uses: actions/checkout@v4 255 | with: 256 | fetch-depth: 0 257 | 258 | - name: Setup sbt 259 | uses: sbt/setup-sbt@v1 260 | 261 | - name: Setup Java (temurin@8) 262 | id: setup-java-temurin-8 263 | if: matrix.java == 'temurin@8' 264 | uses: actions/setup-java@v4 265 | with: 266 | distribution: temurin 267 | java-version: 8 268 | cache: sbt 269 | 270 | - name: sbt update 271 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 272 | run: sbt +update 273 | 274 | - name: Submit Dependencies 275 | uses: scalacenter/sbt-dependency-submission@v2 276 | with: 277 | modules-ignore: rootjs_3 rootjs_2.12 rootjs_2.13 rootjvm_3 rootjvm_2.12 rootjvm_2.13 rootnative_3 rootnative_2.12 rootnative_2.13 278 | configs-ignore: test scala-tool scala-doc-tool test-internal 279 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | run: | 21 | # Customize those three lines with your repository and credentials: 22 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 23 | 24 | # A shortcut to call GitHub API. 25 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 26 | 27 | # A temporary file which receives HTTP response headers. 28 | TMPFILE=/tmp/tmp.$$ 29 | 30 | # An associative array, key: artifact name, value: number of artifacts of that name. 31 | declare -A ARTCOUNT 32 | 33 | # Process all artifacts on this repository, loop on returned "pages". 34 | URL=$REPO/actions/artifacts 35 | while [[ -n "$URL" ]]; do 36 | 37 | # Get current page, get response headers in a temporary file. 38 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 39 | 40 | # Get URL of next page. Will be empty if we are at the last page. 41 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 42 | rm -f $TMPFILE 43 | 44 | # Number of artifacts on this page: 45 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 46 | 47 | # Loop on all artifacts on this page. 48 | for ((i=0; $i < $COUNT; i++)); do 49 | 50 | # Get name of artifact and count instances of this name. 51 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 52 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 53 | 54 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 55 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 56 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 57 | ghapi -X DELETE $REPO/actions/artifacts/$id 58 | done 59 | done 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | target 4 | .metals 5 | .bloop 6 | .bsp 7 | metals.sbt 8 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.9.4" 2 | 3 | runner.dialect = Scala213Source3 4 | 5 | style = default 6 | 7 | maxColumn = 100 8 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Scalacheck Effect Copyright (c) 2020 Typelevel. 2 | 3 | Code in ScalaCheck Effect is derived in part from ScalaCheck and munit. 4 | The Scalacheck license is included in full below, followed by the munit NOTICE. 5 | 6 | ------ 7 | 8 | Copyright (c) 2007-2019, Rickard Nilsson 9 | 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without modification, 13 | are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | * Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | * Neither the name of the copyright holder nor the names of its contributors 21 | may be used to endorse or promote products derived from this software 22 | without specific prior written permission. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | ---- 37 | 38 | 39 | # License notice for Scala.js 40 | 41 | MUnit contains parts which are derived from 42 | [the Scala.js project](https://github.com/scala-js/scala-js). We include the 43 | text of the original license below: 44 | 45 | ``` 46 | Scala.js 47 | Copyright (c) 2013-2018 EPFL 48 | 49 | Scala.js includes software developed at EPFL (https://lamp.epfl.ch/ and 50 | https://scala.epfl.ch/). 51 | 52 | Licensed under the Apache License, Version 2.0 (the "License"). 53 | Unless required by applicable law or agreed to in writing, software 54 | distributed under the License is distributed on an "AS IS" BASIS, 55 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 56 | See the License for the specific language governing permissions and 57 | limitations under the License. 58 | 59 | This project contains a translation of JUnit 4.12 to Scala.js in 60 | junit-runtime/src/main/scala/org/junit/. The original code can be found at 61 | https://github.com/junit-team/junit4/commit/64155f8a9babcfcf4263cf4d08253a1556e75481 62 | As a translation, it constitutes a derivative work and is therefore licensed 63 | under the Eclipse Public License v1.0, whose full text can be found in 64 | munit/js/src/main/scala/org/junit/LICENSE-junit.txt 65 | 66 | This project contains a translation of Hamcrest 1.3 to Scala.js in 67 | junit-runtime/src/main/scala/org/hamcrest/. The original code can be found at 68 | https://github.com/hamcrest/JavaHamcrest/tree/hamcrest-java-1.3 69 | As a translation, it constitutes a derivative work and is therefore licensed 70 | under the BSD License, whose full text can be found in 71 | munit/js/src/main/scala/org/hamcrest/LICENSE-hamcrest.txt 72 | ``` 73 | 74 | # License notice for java-diff-utils 75 | 76 | MUnit contains parts which are derived from 77 | [the java-diff-utils project](https://code.google.com/archive/p/java-diff-utils/source). 78 | The original license can be found here: 79 | https://www.apache.org/licenses/LICENSE-2.0.txt 80 | 81 | # License notice for utest 82 | 83 | MUnit contains parts which are derived from 84 | [the utest project](https://github.com/lihaoyi/utest). The original license can 85 | be found here: https://spdx.org/licenses/MIT.html 86 | 87 | # License notice for Scalameta 88 | 89 | MUnit contains parts which are derived from 90 | [the Scalameta project](https://github.com/scalameta/scalameta). The original 91 | license can be found here: 92 | https://github.com/scalameta/scalameta/blob/master/LICENSE.md 93 | 94 | # License notice for junit-interface 95 | 96 | MUnit contains parts which are derived from [the JUnit 4 interface for 97 | sbt](https://github.com/sbt/junit-interface). We include the text of the 98 | original license below: 99 | 100 | ``` 101 | Copyright (c) 2009-2012, Stefan Zeiger 102 | All rights reserved. 103 | 104 | Redistribution and use in source and binary forms, with or without 105 | modification, are permitted provided that the following conditions are met: 106 | 107 | * Redistributions of source code must retain the above copyright notice, 108 | this list of conditions and the following disclaimer. 109 | 110 | * Redistributions in binary form must reproduce the above copyright 111 | notice, this list of conditions and the following disclaimer in the 112 | documentation and/or other materials provided with the distribution. 113 | 114 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 115 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 116 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 117 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 118 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 119 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 120 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 121 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 122 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 123 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 124 | POSSIBILITY OF SUCH DAMAGE. 125 | ``` 126 | -------------------------------------------------------------------------------- /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 | # ScalaCheck Effect 2 | 3 | [![Continuous Integration](https://github.com/typelevel/scalacheck-effect/workflows/Continuous%20Integration/badge.svg)](https://github.com/typelevel/scalacheck-effect/actions?query=workflow%3A%22Continuous+Integration%22) 4 | [![Discord](https://img.shields.io/discord/632277896739946517.svg?label=&logo=discord&logoColor=ffffff&color=404244&labelColor=6A7EC2)](https://discord.gg/XF3CXcMzqD) 5 | [![Latest version](https://index.scala-lang.org/typelevel/scalacheck-effect/scalacheck-effect/latest.svg?color=orange)](https://index.scala-lang.org/typelevel/scalacheck-effect/scalacheck-effect) 6 | 7 | 8 | ScalaCheck Effect is a library that extends the functionality of [ScalaCheck](https://scalacheck.org) to support "effectful" properties. An effectful property is one that evaluates each sample in some type constructor `F[_]`. For example: 9 | 10 | ```scala 11 | import org.scalacheck.effect.PropF 12 | import org.scalacheck.Test 13 | import cats.effect.{ExitCode, IO, IOApp} 14 | 15 | object Example extends IOApp { 16 | def run(args: List[String]): IO[ExitCode] = { 17 | val p: PropF[IO] = 18 | PropF.forAllF { (x: Int) => 19 | IO(x).map(res => assert(res == x)) 20 | } 21 | 22 | val result: IO[Test.Result] = p.check() 23 | 24 | result.flatMap(r => IO(println(r))).as(ExitCode.Success) 25 | } 26 | } 27 | ``` 28 | 29 | Running this program results in the output: `Result(Passed,100,0,Map(),0)`. 30 | 31 | This library provides the `org.scalacheck.effect.PropF` type, which is the effectul analog to `org.scalacheck.Prop`. In this example, we use `PropF.forAllF` to write a property of the shape `Int => IO[Unit]`. This example uses `cats.effect.IO` as the type constructor, but any effect `F[_]` with an instance of `MonadError[F, Throwable]` can be used, including `scala.concurrent.Future`. 32 | 33 | The key idea here is using the `PropF.{forAllF, forAllNoShrinkF}` methods to create `PropF[F]` instances. The `check()` method on `PropF` converts a `PropF[F]` to a `F[Test.Result]`. 34 | 35 | ## sbt dependency 36 | 37 | ```scala 38 | libraryDependencies += "org.typelevel" %% "scalacheck-effect" % scalacheckEffectVersion 39 | ``` 40 | 41 | ## MUnit Integration 42 | 43 | This project also provides support for checking `PropF` values from within [MUnit](https://scalameta.org/munit/) based test suites. To use scalacheck-effect with munit, add the following dependency to your build: 44 | 45 | ```scala 46 | libraryDependencies += "org.typelevel" %% "scalacheck-effect-munit" % scalacheckEffectVersion % Test 47 | ``` 48 | The following usage example is for Cats Effect 2: 49 | 50 | ```scala 51 | import munit.{CatsEffectSuite, ScalaCheckEffectSuite} 52 | import org.scalacheck.effect.PropF 53 | 54 | // Example uses https://github.com/typelevel/munit-cats-effect 55 | 56 | class ExampleSuite extends CatsEffectSuite with ScalaCheckEffectSuite { 57 | test("first PropF test") { 58 | PropF.forAllF { (x: Int) => 59 | IO(x).start.flatMap(_.join).map(res => assert(res == x)) 60 | } 61 | } 62 | } 63 | ``` 64 | 65 | For Cats Effect 3, the `join` becomes `joinWithNever`, so the `PropF` in the above example becomes: 66 | 67 | ```scala 68 | PropF.forAllF { (x: Int) => 69 | IO(x).start.flatMap(_.joinWithNever).map(res => assert(res == x)) 70 | } 71 | ``` 72 | 73 | For more details on the differences between Cats Effect 2 and 3 see 74 | [the Cats Effect 3.x Migration Guide](https://typelevel.org/cats-effect/docs/migration-guide#exitcase-fiber). 75 | 76 | ## Design Goals 77 | 78 | - Support effectful properties without blocking. 79 | - Compatibility with `Gen`/`Cogen`/`Arbitrary`. 80 | - Parity with `Prop` features, including shrinking. 81 | - Follow same style as ScalaCheck and use ScalaCheck reporting. 82 | - Integrate well with popular test frameworks. 83 | - Non-goal: provide direct support for checking effectful properties directly from SBT or from standalone app. 84 | 85 | ## Frequently Asked Questions 86 | 87 | ### Why not just call Await.result / unsafeRunSync inside a property definition? 88 | 89 | Calling `Await.result`, `unsafeRunSync()` or a similar blocking operation is not possible on Scala.js. 90 | 91 | ### How to override the default ScalaCheck test parameters? 92 | 93 | By overriding the `scalaCheckTestParameters` from the super `ScalaCheckSuite`. 94 | 95 | ```scala 96 | override def scalaCheckTestParameters = 97 | super.scalaCheckTestParameters.withMinSuccessfulTests(20) 98 | ``` 99 | 100 | ## Acknowledgements 101 | 102 | This library builds heavily on the ideas in ScalaCheck. It grew out of the FS2 [`AsyncPropertySuite`](https://github.com/functional-streams-for-scala/fs2/blob/48f7188ef2df959189f931a7bbb68df4cb81c82a/core/shared/src/test/scala/fs2/AsyncPropertySuite.scala), which only implemented a handful of features. The [Weaver Test](https://disneystreaming.github.io/weaver-test/) framework also has similar support for effectful properties. Finally, the [Scala Hedgehog](https://github.com/hedgehogqa/scala-hedgehog/) library has a [prototype of similar functionality](https://github.com/hedgehogqa/scala-hedgehog/pull/147). 103 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / tlBaseVersion := "2.0" 2 | 3 | ThisBuild / developers += tlGitHubDev("mpilquist", "Michael Pilquist") 4 | ThisBuild / startYear := Some(2021) 5 | 6 | ThisBuild / crossScalaVersions := List("3.3.6", "2.12.20", "2.13.16") 7 | ThisBuild / tlVersionIntroduced := Map("3" -> "1.0.2") 8 | 9 | lazy val root = tlCrossRootProject.aggregate(core, munit) 10 | 11 | lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform) 12 | .settings( 13 | name := "scalacheck-effect", 14 | tlFatalWarnings := false 15 | ) 16 | .settings( 17 | libraryDependencies ++= List( 18 | "org.scalacheck" %%% "scalacheck" % "1.17.1", 19 | "org.typelevel" %%% "cats-core" % "2.11.0" 20 | ) 21 | ) 22 | 23 | lazy val munit = crossProject(JSPlatform, JVMPlatform, NativePlatform) 24 | .settings( 25 | name := "scalacheck-effect-munit", 26 | testFrameworks += new TestFramework("munit.Framework") 27 | ) 28 | .dependsOn(core) 29 | .settings( 30 | libraryDependencies ++= List( 31 | "org.scalameta" %%% "munit-scalacheck" % "1.0.0-M11", 32 | "org.typelevel" %%% "cats-effect" % "3.6.1" % Test 33 | ) 34 | ) 35 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/org/scalacheck/effect/PropF.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Typelevel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.scalacheck.effect 18 | 19 | import scala.collection.immutable.Stream 20 | import scala.collection.immutable.Stream.#:: 21 | 22 | import cats.MonadError 23 | import cats.implicits._ 24 | import org.scalacheck.{Arbitrary, Gen, Prop, Shrink, Test} 25 | import org.scalacheck.util.{FreqMap, Pretty} 26 | 27 | /** An effectful property. 28 | * 29 | * Effectful properties are ones in which each sample evaluates in a type constructor. That is, 30 | * instead of directly computing a result from a single sample of generated values, the result is 31 | * computed in some effect `F[_]` -- e.g., `cats.effect.IO` or `scala.concurrent.Future`. A 32 | * property which computes in an effect `F[_]` has the type `PropF[F]`. 33 | * 34 | * `PropF[F]` instances can be constructed for any effect which has a `MonadError[F, Throwable]` 35 | * instance. 36 | * 37 | * The most common way to construct `PropF[F]` values is by using one of the `forAllF` methods on 38 | * the `PropF` companion. These are analogous to `Prop.forAll` from ScalaCheck. When computing the 39 | * result of a single sample, `F[Unit]` values are treated as successes and any exceptions thrown 40 | * are treated as falsifications. 41 | */ 42 | sealed trait PropF[F[_]] { 43 | implicit val F: MonadError[F, Throwable] 44 | 45 | def map(f: PropF.Result[F] => PropF.Result[F]): PropF[F] = 46 | this match { 47 | case r: PropF.Result[F] => f(r) 48 | case PropF.Parameterized(g) => PropF.Parameterized(p => g(p).map(f)) 49 | case PropF.Suspend(effect) => PropF.Suspend(effect.map(_.map(f))) 50 | } 51 | 52 | def flatMap(f: PropF.Result[F] => PropF[F]): PropF[F] = 53 | this match { 54 | case r: PropF.Result[F] => f(r) 55 | case PropF.Parameterized(g) => PropF.Parameterized(p => g(p).flatMap(f)) 56 | case PropF.Suspend(effect) => PropF.Suspend(effect.map(_.flatMap(f))) 57 | } 58 | 59 | /** Checks a single sample. */ 60 | def checkOne(params: Gen.Parameters = Gen.Parameters.default): F[PropF.Result[F]] = { 61 | this match { 62 | case r: PropF.Result[F] => 63 | F.pure(r) 64 | case PropF.Suspend(fa) => 65 | fa.flatMap { next => 66 | next.checkOne(Prop.slideSeed(params)) 67 | } 68 | case PropF.Parameterized(next) => 69 | next(Prop.slideSeed(params)).checkOne(Prop.slideSeed(params)) 70 | } 71 | } 72 | 73 | /** Checks this property. */ 74 | def check( 75 | testParams: Test.Parameters = Test.Parameters.default, 76 | genParams: Gen.Parameters = Gen.Parameters.default 77 | ): F[Test.Result] = { 78 | 79 | import testParams.{minSuccessfulTests, minSize, maxDiscardRatio, maxSize} 80 | val sizeStep = (maxSize - minSize) / minSuccessfulTests.toDouble 81 | val maxDiscarded = minSuccessfulTests * maxDiscardRatio 82 | 83 | def loop(params: Gen.Parameters, passed: Int, discarded: Int): F[Test.Result] = { 84 | if (passed >= minSuccessfulTests) 85 | F.pure(Test.Result(Test.Passed, passed, discarded, FreqMap.empty)) 86 | else if (discarded >= maxDiscarded) 87 | F.pure(Test.Result(Test.Exhausted, passed, discarded, FreqMap.empty)) 88 | else { 89 | val count = passed + discarded 90 | val size = minSize.toDouble + (sizeStep * count) 91 | checkOne(params.withSize(size.round.toInt)).flatMap { result => 92 | result.status match { 93 | case Prop.True => 94 | loop(Prop.slideSeed(params), passed + 1, discarded) 95 | case Prop.Proof => 96 | F.pure(Test.Result(Test.Proved(result.args), passed + 1, discarded, FreqMap.empty)) 97 | case Prop.Exception(e) => 98 | F.pure( 99 | Test.Result( 100 | Test.PropException(result.args, e, result.labels), 101 | passed, 102 | discarded, 103 | FreqMap.empty 104 | ) 105 | ) 106 | case Prop.False => 107 | F.pure( 108 | Test.Result( 109 | Test.Failed(result.args, result.labels), 110 | passed, 111 | discarded, 112 | FreqMap.empty 113 | ) 114 | ) 115 | case Prop.Undecided => 116 | loop(Prop.slideSeed(params), passed, discarded + 1) 117 | } 118 | } 119 | } 120 | } 121 | 122 | loop(genParams, 0, 0) 123 | } 124 | 125 | private[effect] def addArg(arg: Prop.Arg[Any]): PropF[F] = 126 | map(r => r.copy(args = arg :: r.args)) 127 | 128 | private[effect] def provedToTrue: PropF[F] = 129 | map { r => 130 | if (r.status == Prop.Proof) r.copy(status = Prop.True) 131 | else r 132 | } 133 | } 134 | 135 | object PropF { 136 | def apply[F[_]]( 137 | f: Gen.Parameters => PropF[F] 138 | )(implicit F: MonadError[F, Throwable]): PropF[F] = Parameterized(f) 139 | 140 | private[effect] case class Parameterized[F[_]](f: Gen.Parameters => PropF[F])(implicit 141 | val F: MonadError[F, Throwable] 142 | ) extends PropF[F] 143 | 144 | case class Result[F[_]]( 145 | status: Prop.Status, 146 | args: List[Prop.Arg[Any]], 147 | collected: Set[Any], 148 | labels: Set[String] 149 | )(implicit val F: MonadError[F, Throwable]) 150 | extends PropF[F] { 151 | def failure: Boolean = 152 | status match { 153 | case Prop.False => true 154 | case Prop.Exception(_) => true 155 | case _ => false 156 | } 157 | } 158 | 159 | def status[F[_]](status: Prop.Status)(implicit F: MonadError[F, Throwable]): PropF[F] = 160 | Result(status, Nil, Set.empty, Set.empty) 161 | 162 | def undecided[F[_]](implicit F: MonadError[F, Throwable]): PropF[F] = 163 | status(Prop.Undecided) 164 | 165 | def falsified[F[_]](implicit F: MonadError[F, Throwable]): PropF[F] = 166 | status(Prop.False) 167 | 168 | def proved[F[_]](implicit F: MonadError[F, Throwable]): PropF[F] = 169 | status(Prop.Proof) 170 | 171 | def passed[F[_]](implicit F: MonadError[F, Throwable]): PropF[F] = 172 | status(Prop.True) 173 | 174 | def exception[F[_]](t: Throwable)(implicit F: MonadError[F, Throwable]): PropF[F] = 175 | status(Prop.Exception(t)) 176 | 177 | def boolean[F[_]]( 178 | b: Boolean 179 | )(implicit F: MonadError[F, Throwable]): PropF[F] = if (b) passed else falsified 180 | 181 | private[effect] case class Suspend[F[_], A](effect: F[PropF[F]])(implicit 182 | val F: MonadError[F, Throwable] 183 | ) extends PropF[F] 184 | 185 | implicit def effectOfPropFToPropF[F[_]]( 186 | fa: F[PropF[F]] 187 | )(implicit F: MonadError[F, Throwable]): PropF[F] = 188 | Suspend[F, Result[F]]( 189 | fa.handleError(t => Result[F](Prop.Exception(t), Nil, Set.empty, Set.empty)) 190 | ) 191 | 192 | implicit def effectOfUnitToPropF[F[_]]( 193 | fu: F[Unit] 194 | )(implicit F: MonadError[F, Throwable]): PropF[F] = 195 | Suspend[F, Result[F]]( 196 | fu.as(Result[F](Prop.True, Nil, Set.empty, Set.empty): PropF[F]) 197 | .handleError(t => Result[F](Prop.Exception(t), Nil, Set.empty, Set.empty)) 198 | ) 199 | 200 | implicit def effectOfBooleanToPropF[F[_]]( 201 | fb: F[Boolean] 202 | )(implicit F: MonadError[F, Throwable]): PropF[F] = 203 | effectOfPropFToPropF(F.map(fb)(boolean(_))) 204 | 205 | def forAllNoShrinkF[F[_], T1, P]( 206 | g1: Gen[T1] 207 | )( 208 | f: T1 => P 209 | )(implicit 210 | toProp: P => PropF[F], 211 | F: MonadError[F, Throwable], 212 | pp1: T1 => Pretty 213 | ): PropF[F] = 214 | PropF[F] { params0 => 215 | val (params, seed) = Prop.startSeed(params0) 216 | try { 217 | val r = g1.doPureApply(params, seed) 218 | r.retrieve match { 219 | case Some(x) => 220 | val labels = r.labels.mkString(",") 221 | toProp(f(x)).addArg(Prop.Arg(labels, x, 0, x, pp1(x), pp1(x))).provedToTrue 222 | case None => PropF.undecided 223 | } 224 | } catch { 225 | case _: Gen.RetrievalError => PropF.undecided 226 | } 227 | } 228 | 229 | def forAllNoShrinkF[F[_], T1, P]( 230 | f: T1 => P 231 | )(implicit 232 | toProp: P => PropF[F], 233 | F: MonadError[F, Throwable], 234 | a1: Arbitrary[T1], 235 | pp1: T1 => Pretty 236 | ): PropF[F] = 237 | forAllNoShrinkF(a1.arbitrary)(f) 238 | 239 | def forAllNoShrinkF[F[_], T1, T2, P]( 240 | g1: Gen[T1], 241 | g2: Gen[T2] 242 | )( 243 | f: (T1, T2) => P 244 | )(implicit 245 | toProp: P => PropF[F], 246 | F: MonadError[F, Throwable], 247 | pp1: T1 => Pretty, 248 | pp2: T2 => Pretty 249 | ): PropF[F] = 250 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2)(f(t1, _))) 251 | 252 | def forAllNoShrinkF[F[_], T1, T2, P]( 253 | f: (T1, T2) => P 254 | )(implicit 255 | toProp: P => PropF[F], 256 | F: MonadError[F, Throwable], 257 | a1: Arbitrary[T1], 258 | pp1: T1 => Pretty, 259 | a2: Arbitrary[T2], 260 | pp2: T2 => Pretty 261 | ): PropF[F] = 262 | forAllNoShrinkF(a1.arbitrary, a2.arbitrary)(f) 263 | 264 | def forAllNoShrinkF[F[_], T1, T2, T3, P]( 265 | g1: Gen[T1], 266 | g2: Gen[T2], 267 | g3: Gen[T3] 268 | )( 269 | f: (T1, T2, T3) => P 270 | )(implicit 271 | toProp: P => PropF[F], 272 | F: MonadError[F, Throwable], 273 | pp1: T1 => Pretty, 274 | pp2: T2 => Pretty, 275 | pp3: T3 => Pretty 276 | ): PropF[F] = 277 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2, g3)(f(t1, _, _))) 278 | 279 | def forAllNoShrinkF[F[_], T1, T2, T3, P]( 280 | f: (T1, T2, T3) => P 281 | )(implicit 282 | toProp: P => PropF[F], 283 | F: MonadError[F, Throwable], 284 | a1: Arbitrary[T1], 285 | pp1: T1 => Pretty, 286 | a2: Arbitrary[T2], 287 | pp2: T2 => Pretty, 288 | a3: Arbitrary[T3], 289 | pp3: T3 => Pretty 290 | ): PropF[F] = 291 | forAllNoShrinkF(a1.arbitrary, a2.arbitrary, a3.arbitrary)(f) 292 | 293 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, P]( 294 | g1: Gen[T1], 295 | g2: Gen[T2], 296 | g3: Gen[T3], 297 | g4: Gen[T4] 298 | )( 299 | f: (T1, T2, T3, T4) => P 300 | )(implicit 301 | toProp: P => PropF[F], 302 | F: MonadError[F, Throwable], 303 | pp1: T1 => Pretty, 304 | pp2: T2 => Pretty, 305 | pp3: T3 => Pretty, 306 | pp4: T4 => Pretty 307 | ): PropF[F] = 308 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2, g3, g4)(f(t1, _, _, _))) 309 | 310 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, P]( 311 | f: (T1, T2, T3, T4) => P 312 | )(implicit 313 | toProp: P => PropF[F], 314 | F: MonadError[F, Throwable], 315 | a1: Arbitrary[T1], 316 | pp1: T1 => Pretty, 317 | a2: Arbitrary[T2], 318 | pp2: T2 => Pretty, 319 | a3: Arbitrary[T3], 320 | pp3: T3 => Pretty, 321 | a4: Arbitrary[T4], 322 | pp4: T4 => Pretty 323 | ): PropF[F] = 324 | forAllNoShrinkF(a1.arbitrary, a2.arbitrary, a3.arbitrary, a4.arbitrary)(f) 325 | 326 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, P]( 327 | g1: Gen[T1], 328 | g2: Gen[T2], 329 | g3: Gen[T3], 330 | g4: Gen[T4], 331 | g5: Gen[T5] 332 | )( 333 | f: (T1, T2, T3, T4, T5) => P 334 | )(implicit 335 | toProp: P => PropF[F], 336 | F: MonadError[F, Throwable], 337 | pp1: T1 => Pretty, 338 | pp2: T2 => Pretty, 339 | pp3: T3 => Pretty, 340 | pp4: T4 => Pretty, 341 | pp5: T5 => Pretty 342 | ): PropF[F] = 343 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2, g3, g4, g5)(f(t1, _, _, _, _))) 344 | 345 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, P]( 346 | f: (T1, T2, T3, T4, T5) => P 347 | )(implicit 348 | toProp: P => PropF[F], 349 | F: MonadError[F, Throwable], 350 | a1: Arbitrary[T1], 351 | pp1: T1 => Pretty, 352 | a2: Arbitrary[T2], 353 | pp2: T2 => Pretty, 354 | a3: Arbitrary[T3], 355 | pp3: T3 => Pretty, 356 | a4: Arbitrary[T4], 357 | pp4: T4 => Pretty, 358 | a5: Arbitrary[T5], 359 | pp5: T5 => Pretty 360 | ): PropF[F] = 361 | forAllNoShrinkF( 362 | a1.arbitrary, 363 | a2.arbitrary, 364 | a3.arbitrary, 365 | a4.arbitrary, 366 | a5.arbitrary 367 | )(f) 368 | 369 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, P]( 370 | g1: Gen[T1], 371 | g2: Gen[T2], 372 | g3: Gen[T3], 373 | g4: Gen[T4], 374 | g5: Gen[T5], 375 | g6: Gen[T6] 376 | )( 377 | f: (T1, T2, T3, T4, T5, T6) => P 378 | )(implicit 379 | toProp: P => PropF[F], 380 | F: MonadError[F, Throwable], 381 | pp1: T1 => Pretty, 382 | pp2: T2 => Pretty, 383 | pp3: T3 => Pretty, 384 | pp4: T4 => Pretty, 385 | pp5: T5 => Pretty, 386 | pp6: T6 => Pretty 387 | ): PropF[F] = 388 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2, g3, g4, g5, g6)(f(t1, _, _, _, _, _))) 389 | 390 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, P]( 391 | f: (T1, T2, T3, T4, T5, T6) => P 392 | )(implicit 393 | toProp: P => PropF[F], 394 | F: MonadError[F, Throwable], 395 | a1: Arbitrary[T1], 396 | pp1: T1 => Pretty, 397 | a2: Arbitrary[T2], 398 | pp2: T2 => Pretty, 399 | a3: Arbitrary[T3], 400 | pp3: T3 => Pretty, 401 | a4: Arbitrary[T4], 402 | pp4: T4 => Pretty, 403 | a5: Arbitrary[T5], 404 | pp5: T5 => Pretty, 405 | a6: Arbitrary[T6], 406 | pp6: T6 => Pretty 407 | ): PropF[F] = 408 | forAllNoShrinkF( 409 | a1.arbitrary, 410 | a2.arbitrary, 411 | a3.arbitrary, 412 | a4.arbitrary, 413 | a5.arbitrary, 414 | a6.arbitrary 415 | )(f) 416 | 417 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, T7, P]( 418 | g1: Gen[T1], 419 | g2: Gen[T2], 420 | g3: Gen[T3], 421 | g4: Gen[T4], 422 | g5: Gen[T5], 423 | g6: Gen[T6], 424 | g7: Gen[T7] 425 | )( 426 | f: (T1, T2, T3, T4, T5, T6, T7) => P 427 | )(implicit 428 | toProp: P => PropF[F], 429 | F: MonadError[F, Throwable], 430 | pp1: T1 => Pretty, 431 | pp2: T2 => Pretty, 432 | pp3: T3 => Pretty, 433 | pp4: T4 => Pretty, 434 | pp5: T5 => Pretty, 435 | pp6: T6 => Pretty, 436 | pp7: T7 => Pretty 437 | ): PropF[F] = 438 | forAllNoShrinkF(g1)(t1 => forAllNoShrinkF(g2, g3, g4, g5, g6, g7)(f(t1, _, _, _, _, _, _))) 439 | 440 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, T7, P]( 441 | f: (T1, T2, T3, T4, T5, T6, T7) => P 442 | )(implicit 443 | toProp: P => PropF[F], 444 | F: MonadError[F, Throwable], 445 | a1: Arbitrary[T1], 446 | pp1: T1 => Pretty, 447 | a2: Arbitrary[T2], 448 | pp2: T2 => Pretty, 449 | a3: Arbitrary[T3], 450 | pp3: T3 => Pretty, 451 | a4: Arbitrary[T4], 452 | pp4: T4 => Pretty, 453 | a5: Arbitrary[T5], 454 | pp5: T5 => Pretty, 455 | a6: Arbitrary[T6], 456 | pp6: T6 => Pretty, 457 | a7: Arbitrary[T7], 458 | pp7: T7 => Pretty 459 | ): PropF[F] = 460 | forAllNoShrinkF( 461 | a1.arbitrary, 462 | a2.arbitrary, 463 | a3.arbitrary, 464 | a4.arbitrary, 465 | a5.arbitrary, 466 | a6.arbitrary, 467 | a7.arbitrary 468 | )(f) 469 | 470 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, T7, T8, P]( 471 | g1: Gen[T1], 472 | g2: Gen[T2], 473 | g3: Gen[T3], 474 | g4: Gen[T4], 475 | g5: Gen[T5], 476 | g6: Gen[T6], 477 | g7: Gen[T7], 478 | g8: Gen[T8] 479 | )( 480 | f: (T1, T2, T3, T4, T5, T6, T7, T8) => P 481 | )(implicit 482 | toProp: P => PropF[F], 483 | F: MonadError[F, Throwable], 484 | pp1: T1 => Pretty, 485 | pp2: T2 => Pretty, 486 | pp3: T3 => Pretty, 487 | pp4: T4 => Pretty, 488 | pp5: T5 => Pretty, 489 | pp6: T6 => Pretty, 490 | pp7: T7 => Pretty, 491 | pp8: T8 => Pretty 492 | ): PropF[F] = 493 | forAllNoShrinkF(g1)(t1 => 494 | forAllNoShrinkF(g2, g3, g4, g5, g6, g7, g8)(f(t1, _, _, _, _, _, _, _)) 495 | ) 496 | 497 | def forAllNoShrinkF[F[_], T1, T2, T3, T4, T5, T6, T7, T8, P]( 498 | f: (T1, T2, T3, T4, T5, T6, T7, T8) => P 499 | )(implicit 500 | toProp: P => PropF[F], 501 | F: MonadError[F, Throwable], 502 | a1: Arbitrary[T1], 503 | pp1: T1 => Pretty, 504 | a2: Arbitrary[T2], 505 | pp2: T2 => Pretty, 506 | a3: Arbitrary[T3], 507 | pp3: T3 => Pretty, 508 | a4: Arbitrary[T4], 509 | pp4: T4 => Pretty, 510 | a5: Arbitrary[T5], 511 | pp5: T5 => Pretty, 512 | a6: Arbitrary[T6], 513 | pp6: T6 => Pretty, 514 | a7: Arbitrary[T7], 515 | pp7: T7 => Pretty, 516 | a8: Arbitrary[T8], 517 | pp8: T8 => Pretty 518 | ): PropF[F] = 519 | forAllNoShrinkF( 520 | a1.arbitrary, 521 | a2.arbitrary, 522 | a3.arbitrary, 523 | a4.arbitrary, 524 | a5.arbitrary, 525 | a6.arbitrary, 526 | a7.arbitrary, 527 | a8.arbitrary 528 | )(f) 529 | 530 | def forAllShrinkF[F[_], T, P]( 531 | gen: Gen[T], 532 | shrink: T => Stream[T] 533 | )( 534 | f: T => P 535 | )(implicit 536 | toProp: P => PropF[F], 537 | F: MonadError[F, Throwable], 538 | pp: T => Pretty 539 | ): PropF[F] = 540 | PropF[F] { prms0 => 541 | val (prms, seed) = Prop.startSeed(prms0) 542 | val gr = gen.doApply(prms, seed) 543 | val labels = gr.labels.mkString(",") 544 | 545 | def result(x: T): F[Result[F]] = 546 | toProp(f(x)).provedToTrue.checkOne(Prop.slideSeed(prms0)) 547 | 548 | def getFirstFailure(xs: Stream[T]): F[Either[(T, Result[F]), (T, Result[F])]] = { 549 | assert(!xs.isEmpty, "Stream cannot be empty") 550 | val results: Stream[(T, F[Result[F]])] = xs.map(x => (x, result(x))) 551 | val (firstT, firstResF) = results.head 552 | firstResF.flatMap { firstRes => 553 | def loop( 554 | results: Stream[(T, F[Result[F]])] 555 | ): F[Either[(T, Result[F]), (T, Result[F])]] = { 556 | results match { 557 | case hd #:: tl => 558 | hd._2.flatMap { r => 559 | if (r.failure) F.pure(Left((hd._1, r))) 560 | else loop(tl) 561 | } 562 | case _ => F.pure(Right((firstT, firstRes))) 563 | } 564 | } 565 | loop(results) 566 | } 567 | } 568 | 569 | def shrinker(x: T, r: Result[F], shrinks: Int, orig: T): F[PropF[F]] = { 570 | val xs = shrink(x).filter(gr.sieve) 571 | val res = r.addArg(Prop.Arg(labels, x, shrinks, orig, pp(x), pp(orig))) 572 | if (xs.isEmpty) F.pure(res) 573 | else 574 | getFirstFailure(xs).flatMap { 575 | case Right((_, _)) => F.pure(res) 576 | case Left((x2, r2)) => shrinker(x2, replOrig(r, r2), shrinks + 1, orig) 577 | } 578 | } 579 | 580 | def replOrig(r0: Result[F], r1: Result[F]): Result[F] = 581 | (r0.args, r1.args) match { 582 | case (a0 :: _, a1 :: as) => 583 | r1.copy( 584 | args = a1.copy( 585 | origArg = a0.origArg, 586 | prettyOrigArg = a0.prettyOrigArg 587 | ) :: as 588 | ) 589 | case _ => r1 590 | } 591 | 592 | gr.retrieve match { 593 | case None => PropF.undecided 594 | case Some(x) => 595 | Suspend(result(x).flatMap { r => 596 | if (r.failure && prms.useLegacyShrinking) shrinker(x, r, 0, x) 597 | else F.pure(r.addArg(Prop.Arg(labels, x, 0, x, pp(x), pp(x)))) 598 | }) 599 | } 600 | } 601 | 602 | def forAllF[F[_], T1, P]( 603 | g1: Gen[T1] 604 | )( 605 | f: T1 => P 606 | )(implicit 607 | toProp: P => PropF[F], 608 | F: MonadError[F, Throwable], 609 | s1: Shrink[T1], 610 | pp1: T1 => Pretty 611 | ): PropF[F] = forAllShrinkF(g1, s1.shrink)(f) 612 | 613 | def forAllF[F[_], T1, T2, P]( 614 | g1: Gen[T1], 615 | g2: Gen[T2] 616 | )( 617 | f: (T1, T2) => P 618 | )(implicit 619 | toProp: P => PropF[F], 620 | F: MonadError[F, Throwable], 621 | s1: Shrink[T1], 622 | pp1: T1 => Pretty, 623 | s2: Shrink[T2], 624 | pp2: T2 => Pretty 625 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2)(f(t1, _))) 626 | 627 | def forAllF[F[_], T1, T2, T3, P]( 628 | g1: Gen[T1], 629 | g2: Gen[T2], 630 | g3: Gen[T3] 631 | )( 632 | f: (T1, T2, T3) => P 633 | )(implicit 634 | toProp: P => PropF[F], 635 | F: MonadError[F, Throwable], 636 | s1: Shrink[T1], 637 | pp1: T1 => Pretty, 638 | s2: Shrink[T2], 639 | pp2: T2 => Pretty, 640 | s3: Shrink[T3], 641 | pp3: T3 => Pretty 642 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3)(f(t1, _, _))) 643 | 644 | def forAllF[F[_], T1, T2, T3, T4, P]( 645 | g1: Gen[T1], 646 | g2: Gen[T2], 647 | g3: Gen[T3], 648 | g4: Gen[T4] 649 | )( 650 | f: (T1, T2, T3, T4) => P 651 | )(implicit 652 | toProp: P => PropF[F], 653 | F: MonadError[F, Throwable], 654 | s1: Shrink[T1], 655 | pp1: T1 => Pretty, 656 | s2: Shrink[T2], 657 | pp2: T2 => Pretty, 658 | s3: Shrink[T3], 659 | pp3: T3 => Pretty, 660 | s4: Shrink[T4], 661 | pp4: T4 => Pretty 662 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3, g4)(f(t1, _, _, _))) 663 | 664 | def forAllF[F[_], T1, T2, T3, T4, T5, P]( 665 | g1: Gen[T1], 666 | g2: Gen[T2], 667 | g3: Gen[T3], 668 | g4: Gen[T4], 669 | g5: Gen[T5] 670 | )( 671 | f: (T1, T2, T3, T4, T5) => P 672 | )(implicit 673 | toProp: P => PropF[F], 674 | F: MonadError[F, Throwable], 675 | s1: Shrink[T1], 676 | pp1: T1 => Pretty, 677 | s2: Shrink[T2], 678 | pp2: T2 => Pretty, 679 | s3: Shrink[T3], 680 | pp3: T3 => Pretty, 681 | s4: Shrink[T4], 682 | pp4: T4 => Pretty, 683 | s5: Shrink[T5], 684 | pp5: T5 => Pretty 685 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3, g4, g5)(f(t1, _, _, _, _))) 686 | 687 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, P]( 688 | g1: Gen[T1], 689 | g2: Gen[T2], 690 | g3: Gen[T3], 691 | g4: Gen[T4], 692 | g5: Gen[T5], 693 | g6: Gen[T6] 694 | )( 695 | f: (T1, T2, T3, T4, T5, T6) => P 696 | )(implicit 697 | toProp: P => PropF[F], 698 | F: MonadError[F, Throwable], 699 | s1: Shrink[T1], 700 | pp1: T1 => Pretty, 701 | s2: Shrink[T2], 702 | pp2: T2 => Pretty, 703 | s3: Shrink[T3], 704 | pp3: T3 => Pretty, 705 | s4: Shrink[T4], 706 | pp4: T4 => Pretty, 707 | s5: Shrink[T5], 708 | pp5: T5 => Pretty, 709 | s6: Shrink[T6], 710 | pp6: T6 => Pretty 711 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3, g4, g5, g6)(f(t1, _, _, _, _, _))) 712 | 713 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, T7, P]( 714 | g1: Gen[T1], 715 | g2: Gen[T2], 716 | g3: Gen[T3], 717 | g4: Gen[T4], 718 | g5: Gen[T5], 719 | g6: Gen[T6], 720 | g7: Gen[T7] 721 | )( 722 | f: (T1, T2, T3, T4, T5, T6, T7) => P 723 | )(implicit 724 | toProp: P => PropF[F], 725 | F: MonadError[F, Throwable], 726 | s1: Shrink[T1], 727 | pp1: T1 => Pretty, 728 | s2: Shrink[T2], 729 | pp2: T2 => Pretty, 730 | s3: Shrink[T3], 731 | pp3: T3 => Pretty, 732 | s4: Shrink[T4], 733 | pp4: T4 => Pretty, 734 | s5: Shrink[T5], 735 | pp5: T5 => Pretty, 736 | s6: Shrink[T6], 737 | pp6: T6 => Pretty, 738 | s7: Shrink[T7], 739 | pp7: T7 => Pretty 740 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3, g4, g5, g6, g7)(f(t1, _, _, _, _, _, _))) 741 | 742 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, T7, T8, P]( 743 | g1: Gen[T1], 744 | g2: Gen[T2], 745 | g3: Gen[T3], 746 | g4: Gen[T4], 747 | g5: Gen[T5], 748 | g6: Gen[T6], 749 | g7: Gen[T7], 750 | g8: Gen[T8] 751 | )( 752 | f: (T1, T2, T3, T4, T5, T6, T7, T8) => P 753 | )(implicit 754 | toProp: P => PropF[F], 755 | F: MonadError[F, Throwable], 756 | s1: Shrink[T1], 757 | pp1: T1 => Pretty, 758 | s2: Shrink[T2], 759 | pp2: T2 => Pretty, 760 | s3: Shrink[T3], 761 | pp3: T3 => Pretty, 762 | s4: Shrink[T4], 763 | pp4: T4 => Pretty, 764 | s5: Shrink[T5], 765 | pp5: T5 => Pretty, 766 | s6: Shrink[T6], 767 | pp6: T6 => Pretty, 768 | s7: Shrink[T7], 769 | pp7: T7 => Pretty, 770 | s8: Shrink[T8], 771 | pp8: T8 => Pretty 772 | ): PropF[F] = forAllF(g1)(t1 => forAllF(g2, g3, g4, g5, g6, g7, g8)(f(t1, _, _, _, _, _, _, _))) 773 | 774 | def forAllF[F[_], T1, P]( 775 | f: T1 => P 776 | )(implicit 777 | toProp: P => PropF[F], 778 | F: MonadError[F, Throwable], 779 | a1: Arbitrary[T1], 780 | s1: Shrink[T1], 781 | pp1: T1 => Pretty 782 | ): PropF[F] = forAllShrinkF(a1.arbitrary, s1.shrink)(f) 783 | 784 | def forAllF[F[_], T1, T2, P]( 785 | f: (T1, T2) => P 786 | )(implicit 787 | toProp: P => PropF[F], 788 | F: MonadError[F, Throwable], 789 | a1: Arbitrary[T1], 790 | s1: Shrink[T1], 791 | pp1: T1 => Pretty, 792 | a2: Arbitrary[T2], 793 | s2: Shrink[T2], 794 | pp2: T2 => Pretty 795 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _))) 796 | 797 | def forAllF[F[_], T1, T2, T3, P]( 798 | f: (T1, T2, T3) => P 799 | )(implicit 800 | toProp: P => PropF[F], 801 | F: MonadError[F, Throwable], 802 | a1: Arbitrary[T1], 803 | s1: Shrink[T1], 804 | pp1: T1 => Pretty, 805 | a2: Arbitrary[T2], 806 | s2: Shrink[T2], 807 | pp2: T2 => Pretty, 808 | a3: Arbitrary[T3], 809 | s3: Shrink[T3], 810 | pp3: T3 => Pretty 811 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _))) 812 | 813 | def forAllF[F[_], T1, T2, T3, T4, P]( 814 | f: (T1, T2, T3, T4) => P 815 | )(implicit 816 | toProp: P => PropF[F], 817 | F: MonadError[F, Throwable], 818 | a1: Arbitrary[T1], 819 | s1: Shrink[T1], 820 | pp1: T1 => Pretty, 821 | a2: Arbitrary[T2], 822 | s2: Shrink[T2], 823 | pp2: T2 => Pretty, 824 | a3: Arbitrary[T3], 825 | s3: Shrink[T3], 826 | pp3: T3 => Pretty, 827 | a4: Arbitrary[T4], 828 | s4: Shrink[T4], 829 | pp4: T4 => Pretty 830 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _, _))) 831 | 832 | def forAllF[F[_], T1, T2, T3, T4, T5, P]( 833 | f: (T1, T2, T3, T4, T5) => P 834 | )(implicit 835 | toProp: P => PropF[F], 836 | F: MonadError[F, Throwable], 837 | a1: Arbitrary[T1], 838 | s1: Shrink[T1], 839 | pp1: T1 => Pretty, 840 | a2: Arbitrary[T2], 841 | s2: Shrink[T2], 842 | pp2: T2 => Pretty, 843 | a3: Arbitrary[T3], 844 | s3: Shrink[T3], 845 | pp3: T3 => Pretty, 846 | a4: Arbitrary[T4], 847 | s4: Shrink[T4], 848 | pp4: T4 => Pretty, 849 | a5: Arbitrary[T5], 850 | s5: Shrink[T5], 851 | pp5: T5 => Pretty 852 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _, _, _))) 853 | 854 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, P]( 855 | f: (T1, T2, T3, T4, T5, T6) => P 856 | )(implicit 857 | toProp: P => PropF[F], 858 | F: MonadError[F, Throwable], 859 | a1: Arbitrary[T1], 860 | s1: Shrink[T1], 861 | pp1: T1 => Pretty, 862 | a2: Arbitrary[T2], 863 | s2: Shrink[T2], 864 | pp2: T2 => Pretty, 865 | a3: Arbitrary[T3], 866 | s3: Shrink[T3], 867 | pp3: T3 => Pretty, 868 | a4: Arbitrary[T4], 869 | s4: Shrink[T4], 870 | pp4: T4 => Pretty, 871 | a5: Arbitrary[T5], 872 | s5: Shrink[T5], 873 | pp5: T5 => Pretty, 874 | a6: Arbitrary[T6], 875 | s6: Shrink[T6], 876 | pp6: T6 => Pretty 877 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _, _, _, _))) 878 | 879 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, T7, P]( 880 | f: (T1, T2, T3, T4, T5, T6, T7) => P 881 | )(implicit 882 | toProp: P => PropF[F], 883 | F: MonadError[F, Throwable], 884 | a1: Arbitrary[T1], 885 | s1: Shrink[T1], 886 | pp1: T1 => Pretty, 887 | a2: Arbitrary[T2], 888 | s2: Shrink[T2], 889 | pp2: T2 => Pretty, 890 | a3: Arbitrary[T3], 891 | s3: Shrink[T3], 892 | pp3: T3 => Pretty, 893 | a4: Arbitrary[T4], 894 | s4: Shrink[T4], 895 | pp4: T4 => Pretty, 896 | a5: Arbitrary[T5], 897 | s5: Shrink[T5], 898 | pp5: T5 => Pretty, 899 | a6: Arbitrary[T6], 900 | s6: Shrink[T6], 901 | pp6: T6 => Pretty, 902 | a7: Arbitrary[T7], 903 | s7: Shrink[T7], 904 | pp7: T7 => Pretty 905 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _, _, _, _, _))) 906 | 907 | def forAllF[F[_], T1, T2, T3, T4, T5, T6, T7, T8, P]( 908 | f: (T1, T2, T3, T4, T5, T6, T7, T8) => P 909 | )(implicit 910 | toProp: P => PropF[F], 911 | F: MonadError[F, Throwable], 912 | a1: Arbitrary[T1], 913 | s1: Shrink[T1], 914 | pp1: T1 => Pretty, 915 | a2: Arbitrary[T2], 916 | s2: Shrink[T2], 917 | pp2: T2 => Pretty, 918 | a3: Arbitrary[T3], 919 | s3: Shrink[T3], 920 | pp3: T3 => Pretty, 921 | a4: Arbitrary[T4], 922 | s4: Shrink[T4], 923 | pp4: T4 => Pretty, 924 | a5: Arbitrary[T5], 925 | s5: Shrink[T5], 926 | pp5: T5 => Pretty, 927 | a6: Arbitrary[T6], 928 | s6: Shrink[T6], 929 | pp6: T6 => Pretty, 930 | a7: Arbitrary[T7], 931 | s7: Shrink[T7], 932 | pp7: T7 => Pretty, 933 | a8: Arbitrary[T8], 934 | s8: Shrink[T8], 935 | pp8: T8 => Pretty 936 | ): PropF[F] = forAllF((t1: T1) => forAllF(f(t1, _, _, _, _, _, _, _))) 937 | } 938 | -------------------------------------------------------------------------------- /munit/shared/src/main/scala/munit/ScalaCheckEffectSuite.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Typelevel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package munit 18 | 19 | import cats.implicits._ 20 | import org.scalacheck.{Gen, Test => ScalaCheckTest} 21 | import org.scalacheck.Test.PropException 22 | import org.scalacheck.effect.PropF 23 | import org.scalacheck.rng.Seed 24 | import org.scalacheck.util.Pretty 25 | 26 | /** Extends `ScalaCheckSuite`, adding support for evaluation of effectful properties (`PropF[F]` 27 | * values). 28 | * 29 | * This trait transforms tests which return `PropF[F]` values in to `F[Unit]` values. The `F[Unit]` 30 | * values are transformed to a `Future[Unit]` via `munitValueTransform`. Hence, an appropriate 31 | * value transform must be registered for the effect type in use. This is typically done by mixing 32 | * in an MUnit compatibility trait for the desired effect type. 33 | */ 34 | trait ScalaCheckEffectSuite extends ScalaCheckSuite { 35 | 36 | private val initialSeed: Seed = 37 | scalaCheckTestParameters.initialSeed.getOrElse( 38 | Seed.fromBase64(scalaCheckInitialSeed).get 39 | ) 40 | 41 | private val genParameters: Gen.Parameters = 42 | Gen.Parameters.default 43 | .withLegacyShrinking(scalaCheckTestParameters.useLegacyShrinking) 44 | .withInitialSeed(initialSeed) 45 | 46 | override def munitValueTransforms: List[ValueTransform] = { 47 | val testResultTransform = 48 | new ValueTransform( 49 | "ScalaCheck TestResult", 50 | { case p: ScalaCheckTest.Result => 51 | super.munitValueTransform(parseTestResult(p)) 52 | } 53 | ) 54 | 55 | super.munitValueTransforms :+ scalaCheckPropFValueTransform :+ testResultTransform 56 | } 57 | 58 | private val scalaCheckPropFValueTransform: ValueTransform = 59 | new ValueTransform( 60 | "ScalaCheck PropF", 61 | { case p: PropF[f] => 62 | super.munitValueTransform(checkPropF[f](p)) 63 | } 64 | ) 65 | 66 | private def checkPropF[F[_]](prop: PropF[F])(implicit loc: Location): F[Unit] = { 67 | import prop.F 68 | prop.check(scalaCheckTestParameters, genParameters).map(fixResultException).map(parseTestResult) 69 | } 70 | 71 | private def parseTestResult(result: ScalaCheckTest.Result)(implicit loc: Location): Unit = { 72 | if (!result.passed) { 73 | val seed = genParameters.initialSeed.get 74 | val seedMessage = 75 | s"""|Failing seed: ${seed.toBase64} 76 | |You can reproduce this failure by adding the following override to your suite: 77 | | 78 | | override def scalaCheckInitialSeed = "${seed.toBase64}" 79 | |""".stripMargin 80 | fail(seedMessage + "\n" + Pretty.pretty(result, scalaCheckPrettyParameters)) 81 | } 82 | } 83 | 84 | private def fixResultException(result: ScalaCheckTest.Result): ScalaCheckTest.Result = 85 | result.copy( 86 | status = result.status match { 87 | case p @ PropException(_, e, _) => p.copy(e = Exceptions.rootCause(e)) 88 | case default => default 89 | } 90 | ) 91 | 92 | } 93 | 94 | object ScalaCheckEffectSuite {} 95 | -------------------------------------------------------------------------------- /munit/shared/src/test/scala/munit/Example.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Typelevel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package munit 18 | 19 | import cats.effect.IO 20 | import cats.effect.unsafe.implicits.global 21 | import org.scalacheck.Test.Parameters 22 | import org.scalacheck.effect.PropF 23 | 24 | import java.util.concurrent.atomic.AtomicBoolean 25 | 26 | class Example extends ScalaCheckEffectSuite { 27 | override def munitValueTransforms: List[ValueTransform] = 28 | super.munitValueTransforms ++ List(munitIOTransform) 29 | 30 | // From https://github.com/scalameta/munit/pull/134 31 | private val munitIOTransform: ValueTransform = 32 | new ValueTransform("IO", { case e: IO[_] => e.unsafeToFuture() }) 33 | 34 | test("one") { 35 | PropF.forAllNoShrinkF { (a: Int) => IO(assert(a == a)) } 36 | } 37 | 38 | test("one-alt") { 39 | PropF.forAllNoShrinkF { (a: Int) => IO(a == a) } 40 | } 41 | 42 | test("two") { 43 | PropF.forAllF { (a: Int, b: Int) => 44 | IO { assertEquals(a + b, b + a) } 45 | } 46 | } 47 | 48 | test("three") { 49 | PropF 50 | .forAllF { (a: Int) => 51 | IO { assert(a == a) } 52 | } 53 | .check(Parameters.default.withMinSuccessfulTests(1)) 54 | } 55 | 56 | val ready = new AtomicBoolean(false) 57 | 58 | val fixture = FunFixture[Unit]( 59 | setup = { _ => ready.set(true) }, 60 | teardown = { _ => ready.set(false) } 61 | ) 62 | 63 | fixture.test("fixture and forall") { _ => 64 | PropF.forAllF { (_: Int) => 65 | IO { assertEquals(ready.get(), true) } 66 | } 67 | } 68 | 69 | // test("three") { 70 | // forAllAsyncNoShrink(Arbitrary.arbitrary[Int].filter(i => i > 10 && i < 20)) { 71 | // (a: Int) => IO { println(a) } 72 | // } 73 | // } 74 | } 75 | -------------------------------------------------------------------------------- /munit/shared/src/test/scala/munit/ScalaCheckEffectSuiteSuite.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Typelevel 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package munit 18 | 19 | import cats.effect.IO 20 | import cats.effect.unsafe.implicits.global 21 | import org.scalacheck.effect.PropF 22 | import org.scalacheck.{Arbitrary, Gen, Shrink} 23 | 24 | // Who tests the tests? 25 | class ScalaCheckEffectSuiteSuite extends ScalaCheckEffectSuite { 26 | 27 | implicit def noShrink[T]: Shrink[T] = Shrink.shrinkAny[T] 28 | 29 | override def munitValueTransforms: List[ValueTransform] = 30 | super.munitValueTransforms ++ List(munitIOTransform) 31 | 32 | // From https://github.com/scalameta/munit/pull/134 33 | private val munitIOTransform: ValueTransform = 34 | new ValueTransform("IO", { case e: IO[_] => e.unsafeToFuture() }) 35 | 36 | test("Correctly slides seed for multi-arg PropF") { 37 | implicit val arbForInt: Arbitrary[Int] = Arbitrary( 38 | Gen.choose(0, Int.MaxValue) 39 | ) 40 | 41 | var last: Option[Int] = None 42 | var duplicates = 0 43 | 44 | PropF.forAllF { (x: Int, y: Int) => 45 | if (Some(x) == last) { 46 | duplicates = duplicates + 1 47 | } 48 | last = Some(y) 49 | 50 | // This is a bit of a heuristic as the generator may genuinely produce identical consecutive values 51 | // The bug was that it wasn't sliding so the first generated value was _always_ equal to the second 52 | // generated value from the previous iteration 53 | IO { assert(clue(duplicates) < 10) } 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.1 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2") 2 | addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.0") 3 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17") 4 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") 5 | --------------------------------------------------------------------------------