Skip to content

Commit

Permalink
Defines a custom Truth Subject for RawEntity.
Browse files Browse the repository at this point in the history
It will display a much nicer diff when two RawEntities are different.

Instead of displaying something incomprehensible like this:

expected value: RawEntity(id=nYv2CsqyUhizSgATk1iWDdcH2lyH, singletons={textField=Primitive(JfMTAZxJfMTAZx), numField=Primitive(0.9139672143466999), boolField=Primitive(true), byteField=Primitive(118), shortField=Primitive(13838), intField=Primitive(-550336807), longField=Primitive(2404219702989143466), charField=Primitive(X), floatField=Primitive(0.76486343), doubleField=Primitive(0.9566789532716665), instantField=Primitive(51598822455702662), durationField=Primitive(-4422967655465562966), bigintField=Primitive(-5994803041829044736), ...
but was       : RawEntity(id=nYv2CsqyUhizSgATk1iWDdcH2lyH, singletons={longField=Primitive(2404219702989143466), floatField=Primitive(0.76486343), numListField=List([Primitive(0.7339245428056974), Primitive(0.672748944749758)]), instantField=Primitive(51598822455702662), inlineListField=List([RawEntity(id=PbHWmtJ5fzjBiSLcvLx3hVmo2rJEKGckEwUl, singletons={longField=Primitive(-1888722560329383402), moreReferenceField=RawReference(id=Nx2b42EL59jIINYKTlTCm1HXrAtw5D7YQZiOzJQRWQ7, storageKey=dummy://NpUkJfMTAZxhdyDOLss085RG4kMrQwb4z4OAtr, version=null, _creationTimestamp=-1, _expirationTimestamp=-1, isHardReference=false), numberField=Primitive(0.060857055216978706), ...

It now says this:

RawEntity instances are different
for field: rawEntity.collections[numsField][1]
expected : Primitive(0.695700200506916)
but was  : Primitive(0.46839655204527963)
PiperOrigin-RevId: 365955241
  • Loading branch information
csilvestrini authored and arcs-c3po committed Mar 31, 2021
1 parent 20e15fc commit 652a8d2
Show file tree
Hide file tree
Showing 24 changed files with 530 additions and 9 deletions.
22 changes: 18 additions & 4 deletions java/arcs/core/data/testutil/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@ licenses(["notice"])

package(default_visibility = ["//java/arcs:allowed-packages"])

GENERATOR_SRCS = ["Generators.kt"]

arcs_kt_library(
name = "generators",
testonly = 1,
srcs = [
"Generators.kt",
name = "testutil",
testonly = True,
srcs = glob(
["*.kt"],
exclude = GENERATOR_SRCS,
),
deps = [
"//java/arcs/core/data:rawentity",
"//java/arcs/core/data/util:data-util",
"//third_party/java/truth:truth-android",
],
)

arcs_kt_library(
name = "generators",
testonly = True,
srcs = GENERATOR_SRCS,
deps = [
"//java/arcs/core/common",
"//java/arcs/core/data",
Expand Down
198 changes: 198 additions & 0 deletions java/arcs/core/data/testutil/RawEntitySubject.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package arcs.core.data.testutil

import arcs.core.data.RawEntity
import arcs.core.data.util.ReferencableList
import com.google.common.truth.Fact
import com.google.common.truth.Fact.fact
import com.google.common.truth.Fact.simpleFact
import com.google.common.truth.FailureMetadata
import com.google.common.truth.Subject
import com.google.common.truth.Truth.assertAbout
import java.lang.Integer.max

/**
* Custom Truth [Subject] for asserting on [RawEntity] instances.
*
* Displays a much nicer diff between unequal entities than the default diff, e.g.:
*
* RawEntity instances are different
* for field: rawEntity.collections[foo][1]
* expected : Primitive(0.695700200506916)
* but was : Primitive(0.46839655204527963)
*
* See [RawEntitySubject.Companion.assertThat] for instructions on how to use this.
*/
class RawEntitySubject private constructor(
failureMetadata: FailureMetadata,
subject: RawEntity?
) : Subject(failureMetadata, subject) {
private val actual: RawEntity? = subject

override fun isEqualTo(expected: Any?) {
if (actual == expected) {
return
}
if (expected !is RawEntity || actual == null) {
// Standard failure scenarios, defer to Truth's standard error message.
super.isEqualTo(expected)
return // Redundant return, above line should always throw.
}

var diffs = RawEntityDiffer().diff(expected, actual)

if (diffs.isEmpty()) {
diffs = listOf(
simpleFact("<RawEntity instances are not equal, but no differences could be found>"),
fact("expected", expected.toString()),
fact("but was", actual.toString())
)
}

failWithoutActual(
simpleFact("RawEntity instances are different"),
*diffs.toTypedArray()
)
}

class RawEntityDiffer {
private val diffs = mutableListOf<Fact>()

fun diff(expected: RawEntity, actual: RawEntity): List<Fact> {
diffRawEntity(prefix = "rawEntity", expected, actual)
return diffs
}

private fun diffRawEntity(prefix: String, expected: RawEntity, actual: RawEntity) {
if (expected == actual) {
return
}
if (actual.id != expected.id) {
addDiff("$prefix.id", expected.id, actual.id)
}
if (actual.creationTimestamp != expected.creationTimestamp) {
addDiff(
"$prefix.creationTimestamp",
expected.creationTimestamp.toString(),
actual.creationTimestamp.toString()
)
}
if (actual.expirationTimestamp != expected.expirationTimestamp) {
addDiff(
"$prefix.expirationTimestamp",
expected.expirationTimestamp.toString(),
actual.expirationTimestamp.toString()
)
}
diffMap("$prefix.singletons", expected.singletons, actual.singletons)
diffMap("$prefix.collections", expected.collections, actual.collections)
}

private fun diffMap(prefix: String, expected: Map<String, Any?>, actual: Map<String, Any?>) {
if (expected == actual) {
return
}
val expectedKeys = expected.keys
val actualKeys = actual.keys
for (key in expectedKeys) {
if (key in actualKeys) {
// Check values.
diffValue("$prefix[$key]", expected[key], actual[key])
} else {
// Expected key is missing.
addDiff("$prefix[$key]", expected[key].toString(), "<absent>")
}
}
for (key in actualKeys) {
if (key !in expectedKeys) {
// Unexpected key.
addDiff("$prefix[$key]", "<absent>", actual[key].toString())
}
}
}

private fun diffValue(prefix: String, expected: Any?, actual: Any?) {
when {
expected == actual -> Unit
expected is RawEntity && actual is RawEntity -> {
diffRawEntity(prefix, expected, actual)
}
expected is List<*> && actual is List<*> -> {
diffList(prefix, expected, actual)
}
expected is Set<*> && actual is Set<*> -> {
diffSet(prefix, expected, actual)
}
expected is ReferencableList<*> && actual is ReferencableList<*> -> {
diffValue("$prefix.itemType", expected.itemType, actual.itemType)
diffList(prefix, expected.value, actual.value)
}
else -> {
addDiff(prefix, expected.toString(), actual.toString())
}
}
}

private fun diffList(prefix: String, expected: List<*>, actual: List<*>) {
for (i in 0 until max(expected.size, actual.size)) {
diffValue(
"$prefix[$i]",
expected.getOrElse(i) { "<absent>" },
actual.getOrElse(i) { "<absent>" }
)
}
}

private fun diffSet(prefix: String, expected: Set<*>, actual: Set<*>) {
// Convert each set to a stable sorted list, then compare lists.
diffList(
prefix,
expected.sortedBy { it.toString() }.toList(),
actual.sortedBy { it.toString() }.toList()
)
}

private fun addDiff(field: String, expected: String, actual: String) {
diffs.add(fact("for field", field))
diffs.add(fact("expected", expected))
diffs.add(fact("but was", actual))
}
}

private object Factory : Subject.Factory<RawEntitySubject, RawEntity> {
override fun createSubject(
failureMetadata: FailureMetadata,
actual: RawEntity?
): RawEntitySubject {
return RawEntitySubject(failureMetadata, actual)
}
}

companion object {
/**
* Returns a Truth [Subject] that can be used to assert on a [RawEntity].
*
* To use this, simply import this method, and then use [assertThat] as usual on any [RawEntity]
* instance:
*
* ```
* import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
*
* val myEntity = RawEntity(...)
* assertThat(myEntity).isEqualTo(...)
* ```
*/
fun assertThat(actual: RawEntity): RawEntitySubject {
return assertAbout(Factory).that(actual)
}

/**
* Factory method for creating a [Factory] instance, to be used with methods like
* `assertWithMessage`.
*
* ```kotlin
* assertWithMessage("blah").about(rawEntities()).that(...)
* ```
*/
fun rawEntities(): Subject.Factory<RawEntitySubject, RawEntity> = Factory
}
}
1 change: 1 addition & 0 deletions javatests/arcs/android/crdt/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ arcs_kt_android_test_suite(
"//java/arcs/core/crdt/testutil:generators",
"//java/arcs/core/data:rawentity",
"//java/arcs/core/data:schema_fields",
"//java/arcs/core/data/testutil",
"//java/arcs/core/data/testutil:generators",
"//java/arcs/core/data/util:data-util",
"//java/arcs/core/entity/testutil",
Expand Down
4 changes: 3 additions & 1 deletion javatests/arcs/android/crdt/CrdtSetProtoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import arcs.core.common.Referencable
import arcs.core.crdt.CrdtSet
import arcs.core.crdt.VersionMap
import arcs.core.data.RawEntity
import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -48,7 +49,8 @@ class CrdtSetProtoTest {
@Test
fun data_parcelableRoundTrip_works() {
val data = CrdtSet.DataImpl(
versionMap, mutableMapOf(
versionMap,
mutableMapOf(
entity1.id to CrdtSet.DataValue(VersionMap("alice" to 1), entity1),
entity2.id to CrdtSet.DataValue(VersionMap("alice" to 1), entity2)
)
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/android/crdt/RawEntityProtoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import android.os.Parcel
import androidx.test.ext.junit.runners.AndroidJUnit4
import arcs.android.util.writeProto
import arcs.core.data.RawEntity
import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
import arcs.core.entity.testutil.FixtureEntities
import com.google.common.truth.Truth.assertThat
import org.junit.Test
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/android/crdt/ReferencableProtoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import arcs.core.crdt.CrdtEntity
import arcs.core.crdt.VersionMap
import arcs.core.data.FieldType
import arcs.core.data.RawEntity
import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
import arcs.core.data.util.ReferencableList
import arcs.core.data.util.ReferencablePrimitive
import arcs.core.data.util.toReferencable
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/android/crdt/ReferenceProtoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import arcs.android.util.writeProto
import arcs.core.crdt.VersionMap
import arcs.core.data.RawEntity
import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
import arcs.core.storage.RawReference
import arcs.core.storage.StorageKeyManager
import arcs.core.storage.keys.RamDiskStorageKey
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/android/storage/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ arcs_kt_android_test_suite(
"//java/arcs/android/util/testutil",
"//java/arcs/core/crdt",
"//java/arcs/core/data",
"//java/arcs/core/data/testutil",
"//java/arcs/core/entity/testutil",
"//java/arcs/core/storage",
"//java/arcs/core/storage/database",
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/core/crdt/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ arcs_kt_jvm_test_suite(
"//java/arcs/core/crdt/testutil:generators",
"//java/arcs/core/data",
"//java/arcs/core/data:schema_fields",
"//java/arcs/core/data/testutil",
"//java/arcs/core/data/util:data-util",
"//java/arcs/core/testutil",
"//java/arcs/core/util",
Expand Down
1 change: 1 addition & 0 deletions javatests/arcs/core/crdt/CrdtEntityTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import arcs.core.crdt.CrdtEntity.Operation.SetSingleton
import arcs.core.crdt.CrdtEntity.Reference.Companion.defaultReferenceBuilder
import arcs.core.data.FieldType
import arcs.core.data.RawEntity
import arcs.core.data.testutil.RawEntitySubject.Companion.assertThat
import arcs.core.data.util.ReferencableList
import arcs.core.data.util.ReferencablePrimitive
import arcs.core.data.util.toReferencable
Expand Down
19 changes: 19 additions & 0 deletions javatests/arcs/core/data/testutil/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load(
"//third_party/java/arcs/build_defs:build_defs.bzl",
"arcs_kt_jvm_test_suite",
)

licenses(["notice"])

arcs_kt_jvm_test_suite(
name = "testutil",
package = "arcs.core.data.testutil",
deps = [
"//java/arcs/core/data:rawentity",
"//java/arcs/core/data:schema_fields",
"//java/arcs/core/data/testutil",
"//java/arcs/core/data/util:data-util",
"//third_party/java/junit:junit-android",
"//third_party/java/truth:truth-android",
],
)
Loading

0 comments on commit 652a8d2

Please sign in to comment.