├── .github └── workflows │ ├── ci.yml │ └── clean.yml ├── .gitignore ├── .scalafmt.conf ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.sbt ├── docs └── index.md ├── project ├── build.properties └── plugins.sbt └── scalatest └── src ├── main └── scala │ └── org │ └── typelevel │ └── discipline │ └── scalatest │ └── Discipline.scala └── test └── scala └── scalatest └── org └── typelevel └── discipline └── scalatest ├── DisciplineTest.scala └── laws.scala /.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: [2.12, 3, 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 scalatest/.native/target scalatest/.js/target scalatest/.jvm/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 scalatest/.native/target scalatest/.js/target scalatest/.jvm/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 (2.12, rootJS) 131 | uses: actions/download-artifact@v4 132 | with: 133 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS 134 | 135 | - name: Inflate target directories (2.12, rootJS) 136 | run: | 137 | tar xf targets.tar 138 | rm targets.tar 139 | 140 | - name: Download target directories (2.12, rootJVM) 141 | uses: actions/download-artifact@v4 142 | with: 143 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM 144 | 145 | - name: Inflate target directories (2.12, rootJVM) 146 | run: | 147 | tar xf targets.tar 148 | rm targets.tar 149 | 150 | - name: Download target directories (2.12, rootNative) 151 | uses: actions/download-artifact@v4 152 | with: 153 | name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootNative 154 | 155 | - name: Inflate target directories (2.12, rootNative) 156 | run: | 157 | tar xf targets.tar 158 | rm targets.tar 159 | 160 | - name: Download target directories (3, rootJS) 161 | uses: actions/download-artifact@v4 162 | with: 163 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS 164 | 165 | - name: Inflate target directories (3, rootJS) 166 | run: | 167 | tar xf targets.tar 168 | rm targets.tar 169 | 170 | - name: Download target directories (3, rootJVM) 171 | uses: actions/download-artifact@v4 172 | with: 173 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM 174 | 175 | - name: Inflate target directories (3, rootJVM) 176 | run: | 177 | tar xf targets.tar 178 | rm targets.tar 179 | 180 | - name: Download target directories (3, rootNative) 181 | uses: actions/download-artifact@v4 182 | with: 183 | name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootNative 184 | 185 | - name: Inflate target directories (3, 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_2.12 rootjs_3 rootjs_2.13 docs_2.12 docs_3 docs_2.13 rootjvm_2.12 rootjvm_3 rootjvm_2.13 rootnative_2.12 rootnative_3 rootnative_2.13 278 | configs-ignore: test scala-tool scala-doc-tool test-internal 279 | 280 | site: 281 | name: Generate Site 282 | strategy: 283 | matrix: 284 | os: [ubuntu-22.04] 285 | java: [temurin@11] 286 | runs-on: ${{ matrix.os }} 287 | steps: 288 | - name: Checkout current branch (full) 289 | uses: actions/checkout@v4 290 | with: 291 | fetch-depth: 0 292 | 293 | - name: Setup sbt 294 | uses: sbt/setup-sbt@v1 295 | 296 | - name: Setup Java (temurin@8) 297 | id: setup-java-temurin-8 298 | if: matrix.java == 'temurin@8' 299 | uses: actions/setup-java@v4 300 | with: 301 | distribution: temurin 302 | java-version: 8 303 | cache: sbt 304 | 305 | - name: sbt update 306 | if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' 307 | run: sbt +update 308 | 309 | - name: Setup Java (temurin@11) 310 | id: setup-java-temurin-11 311 | if: matrix.java == 'temurin@11' 312 | uses: actions/setup-java@v4 313 | with: 314 | distribution: temurin 315 | java-version: 11 316 | cache: sbt 317 | 318 | - name: sbt update 319 | if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' 320 | run: sbt +update 321 | 322 | - name: Generate site 323 | run: sbt docs/tlSite 324 | 325 | - name: Publish site 326 | if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' 327 | uses: peaceiris/actions-gh-pages@v4.0.0 328 | with: 329 | github_token: ${{ secrets.GITHUB_TOKEN }} 330 | publish_dir: site/target/docs/site 331 | keep_files: true 332 | -------------------------------------------------------------------------------- /.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 | target/ 2 | .idea/ 3 | # vim 4 | *.sw? 5 | 6 | # Ignore [ce]tags files 7 | tags 8 | 9 | .bloop 10 | .metals 11 | .bsp/ 12 | .vscode 13 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=3.9.4 2 | runner.dialect = scala213source3 3 | align.openParenCallSite = true 4 | align.openParenDefnSite = true 5 | maxColumn = 120 6 | continuationIndent.defnSite = 2 7 | assumeStandardLibraryStripMargin = true 8 | danglingParentheses.preset = true 9 | rewrite.rules = [AvoidInfix, SortImports, RedundantBraces, RedundantParens, SortModifiers] 10 | docstrings.style = Asterisk 11 | docstrings.wrapMaxColumn = 77 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # changelog 2 | 3 | This file summarizes **notable** changes for each release, but does not describe internal changes unless they are particularly exciting. This change log is ordered chronologically, so each release contains all changes described below it. 4 | 5 | ---- 6 | 7 | ## v1.0.0-M1 (2019-08-03) 8 | 9 | * Release cycle decoupled from discipline-core. 10 | * Now supports being mixed into any `TestRegistration`. 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Ross A. Baker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # discipline-scalatest 2 | 3 | ScalaTest binding for Typelevel Discipline 4 | 5 | ## [Head on over to the microsite](https://typelevel.org/discipline-scalatest) 6 | 7 | ## Quick Start 8 | 9 | ```scala 10 | libraryDependencies ++= Seq( 11 | "org.typelevel" %% "discipline-scalatest" % "" 12 | ) 13 | ``` 14 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / tlBaseVersion := "2.3" 2 | 3 | ThisBuild / crossScalaVersions := Seq("2.12.20", "3.3.6", "2.13.16") 4 | ThisBuild / tlVersionIntroduced := Map("3" -> "2.1.5") 5 | 6 | ThisBuild / startYear := Some(2019) 7 | ThisBuild / licenses := Seq(License.MIT) 8 | ThisBuild / developers := List( 9 | tlGitHubDev("larsrh", "Lars Hupel"), 10 | tlGitHubDev("rossabaker", "Ross A. Baker"), 11 | tlGitHubDev("travisbrown", "Travis Brown") 12 | ) 13 | 14 | lazy val root = tlCrossRootProject.aggregate(scalatest) 15 | 16 | lazy val scalatest = crossProject(JSPlatform, JVMPlatform, NativePlatform) 17 | .crossType(CrossType.Pure) 18 | .in(file("scalatest")) 19 | .settings( 20 | name := "discipline-scalatest", 21 | libraryDependencies ++= Seq( 22 | "org.typelevel" %%% "discipline-core" % "1.7.0", 23 | "org.scalatestplus" %%% "scalacheck-1-18" % "3.2.19.0", 24 | "org.scalatest" %%% "scalatest-funspec" % "3.2.19", 25 | "org.scalatest" %%% "scalatest-flatspec" % "3.2.19", 26 | "org.scalatest" %%% "scalatest-funsuite" % "3.2.19", 27 | "org.scalatest" %%% "scalatest-wordspec" % "3.2.19" 28 | ) 29 | ) 30 | .nativeSettings( 31 | tlVersionIntroduced := List("2.12", "2.13", "3").map(_ -> "2.3.0").toMap 32 | ) 33 | 34 | lazy val docs = project 35 | .in(file("site")) 36 | .settings( 37 | tlSiteHelium ~= { 38 | import laika.helium.config.* 39 | def textLink(p: (String, URL)) = TextLink.external(p._2.toString(), p._1) 40 | 41 | _.site.mainNavigation( 42 | appendLinks = Seq( 43 | ThemeNavigationSection( 44 | "Related Projects", 45 | textLink(TypelevelProject.Cats), 46 | textLink(TypelevelProject.Discipline) 47 | ) 48 | ) 49 | ) 50 | } 51 | ) 52 | .dependsOn(scalatest.jvm) 53 | .enablePlugins(TypelevelSitePlugin) 54 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # discipline-scalatest 2 | 3 | ScalaTest binding for Typelevel Discipline 4 | 5 | ## Quick Start 6 | 7 | ```scala 8 | libraryDependencies ++= Seq( 9 | "org.typelevel" %% "discipline-scalatest" % "@VERSION@" 10 | ) 11 | ``` 12 | 13 | ## Mixing in `Discipline` 14 | 15 | Suppose we have the following laws for truth. More useful laws are published in projects like [`cats-laws`](https://github.com/typelevel/cats), [`cats-effect-laws`](https://github.com/typelevel/cats-effect), and [`spire-laws`](https://gihtub.com/typelevel/spire). 16 | 17 | ```scala mdoc 18 | import org.scalacheck.Prop 19 | import org.typelevel.discipline.Laws 20 | 21 | object TruthLaws extends Laws { 22 | def truth = new DefaultRuleSet( 23 | name = "truth", 24 | parent = None, 25 | "true" -> Prop(_ => Prop.Result(status = Prop.True)) 26 | ) 27 | } 28 | ``` 29 | 30 | discipline-scalatest provides a `FunSuiteDiscipline` mixin (as well as similar traits for `FlatSpec` and `FunSpec`), whose `checkAll` helper lets us easily check the laws in ScalaTest: 31 | 32 | ```scala mdoc 33 | import org.scalatest.funsuite.AnyFunSuite 34 | import org.scalatestplus.scalacheck.Checkers 35 | import org.typelevel.discipline.scalatest.FunSuiteDiscipline 36 | 37 | class TruthSuite extends AnyFunSuite with FunSuiteDiscipline with Checkers { 38 | checkAll("Truth", TruthLaws.truth) 39 | } 40 | ``` 41 | -------------------------------------------------------------------------------- /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.typelevel" % "sbt-typelevel-site" % "0.8.0") 4 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") 5 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") 6 | -------------------------------------------------------------------------------- /scalatest/src/main/scala/org/typelevel/discipline/scalatest/Discipline.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Typelevel 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package org.typelevel.discipline 23 | package scalatest 24 | 25 | import org.scalactic.Prettifier 26 | import org.scalactic.source.Position 27 | import org.scalatest.flatspec.AnyFlatSpecLike 28 | import org.scalatest.funspec.AnyFunSpecLike 29 | import org.scalatest.funsuite.AnyFunSuiteLike 30 | import org.scalatest.prop.Configuration 31 | import org.scalatest.wordspec.AnyWordSpec 32 | import org.scalatestplus.scalacheck.Checkers 33 | 34 | trait Discipline { self: Configuration => 35 | 36 | /** 37 | * Convert from our configuration type to the one required by `Checkers`. 38 | * 39 | * We don't extend `Checkers` because we want to leave the user as much 40 | * control as possible over available testing styles. Unfortunately we need 41 | * this conversion because ScalaTest defines `PropertyCheckConfiguration` 42 | * as a case class in the `Configuration` trait. 43 | */ 44 | final protected[this] def convertConfiguration( 45 | config: PropertyCheckConfiguration 46 | ): Checkers.PropertyCheckConfiguration = 47 | Checkers.PropertyCheckConfiguration( 48 | config.minSuccessful, 49 | config.maxDiscardedFactor, 50 | config.minSize, 51 | config.sizeRange, 52 | config.workers 53 | ) 54 | 55 | def checkAll(name: String, ruleSet: Laws#RuleSet)(implicit 56 | config: PropertyCheckConfiguration, 57 | prettifier: Prettifier, 58 | pos: Position 59 | ): Unit 60 | } 61 | 62 | trait FlatSpecDiscipline extends Discipline { self: AnyFlatSpecLike with Configuration => 63 | final def checkAll(name: String, 64 | ruleSet: Laws#RuleSet 65 | )(implicit config: PropertyCheckConfiguration, prettifier: Prettifier, pos: Position): Unit = 66 | ruleSet.all.properties.toList match { 67 | case first :: rest => 68 | name should first._1 in Checkers.check(first._2)(convertConfiguration(config), prettifier, pos) 69 | 70 | for ((id, prop) <- rest) 71 | it should id in Checkers.check(prop)(convertConfiguration(config), prettifier, pos) 72 | 73 | case Nil => 74 | } 75 | } 76 | 77 | trait FunSpecDiscipline extends Discipline { self: AnyFunSpecLike with Configuration => 78 | final def checkAll(name: String, 79 | ruleSet: Laws#RuleSet 80 | )(implicit config: PropertyCheckConfiguration, prettifier: Prettifier, pos: Position): Unit = 81 | describe(name) { 82 | for ((id, prop) <- ruleSet.all.properties) 83 | it(id) { 84 | Checkers.check(prop)(convertConfiguration(config), prettifier, pos) 85 | } 86 | } 87 | } 88 | 89 | trait FunSuiteDiscipline extends Discipline { self: AnyFunSuiteLike with Configuration => 90 | final def checkAll(name: String, 91 | ruleSet: Laws#RuleSet 92 | )(implicit config: PropertyCheckConfiguration, prettifier: Prettifier, pos: Position): Unit = 93 | for ((id, prop) <- ruleSet.all.properties) 94 | test(s"${name}.${id}") { 95 | Checkers.check(prop)(convertConfiguration(config), prettifier, pos) 96 | } 97 | } 98 | 99 | trait WordSpecDiscipline extends Discipline { self: AnyWordSpec with Configuration => 100 | 101 | def checkAll(name: String, 102 | ruleSet: Laws#RuleSet 103 | )(implicit config: PropertyCheckConfiguration, prettifier: Prettifier, pos: Position): Unit = 104 | for ((id, prop) <- ruleSet.all.properties) 105 | registerTest(s"${name}.${id}") { 106 | Checkers.check(prop)(convertConfiguration(config), prettifier, pos) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/scalatest/org/typelevel/discipline/scalatest/DisciplineTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Typelevel 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package org.typelevel.discipline 23 | package scalatest 24 | 25 | import org.scalatest.flatspec.AnyFlatSpec 26 | import org.scalatest.funspec.AnyFunSpec 27 | import org.scalatest.funsuite.AnyFunSuite 28 | import org.scalatest.wordspec.AnyWordSpec 29 | import org.scalatestplus.scalacheck.Checkers 30 | 31 | trait DummyBase extends Discipline with Checkers { 32 | checkAll("Dummy", DummyLaws.dummy) 33 | } 34 | 35 | class DummyFlatSpec extends AnyFlatSpec with DummyBase with FlatSpecDiscipline 36 | class DummyFunSpec extends AnyFunSpec with DummyBase with FunSpecDiscipline 37 | class DummyFunSuite extends AnyFunSuite with DummyBase with FunSuiteDiscipline 38 | class DummyWordSpec extends AnyWordSpec with DummyBase with WordSpecDiscipline 39 | -------------------------------------------------------------------------------- /scalatest/src/test/scala/scalatest/org/typelevel/discipline/scalatest/laws.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Typelevel 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | * this software and associated documentation files (the "Software"), to deal in 6 | * the Software without restriction, including without limitation the rights to 7 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | * the Software, and to permit persons to whom the Software is furnished to do so, 9 | * subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | package org.typelevel.discipline.scalatest 23 | 24 | import org.scalacheck.Prop 25 | import org.typelevel.discipline.Laws 26 | 27 | object Dummy { 28 | def prop = Prop(_ => Prop.Result(status = Prop.True)) 29 | def prop2 = Prop(true) 30 | } 31 | 32 | object DummyLaws extends Laws { 33 | def dummy = 34 | new DefaultRuleSet( 35 | name = "dummy", 36 | parent = None, 37 | "true" -> Dummy.prop, 38 | "alsoTrue" -> Dummy.prop2 39 | ) 40 | } 41 | --------------------------------------------------------------------------------