Support parsing multiplatform tests

commit_hash:30c0f59f613adf7914f390c20f751136e7b34b75
This commit is contained in:
grechka62
2026-04-29 15:32:34 +03:00
parent 1d14f691f1
commit cf1e5515ff
28 changed files with 272 additions and 23 deletions
+3
View File
@@ -22,6 +22,9 @@ dependencies {
api(libs.androidx.core)
testImplementation(project(":test-utils"))
testImplementation(libs.json)
testImplementation(libs.kotlin.reflect)
}
@@ -774,7 +774,7 @@ public class JsonPropertyParser {
@Nullable final List<V> list,
@NonNull final Function1<V, R> converter
) {
if (list != null && !list.isEmpty()) {
if (list != null) {
int length = list.size();
JSONArray array = new JSONArray();
for (int i = 0; i < length; i++) {
@@ -796,7 +796,7 @@ public class JsonPropertyParser {
@Nullable final List<V> list,
@NonNull final Lazy<Serializer<JSONObject, V>> serializer
) {
if (list != null && !list.isEmpty()) {
if (list != null) {
int length = list.size();
JSONArray array = new JSONArray();
@@ -0,0 +1,103 @@
package com.yandex.div.internal.parser
import com.yandex.div.data.DivParsingEnvironment
import com.yandex.div.internal.util.forEach
import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.test.crossplatform.ParsingResult
import com.yandex.div.test.crossplatform.ParsingUtils.parseFiles
import com.yandex.div.test.crossplatform.isForAndroid
import com.yandex.div2.DivData
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
class ParsingMultiplatformTest(testCaseParsingResult: ParsingResult<ParsingTestCase>) {
private val testCase = testCaseParsingResult.getOrThrow()
private val errors = mutableListOf<Throwable>()
@Test
fun run() {
val logger = ParsingErrorLogger { errors += it }
val env = DivParsingEnvironment(logger)
val divData = runCatching {
testCase.templates?.let { env.parseTemplates(it) }
DivData(env, testCase.card)
}.getOrNull()
assertCardEquals(testCase.expectedResult.card, divData?.writeToJSON())
Assert.assertEquals("Unexpected error count:", testCase.expectedResult.errorCount, errors.size)
}
private fun assertCardEquals(expected: JSONObject?, actual: JSONObject?) {
expected ?: return Assert.assertNull(actual)
actual ?: return Assert.fail(
"Failed to parse DivData. Errors: ${errors.joinToString(", ") { it.message ?: "" }}"
)
runCatching {
expected.compareValues("", actual)
}.onFailure {
Assert.fail(it.message)
}
}
private fun JSONObject.compareValues(parentPath: String, actual: JSONObject): Boolean {
forEach { key, expectedValue: Any? ->
val actualValue = actual.opt(key)
val path = parentPath.takeIf { it.isNotEmpty() }?.let { "$it/$key" } ?: key
if (!expectedValue.compare(path, actualValue)) {
throwError(path, expectedValue, actualValue)
}
}
return true
}
private fun Any?.compare(path: String, actual: Any?): Boolean {
return when (this) {
JSONObject.NULL -> actual == null || actual == this
is JSONObject -> (actual as? JSONObject)?.let { compareValues(path, it) } ?: false
is JSONArray -> (actual as? JSONArray)?.let { compareValues(path, it)} ?: false
is Int -> actual == this || (actual as? Long)?.toInt() == this
else -> actual == this
}
}
private fun JSONArray.compareValues(parentPath: String, actual: JSONArray): Boolean {
forEach { i, expectedValue: Any? ->
val actualValue = actual.opt(i)
val path = parentPath.takeIf { it.isNotEmpty() }?.let { "$it/$i" } ?: "$i"
if (!expectedValue.compare(path, actualValue)) {
throwError(path, expectedValue, actualValue)
}
}
return true
}
private fun throwError(path: String, expected: Any?, actual: Any?): Nothing =
throw Exception("DivData comparison failed for path '$path', expected='$expected', actual='$actual'")
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun cases(): List<ParsingResult<ParsingTestCase>> {
return parseFiles("parsing_test_data") { file, jsonString ->
val json = JSONObject(jsonString)
if (!json.isForAndroid) return@parseFiles emptyList()
val result = try {
val testCase = ParsingTestCase.from(file.name, json)
ParsingResult.Success(testCase)
} catch (e: Exception) {
ParsingResult.Error(fileName = file.name, error = e)
}
listOf(result)
}
}
}
}
@@ -0,0 +1,35 @@
package com.yandex.div.internal.parser
import org.json.JSONException
import org.json.JSONObject
class ParsingTestCase(
val name: String,
val templates: JSONObject?,
val card: JSONObject,
val expectedResult: ExpectedResult,
) {
class ExpectedResult(
val errorCount: Int?,
val card: JSONObject?,
)
override fun toString() = name
companion object {
@Throws(JSONException::class)
fun from(name: String, json: JSONObject): ParsingTestCase {
val expectedResult = json.getJSONObject("expected")
return ParsingTestCase(
name = name,
templates = json.optJSONObject("templates"),
card = json.getJSONObject("card"),
expectedResult = ExpectedResult(
errorCount = expectedResult.optInt("error_count"),
card = expectedResult.optJSONObject("card"),
),
)
}
}
}
@@ -8,7 +8,6 @@ data class ExpressionTestCase(
val expression: String,
val variables: List<JSONObject>,
val functions: List<JSONObject>,
val platform: List<String>,
val expectedType: String,
val expectedValue: Any,
val expectedWarnings: List<String>,
@@ -10,7 +10,6 @@ import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.test.crossplatform.ParsingResult
import com.yandex.div.test.crossplatform.isForAndroid
import com.yandex.div.test.crossplatform.parseDateTime
import com.yandex.div.test.crossplatform.platforms
import com.yandex.div.test.crossplatform.toObjectList
import com.yandex.div2.DivData
import com.yandex.div2.DivEvaluableType
@@ -64,7 +63,6 @@ object ExpressionTestCaseUtils {
json.getString(CASE_EXPRESSION_VALUE_FIELD),
json.optJSONArray(CASE_VARIABLES_FIELD).toObjectList(),
json.optJSONArray(CASE_FUNCTIONS_FIELD).toObjectList(),
json.platforms,
json.getJSONObject(CASE_EXPECTED_VALUE_FIELD).type,
json.getJSONObject(CASE_EXPECTED_VALUE_FIELD).getValue(),
json.optJSONArray(CASE_EXPECTED_WARNINGS_FIELD)?.map { it as String } ?: emptyList(),
@@ -55,11 +55,11 @@ object ParsingUtils {
}
}
val JSONObject.platforms: List<String>
get() = getJSONArray("platforms").map { it as String }
val JSONObject.platforms: List<String>?
get() = optJSONArray("platforms")?.map { it as String }
val JSONObject.isForAndroid: Boolean
get() = platforms.contains("android")
get() = platforms?.contains("android") ?: true
fun JSONArray?.toObjectList(): List<JSONObject> {
if (this == null) {