Skip to content

Commit 761f427

Browse files
sherpalmisherpal
andauthored
Setup scoverage (#17)
* Setup scoverage, en route towards 100% * Removed non-compiling bugs for 2.12 * Branch coverage (whatever it is) is now at 100% * milestone of 90% reached * Coverage is now at 99.53%, left over is mysterious * Lowering coverage minimum while investigating the mysterious issue Co-authored-by: Antoine Doeraene <antoine.doeraene@mibexsoftware.com>
1 parent 1ee711f commit 761f427

37 files changed

+848
-146
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Release
22
on:
33
push:
4-
branches: [master, main]
4+
branches: [publish]
55
tags: ["*"]
66
jobs:
77
publish:

.github/workflows/scala.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Scala CI
22

33
on:
44
push:
5-
branches: [ master ]
5+
branches: [ '**' ]
66

77
jobs:
88
build:
@@ -23,3 +23,5 @@ jobs:
2323
distribution: 'adopt'
2424
- name: Run tests
2525
run: sbt +test
26+
- name: Run coverage
27+
run: sbt clean coverage url-dslJVM/test && sbt coverageReport

build.sbt

+48-23
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,37 @@
11
import sbtcrossproject.CrossPlugin.autoImport.{CrossType, crossProject}
22
import xerial.sbt.Sonatype._
33

4-
inThisBuild(List(
5-
name := "url-dsl",
6-
organization := "be.doeraene",
7-
description := "A tiny library for parsing and creating urls in a type-safe way",
8-
homepage := Some(url("https://github.com/sherpal/url-dsl")),
9-
licenses := List("MIT" -> url("http://www.opensource.org/licenses/mit-license.php")),
10-
developers := List(
11-
Developer(
12-
"sherpal",
13-
"Antoine Doeraene",
14-
"antoine.doeraene@gmail.com",
15-
url("https://github.com/sherpal")
16-
)
17-
),
18-
crossScalaVersions := Seq("3.2.0", "2.13.5", "2.12.13"),
19-
scalaVersion := crossScalaVersions.value.head,
20-
scalacOptions ++= Seq("-feature", "-deprecation"),
21-
))
4+
5+
ThisBuild / scalacOptions ++= Seq( // use ++= to add to existing options
6+
"-encoding",
7+
"utf8", // if an option takes an arg, supply it on the same line
8+
"-feature", // then put the next option on a new line for easy editing
9+
"-language:implicitConversions",
10+
"-language:existentials",
11+
"-unchecked",
12+
"-Xfatal-warnings",
13+
"-deprecation"
14+
)
15+
16+
inThisBuild(
17+
List(
18+
name := "url-dsl",
19+
organization := "be.doeraene",
20+
description := "A tiny library for parsing and creating urls in a type-safe way",
21+
homepage := Some(url("https://github.com/sherpal/url-dsl")),
22+
licenses := List("MIT" -> url("http://www.opensource.org/licenses/mit-license.php")),
23+
developers := List(
24+
Developer(
25+
"sherpal",
26+
"Antoine Doeraene",
27+
"antoine.doeraene@gmail.com",
28+
url("https://github.com/sherpal")
29+
)
30+
),
31+
crossScalaVersions := Seq("3.2.0", "2.13.5", "2.12.13"),
32+
scalaVersion := crossScalaVersions.value.head
33+
)
34+
)
2235

2336
lazy val `url-dsl` = crossProject(JSPlatform, JVMPlatform)
2437
.crossType(CrossType.Pure)
@@ -27,12 +40,24 @@ lazy val `url-dsl` = crossProject(JSPlatform, JVMPlatform)
2740
.settings(
2841
libraryDependencies ++= Seq(
2942
"app.tulz" %%% "tuplez-full-light" % "0.3.8",
30-
"org.scalatest" %%% "scalatest" % "3.2.9" % "test",
31-
"org.scalacheck" %%% "scalacheck" % "1.15.4" % "test"
43+
"org.scalatest" %%% "scalatest" % "3.2.9" % Test,
44+
"org.scalacheck" %%% "scalacheck" % "1.15.4" % Test,
45+
"org.scalameta" %%% "munit" % "0.7.29" % Test
3246
)
3347
)
48+
.jvmSettings(
49+
coverageFailOnMinimum := true,
50+
coverageMinimumStmtTotal := 99,
51+
coverageMinimumBranchTotal := 100,
52+
coverageMinimumStmtPerPackage := 80,
53+
coverageMinimumBranchPerPackage := 100,
54+
coverageMinimumStmtPerFile := 60,
55+
coverageMinimumBranchPerFile := 100
56+
)
3457

35-
lazy val root = project.in(file("."))
36-
.aggregate(`url-dsl`.js, `url-dsl`.jvm).settings(
37-
publish / skip := true,
58+
lazy val root = project
59+
.in(file("."))
60+
.aggregate(`url-dsl`.js, `url-dsl`.jvm)
61+
.settings(
62+
publish / skip := true
3863
)

project/build.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
sbt.version = 1.5.2
1+
sbt.version = 1.8.0

project/plugins.sbt

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ val scalaJSVersion =
1010
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")
1111
addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion)
1212
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10")
13+
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6")

url-dsl/.js/src/main/scala/urldsl/url/JSUrlStringParser.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ final class JSUrlStringParser(val rawUrl: String) extends UrlStringParser {
66

77
private val urlParser = new URL(rawUrl)
88

9-
def queryParametersString: String = urlParser.search
9+
def queryParametersString: String = urlParser.search.dropWhile(_ == '?')
1010

1111
def path: String = urlParser.pathname
1212

url-dsl/src/main/scala/urldsl/errors/DummyError.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ object DummyError {
1919

2020
implicit final lazy val dummyErrorIsPathMatchingError: PathMatchingError[DummyError] =
2121
new PathMatchingError[DummyError] {
22-
def malformed(str: String): DummyError = dummyError
22+
def malformed(str: => String): DummyError = dummyError
2323

24-
def endOfSegmentRequired(remainingSegments: List[Segment]): DummyError = dummyError
24+
def endOfSegmentRequired(remainingSegments: => List[Segment]): DummyError = dummyError
2525

26-
def wrongValue(expected: String, actual: String): DummyError = dummyError
26+
def wrongValue(expected: => String, actual: => String): DummyError = dummyError
2727

2828
def missingSegment: DummyError = dummyError
2929

url-dsl/src/main/scala/urldsl/errors/ErrorFromThrowable.scala

+4
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ trait ErrorFromThrowable[A] {
1111
def fromThrowable(throwable: Throwable): A
1212

1313
}
14+
15+
object ErrorFromThrowable {
16+
def apply[A](implicit error: ErrorFromThrowable[A]): ErrorFromThrowable[A] = error
17+
}

url-dsl/src/main/scala/urldsl/errors/PathMatchingError.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import urldsl.vocabulary.Segment
1313
*/
1414
trait PathMatchingError[+A] {
1515

16-
def malformed(str: String): A
17-
def endOfSegmentRequired(remainingSegments: List[Segment]): A
18-
def wrongValue(expected: String, actual: String): A
16+
def malformed(str: => String): A
17+
def endOfSegmentRequired(remainingSegments: => List[Segment]): A
18+
def wrongValue(expected: => String, actual: => String): A
1919
def missingSegment: A
2020
def unit: A
2121

url-dsl/src/main/scala/urldsl/errors/SimplePathMatchingError.scala

+3-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import urldsl.vocabulary.Segment
55
sealed trait SimplePathMatchingError
66

77
object SimplePathMatchingError {
8-
9-
final case class MalformedInt(str: String) extends SimplePathMatchingError
108
final case class EndOfSegmentRequired(remainingSegments: Seq[Segment]) extends SimplePathMatchingError
119
final case class WrongValue(expected: String, received: String) extends SimplePathMatchingError
1210
case object MissingSegment extends SimplePathMatchingError
@@ -15,12 +13,12 @@ object SimplePathMatchingError {
1513

1614
implicit lazy val pathMatchingError: PathMatchingError[SimplePathMatchingError] =
1715
new PathMatchingError[SimplePathMatchingError] {
18-
def malformed(str: String): SimplePathMatchingError = MalformedInt(str)
16+
def malformed(str: => String): SimplePathMatchingError = SimpleError(str)
1917

20-
def endOfSegmentRequired(remainingSegments: List[Segment]): SimplePathMatchingError =
18+
def endOfSegmentRequired(remainingSegments: => List[Segment]): SimplePathMatchingError =
2119
EndOfSegmentRequired(remainingSegments)
2220

23-
def wrongValue(expected: String, actual: String): SimplePathMatchingError = WrongValue(expected, actual)
21+
def wrongValue(expected: => String, actual: => String): SimplePathMatchingError = WrongValue(expected, actual)
2422

2523
def missingSegment: SimplePathMatchingError = MissingSegment
2624

url-dsl/src/main/scala/urldsl/language/Fragment.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,10 @@ object Fragment {
174174
case MaybeFragment(Some(fragment)) =>
175175
fromString(fragment) match {
176176
case Left(value) => Left(value)
177-
case Right(_: T) => Right(())
178-
case Right(value) => Left(fragmentMatchingError.wrongValue(value, t))
177+
case Right(decodedValue) => decodedValue match {
178+
case value: T if value == t => Right(())
179+
case _ => Left(fragmentMatchingError.wrongValue(decodedValue, t))
180+
}
179181
}
180182
},
181183
_ => MaybeFragment(Some(printer.print(t)))

url-dsl/src/main/scala/urldsl/language/PathSegmentWithQueryParams.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ final class PathSegmentWithQueryParams[PathType, +PathError, ParamsType, +Params
6363
def createUrl(path: PathType, params: ParamsType): (List[Segment], Map[String, Param]) =
6464
(pathSegment.createSegments(path), queryParams.createParams(params))
6565

66-
def createUrl(path: PathType)(implicit ev: Unit =:= ParamsType): (List[Segment], Map[String, Param]) =
67-
createUrl(path, ev(()))
66+
def createUrl(query: ParamsType)(implicit ev: Unit =:= PathType): (List[Segment], Map[String, Param]) =
67+
createUrl(ev(()), query)
6868

6969
def createUrlString(
7070
path: PathType,

url-dsl/src/main/scala/urldsl/url/UrlStringGenerator.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ trait UrlStringGenerator {
2222
val paramsString = makeParams(params)
2323
val pathString = makePath(segments)
2424

25-
pathString + (if (paramsString.nonEmpty) "?" else "") + pathString
25+
pathString ++ (if (paramsString.nonEmpty) "?" else "") ++ paramsString
2626
}
2727

2828
final def makeFragment(maybeFragment: MaybeFragment): String = maybeFragment.value match {

url-dsl/src/test/scala-2.13/urldsl/vocabulary/FromString213Spec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ final class FromString213Spec extends AnyFlatSpec with Matchers {
1616

1717
getTheT[Int]("123") should be (Right(123))
1818

19-
getTheT[Double]("Hi") should be (Left(DummyError.dummyError))
19+
getTheT[BigInt]("Hi") should be (Left(DummyError.dummyError))
2020

2121
}
2222

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package urldsl.vocabulary
2+
3+
final class CodecSpecFor3 extends munit.FunSuite {
4+
implicit val userCodec: Codec[(String, Int), User] = Codec.factory(
5+
(User.apply _).tupled,
6+
{ case User(s, x) =>
7+
(s, x)
8+
}
9+
)
10+
11+
implicit def swapCodec[A, B]: Codec[(A, B), (B, A)] = Codec.factory(_.swap, _.swap)
12+
13+
def useACodec[T, U](t: T)(implicit codec: Codec[T, U]): U = codec.leftToRight(t)
14+
15+
def useACodecReverse[T, U](u: U)(implicit codec: Codec[T, U]): T = codec.rightToLeft(u)
16+
17+
test("Summoner method is called") {
18+
assert(Codec[(Int, String), (String, Int)] != null)
19+
}
20+
21+
test("The implicit composed codec should be invoked") {
22+
assertEquals(useACodec[(Int, String), User]((22, "Alice")), User("Alice", 22))
23+
assertEquals(useACodecReverse[(Int, String), User](User("Alice", 22)), (22, "Alice"))
24+
}
25+
26+
test("Codec can be lifted to seq") {
27+
assertEquals(useACodec[List[(Int, String)], List[User]](List((22, "Alice"))), List(User("Alice", 22)))
28+
assertEquals(useACodecReverse[List[(Int, String)], List[User]](List(User("Alice", 22))), List((22, "Alice")))
29+
}
30+
31+
test("Zip codec can be used") {
32+
assertEquals(
33+
useACodec[(List[String], List[Int]), List[(String, Int)]]((List("hello"), List(7))),
34+
List(("hello", 7))
35+
)
36+
assertEquals(
37+
useACodecReverse[(List[String], List[Int]), List[(String, Int)]](List(("hello", 7))),
38+
(List("hello"), List(7))
39+
)
40+
}
41+
42+
}
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package urldsl.vocabulary
2+
3+
import org.scalatest.flatspec.AnyFlatSpec
4+
import org.scalatest.matchers.should.Matchers
5+
import urldsl.errors.DummyError
6+
7+
final class FromString3Spec extends AnyFlatSpec with Matchers {
8+
9+
def getTheT[T](s: String)(using fromString: FromString[T, DummyError]): Either[DummyError, T] = fromString(s)
10+
11+
"The FromString implicit for Numeric" should "be correctly called" in {
12+
13+
getTheT[BigInt]("123456789012345678901234567890") should be(
14+
Right(BigInt("123456789012345678901234567890"))
15+
)
16+
17+
getTheT[BigDecimal]("123.4") should be (Right(BigDecimal(123.4)))
18+
19+
getTheT[BigInt]("Hi") should be (Left(DummyError.dummyError))
20+
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package urldsl.language
2+
3+
import org.scalacheck.{Gen, Prop}
4+
import org.scalacheck.Prop.forAll
5+
import urldsl.errors.{DummyError, ErrorFromThrowable, FragmentMatchingError}
6+
import urldsl.vocabulary.{Codec, MaybeFragment}
7+
8+
final class FragmentDummyErrorProperties
9+
extends FragmentProperties[DummyError](
10+
Fragment.dummyErrorImpl,
11+
DummyError.dummyErrorIsFragmentMatchingError,
12+
"FragmentDummyErrorProperties"
13+
) {
14+
15+
property("Filtering non positive ints with dummy error sugar") = forAll(Gen.option(Gen.choose(-1000, 1000))) {
16+
(maybeX: Option[Int]) =>
17+
case class PosInt(value: Int)
18+
19+
implicit def codec: Codec[Int, PosInt] = Codec.factory(PosInt.apply, _.value)
20+
21+
val predicate: Int => Boolean = _ > 0
22+
23+
Prop(
24+
intFragment
25+
.filter(predicate)
26+
.as[PosInt]
27+
.?
28+
.matchFragment(MaybeFragment(maybeX.map(_.toString))) == Right(maybeX.filter(predicate).map(PosInt.apply))
29+
) && Prop(
30+
intFragment.?.createFragment(maybeX) == MaybeFragment(maybeX.map(_.toString))
31+
)
32+
}
33+
34+
}

0 commit comments

Comments
 (0)