Skip to content

Commit

Permalink
Add a testkit (#253)
Browse files Browse the repository at this point in the history
* Add algebra interop module

* Add algebra instances for Rational

* Headers

* Add cats CommutativeGroup instance for Rational

* Order for Rational

* Setup algebra for testing

* Create a testkit

* Add Cogen[Rational]

* Test laws for Rational instances

* Fix unlawful pow

* Make Rational#hashCode lawful

* Add arbs for (Delta)Quantity

* Add Cats instances for Rational, test laws
  • Loading branch information
armanbilge authored Jul 7, 2022
1 parent 4fe10f8 commit a7eef88
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: mkdir -p spire/.js/target spire/.jvm/target benchmarks/target target all/target units/.jvm/target unidocs/target .js/target core/.native/target site/target spire/.native/target core/.js/target units/.native/target core/.jvm/target .jvm/target .native/target units/.js/target project/target
run: mkdir -p spire/.js/target spire/.jvm/target benchmarks/target testkit/.native/target target all/target units/.jvm/target testkit/.js/target unidocs/target .js/target core/.native/target site/target spire/.native/target core/.js/target units/.native/target core/.jvm/target .jvm/target .native/target units/.js/target testkit/.jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: tar cf targets.tar spire/.js/target spire/.jvm/target benchmarks/target target all/target units/.jvm/target unidocs/target .js/target core/.native/target site/target spire/.native/target core/.js/target units/.native/target core/.jvm/target .jvm/target .native/target units/.js/target project/target
run: tar cf targets.tar spire/.js/target spire/.jvm/target benchmarks/target testkit/.native/target target all/target units/.jvm/target testkit/.js/target unidocs/target .js/target core/.native/target site/target spire/.native/target core/.js/target units/.native/target core/.jvm/target .jvm/target .native/target units/.js/target testkit/.jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
Expand Down
17 changes: 16 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def commonSettings = Seq(
)

lazy val root = tlCrossRootProject
.aggregate(core, units, spire, unidocs)
.aggregate(core, units, spire, testkit, unidocs)

lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
Expand All @@ -54,6 +54,21 @@ lazy val spire = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.settings(commonSettings :_*)
.settings(libraryDependencies += "org.typelevel" %%% "spire" % "0.18.0")

lazy val testkit = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("testkit"))
.settings(
name := "coulomb-testkit",
libraryDependencies ++= Seq(
"org.scalacheck" %%% "scalacheck" % "1.16.0",
"org.scalameta" %%% "munit-scalacheck" % "1.0.0-M6" % Test,
"org.typelevel" %%% "algebra-laws" % "2.8.0" % Test,
"org.typelevel" %%% "discipline-munit" % "2.0.0-M3" % Test,
)
)
.dependsOn(core % "compile->compile;test->test")
.settings(commonSettings :_*)

// a target for rolling up all subproject deps: a convenient
// way to get a repl that has access to all subprojects
// sbt all/console
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/coulomb/ops/algebra/cats/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ package coulomb.ops.algebra.cats
object all:
export quantity.given
export deltaquantity.given
export rational.given
42 changes: 42 additions & 0 deletions core/src/main/scala/coulomb/ops/algebra/cats/rational.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2022 Erik Erlandson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coulomb.ops.algebra.cats

import algebra.ring.*
import cats.kernel.*
import coulomb.ops.standard.*
import coulomb.rational.*

object rational:
given Field[Rational] with
def zero = Rational.const0
def one = Rational.const1
def plus(x: Rational, y: Rational) = x + y
override def minus(x: Rational, y: Rational) = x - y
def times(x: Rational, y: Rational) = x * y
def div(x: Rational, y: Rational) = x / y
def negate(x: Rational) = -x
override def pow(x: Rational, n: Int) = x.pow(n)

given CommutativeGroup[Rational] = summon[Field[Rational]].additive

given Order[Rational] with Hash[Rational] with
def hash(x: Rational) = x.hashCode
def compare(x: Rational, y: Rational) =
if x == y then 0
else if x > y then 1
else -1
3 changes: 2 additions & 1 deletion core/src/main/scala/coulomb/rational/rational.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ final class Rational private (val n: BigInt, val d: BigInt) extends Serializable
if (e < 0) then
canonical(d.pow(-e), n.pow(-e))
else if (e == 0) then
require(n != 0)
canonical(1, 1)
else if (e == 1) then
this
Expand Down Expand Up @@ -79,6 +78,8 @@ final class Rational private (val n: BigInt, val d: BigInt) extends Serializable
case v: Long => (n == v) && (d == 1)
case _ => false

override def hashCode: Int = 29 * (37 * n.## + d.##)

inline def < (rhs: Rational): Boolean = (n * rhs.d) < (rhs.n * d)
inline def > (rhs: Rational): Boolean = rhs < this
inline def <= (rhs: Rational): Boolean = !(this > rhs)
Expand Down
41 changes: 41 additions & 0 deletions testkit/src/main/scala/coulomb/testkit/arbitraries.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 Erik Erlandson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coulomb
package testkit

import coulomb.rational.*
import coulomb.syntax.*
import org.scalacheck.*

given Arbitrary[Rational] = Arbitrary(
for
n <- Arbitrary.arbitrary[BigInt]
d <- Arbitrary.arbitrary[BigInt].suchThat(_ != 0)
yield Rational(n, d)
)

given Cogen[Rational] = summon[Cogen[(BigInt, BigInt)]].contramap(r => (r.n, r.d))

given [V, U](using arbV: Arbitrary[V]): Arbitrary[Quantity[V, U]] =
Arbitrary(arbV.arbitrary.map(_.withUnit[U]))

given [V, U, B](using arbV: Arbitrary[V]): Arbitrary[DeltaQuantity[V, U, B]] =
Arbitrary(arbV.arbitrary.map(_.withDeltaUnit[U, B]))

given [V, U](using cogenV: Cogen[V]): Cogen[Quantity[V, U]] = cogenV.contramap(_.value)

given [V, U, B](using cogenV: Cogen[V]): Cogen[DeltaQuantity[V, U, B]] = cogenV.contramap(_.value)
41 changes: 41 additions & 0 deletions testkit/src/test/scala/coulomb/testkit/RationalSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 Erik Erlandson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package coulomb.testkit

import algebra.laws.*
import cats.kernel.laws.discipline.*
import coulomb.ops.algebra.cats.all.given
import coulomb.rational.*
import munit.DisciplineSuite
import org.scalacheck.Prop.*

class RationalSuite extends DisciplineSuite:
property("rational identity") {
forAll { (r: Rational) =>
r == Rational.const0 || (Rational.const1 / r) * r == Rational.const1
}
}

property("functions of rationals are pure") {
forAll { (x: Rational, y: Rational, f: Rational => Rational) =>
x != y || f(x) == f(y)
}
}

checkAll("Rational", RingLaws[Rational].field)
checkAll("Rational", OrderTests[Rational].order)
checkAll("Rational", HashTests[Rational].hash)

0 comments on commit a7eef88

Please sign in to comment.