From ccf1725a262bf8aea4500ec9a3b2da68e4a3a8c9 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 08:57:27 -0400 Subject: [PATCH 01/13] Add munit, convert core module tests --- build.sbt | 5 ++- .../chrisdavenport/cormorant/ErrorSpec.scala | 35 ++++++++++--------- .../cormorant/PrinterSpec.scala | 28 ++++++--------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/build.sbt b/build.sbt index f112e8f6..a8130ac6 100644 --- a/build.sbt +++ b/build.sbt @@ -85,6 +85,7 @@ val shapelessV = "2.3.3" val http4sV = "0.21.18" val catsScalacheckV = "0.3.0" val specs2V = "4.10.6" +val munitV = "0.7.26" lazy val core = project.in(file("modules/core")) .settings(commonSettings) @@ -190,12 +191,14 @@ lazy val docs = project.in(file("modules")) lazy val commonSettings = Seq( addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full), addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), + testFrameworks += new TestFramework("munit.Framework"), libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % catsV, "org.typelevel" %% "cats-effect" % catsEffectV, + "org.scalameta" %% "munit" % munitV % Test, "org.specs2" %% "specs2-core" % specs2V % Test, "org.specs2" %% "specs2-scalacheck" % specs2V % Test, "io.chrisdavenport" %% "cats-scalacheck" % catsScalacheckV % Test, ) -) \ No newline at end of file +) diff --git a/modules/core/src/test/scala/io/chrisdavenport/cormorant/ErrorSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/cormorant/ErrorSpec.scala index a5003e51..83bd2bac 100644 --- a/modules/core/src/test/scala/io/chrisdavenport/cormorant/ErrorSpec.scala +++ b/modules/core/src/test/scala/io/chrisdavenport/cormorant/ErrorSpec.scala @@ -1,23 +1,24 @@ package io.chrisdavenport.cormorant -class ErrorSpec extends org.specs2.mutable.Specification{ - "Error.DecodeFailure" should { - "toString should work" in { - Error.DecodeFailure.single("reason").toString() - .must_===("DecodeFailure(NonEmptyList(reason))") - } +class ErrorSpec extends munit.FunSuite { + test("Error.DecodeFailure toString should work") { + assertEquals( + Error.DecodeFailure.single("reason").toString(), + "DecodeFailure(NonEmptyList(reason))" + ) } - "Error.ParseFailure" should { - "toString should work" in { - Error.ParseFailure.invalidInput("invalid").toString() - .must_===("ParseFailure(Invalid Input: Received invalid)") - } + + test("Error.ParseFailure toString should work") { + assertEquals( + Error.ParseFailure.invalidInput("invalid").toString(), + "ParseFailure(Invalid Input: Received invalid)" + ) } - "Error.PrintFailure" should { - "toString should work" in { - Error.PrintFailure("reason").toString() - .must_===("PrintFailure(reason)") - } + test("Error.PrintFailure toString should work") { + assertEquals( + Error.PrintFailure("reason").toString(), + "PrintFailure(reason)" + ) } -} \ No newline at end of file +} diff --git a/modules/core/src/test/scala/io/chrisdavenport/cormorant/PrinterSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/cormorant/PrinterSpec.scala index 5b67aa37..c96446b3 100644 --- a/modules/core/src/test/scala/io/chrisdavenport/cormorant/PrinterSpec.scala +++ b/modules/core/src/test/scala/io/chrisdavenport/cormorant/PrinterSpec.scala @@ -1,16 +1,10 @@ package io.chrisdavenport.cormorant -import org.specs2._ import _root_.cats.data._ -object PrinterSpec extends Specification { - override def is = s2""" - Print a simple csv $simpleCSVPrint - Printer field with a surrounded field $fieldSurroundedCorrectly - Printer field with escaped field $fieldEscapedCorrectly - """ +class PrinterSpec extends munit.FunSuite { - def simpleCSVPrint = { + test("Print a simple csv") { val csv = CSV.Complete( CSV.Headers( NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) @@ -28,22 +22,20 @@ object PrinterSpec extends Specification { |Red,Margarine,2 |Yellow,Broccoli,3""".stripMargin - Printer.default.print(csv) should_=== expectedCSVString + assertEquals(Printer.default.print(csv), expectedCSVString) } - - def fieldSurroundedCorrectly = { + test("Printer field with a surrounded field") { val csv = CSV.Field("Snow, John") val expectedCSVString = "\"Snow, John\"" - - Printer.default.print(csv) should_=== expectedCSVString + + assertEquals(Printer.default.print(csv), expectedCSVString) } - def fieldEscapedCorrectly = { + test("Printer field with escaped field") { val csv = CSV.Field("Snow, \"John\"") val expectedCSVString = "\"Snow, \"\"John\"\"\"" - - Printer.default.print(csv) should_=== expectedCSVString - } -} \ No newline at end of file + assertEquals(Printer.default.print(csv), expectedCSVString) + } +} From 2163c0a5130dd91213dc2a97661b176f40dc2466 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 09:35:02 -0400 Subject: [PATCH 02/13] Add munit-cats-effect, convert StreamingParserSpec --- build.sbt | 2 + .../cormorant/fs2/StreamingParserSpec.scala | 54 +++++++++---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/build.sbt b/build.sbt index a8130ac6..6a776c19 100644 --- a/build.sbt +++ b/build.sbt @@ -86,6 +86,7 @@ val http4sV = "0.21.18" val catsScalacheckV = "0.3.0" val specs2V = "4.10.6" val munitV = "0.7.26" +val munitCatsEffectV = "1.0.3" lazy val core = project.in(file("modules/core")) .settings(commonSettings) @@ -197,6 +198,7 @@ lazy val commonSettings = Seq( "org.typelevel" %% "cats-core" % catsV, "org.typelevel" %% "cats-effect" % catsEffectV, "org.scalameta" %% "munit" % munitV % Test, + "org.typelevel" %% "munit-cats-effect-2" % munitCatsEffectV % Test, "org.specs2" %% "specs2-core" % specs2V % Test, "org.specs2" %% "specs2-scalacheck" % specs2V % Test, "io.chrisdavenport" %% "cats-scalacheck" % catsScalacheckV % Test, diff --git a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingParserSpec.scala b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingParserSpec.scala index 3ec70c27..bb060c62 100644 --- a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingParserSpec.scala +++ b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingParserSpec.scala @@ -3,49 +3,45 @@ package fs2 import cats.data.NonEmptyList import cats.effect._ -import cats.effect.testing.specs2.CatsIO +import munit.CatsEffectSuite import _root_.fs2.Stream import io.chrisdavenport.cormorant._ -// import io.chrisdavenport.cormorant.implicits._ -// import scala.concurrent.duration._ import java.io.ByteArrayInputStream import java.io.InputStream -class StreamingParserSpec extends CormorantSpec with CatsIO { +class StreamingParserSpec extends CatsEffectSuite { def ruinDelims(str: String) = augmentString(str).flatMap { case '\n' => "\r\n" case c => c.toString } - "Streaming Parser" should { - // https://github.com/ChristopherDavenport/cormorant/pull/84 - "parse a known value that did not work with streaming" in { - val x = """First Name,Last Name,Email + // https://github.com/ChristopherDavenport/cormorant/pull/84 + test("Streaming Parser parses a known value that did not work with streaming") { + val x = """First Name,Last Name,Email Larry,Bordowitz,larry@example.com Anonymous,Hippopotamus,hippo@example.com""" - val source = IO.pure(new ByteArrayInputStream(ruinDelims(x).getBytes): InputStream) - Stream.resource(Blocker[IO]).flatMap{blocker => - _root_.fs2.io.readInputStream( - source, - chunkSize = 4, - blocker - ) - } - .through(_root_.fs2.text.utf8Decode) - .through(parseComplete[IO]) - .compile - .toVector - .map{ v => - val header = CSV.Headers(NonEmptyList.of(CSV.Header("First Name"), CSV.Header("Last Name"), CSV.Header("Email"))) - val row1 = CSV.Row(NonEmptyList.of(CSV.Field("Larry"), CSV.Field("Bordowitz"), CSV.Field("larry@example.com"))) - val row2 = CSV.Row(NonEmptyList.of(CSV.Field("Anonymous"), CSV.Field("Hippopotamus"), CSV.Field("hippo@example.com"))) - Vector( - (header, row1), - (header, row2) - ) must_=== v - } + val source = IO.pure(new ByteArrayInputStream(ruinDelims(x).getBytes): InputStream) + Stream.resource(Blocker[IO]).flatMap{blocker => + _root_.fs2.io.readInputStream( + source, + chunkSize = 4, + blocker + ) } + .through(_root_.fs2.text.utf8Decode) + .through(parseComplete[IO]) + .compile + .toVector + .map{ v => + val header = CSV.Headers(NonEmptyList.of(CSV.Header("First Name"), CSV.Header("Last Name"), CSV.Header("Email"))) + val row1 = CSV.Row(NonEmptyList.of(CSV.Field("Larry"), CSV.Field("Bordowitz"), CSV.Field("larry@example.com"))) + val row2 = CSV.Row(NonEmptyList.of(CSV.Field("Anonymous"), CSV.Field("Hippopotamus"), CSV.Field("hippo@example.com"))) + assertEquals(Vector( + (header, row1), + (header, row2) + ), v) + } } From 69190135911c6021cdc933dd9f5e85f93d848745 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 11:06:37 -0400 Subject: [PATCH 03/13] Add scalacheck-effect, StreamingPrinterSpec WIP --- build.sbt | 3 ++ .../cormorant/fs2/StreamingPrinterSpec.scala | 49 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/build.sbt b/build.sbt index 6a776c19..99df9a31 100644 --- a/build.sbt +++ b/build.sbt @@ -87,6 +87,7 @@ val catsScalacheckV = "0.3.0" val specs2V = "4.10.6" val munitV = "0.7.26" val munitCatsEffectV = "1.0.3" +val scalacheckEffectV = "1.0.2" lazy val core = project.in(file("modules/core")) .settings(commonSettings) @@ -198,7 +199,9 @@ lazy val commonSettings = Seq( "org.typelevel" %% "cats-core" % catsV, "org.typelevel" %% "cats-effect" % catsEffectV, "org.scalameta" %% "munit" % munitV % Test, + "org.scalameta" %% "munit-scalacheck" % munitV % Test, "org.typelevel" %% "munit-cats-effect-2" % munitCatsEffectV % Test, + "org.typelevel" %% "scalacheck-effect-munit" % scalacheckEffectV % Test, "org.specs2" %% "specs2-core" % specs2V % Test, "org.specs2" %% "specs2-scalacheck" % specs2V % Test, "io.chrisdavenport" %% "cats-scalacheck" % catsScalacheckV % Test, diff --git a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala index 8f960d85..860d3131 100644 --- a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala +++ b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala @@ -8,35 +8,42 @@ import _root_.fs2.Stream import io.chrisdavenport.cormorant._ import io.chrisdavenport.cormorant.implicits._ import scala.concurrent.duration._ +import munit.CatsEffectSuite +import munit.ScalaCheckEffectSuite +import org.scalacheck.effect.PropF -class StreamingPrinterSpec extends CormorantSpec with CatsIO { - - override val Timeout = 1.minute +class StreamingPrinterSuite extends CatsEffectSuite with ScalaCheckEffectSuite with CormorantArbitraries { - "Streaming printer should" in { - - "row should round trip" in prop { a: CSV.Row => + test("Streaming printer row should round trip") { + PropF.forAllF { (a: CSV.Row) => Stream .emit[IO, CSV.Row](a) .through(encodeRows(Printer.default)) .through(parseRows) .compile .toList - .unsafeRunSync() must_=== List(a) - }//.set(minTestsOk = 20, workers = 2) - - "rows should round trip" in prop { a: CSV.Rows => - val decoded = CSV.Rows( - Stream - .emits[IO, CSV.Row](a.rows) - .through(encodeRows(Printer.default)) - .through(parseRows) - .compile - .toList - .unsafeRunSync() - ) - decoded must_=== a - }//.set(minTestsOk = 20, workers = 2) + .map(r => assertEquals(r, List(a))) + } + } + + test("Streaming printer rows should round trip") { + PropF.forAllF { (a: CSV.Rows) => + Stream + .emits[IO, CSV.Row](a.rows) + .through(encodeRows(Printer.default)) + .through(parseRows) + .compile + .toList + .map(CSV.Rows) + .map(r => assertEquals(r, a)) + } + } +} +class StreamingPrinterSpec extends CormorantSpec with CatsIO { + + override val Timeout = 1.minute + + "Streaming printer should" in { "rows special case for empty removal" in { import CSV._ From a39c6c72941ab419a8e71e5d8cc5e42c77ed4053 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 12:25:21 -0400 Subject: [PATCH 04/13] Convert StreamingPrinterSpec to munit --- .../cormorant/fs2/StreamingPrinterSpec.scala | 105 +++++++++--------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala index 860d3131..f09cf4ae 100644 --- a/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala +++ b/modules/fs2/src/test/scala/io/chrisdavenport/cormorant/fs2/StreamingPrinterSpec.scala @@ -3,16 +3,17 @@ package fs2 import cats.data.NonEmptyList import cats.effect._ -import cats.effect.testing.specs2.CatsIO import _root_.fs2.Stream import io.chrisdavenport.cormorant._ import io.chrisdavenport.cormorant.implicits._ -import scala.concurrent.duration._ import munit.CatsEffectSuite import munit.ScalaCheckEffectSuite import org.scalacheck.effect.PropF -class StreamingPrinterSuite extends CatsEffectSuite with ScalaCheckEffectSuite with CormorantArbitraries { +class StreamingPrinterSuite + extends CatsEffectSuite + with ScalaCheckEffectSuite + with CormorantArbitraries { test("Streaming printer row should round trip") { PropF.forAllF { (a: CSV.Row) => @@ -38,78 +39,78 @@ class StreamingPrinterSuite extends CatsEffectSuite with ScalaCheckEffectSuite w .map(r => assertEquals(r, a)) } } -} -class StreamingPrinterSpec extends CormorantSpec with CatsIO { - - override val Timeout = 1.minute - - "Streaming printer should" in { - "rows special case for empty removal" in { - import CSV._ + test("Streaming printer rows special case for empty removal") { + import CSV._ - val rows = Rows( - List( - Row(NonEmptyList.of(Field(""))), - // Row(NonEmptyList.of(Field(""))) - ) + val rows = Rows( + List( + Row(NonEmptyList.of(Field(""))) + // Row(NonEmptyList.of(Field(""))) ) - val expected = List.empty[CSV.Row] + ) + val expected = List.empty[CSV.Row] + + Stream + .emits[IO, CSV.Row](rows.rows) + .through(encodeRows(Printer.default)) + .through(parseRows) + .compile + .toList + .map(assertEquals(_, expected)) + } - Stream - .emits[IO, CSV.Row](rows.rows) - .through(encodeRows(Printer.default)) - .through(parseRows) - .compile - .toList - .unsafeRunSync() must_=== expected - } + test("Streaming printer should complete should write as expected") { + final case class Foo(color: String, food: String, number: Int) - "complete should write as expected" in { - final case class Foo(color: String, food: String, number: Int) + val list = List( + Foo("Blue", "Pizza", 1), + Foo("Red", "Margarine", 2), + Foo("Yellow", "Broccoli", 3) + ) - val list = List( - Foo("Blue", "Pizza", 1), - Foo("Red", "Margarine", 2), - Foo("Yellow", "Broccoli", 3) - ) - - implicit val L: LabelledWrite[Foo] = new LabelledWrite[Foo] { - override def headers: CSV.Headers = CSV.Headers( + implicit val L: LabelledWrite[Foo] = new LabelledWrite[Foo] { + override def headers: CSV.Headers = + CSV.Headers( NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) ) - override def write(a: Foo): CSV.Row = CSV.Row( + override def write(a: Foo): CSV.Row = + CSV.Row( NonEmptyList.of(a.color.field, a.food.field, a.number.field) ) - } + } - val result = Stream.emits(list) - .through(writeLabelled(Printer.default)) - .compile - .string + val result = Stream + .emits(list) + .through(writeLabelled(Printer.default)) + .compile + .string - val expectedCSVString = """Color,Food,Number + val expectedCSVString = """Color,Food,Number |Blue,Pizza,1 |Red,Margarine,2 |Yellow,Broccoli,3""".stripMargin - result should_=== expectedCSVString - } - + assertEquals(result, expectedCSVString) + } - "complete should round trip with streaming encoder" in prop { csv: CSV.Complete => + test("Streaming printer should round trip with streaming encoder") { + PropF.forAllF { (csv: CSV.Complete) => val expected = csv.rows.rows.map(row => (csv.headers, row)) - Stream.emits(csv.rows.rows) + Stream + .emits(csv.rows.rows) .through(encodeWithHeaders(csv.headers, Printer.default)) .covary[IO] .through(parseComplete) .compile .toList - .map(_ must_=== expected) + .map(assertEquals(_, expected)) } + } - "complete should round trip with printer" in prop { csv: CSV.Complete => + test("Streaming printer should round trip with printer") { + PropF.forAllF { (csv: CSV.Complete) => val output = Printer.default.print(csv) val expected = csv.rows.rows.map(row => (csv.headers, row)) Stream(output) @@ -117,9 +118,7 @@ class StreamingPrinterSpec extends CormorantSpec with CatsIO { .through(parseComplete) .compile .toList - .map(_ must_=== expected) + .map(assertEquals(_, expected)) } - } - -} \ No newline at end of file +} From bd29bfec796609ffdf26f51bcfec4c257d6a415e Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 12:31:20 -0400 Subject: [PATCH 05/13] Remove specs dep in fs2 module --- build.sbt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 99df9a31..3c217b2b 100644 --- a/build.sbt +++ b/build.sbt @@ -132,8 +132,7 @@ lazy val fs2 = project.in(file("modules/fs2")) name := "cormorant-fs2", libraryDependencies ++= Seq( "co.fs2" %% "fs2-core" % "2.4.6", - "co.fs2" %% "fs2-io" % "2.4.6" % Test, - "com.codecommit" %% "cats-effect-testing-specs2" % catsEffectTestV % Test + "co.fs2" %% "fs2-io" % "2.4.6" % Test ) ) From 306f421cd89e388368bc4c0a1e98878f6886eb38 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Fri, 28 May 2021 15:25:51 -0400 Subject: [PATCH 06/13] Convert Http4sSpec to munit --- .../cormorant/http4s/Http4sSpec.scala | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/modules/http4s/src/test/scala/io/chrisdavenport/cormorant/http4s/Http4sSpec.scala b/modules/http4s/src/test/scala/io/chrisdavenport/cormorant/http4s/Http4sSpec.scala index c91b9db7..fdff2777 100644 --- a/modules/http4s/src/test/scala/io/chrisdavenport/cormorant/http4s/Http4sSpec.scala +++ b/modules/http4s/src/test/scala/io/chrisdavenport/cormorant/http4s/Http4sSpec.scala @@ -6,23 +6,41 @@ import org.http4s._ import org.http4s.client._ import org.http4s.dsl.io._ import org.http4s.implicits._ +import munit.CatsEffectSuite +import munit.ScalaCheckEffectSuite +import org.scalacheck.effect.PropF +import org.scalacheck.Test.Parameters -class Http4sSpec extends CormorantSpec { - "Http4s Entity Encoder/Decoder" should { - "round trip rows" in prop { rows: CSV.Rows => - val service = HttpRoutes.of[IO] { - case _ => Ok(rows) - } - val client = Client.fromHttpApp(service.orNotFound) - client.expect[CSV.Rows]("").unsafeRunSync() must_=== rows - }.set(minTestsOk = 20, workers = 2) - "round trip complete" in prop { rows: CSV.Complete => - val service = HttpRoutes.of[IO] { - case _ => Ok(rows) +class StreamingPrinterSuite + extends CatsEffectSuite + with ScalaCheckEffectSuite + with CormorantArbitraries { + + val minTestsOK = Parameters.default + .withMinSuccessfulTests(20) + .withWorkers(2) + + test("Http4s Entity Encoder/Decoder round trip rows") { + PropF + .forAllF { (rows: CSV.Rows) => + val service = HttpRoutes.of[IO] { + case _ => Ok(rows) + } + val client = Client.fromHttpApp(service.orNotFound) + client.expect[CSV.Rows]("").map(assertEquals(_, rows)) } - val client = Client.fromHttpApp(service.orNotFound) - client.expect[CSV.Complete]("").unsafeRunSync() must_=== rows - }.set(minTestsOk = 20, workers = 2) + .check(minTestsOK) } + test("Http4s Entity Encoder/Decoder round trip complete") { + PropF + .forAllF { (rows: CSV.Complete) => + val service = HttpRoutes.of[IO] { + case _ => Ok(rows) + } + val client = Client.fromHttpApp(service.orNotFound) + client.expect[CSV.Complete]("").map(assertEquals(_, rows)) + } + .check(minTestsOK) + } } From 9c87f5e2aa67256372f5998f69f6aae0e8e16af9 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 07:49:10 -0400 Subject: [PATCH 07/13] Convert parser tests to munit --- .../cormorant/parser/CSVParserSpecs.scala | 289 +++++++++--------- .../parser/PrinterParserParity.scala | 67 ++-- .../cormorant/parser/TSVParserSpecs.scala | 273 ++++++++--------- 3 files changed, 315 insertions(+), 314 deletions(-) diff --git a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/CSVParserSpecs.scala b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/CSVParserSpecs.scala index 8e712d5d..1af31690 100644 --- a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/CSVParserSpecs.scala +++ b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/CSVParserSpecs.scala @@ -1,171 +1,166 @@ package io.chrisdavenport.cormorant.parser -import org.specs2._ import _root_.io.chrisdavenport.cormorant._ import atto._ import Atto._ import cats.implicits._ import _root_.cats.data._ -class CSVParserSpec extends mutable.Specification { - // override def is = s2""" - // Parse a simple csv header $parseASimpleCSVHeader - // """ - "CSVParser" should { - "parse a single header" in { - val basicString = "Something," - val expect = CSV.Header("Something") - CSVParser.name.parse(basicString).done must_=== ParseResult.Done(",", expect) - } - - "parse first header in a header list" in { - val baseHeader = "Something,Something2,Something3" - val expect = CSV.Header("Something") - - CSVParser.name.parse(baseHeader) must_=== ParseResult.Done(",Something2,Something3", expect) - } - - "parse a group of headers" in { - val baseHeader = "Something,Something2,Something3" - val expect = List( +class CSVParserSpec extends munit.FunSuite { + + test("parse a single header") { + val basicString = "Something," + val expect = CSV.Header("Something") + assertEquals(CSVParser.name.parse(basicString).done, ParseResult.Done(",", expect)) + } + + test("parse first header in a header list") { + val baseHeader = "Something,Something2,Something3" + val expect = CSV.Header("Something") + + assertEquals(CSVParser.name.parse(baseHeader), ParseResult.Done(",Something2,Something3", expect)) + } + + test("parse a group of headers") { + val baseHeader = "Something,Something2,Something3" + val expect = List( + CSV.Header("Something"), + CSV.Header("Something2"), + CSV.Header("Something3") + ) + val result = (CSVParser.name, many(CSVParser.SEPARATOR ~> CSVParser.name)).mapN(_ :: _).parse(baseHeader).done + assertEquals(result, ParseResult.Done("", expect)) + } + + test("parse headers correctly") { + val baseHeader = """Something,Something2,Something3""" + val expect = CSV.Headers( + NonEmptyList.of( CSV.Header("Something"), CSV.Header("Something2"), CSV.Header("Something3") ) - val result = (CSVParser.name, many(CSVParser.SEPARATOR ~> CSVParser.name)).mapN(_ :: _).parse(baseHeader).done - result must_=== ParseResult.Done("", expect) - } - - "parse headers correctly" in { - val baseHeader = """Something,Something2,Something3""" - val expect = CSV.Headers( - NonEmptyList.of( - CSV.Header("Something"), - CSV.Header("Something2"), - CSV.Header("Something3") - ) - ) - val result = CSVParser.header.parse(baseHeader).done - - result must_== ParseResult.Done("", expect) - } - - "parse a row correctly" in { - val singleRow = "yellow,green,blue" - val expected = CSV.Row( - NonEmptyList.of( - CSV.Field("yellow"), - CSV.Field("green"), - CSV.Field("blue") - ) + ) + val result = CSVParser.header.parse(baseHeader).done + + assertEquals(result, ParseResult.Done("", expect)) + } + + test("parse a row correctly") { + val singleRow = "yellow,green,blue" + val expected = CSV.Row( + NonEmptyList.of( + CSV.Field("yellow"), + CSV.Field("green"), + CSV.Field("blue") ) + ) + + assertEquals(CSVParser.record.parse(singleRow).done.either, Right(expected)) + } - CSVParser.record.parse(singleRow).done.either must_=== Right(expected) - } + test("parse rows correctly") { + val csv = CSV.Rows( + List( + CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))) + ) + ) + val csvParse = """Blue,Pizza,1 + |Red,Margarine,2""".stripMargin + assertEquals(CSVParser.fileBody.parse(csvParse).done.either, Either.right(csv)) + } - "parse rows correctly" in { - val csv = CSV.Rows( + test("complete a csv parse") { + val csv = CSV.Complete( + CSV.Headers( + NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) + ), + CSV.Rows( List( CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))) - ) - ) - val csvParse = """Blue,Pizza,1 - |Red,Margarine,2""".stripMargin - CSVParser.fileBody.parse(csvParse).done.either must_=== Either.right(csv) - } - - "complete a csv parse" in { - val csv = CSV.Complete( - CSV.Headers( - NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) - ), - CSV.Rows( - List( - CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), - CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) - ) + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), + CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) ) ) - val expectedCSVString = """Color,Food,Number - |Blue,Pizza,1 - |Red,Margarine,2 - |Yellow,Broccoli,3""".stripMargin - - CSVParser.`complete-file` - .parse(expectedCSVString) - .done - .either must_=== Either.right(csv) - } - - "parse a complete csv with a trailing new line by stripping it" in { - val csv = CSV.Complete( - CSV.Headers( - NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) - ), - CSV.Rows( - List( - CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), - CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) - ) + ) + val expectedCSVString = """Color,Food,Number + |Blue,Pizza,1 + |Red,Margarine,2 + |Yellow,Broccoli,3""".stripMargin + + assertEquals(CSVParser.`complete-file` + .parse(expectedCSVString) + .done + .either, Either.right(csv)) + } + + test("parse a complete csv with a trailing new line by stripping it") { + val csv = CSV.Complete( + CSV.Headers( + NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) + ), + CSV.Rows( + List( + CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), + CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) ) ) - val expectedCSVString = """Color,Food,Number - |Blue,Pizza,1 - |Red,Margarine,2 - |Yellow,Broccoli,3 - |""".stripMargin - - CSVParser.`complete-file` - .parse(expectedCSVString) - .done - .either - .map(_.stripTrailingRow) must_=== Either.right(csv) - } - - "parse an escaped row with a comma" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow,Dog"), - CSV.Field("Blue") - )) - val parseString = "Green,\"Yellow,Dog\",Blue" - CSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - "parse an escaped row with a double quote escaped" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow, \"Dog\""), - CSV.Field("Blue") - )) - val parseString = "Green,\"Yellow, \"\"Dog\"\"\",Blue" - CSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - - - "parse an escaped row with embedded newline" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\n Dog"), - CSV.Field("Blue") - )) - val parseString = "Green,\"Yellow\n Dog\",Blue" - CSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - "parse an escaped row with embedded CRLF" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\r\n Dog"), - CSV.Field("Blue") - )) - val parseString = "Green,\"Yellow\r\n Dog\",Blue" - CSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } + ) + val expectedCSVString = """Color,Food,Number + |Blue,Pizza,1 + |Red,Margarine,2 + |Yellow,Broccoli,3 + |""".stripMargin + + assertEquals(CSVParser.`complete-file` + .parse(expectedCSVString) + .done + .either + .map(_.stripTrailingRow), Either.right(csv)) + } + test("parse an escaped row with a comma") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow,Dog"), + CSV.Field("Blue") + )) + val parseString = "Green,\"Yellow,Dog\",Blue" + assertEquals(CSVParser.record.parse(parseString).done.either, Either.right(csv)) } + + test("parse an escaped row with a double quote escaped") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow, \"Dog\""), + CSV.Field("Blue") + )) + val parseString = "Green,\"Yellow, \"\"Dog\"\"\",Blue" + assertEquals(CSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + + + + test("parse an escaped row with embedded newline") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\n Dog"), + CSV.Field("Blue") + )) + val parseString = "Green,\"Yellow\n Dog\",Blue" + assertEquals(CSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + + test("parse an escaped row with embedded CRLF") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\r\n Dog"), + CSV.Field("Blue") + )) + val parseString = "Green,\"Yellow\r\n Dog\",Blue" + assertEquals(CSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + } \ No newline at end of file diff --git a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/PrinterParserParity.scala b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/PrinterParserParity.scala index 96675939..6d920578 100644 --- a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/PrinterParserParity.scala +++ b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/PrinterParserParity.scala @@ -2,46 +2,57 @@ package io.chrisdavenport.cormorant.parser import cats.implicits._ import io.chrisdavenport.cormorant._ +import _root_.io.chrisdavenport.cormorant.implicits._ +import munit.ScalaCheckSuite +import org.scalacheck.Test.Parameters +import org.scalacheck.Prop._ -class PrinterParserParity extends CormorantSpec { +class PrinterParserParity extends ScalaCheckSuite with CormorantArbitraries { - "Printer should round trip with parser" in { - "field should round trip" in prop { a : CSV.Field => - import _root_.io.chrisdavenport.cormorant.implicits._ - val encoded = a.print(Printer.default) - parseField(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + val minTestsOK = Parameters.default + .withMinSuccessfulTests(20) + .withWorkers(2) - "row should round trip" in prop { a: CSV.Row => - import _root_.io.chrisdavenport.cormorant.implicits._ + property("field should round trip") { + forAll { a: CSV.Field => val encoded = a.print(Printer.default) - parseRow(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + assertEquals(parseField(encoded), Either.right(a)) + } + }.check(minTestsOK) - "rows should round trip" in prop { a: CSV.Rows => - import _root_.io.chrisdavenport.cormorant.implicits._ + property("row should round trip") { + forAll { a: CSV.Row => val encoded = a.print(Printer.default) - parseRows(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + assertEquals(parseRow(encoded), Either.right(a)) + } + }.check(minTestsOK) - "header should round trip" in prop {a: CSV.Header => - import _root_.io.chrisdavenport.cormorant.implicits._ + property("rows should round trip") { + forAll { a: CSV.Rows => val encoded = a.print(Printer.default) - parseHeader(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + assertEquals(parseRows(encoded), Either.right(a)) + } + }.check(minTestsOK) - "headers should round trip" in prop {a: CSV.Headers => - import _root_.io.chrisdavenport.cormorant.implicits._ + property("header should round trip") { + forAll { a: CSV.Header => val encoded = a.print(Printer.default) - parseHeaders(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + assertEquals(parseHeader(encoded), Either.right(a)) + } + }.check(minTestsOK) - "complete should round trip" in prop {a: CSV.Complete => - import _root_.io.chrisdavenport.cormorant.implicits._ + property("headers should round trip") { + forAll { a: CSV.Headers => val encoded = a.print(Printer.default) - parseComplete(encoded) must_=== Either.right(a) - }.set(minTestsOk = 20, workers = 2) + assertEquals(parseHeaders(encoded), Either.right(a)) + } + }.check(minTestsOK) - } + property("complete should round trip") { + forAll { a: CSV.Complete => + val encoded = a.print(Printer.default) + assertEquals(parseComplete(encoded), Either.right(a)) + } + }.check(minTestsOK) } diff --git a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/TSVParserSpecs.scala b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/TSVParserSpecs.scala index 726a7fc4..91e3e181 100644 --- a/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/TSVParserSpecs.scala +++ b/modules/parser/src/test/scala/io/chrisdavenport/cormorant/parser/TSVParserSpecs.scala @@ -1,165 +1,160 @@ package io.chrisdavenport.cormorant.parser -import org.specs2._ import _root_.io.chrisdavenport.cormorant._ import atto._ import Atto._ import cats.implicits._ import _root_.cats.data._ -class TSVParserSpec extends mutable.Specification { - // override def is = s2""" - // Parse a simple csv header $parseASimpleCSVHeader - // """ - "TSVParser" should { - "parse a single header" in { - val basicString = "Something\t" - val expect = CSV.Header("Something") - TSVParser.name.parse(basicString).done must_=== ParseResult.Done("\t", expect) - } - - "parse first header in a header list" in { - val baseHeader = "Something\tSomething2\tSomething3" - val expect = CSV.Header("Something") - - TSVParser.name.parse(baseHeader) must_=== ParseResult.Done("\tSomething2\tSomething3", expect) - } - - "parse a group of headers" in { - val baseHeader = "Something\tSomething2\tSomething3" - val expect = List( +class TSVParserSpec extends munit.FunSuite { + + test("parse a single header") { + val basicString = "Something\t" + val expect = CSV.Header("Something") + assertEquals(TSVParser.name.parse(basicString).done, ParseResult.Done("\t", expect)) + } + + test("parse first header in a header list") { + val baseHeader = "Something\tSomething2\tSomething3" + val expect = CSV.Header("Something") + + assertEquals(TSVParser.name.parse(baseHeader), ParseResult.Done("\tSomething2\tSomething3", expect)) + } + + test("parse a group of headers") { + val baseHeader = "Something\tSomething2\tSomething3" + val expect = List( + CSV.Header("Something"), + CSV.Header("Something2"), + CSV.Header("Something3") + ) + val result = (TSVParser.name, many(TSVParser.SEPARATOR ~> TSVParser.name)).mapN(_ :: _).parse(baseHeader).done + assertEquals(result, ParseResult.Done("", expect)) + } + + test("parse headers correctly") { + val baseHeader = "Something\tSomething2\tSomething3" + val expect = CSV.Headers( + NonEmptyList.of( CSV.Header("Something"), CSV.Header("Something2"), CSV.Header("Something3") ) - val result = (TSVParser.name, many(TSVParser.SEPARATOR ~> TSVParser.name)).mapN(_ :: _).parse(baseHeader).done - result must_=== ParseResult.Done("", expect) - } - - "parse headers correctly" in { - val baseHeader = "Something\tSomething2\tSomething3" - val expect = CSV.Headers( - NonEmptyList.of( - CSV.Header("Something"), - CSV.Header("Something2"), - CSV.Header("Something3") - ) - ) - val result = TSVParser.header.parse(baseHeader).done - - result must_== ParseResult.Done("", expect) - } - - "parse a row correctly" in { - val singleRow = "yellow\tgreen\tblue" - val expected = CSV.Row( - NonEmptyList.of( - CSV.Field("yellow"), - CSV.Field("green"), - CSV.Field("blue") - ) + ) + val result = TSVParser.header.parse(baseHeader).done + + assertEquals(result, ParseResult.Done("", expect)) + } + + test("parse a row correctly") { + val singleRow = "yellow\tgreen\tblue" + val expected = CSV.Row( + NonEmptyList.of( + CSV.Field("yellow"), + CSV.Field("green"), + CSV.Field("blue") ) + ) + + assertEquals(TSVParser.record.parse(singleRow).done.either, Right(expected)) + } - TSVParser.record.parse(singleRow).done.either must_=== Right(expected) - } + test("parse rows correctly") { + val csv = CSV.Rows( + List( + CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))) + ) + ) + val csvParse = "Blue\tPizza\t1\nRed\tMargarine\t2" + assertEquals(TSVParser.fileBody.parse(csvParse).done.either, Either.right(csv)) + } - "parse rows correctly" in { - val csv = CSV.Rows( + test("complete a csv parse") { + val csv = CSV.Complete( + CSV.Headers( + NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) + ), + CSV.Rows( List( CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))) - ) - ) - val csvParse = "Blue\tPizza\t1\nRed\tMargarine\t2" - TSVParser.fileBody.parse(csvParse).done.either must_=== Either.right(csv) - } - - "complete a csv parse" in { - val csv = CSV.Complete( - CSV.Headers( - NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) - ), - CSV.Rows( - List( - CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), - CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) - ) + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), + CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) ) ) - val expectedCSVString = - "Color\tFood\tNumber\nBlue\tPizza\t1\nRed\tMargarine\t2\nYellow\tBroccoli\t3" - - TSVParser.`complete-file` - .parse(expectedCSVString) - .done - .either must_=== Either.right(csv) - } - - "parse a complete csv with a trailing new line by stripping it" in { - val csv = CSV.Complete( - CSV.Headers( - NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) - ), - CSV.Rows( - List( - CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), - CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), - CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) - ) + ) + val expectedCSVString = + "Color\tFood\tNumber\nBlue\tPizza\t1\nRed\tMargarine\t2\nYellow\tBroccoli\t3" + + assertEquals(TSVParser.`complete-file` + .parse(expectedCSVString) + .done + .either, Either.right(csv)) + } + + test("parse a complete csv with a trailing new line by stripping it") { + val csv = CSV.Complete( + CSV.Headers( + NonEmptyList.of(CSV.Header("Color"), CSV.Header("Food"), CSV.Header("Number")) + ), + CSV.Rows( + List( + CSV.Row(NonEmptyList.of(CSV.Field("Blue"), CSV.Field("Pizza"), CSV.Field("1"))), + CSV.Row(NonEmptyList.of(CSV.Field("Red"), CSV.Field("Margarine"), CSV.Field("2"))), + CSV.Row(NonEmptyList.of(CSV.Field("Yellow"), CSV.Field("Broccoli"), CSV.Field("3"))) ) ) - val expectedCSVString = + ) + val expectedCSVString = "Color\tFood\tNumber\nBlue\tPizza\t1\nRed\tMargarine\t2\nYellow\tBroccoli\t3\n" - TSVParser.`complete-file` - .parse(expectedCSVString) - .done - .either - .map(_.stripTrailingRow) must_=== Either.right(csv) - } - - "parse an escaped row with a tab" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\tDog"), - CSV.Field("Blue") - )) - val parseString = "Green\t\"Yellow\tDog\"\tBlue" - TSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - "parse an escaped row with a double quote escaped" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\t \"Dog\""), - CSV.Field("Blue") - )) - val parseString = "Green\t\"Yellow\t \"\"Dog\"\"\"\tBlue" - TSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - - - "parse an escaped row with embedded newline" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\n Dog"), - CSV.Field("Blue") - )) - val parseString = "Green\t\"Yellow\n Dog\"\tBlue" - TSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } - - "parse an escaped row with embedded CRLF" in { - val csv = CSV.Row(NonEmptyList.of( - CSV.Field("Green"), - CSV.Field("Yellow\r\n Dog"), - CSV.Field("Blue") - )) - val parseString = "Green\t\"Yellow\r\n Dog\"\tBlue" - TSVParser.record.parse(parseString).done.either must_=== Either.right(csv) - } + assertEquals(TSVParser.`complete-file` + .parse(expectedCSVString) + .done + .either + .map(_.stripTrailingRow) , Either.right(csv)) + } + test("parse an escaped row with a tab") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\tDog"), + CSV.Field("Blue") + )) + val parseString = "Green\t\"Yellow\tDog\"\tBlue" + assertEquals(TSVParser.record.parse(parseString).done.either, Either.right(csv)) } + + test("parse an escaped row with a double quote escaped") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\t \"Dog\""), + CSV.Field("Blue") + )) + val parseString = "Green\t\"Yellow\t \"\"Dog\"\"\"\tBlue" + assertEquals(TSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + + + + test("parse an escaped row with embedded newline") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\n Dog"), + CSV.Field("Blue") + )) + val parseString = "Green\t\"Yellow\n Dog\"\tBlue" + assertEquals(TSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + + test("parse an escaped row with embedded CRLF") { + val csv = CSV.Row(NonEmptyList.of( + CSV.Field("Green"), + CSV.Field("Yellow\r\n Dog"), + CSV.Field("Blue") + )) + val parseString = "Green\t\"Yellow\r\n Dog\"\tBlue" + assertEquals(TSVParser.record.parse(parseString).done.either, Either.right(csv)) + } + } \ No newline at end of file From b16073c91b7c547508664083de66ac4741ba7d96 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 07:52:46 -0400 Subject: [PATCH 08/13] Convert refined tests to munit --- .../cormorant/refined/RefinedSpec.scala | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/modules/refined/src/test/scala/io/chrisdavenport/cormorant/refined/RefinedSpec.scala b/modules/refined/src/test/scala/io/chrisdavenport/cormorant/refined/RefinedSpec.scala index f254bda8..e9509c0d 100644 --- a/modules/refined/src/test/scala/io/chrisdavenport/cormorant/refined/RefinedSpec.scala +++ b/modules/refined/src/test/scala/io/chrisdavenport/cormorant/refined/RefinedSpec.scala @@ -1,45 +1,42 @@ package io.chrisdavenport.cormorant.refined -import org.specs2._ -class RefinedSpec extends mutable.Specification { - "refined module" should { - "be able to derive a put for a class" in { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ +class RefinedSpec extends munit.FunSuite { + test("be able to derive a put for a class") { + import _root_.io.chrisdavenport.cormorant._ + import _root_.io.chrisdavenport.cormorant.implicits._ - import eu.timepit.refined._ - import eu.timepit.refined.api.Refined - import eu.timepit.refined.collection.NonEmpty - // import eu.timepit.refined.auto._ - // import eu.timepit.refined.numeric._ + import eu.timepit.refined._ + import eu.timepit.refined.api.Refined + import eu.timepit.refined.collection.NonEmpty + // import eu.timepit.refined.auto._ + // import eu.timepit.refined.numeric._ - // import eu.timepit.refined.boolean._ - // import eu.timepit.refined.char._ - // import eu.timepit.refined.collection._ - // import eu.timepit.refined.generic._ - // import eu.timepit.refined.string._ - // import shapeless.{ ::, HNil } + // import eu.timepit.refined.boolean._ + // import eu.timepit.refined.char._ + // import eu.timepit.refined.collection._ + // import eu.timepit.refined.generic._ + // import eu.timepit.refined.string._ + // import shapeless.{ ::, HNil } - val refinedValue : String Refined NonEmpty = refineMV[NonEmpty]("Hello") + val refinedValue : String Refined NonEmpty = refineMV[NonEmpty]("Hello") - Put[String Refined NonEmpty].put(refinedValue) must_=== CSV.Field("Hello") + assertEquals(Put[String Refined NonEmpty].put(refinedValue), CSV.Field("Hello")) - } + } - "be able to derive a get for a class" in { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ + test("be able to derive a get for a class") { + import _root_.io.chrisdavenport.cormorant._ + import _root_.io.chrisdavenport.cormorant.implicits._ - import eu.timepit.refined._ - import eu.timepit.refined.api.Refined - import eu.timepit.refined.collection.NonEmpty + import eu.timepit.refined._ + import eu.timepit.refined.api.Refined + import eu.timepit.refined.collection.NonEmpty - val refinedValue : String Refined NonEmpty = refineMV[NonEmpty]("Hello") - val csv = CSV.Field("Hello") + val refinedValue : String Refined NonEmpty = refineMV[NonEmpty]("Hello") + val csv = CSV.Field("Hello") - Get[String Refined NonEmpty].get(csv) must_=== Right(refinedValue) + assertEquals(Get[String Refined NonEmpty].get(csv), Right(refinedValue)) - } } } \ No newline at end of file From 87e6070fc6bbb19a69f55f53acbafa6d4b239ea7 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 08:08:40 -0400 Subject: [PATCH 09/13] Convert generic module tests to munit --- .../cormorant/generic/AutoSpec.scala | 45 +++--------- .../cormorant/generic/SemiAutoSpec.scala | 73 ++++++------------- 2 files changed, 33 insertions(+), 85 deletions(-) diff --git a/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/AutoSpec.scala b/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/AutoSpec.scala index 747cdc2d..29988ac1 100644 --- a/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/AutoSpec.scala +++ b/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/AutoSpec.scala @@ -1,33 +1,21 @@ package io.chrisdavenport.cormorant.generic import cats.data._ -import org.specs2._ +import _root_.io.chrisdavenport.cormorant._ +import _root_.io.chrisdavenport.cormorant.implicits._ +import _root_.io.chrisdavenport.cormorant.generic.auto._ -class AutoSpec extends Specification { - override def is = s2""" - encode a row with Write automatically $rowGenericallyDerived - encode a comple with LabelledWrite automatically $rowNameDerived - read a row with read automatically $readRowDerived - read a row with labelledread automatically $nameBasedReadDerived - """ - - def rowGenericallyDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.auto._ +class AutoSpec extends munit.FunSuite { + test("encode a row with Write automatically") { case class Example(i: Int, s: String, b: Int) val encoded = Example(1,"Hello",73).writeRow val expected = CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))) - - encoded must_=== expected + assertEquals(encoded, expected) } - def rowNameDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.auto._ + test("encode a comple with LabelledWrite automatically") { case class Example(i: Int, s: Option[String], b: Int) val encoded = List(Example(1, Option("Hello"), 73)).writeComplete @@ -35,23 +23,17 @@ class AutoSpec extends Specification { CSV.Headers(NonEmptyList.of(CSV.Header("i"), CSV.Header("s"), CSV.Header("b"))), CSV.Rows(List(CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))))) ) - encoded must_=== expected + assertEquals(encoded, expected) } - def readRowDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.auto._ + test("read a row with read automatically") { case class Example(i: Int, s: Option[String], b: Int) val from = CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))) val expected = Example(1, Some("Hello"), 73) - from.readRow[Example] must_=== Right(expected) + assertEquals(from.readRow[Example], Right(expected)) } - def nameBasedReadDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.auto._ + test("read a row with labelledread automatically") { import cats.syntax.either._ case class Example(i: Int, s: Option[String], b: Int) @@ -63,9 +45,6 @@ class AutoSpec extends Specification { ) val expected = List(Example(1, Option("Hello"), 73)).map(Either.right) - - fromCSV.readLabelled[Example] must_=== expected + assertEquals(fromCSV.readLabelled[Example], expected) } - - } \ No newline at end of file diff --git a/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/SemiAutoSpec.scala b/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/SemiAutoSpec.scala index b4359d30..d0375d6c 100644 --- a/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/SemiAutoSpec.scala +++ b/modules/generic/src/test/scala/io/chrisdavenport/cormorant/generic/SemiAutoSpec.scala @@ -1,37 +1,24 @@ package io.chrisdavenport.cormorant.generic import cats.data._ -import org.specs2._ import cats.implicits._ +import _root_.io.chrisdavenport.cormorant._ +import _root_.io.chrisdavenport.cormorant.implicits._ +import _root_.io.chrisdavenport.cormorant.generic.semiauto._ -class SemiAutoSpec extends Specification { - override def is = s2""" - encode a write row correctly $rowGenericallyDerived - encode a labelledWrite complete correctly $rowNameDerived - read a correctly encoded row $readRowDerived - read a labelledRead row by name $nameBasedReadDerived - read a product field row $derivedProductRead - write a product field row $derivedProductWrite - """ - - def rowGenericallyDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ +class SemiAutoSpec extends munit.FunSuite { + test("encode a write row correctly") { case class Example(i: Int, s: String, b: Int) implicit val writeExample: Write[Example] = deriveWrite val encoded = Encoding.writeRow(Example(1,"Hello",73)) val expected = CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))) - encoded must_=== expected + assertEquals(encoded, expected) } - def rowNameDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("encode a labelledWrite complete correctly") { case class Example(i: Int, s: Option[String], b: Int) implicit val writeExample: LabelledWrite[Example] = deriveLabelledWrite @@ -40,24 +27,18 @@ class SemiAutoSpec extends Specification { CSV.Headers(NonEmptyList.of(CSV.Header("i"), CSV.Header("s"), CSV.Header("b"))), CSV.Rows(List(CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))))) ) - encoded must_=== expected + assertEquals(encoded, expected) } - def readRowDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("read a correctly encoded row") { case class Example(i: Int, s: Option[String], b: Int) implicit val derivedRead: Read[Example] = deriveRead val from = CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("Hello"), CSV.Field("73"))) val expected = Example(1, Some("Hello"), 73) - Read[Example].read(from) must_=== Right(expected) + assertEquals(Read[Example].read(from), Right(expected) ) } - def nameBasedReadDerived = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("read a labelledRead row by name") { import cats.syntax.either._ case class Example(i: Int, s: Option[String], b: Int) @@ -70,13 +51,10 @@ class SemiAutoSpec extends Specification { ) val expected = List(Example(1, Option("Hello"), 73)).map(Either.right) - Decoding.readLabelled[Example](fromCSV) must_=== expected + assertEquals(Decoding.readLabelled[Example](fromCSV), expected) } - def derivedProductRead = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("read a product field row") { case class Foo(i: Int) case class Example(i: Foo, s: Option[String], b: Int) implicit val f : Read[Foo] = deriveRead @@ -86,28 +64,22 @@ class SemiAutoSpec extends Specification { val fromCSV = CSV.Row(NonEmptyList.of(CSV.Field("73"), CSV.Field("Hello"), CSV.Field("1"))) - r.read(fromCSV) must_=== Right(Example(Foo(73), Some("Hello"), 1)) + assertEquals(r.read(fromCSV), Right(Example(Foo(73), Some("Hello"), 1))) } - def derivedProductWrite = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("write a product field row") { case class Foo(i: Int, x: String) case class Example(i: Foo, s: Option[String], b: Int) implicit val f : Write[Foo] = deriveWrite val _ = f implicit val r : Write[Example] = deriveWrite val input = Example(Foo(73, "yellow"), Some("foo"), 5) - r.write(input) must_=== CSV.Row( + assertEquals(r.write(input), CSV.Row( NonEmptyList.of(CSV.Field("73"), CSV.Field("yellow"), CSV.Field("foo"), CSV.Field("5")) - ) + )) } - def derivedProductLabelledRead = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("read a labelled product field row") { case class Foo(i: Int) case class Example(i: Foo, s: Option[String], b: Int) implicit val f : LabelledRead[Foo] = deriveLabelledRead @@ -120,13 +92,10 @@ class SemiAutoSpec extends Specification { val expected = List(Example(Foo(1), Option("Hello"), 73)) .map(Either.right) - Decoding.readLabelled[Example](fromCSV) must_=== expected + assertEquals(Decoding.readLabelled[Example](fromCSV), expected) } - def derivedProductLabelledWrite = { - import _root_.io.chrisdavenport.cormorant._ - import _root_.io.chrisdavenport.cormorant.implicits._ - import _root_.io.chrisdavenport.cormorant.generic.semiauto._ + test("write a labelled product field row") { case class Foo(i: Int, m: String) case class Example(i: Foo, s: Option[String], b: Int) implicit val f : LabelledWrite[Foo] = deriveLabelledWrite @@ -137,7 +106,7 @@ class SemiAutoSpec extends Specification { CSV.Headers(NonEmptyList.of(CSV.Header("i"), CSV.Header("m"), CSV.Header("s"), CSV.Header("b"))), CSV.Rows(List(CSV.Row(NonEmptyList.of(CSV.Field("1"), CSV.Field("bar"), CSV.Field("Hello"), CSV.Field("73"))))) ) - encoded must_=== expected + assertEquals(encoded, expected) } } \ No newline at end of file From 740b4ca90514c550dff64e8e5c006aaec9da381a Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 08:10:23 -0400 Subject: [PATCH 10/13] Delete CormorantSpec --- .../scala/io/chrisdavenport/cormorant/CormorantSpec.scala | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 modules/core/src/test/scala/io/chrisdavenport/cormorant/CormorantSpec.scala diff --git a/modules/core/src/test/scala/io/chrisdavenport/cormorant/CormorantSpec.scala b/modules/core/src/test/scala/io/chrisdavenport/cormorant/CormorantSpec.scala deleted file mode 100644 index 827a2391..00000000 --- a/modules/core/src/test/scala/io/chrisdavenport/cormorant/CormorantSpec.scala +++ /dev/null @@ -1,5 +0,0 @@ -package io.chrisdavenport.cormorant - -trait CormorantSpec extends org.specs2.mutable.Specification - with org.specs2.ScalaCheck - with CormorantArbitraries \ No newline at end of file From b5179ab8eb2175a07febd3993a95fab120cb5fff Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 08:17:51 -0400 Subject: [PATCH 11/13] Remove specs2 from build.sbt --- build.sbt | 3 --- 1 file changed, 3 deletions(-) diff --git a/build.sbt b/build.sbt index 3c217b2b..43cedb04 100644 --- a/build.sbt +++ b/build.sbt @@ -84,7 +84,6 @@ val catsEffectTestV = "0.4.2" val shapelessV = "2.3.3" val http4sV = "0.21.18" val catsScalacheckV = "0.3.0" -val specs2V = "4.10.6" val munitV = "0.7.26" val munitCatsEffectV = "1.0.3" val scalacheckEffectV = "1.0.2" @@ -201,8 +200,6 @@ lazy val commonSettings = Seq( "org.scalameta" %% "munit-scalacheck" % munitV % Test, "org.typelevel" %% "munit-cats-effect-2" % munitCatsEffectV % Test, "org.typelevel" %% "scalacheck-effect-munit" % scalacheckEffectV % Test, - "org.specs2" %% "specs2-core" % specs2V % Test, - "org.specs2" %% "specs2-scalacheck" % specs2V % Test, "io.chrisdavenport" %% "cats-scalacheck" % catsScalacheckV % Test, ) ) From 744de7851d790d75be5d9b1663357de4e3d6b577 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 08:24:09 -0400 Subject: [PATCH 12/13] Ignore .vscode dir --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 1a30ec7d..71774b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ target/ # vim *.sw? +.vscode/ + .DS_Store **/.DS_Store From a66975dd7ba1120b59f9f51a1994ba7aec069179 Mon Sep 17 00:00:00 2001 From: Andrew Valencik Date: Sat, 29 May 2021 08:24:25 -0400 Subject: [PATCH 13/13] Ignore all metals.sbt files as recommended --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 71774b0b..ddccbf4e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ target/ tags .metals .bloop -project/metals.sbt \ No newline at end of file +metals.sbt