From 00613da64c4db8a34adf6d88fd17abef92fed55d Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 10:31:49 +0100 Subject: [PATCH] Tests : create lambda recorder --- .../tests/testutils/lambda/Assertions.kt | 92 ++++++++++++++++++ .../tests/testutils/lambda/LambdaRecorder.kt | 97 +++++++++++++++++++ .../testutils/lambda/ParameterMatcher.kt | 41 ++++++++ 3 files changed, 230 insertions(+) create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt new file mode 100644 index 0000000000..4b08a160ba --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2024 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 io.element.android.tests.testutils.lambda + +fun assert(lambdaRecorder: LambdaRecorder): LambdaRecorderAssertions { + return lambdaRecorder.assertions() +} + +class LambdaRecorderAssertions internal constructor( + private val parametersSequence: List>, +) { + fun isCalledOnce(): CalledOnceParametersAssertions { + return CalledOnceParametersAssertions( + assertions = isCalledExactly(1) + ) + } + + fun isNeverCalled() { + isCalledExactly(0) + } + + fun isCalledExactly(times: Int): ParametersAssertions { + if (parametersSequence.size != times) { + throw AssertionError("Expected to be called $times, but was called ${parametersSequence.size} times") + } + return ParametersAssertions(parametersSequence) + } +} + +class CalledOnceParametersAssertions internal constructor(private val assertions: ParametersAssertions) { + fun with(vararg matchers: ParameterMatcher) { + assertions.withSequence(matchers.toList()) + } + + fun withNoParameter() { + assertions.withNoParameter() + } +} + +class ParametersAssertions internal constructor( + private val parametersSequence: List> +) { + fun withSequence(vararg matchersSequence: List) { + if (parametersSequence.size != matchersSequence.size) { + throw AssertionError("Expected ${matchersSequence.size} parameters, but got ${parametersSequence.size} parameters") + } + parametersSequence.zip(matchersSequence).forEach { (parameters, matchers) -> + if (parameters.size != matchers.size) { + throw AssertionError("Expected ${matchers.size} parameters, but got ${parameters.size} parameters") + } + parameters.zip(matchers).forEachIndexed { j, (param, matcher) -> + if (!matcher.match(param)) { + throw AssertionError("Parameter $j does not match the expected value") + } + } + } + for (i in parametersSequence.indices) { + val params = parametersSequence[i] + val checker = matchersSequence[i] + if (params.size != checker.size) { + throw AssertionError("Expected ${checker.size} parameters, but got ${params.size} parameters") + } + for (j in params.indices) { + val param = params[j] + val check = checker[j] + if (!check.match(param)) { + throw AssertionError("Parameter $j does not match the expected value") + } + } + } + } + + fun withNoParameter() { + if (parametersSequence.any { it.isNotEmpty() }) { + throw AssertionError("Expected no parameters, but got some") + } + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt new file mode 100644 index 0000000000..52c3dc63a8 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 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 io.element.android.tests.testutils.lambda + +/** + * A recorder that can be used to record the parameters of lambda invocation. + */ +abstract class LambdaRecorder internal constructor() { + private val parametersSequence: MutableList> = mutableListOf() + + internal fun onInvoke(vararg params: Any?) { + parametersSequence.add(params.toList()) + } + + fun assertions(): LambdaRecorderAssertions { + return LambdaRecorderAssertions(parametersSequence = parametersSequence) + } +} + +inline fun lambdaRecorder( + noinline block: () -> R +): LambdaNoParamRecorder { + return LambdaNoParamRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T) -> R +): LambdaOneParamRecorder { + return LambdaOneParamRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2) -> R +): LambdaTwoParamsRecorder { + return LambdaTwoParamsRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2, T3) -> R +): LambdaThreeParamsRecorder { + return LambdaThreeParamsRecorder(block) +} + +inline fun lambdaRecorder( + noinline block: (T1, T2, T3, T4) -> R +): LambdaFourParamsRecorder { + return LambdaFourParamsRecorder(block) +} + +class LambdaNoParamRecorder(val block: () -> R) : LambdaRecorder(), () -> R { + override fun invoke(): R { + onInvoke() + return block() + } +} + +class LambdaOneParamRecorder(val block: (T) -> R) : LambdaRecorder(), (T) -> R { + override fun invoke(p: T): R { + onInvoke(p) + return block(p) + } +} + +class LambdaTwoParamsRecorder(val block: (T1, T2) -> R) : LambdaRecorder(), (T1, T2) -> R { + override fun invoke(p1: T1, p2: T2): R { + onInvoke(p1, p2) + return block(p1, p2) + } +} + +class LambdaThreeParamsRecorder(val block: (T1, T2, T3) -> R) : LambdaRecorder(), (T1, T2, T3) -> R { + override fun invoke(p1: T1, p2: T2, p3: T3): R { + onInvoke(p1, p2, p3) + return block(p1, p2, p3) + } +} + +class LambdaFourParamsRecorder(val block: (T1, T2, T3, T4) -> R) : LambdaRecorder(), (T1, T2, T3, T4) -> R { + override fun invoke(p1: T1, p2: T2, p3: T3, p4: T4): R { + onInvoke(p1, p2, p3, p4) + return block(p1, p2, p3, p4) + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt new file mode 100644 index 0000000000..b070204d28 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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 io.element.android.tests.testutils.lambda + +/** + * A matcher that can be used to match parameters in lambda calls. + * This is useful to assert that a lambda has been called with specific parameters. + */ +interface ParameterMatcher { + fun match(param: Any?): Boolean +} + +/** + * A matcher that matches a specific value. + * Can be used to assert that a lambda has been called with a specific value. + */ +fun value(expectedValue: T) = object : ParameterMatcher { + override fun match(param: Any?) = param == expectedValue +} + +/** + * A matcher that matches any value. + * Can be used when we don't care about the value of a parameter. + */ +fun any() = object : ParameterMatcher { + override fun match(param: Any?) = true +}