diff --git a/api/android/revanced-library.api b/api/android/revanced-library.api index 728d350..cf91bf6 100644 --- a/api/android/revanced-library.api +++ b/api/android/revanced-library.api @@ -88,6 +88,15 @@ public final class app/revanced/library/Options$Patch$Option { public final fun getValue ()Ljava/lang/Object; } +public final class app/revanced/library/OptionsKt { + public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V +} + +public final class app/revanced/library/PatchKt { + public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; + public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map; +} + public final class app/revanced/library/PatchUtils { public static final field INSTANCE Lapp/revanced/library/PatchUtils; public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; @@ -122,6 +131,11 @@ public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPa public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch { } +public final class app/revanced/library/SerializationKt { + public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V + public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V +} + public final class app/revanced/library/Utils { public static final field INSTANCE Lapp/revanced/library/Utils; public final fun isAndroidEnvironment ()Z diff --git a/api/jvm/revanced-library.api b/api/jvm/revanced-library.api index 6587605..dff6f4a 100644 --- a/api/jvm/revanced-library.api +++ b/api/jvm/revanced-library.api @@ -88,6 +88,15 @@ public final class app/revanced/library/Options$Patch$Option { public final fun getValue ()Ljava/lang/Object; } +public final class app/revanced/library/OptionsKt { + public static final fun setOptions (Ljava/util/Set;Ljava/util/Map;)V +} + +public final class app/revanced/library/PatchKt { + public static final fun mostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; + public static synthetic fun mostCommonCompatibleVersions$default (Ljava/util/Set;Ljava/util/Set;ZILjava/lang/Object;)Ljava/util/Map; +} + public final class app/revanced/library/PatchUtils { public static final field INSTANCE Lapp/revanced/library/PatchUtils; public final fun getMostCommonCompatibleVersions (Ljava/util/Set;Ljava/util/Set;Z)Ljava/util/Map; @@ -122,6 +131,11 @@ public final class app/revanced/library/PatchUtils$Json$FullJsonPatch$FullJsonPa public abstract interface class app/revanced/library/PatchUtils$Json$JsonPatch { } +public final class app/revanced/library/SerializationKt { + public static final fun serializeTo (Ljava/util/Set;Ljava/io/OutputStream;Z)V + public static synthetic fun serializeTo$default (Ljava/util/Set;Ljava/io/OutputStream;ZILjava/lang/Object;)V +} + public final class app/revanced/library/Utils { public static final field INSTANCE Lapp/revanced/library/Utils; public final fun isAndroidEnvironment ()Z diff --git a/build.gradle.kts b/build.gradle.kts index 643dd26..fdad00f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,8 @@ plugins { - alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.library) alias(libs.plugins.binary.compatibility.validator) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) `maven-publish` signing } @@ -47,25 +48,26 @@ kotlin { sourceSets { androidMain.dependencies { + implementation(libs.core.ktx) implementation(libs.libsu.nio) implementation(libs.libsu.service) - implementation(libs.core.ktx) } commonMain.dependencies { - implementation(libs.revanced.patcher) - implementation(libs.kotlin.reflect) - implementation(libs.jadb) // Fork with Shell v2 support. - implementation(libs.bcpkix.jdk15on) - implementation(libs.jackson.module.kotlin) - implementation(libs.apkzlib) implementation(libs.apksig) + implementation(libs.apkzlib) + implementation(libs.bcpkix.jdk15on) implementation(libs.guava) + implementation(libs.jadb) + implementation(libs.jackson.module.kotlin) + implementation(libs.kotlin.reflect) + implementation(libs.kotlinx.serialization.json) + implementation(libs.revanced.patcher) } commonTest.dependencies { - implementation(libs.revanced.patcher) implementation(libs.kotlin.test.junit) + implementation(libs.revanced.patcher) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a7c7c9a..3a881e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,9 @@ [versions] +android = "8.5.1" +bcpkix-jdk15on = "1.70" +binary-compatibility-validator = "0.15.1" +core-ktx = "1.13.1" +guava = "33.0.0-jre" jackson-module-kotlin = "2.16.1" jadb = "1.2.1" kotlin = "2.0.0" @@ -8,22 +13,25 @@ libsu = "5.2.2" revanced-patcher = "20.0.0" [libraries] -jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" } -jadb = { module = "app.revanced:jadb", version.ref = "jadb" } -kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } -revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } apkzlib = { module = "com.android.tools.build:apkzlib", version.ref = "android" } apksig = { module = "com.android.tools.build:apksig", version.ref = "android" } bcpkix-jdk15on = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bcpkix-jdk15on" } +core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } guava = { module = "com.google.guava:guava", version.ref = "guava" } +jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson-module-kotlin" } +jadb = { module = "app.revanced:jadb", version.ref = "jadb" } # Fork with Shell v2 support. +kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } +kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" } libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" } -core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } +revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" } [plugins] -binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } android-library = { id = "com.android.library", version.ref = "android" } +binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/src/commonMain/kotlin/app/revanced/library/Options.kt b/src/commonMain/kotlin/app/revanced/library/Options.kt index 98bd2f6..3775f3c 100644 --- a/src/commonMain/kotlin/app/revanced/library/Options.kt +++ b/src/commonMain/kotlin/app/revanced/library/Options.kt @@ -8,7 +8,32 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import java.io.File import java.util.logging.Logger +private val logger = Logger.getLogger("Options") + +typealias PatchName = String +typealias OptionKey = String +typealias OptionValue = Any? +typealias PatchesOptions = Map> + +/** + * Set the options for a set of patches that have a name. + * + * @param options The options to set. The key is the patch name and the value is a map of option keys to option values. + */ +fun Set>.setOptions(options: PatchesOptions) = filter { it.name != null }.forEach { patch -> + val patchOptions = options[patch.name] ?: return@forEach + + patch.options.forEach option@{ option -> + try { + patch.options[option.key] = patchOptions[option.key] ?: return@option + } catch (e: OptionException) { + logger.warning("Could not set option value for the \"${patch.name}\" patch: ${e.message}") + } + } +} + @Suppress("unused") +@Deprecated("Functions have been moved to top level.") object Options { private val logger = Logger.getLogger(Options::class.java.name) @@ -21,6 +46,7 @@ object Options { * @param prettyPrint Whether to pretty print the JSON. * @return The JSON string containing the options. */ + @Deprecated("Functions have been moved to the Serialization class.") fun serialize( patches: Set>, prettyPrint: Boolean = false, @@ -35,7 +61,7 @@ object Options { try { option.value } catch (e: OptionException) { - logger.warning("Using default option value for the ${patch.name} patch: ${e.message}") + logger.warning("Using default option value for the \"${patch.name}\" patch: ${e.message}") option.default } @@ -60,6 +86,7 @@ object Options { * @return A set of [Patch]s. * @see Patch */ + @Deprecated("Functions have been moved to the Serialization class.") fun deserialize(json: String): Array = mapper.readValue(json, Array::class.java) /** @@ -67,26 +94,16 @@ object Options { * * @param json The JSON string containing the options. */ + @Deprecated("Function has been moved to top level.") fun Set>.setOptions(json: String) { filter { it.options.any() }.let { patches -> if (patches.isEmpty()) return - val jsonPatches = - deserialize(json).associate { - it.patchName to it.options.associate { option -> option.key to option.value } - } - - patches.forEach { patch -> - jsonPatches[patch.name]?.let { jsonPatchOptions -> - jsonPatchOptions.forEach { (option, value) -> - try { - patch.options[option] = value - } catch (e: OptionException) { - logger.warning("Could not set option value for the ${patch.name} patch: ${e.message}") - } - } - } + val jsonPatches = deserialize(json).associate { + it.patchName to it.options.associate { option -> option.key to option.value } } + + setOptions(jsonPatches) } } @@ -96,6 +113,7 @@ object Options { * @param file The file containing the JSON string containing the options. * @see setOptions */ + @Deprecated("Function has been moved to top level.") fun Set>.setOptions(file: File) = setOptions(file.readText()) /** diff --git a/src/commonMain/kotlin/app/revanced/library/Patch.kt b/src/commonMain/kotlin/app/revanced/library/Patch.kt new file mode 100644 index 0000000..3595c77 --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/Patch.kt @@ -0,0 +1,52 @@ +package app.revanced.library + +import app.revanced.patcher.patch.Package +import app.revanced.patcher.patch.Patch + +typealias PackageName = String +typealias Version = String +typealias Count = Int + +typealias VersionMap = LinkedHashMap +typealias PackageNameMap = Map + +/** + * Get the count of versions for each compatible package from the set of [Patch] ordered by the most common version. + * + * @param packageNames The names of the compatible packages to include. If null, all packages will be included. + * @param countUnusedPatches Whether to count patches that are not used. + * @return A map of package names to a map of versions to their count. + */ +fun Set>.mostCommonCompatibleVersions( + packageNames: Set? = null, + countUnusedPatches: Boolean = false, +): PackageNameMap = buildMap { + fun filterWantedPackages(compatiblePackages: List): List { + val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages + return compatiblePackages.filter { (name, _) -> name in wantedPackages } + } + + this@mostCommonCompatibleVersions.filter { it.use || countUnusedPatches } + .flatMap { it.compatiblePackages ?: emptyList() } + .let(::filterWantedPackages) + .forEach { (name, versions) -> + if (versions?.isEmpty() == true) { + return@forEach + } + + val versionMap = getOrPut(name) { linkedMapOf() } + + versions?.forEach { version -> + versionMap[version] = versionMap.getOrDefault(version, 0) + 1 + } + } + + // Sort the version maps by the most common version. + forEach { (packageName, versionMap) -> + this[packageName] = + versionMap + .asIterable() + .sortedWith(compareByDescending { it.value }) + .associate { it.key to it.value } as VersionMap + } +} diff --git a/src/commonMain/kotlin/app/revanced/library/PatchUtils.kt b/src/commonMain/kotlin/app/revanced/library/PatchUtils.kt index a8dc271..23221e4 100644 --- a/src/commonMain/kotlin/app/revanced/library/PatchUtils.kt +++ b/src/commonMain/kotlin/app/revanced/library/PatchUtils.kt @@ -8,63 +8,19 @@ import java.io.InputStream import java.io.OutputStream import kotlin.reflect.KType -typealias PackageName = String -typealias Version = String -typealias Count = Int - -typealias VersionMap = LinkedHashMap -typealias PackageNameMap = Map - -/** - * Utility functions for working with patches. - */ -@Suppress("MemberVisibilityCanBePrivate", "unused") +@Deprecated("Functions have been moved to top level.") object PatchUtils { - /** - * Get the count of versions for each compatible package from a supplied set of [patches] ordered by the most common version. - * - * @param patches The set of patches to check. - * @param packageNames The names of the compatible packages to include. If null, all packages will be included. - * @param countUnusedPatches Whether to count patches that are not used. - * @return A map of package names to a map of versions to their count. - */ + @Deprecated( + "Function has been moved to top level.", + ReplaceWith("patches.mostCommonCompatibleVersions(packageNames, countUnusedPatches)"), + ) fun getMostCommonCompatibleVersions( patches: Set>, packageNames: Set? = null, countUnusedPatches: Boolean = false, - ): PackageNameMap = - buildMap { - fun filterWantedPackages(compatiblePackages: Iterable): Iterable { - val wantedPackages = packageNames?.toHashSet() ?: return compatiblePackages - return compatiblePackages.filter { (name, _) -> name in wantedPackages } - } - - patches - .filter { it.use || countUnusedPatches } - .flatMap { it.compatiblePackages ?: emptyList() } - .let(::filterWantedPackages) - .forEach { (name, versions) -> - if (versions?.isEmpty() == true) { - return@forEach - } - - val versionMap = getOrPut(name) { linkedMapOf() } - - versions?.forEach { version -> - versionMap[version] = versionMap.getOrDefault(version, 0) + 1 - } - } - - // Sort the version maps by the most common version. - forEach { (packageName, versionMap) -> - this[packageName] = - versionMap - .asIterable() - .sortedWith(compareByDescending { it.value }) - .associate { it.key to it.value } as VersionMap - } - } + ): PackageNameMap = patches.mostCommonCompatibleVersions(packageNames, countUnusedPatches) + @Deprecated("Functions have been moved to the Serialization class.") object Json { private val mapper = jacksonObjectMapper() @@ -76,6 +32,7 @@ object PatchUtils { * @param prettyPrint Whether to pretty print the JSON. * @param outputStream The output stream to write the JSON to. */ + @Deprecated("Functions have been moved to the Serialization class.") fun serialize( patches: Set>, transform: (Patch<*>) -> JsonPatch = { patch -> FullJsonPatch.fromPatch(patch) }, @@ -99,6 +56,7 @@ object PatchUtils { * @return A set of [JsonPatch]es. * @see FullJsonPatch */ + @Deprecated("This function will be removed in the future.") fun deserialize( inputStream: InputStream, jsonPatchElementClass: Class, diff --git a/src/commonMain/kotlin/app/revanced/library/Serialization.kt b/src/commonMain/kotlin/app/revanced/library/Serialization.kt new file mode 100644 index 0000000..1b1641a --- /dev/null +++ b/src/commonMain/kotlin/app/revanced/library/Serialization.kt @@ -0,0 +1,114 @@ +package app.revanced.library + +import app.revanced.patcher.patch.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.* +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeStructure +import kotlinx.serialization.json.* +import java.io.OutputStream + +private class PatchSerializer : KSerializer> { + override val descriptor = buildClassSerialDescriptor("Patch") { + element("name") + element("description") + element("use") + element>("dependencies") + element?>("compatiblePackages") + element("options", OptionSerializer.descriptor) + } + + override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported") + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: Patch<*>) { + encoder.encodeStructure(descriptor) { + encodeNullableSerializableElement( + descriptor, + 0, + String.serializer(), + value.name, + ) + encodeNullableSerializableElement( + descriptor, + 1, + String.serializer(), + value.description, + ) + encodeBooleanElement( + descriptor, + 2, + value.use, + ) + encodeSerializableElement( + descriptor, + 3, + ListSerializer(String.serializer()), + value.dependencies.map { it.name ?: it.toString() }, + ) + encodeNullableSerializableElement( + descriptor, + 4, + SetSerializer(PairSerializer(String.serializer(), SetSerializer(String.serializer()).nullable)), + value.compatiblePackages, + ) + encodeSerializableElement( + descriptor, + 5, + SetSerializer(OptionSerializer), + value.options.values.toSet(), + ) + } + } + + private object OptionSerializer : KSerializer> { + override val descriptor = buildClassSerialDescriptor("Option") { + element("key") + element("title") + element("description") + element("required") + // Type does not matter for serialization. Using String. + element("type") + element("default") + // Map value type does not matter for serialization. Using String. + element?>("values") + } + + override fun deserialize(decoder: Decoder) = throw NotImplementedError("Deserialization is unsupported") + + @OptIn(ExperimentalSerializationApi::class) + override fun serialize(encoder: Encoder, value: Option<*>) { + encoder.encodeStructure(descriptor) { + encodeStringElement(descriptor, 0, value.key) + encodeNullableSerializableElement(descriptor, 1, String.serializer(), value.title) + encodeNullableSerializableElement(descriptor, 2, String.serializer(), value.description) + encodeBooleanElement(descriptor, 3, value.required) + encodeSerializableElement(descriptor, 4, String.serializer(), value.type.toString()) + encodeNullableSerializableElement(descriptor, 5, serializer(value.type), value.default) + encodeNullableSerializableElement(descriptor, 6, MapSerializer(String.serializer(), serializer(value.type)), value.values) + } + } + } +} + +private val patchPrettySerializer by lazy { Json { prettyPrint = true } } +private val patchSerializer by lazy { Json } + +/** + * Serialize this set of [Patch] to JSON and write it to the given [outputStream]. + * + * @param outputStream The output stream to write the JSON to. + * @param prettyPrint Whether to pretty print the JSON. + */ +@OptIn(ExperimentalSerializationApi::class) +fun Set>.serializeTo( + outputStream: OutputStream, + prettyPrint: Boolean = true, +) = if (prettyPrint) { + patchPrettySerializer +} else { + patchSerializer +}.encodeToStream(SetSerializer(PatchSerializer()), this, outputStream) diff --git a/src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt b/src/commonTest/kotlin/app/revanced/library/MostCommonCompatibleVersionsTest.kt similarity index 73% rename from src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt rename to src/commonTest/kotlin/app/revanced/library/MostCommonCompatibleVersionsTest.kt index dfb3f36..e025dd2 100644 --- a/src/commonTest/kotlin/app/revanced/library/PatchUtilsTest.kt +++ b/src/commonTest/kotlin/app/revanced/library/MostCommonCompatibleVersionsTest.kt @@ -1,27 +1,19 @@ package app.revanced.library -import app.revanced.patcher.PatchSet -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption -import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.intArrayPatchOption -import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream +import app.revanced.patcher.patch.* import kotlin.test.Test import kotlin.test.assertEquals -internal class PatchUtilsTest { +internal class MostCommonCompatibleVersionsTest { private val patches = arrayOf( - newPatch("some.package", setOf("a")) { stringPatchOption("string", "value") }, + newPatch("some.package", setOf("a")) { stringOption("string", "value") }, newPatch("some.package", setOf("a", "b"), use = false), newPatch("some.package", setOf("a", "b", "c"), use = false), newPatch("some.other.package", setOf("b"), use = false), - newPatch("some.other.package", setOf("b", "c")) { booleanPatchOption("bool", true) }, + newPatch("some.other.package", setOf("b", "c")) { booleanOption("bool", true) }, newPatch("some.other.package", setOf("b", "c", "d")), - newPatch("some.other.other.package") { intArrayPatchOption("intArray", arrayOf(1, 2, 3)) }, + newPatch("some.other.other.package") { intsOption("intArray", listOf(1, 2, 3)) }, newPatch("some.other.other.package", setOf("a")), newPatch("some.other.other.package", setOf("b")), newPatch("some.other.other.other.package", use = false), @@ -141,38 +133,24 @@ internal class PatchUtilsTest { assertEqualsVersion(null, patches, "other.package") } - @Test - fun `serializes to and deserializes from JSON string correctly`() { - val out = ByteArrayOutputStream() - PatchUtils.Json.serialize(patches, outputStream = out) - - val deserialized = - PatchUtils.Json.deserialize( - ByteArrayInputStream(out.toByteArray()), - PatchUtils.Json.FullJsonPatch::class.java, - ) - - assert(patches.size == deserialized.size) - } - private fun assertEqualsVersions( expected: PackageNameMap, - patches: PatchSet, + patches: Set>, compatiblePackageNames: Set?, countUnusedPatches: Boolean = false, ) = assertEquals( expected, - PatchUtils.getMostCommonCompatibleVersions(patches, compatiblePackageNames, countUnusedPatches), + patches.mostCommonCompatibleVersions(compatiblePackageNames, countUnusedPatches), ) private fun assertEqualsVersion( expected: String?, - patches: PatchSet, + patches: Set>, compatiblePackageName: String, ) { assertEquals( expected, - PatchUtils.getMostCommonCompatibleVersions(patches, setOf(compatiblePackageName)) + patches.mostCommonCompatibleVersions(setOf(compatiblePackageName)) .entries.firstOrNull()?.value?.keys?.firstOrNull(), ) } @@ -181,19 +159,23 @@ internal class PatchUtilsTest { packageName: String, versions: Set? = null, use: Boolean = true, - options: Patch<*>.() -> Unit = {}, - ) = object : BytecodePatch( + options: PatchBuilder<*>.() -> Unit = {}, + ) = bytecodePatch( name = "test", - compatiblePackages = setOf(CompatiblePackage(packageName, versions?.toSet())), use = use, ) { - init { - options() + if (versions == null) { + compatibleWith(packageName) + } else { + compatibleWith( + if (versions.isEmpty()) { + packageName() + } else { + packageName(*versions.toTypedArray()) + }, + ) } - override fun execute(context: BytecodeContext) {} - - // Needed to make the patches unique. - override fun equals(other: Any?) = false + options() } } diff --git a/src/commonTest/kotlin/app/revanced/library/OptionsTest.kt b/src/commonTest/kotlin/app/revanced/library/OptionsTest.kt new file mode 100644 index 0000000..ec535c2 --- /dev/null +++ b/src/commonTest/kotlin/app/revanced/library/OptionsTest.kt @@ -0,0 +1,36 @@ +package app.revanced.library + +import app.revanced.patcher.patch.booleanOption +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.stringOption +import kotlin.test.Test +import kotlin.test.assertEquals + +class OptionsTest { + @Test + fun `serializes and deserializes`() { + val options = mapOf( + "Test patch" to mapOf("key1" to "test", "key2" to false), + ) + + val patch = bytecodePatch("Test patch") { + stringOption("key1") + booleanOption("key2", true) + } + val duplicatePatch = bytecodePatch("Test patch") { + stringOption("key1") + } + val unnamedPatch = bytecodePatch { + booleanOption("key1") + } + + setOf(patch, duplicatePatch, unnamedPatch).setOptions(options) + + assert(patch.options["key1"].value == "test") + assert(patch.options["key2"].value == false) + + assertEquals(patch.options["key1"].value, duplicatePatch.options["key1"].value) + + assert(unnamedPatch.options["key1"].value == null) + } +} diff --git a/src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt b/src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt deleted file mode 100644 index cc70e5e..0000000 --- a/src/commonTest/kotlin/app/revanced/library/PatchOptionsTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package app.revanced.library - -import app.revanced.library.Options.setOptions -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.booleanPatchOption -import app.revanced.patcher.patch.options.PatchOption.PatchExtensions.stringPatchOption -import kotlin.test.Test - -class PatchOptionsTest { - private var patches = setOf(PatchOptionsTestPatch) - - private val serializedJson = - "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":null},{\"key\":\"key2\"," + - "\"value\":true}]}]" - - private val changedJson = - "[{\"patchName\":\"PatchOptionsTestPatch\",\"options\":[{\"key\":\"key1\",\"value\":\"test\"},{\"key\":\"key2" + - "\",\"value\":false}]}]" - - @Test - fun `serializes and deserializes`() { - assert(serializedJson == Options.serialize(patches)) - - patches.setOptions(changedJson) - - assert(PatchOptionsTestPatch.option1 == "test") - assert(PatchOptionsTestPatch.option2 == false) - } - - @Patch("PatchOptionsTestPatch") - object PatchOptionsTestPatch : BytecodePatch(emptySet()) { - var option1 by stringPatchOption("key1", null, null, "title1", "description1") - var option2 by booleanPatchOption("key2", true, null, "title2", "description2") - - override fun execute(context: BytecodeContext) { - // Do nothing - } - } -} diff --git a/src/commonTest/kotlin/app/revanced/library/SerializationTest.kt b/src/commonTest/kotlin/app/revanced/library/SerializationTest.kt new file mode 100644 index 0000000..7a8727e --- /dev/null +++ b/src/commonTest/kotlin/app/revanced/library/SerializationTest.kt @@ -0,0 +1,55 @@ +package app.revanced.library + +import app.revanced.patcher.patch.* +import kotlinx.serialization.json.* +import java.io.ByteArrayOutputStream +import kotlin.test.Test +import kotlin.test.assertIs + +class SerializationTest { + private val testPatch = bytecodePatch("Test patch") { + compatibleWith("com.example.package"("1.0.0")) + compatibleWith("com.example.package2") + + dependsOn(bytecodePatch(), bytecodePatch()) + + stringOption("key1", null, null, "title1", "description1") + booleanOption("key2", true, null, "title2", "description2") + floatsOption("key3", listOf(1.0f), mapOf("list" to listOf(1f)), "title3", "description3") + } + + private var patches = setOf(testPatch) + + @Test + fun `serializes and deserializes`() { + val serializedJson = ByteArrayOutputStream().apply { patches.serializeTo(this) }.toString() + val deserializedJson = Json.parseToJsonElement(serializedJson) + + // Test patch serialization. + + assertIs(deserializedJson) + + val deserializedPatch = deserializedJson[0].jsonObject + + assert(deserializedPatch["name"]!!.jsonPrimitive.content == "Test patch") + + assert(deserializedPatch["compatiblePackages"]!!.jsonArray.size == 2) { + "The patch should be compatible with two packages." + } + + assert(deserializedPatch["dependencies"]!!.jsonArray.size == 2) { + "Even though the dependencies are named the same, they are different objects." + } + + // Test option serialization. + + val options = deserializedPatch["options"]!!.jsonArray + + assert(options.size == 3) { "The patch should have three options." } + + assert(options[0].jsonObject["title"]!!.jsonPrimitive.content == "title1") + assert(options[0].jsonObject["default"]!!.jsonPrimitive.contentOrNull == null) + assert(options[1].jsonObject["default"]!!.jsonPrimitive.boolean) + assert(options[2].jsonObject["values"]!!.jsonObject["list"]!!.jsonArray[0].jsonPrimitive.float == 1f) + } +}