Skip to content

Commit

Permalink
Create RawEntityDereferencerTest, storage Reference-> CrdtEntity.Refe…
Browse files Browse the repository at this point in the history
…rence (#4812)

* Create RawEntityDereferencerTest, make storage Reference implement CrdtEntity.Reference.

Also: Create ParcelableReference.

* Add dep. Also, apparently read/writeBoolean is Q-only.

* Just write null if there is no version map.

* Just write null if there is no version map.
  • Loading branch information
Jason Feinstein committed Mar 3, 2020
1 parent ba7a107 commit a695797
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 19 deletions.
2 changes: 2 additions & 0 deletions java/arcs/android/crdt/BUILD
Expand Up @@ -21,6 +21,8 @@ kt_android_library(
"//java/arcs/core/crdt",
"//java/arcs/core/data:rawentity",
"//java/arcs/core/data/util:data-util",
"//java/arcs/core/storage:reference",
"//java/arcs/core/storage:storage_key",
"//third_party/java/jsr305_annotations",
],
)
Expand Down
6 changes: 5 additions & 1 deletion java/arcs/android/crdt/ParcelableReferencable.kt
Expand Up @@ -13,11 +13,12 @@ package arcs.android.crdt

import android.os.Parcel
import android.os.Parcelable
import arcs.android.crdt.ParcelableReferencable.Type
import arcs.core.common.Referencable
import arcs.core.crdt.CrdtEntity
import arcs.core.data.RawEntity
import arcs.core.data.util.ReferencablePrimitive
import java.lang.IllegalArgumentException
import arcs.core.storage.Reference
import javax.annotation.OverridingMethodsMustInvokeSuper

/**
Expand All @@ -34,6 +35,7 @@ interface ParcelableReferencable : Parcelable {
// TODO: Add other ParcelableReferencable subclasses.
RawEntity(ParcelableRawEntity.CREATOR),
CrdtEntityReferenceImpl(ParcelableCrdtEntity.ReferenceImpl),
StorageReferenceImpl(ParcelableReference.CREATOR),
Primitive(ParcelableReferencablePrimitive.CREATOR);

override fun writeToParcel(parcel: Parcel, flags: Int) {
Expand All @@ -56,6 +58,7 @@ interface ParcelableReferencable : Parcelable {
// TODO: Add other ParcelableReferencable subclasses.
is ParcelableRawEntity -> Type.RawEntity
is ParcelableCrdtEntity.ReferenceImpl -> Type.CrdtEntityReferenceImpl
is ParcelableReference -> Type.StorageReferenceImpl
is ParcelableReferencablePrimitive -> Type.Primitive
else -> throw IllegalArgumentException(
"Unsupported Referencable type: ${this.javaClass}"
Expand All @@ -71,6 +74,7 @@ interface ParcelableReferencable : Parcelable {
operator fun invoke(actual: Referencable): ParcelableReferencable = when (actual) {
// TODO: Add other ParcelableReferencable subclasses.
is RawEntity -> ParcelableRawEntity(actual)
is Reference -> ParcelableReference(actual)
is CrdtEntity.ReferenceImpl -> ParcelableCrdtEntity.ReferenceImpl(actual)
is ReferencablePrimitive<*> -> ParcelableReferencablePrimitive(actual)
else ->
Expand Down
57 changes: 57 additions & 0 deletions java/arcs/android/crdt/ParcelableReference.kt
@@ -0,0 +1,57 @@
/*
* Copyright 2020 Google LLC.
*
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
*
* Code distributed by Google as part of this project is also subject to an additional IP rights
* grant found at
* http://polymer.github.io/PATENTS.txt
*/

package arcs.android.crdt

import android.os.Parcel
import android.os.Parcelable
import arcs.android.util.writeProto
import arcs.core.storage.Reference
import arcs.core.storage.StorageKeyParser

/** Parcelable version of [Reference]. */
data class ParcelableReference(override val actual: Reference) : ParcelableReferencable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
super.writeToParcel(parcel, flags)
parcel.writeString(actual.id)
parcel.writeString(actual.storageKey.toString())
actual.version?.let {
parcel.writeProto(it.toProto())
} ?: {
parcel.writeTypedObject(null, flags)
}()
}

override fun describeContents(): Int = 0

/* Don't use this directly, instead use ParcelableReferencable. */
internal companion object CREATOR : Parcelable.Creator<ParcelableReference> {
override fun createFromParcel(parcel: Parcel): ParcelableReference {
val id = requireNotNull(parcel.readString()) {
"Required id not found in parcel for ParcelableReference"
}
val storageKeyString = requireNotNull(parcel.readString()) {
"Required storageKey not found in parcel for ParcelableReference"
}
val versionMap = parcel.readVersionMap()?.takeIf { it.isNotEmpty() }

return ParcelableReference(
Reference(id, StorageKeyParser.parse(storageKeyString), versionMap)
)
}

override fun newArray(size: Int): Array<ParcelableReference?> = arrayOfNulls(size)
}
}

