Start introducing tests

This commit is contained in:
ganfra 2018-12-11 15:36:09 +01:00
parent 683305030a
commit 0266380485
7 changed files with 186 additions and 52 deletions

View File

@ -20,6 +20,7 @@ repositories {

android {
compileSdkVersion 28
testOptions.unitTests.includeAndroidResources = true

defaultConfig {
minSdkVersion 21
@ -45,6 +46,7 @@ dependencies {
def support_version = '28.0.0'
def moshi_version = '1.8.0'
def lifecycle_version = "1.1.1"
def powermock_version = "2.0.0-RC.4"

implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -94,7 +96,14 @@ dependencies {


testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.0.2'
testImplementation 'org.robolectric:shadows-support-v4:3.0'
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
testImplementation 'org.amshove.kluent:kluent-android:1.44'

androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'


}

View File

@ -1,26 +0,0 @@
package im.vector.matrix.android;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("im.vector.matrix.android.test", appContext.getPackageName());
}
}

View File

@ -0,0 +1,15 @@
package im.vector.matrix.android

import android.content.Context
import android.support.test.InstrumentationRegistry
import java.io.File

abstract class InstrumentedTest {
fun context(): Context {
return InstrumentationRegistry.getTargetContext()
}

fun cacheDir(): File {
return context().cacheDir
}
}

View File

@ -0,0 +1,147 @@
package im.vector.matrix.android.session.room.timeline

import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.helper.add
import im.vector.matrix.android.internal.database.helper.addAll
import im.vector.matrix.android.internal.database.helper.isUnlinked
import im.vector.matrix.android.internal.database.helper.lastStateIndex
import im.vector.matrix.android.internal.database.helper.merge
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.createObject
import org.amshove.kluent.shouldEqual
import org.junit.Before
import org.junit.Test
import kotlin.random.Random


internal class ChunkEntityTest : InstrumentedTest() {

private lateinit var monarchy: Monarchy

@Before
fun setup() {
Realm.init(context())
val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build()
monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build()
}


@Test
fun add_shouldAdd_whenNotAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeEvent(false)
chunk.add(fakeEvent, PaginationDirection.FORWARDS)
chunk.events.size shouldEqual 1
}
}

@Test
fun add_shouldNotAdd_whenAlreadyIncluded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeEvent(false)
chunk.add(fakeEvent, PaginationDirection.FORWARDS)
chunk.add(fakeEvent, PaginationDirection.FORWARDS)
chunk.events.size shouldEqual 1
}
}

@Test
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeEvent(true)
chunk.add(fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
}
}

@Test
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvent = createFakeEvent(false)
chunk.add(fakeEvent, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
}
}

@Test
fun addAll_shouldStateIndexIncremented_whenStateEventsAreAddedForward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
chunk.addAll(fakeEvents, PaginationDirection.FORWARDS)
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual numberOfStateEvents
}
}

@Test
fun addAll_shouldStateIndexDecremented_whenStateEventsAreAddedBackward() {
monarchy.runTransactionSync { realm ->
val chunk: ChunkEntity = realm.createObject()
val fakeEvents = createFakeListOfEvents(30)
val numberOfStateEvents = fakeEvents.filter { it.isStateEvent() }.size
val lastIsState = fakeEvents.last().isStateEvent()
val expectedStateIndex = if (lastIsState) -numberOfStateEvents + 1 else -numberOfStateEvents
chunk.addAll(fakeEvents, PaginationDirection.BACKWARDS)
chunk.lastStateIndex(PaginationDirection.BACKWARDS) shouldEqual expectedStateIndex
}
}

@Test
fun merge_shouldAddEvents_whenMergingBackward() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
chunk1.events.size shouldEqual 60
}
}

@Test
fun merge_shouldEventsBeLinked_whenMergingLinkedWithUnlinked() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = false)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked() shouldEqual false
}
}

@Test
fun merge_shouldEventsBeUnlinked_whenMergingUnlinkedWithUnlinked() {
monarchy.runTransactionSync { realm ->
val chunk1: ChunkEntity = realm.createObject()
val chunk2: ChunkEntity = realm.createObject()
chunk1.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk2.addAll(createFakeListOfEvents(30), PaginationDirection.BACKWARDS, isUnlinked = true)
chunk1.merge(chunk2, PaginationDirection.BACKWARDS)
chunk1.isUnlinked() shouldEqual true
}
}


private fun createFakeListOfEvents(size: Int = 10): List<Event> {
return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
}

private fun createFakeEvent(asStateEvent: Boolean = false): Event {
val eventId = Random.nextLong(System.currentTimeMillis()).toString()
val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
return Event(type, eventId)
}

}

View File

@ -2,7 +2,6 @@ package im.vector.matrix.android.internal.database.helper

import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
@ -41,7 +40,7 @@ internal fun ChunkEntity.merge(chunkToMerge: ChunkEntity,
eventsToMerge = chunkToMerge.events
}
eventsToMerge.forEach {
add(it.asDomain(), direction, isUnlinked = isUnlinked)
add(it, direction, isUnlinked = isUnlinked)
}
}

@ -63,16 +62,23 @@ internal fun ChunkEntity.add(event: Event,
direction: PaginationDirection,
stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) {
add(event.asEntity(), direction, stateIndexOffset, isUnlinked)
}

internal fun ChunkEntity.add(eventEntity: EventEntity,
direction: PaginationDirection,
stateIndexOffset: Int = 0,
isUnlinked: Boolean = false) {
if (!isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains")
}

if (event.eventId == null || events.fastContains(event.eventId)) {
if (eventEntity.eventId.isEmpty() || events.fastContains(eventEntity.eventId)) {
return
}

var currentStateIndex = lastStateIndex(direction, defaultValue = stateIndexOffset)
if (direction == PaginationDirection.FORWARDS && event.isStateEvent()) {
if (direction == PaginationDirection.FORWARDS && EventType.isStateEvent(eventEntity.type)) {
currentStateIndex += 1
} else if (direction == PaginationDirection.BACKWARDS && events.isNotEmpty()) {
val lastEventType = events.last()?.type ?: ""
@ -81,7 +87,6 @@ internal fun ChunkEntity.add(event: Event,
}
}

val eventEntity = event.asEntity()
eventEntity.stateIndex = currentStateIndex
eventEntity.isUnlinked = isUnlinked
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
@ -90,7 +95,7 @@ internal fun ChunkEntity.add(event: Event,

internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING).findFirst()?.stateIndex
PaginationDirection.BACKWARDS -> events.where().sort(EventEntityFields.STATE_INDEX, Sort.ASCENDING).findFirst()?.stateIndex
} ?: defaultValue
}

View File

@ -69,6 +69,7 @@ internal fun RealmList<EventEntity>.find(eventId: String): EventEntity? {
return this.where().equalTo(EventEntityFields.EVENT_ID, eventId).findFirst()
}

internal fun RealmList<EventEntity>.fastContains(eventId: String): Boolean {
internal fun RealmList<EventEntity>.
fastContains(eventId: String): Boolean {
return this.find(eventId) != null
}

View File

@ -1,17 +0,0 @@
package im.vector.matrix.android;

import org.junit.Test;

import static org.junit.Assert.*;

/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}