Skip to content

Commit

Permalink
DPP-726 Add string interning unit tests (#11841)
Browse files Browse the repository at this point in the history
* DPP-726 Add string interning unit tests

changelog_begin
changelog_end

* Add more test cases
  • Loading branch information
rautenrieth-da authored Nov 24, 2021
1 parent 59eb0d2 commit 25b476f
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.platform.store.interning

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class RawStringInterningSpec extends AnyFlatSpec with Matchers {

behavior of "RawStringInterning.from"

it should "start empty" in {
val current = RawStringInterning.from(Nil)
current.map shouldBe empty
current.idMap shouldBe empty
current.lastId shouldBe 0
}

it should "append empty entries to existing cache" in {
val previous = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val current = RawStringInterning.from(Nil, previous)
current.map shouldBe previous.map
current.idMap shouldBe previous.idMap
current.lastId shouldBe previous.lastId
}

it should "append non-empty entries to empty cache" in {
val current = RawStringInterning.from(List(1 -> "one"))
current.map shouldBe Map("one" -> 1)
current.idMap shouldBe Map(1 -> "one")
current.lastId shouldBe 1
}

it should "append non-empty entries to non-empty cache" in {
val previous = RawStringInterning(
Map("one" -> 1, "two" -> 2),
Map(1 -> "one", 2 -> "two"),
2,
)
val current = RawStringInterning.from(List(3 -> "three"), previous)
current.map shouldBe Map("one" -> 1, "two" -> 2, "three" -> 3)
current.idMap shouldBe Map(1 -> "one", 2 -> "two", 3 -> "three")
current.lastId shouldBe 3
}

behavior of "RawStringInterning.newEntries"

it should "return an empty result if the input and previous state is empty" in {
val current = RawStringInterning.from(Nil)
val newEntries = RawStringInterning.newEntries(Iterator.empty, current)
newEntries shouldBe empty
}

it should "return an empty result if the input is empty" in {
val current = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val newEntries = RawStringInterning.newEntries(Iterator.empty, current)
newEntries shouldBe empty
}

it should "return an empty result if the input only contains duplicates" in {
val current = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val newEntries = RawStringInterning.newEntries(List("one").iterator, current)
newEntries shouldBe empty
}

it should "return a new entry if the input is an unknown string" in {
val current = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val newEntries = RawStringInterning.newEntries(List("two").iterator, current)
newEntries shouldBe Vector(2 -> "two")
}

it should "not return a new entry for known strings" in {
val current = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val newEntries = RawStringInterning.newEntries(List("one", "two").iterator, current)
newEntries shouldBe Vector(2 -> "two")
}

it should "handle duplicate unknown strings" in {
val current = RawStringInterning(Map("one" -> 1), Map(1 -> "one"), 1)
val newEntries = RawStringInterning.newEntries(List("two", "two", "two").iterator, current)
newEntries shouldBe Vector(2 -> "two")
}

it should "handle mixed input" in {
val current = RawStringInterning(Map("one" -> 1, "two" -> 2), Map(1 -> "one", 2 -> "two"), 2)
val newEntries = RawStringInterning.newEntries(
List("one", "three", "two", "four", "two", "four").iterator,
current,
)
newEntries shouldBe Vector(3 -> "three", 4 -> "four")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.platform.store.interning

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class StringInterningDomainSpec extends AnyFlatSpec with Matchers {

behavior of "StringInterningDomain.prefixing"

case class StringBox(value: String)
object StringBox {
def from(raw: String): StringBox = StringBox(raw)
def to(boxed: StringBox): String = boxed.value
}

class StaticStringInterningAccessor(
idToString: Map[Int, String],
stringToId: Map[String, Int],
) extends StringInterningAccessor[String] {
override def internalize(t: String): Int = tryInternalize(t).get
override def tryInternalize(t: String): Option[Int] = stringToId.get(t)
override def externalize(id: Int): String = tryExternalize(id).get
override def tryExternalize(id: Int): Option[String] = idToString.get(id)
}

object StaticStringInterningAccessor {
def apply(entries: Seq[(Int, String)]): StaticStringInterningAccessor = {
new StaticStringInterningAccessor(
idToString = entries.toMap,
stringToId = entries.map(_.swap).toMap,
)
}
}

it should "handle a known string " in {
val accessor = StaticStringInterningAccessor(List(1 -> ".one", 2 -> ".two"))
val domain = StringInterningDomain.prefixing(".", accessor, StringBox.from, StringBox.to)

domain.tryExternalize(2) shouldBe Some(StringBox("two"))
domain.externalize(2) shouldBe StringBox("two")
domain.tryInternalize(StringBox("two")) shouldBe Some(2)
domain.internalize(StringBox("two")) shouldBe 2

domain.unsafe.tryExternalize(2) shouldBe Some("two")
domain.unsafe.externalize(2) shouldBe "two"
domain.unsafe.tryInternalize("two") shouldBe Some(2)
domain.unsafe.internalize("two") shouldBe 2
}

it should "handle an unknown string" in {
val accessor = StaticStringInterningAccessor(List(1 -> ".one", 2 -> ".two"))
val domain = StringInterningDomain.prefixing(".", accessor, StringBox.from, StringBox.to)

domain.tryExternalize(3) shouldBe empty
domain.tryInternalize(StringBox("three")) shouldBe empty

domain.unsafe.tryExternalize(3) shouldBe empty
domain.unsafe.tryInternalize("three") shouldBe empty
}

it should "work when two different domains share an accessor" in {
val accessor = StaticStringInterningAccessor(List(1 -> "aX", 2 -> "bX"))
val domainA = StringInterningDomain.prefixing("a", accessor, StringBox.from, StringBox.to)
val domainB = StringInterningDomain.prefixing("b", accessor, StringBox.from, StringBox.to)

domainA.internalize(StringBox("X")) shouldBe 1
domainB.internalize(StringBox("X")) shouldBe 2
domainA.externalize(1) shouldBe StringBox("X")
domainB.externalize(2) shouldBe StringBox("X")

domainA.unsafe.internalize("X") shouldBe 1
domainB.unsafe.internalize("X") shouldBe 2
domainA.unsafe.externalize(1) shouldBe "X"
domainB.unsafe.externalize(2) shouldBe "X"
}

it should "work when two identical domains share an accessor" in {
val accessor = StaticStringInterningAccessor(List(1 -> ".one", 2 -> ".two"))
val domainA = StringInterningDomain.prefixing(".", accessor, StringBox.from, StringBox.to)
val domainB = StringInterningDomain.prefixing(".", accessor, StringBox.from, StringBox.to)

domainA.internalize(StringBox("one")) shouldBe 1
domainB.internalize(StringBox("one")) shouldBe 1
domainA.externalize(2) shouldBe StringBox("two")
domainB.externalize(2) shouldBe StringBox("two")

domainA.unsafe.internalize("one") shouldBe 1
domainB.unsafe.internalize("one") shouldBe 1
domainA.unsafe.externalize(2) shouldBe "two"
domainB.unsafe.externalize(2) shouldBe "two"
}
}

0 comments on commit 25b476f

Please sign in to comment.