/** Writes the [Reference] to the receiving [Parcel]. */
fun Parcel.writeReference(reference: Reference, flags: Int) =
writeTypedObject(ParcelableReference(reference), flags)
6 changes: 4 additions & 2 deletions java/arcs/core/crdt/extension/ConversionExtensions.kt
Expand Up @@ -19,8 +19,10 @@ import arcs.core.data.util.ReferencablePrimitive
import arcs.core.util.Base64

/** Converts the [RawEntity] into a [CrdtEntity.Data] model, at the given version. */
fun RawEntity.toCrdtEntityData(versionMap: VersionMap): CrdtEntity.Data =
CrdtEntity.Data(versionMap.copy(), this) { CrdtEntity.ReferenceImpl(it.id) }
fun RawEntity.toCrdtEntityData(
versionMap: VersionMap,
referenceBuilder: (Referencable) -> CrdtEntity.Reference = { CrdtEntity.ReferenceImpl(it.id) }
): CrdtEntity.Data = CrdtEntity.Data(versionMap.copy(), this, referenceBuilder)

private fun Any?.toReferencable(): Referencable {
requireNotNull(this) { "Cannot create a referencable from a null value." }
Expand Down
2 changes: 1 addition & 1 deletion java/arcs/core/data/Reference.kt
Expand Up @@ -27,7 +27,7 @@ import kotlinx.coroutines.Dispatchers
*
* Developers can check the liveness of a [Reference] using either [isAlive] or [isDead].
*/
interface Reference<T : Referencable> {
interface Reference<T : Referencable> : arcs.core.crdt.CrdtEntity.Reference {
/**
* Fetches the actual [Entity] value being referenced from storage.
*
Expand Down
2 changes: 1 addition & 1 deletion java/arcs/core/storage/DirectStore.kt
Expand Up @@ -92,7 +92,7 @@ class DirectStore<Data : CrdtData, Op : CrdtOperation, T> /* internal */ constru
return when (message) {
is ProxyMessage.SyncRequest -> {
callbacks.value[message.id]?.invoke(
ProxyMessage.ModelUpdate(localModel.data, message.id)
ProxyMessage.ModelUpdate(getLocalData(), message.id)
)
true
}
Expand Down
5 changes: 4 additions & 1 deletion java/arcs/core/storage/ReferenceModeStore.kt
Expand Up @@ -566,7 +566,10 @@ class ReferenceModeStore private constructor(
entity,
VersionMap(crdtKey to maxVersion),
fieldVersionProvider
) { CrdtEntity.ReferenceImpl(it.id) }
) {
if (it is Reference) it
else CrdtEntity.Reference.buildReference(it)
}
}

companion object {
Expand Down
11 changes: 9 additions & 2 deletions java/arcs/core/storage/driver/Database.kt
Expand Up @@ -271,7 +271,11 @@ class DatabaseDriver<Data : Any>(
)?.also {
dataAndVersion = when (it) {
is DatabaseData.Entity ->
it.rawEntity.toCrdtEntityData(it.versionMap)
it.rawEntity.toCrdtEntityData(it.versionMap) { refable ->
// Use the storage reference if it is one.
if (refable is Reference) refable
else CrdtEntity.Reference.buildReference(refable)
}
is DatabaseData.Singleton ->
it.reference.toCrdtSingletonData(it.versionMap)
is DatabaseData.Collection ->
Expand Down Expand Up @@ -361,7 +365,10 @@ class DatabaseDriver<Data : Any>(
val actualData = when (data) {
is DatabaseData.Singleton -> data.reference.toCrdtSingletonData(data.versionMap)
is DatabaseData.Collection -> data.values.toCrdtSetData(data.versionMap)
is DatabaseData.Entity -> data.rawEntity.toCrdtEntityData(data.versionMap)
is DatabaseData.Entity -> data.rawEntity.toCrdtEntityData(data.versionMap) {
if (it is Reference) it
else CrdtEntity.Reference.buildReference(it)
}
} as Data

// Stash it locally.
Expand Down
21 changes: 11 additions & 10 deletions java/arcs/core/storage/handle/RawEntityDereferencer.kt
Expand Up @@ -73,17 +73,18 @@ class RawEntityDereferencer(
launch { store.onProxyMessage(ProxyMessage.SyncRequest(token)) }

// Only return the item if we've actually managed to pull it out of the database.
deferred.await().takeIf { it matches schema }
deferred.await().takeIf { it matches schema }?.copy(id = reference.id)
}
}

private infix fun RawEntity.matches(schema: Schema): Boolean {
// Only allow empty to match if the Schema is also empty.
// TODO: Is this a correct assumption?
if (singletons.isEmpty() && collections.isEmpty())
return schema.fields.singletons.isEmpty() && schema.fields.collections.isEmpty()
/* internal */
infix fun RawEntity.matches(schema: Schema): Boolean {
// Only allow empty to match if the Schema is also empty.
// TODO: Is this a correct assumption?
if (singletons.isEmpty() && collections.isEmpty())
return schema.fields.singletons.isEmpty() && schema.fields.collections.isEmpty()

// Return true if any of the RawEntity's fields are part of the Schema.
return (singletons.isEmpty() || singletons.keys.any { it in schema.fields.singletons }) &&
(collections.isEmpty() || collections.keys.any { it in schema.fields.collections })
}
// Return true if any of the RawEntity's fields are part of the Schema.
return (singletons.isEmpty() || singletons.keys.any { it in schema.fields.singletons }) &&
(collections.isEmpty() || collections.keys.any { it in schema.fields.collections })
}
104 changes: 104 additions & 0 deletions javatests/arcs/android/storage/ParcelableReferenceTest.kt
@@ -0,0 +1,104 @@
/*
* Copyright 2020 Google LLC.
*
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
*
* Code distributed by Google as part of this project is also subject to an additional IP rights
* grant found at
* http://polymer.github.io/PATENTS.txt
*/

package arcs.android.storage

import android.os.Parcel
import androidx.test.ext.junit.runners.AndroidJUnit4
import arcs.android.crdt.ParcelableRawEntity
import arcs.android.crdt.readReferencable
import arcs.android.crdt.writeReference
import arcs.core.crdt.VersionMap
import arcs.core.data.RawEntity
import arcs.core.storage.Reference
import arcs.core.storage.driver.RamDiskStorageKey
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ParcelableReferenceTest {
@Before
fun setUp() {
RamDiskStorageKey.registerParser()
}

@Test
fun parcelableRoundtrip_works_withNullVersionMap() {
val expected = Reference("myId", RamDiskStorageKey("backingKey"), null)

// Create a parcel and populate it with a ParcelableOperations object.
val marshalled = with(Parcel.obtain()) {
writeReference(expected, 0)
marshall()
}

// Now unmarshall the parcel, so we can verify the contents.
val unmarshalled = with(Parcel.obtain()) {
unmarshall(marshalled, 0, marshalled.size)
setDataPosition(0)
readReferencable()
}
assertThat(unmarshalled).isEqualTo(expected)
}

@Test
fun parcelableRoundtrip_works_withNonNullVersionMap() {
val expected = Reference(
"myId",
RamDiskStorageKey("backingKey"),
VersionMap("foo" to 1)
)

// Create a parcel and populate it with a ParcelableOperations object.
val marshalled = with(Parcel.obtain()) {
writeReference(expected, 0)
marshall()
}

// Now unmarshall the parcel, so we can verify the contents.
val unmarshalled = with(Parcel.obtain()) {
unmarshall(marshalled, 0, marshalled.size)
setDataPosition(0)
readReferencable()
}
assertThat(unmarshalled).isEqualTo(expected)
}

@Test
fun parcelableRoundtripWorks_whenReference_isPartOfRawEntity() {
val expectedReference = Reference(
"myId",
RamDiskStorageKey("backingKey"),
VersionMap("foo" to 1)
)
val expected = RawEntity(
"myId",
singletons = mapOf("foo" to expectedReference),
collections = emptyMap()
)

// Create a parcel and populate it with a ParcelableOperations object.
val marshalled = with(Parcel.obtain()) {
writeTypedObject(ParcelableRawEntity(expected), 0)
marshall()
}

// Now unmarshall the parcel, so we can verify the contents.
val unmarshalled = with(Parcel.obtain()) {
unmarshall(marshalled, 0, marshalled.size)
setDataPosition(0)
readReferencable()
}
assertThat(unmarshalled).isEqualTo(expected)
}
}
7 changes: 6 additions & 1 deletion javatests/arcs/core/storage/driver/DatabaseDriverTest.kt
Expand Up @@ -95,7 +95,12 @@ class DatabaseDriverTest {
calledWithVersion = version
}

assertThat(calledWithData).isEqualTo(entity.toCrdtEntityData(VersionMap()))
assertThat(calledWithData).isEqualTo(
entity.toCrdtEntityData(VersionMap()) {
if (it is Reference) it
else buildReference(it)
}
)
assertThat(calledWithVersion).isEqualTo(1)
}

Expand Down

0 comments on commit a695797

Please sign in to comment.