Skip to content

Commit

Permalink
Add parser tests (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
htmldoug authored Dec 19, 2021
1 parent 4c53d9b commit 82824fb
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 148 deletions.
12 changes: 10 additions & 2 deletions project/WeePicklePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ object WeePicklePlugin extends AutoPlugin {

val scala211 = "2.11.12"
val scala212 = "2.12.12"
val scala213 = "2.13.5"
val scala3 = "3.0.1"
val scala213 = "2.13.7"
val scala3 = "3.1.0"
val supportedScala2Versions = Seq(scala211, scala212, scala213)
val supportedScalaVersions = supportedScala2Versions :+ scala3

Expand Down Expand Up @@ -119,6 +119,14 @@ object WeePicklePlugin extends AutoPlugin {

builder.result()
},
Test / scalacOptions ++= {
scalaBinaryVersion.value match {
case "3" => Seq(
"-Wconf:cat=deprecation:info" // scalacheck uses Stream
)
case _ => Nil
}
},
testFrameworks += new TestFramework("utest.runner.Framework"),
)
}
Empty file.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.rallyhealth.weejson.v1

import org.scalacheck.{Arbitrary, Gen}
import org.scalacheck.{Arbitrary, Gen, Shrink}

import scala.collection.mutable.ArrayBuffer

trait GenBufferedValue {
import BufferedValue.{Arr, Obj, Str, Bool, Null, AnyNum}
import com.rallyhealth.weejson.v1.BufferedValue._

def genArray(depth: Int): Gen[Arr] = {
for {
Expand Down Expand Up @@ -52,6 +52,17 @@ trait GenBufferedValue {
}

implicit val arbNum: Arbitrary[AnyNum] = Arbitrary {
Arbitrary.arbitrary[Double].map(BigDecimal(_)).map(AnyNum(_))
Arbitrary.arbitrary[Double].map(BigDecimal(_)).map(AnyNum(_)) // TODO open to all BigDecimals after #102
}

implicit val shrinkValue: Shrink[BufferedValue] = Shrink[BufferedValue] {
case Obj(map) => Shrink.shrink(map).map(Obj(_))
case Arr(buf) => Shrink.shrink(buf).map(Arr(_))
case NumDouble(d) => NumLong(d.longValue) +: Shrink.shrink(d).map(NumDouble(_))
case NumLong(long) => Shrink.shrink(long).map(NumLong(_))
case Str(s) => Shrink.shrink(s).map(Str(_))
case Timestamp(s) => Shrink.shrink(s).map(Timestamp(_))
case Ext(tag, bytes) => Shrink.shrink(bytes).map(Ext(tag, _))
case _ => Stream.empty
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.rallyhealth.weejson.v1

import com.rallyhealth.weejson.v1._
import org.scalacheck.{Arbitrary, Gen}
import org.scalacheck.{Arbitrary, Gen, Shrink}

import scala.collection.mutable.ArrayBuffer

Expand Down Expand Up @@ -54,4 +54,12 @@ trait GenValue {
implicit val arbNum: Arbitrary[Num] = Arbitrary {
Arbitrary.arbitrary[Double].map(Num(_))
}

implicit val shrinkValue: Shrink[Value] = Shrink[Value] {
case Obj(map) => Shrink.shrink(map).map(Obj(_))
case Arr(buf) => Shrink.shrink(buf).map(Arr(_))
case Num(bd) => Shrink.shrink(bd).map(Num(_))
case Str(s) => Shrink.shrink(s).map(Str(_))
case _ => Stream.empty
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.rallyhealth.weepickle.v1

import com.rallyhealth.weepickle.v1.core.{ArrVisitor, NoOpVisitor, ObjArrVisitor, ObjVisitor, Visitor}

/**
* Returns the max depth encountered.
* Useful for creating valid test inputs that can be sent to a parser that throws at high depth.
*/
class MaxDepthVisitor extends NoOpVisitor[Int](0) {

private[this] var maxDepth = 0
private[this] var currentDepth = 0

private def inc() = {
currentDepth += 1
maxDepth = math.max(maxDepth, currentDepth)
}

private def dec() = {
currentDepth -= 1
}

private trait ObjArrBase {
self: ObjArrVisitor[Any, Int] =>

override def subVisitor: Visitor[_, _] = {
inc()
MaxDepthVisitor.this.map(_ => maxDepth)
}

override def visitValue(v: Any): Unit = dec()

override def visitEnd(): Int = maxDepth
}

override def visitObject(length: Int): ObjVisitor[Any, Int] = new ObjVisitor[Any, Int] with ObjArrBase {
override def visitKey(): Visitor[_, _] = {
inc()
NoOpVisitor
}

override def visitKeyValue(v: Any): Unit = dec()
}

override def visitArray(length: Int): ArrVisitor[Any, Int] = new ArrVisitor[Any, Int] with ObjArrBase
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.rallyhealth.weepickle.v1

import com.rallyhealth.weejson.v1.jackson.{FromJson, ToJson, ToPrettyJson}
import com.rallyhealth.weejson.v1.{BufferedValue, GenBufferedValue}
import com.rallyhealth.weepickle.v1.core.FromInput
import org.scalactic.TypeCheckedTripleEquals
import org.scalatest.freespec.AnyFreeSpec
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks

import java.io.{ByteArrayInputStream, StringReader}
import java.nio.file.Files
import scala.language.{existentials, implicitConversions}

abstract class ParserSpec(parse: Array[Byte] => FromInput, depthLimit: Int = 100)
extends AnyFreeSpec
with ScalaCheckPropertyChecks
with GenBufferedValue
with TypeCheckedTripleEquals {

import com.rallyhealth.weejson.v1.BufferedValue._
import com.rallyhealth.weejson.v1.BufferedValueOps._

private implicit def toBytes(s: String): Array[Byte] = s.getBytes

"roundtrip" in testJson()
"deep arr" in testDepth(Arr(_))
"deep obj" in testDepth(b => Obj("k" -> b))

private def testJson(tweak: BufferedValue => BufferedValue = identity) = {
forAll { (b: BufferedValue) =>
val value = tweak(b)
val expected = value.transform(ToJson.string)

def testInput(s: Array[Byte]) = assert(parse(s).transform(ToJson.string) === expected)

testInput(expected)
testInput(s"\n $expected \n ")
testInput(value.transform(ToPrettyJson.bytes))

assert(parse(expected.getBytes()).transform(BufferedValue.Builder) === value)
}
}

private def testDepth(f: BufferedValue => BufferedValue) = {
testJson { in =>
val depth = in.transform(new MaxDepthVisitor)
val numPads = math.max(depthLimit - depth, 0)
(0 to numPads).foldLeft(in)((p, _) => f(p))
}
}
}

class FromJsonBytesSpec extends ParserSpec(FromJson(_))

class FromJsonStringSpec extends ParserSpec(b => FromJson(new String(b)))

class FromJsonInputStreamSpec extends ParserSpec(b => FromJson(new ByteArrayInputStream(b)))

class FromJsonReaderSpec extends ParserSpec(b => FromJson(new StringReader(new String(b))))

class FromJsonPathSpec extends ParserSpec(
b => FromJson(Files.write(Files.createTempFile("FromJsonPathSpec", ".json"), b))
)

class FromJsonFileSpec extends ParserSpec(
b => FromJson(Files.write(Files.createTempFile("FromJsonFileSpec", ".json"), b).toFile)
)

0 comments on commit 82824fb

Please sign in to comment.