First local thread integration test
This commit is contained in:
parent
aadbf69f3a
commit
e482ef4262
4
.github/workflows/integration.yml
vendored
4
.github/workflows/integration.yml
vendored
@ -69,8 +69,8 @@ jobs:
|
|||||||
python3 -m venv .synapse
|
python3 -m venv .synapse
|
||||||
source .synapse/bin/activate
|
source .synapse/bin/activate
|
||||||
pip install synapse matrix-synapse
|
pip install synapse matrix-synapse
|
||||||
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
|
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
|
||||||
# curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
|
| sed s/127.0.0.1/0.0.0.0/g | bash
|
||||||
- name: Run integration tests on API ${{ matrix.api-level }}
|
- name: Run integration tests on API ${{ matrix.api-level }}
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
|
@ -184,18 +184,73 @@ class CommonTestHelper(context: Context) {
|
|||||||
/**
|
/**
|
||||||
* Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
|
* Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
|
||||||
*/
|
*/
|
||||||
private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
|
private fun sendTextMessagesBatched(room: Room, message: String, count: Int, rootThreadEventId: String? = null) {
|
||||||
(1 until count + 1)
|
(1 until count + 1)
|
||||||
.map { "$message #$it" }
|
.map { "$message #$it" }
|
||||||
.chunked(10)
|
.chunked(10)
|
||||||
.forEach { batchedMessages ->
|
.forEach { batchedMessages ->
|
||||||
batchedMessages.forEach { formattedMessage ->
|
batchedMessages.forEach { formattedMessage ->
|
||||||
|
if (rootThreadEventId != null) {
|
||||||
|
room.replyInThread(
|
||||||
|
rootThreadEventId = rootThreadEventId,
|
||||||
|
replyInThreadText = formattedMessage)
|
||||||
|
} else {
|
||||||
room.sendTextMessage(formattedMessage)
|
room.sendTextMessage(formattedMessage)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Thread.sleep(1_000L)
|
Thread.sleep(1_000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reply in a thread
|
||||||
|
* @param room the room where to send the messages
|
||||||
|
* @param message the message to send
|
||||||
|
* @param numberOfMessages the number of time the message will be sent
|
||||||
|
*/
|
||||||
|
fun replyInThreadMessage(
|
||||||
|
room: Room,
|
||||||
|
message: String,
|
||||||
|
numberOfMessages: Int,
|
||||||
|
rootThreadEventId: String,
|
||||||
|
timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||||
|
|
||||||
|
val sentEvents = ArrayList<TimelineEvent>(numberOfMessages)
|
||||||
|
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||||
|
timeline.start()
|
||||||
|
waitWithLatch(timeout + 1_000L * numberOfMessages) { latch ->
|
||||||
|
val timelineListener = object : Timeline.Listener {
|
||||||
|
override fun onTimelineFailure(throwable: Throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewTimelineEvents(eventIds: List<String>) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
|
||||||
|
val newMessages = snapshot
|
||||||
|
.filter { it.root.sendState == SendState.SYNCED }
|
||||||
|
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||||
|
.filter { it.root.getClearContent().toModel<MessageContent>()?.body?.startsWith(message) == true }
|
||||||
|
|
||||||
|
Timber.v("New synced message size: ${newMessages.size}")
|
||||||
|
if (newMessages.size == numberOfMessages) {
|
||||||
|
sentEvents.addAll(newMessages)
|
||||||
|
// Remove listener now, if not at the next update sendEvents could change
|
||||||
|
timeline.removeListener(this)
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeline.addListener(timelineListener)
|
||||||
|
sendTextMessagesBatched(room, message, numberOfMessages, rootThreadEventId)
|
||||||
|
}
|
||||||
|
timeline.dispose()
|
||||||
|
// Check that all events has been created
|
||||||
|
assertEquals("Message number do not match $sentEvents", numberOfMessages.toLong(), sentEvents.size.toLong())
|
||||||
|
return sentEvents
|
||||||
|
}
|
||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.session.room.threads
|
||||||
|
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeFalse
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.isThread
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class GenerateThreadMessageTests : InstrumentedTest {
|
||||||
|
|
||||||
|
private val commonTestHelper = CommonTestHelper(context())
|
||||||
|
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||||
|
|
||||||
|
private val logPrefix = "---Test--> "
|
||||||
|
|
||||||
|
// @Rule
|
||||||
|
// @JvmField
|
||||||
|
// val mRetryTestRule = RetryTestRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun reply_in_thread_to_normal_timeline_message_should_create_a_thread() {
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceRoomId = cryptoTestData.roomId
|
||||||
|
|
||||||
|
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
|
||||||
|
|
||||||
|
// Let's send a message in the normal timeline
|
||||||
|
val textMessage = "This is a normal timeline message"
|
||||||
|
val sentMessages = commonTestHelper.sendTextMessage(
|
||||||
|
room = aliceRoom,
|
||||||
|
message = textMessage,
|
||||||
|
nbOfMessages = 1)
|
||||||
|
|
||||||
|
val initMessage = sentMessages.first()
|
||||||
|
|
||||||
|
initMessage.root.isThread().shouldBeFalse()
|
||||||
|
initMessage.root.isTextMessage().shouldBeTrue()
|
||||||
|
initMessage.root.getRootThreadEventId().shouldBeNull()
|
||||||
|
initMessage.root.threadDetails?.isRootThread?.shouldBeFalse()
|
||||||
|
|
||||||
|
// Let's reply in timeline to that message
|
||||||
|
val repliesInThread = commonTestHelper.replyInThreadMessage(
|
||||||
|
room = aliceRoom,
|
||||||
|
message = "Reply In the above thread",
|
||||||
|
numberOfMessages = 1,
|
||||||
|
rootThreadEventId = initMessage.root.eventId.orEmpty())
|
||||||
|
|
||||||
|
val replyInThread = repliesInThread.first()
|
||||||
|
replyInThread.root.isThread().shouldBeTrue()
|
||||||
|
replyInThread.root.isTextMessage().shouldBeTrue()
|
||||||
|
replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
|
||||||
|
|
||||||
|
// The init normal message should now be a root thread event
|
||||||
|
val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
|
||||||
|
timeline.start()
|
||||||
|
|
||||||
|
aliceSession.startSync(true)
|
||||||
|
run {
|
||||||
|
val lock = CountDownLatch(1)
|
||||||
|
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||||
|
val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == initMessage.root.eventId }?.root?.threadDetails
|
||||||
|
initMessageThreadDetails?.isRootThread?.shouldBeTrue() ?: assert(false)
|
||||||
|
initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
|
||||||
|
Timber.e("$logPrefix $initMessageThreadDetails")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
timeline.addListener(eventsListener)
|
||||||
|
commonTestHelper.await(lock, 600_000)
|
||||||
|
}
|
||||||
|
aliceSession.stopSync()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.session.room.threads
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry test rule used to retry test that failed.
|
||||||
|
* Retry failed test 3 times
|
||||||
|
*/
|
||||||
|
class RetryTestRule(val retryCount: Int = 3) : TestRule {
|
||||||
|
|
||||||
|
private val TAG = RetryTestRule::class.java.simpleName
|
||||||
|
|
||||||
|
override fun apply(base: Statement, description: Description): Statement {
|
||||||
|
return statement(base, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun statement(base: Statement, description: Description): Statement {
|
||||||
|
return object : Statement() {
|
||||||
|
@Throws(Throwable::class)
|
||||||
|
override fun evaluate() {
|
||||||
|
var caughtThrowable: Throwable? = null
|
||||||
|
|
||||||
|
// implement retry logic here
|
||||||
|
for (i in 0 until retryCount) {
|
||||||
|
try {
|
||||||
|
base.evaluate()
|
||||||
|
return
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
caughtThrowable = t
|
||||||
|
Log.e(TAG, description.displayName + ": run " + (i + 1) + " failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, description.displayName + ": giving up after " + retryCount + " failures")
|
||||||
|
throw caughtThrowable!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user