Skip to content

Commit

Permalink
cleanup and minor perf
Browse files Browse the repository at this point in the history
  • Loading branch information
agourlay committed Sep 22, 2023
1 parent d5e3de6 commit b732eb7
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 84 deletions.
5 changes: 1 addition & 4 deletions benchmarks/src/main/scala/session/AddValuesBench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ class AddValuesBench {

//sbt:benchmarks> jmh:run .*AddValues.* -prof gc -foe true -gc true -rf csv

@Param(Array("1", "2", "3", "4", "5", "10"))
@Param(Array("1", "2", "4", "5", "10"))
var insertNumber: String = ""

/*
[info] Benchmark (insertNumber) Mode Cnt Score Error Units
[info] AddValuesBench.addValues 1 thrpt 10 13374739.456 ± 87290.955 ops/s
[info] AddValuesBench.addValues 2 thrpt 10 5622516.701 ± 4825.476 ops/s
[info] AddValuesBench.addValues 3 thrpt 10 3957480.216 ± 6816.605 ops/s
[info] AddValuesBench.addValues 4 thrpt 10 3055828.945 ± 3980.667 ops/s
[info] AddValuesBench.addValues 5 thrpt 10 2510529.320 ± 4932.346 ops/s
[info] AddValuesBench.addValues 10 thrpt 10 1175018.124 ± 2182.672 ops/s
Expand All @@ -33,7 +32,6 @@ class AddValuesBench {
val values = insertNumber match {
case "1" => oneEntry
case "2" => twoEntries
case "3" => threeEntries
case "4" => fourEntries
case "5" => fiveEntries
case "10" => tenEntries
Expand All @@ -48,7 +46,6 @@ object AddValuesBench {

val oneEntry = tupleEntry :: Nil
val twoEntries = List.fill(2)(tupleEntry)
val threeEntries = List.fill(3)(tupleEntry)
val fourEntries = List.fill(4)(tupleEntry)
val fiveEntries = List.fill(5)(tupleEntry)
val tenEntries = List.fill(10)(tupleEntry)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.github.agourlay.cornichon.dsl

case class BodyElementCollector[Body, Result](fn: List[Body] => Result) {
case class BodyElementCollector[Body, Result](fn: List[Body] => Result) extends AnyVal {
def apply(body: => Body): Result = macro BodyElementCollectorMacro.collectImpl
def apply(body: => Seq[Body]): Result = macro BodyElementCollectorMacro.collectImpl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.agourlay.cornichon.dsl

import com.github.agourlay.cornichon.core.CornichonError
import org.parboiled2._
import scala.collection.mutable.ListBuffer
import scala.util.{ Failure, Success }

object DataTableParser {
Expand Down Expand Up @@ -50,30 +49,6 @@ class DataTableParser(val input: ParserInput) extends Parser with StringHeaderPa

case class DataTable(headers: Headers, rows: Vector[Row]) {
require(rows.forall(_.fields.size == headers.fields.size), "the data table is malformed, all rows must have the same number of elements")

lazy val rawStringList: List[List[(String, String)]] = {
val rowsBuffer = new ListBuffer[List[(String, String)]]()
var i = 0
val rowsLen = rows.length
while (i < rowsLen) {
val row = rows(i)
val fieldsBuffer = new ListBuffer[(String, String)]()
var j = 0
val fieldLen = row.fields.length
while (j < fieldLen) {
val value = row.fields(j)
val stripped = value.stripTrailing()
if (stripped.nonEmpty) {
val name = headers.fields(j)
fieldsBuffer += name -> stripped
}
j += 1
}
i += 1
rowsBuffer += fieldsBuffer.toList
}
rowsBuffer.toList
}
}

case class Headers(fields: Vector[String]) extends AnyVal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import io.circe.Encoder

import scala.concurrent.duration.FiniteDuration

case class HttpMethod(name: String)
case class HttpMethod(name: String) extends AnyVal

object HttpMethods {
val DELETE = HttpMethod("DELETE")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ trait CornichonJson {
parseString(s)
// table is turned into a JArray
case '|' =>
parseDataTable(s).map(jObjs => Json.fromValues(jObjs.iterator.map(Json.fromJsonObject).toVector))
parseDataTableJson(s)
// treated as a JString
case _ =>
Right(Json.fromString(s))
Expand Down Expand Up @@ -74,36 +74,74 @@ trait CornichonJson {
None
}

def parseDataTable(table: String): Either[CornichonError, List[JsonObject]] = {
def parseRow(rawRow: List[(String, String)]): Either[MalformedJsonError[String], JsonObject] = {
val cells = Map.newBuilder[String, Json]
// TODO while loop on Vec
rawRow.foreach {
case (name, rawValue) =>
parseString(rawValue) match {
case Right(json) => cells += (name -> json)
case Left(e) => return Left(e)
}
private def parseDataTableRow(rawRow: List[(String, String)]): Either[MalformedJsonError[String], JsonObject] = {
val cells = List.newBuilder[(String, Json)]
rawRow.foreach {
case (name, rawValue) =>
parseString(rawValue) match {
case Right(json) => cells += (name -> json)
case Left(e) => return Left(e)
}
}
// `fromIterable` is faster than `fromMap`
Right(JsonObject.fromIterable(cells.result()))
}

private def parseDataTableJson(table: String): Either[CornichonError, Json] = {
parseDataTableRaw(table).map { rawRows =>
val rows = Vector.newBuilder[Json]
rawRows.foreach { rawRow =>
parseDataTableRow(rawRow) match {
case Right(r) => rows += Json.fromJsonObject(r)
case Left(e) => return Left(e)
}
}
JsonObject.fromMap(cells.result()).asRight
// `fromValues` wants a Vector as a concrete type
Json.fromValues(rows.result())
}
}

def parseDataTable(table: String): Either[CornichonError, List[JsonObject]] = {
parseDataTableRaw(table).map { rawRows =>
val rows = new ListBuffer[JsonObject]()
// TODO while loop on Vec
rawRows.foreach { rawRow =>
parseRow(rawRow) match {
parseDataTableRow(rawRow) match {
case Right(r) => rows += r
case Left(e) => return Left(e)
}
}
rows.toList
rows.result()
}
}

// Returns raw data with duplicates and initial ordering
def parseDataTableRaw(table: String): Either[CornichonError, List[List[(String, String)]]] =
DataTableParser.parse(table).map(_.rawStringList)
def parseDataTableRaw(table: String): Either[CornichonError, List[List[(String, String)]]] = {
DataTableParser.parse(table).map { dataTable =>
val rows = dataTable.rows
val headers = dataTable.headers
val rowsBuffer = new ListBuffer[List[(String, String)]]()
var i = 0
val rowsLen = rows.length
while (i < rowsLen) {
val row = rows(i)
val fieldsBuffer = new ListBuffer[(String, String)]()
var j = 0
val fieldLen = row.fields.length
while (j < fieldLen) {
val value = row.fields(j)
val stripped = value.stripTrailing()
if (stripped.nonEmpty) {
val name = headers.fields(j)
fieldsBuffer += name -> stripped
}
j += 1
}
i += 1
rowsBuffer += fieldsBuffer.toList
}
rowsBuffer.toList
}
}

def parseGraphQLJson(input: String): Either[MalformedGraphQLJsonError[String], Json] =
QueryParser.parseInput(input) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.github.agourlay.cornichon.core.CornichonError
import com.github.agourlay.cornichon.json.CornichonJson._
import com.github.agourlay.cornichon.util.TraverseUtils.traverseLO
import io.circe.{ ACursor, Json }
import java.util.concurrent.atomic.AtomicBoolean
import scala.collection.mutable.ListBuffer

case class JsonPath(operations: Vector[JsonPathOperation]) extends AnyVal {
Expand Down Expand Up @@ -39,40 +38,52 @@ case class JsonPath(operations: Vector[JsonPathOperation]) extends AnyVal {
// Boolean flag to indicate if the operations contain a valid projection segment.
// If it is the case, the result must be interpreted as a List otherwise it is always a List of one element.
private def cursors(input: Json): (List[ACursor], Boolean) = {

def expandCursors(arrayFieldCursor: ACursor, signalValidProjection: AtomicBoolean): List[ACursor] =
arrayFieldCursor.values.fold[List[ACursor]](Nil) { values =>
// the projection is valid because there was an array
signalValidProjection.set(true)
if (values.isEmpty)
Nil
else {
// Better be fast here...
val lb = ListBuffer.empty[ACursor]
var arrayElementsCursor = arrayFieldCursor.downArray
var i = 0
while (i < values.size) {
lb += arrayElementsCursor
arrayElementsCursor = arrayElementsCursor.right
i += 1
// captured in def to avoid passing it around
var projectionMode = false

def expandCursors(arrayFieldCursor: ACursor): List[ACursor] =
arrayFieldCursor.values match {
case None => Nil
case Some(values) =>
// the projection is valid because there was an array
projectionMode = true
val size = values.size
if (size == 0)
Nil
else {
val lb = ListBuffer.empty[ACursor]
var arrayElementsCursor = arrayFieldCursor.downArray
var i = 0
while (i < size) {
lb += arrayElementsCursor
arrayElementsCursor = arrayElementsCursor.right
i += 1
}
lb.toList
}
lb.toList
}
}

// using an AtomicBoolean for signaling...
val projectionMode = new AtomicBoolean(false)
val cursors = operations.foldLeft[List[ACursor]](input.hcursor :: Nil) { (oc, op) =>
op match {
case RootSelection => oc
case FieldSelection(field) => oc.map(_.downField(field))
case RootArrayElementSelection(index) => oc.map(_.downN(index))
case ArrayFieldSelection(field, index) => oc.map(_.downField(field).downN(index))
case RootArrayFieldProjection => oc.flatMap(o => expandCursors(o, projectionMode))
case ArrayFieldProjection(field) => oc.flatMap(o => expandCursors(o.downField(field), projectionMode))
val operationsLen = operations.length
var i = 0
var acc: List[ACursor] = input.hcursor :: Nil
while (i < operationsLen) {
operations(i) match {
case RootSelection =>
// do nothing
case FieldSelection(field) =>
acc = acc.map(_.downField(field))
case RootArrayElementSelection(index) =>
acc = acc.map(_.downN(index))
case ArrayFieldSelection(field, index) =>
acc = acc.map(_.downField(field).downN(index))
case RootArrayFieldProjection =>
acc = acc.flatMap(o => expandCursors(o))
case ArrayFieldProjection(field) =>
acc = acc.flatMap(o => expandCursors(o.downField(field)))
}
i += 1
}
(cursors, projectionMode.get())
(acc, projectionMode)
}

def removeFromJson(input: Json): Json =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.github.agourlay.cornichon.steps.regular.assertStep
import cats.data._
import cats.syntax.validated._
import cats.syntax.apply._

import com.github.agourlay.cornichon.core._
import com.github.agourlay.cornichon.core.Done._

Expand All @@ -14,9 +13,13 @@ trait Assertion { self =>
val validated = self.validated *> other.validated
}

def andAll(others: Seq[Assertion]): Assertion = new Assertion {
val validated = others.fold(self)(_ and _).validated
}
def andAll(others: Seq[Assertion]): Assertion =
if (others.isEmpty)
self
else
new Assertion {
val validated = others.fold(self)(_ and _).validated
}

def or(other: Assertion): Assertion = new Assertion {
val validated =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class DataTableSpec extends FunSuite {
"""

parseDataTable(input) match {
case Right(_) => assert(false)
case Right(_) => assert(cond = false)
case Left(e) => assert(e.renderedMessage.contains("requirement failed: the data table is malformed, all rows must have the same number of elements"))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.github.agourlay.cornichon.steps.regular.assertStep.{ AssertStep, Asse

object HttpListenSteps {

case class HttpListenStepBuilder(name: String) {
case class HttpListenStepBuilder(name: String) extends AnyVal {
def received_calls(count: Int) = AssertStep(
title = s"HTTP mock server '$name' received '$count' calls",
action = sc => Assertion.either {
Expand Down

0 comments on commit b732eb7

Please sign in to comment.