fix(aaptcompiler): add unit tests from upstream

This commit is contained in:
Akash Yadav
2024-05-23 23:20:39 +05:30
parent dc0b00c7f5
commit 87ced38d26
21 changed files with 7970 additions and 0 deletions
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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 com.android.aaptcompiler
import com.android.utils.ILogger
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class BlameLoggerTest {
@Test
fun rewriteSingleSource() {
val mockLogger = MockLogger()
val blameLogger = getMockBlameLogger(mockLogger)
val result = blameLogger.getOriginalSource(BlameLogger.Source("foo/bar.xml", 3, 5))
assertThat(result.sourcePath).isEqualTo("foo/bar.xml.rewritten")
assertThat(result.line).isEqualTo(4)
assertThat(result.column).isEqualTo(7)
}
@Test
fun testsLoggedMessageRewritten() {
val mockLogger = MockLogger()
val blameLogger = getMockBlameLogger(mockLogger)
blameLogger.error("Failed to read file", BlameLogger.Source("foo/bar.xml", 3, 5))
assertThat(mockLogger.errors).hasSize(1)
val loggedError = mockLogger.errors.single()
assertThat(loggedError.first).contains("bar.xml.rewritten:4:7: Failed to read file")
assertThat(loggedError.second).isNull()
}
class MockLogger: ILogger {
val warnings: MutableList<String> = mutableListOf()
val infos: MutableList<String> = mutableListOf()
val errors: MutableList<Pair<String, Throwable?>> = mutableListOf()
val verboses: MutableList<String> = mutableListOf()
override fun warning(msgFormat: String, vararg args: Any?) {
warnings.add(String.format(msgFormat, args))
}
override fun info(msgFormat: String, vararg args: Any?) {
infos.add(String.format(msgFormat, args))
}
override fun error(t: Throwable?, msgFormat: String?, vararg args: Any?) {
errors.add(Pair(String.format(msgFormat!!, args), t))
}
override fun verbose(msgFormat: String, vararg args: Any?) {
verboses.add(String.format(msgFormat, args))
}
}
}
fun getMockBlameLogger(mockLogger: BlameLoggerTest.MockLogger) =
BlameLogger(
mockLogger
) {
BlameLogger.Source(it.sourcePath + ".rewritten", it.line + 1, it.column + 2)
}
@@ -0,0 +1,132 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResTableConfig
import com.google.common.truth.Truth
import org.junit.Test
class ConfigDescriptionTest {
private fun testParse(input: String): ConfigDescription? {
return try {
parse(input)
} catch (e: Exception) {
null
}
}
@Test
fun failWhenQualifiersAreOutOfOrder() {
Truth.assertThat(testParse("en-sw600dp-ldrtl")).isNull()
Truth.assertThat(testParse("land-en")).isNull()
Truth.assertThat(testParse("hdpi-320dpi")).isNull()
}
@Test
fun failWhenQualifiersAreNotMatched() {
Truth.assertThat(testParse("en-sw600dp-ILLEGAL")).isNull()
}
@Test
fun failWhenQualifiersHaveTrailingDelimiter() {
Truth.assertThat(testParse("en-sw600dp-land-")).isNull()
}
@Test
fun testParseBasic() {
val default = testParse("")
Truth.assertThat(default).isNotNull()
Truth.assertThat(default.toString()).isEqualTo("DEFAULT")
val frenchLandscape = testParse("fr-land")
Truth.assertThat(frenchLandscape).isNotNull()
Truth.assertThat(frenchLandscape.toString()).isEqualTo("fr-land")
val longConfig =
testParse("mcc310-pl-sw720dp-normal-long-port-night-xhdpi-keyssoft-qwerty-navexposed-nonav")
Truth.assertThat(longConfig).isNotNull()
Truth.assertThat(longConfig.toString())
.isEqualTo(
"mcc310-pl-sw720dp-normal-long-port-night-xhdpi-keyssoft-qwerty-navexposed-nonav-v13")
}
@Test
fun testLocales() {
val localeConfig = testParse("en-rUS")
Truth.assertThat(localeConfig).isNotNull()
Truth.assertThat(localeConfig.toString()).isEqualTo("en-rUS")
}
@Test
fun testQualifierAddedInApi13() {
val widthConfig = testParse("sw600dp")
Truth.assertThat(widthConfig).isNotNull()
Truth.assertThat(widthConfig.toString()).isEqualTo("sw600dp-v13")
val widthConfigWithLowerVersion = testParse("sw600dp-v8")
Truth.assertThat(widthConfigWithLowerVersion).isNotNull()
Truth.assertThat(widthConfigWithLowerVersion.toString()).isEqualTo("sw600dp-v13")
}
@Test
fun testParseCarAttribute() {
val config = testParse("car")
Truth.assertThat(config).isNotNull()
Truth.assertThat(config!!.uiMode).isEqualTo(ResTableConfig.UI_MODE.TYPE_CAR)
}
@Test
fun testParseVersionOnly() {
val config = testParse("v26")
Truth.assertThat(config).isNotNull()
Truth.assertThat(config!!.sdkVersion).isEqualTo(26)
}
@Test
fun testParsingRoundQualifier() {
val roundConfig = testParse("round")
Truth.assertThat(roundConfig).isNotNull()
Truth.assertThat(roundConfig!!.layoutRound())
.isEqualTo(ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES)
Truth.assertThat(roundConfig.sdkVersion).isEqualTo(23)
Truth.assertThat(roundConfig.toString()).isEqualTo("round-v23")
val notRoundConfig = testParse("notround")
Truth.assertThat(notRoundConfig).isNotNull()
Truth.assertThat(notRoundConfig!!.layoutRound())
.isEqualTo(ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_NO)
Truth.assertThat(notRoundConfig.sdkVersion).isEqualTo(23)
Truth.assertThat(notRoundConfig.toString()).isEqualTo("notround-v23")
}
@Test
fun testWideColorGamutQualifier() {
val wideConfig = testParse("widecg")
Truth.assertThat(wideConfig).isNotNull()
Truth.assertThat(wideConfig!!.wideColorGamut())
.isEqualTo(ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES)
Truth.assertThat(wideConfig.sdkVersion).isEqualTo(26)
Truth.assertThat(wideConfig.toString()).isEqualTo("widecg-v26")
val noWideConfig = testParse("nowidecg")
Truth.assertThat(noWideConfig).isNotNull()
Truth.assertThat(noWideConfig!!.wideColorGamut())
.isEqualTo(ResTableConfig.COLOR_MODE.WIDE_GAMUT_NO)
Truth.assertThat(noWideConfig.sdkVersion).isEqualTo(26)
Truth.assertThat(noWideConfig.toString()).isEqualTo("nowidecg-v26")
}
@Test
fun testHdrQualifier() {
val highDRConfig = testParse("highdr")
Truth.assertThat(highDRConfig).isNotNull()
Truth.assertThat(highDRConfig!!.hdr()).isEqualTo(ResTableConfig.COLOR_MODE.HDR_YES)
Truth.assertThat(highDRConfig.sdkVersion).isEqualTo(26)
Truth.assertThat(highDRConfig.toString()).isEqualTo("highdr-v26")
val lowDRConfig = testParse("lowdr")
Truth.assertThat(lowDRConfig).isNotNull()
Truth.assertThat(lowDRConfig!!.hdr()).isEqualTo(ResTableConfig.COLOR_MODE.HDR_NO)
Truth.assertThat(lowDRConfig.sdkVersion).isEqualTo(26)
Truth.assertThat(lowDRConfig.toString()).isEqualTo("lowdr-v26")
}
}
@@ -0,0 +1,335 @@
package com.android.aaptcompiler
import com.google.common.truth.Truth
import org.junit.Ignore
import org.junit.Test
class FloatParsingTest {
@Test
fun testParseDecFloatZero() {
testParseDecSupported("0", 0.0.toFloat())
testParseDecSupported("-0", -(0.0.toFloat()))
testParseDecSupported("+0.0", 0.0.toFloat())
testParseDecSupported("0.", 0.0.toFloat())
testParseDecSupported(".0", 0.0.toFloat())
testParseDecSupported("0000000000000.0000000000000", 0.0.toFloat())
testParseDecSupported("-0.0", -(0.0.toFloat()))
testParseDecSupported("0e0", 0.0.toFloat())
testParseDecSupported("0e24", 0.0.toFloat())
testParseDecSupported("0e-14", 0.0.toFloat())
// Zero's with absurdly large/small exponents should still parse properly.
testParseDecSupported("0e9001", 0.0.toFloat())
testParseDecSupported("0e+9001", 0.0.toFloat())
testParseDecSupported("0e-9001", 0.0.toFloat())
}
@Test
fun testParseHexFloatZero() {
testParseHexNormal("0x0", 0.0.toFloat())
testParseHexNormal("+0x0", 0.0.toFloat())
testParseHexNormal("0x.0", 0.0.toFloat())
testParseHexNormal("0x0.", 0.0.toFloat())
testParseHexNormal("0x0000000000.0000000000", 0.0.toFloat())
testParseHexNormal("-0x0", -(0.0.toFloat()))
testParseHexNormal("0x0p0", 0.0.toFloat())
testParseHexNormal("0x0p12", 0.0.toFloat())
testParseHexNormal("0x0p-14", 0.0.toFloat())
// Zero's with absurdly large/small exponents should still work.
testParseHexNormal("0x0p256", 0.0.toFloat())
testParseHexNormal("0x0p-256", 0.0.toFloat())
}
@Test
fun testParseDecIntegerValue() {
// Basic Integer Parsing
testParseDecSupported("10", 10.toFloat())
testParseDecSupported("12345", 12345.toFloat())
testParseDecSupported("67890", 67890.toFloat())
// Super-Integer parsing
// 2^32 is larger than int max
testParseDecSupported("4294967296", 4294967296.toFloat())
testParseDecSupported("-4294967296", (-4294967296).toFloat())
testParseDecSupported("3.4028235e38", 3.4028235e38.toFloat())
testParseDecSupported("-3.4028235e38", (-3.4028235e38).toFloat())
// Anything larger should certainly fail
testParseDecNotSupported("3.4028236e38", Float.POSITIVE_INFINITY)
testParseDecNotSupported("-3.4028236e38", Float.NEGATIVE_INFINITY)
}
@Test
fun testParseHexIntegerValue() {
// Basic Integer Parsing
testParseHexNormal("0x10", 16.toFloat())
testParseHexNormal("0x1p4", 16.toFloat())
testParseHexNormal("0x0123", 0x123.toFloat())
testParseHexNormal("0x4567", 0x4567.toFloat())
testParseHexNormal("0x89ab", 0x89ab.toFloat())
testParseHexNormal("0xcdef", 0xcdef.toFloat())
// Super-Integer parsing
// 2^32 is larger than integer max
testParseHexNormal("0x100000000", 4294967296.toFloat())
testParseHexNormal("-0x100000000", (-4294967296).toFloat())
// Max normal float value
testParseHexNormal("0x1.fffffeP127", 3.4028235e38.toFloat())
testParseHexNormal("-0x1.fffffeP127", (-3.4028235e38).toFloat())
// Anything larger should fail
testParseHexNonnormal("0x1.ffffffP127", Float.POSITIVE_INFINITY)
testParseHexNonnormal("-0x1.ffffffP127", Float.NEGATIVE_INFINITY)
// Rounding.
testParseHexNormal("0x2000001", 0x2000002.toFloat())
testParseHexNormal("-0x2000001", (-0x2000002).toFloat())
// This rounding test will assure that the exponent is adjusted properly.
testParseHexNormal("0x1ffffff", 0x2000000.toFloat())
testParseHexNormal("-0x1ffffff", (-0x2000000).toFloat())
}
@Test
fun testParseDecSubIntegerValue() {
// Basic Sub-Integer parsing
testParseDecSupported(".1", .1.toFloat())
//testParseDecSupported("2.25", 2.25.toFloat())
testParseDecSupported("-2.25", (-2.25).toFloat())
// Minimum normal float value
testParseDecSupported("1.17549435e-38", 1.17549435e-38.toFloat())
testParseDecSupported("-1.17549435e-38", (-1.17549435e-38).toFloat())
// Largest denormal number should fail on decimal parse,
// i.e. negative = 0x0/0x1 exponent = 0x0 mantissa = 0x7fffff
testParseDecNotSupported("1.1754942e-38", 1.1754942e-38.toFloat())
testParseDecNotSupported("-1.1754942e-38", (-1.1754942e-38).toFloat())
// Tiny numbers are interpreted as denormal by Kotlin and are rounded to 0.0.
// We do not support those values.
testParseDecNotSupported("1e-100", 0.00.toFloat())
testParseDecNotSupported("-1e-100", -(0.00.toFloat()))
}
@Test
fun testParseHexSubIntegerValue() {
// Basic Sub-Integer parsing
testParseHexNormal("0x.8", .5.toFloat())
testParseHexNormal("0x2.4", 2.25.toFloat())
testParseHexNormal("-0x2.4", (-2.25).toFloat())
// Minimum normal float value
testParseHexNormal("0x1p-126", 1.17549435e-38.toFloat())
testParseHexNormal("-0x1p-126", (-1.17549435e-38).toFloat())
// Edge case for denormal number rounding with less precision to minimum normal float.
testParseHexNormal("0x1.fffffeP-127", 1.17549435e-38.toFloat())
testParseHexNormal("-0x1.fffffeP-127", (-1.17549435e-38).toFloat())
// Largest denormal number should fail on hex parse,
// i.e. negative = 0x0/0x1 exponent = 0x0 mantissa = 0x7fffff
testParseHexNonnormal("0x1.fffffdp-127", 1.1754942e-38.toFloat())
testParseHexNonnormal("-0x1.fffffdp-127", (-1.1754942e-38).toFloat())
// Tiny numbers are interpreted as denormal by Kotlin and are rounded to 0.0.
// We do not support those values.
testParseHexNonnormal("0x1p-255", 0.0.toFloat())
testParseHexNonnormal("-0x1p-255", (-0.0).toFloat())
}
@Test
fun testParseDecFailures() {
testDecFailure(".")
testDecFailure("-.")
testDecFailure("+.")
testDecFailure("+e3")
testDecFailure("-e-2")
testDecFailure("125e")
testDecFailure("-125e")
testDecFailure("+125e")
testDecFailure("e")
testDecFailure("-e")
testDecFailure("1.2.3")
testDecFailure("-1.2.3")
testDecFailure("+-12")
testDecFailure("--0")
testDecFailure("++42")
}
@Test
fun testParseHexFailures() {
testHexFailure("0x.")
testHexFailure("-0x.")
testHexFailure("0xp")
testHexFailure("-0xp")
testHexFailure("0xp0")
testHexFailure("-0xp0")
testHexFailure("0x12p")
testHexFailure("-0x12p")
testHexFailure("0x1.2.1")
testHexFailure("-0x1.2.1")
testHexFailure("0x1.2.1p0")
testHexFailure("-0x1.2.1p0")
testHexFailure("--0x0")
testHexFailure("+-0x0p0")
testHexFailure("-+0x0p0")
testHexFailure("++0x0p0")
}
/**
* Round trip checks all supported float values.
*
* A round trip of a floating point value, goes as follows:
*
* Take a floating point number, in it's binary format.
*
* Call [Float.toString] getting the string representation.
*
* Call [parseFloat] on the string representation.
*
* Ensure that the initial float and it's parsed result, are the same.
*
* This tests goes over ~2 billion different float values, and takes ~1 hour for full
* computation. As such this test is set to be ignored, but should be enabled if float parsing
* is modified significantly.
*/
@Test
@Ignore("b/303076368")
fun roundTrip() {
var currentVal = 0x00800000
val maxVal = 0x7f000000
var err = ""
var numErrors = 0
while (currentVal < maxVal) {
val float = Float.fromBits(currentVal)
val string = float.toString()
val parse = parseFloat(string)
if (float != parse) {
if (numErrors < 1000) {
err += "expected: $float actual: $parse \n"
++numErrors
} else if (numErrors == 1000) {
err += "and more...\n"
break
}
}
++currentVal
}
if (err.isNotEmpty()) {
error(err)
}
}
private fun needsModificationForKotlin(hexText: String): Boolean {
return !hexText.lowercase().contains('p') && hexText.all {
when (it) {
in '0'..'9' -> true
in 'a'..'f' -> true
in 'A'..'F' -> true
'x', 'X' -> true
'.', '-', '+' -> true
else -> false
}
}
}
private fun testParseDecSupported(text: String, expected: Float) {
val parsedFloat = parseFloat(text)
Truth.assertThat(parsedFloat!!.toRawBits().toString(16))
.isEqualTo(expected.toRawBits().toString(16))
Truth.assertThat(parsedFloat).isEqualTo(text.toFloat())
}
private fun testParseDecNotSupported(text: String, expected: Float) {
val parsedFloat = parseFloat(text)
Truth.assertThat(parsedFloat).isNull()
Truth.assertThat(text.toFloat()).isEqualTo(expected)
}
private fun testDecFailure(text: String) {
Truth.assertThat(parseFloat(text)).isNull()
Truth.assertThat(text.toFloatOrNull()).isNull()
}
/**
* Tests a parseHex value that is expected to succeed. This must have the same result as the
* String.toFloat() method, with the possible addition of a "p0" to the text to make it compatible
* with Kotlin's toFloat method.
*
* Specifically, the hex parsing functionality works for all "normal" floating point numbers.
* (i.e.) floating point values that do not have a special value in the exponent to force
**/
private fun testParseHexNormal(text: String, expected: Float) {
val parsedFloat = parseFloat(text)
Truth.assertThat(parsedFloat).isEqualTo(expected)
// We need to append p0 for default float parsing that doesn't already have an exponent marker.
// As this is how Kotlin does it normally.
val modifiedText = if (needsModificationForKotlin(text)) text + "p0" else text
Truth.assertThat(parsedFloat).isEqualTo(modifiedText.toFloat())
}
/**
* Tests a parseHex value with a subnormal (denormal) or special values. parseFloat() should fail
* in these cases, but the text (with the possible addition of "p0") should succeed when parsing
* normally in Kotlin's String.toFloat() method.
*/
private fun testParseHexNonnormal(text: String, expected: Float) {
val parsedFloat = parseFloat(text)
Truth.assertThat(parsedFloat).isNull()
val modifiedText = if (needsModificationForKotlin(text)) text + "p0" else text
if (expected.isNaN()) {
Truth.assertThat(modifiedText.toFloat()).isNaN()
} else {
Truth.assertThat(modifiedText.toFloat()).isEqualTo(expected)
}
}
/**
* Tests a failure to parse and ensures Kotlin has the same behavior.
*/
private fun testHexFailure(text: String) {
Truth.assertThat(parseFloat(text)).isNull()
val modifiedText = if (needsModificationForKotlin(text)) text + "p0" else text
Truth.assertThat(modifiedText.toFloatOrNull()).isNull()
}
}
@@ -0,0 +1,57 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResTableConfig
import com.google.common.truth.Truth
import org.junit.Test
import java.nio.charset.StandardCharsets
class LocaleValueTest {
fun testLanguage(input: String, expectedLang: String, expectFailure: Boolean = false) {
val parts = input.split('-').map { it.lowercase() }
val locale = LocaleValue()
val count = locale.initFromParts(parts, 0)
Truth.assertThat(count).isEqualTo(if (expectFailure) 0 else 1)
Truth.assertThat(locale.language).isEqualTo(expectedLang)
}
fun testLanguageRegion(input: String, expectedLang: String, expectedRegion: String) {
val parts = input.split('-').map { it.lowercase() }
val locale = LocaleValue()
val count = locale.initFromParts(parts, 0)
Truth.assertThat(count).isEqualTo(2)
Truth.assertThat(locale.language).isEqualTo(expectedLang)
Truth.assertThat(locale.region).isEqualTo(expectedRegion)
}
@Test
fun testParseLanguage() {
testLanguage("en", "en")
testLanguage("fr", "fr")
testLanguage("land", "", true)
testLanguage("fr-land", "fr")
testLanguageRegion("fr-rCA", "fr","CA")
}
@Test
fun testWritingLocaleToResTableConfig() {
val languageLocaleValue = "b+be+tarask"
val parts = languageLocaleValue.split('-').map(String::lowercase)
val locale = LocaleValue()
locale.initFromParts(parts, 0)
val resTableConfig = ResTableConfig()
locale.writeTo(resTableConfig)
Truth.assertThat(resTableConfig.language.toString(StandardCharsets.UTF_8))
.isEqualTo("be")
Truth.assertThat(resTableConfig.localeVariant)
// bytearray is utf-8 representation of 'tarask' followed by two null values
// as it is 6 bytes in length but represented by 8 bytes.
.isEqualTo(byteArrayOf(116, 97, 114, 97, 115, 107, 0, 0))
}
}
@@ -0,0 +1,335 @@
package com.android.aaptcompiler
import com.google.common.truth.Truth
import org.junit.Test
class PseudolocaleGeneratorTest {
@Test
fun testPseudolocalizeBasicString() {
val pool = StringPool()
val original =
BasicString(pool.makeRef("Hello world!"), listOf(UntranslatableSection(1, 4)))
val newString1 = pseudolocalizeBasicString(original, Pseudolocalizer.Method.NONE, pool)
Truth.assertThat(newString1.ref.value()).isEqualTo(original.ref.value())
val newString2 = pseudolocalizeBasicString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString2.ref.value()).isEqualTo("[Ĥellö ŵöŕļð¡ one two]")
val newString3 = pseudolocalizeBasicString(original, Pseudolocalizer.Method.BIDI, pool)
Truth.assertThat(newString3.ref.value())
.isEqualTo("${bidiWordStart}H${bidiWordEnd}ell${bidiWordStart}o$bidiWordEnd " +
"${bidiWordStart}world!$bidiWordEnd")
}
@Test
fun testPseudolocalizeStyledString() {
val pool = StringPool()
val spans = listOf(
Span("i", 1, 10),
Span("b", 2, 3),
Span("b", 6, 7))
val original = StyledString(pool.makeRef(StyleString("Hello world!", spans)), listOf())
val newString1 = pseudolocalizeStyledString(original, Pseudolocalizer.Method.NONE, pool)
Truth.assertThat(newString1.ref.value()).isEqualTo(original.ref.value())
Truth.assertThat(newString1.spans().size).isEqualTo(original.spans().size)
val spans1 = newString1.spans()
Truth.assertThat(spans1[0].name.value()).isEqualTo("i")
Truth.assertThat(spans1[0].firstChar).isEqualTo("H".length)
Truth.assertThat(spans1[0].lastChar).isEqualTo("Hello worl".length)
Truth.assertThat(spans1[1].name.value()).isEqualTo("b")
Truth.assertThat(spans1[1].firstChar).isEqualTo("He".length)
Truth.assertThat(spans1[1].lastChar).isEqualTo("Hel".length)
Truth.assertThat(spans1[2].name.value()).isEqualTo("b")
Truth.assertThat(spans1[2].firstChar).isEqualTo("Hello ".length)
Truth.assertThat(spans[2].lastChar).isEqualTo("Hello w".length)
val newString2 = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString2.ref.value()).isEqualTo("[Ĥéļļö ŵöŕļð¡ one two]")
Truth.assertThat(newString2.spans().size).isEqualTo(original.spans().size)
val spans2 = newString2.spans()
Truth.assertThat(spans2[0].name.value()).isEqualTo("i")
Truth.assertThat(spans2[0].firstChar).isEqualTo("[Ĥ".length)
Truth.assertThat(spans2[0].lastChar).isEqualTo("[Ĥéļļö ŵöŕļ".length)
Truth.assertThat(spans2[1].name.value()).isEqualTo("b")
Truth.assertThat(spans2[1].firstChar).isEqualTo("[Ĥé".length)
Truth.assertThat(spans2[1].lastChar).isEqualTo("[Ĥéļ".length)
Truth.assertThat(spans2[2].name.value()).isEqualTo("b")
Truth.assertThat(spans2[2].firstChar).isEqualTo("[Ĥéļļö ".length)
Truth.assertThat(spans2[2].lastChar).isEqualTo("[Ĥéļļö ŵ".length)
}
@Test
fun testPseudolocalizeAdjacentNestedTags() {
val pool = StringPool()
val originalSpans = listOf(Span("b", 0, 3), Span("i", 0, 3))
val original = StyledString(pool.makeRef(StyleString("bold", originalSpans)), listOf())
val newString = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString.ref.value()).isEqualTo("[ɓöļð one]")
Truth.assertThat(newString.spans().size).isEqualTo(original.spans().size)
val spans = newString.spans()
Truth.assertThat(spans[0].name.value()).isEqualTo("b")
Truth.assertThat(spans[0].firstChar).isEqualTo("[".length)
Truth.assertThat(spans[0].lastChar).isEqualTo("[ɓöļ".length)
Truth.assertThat(spans[1].name.value()).isEqualTo("i")
Truth.assertThat(spans[1].firstChar).isEqualTo("[".length)
Truth.assertThat(spans[1].lastChar).isEqualTo("[ɓöļ".length)
}
@Test
fun testPseudolocalizeUnsorted() {
val pool = StringPool()
val originalSpans = listOf(Span("i", 2, 3), Span("b", 0, 1))
val original = StyledString(pool.makeRef(StyleString("bold", originalSpans)), listOf())
val newString = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString.ref.value()).isEqualTo("[ɓöļð one]")
Truth.assertThat(newString.spans().size).isEqualTo(original.spans().size)
val spans = newString.spans()
Truth.assertThat(spans[0].name.value()).isEqualTo("b")
Truth.assertThat(spans[0].firstChar).isEqualTo("[".length)
Truth.assertThat(spans[0].lastChar).isEqualTo("[ɓ".length)
Truth.assertThat(spans[1].name.value()).isEqualTo("i")
Truth.assertThat(spans[1].firstChar).isEqualTo("[ɓö".length)
Truth.assertThat(spans[1].lastChar).isEqualTo("[ɓöļ".length)
}
@Test
fun testPseudolocalizeNestedAndAdjacentTags() {
val pool = StringPool()
val originalSpans = listOf(
Span("b", 16, 19),
Span("em", 29, 47),
Span("i", 38, 40),
Span("b", 44, 47))
val originalText = "This sentence is not what you think it is at all."
val original = StyledString(pool.makeRef(StyleString(originalText, originalSpans)), listOf())
val newString = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString.ref.value())
.isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]")
Truth.assertThat(newString.spans().size).isEqualTo(original.spans().size)
val spans = newString.spans()
Truth.assertThat(spans[0].name.value()).isEqualTo("b")
Truth.assertThat(spans[0].firstChar).isEqualTo("[Ţĥîš šéñţéñçé îš".length)
Truth.assertThat(spans[0].lastChar).isEqualTo("[Ţĥîš šéñţéñçé îš ñö".length)
Truth.assertThat(spans[1].name.value()).isEqualTo("em")
Truth.assertThat(spans[1].firstChar).isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû".length)
Truth.assertThat(spans[1].lastChar)
.isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ".length)
Truth.assertThat(spans[2].name.value()).isEqualTo("i")
Truth.assertThat(spans[2].firstChar).isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ".length)
Truth.assertThat(spans[2].lastChar)
.isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ î".length)
Truth.assertThat(spans[3].name.value()).isEqualTo("b")
Truth.assertThat(spans[3].firstChar)
.isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ".length)
Truth.assertThat(spans[3].lastChar)
.isEqualTo("[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļ".length)
}
@Test
fun testPseudolocalizePartsOfStyledString() {
val pool = StringPool()
val originalSpans = listOf(Span("em", 4, 14), Span("i", 18, 33))
val untranslatables = listOf(UntranslatableSection(11, 15))
val originalText = "This should NOT be pseudolocalized."
val original =
StyledString(pool.makeRef(StyleString(originalText, originalSpans)), untranslatables)
val newString = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
Truth.assertThat(newString.ref.value())
.isEqualTo("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]")
Truth.assertThat(newString.spans().size).isEqualTo(original.spans().size)
val spans = newString.spans()
Truth.assertThat(spans[0].name.value()).isEqualTo("em")
Truth.assertThat(spans[0].firstChar).isEqualTo("[Ţĥîš".length)
Truth.assertThat(spans[0].lastChar).isEqualTo("[Ţĥîš šĥöûļð NO".length)
Truth.assertThat(spans[1].name.value()).isEqualTo("i")
Truth.assertThat(spans[1].firstChar).isEqualTo("[Ţĥîš šĥöûļð NOT ɓé".length)
Truth.assertThat(spans[1].lastChar).isEqualTo("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžé".length)
}
@Test
fun testPseudolocalizeOnlyDefaultConfigs() {
val table = ResourceTable()
val string1 = BasicString(table.stringPool.makeRef("one"))
table.addResource(
parseResourceName("android:string/one")!!.resourceName,
ConfigDescription(),
"",
string1)
val string2 = BasicString(table.stringPool.makeRef("two"))
table.addResource(
parseResourceName("android:string/two")!!.resourceName,
parse("en"),
"",
string2)
val string3Default = BasicString(table.stringPool.makeRef("three"))
table.addResource(
parseResourceName("android:string/three")!!.resourceName,
ConfigDescription(),
"",
string3Default)
val string3Override = BasicString(table.stringPool.makeRef("three next"))
table.addResource(
parseResourceName("android:string/three")!!.resourceName,
parse("en-rXA"),
"",
string3Override)
val string4 = BasicString(table.stringPool.makeRef("four"))
table.addResource(
parseResourceName("android:string/four")!!.resourceName,
ConfigDescription(),
"",
string4)
val resource4 = getValue(table, "android:string/four")
// sanity check
Truth.assertThat(resource4).isNotNull()
resource4!!.translatable = false
val generator = PseudolocaleGenerator()
generator.consume(table)
// Normal pseudolocalization should take place.
Truth.assertThat(getValue(table, "android:string/one", parse("en-rXA"))).isNotNull()
Truth.assertThat(getValue(table, "android:string/one", parse("ar-rXB"))).isNotNull()
// No default config for android:string/two, so no pseudlocales should exist.
Truth.assertThat(getValue(table, "android:string/two", parse("en-rXA"))).isNull()
Truth.assertThat(getValue(table, "android:string/two", parse("ar-rXB"))).isNull()
// Check that we didn't override manual pseudolocalization.
val resource3Override = getValue(table, "android:string/three", parse("en-rXA"))
Truth.assertThat(resource3Override).isNotNull()
Truth.assertThat((resource3Override as BasicString).ref.value()).isEqualTo("three next")
// Check that four's translatable marker was honored.
Truth.assertThat(getValue(table, "android:string/four", parse("en-rXA"))).isNull()
Truth.assertThat(getValue(table, "android:string/four", parse("ar-rXB"))).isNull()
}
@Test
fun testPluralsArePseudolocalized() {
val table = ResourceTable()
val plural = Plural()
plural.setValue(Plural.Type.ZERO, BasicString(table.stringPool.makeRef("zero")))
plural.setValue(Plural.Type.ONE, BasicString(table.stringPool.makeRef("one")))
table.addResource(
parseResourceName("com.pkg:plurals/foo")!!.resourceName, ConfigDescription(), "", plural)
val expected = Plural()
expected.setValue(Plural.Type.ZERO, BasicString(table.stringPool.makeRef("[žéŕö one]")))
expected.setValue(Plural.Type.ONE, BasicString(table.stringPool.makeRef("[öñé one]")))
val generator = PseudolocaleGenerator()
generator.consume(table)
val actual = getValue(table, "com.pkg:plurals/foo", parse("en-rXA"))
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun testUntranslatableSections() {
val table = ResourceTable()
val originalText = "Hello world!"
val originalSpans = listOf(Span("i", 1, 10), Span("b", 2, 3), Span("b", 6, 7))
val originalStyle = StyledString(
table.stringPool.makeRef(StyleString(originalText, originalSpans)),
listOf(UntranslatableSection(6, 8), UntranslatableSection(8, 11)))
val originalString = BasicString(
table.stringPool.makeRef(originalText),
listOf(UntranslatableSection(6, 11)))
table.addResource(
parseResourceName("android:string/foo")!!.resourceName,
ConfigDescription(),
"",
originalStyle)
table.addResource(
parseResourceName("android:string/bar")!!.resourceName,
ConfigDescription(),
"",
originalString)
val generator = PseudolocaleGenerator()
generator.consume(table)
val newStyle = getValue(table, "android:string/foo", parse("en-rXA"))
Truth.assertThat(newStyle).isNotNull()
// "world" should be untranslated.
Truth.assertThat((newStyle as StyledString).ref.value()).contains("world")
val newString = getValue(table, "android:string/bar", parse("en-rXA"))
Truth.assertThat(newString).isNotNull()
// "world" should be untranslated.
Truth.assertThat((newString as BasicString).ref.value()).contains("world")
}
// Regression test for b/188963894.
@Test
fun canPseudolocalizeNestedTagWithSpace() {
val pool = StringPool()
val originalSpans = listOf(
Span("b", 0, 4),
Span("i", 0, 10)
)
val originalText = "hello world"
val original = StyledString(pool.makeRef(StyleString(originalText, originalSpans)), listOf())
val newString = pseudolocalizeStyledString(original, Pseudolocalizer.Method.ACCENT, pool)
val spans = newString.spans()
Truth.assertThat(spans[0].name.value()).isEqualTo("i")
Truth.assertThat(spans[0].firstChar).isEqualTo(1)
Truth.assertThat(spans[0].lastChar).isEqualTo("ĥéļļö ŵöŕļð".length)
Truth.assertThat(spans[1].name.value()).isEqualTo("b")
Truth.assertThat(spans[1].firstChar).isEqualTo(1)
Truth.assertThat(spans[1].lastChar).isEqualTo("ĥéļļö".length)
}
}
@@ -0,0 +1,253 @@
package com.android.aaptcompiler
import com.google.common.truth.Truth
import org.junit.Test
internal val bidiWordStart = String(byteArrayOf(
0xe2.toByte(),
0x80.toByte(),
0x8f.toByte(),
0xe2.toByte(),
0x80.toByte(),
0xae.toByte()))
internal val bidiWordEnd = String(byteArrayOf(
0xe2.toByte(),
0x80.toByte(),
0xac.toByte(),
0xe2.toByte(),
0x80.toByte(),
0x8f.toByte()))
class PseudolocalizerTest() {
private fun simpleTest(input: String, expected: String, method: Pseudolocalizer.Method) {
val localizer = Pseudolocalizer(method)
val result = localizer.start() + localizer.text(input) + localizer.end()
Truth.assertThat(result).isEqualTo(expected)
}
private fun compoundTest(
expected: String, method: Pseudolocalizer.Method, vararg inputs: String) {
val localizer = Pseudolocalizer(method)
val result = StringBuilder().append(localizer.start())
for (input in inputs) {
result.append(localizer.text(input))
}
result.append(localizer.end())
Truth.assertThat(result.toString()).isEqualTo(expected)
}
@Test
fun testNoPseudolocalization() {
simpleTest("", "", Pseudolocalizer.Method.NONE)
simpleTest("Hello, world", "Hello, world", Pseudolocalizer.Method.NONE)
compoundTest("Hello, world", Pseudolocalizer.Method.NONE, "Hello,", " world")
}
@Test
fun testPlainTextAccent() {
simpleTest("", "[]", Pseudolocalizer.Method.ACCENT)
simpleTest("Hello, world", "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer.Method.ACCENT)
simpleTest("Hello, %1d", "[Ĥéļļö, »%1d« one two]", Pseudolocalizer.Method.ACCENT)
simpleTest("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer.Method.ACCENT)
simpleTest("^1 %", "[^1 % one]", Pseudolocalizer.Method.ACCENT)
compoundTest("[]", Pseudolocalizer.Method.ACCENT, "", "", "")
compoundTest("[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer.Method.ACCENT, "Hello,", " world", "")
}
@Test
fun testPlainTextBidi() {
simpleTest("", "", Pseudolocalizer.Method.BIDI)
simpleTest("word", "${bidiWordStart}word$bidiWordEnd", Pseudolocalizer.Method.BIDI)
simpleTest(" word ", " ${bidiWordStart}word$bidiWordEnd ", Pseudolocalizer.Method.BIDI)
simpleTest(
"hello\n world\n",
"${bidiWordStart}hello$bidiWordEnd\n ${bidiWordStart}world$bidiWordEnd\n",
Pseudolocalizer.Method.BIDI)
compoundTest(
"${bidiWordStart}hello$bidiWordEnd\n ${bidiWordStart}world$bidiWordEnd\n",
Pseudolocalizer.Method.BIDI,
"hello", "\n ", "world\n")
}
@Test
fun testSimpleICU() {
// Single-fragment messages.
simpleTest("{placeholder}", "[»{placeholder}«]", Pseudolocalizer.Method.ACCENT)
simpleTest("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer.Method.ACCENT)
simpleTest(
"Copy from {path1} to {path2}",
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Today is {1,date} {1,time}",
"[Ţöðåý îš »{1,date}« »{1,time}« one two]",
Pseudolocalizer.Method.ACCENT)
// Multi-fragment messages.
compoundTest(
"[»{USER}« îš öƒƒļîñé one two]",
Pseudolocalizer.Method.ACCENT,
"{USER}", " ", "is offline")
compoundTest(
"[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]",
Pseudolocalizer.Method.ACCENT,
"Copy from ", "{path1}", " to", " {path2}")
}
@Test
fun testICUBidi() {
// Single-fragment messages.
simpleTest(
"{placeholder}", "$bidiWordStart{placeholder}$bidiWordEnd", Pseudolocalizer.Method.BIDI)
simpleTest(
"{COUNT, plural, one {one} other {other}}",
"{COUNT, plural, one {${bidiWordStart}one$bidiWordEnd} " +
"other {${bidiWordStart}other$bidiWordEnd}}",
Pseudolocalizer.Method.BIDI)
}
@Test
fun testEscaping() {
// Single-fragment messages.
simpleTest(
"'{USER'} is offline", "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", Pseudolocalizer.Method.ACCENT)
// Multi-fragment messages.
compoundTest(
"['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]",
Pseudolocalizer.Method.ACCENT,
"'{USER}", " ", "''is offline")
}
@Test
fun testPluralsAndSelects() {
simpleTest(
"{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}",
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Distance is {COUNT, plural, one {# mile} other {# miles}}",
"[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} other {# ḿîļéš one two}}]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"{1, select, female {{1} added you} male {{1} added you} other {{1} added you}}",
"[{1, select, female {»{1}« åððéð ýöû one two} male {»{1}« åððéð ýöû one two} " +
"other {»{1}« åððéð ýöû one two}}]",
Pseudolocalizer.Method.ACCENT)
compoundTest(
"[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]",
Pseudolocalizer.Method.ACCENT,
"{COUNT, plural, one {Delete a file} ", "other {Delete ", "{COUNT}", " files}}")
}
@Test
fun testNestedICU() {
simpleTest("{person, select, female {{num_circles, plural,=0{{person} didn't add you " +
"to any of her circles.}=1{{person} added you to one of her circles.}other{{person} added " +
"you to her # circles.}}}male {{num_circles, plural,=0{{person} didn't add you to any of " +
"his circles.}=1{{person} added you to one of his circles.}other{{person} added you to his " +
"# circles.}}}other {{num_circles, plural,=0{{person} didn't add you to any of their " +
"circles.}=1{{person} added you to one of their circles.}other{{person} added you to their " +
"# circles.}}}}",
"[{person, select, female {{num_circles, plural,=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ " +
"ĥéŕ çîŕçļéš. one two three four five}=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš. one " +
"two three four}other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš. one two three four}}}male " +
"{{num_circles, plural,=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš. one two three " +
"four five}=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš. one two three four}other" +
"{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš. one two three four}}}other {{num_circles, " +
"plural,=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš. one two three four five}" +
"=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš. one two three four}other{»{person}« " +
"åððéð ýöû ţö ţĥéîŕ # çîŕçļéš. one two three four}}}}]",
Pseudolocalizer.Method.ACCENT)
}
@Test
fun testRedefineMidLocalization() {
val localizer = Pseudolocalizer(Pseudolocalizer.Method.ACCENT)
val result = StringBuilder(localizer.text("Hello, "))
localizer.setMethod(Pseudolocalizer.Method.NONE)
result.append(localizer.text("world!"))
Truth.assertThat(result.toString()).isEqualTo("Ĥéļļö, world!")
}
@Test
fun testNonHtmlTags() {
simpleTest(
"Started &lt; 1 minute ago",
"[Šţåŕţéð &ļţ; 1 ḿîñûţé åĝö one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Started < 1 minute ago",
"[Šţåŕţéð < 1 ḿîñûţé åĝö one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Started &gt; 1 minute ago",
"[Šţåŕţéð &gt; 1 ḿîñûţé åĝö one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Started > 1 minute ago",
"[Šţåŕţéð > 1 ḿîñûţé åĝö one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"Started &lt; 1 minute ago",
"Started &lt; 1 minute ago",
Pseudolocalizer.Method.NONE)
simpleTest(
"Started < 1 minute ago",
"Started < 1 minute ago",
Pseudolocalizer.Method.NONE)
simpleTest(
"hello\n &lt;\n world\n",
"${bidiWordStart}hello$bidiWordEnd\n " +
"${bidiWordStart}&lt;$bidiWordEnd\n " +
"${bidiWordStart}world$bidiWordEnd\n",
Pseudolocalizer.Method.BIDI)
simpleTest(
"hello\n <\n world\n",
"${bidiWordStart}hello$bidiWordEnd\n " +
"${bidiWordStart}<$bidiWordEnd\n " +
"${bidiWordStart}world$bidiWordEnd\n",
Pseudolocalizer.Method.BIDI)
}
@Test
fun testHtmlTags() {
// Words should be translated, but html tags and their contents should not.
simpleTest(
"hello <i>happy</i> world",
"[ĥéļļö <i>ĥåþþý</i> ŵöŕļð one two three]",
Pseudolocalizer.Method.ACCENT)
simpleTest(
"hello &lt;i&gt;happy&lt;/i&gt; world",
"[ĥéļļö &lt;;i&gt;ĥåþþý&lt;;/i&gt; ŵöŕļð one two three]",
Pseudolocalizer.Method.ACCENT)
// The tag (i.e. "a href="idk"") should not be translated, but the inner content (i.e."world")
// should be.
simpleTest(
"hello <a href=\"idk\"/>world</a>",
"[ĥéļļö <a href=\"idk\"/>ŵöŕļð</a> one two]",
Pseudolocalizer.Method.ACCENT)
// Even if from HTML's point of view the tags are placed incorrectly, the pseudolocalizer should
// be able to handle them: not translate the tags, and only translate the contents outside and
// between them.
simpleTest(
"Incorrectly <b> nested <i> tags </b> should </i> still <a> work",
"[Îñçöŕŕéçţļý <b> ñéšţéð <i> ţåĝš </b> šĥöûļð </i> šţîļļ <a> ŵöŕķ one two three four five six]",
Pseudolocalizer.Method.ACCENT)
}
}
@@ -0,0 +1,641 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.proto.deserializeConfigFromPb
import com.android.aaptcompiler.proto.deserializeFileTypeFromPb
import com.android.aaptcompiler.proto.deserializeTableFromPb
import com.android.aaptcompiler.testutils.ContainerReader
import com.android.aaptcompiler.testutils.FileEntry
import com.android.aaptcompiler.testutils.TableEntry
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.android.utils.FileUtils
import com.google.common.truth.Truth
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class ResourceCompilerTest {
@Rule
@JvmField
var tempFolder = TemporaryFolder()
lateinit var outputDir: File
@Before
fun setup() {
outputDir = tempFolder.newFolder()
}
private fun createFile(input: String, name: String, parentFolder: File? = null): File {
val file = if (parentFolder != null) {
File(parentFolder, name)
} else {
tempFolder.newFile(name)
}
file.writeText(input)
return file
}
private fun testValuesFile(
input: String,
config: String = "",
options: ResourceCompilerOptions = ResourceCompilerOptions()
): File {
val valuesFolder = tempFolder.newFolder("values")
val configSuffix = if (config.isEmpty()) "" else "-$config"
val file = createFile(input, "test$configSuffix.xml", valuesFolder)
compileResource(file, outputDir, options, getMockBlameLogger(BlameLoggerTest.MockLogger()))
val filePath = extractPathData(file)
// The extension is changed when the table is compiled.
filePath.extension = "arsc"
return File(outputDir, filePath.getIntermediateContainerFilename())
}
private fun testXmlFile(
type: AaptResourceType,
input: String,
config: String = "",
options: ResourceCompilerOptions = ResourceCompilerOptions()
): File {
val resourceFolder = tempFolder.newFolder(type.tagName)
val configSuffix = if (config.isEmpty()) "" else "-$config"
val file = createFile(input, "test$configSuffix.xml", resourceFolder)
compileResource(file, outputDir, options, getMockBlameLogger(BlameLoggerTest.MockLogger()))
val filePath = extractPathData(file)
return File(outputDir, filePath.getIntermediateContainerFilename())
}
private fun testRawFile(input: String, extension: String, config: String = ""): File {
val resourceFolder = tempFolder.newFolder(AaptResourceType.RAW.tagName)
val configSuffix = if (config.isEmpty()) "" else "-$config"
val file = createFile(input, "test$configSuffix.$extension", resourceFolder)
compileResource(
file,
outputDir,
ResourceCompilerOptions(),
getMockBlameLogger(BlameLoggerTest.MockLogger())
)
val filePath = extractPathData(file)
return File(outputDir, filePath.getIntermediateContainerFilename())
}
private fun testPngFile(
type: AaptResourceType,
input: String,
config: String = "",
isPatch9: Boolean = false,
options: ResourceCompilerOptions = ResourceCompilerOptions()
): File {
val resourceFolder = tempFolder.newFolder(type.tagName)
val configSuffix = if (config.isEmpty()) "" else "-$config"
val extension = if (isPatch9) "9.png" else "png"
val file = createFile(input, "test$configSuffix.$extension", resourceFolder)
compileResource(file, outputDir, options, getMockBlameLogger(BlameLoggerTest.MockLogger()))
val filePath = extractPathData(file)
return File(outputDir, filePath.getIntermediateContainerFilename())
}
@Test
fun testEmptyValuesFile() {
val result = ContainerReader(testValuesFile(""))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
// compilation package should still be available.
Truth.assertThat(newTable.packages).hasSize(1)
val pkg = newTable.packages[0]
Truth.assertThat(pkg.groups).isEmpty()
Truth.assertThat(pkg.name).isEqualTo("")
}
@Test
fun failInvalidRoot() {
try {
testValuesFile("<resource></resource>")
fail()
} catch (e: Exception) {
// expected.
}
}
@Test
fun testRootOnlyValuesFile() {
val result = ContainerReader(testValuesFile("<resources></resources>"))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
// compilation package should still be available.
Truth.assertThat(newTable.packages).hasSize(1)
val pkg = newTable.packages[0]
Truth.assertThat(pkg.groups).isEmpty()
Truth.assertThat(pkg.name).isEqualTo("")
}
@Test
fun testStringValuesFile() {
val input = """
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="normal1">Hi there!</string>
<string name="normal2"> " Hello there! " </string>
<string name="normal3"> Hey
there! </string>
<string name="styled1"> <b>Yo</b>, how are you? </string>
<string name="styled2"> <i> italicized <b> and bold</b></i></string>
<string name="styled3"><all><the><spans>Hi</spans></the></all></string>
<plurals name="plural1">
<item quantity="one">Found %d puppy!</item>
<item quantity="other">Found %d puppies!</item>
</plurals>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
val normal1 = getValue(newTable, "string/normal1") as? BasicString
Truth.assertThat(normal1).isNotNull()
Truth.assertThat(normal1!!.toString()).isEqualTo("Hi there!")
val normal2 = getValue(newTable, "string/normal2") as? BasicString
Truth.assertThat(normal2).isNotNull()
// only spaces inside the quotes should be preserved from the xml.
Truth.assertThat(normal2!!.toString()).isEqualTo(" Hello there! ")
val normal3 = getValue(newTable, "string/normal3") as? BasicString
Truth.assertThat(normal3).isNotNull()
// Spaces on the end are skipped for BasicStrings. However white space is compressed
// inbetween words in the strings (the newline and spaces are treated as a single space.
Truth.assertThat(normal3!!.toString()).isEqualTo("Hey there!")
val styled1 = getValue(newTable, "string/styled1") as? StyledString
Truth.assertThat(styled1).isNotNull()
// Spaces are preserved on the ends of a styled string.
Truth.assertThat(styled1!!.toString()).isEqualTo(" Yo, how are you? ")
val spans1 = styled1.spans()
Truth.assertThat(spans1).hasSize(1)
// Spans are firstChar to lastChar inclusive, so the last char ends on 2, not 3.
Truth.assertThat(spans1[0].firstChar).isEqualTo(1)
Truth.assertThat(spans1[0].lastChar).isEqualTo(2)
Truth.assertThat(spans1[0].name.value()).isEqualTo("b")
val styled2 = getValue(newTable, "string/styled2") as? StyledString
Truth.assertThat(styled2).isNotNull()
// Whitespace is only compressed "around" spans but not through spans. I.e.
// "b <b> a" -> "b a" with two spaces.
Truth.assertThat(styled2!!.toString()).isEqualTo(" italicized and bold")
val spans2 = styled2.spans()
Truth.assertThat(spans2).hasSize(2)
Truth.assertThat(spans2[0].firstChar).isEqualTo(1)
Truth.assertThat(spans2[0].lastChar).isEqualTo(21)
Truth.assertThat(spans2[0].name.value()).isEqualTo("i")
Truth.assertThat(spans2[1].firstChar).isEqualTo(13)
Truth.assertThat(spans2[1].lastChar).isEqualTo(21)
Truth.assertThat(spans2[1].name.value()).isEqualTo("b")
val styled3 = getValue(newTable, "string/styled3") as? StyledString
Truth.assertThat(styled3).isNotNull()
Truth.assertThat(styled3!!.toString()).isEqualTo("Hi")
val spans3 = styled3.spans()
Truth.assertThat(spans3).hasSize(3)
Truth.assertThat(spans3[0].firstChar).isEqualTo(0)
Truth.assertThat(spans3[0].lastChar).isEqualTo(1)
Truth.assertThat(spans3[0].name.value()).isEqualTo("all")
Truth.assertThat(spans3[1].firstChar).isEqualTo(0)
Truth.assertThat(spans3[1].lastChar).isEqualTo(1)
Truth.assertThat(spans3[1].name.value()).isEqualTo("the")
Truth.assertThat(spans3[2].firstChar).isEqualTo(0)
Truth.assertThat(spans3[2].lastChar).isEqualTo(1)
Truth.assertThat(spans3[2].name.value()).isEqualTo("spans")
val plurals = getValue(newTable, "plurals/plural1") as? Plural
Truth.assertThat(plurals).isNotNull()
val oneValue = plurals!!.values[Plural.Type.ONE.ordinal] as? BasicString
Truth.assertThat(oneValue).isNotNull()
Truth.assertThat(oneValue!!.toString()).isEqualTo("Found %d puppy!")
val manyValue = plurals.values[Plural.Type.OTHER.ordinal] as? BasicString
Truth.assertThat(manyValue).isNotNull()
Truth.assertThat(manyValue!!.toString()).isEqualTo("Found %d puppies!")
Truth.assertThat(plurals.values[Plural.Type.ZERO.ordinal]).isNull()
Truth.assertThat(plurals.values[Plural.Type.TWO.ordinal]).isNull()
Truth.assertThat(plurals.values[Plural.Type.FEW.ordinal]).isNull()
Truth.assertThat(plurals.values[Plural.Type.MANY.ordinal]).isNull()
}
@Test
fun testParseItems() {
val input = """
<resources>
<string name="nullAttempt">@null</string>
<string name="emptyAttempt">@empty</string>
<string name="referenceString">@string/nullAttempt</string>
<color name="color1">#abc</color>
<color name="color2">#f123</color>
<color name="red">#ff0000</color>
<color name="cyan">#ff00ffff</color>
<bool name="check1">true</bool>
<integer name="int1">1234</integer>
<integer name="int2">0x24</integer>
<item name="pi" type="dimen" format="float">3.14</item>
<dimen name="itemWidth">12dp</dimen>
<fraction name="fraction1">100%</fraction>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
// Null types are empty references to aapt.
val nullReference = getValue(newTable, "string/nullAttempt") as? Reference
Truth.assertThat(nullReference).isNotNull()
Truth.assertThat(nullReference!!.name).isEqualTo(ResourceName.EMPTY)
// Empty references are binary primitives with the null format.
val emptyReference = getValue(newTable, "string/emptyAttempt") as? BinaryPrimitive
Truth.assertThat(emptyReference).isNotNull()
Truth.assertThat(emptyReference!!.resValue.dataType).isEqualTo(ResValue.DataType.NULL)
Truth.assertThat(emptyReference.resValue.data).isEqualTo(ResValue.NullFormat.EMPTY)
val stringReference = getValue(newTable, "string/referenceString") as? Reference
Truth.assertThat(stringReference).isNotNull()
Truth.assertThat(stringReference!!.name).isEqualTo(parseNameOrFail("string/nullAttempt"))
val color1 = getValue(newTable, "color/color1") as? BinaryPrimitive
Truth.assertThat(color1).isNotNull()
Truth.assertThat(color1!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_RGB4)
Truth.assertThat(color1.resValue.data).isEqualTo(0xffaabbcc.toInt())
val color2 = getValue(newTable, "color/color2") as? BinaryPrimitive
Truth.assertThat(color2).isNotNull()
Truth.assertThat(color2!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_ARGB4)
Truth.assertThat(color2.resValue.data).isEqualTo(0xff112233.toInt())
val color3 = getValue(newTable, "color/red") as? BinaryPrimitive
Truth.assertThat(color3).isNotNull()
Truth.assertThat(color3!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_RGB8)
Truth.assertThat(color3.resValue.data).isEqualTo(0xffff0000.toInt())
val color4 = getValue(newTable, "color/cyan") as? BinaryPrimitive
Truth.assertThat(color4).isNotNull()
Truth.assertThat(color4!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_ARGB8)
Truth.assertThat(color4.resValue.data).isEqualTo(0xff00ffff.toInt())
val bool = getValue(newTable, "bool/check1") as? BinaryPrimitive
Truth.assertThat(bool).isNotNull()
Truth.assertThat(bool!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_BOOLEAN)
Truth.assertThat(bool.resValue.data).isEqualTo(-1)
val integer1 = getValue(newTable, "integer/int1") as? BinaryPrimitive
Truth.assertThat(integer1).isNotNull()
Truth.assertThat(integer1!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_DEC)
Truth.assertThat(integer1.resValue.data).isEqualTo(1234)
val integer2 = getValue(newTable, "integer/int2") as? BinaryPrimitive
Truth.assertThat(integer2).isNotNull()
Truth.assertThat(integer2!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_HEX)
Truth.assertThat(integer2.resValue.data).isEqualTo(0x24)
val floatVal = getValue(newTable, "dimen/pi") as? BinaryPrimitive
Truth.assertThat(floatVal).isNotNull()
Truth.assertThat(floatVal!!.resValue.dataType).isEqualTo(ResValue.DataType.FLOAT)
Truth.assertThat(floatVal.resValue.data).isEqualTo(3.14.toFloat().toRawBits())
val dimension = getValue(newTable, "dimen/itemWidth") as? BinaryPrimitive
Truth.assertThat(dimension).isNotNull()
Truth.assertThat(dimension!!.resValue.dataType).isEqualTo(ResValue.DataType.DIMENSION)
Truth.assertThat(dimension.resValue.data).isEqualTo(0xc01)
val fraction = getValue(newTable, "fraction/fraction1") as? BinaryPrimitive
Truth.assertThat(fraction).isNotNull()
Truth.assertThat(fraction!!.resValue.dataType).isEqualTo(ResValue.DataType.FRACTION)
Truth.assertThat(fraction.resValue.data).isEqualTo(0x100)
}
@Test
fun testParseInvalidColorHexCode() {
val input = """
<resources>
<color name="invalid">#0xE1E1E1</color>
</resources>
""".trimIndent()
try {
testValuesFile(input)
} catch (e: ResourceCompilationException) {
Truth.assertThat(e.message).contains("Unable to parse hex color '#0xE1E1E1'.")
}
}
@Test
fun parseStyleableWithAndroidAttr() {
val input = """
<resources>
<declare-styleable name="oneattr">
<attr name="android:foo"/>
</declare-styleable>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
val styleable = getValue(newTable, "styleable/oneattr") as? Styleable
Truth.assertThat(styleable).isNotNull()
Truth.assertThat(styleable!!.entries).hasSize(1)
val styleAttribute = styleable.entries[0]
Truth.assertThat(styleAttribute.name).isEqualTo(parseNameOrFail("android:attr/foo"))
}
@Test
fun parseArrayTypes() {
val input = """
<resources>
<integer-array name="ints">
<item>0</item>
<item>1</item>
</integer-array>
<string-array name="strings">
<item>foo</item>
<item>bar</item>
</string-array>
<array name="string_refs">
<item>@string/s0</item>
<item>@string/s1</item>
<item>@string/s2</item>
</array>
<array name="colors">
<item>#FFFF0000</item>
<item>#FF00FF00</item>
<item>#FF0000FF</item>
</array>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
val intsArray = getValue(newTable, "array/ints") as? ArrayResource
Truth.assertThat(intsArray).isNotNull()
Truth.assertThat(intsArray!!.elements).hasSize(2)
Truth.assertThat((intsArray.elements[0] as BinaryPrimitive).resValue.data).isEqualTo(0)
Truth.assertThat((intsArray.elements[1] as BinaryPrimitive).resValue.data).isEqualTo(1)
val stringsArray1 = getValue(newTable, "array/strings") as? ArrayResource
Truth.assertThat(stringsArray1).isNotNull()
Truth.assertThat(stringsArray1!!.elements).hasSize(2)
Truth.assertThat((stringsArray1.elements[0] as BasicString).toString()).isEqualTo("foo")
Truth.assertThat((stringsArray1.elements[1] as BasicString).toString()).isEqualTo("bar")
val stringsArray2 = getValue(newTable, "array/string_refs") as? ArrayResource
Truth.assertThat(stringsArray2).isNotNull()
Truth.assertThat(stringsArray2!!.elements).hasSize(3)
Truth.assertThat((stringsArray2.elements[0] as Reference).name)
.isEqualTo(parseNameOrFail("string/s0"))
Truth.assertThat((stringsArray2.elements[1] as Reference).name)
.isEqualTo(parseNameOrFail("string/s1"))
Truth.assertThat((stringsArray2.elements[2] as Reference).name)
.isEqualTo(parseNameOrFail("string/s2"))
val colorsArray = getValue(newTable, "array/colors") as? ArrayResource
Truth.assertThat(colorsArray).isNotNull()
Truth.assertThat(colorsArray!!.elements).hasSize(3)
Truth.assertThat((colorsArray.elements[0] as? BinaryPrimitive)?.resValue?.data)
.isEqualTo(0xffff0000.toInt())
Truth.assertThat((colorsArray.elements[1] as? BinaryPrimitive)?.resValue?.data)
.isEqualTo(0xff00ff00.toInt())
Truth.assertThat((colorsArray.elements[2] as? BinaryPrimitive)?.resValue?.data)
.isEqualTo(0xff0000ff.toInt())
}
@Test
fun parseAttrInStyleable() {
val input = """
<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
val styleable = getValue(newTable, "styleable/PieChart") as? Styleable
Truth.assertThat(styleable).isNotNull()
Truth.assertThat(styleable!!.entries).hasSize(2)
val reference1 = styleable.entries[0]
Truth.assertThat(reference1.name).isEqualTo(parseNameOrFail("attr/showText"))
val reference2 = styleable.entries[1]
Truth.assertThat(reference2.name).isEqualTo(parseNameOrFail("attr/labelPosition"))
val attr1 = getValue(newTable, "attr/showText") as? AttributeResource
Truth.assertThat(attr1).isNotNull()
Truth.assertThat(attr1!!.symbols).hasSize(0)
val attr2 = getValue(newTable, "attr/labelPosition") as? AttributeResource
Truth.assertThat(attr2).isNotNull()
Truth.assertThat(attr2!!.symbols).hasSize(2)
val symbol1 = attr2.symbols[0]
Truth.assertThat(symbol1.type).isEqualTo(ResValue.DataType.INT_DEC.byteValue)
Truth.assertThat(symbol1.value).isEqualTo(0)
Truth.assertThat(symbol1.symbol.name).isEqualTo(parseNameOrFail("id/left"))
val symbol2 = attr2.symbols[1]
Truth.assertThat(symbol2.type).isEqualTo(ResValue.DataType.INT_DEC.byteValue)
Truth.assertThat(symbol2.value).isEqualTo(1)
Truth.assertThat(symbol2.symbol.name).isEqualTo(parseNameOrFail("id/right"))
}
@Test
fun testCompileRawFileSupported() {
val input = """
I'm just writing some text here, because raw files can be anything.
""".trimIndent()
val result = ContainerReader(testRawFile(input, "txt"))
Truth.assertThat(result.numEntries).isEqualTo(1)
val entry = result.entries[0] as? FileEntry
Truth.assertThat(entry).isNotNull()
val header = entry!!.header
val data = entry.data
Truth.assertThat(header.resourceName).isEqualTo("raw/test")
Truth.assertThat(deserializeConfigFromPb(header.config, null).toString())
.isEqualTo("DEFAULT")
Truth.assertThat(header.exportedSymbolCount).isEqualTo(0)
Truth.assertThat(header.type).isEqualTo(Resources.FileReference.Type.UNKNOWN)
val dataString = String(data)
Truth.assertThat(dataString).isEqualTo(input)
}
@Test
fun testCompilePatch9ProcessingNotSupported() {
val input = """
What is here doesn't really matter, as the files location and extension determines
whether or not this file is treated as a png.
""".trimIndent()
try {
testPngFile(AaptResourceType.LAYOUT, input, isPatch9 = true)
fail()
} catch (e: ResourceCompilationException) {
Truth.assertThat(e.cause!!.message).contains(
"Patch 9 PNG processing is not supported with the JVM Android resource compiler."
)
// expected
}
}
@Test
fun testCompilePngCrunchingNotSupported() {
val input = """
What is here doesn't really matter, as the files location and extension determines
whether or not this file is treated as a png.
""".trimIndent()
try {
testPngFile(
AaptResourceType.LAYOUT,
input,
options = ResourceCompilerOptions(requirePngCrunching = true)
)
fail()
} catch (e: ResourceCompilationException) {
Truth.assertThat(e.cause!!.message).contains(
"PNG crunching is not supported with the JVM Android resource compiler."
)
// expected
}
}
@Test
fun testCompilePngWithoutCrunchingSupported() {
val input = """
What is here doesn't really matter, as the files location and extension determines
whether or not this file is treated as a png.
""".trimIndent()
val result = ContainerReader(testPngFile(AaptResourceType.LAYOUT, input))
Truth.assertThat(result.numEntries).isEqualTo(1)
val entry = result.entries[0] as? FileEntry
Truth.assertThat(entry).isNotNull()
val header = entry!!.header
val data = entry.data
Truth.assertThat(header.resourceName).isEqualTo("layout/test")
Truth.assertThat(deserializeConfigFromPb(header.config, null).toString())
.isEqualTo("DEFAULT")
Truth.assertThat(header.exportedSymbolCount).isEqualTo(0)
Truth.assertThat(header.type).isEqualTo(Resources.FileReference.Type.UNKNOWN)
val dataString = String(data)
Truth.assertThat(dataString).isEqualTo(input)
}
@Test
fun testCompileFileWithCustomSourcePath() {
val input = // language=XML
"""<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextBox android:id="@+id/text_one" android:background="@color/color_one"/>
</LinearLayout>
""".trimIndent()
val path = FileUtils.join("main", "res", "values", "test.xml")
val options = ResourceCompilerOptions(sourcePath = path)
val result = ContainerReader(
testXmlFile(type = AaptResourceType.LAYOUT, input = input, options = options))
Truth.assertThat(result.numEntries).isEqualTo(1)
val entries = result.entries as List<FileEntry>
val sourcePath: String = entries[0].header.sourcePath
Truth.assertThat(sourcePath).isEqualTo(path)
}
@Test
fun testMacros() {
val input = """
<?xml version="1.0" encoding="utf-8"?>
<resources>
<macro name="m1">Hello world</macro>
<macro name="m2">@macro/m1</macro>
</resources>
""".trimIndent()
val result = ContainerReader(testValuesFile(input))
Truth.assertThat(result.entries).hasSize(1)
val entry = result.entries[0] as? TableEntry
Truth.assertThat(entry).isNotNull()
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(entry!!.table, newTable, null)).isTrue()
val m1 = getValue(newTable, "macro/m1") as? Macro
Truth.assertThat(m1).isNotNull()
Truth.assertThat(m1!!.rawValue).isEqualTo("Hello world")
val m2 = getValue(newTable, "macro/m2") as? Macro
Truth.assertThat(m2).isNotNull()
Truth.assertThat(m2!!.rawValue).isEqualTo("@macro/m1")
}
}
@@ -0,0 +1,479 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.android.ResTableConfig
import com.android.aaptcompiler.android.ResValue
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.google.common.truth.Truth
import org.junit.Test
internal fun getValue(
table: ResourceTable,
resName: String,
config: ConfigDescription = ConfigDescription(),
productName: String = ""): Value? {
val name = parseResourceName(resName)!!.resourceName
val result = table.findResource(name)
result ?: return null
val configValue = result.entry.findValue(config, productName)
configValue ?: return null
return configValue.value
}
class ResourceTableTest {
@Test
fun failToAddResourceWithBadName() {
val table = ResourceTable()
val id = Id()
id.source = Source("test.xml", 21)
Truth.assertThat(
table.addResource(
ResourceName("android", AaptResourceType.ID, "hey,there"),
ConfigDescription(),
"",
id)).isFalse()
Truth.assertThat(
table.addResource(
ResourceName("android", AaptResourceType.ID, "hey:there"),
ConfigDescription(),
"",
id)).isFalse()
}
@Test
fun testAddResourceWithMangledNameHandlesWeirdNames() {
val table = ResourceTable()
val id = Id()
id.source = Source ("test.xml", 23)
Truth.assertThat(
table.addResourceMangled(
parseResourceName("android:id/heythere ")!!.resourceName,
ConfigDescription(),
"",
id)).isTrue()
}
@Test
fun testAddResource() {
val table = ResourceTable()
val id = Id()
id.source = Source("test.xml", 25)
Truth.assertThat(
table.addResource(
parseResourceName("android:attr/id")!!.resourceName,
ConfigDescription(),
"",
id)).isTrue()
Truth.assertThat(getValue(table, "android:attr/id")).isNotNull()
}
@Test
fun testStyleables() {
// Styleable children are nested deep under ResourceEntry, so the ResourceGroup.getStyleable()
// method was introduce. This test checks that it works correctly.
val table = ResourceTable()
val styleableOne = Styleable()
styleableOne.entries.add(0, Reference(ResourceName("", AaptResourceType.ATTR, "child_one")))
styleableOne.entries.add(1, Reference(ResourceName("android", AaptResourceType.ATTR, "child_two")))
Truth.assertThat(
table.addResource(
ResourceName("", AaptResourceType.STYLEABLE, "styleable_parent_one"),
ConfigDescription(),
"",
styleableOne)).isTrue()
val styleableTwo = Styleable()
styleableTwo.entries.add(0, Reference(ResourceName("", AaptResourceType.ATTR, "child_one")))
styleableTwo.entries.add(1, Reference(ResourceName("android", AaptResourceType.ATTR, "child_three")))
Truth.assertThat(
table.addResource(
ResourceName("", AaptResourceType.STYLEABLE, "styleable_parent_two"),
ConfigDescription(),
"",
styleableTwo)).isTrue()
Truth.assertThat(getValue(table, "styleable/styleable_parent_one")).isNotNull()
Truth.assertThat(getValue(table, "styleable/styleable_parent_two")).isNotNull()
Truth.assertThat(table.packages).hasSize(1)
Truth.assertThat(table.packages[0].groups).hasSize(1)
val styleableGroup = table.packages[0].groups[0]
Truth.assertThat(styleableGroup.entries).hasSize(2)
val styleableEntryOne = styleableGroup.entries.entries.toList()[0]
Truth.assertThat(styleableEntryOne).isNotNull()
Truth.assertThat(styleableEntryOne.value.values.first().name).isEqualTo("styleable_parent_one")
val children = table.packages[0].groups[0].getStyleable(styleableEntryOne).entries
Truth.assertThat(children).hasSize(2)
Truth.assertThat(children[0].name.toString()).isEqualTo("attr/child_one")
Truth.assertThat(children[1].name.toString()).isEqualTo("android:attr/child_two")
val styleableEntryTwo = styleableGroup.entries.entries.toList()[1]
Truth.assertThat(styleableEntryTwo).isNotNull()
Truth.assertThat(styleableEntryTwo.value.values.first().name).isEqualTo("styleable_parent_two")
val children_two = table.packages[0].groups[0].getStyleable(styleableEntryTwo).entries
Truth.assertThat(children_two).hasSize(2)
Truth.assertThat(children_two[0].name.toString()).isEqualTo("attr/child_one")
Truth.assertThat(children_two[1].name.toString()).isEqualTo("android:attr/child_three")
}
@Test
fun testAddMultipleResource() {
val table = ResourceTable()
val config = ConfigDescription()
val languageConfig =
ConfigDescription(ResTableConfig(language = byteArrayOf('p'.toByte(), 'l'.toByte())))
val id1 = Id()
id1.source = Source("test/path/file.xml", 10)
val id2 = Id()
id2.source = Source("test/path/file.xml", 12)
val id3 = Id()
id3.source = Source("test/path/file.xml", 14)
val id4 = Id()
id4.source = Source ("test/path/file.xml", 20)
Truth.assertThat(
table.addResource(
parseResourceName("android:attr/layout_width")!!.resourceName,
config,
"",
id1)).isTrue()
Truth.assertThat(
table.addResource(
parseResourceName("android:attr/id")!!.resourceName,
config,
"",
id2)).isTrue()
Truth.assertThat(
table.addResource(
parseResourceName("android:string/ok")!!.resourceName,
config,
"",
id3)).isTrue()
Truth.assertThat(
table.addResource(
parseResourceName("android:string/ok")!!.resourceName,
languageConfig,
"",
id4)).isTrue()
Truth.assertThat(getValue(table, "android:attr/layout_width")).isNotNull()
Truth.assertThat(getValue(table, "android:attr/id")).isNotNull()
Truth.assertThat(getValue(table, "android:string/ok")).isNotNull()
Truth.assertThat(getValue(table, "android:string/ok", languageConfig)).isNotNull()
}
@Test
fun testOverrideWeakResource() {
val table = ResourceTable()
val config = ConfigDescription()
val weakAttr = AttributeResource()
weakAttr.weak = true
val strongAttr = AttributeResource()
strongAttr.weak = false
Truth.assertThat(
table.addResource(
parseResourceName("android:attr/foo")!!.resourceName,
config,
"",
weakAttr)).isTrue()
var attr = getValue(table, "android:attr/foo") as? AttributeResource
Truth.assertThat(attr).isNotNull()
Truth.assertThat(attr!!.weak).isTrue()
Truth.assertThat(
table.addResource(
parseResourceName("android:attr/foo")!!.resourceName,
config,
"",
strongAttr)).isTrue()
attr = getValue(table, "android:attr/foo") as? AttributeResource
Truth.assertThat(attr).isNotNull()
Truth.assertThat(attr!!.weak).isFalse()
}
@Test
fun testCompatibleDuplicateAttributesAreAllowed() {
val table = ResourceTable()
val config = ConfigDescription()
val name = parseResourceName("android:attr/foo")!!.resourceName
val weak1 = AttributeResource(Resources.Attribute.FormatFlags.STRING_VALUE)
weak1.weak = true
val weak2 = AttributeResource(
Resources.Attribute.FormatFlags.STRING_VALUE or
Resources.Attribute.FormatFlags.REFERENCE_VALUE)
weak2.weak = true
Truth.assertThat(table.addResource(name, config, "", weak1)).isTrue()
Truth.assertThat(table.addResource(name, config, "", weak2)).isTrue()
}
@Test
fun testProductVaryingValues() {
val table = ResourceTable()
val config = parse("land")
val name = parseResourceName("android:string/foo")!!.resourceName
Truth.assertThat(table.addResource(name, config, "tablet", Id())).isTrue()
Truth.assertThat(table.addResource(name, config, "phone", Id())).isTrue()
Truth.assertThat(getValue(table, "android:string/foo", parse("land"), "tablet")).isNotNull()
Truth.assertThat(getValue(table, "android:string/foo", parse("land"), "phone")).isNotNull()
val searchResult = table.findResource(parseResourceName("android:string/foo")!!.resourceName)
Truth.assertThat(searchResult).isNotNull()
val values = searchResult!!.entry.values
values.sortBy { it.product }
Truth.assertThat(values).hasSize(2)
Truth.assertThat(values[0].product).isEqualTo("phone")
Truth.assertThat(values[1].product).isEqualTo("tablet")
}
private fun visibilityOfResourceTest(
table: ResourceTable,
name: ResourceName,
expected: ResourceVisibility,
expectedComment: String) {
val result = table.findResource(name)
Truth.assertThat(result).isNotNull()
val visibility = result!!.entry.visibility
Truth.assertThat(visibility.level).isEqualTo(expected)
Truth.assertThat(visibility.comment).isEqualTo(expectedComment)
}
@Test
fun testSetVisibility() {
val table = ResourceTable()
val name = parseResourceName("android:string/foo")!!.resourceName
var visibility = Visibility(comment = "private", level = ResourceVisibility.PRIVATE)
Truth.assertThat(table.setVisibility(name, visibility)).isTrue()
visibilityOfResourceTest(table, name, ResourceVisibility.PRIVATE, "private")
// Undefined visibility will not override defined visibility.
visibility = Visibility(comment = "undefined", level = ResourceVisibility.UNDEFINED)
Truth.assertThat(table.setVisibility(name, visibility)).isTrue()
visibilityOfResourceTest(table, name, ResourceVisibility.PRIVATE, "private")
// Public visibility clashes with previously defined private visibility.
visibility = Visibility(comment = "public", level = ResourceVisibility.PUBLIC)
Truth.assertThat(table.setVisibility(name, visibility)).isFalse()
}
@Test
fun testSetVisibilityWithId() {
val table = ResourceTable()
var name = parseResourceName("android:string/foo")!!.resourceName
val visibility1 = Visibility(comment = "default", level = ResourceVisibility.UNDEFINED)
Truth.assertThat(table.setVisibilityWithId(name, visibility1, 0)).isTrue()
visibilityOfResourceTest(table, name, ResourceVisibility.UNDEFINED, "default")
val visibility2 = Visibility(comment = "public", level = ResourceVisibility.PUBLIC)
Truth.assertThat(table.setVisibilityWithId(name, visibility2, 0x01020000)).isTrue()
visibilityOfResourceTest(table, name, ResourceVisibility.PUBLIC, "public")
name = parseResourceName("android:string/foobar")!!.resourceName
val visibility3 = Visibility(comment = "private2", level = ResourceVisibility.PRIVATE)
Truth.assertThat(table.setVisibilityWithId(name, visibility3, 0)).isTrue()
visibilityOfResourceTest(table, name, ResourceVisibility.PRIVATE, "private2")
}
@Test
fun testSetAllowNew() {
val table = ResourceTable()
val name = parseResourceName("android:string/foo")!!.resourceName
var allowNew = AllowNew(Source(""), "first")
Truth.assertThat(table.setAllowNew(name, allowNew)).isTrue()
var result = table.findResource(name)
Truth.assertThat(result).isNotNull()
Truth.assertThat(result!!.entry.allowNew).isNotNull()
Truth.assertThat(result.entry.allowNew!!.comment).isEqualTo("first")
allowNew = AllowNew(Source(""), "second")
Truth.assertThat(table.setAllowNew(name, allowNew)).isTrue()
result = table.findResource(name)
Truth.assertThat(result).isNotNull()
Truth.assertThat(result!!.entry.allowNew).isNotNull()
Truth.assertThat(result.entry.allowNew!!.comment).isEqualTo("second")
}
@Test
fun testSetOverlayable() {
val table = ResourceTable()
val overlayable =
Overlayable("Name", "overlay://theme", Source("res/values/overlayable.xml", 40))
val overlayableItem = OverlayableItem(
overlayable,
OverlayableItem.Policy.PRODUCT or OverlayableItem.Policy.VENDOR,
"comment",
Source("res/values/overlayable.xml", 42))
val name = parseResourceName("android:string/foo")!!.resourceName
table.setOverlayable(name, overlayableItem)
val result = table.findResource(name)
Truth.assertThat(result).isNotNull()
Truth.assertThat(result!!.entry.overlayable).isNotNull()
val entryOverlayable = result.entry.overlayable!!
Truth.assertThat(entryOverlayable.overlayable.name).isEqualTo("Name")
Truth.assertThat(entryOverlayable.overlayable.actor).isEqualTo("overlay://theme")
Truth.assertThat(entryOverlayable.overlayable.source.path)
.isEqualTo("res/values/overlayable.xml")
Truth.assertThat(entryOverlayable.overlayable.source.line).isEqualTo(40)
Truth.assertThat(entryOverlayable.policies)
.isEqualTo(OverlayableItem.Policy.PRODUCT or OverlayableItem.Policy.VENDOR)
Truth.assertThat(entryOverlayable.comment).isEqualTo("comment")
Truth.assertThat(entryOverlayable.source.path).isEqualTo("res/values/overlayable.xml")
Truth.assertThat(entryOverlayable.source.line).isEqualTo(42)
}
@Test
fun testSetMultipleOverlayableResources() {
val table = ResourceTable()
val foo = parseResourceName("android:string/foo")!!.resourceName
val overlayable = Overlayable("Name", "overlay://theme", Source(""))
val overlayableItem = OverlayableItem(overlayable, OverlayableItem.Policy.PRODUCT)
Truth.assertThat(table.setOverlayable(foo, overlayableItem)).isTrue()
val bar = parseResourceName("android:string/bar")!!.resourceName
val overlayableItem2 = OverlayableItem(overlayable, OverlayableItem.Policy.PRODUCT)
Truth.assertThat(table.setOverlayable(bar, overlayableItem2)).isTrue()
val baz = parseResourceName("android:string/baz")!!.resourceName
val overlayableItem3 = OverlayableItem(overlayable, OverlayableItem.Policy.VENDOR)
Truth.assertThat(table.setOverlayable(baz, overlayableItem3)).isTrue()
}
@Test
fun testSetSameOverlayableDifferentNames() {
val table = ResourceTable()
val foo = parseResourceName("android:string/foo")!!.resourceName
val overlayable = Overlayable("Name", "overlay://theme", Source(""))
val overlayableItem = OverlayableItem(overlayable, OverlayableItem.Policy.PRODUCT)
Truth.assertThat(table.setOverlayable(foo, overlayableItem)).isTrue()
val bar = parseResourceName("android:string/bar")!!.resourceName
val overlayable2 = Overlayable("Name2", "overlay://theme", Source(""))
val overlayableItem2 = OverlayableItem(overlayable2, OverlayableItem.Policy.PRODUCT)
Truth.assertThat(table.setOverlayable(bar, overlayableItem2)).isTrue()
}
@Test
fun failToSetOverlayablesSameResource() {
val table = ResourceTable()
val name = parseResourceName("android:string/foo")!!.resourceName
val overlayable = Overlayable("Name", "overlay://theme", Source(""))
val overlayableItem = OverlayableItem(overlayable)
Truth.assertThat(table.setOverlayable(name, overlayableItem)).isTrue()
val overlayableItem2 = OverlayableItem(overlayable)
Truth.assertThat(table.setOverlayable(name, overlayableItem2)).isFalse()
}
@Test
fun failToSetDifferentOverlayablesSameResource() {
val table = ResourceTable()
val name = parseResourceName("android:string/foo")!!.resourceName
val overlayable = Overlayable("Name", "overlay://theme", Source(""))
val overlayableItem = OverlayableItem(overlayable)
Truth.assertThat(table.setOverlayable(name, overlayableItem)).isTrue()
val overlayable2 = Overlayable("Other", "overlay://theme", Source(""))
val overlayableItem2 = OverlayableItem(overlayable2)
Truth.assertThat(table.setOverlayable(name, overlayableItem2)).isFalse()
}
@Test
fun testAllowDuplicateResourceNames() {
val table = ResourceTable(false)
val name = parseResourceName("android:bool/foo")!!.resourceName
Truth.assertThat(
table.addResourceWithId(
name,
0x7f0100ff,
ConfigDescription(),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, 0)))).isTrue()
Truth.assertThat(
table.addResourceWithId(
name,
0x7f010100,
ConfigDescription(),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, 1)))).isTrue()
Truth.assertThat(
table.setVisibilityWithId(
name, Visibility(level = ResourceVisibility.PUBLIC), 0x7f0100ff)).isTrue()
Truth.assertThat(
table.setVisibilityWithId(
name, Visibility(level = ResourceVisibility.PRIVATE), 0x7f010100)).isTrue()
val tablePackage = table.findPackageById(0x7f)
Truth.assertThat(tablePackage).isNotNull()
val tableGroup = tablePackage!!.findGroup(AaptResourceType.BOOL)
Truth.assertThat(tableGroup).isNotNull()
val entry1 = tableGroup!!.findEntry("foo", 0x00ff)
Truth.assertThat(entry1).isNotNull()
Truth.assertThat(entry1!!.id).isEqualTo(0x00ff)
Truth.assertThat(entry1.values).hasSize(1)
val value1 = entry1.values[0].value as? BinaryPrimitive
Truth.assertThat(value1).isNotNull()
Truth.assertThat(value1!!.resValue.data).isEqualTo(0)
Truth.assertThat(entry1.visibility.level).isEqualTo(ResourceVisibility.PUBLIC)
val entry2 = tableGroup.findEntry("foo", 0x0100)
Truth.assertThat(entry2).isNotNull()
Truth.assertThat(entry2!!.id).isEqualTo(0x0100)
Truth.assertThat(entry2.values).hasSize(1)
val value2 = entry2.values[0].value as? BinaryPrimitive
Truth.assertThat(value2).isNotNull()
Truth.assertThat(value2!!.resValue.data).isEqualTo(1)
Truth.assertThat(entry2.visibility.level).isEqualTo(ResourceVisibility.PRIVATE)
}
}
@@ -0,0 +1,156 @@
package com.android.aaptcompiler
import com.android.aapt.Resources
import com.android.aaptcompiler.android.ResValue
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class ResourceUtilsTest {
@Test
fun testParseBool() {
assertThat(parseAsBool("true")).isTrue()
assertThat(parseAsBool("TRUE")).isTrue()
assertThat(parseAsBool("True")).isTrue()
assertThat(parseAsBool("false")).isFalse()
assertThat(parseAsBool("FALSE")).isFalse()
assertThat(parseAsBool("False")).isFalse()
assertThat(parseAsBool(" False\n ")).isFalse()
}
@Test
fun testParseResourceName() {
var result = parseResourceName("android:color/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("android", AaptResourceType.COLOR, "foo"))
assertThat(result?.isPrivate).isFalse()
result = parseResourceName("color/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("", AaptResourceType.COLOR, "foo"))
assertThat(result?.isPrivate).isFalse()
result = parseResourceName("*android:string/foo")
assertThat(result).isNotNull()
assertThat(result?.resourceName)
.isEqualTo(ResourceName("android", AaptResourceType.STRING, "foo"))
assertThat(result?.isPrivate).isTrue()
result = parseResourceName("")
assertThat(result).isNull()
}
@Test
fun testParseReferenceWithNoPackage() {
val result = parseReference("@color/bar")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("", AaptResourceType.COLOR, "bar"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@Test
fun testParseReferenceWithPackage() {
val result = parseReference("@android:integer/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.INTEGER, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@Test
fun testParseReferenceWithSurroundingWhitespace() {
val result = parseReference("\t @android:integer/foo\n \n\t")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.INTEGER, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isFalse()
}
@Test
fun testParseAutoCreateIdReference() {
val result = parseReference("@+android:id/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.ID, "foo"))
assertThat(result?.createNew).isTrue()
assertThat(result?.reference?.isPrivate).isFalse()
}
@Test
fun testParsePrivateReference() {
val result = parseReference("@*android:id/foo")
assertThat(result).isNotNull()
assertThat(result?.reference?.name)
.isEqualTo(ResourceName("android", AaptResourceType.ID, "foo"))
assertThat(result?.createNew).isFalse()
assertThat(result?.reference?.isPrivate).isTrue()
}
@Test
fun testFailToParseAutoCreateNonIdReference() {
val result = parseReference("@+android:color/foo")
assertThat(result).isNull()
}
@Test
fun testParseAttributeReference() {
assertThat(parseAttributeReference("?android")).isNotNull()
assertThat(parseAttributeReference("?android:foo")).isNotNull()
assertThat(parseAttributeReference("?attr/foo")).isNotNull()
assertThat(parseAttributeReference("?android:attr/foo")).isNotNull()
assertThat(parseAttributeReference("?style/foo")).isNull()
assertThat(parseAttributeReference("?android:style/foo")).isNull()
assertThat(parseAttributeReference("?android:")).isNull()
assertThat(parseAttributeReference("?android:attr/")).isNull()
assertThat(parseAttributeReference("?:attr/")).isNull()
assertThat(parseAttributeReference("?:attr/foo")).isNull()
assertThat(parseAttributeReference("?:/")).isNull()
assertThat(parseAttributeReference("?:/foo")).isNull()
assertThat(parseAttributeReference("?attr/")).isNull()
assertThat(parseAttributeReference("?/foo")).isNull()
}
@Test
fun testNullIsEmptyReference() {
assertThat(makeNull()).isEqualTo(Reference())
assertThat(tryParseNullOrEmpty("@null")).isEqualTo(Reference())
}
@Test
fun testEmptyIsBinaryPrimitive() {
assertThat(makeEmpty())
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.EMPTY)))
assertThat(tryParseNullOrEmpty("@empty"))
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.EMPTY)))
}
@Test
fun testItemsWithWhitespaceAreParsedCorrectly() {
var result =
tryParseItemForAttribute(" 12\n ", Resources.Attribute.FormatFlags.INTEGER_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.INT_DEC, 12)))
result =
tryParseItemForAttribute(" true\n ", Resources.Attribute.FormatFlags.BOOLEAN_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.INT_BOOLEAN, -1)))
val expectedFloat = 12.0f
result =
tryParseItemForAttribute(" 12.0\n ", Resources.Attribute.FormatFlags.FLOAT_VALUE)
assertThat(result).isNotNull()
assertThat(result)
.isEqualTo(BinaryPrimitive(ResValue(ResValue.DataType.FLOAT, expectedFloat.toRawBits())))
}
}
@@ -0,0 +1,334 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.android.ResStringPool
import com.android.aaptcompiler.buffer.BigBuffer
import com.google.common.truth.Truth
import org.junit.Assert.assertThrows
import org.junit.Ignore
import org.junit.Test
import java.nio.ByteBuffer
class StringPoolTest {
@Test
fun testInsertOneString() {
val pool = StringPool()
val ref = pool.makeRef("wut")
Truth.assertThat(ref.value()).isEqualTo("wut")
}
@Test
fun testInsertTwoStrings() {
val pool = StringPool()
val ref1 = pool.makeRef("wut")
val ref2 = pool.makeRef("hey")
Truth.assertThat(ref1.value()).isEqualTo("wut")
Truth.assertThat(ref2.value()).isEqualTo("hey")
}
@Test
fun testInsertDuplicateStrings() {
val pool = StringPool()
val ref1 = pool.makeRef("wut")
val ref2 = pool.makeRef("wut")
Truth.assertThat(ref1.value()).isEqualTo("wut")
Truth.assertThat(ref2.value()).isEqualTo("wut")
Truth.assertThat(pool.size()).isEqualTo(1)
}
@Test
fun testStringsWithDifferentPriorityAreNotDuplicates() {
val pool = StringPool()
val ref1 = pool.makeRef("wut", StringPool.Context(0x81010001L))
val ref2 = pool.makeRef("wut", StringPool.Context(0x81010002L))
Truth.assertThat(ref1.value()).isEqualTo("wut")
Truth.assertThat(ref2.value()).isEqualTo("wut")
Truth.assertThat(pool.size()).isEqualTo(2)
}
@Test
fun testMaintainInsertionOrderIndex() {
val pool = StringPool()
val ref1 = pool.makeRef("z")
val ref2 = pool.makeRef("a")
val ref3 = pool.makeRef("m")
Truth.assertThat(ref1.index()).isEqualTo(0)
Truth.assertThat(ref2.index()).isEqualTo(1)
Truth.assertThat(ref3.index()).isEqualTo(2)
}
@Test
@Ignore("b/303074426") // TODO(@daniellabar): fix pruning and reference management.
fun pruneStringsWithNoReferences() {
// TEST HERE
}
@Test
fun sortAndMaintainIndexesInStringReferences() {
val pool = StringPool()
val ref1 = pool.makeRef("z")
val ref2 = pool.makeRef("a")
val ref3 = pool.makeRef("m")
Truth.assertThat(ref1.value()).isEqualTo("z")
Truth.assertThat(ref1.index()).isEqualTo(0)
Truth.assertThat(ref2.value()).isEqualTo("a")
Truth.assertThat(ref2.index()).isEqualTo(1)
Truth.assertThat(ref3.value()).isEqualTo("m")
Truth.assertThat(ref3.index()).isEqualTo(2)
pool.sort()
Truth.assertThat(ref1.value()).isEqualTo("z")
Truth.assertThat(ref1.index()).isEqualTo(2)
Truth.assertThat(ref2.value()).isEqualTo("a")
Truth.assertThat(ref2.index()).isEqualTo(0)
Truth.assertThat(ref3.value()).isEqualTo("m")
Truth.assertThat(ref3.index()).isEqualTo(1)
}
@Test
fun deduplicateStillWorksWithSort() {
val pool = StringPool()
val ref1 = pool.makeRef("z")
val ref2 = pool.makeRef("a")
val ref3 = pool.makeRef("m")
pool.sort()
val ref4 = pool.makeRef("z")
val ref5 = pool.makeRef("a")
val ref6 = pool.makeRef("m")
Truth.assertThat(ref1.index()).isEqualTo(ref4.index())
Truth.assertThat(ref2.index()).isEqualTo(ref5.index())
Truth.assertThat(ref3.index()).isEqualTo(ref6.index())
}
@Test
fun testAddStyles() {
val pool = StringPool()
val ref = pool.makeRef(StyleString("android", listOf(Span("b", 2, 6))))
Truth.assertThat(ref.index()).isEqualTo(0)
Truth.assertThat(ref.value()).isEqualTo("android")
Truth.assertThat(ref.spans()).hasSize(1)
val span = ref.spans()[0]
Truth.assertThat(span.name.value()).isEqualTo("b")
Truth.assertThat(span.firstChar).isEqualTo(2)
Truth.assertThat(span.lastChar).isEqualTo(6)
}
@Test
fun doNotDeduplicateStyleWithString() {
val pool = StringPool()
val ref = pool.makeRef("android")
val styleRef = pool.makeRef(StyleString("android", listOf()))
Truth.assertThat(ref.index()).isNotEqualTo(styleRef.index())
}
@Test
fun testSortingKeepsStylesAndStringsSeparate() {
val pool = StringPool()
val ref1 = pool.makeRef(StyleString("beta", listOf()))
val ref2 = pool.makeRef("alpha")
val ref3 = pool.makeRef(StyleString("alpha", listOf()))
Truth.assertThat(ref2.index()).isNotEqualTo(ref3.index())
pool.sort()
Truth.assertThat(ref3.index()).isEqualTo(0)
Truth.assertThat(ref1.index()).isEqualTo(1)
Truth.assertThat(ref2.index()).isEqualTo(2)
}
@Test
fun flattenEmptyStringPoolUtf8() {
val pool = StringPool()
val buffer = BigBuffer(1028)
pool.flattenUtf8(buffer, null)
Truth.assertThat(buffer.size).isEqualTo(28)
val test = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
Truth.assertThat(test.stringPoolSize).isEqualTo(0)
}
@Test
fun flattenOddCharactersUtf16() {
val pool = StringPool()
pool.makeRef("\u093f")
val buffer = BigBuffer(1028)
pool.flattenUtf16(buffer, null)
// header size (28) + string array (4) + string length (2) + string itself (2) +
// null terminator (2) + padding (2)
Truth.assertThat(buffer.size).isEqualTo(40)
val test = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
Truth.assertThat(test.strings).hasSize(1)
val string = test.strings[0]
Truth.assertThat(string).isEqualTo("\u093f")
}
@Test
fun flattenPool() {
val pool = StringPool()
val ref1 = pool.makeRef("hello")
val ref2 = pool.makeRef("goodbye")
val ref3 = pool.makeRef(LONG_STRING)
val ref4 = pool.makeRef("")
val ref5 = pool.makeRef(StyleString("style", listOf(Span("b", 0, 1), Span("i", 2, 3))))
Truth.assertThat(ref5.index()).isEqualTo(0)
Truth.assertThat(ref1.index()).isEqualTo(1)
Truth.assertThat(ref2.index()).isEqualTo(2)
Truth.assertThat(ref3.index()).isEqualTo(3)
Truth.assertThat(ref4.index()).isEqualTo(4)
val utf8Buffer = BigBuffer(1024)
val utf16Buffer = BigBuffer(1024)
pool.flattenUtf8(utf8Buffer, null)
pool.flattenUtf16(utf16Buffer, null)
// Test both buffers.
val utf8Test = ResStringPool.get(ByteBuffer.wrap(utf8Buffer.toBytes()), utf8Buffer.size)
val utf16Test = ResStringPool.get(ByteBuffer.wrap(utf16Buffer.toBytes()), utf16Buffer.size)
// size = number of direct string refs (5) + number of refs created by spans.
Truth.assertThat(utf8Test.strings).hasSize(7)
Truth.assertThat(utf16Test.strings).hasSize(7)
Truth.assertThat(utf8Test.strings[0]).isEqualTo("style")
Truth.assertThat(utf16Test.strings[0]).isEqualTo("style")
Truth.assertThat(utf8Test.strings[1]).isEqualTo("hello")
Truth.assertThat(utf16Test.strings[1]).isEqualTo("hello")
Truth.assertThat(utf8Test.strings[2]).isEqualTo("goodbye")
Truth.assertThat(utf16Test.strings[2]).isEqualTo("goodbye")
Truth.assertThat(utf8Test.strings[3]).isEqualTo(LONG_STRING)
Truth.assertThat(utf16Test.strings[3]).isEqualTo(LONG_STRING)
Truth.assertThat(utf8Test.strings[4]).isEqualTo("")
Truth.assertThat(utf16Test.strings[4]).isEqualTo("")
Truth.assertThat(utf8Test.styles).hasSize(1)
Truth.assertThat(utf16Test.styles).hasSize(1)
val utf8Style = utf8Test.styles[0]
val utf16Style = utf16Test.styles[0]
Truth.assertThat(utf8Test.strings[utf8Style[0].name.index]).isEqualTo("b")
Truth.assertThat(utf16Test.strings[utf16Style[0].name.index]).isEqualTo("b")
Truth.assertThat(utf8Style[0].firstChar).isEqualTo(0)
Truth.assertThat(utf16Style[0].firstChar).isEqualTo(0)
Truth.assertThat(utf8Style[0].lastChar).isEqualTo(1)
Truth.assertThat(utf16Style[0].lastChar).isEqualTo(1)
Truth.assertThat(utf8Test.strings[utf8Style[1].name.index]).isEqualTo("i")
Truth.assertThat(utf16Test.strings[utf16Style[1].name.index]).isEqualTo("i")
Truth.assertThat(utf8Style[1].firstChar).isEqualTo(2)
Truth.assertThat(utf16Style[1].firstChar).isEqualTo(2)
Truth.assertThat(utf8Style[1].lastChar).isEqualTo(3)
Truth.assertThat(utf16Style[1].lastChar).isEqualTo(3)
}
@Test
fun testMaxEncodingLengthUTF8() {
val pool1 = StringPool()
pool1.makeRef("Why, hello!")
var utf8Buffer = BigBuffer(1024)
pool1.flattenUtf8(utf8Buffer, null)
val utf8Test = ResStringPool.get(ByteBuffer.wrap(utf8Buffer.toBytes()), utf8Buffer.size)
Truth.assertThat(utf8Test.strings).hasSize(1)
Truth.assertThat(utf8Test.strings[0]).isEqualTo("Why, hello!")
val pool2 = StringPool()
val superLongString = String(CharArray(33000) {'a'})
pool2.makeRef("This fits1")
pool2.makeRef(superLongString)
pool2.makeRef("This fits2")
utf8Buffer = BigBuffer(1024)
val utf8Exception = assertThrows(Exception::class.java) {
pool2.flattenUtf8(utf8Buffer, null)
}
Truth.assertThat(utf8Exception.message).contains(
"String of size 33000 bytes is too large to encode using UTF-8 (32767 bytes).")
}
@Test
fun testMaxEncodingLengthUTF16() {
val pool1 = StringPool()
pool1.makeRef("Why, hello!")
var utf16Buffer = BigBuffer(1024)
pool1.flattenUtf16(utf16Buffer, null)
var utf16Test = ResStringPool.get(ByteBuffer.wrap(utf16Buffer.toBytes()), utf16Buffer.size)
Truth.assertThat(utf16Test.strings).hasSize(1)
Truth.assertThat(utf16Test.strings[0]).isEqualTo("Why, hello!")
val pool2 = StringPool()
val superLongString = String(CharArray(33000) {'a'})
pool2.makeRef("This fits1")
pool2.makeRef(superLongString)
pool2.makeRef("This fits2")
utf16Buffer = BigBuffer(1024)
pool2.flattenUtf16(utf16Buffer, null)
utf16Test = ResStringPool.get(ByteBuffer.wrap(utf16Buffer.toBytes()), utf16Buffer.size)
Truth.assertThat(utf16Test.strings).hasSize(3)
Truth.assertThat(utf16Test.strings[0]).isEqualTo("This fits1")
Truth.assertThat(utf16Test.strings[1]).isEqualTo(superLongString)
Truth.assertThat(utf16Test.strings[2]).isEqualTo("This fits2")
}
companion object {
const val LONG_STRING = "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑" +
"え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限" +
"します。メール、SMSや、同期を使 " +
"用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ" +
"ーバーは端末の充電中は自動的にOFFになります。"
}
}
@@ -0,0 +1,60 @@
package com.android.aaptcompiler
import com.google.common.truth.Truth
import org.junit.Test
class UnicodeUtilTest {
@Test
fun testIsXidStart() {
val validInput = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZˮø"
for (i in 0.until(validInput.codePointCount(0, validInput.length))) {
Truth.assertThat(isXidStart(validInput.codePointAt(i))).isTrue()
}
val invalidInput = "$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\",1234567890_"
for (i in 0.until(invalidInput.codePointCount(0, invalidInput.length))) {
Truth.assertThat(isXidStart(invalidInput.codePointAt(i))).isFalse()
}
}
@Test
fun testIsXidContinue() {
val validInput = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_ˮø"
for (i in 0.until(validInput.codePointCount(0, validInput.length))) {
Truth.assertThat(isXidContinue(validInput.codePointAt(i))).isTrue()
}
val invalidInput = "$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\","
for (i in 0.until(invalidInput.codePointCount(0, invalidInput.length))) {
Truth.assertThat(isXidContinue(invalidInput.codePointAt(i))).isFalse()
}
}
@Test
fun testIsJavaIdentifier() {
Truth.assertThat(isJavaIdentifier("FøøBar_12")).isTrue()
Truth.assertThat(isJavaIdentifier("Føø\$Bar")).isTrue()
Truth.assertThat(isJavaIdentifier("_FøøBar")).isTrue()
Truth.assertThat(isJavaIdentifier("\$Føø\$Bar")).isTrue()
Truth.assertThat(isJavaIdentifier("12FøøBar")).isFalse()
Truth.assertThat(isJavaIdentifier(".Hello")).isFalse()
}
@Test
fun testIsValidResourceEntryName() {
Truth.assertThat(isJavaIdentifier("FøøBar")).isTrue()
Truth.assertThat(isValidResourceEntryName("FøøBar_12")).isTrue()
Truth.assertThat(isValidResourceEntryName("Føø.Bar")).isTrue()
Truth.assertThat(isValidResourceEntryName("Føø-Bar")).isTrue()
Truth.assertThat(isValidResourceEntryName("_FøøBar")).isTrue()
Truth.assertThat(isValidResourceEntryName("12FøøBar")).isFalse()
Truth.assertThat(isValidResourceEntryName("Føø\$Bar")).isFalse()
Truth.assertThat(isValidResourceEntryName("Føø/Bar")).isFalse()
Truth.assertThat(isValidResourceEntryName("Føø:Bar")).isFalse()
Truth.assertThat(isValidResourceEntryName("Føø;Bar")).isFalse()
Truth.assertThat(isValidResourceEntryName("0_resource_name_obfuscated")).isFalse()
}
}
@@ -0,0 +1,697 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
/**
* Tests for the XmlProcessor, which parses XML files found in <source-set>/res/xml* directories.
*
* TODO(b/132800341): add a connected test to verify contents at runtime.
* If you need to manually verify the correct behaviour, place the rest file under the res/xml
* folder. To read contents you can use the XmlPullParser at runtime in an android project, for
* example to verify the strings are parsed correctly you can query:
*
* val text = view.findViewById<android.widget.TextView>(R.id.text)
* val xml = view.resources.getXml(R.xml.test)
* var eventType = -1
*
* val builder = StringBuilder()
* while (eventType != XmlResourceParser.END_DOCUMENT) {
* if (xml.eventType == XmlResourceParser.TEXT) {
* builder.append("${xml.name} = \"${xml.text}\"\n")
* }
*
* eventType = xml.next()
* }
* text.text = "Found:\n${builder.toString()}"
*/
class XmlProcessorTest {
lateinit var processor: XmlProcessor
@Before
fun beforeTest() {
processor = XmlProcessor(Source(""), null)
}
private fun processTest(input: String, configDescription: String = ""): ResourceFile? {
val file = ResourceFile(
parseNameOrFail("layout/test"),
parse(configDescription),
Source(""),
ResourceFile.Type.ProtoXml)
try {
processor.process(file, input.byteInputStream())
} catch (e: Exception) {
return null
}
return file
}
private fun getAttrName(inputFile: ResourceFile, attrNum: Int): ResourceName =
inputFile.name.copy(entry = "${inputFile.name.pck}${'$'}${inputFile.name.entry}__$attrNum")
@Test
fun testEmpty() {
assertThat(processTest("")).isNotNull()
}
@Test
fun testCollectsIds() {
val input = """
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+id/bar">
<SubView
android:id="@+id/car"
class="@+id/bar"/>
</View>
""".trimIndent()
assertThat(processTest(input)).isNotNull()
val collectedIds = processor.primaryFile.exportedSymbols
assertThat(collectedIds).hasSize(3)
assertThat(collectedIds[0]).isEqualTo(SourcedResourceName(parseNameOrFail("id/bar"), 3))
assertThat(collectedIds[1]).isEqualTo(SourcedResourceName(parseNameOrFail("id/car"), 6))
assertThat(collectedIds[2]).isEqualTo(SourcedResourceName(parseNameOrFail("id/foo"), 3))
}
@Test
fun testNoCollectNonIds() {
assertThat(processTest("""<View foo="@+string/foo"/>""")).isNotNull()
assertThat(processor.primaryFile.exportedSymbols).isEmpty()
val input = """
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+string/bar"/>
""".trimIndent()
assertThat(processTest(input)).isNotNull()
assertThat(processor.primaryFile.exportedSymbols).hasSize(1)
assertThat(processor.primaryFile.exportedSymbols[0])
.isEqualTo(SourcedResourceName(parseNameOrFail("id/foo"), 3))
}
@Test
fun failOnInvalidIds() {
assertThat(processTest("""<View foo="@+id/foo${'$'}bar"/>""")).isNull()
}
@Test
fun checkNoSpaceAdded() {
// XMLEventReader parser divides this string into two for some reason. When we're processing
// it, we should make sure we join them without a new space, keeping the original string
// intact.
val stringDividedByReader = "1a(2d1r1u]1o2r3c4u1]1o2l1a(2l2u"
val input = """<l>$stringDividedByReader</l>"""
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("l")
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(1)
// No extra spaces should be added in the middle of the string (even if XMLEventReader divides
// it).
assertThat(element.getChild(0).text).isEqualTo(stringDividedByReader)
}
@Test
fun checkNoSpacesRemoved() {
val contentWithSpaces = " hello world! We meet again "
val input = """<l>$contentWithSpaces</l>"""
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("l")
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(1)
// Extra spaces should be kept.
assertThat(element.getChild(0).text).isEqualTo(contentWithSpaces)
}
@Test
fun checkNoNewLineRemoved() {
val contentWithNewLines = """
<test>
Text that is
continued here.
</test>""".trimIndent()
val inputFile = processTest(contentWithNewLines)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("test")
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(1)
// Indentation versus the parent node should be kept here. New lines should be respected.
assertThat(element.getChild(0).text).isEqualTo("""
Text that is
continued here.
""")
}
@Test
fun checkWhitespaceComboIsNotRemoved() {
val contentWithNewLines = """
hello world !
We meet
again
"""
val input = """<l>$contentWithNewLines</l>"""
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("l")
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(1)
// We should keep the new lines and extra whitespaces, just as AAPT2 does in res/xml* files.
assertThat(element.getChild(0).text).isEqualTo(contentWithNewLines)
}
@Test
fun checkTextWithComment() {
val textWithComment = """
Text that is
<!-- comment -->
broken by child.""".trimIndent()
val input = """<l>$textWithComment</l>"""
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("l")
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(2)
// New lines are kept (including new lines), only the comment is removed.
assertThat(element.getChild(0).text).isEqualTo("Text that is\n")
assertThat(element.getChild(1).text).isEqualTo("\nbroken by child.")
}
@Test
fun testNoInlineXml() {
val input = """
<View xmlns:android="http://schemas.android.com/apk/res/android">
<View android:text="hey">
<View android:id="hi" />
</View>
</View>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(1)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("View")
assertThat(element.namespaceDeclarationList).hasSize(1)
assertThat(element.attributeList).hasSize(0)
assertThat(element.childList).hasSize(1)
val namespace = element.namespaceDeclarationList[0]
assertThat(namespace.uri).isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(namespace.prefix).isEqualTo("android")
val childNode = element.childList[0]
assertThat(childNode.hasElement()).isTrue()
val child = childNode.element
assertThat(child.name).isEqualTo("View")
assertThat(child.namespaceDeclarationList).hasSize(0)
assertThat(child.attributeList).hasSize(1)
assertThat(child.childList).hasSize(1)
val childAttr = child.attributeList[0]
assertThat(childAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(childAttr.name).isEqualTo("text")
assertThat(childAttr.value).isEqualTo("hey")
val grandchildNode = child.childList[0]
assertThat(grandchildNode.hasElement()).isTrue()
val grandchild = grandchildNode.element
assertThat(grandchild.name).isEqualTo("View")
assertThat(grandchild.namespaceDeclarationList).hasSize(0)
assertThat(grandchild.attributeList).hasSize(1)
assertThat(grandchild.childList).hasSize(0)
val grandchildAttr = grandchild.attributeList[0]
assertThat(grandchildAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(grandchildAttr.name).isEqualTo("id")
assertThat(grandchildAttr.value).isEqualTo("hi")
}
@Test
fun testExtractOneXmlResource() {
val input = """
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
<View2 android:text="hey">
<View3 android:id="hi" />
</View2>
</aapt:attr>
</View1>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(2)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("View1")
assertThat(element.namespaceDeclarationList).hasSize(2)
assertThat(element.attributeList).hasSize(1)
assertThat(element.childList).hasSize(0)
val namespace1 = element.namespaceDeclarationList[0]
assertThat(namespace1.uri).isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(namespace1.prefix).isEqualTo("android")
val namespace2 = element.namespaceDeclarationList[1]
assertThat(namespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(namespace2.prefix).isEqualTo("aapt")
// the aapt:attr should be pulled out as an attribute.
// i.e. 'android:text="@layout/$test__0"'
val attr = element.attributeList[0]
assertThat(attr.namespaceUri).isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(attr.name).isEqualTo("text")
assertThat(attr.value).isEqualTo("@${getAttrName(inputFile!!, 0)}")
val outlinedProto = processor.xmlResources[1].xmlProto
assertThat(outlinedProto).isNotNull()
assertThat(outlinedProto.hasElement()).isTrue()
val outlinedElement = outlinedProto.element
assertThat(outlinedElement.name).isEqualTo("View2")
// The outlined element inherits all active namespace declarations.
assertThat(outlinedElement.namespaceDeclarationList).hasSize(2)
assertThat(outlinedElement.attributeList).hasSize(1)
assertThat(outlinedElement.childList).hasSize(1)
val outlinedNamespace1 = outlinedElement.namespaceDeclarationList[0]
assertThat(outlinedNamespace1.uri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedNamespace1.prefix).isEqualTo("android")
val outlinedNamespace2 = outlinedElement.namespaceDeclarationList[1]
assertThat(outlinedNamespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(outlinedNamespace2.prefix).isEqualTo("aapt")
val outlinedAttr = outlinedElement.attributeList[0]
assertThat(outlinedAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedAttr.name).isEqualTo("text")
assertThat(outlinedAttr.value).isEqualTo("hey")
val outlinedChildNode = outlinedElement.childList[0]
assertThat(outlinedChildNode.hasElement()).isTrue()
val outlinedChild = outlinedChildNode.element
assertThat(outlinedChild.name).isEqualTo("View3")
assertThat(outlinedChild.namespaceDeclarationList).hasSize(0)
assertThat(outlinedChild.attributeList).hasSize(1)
assertThat(outlinedChild.childList).hasSize(0)
val outlinedChildAttr = outlinedChild.attributeList[0]
assertThat(outlinedChildAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedChildAttr.name).isEqualTo("id")
assertThat(outlinedChildAttr.value).isEqualTo("hi")
}
@Test
fun testExtractTwoSiblingResources() {
val input = """
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
<View2 android:text="hey">
<View3 android:id="hi" />
</View2>
</aapt:attr>
<aapt:attr name="android:drawable">
<vector />
</aapt:attr>
</View1>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(3)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("View1")
assertThat(element.namespaceDeclarationList).hasSize(2)
assertThat(element.attributeList).hasSize(2)
assertThat(element.childList).hasSize(0)
val namespace1 = element.namespaceDeclarationList[0]
assertThat(namespace1.uri).isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(namespace1.prefix).isEqualTo("android")
val namespace2 = element.namespaceDeclarationList[1]
assertThat(namespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(namespace2.prefix).isEqualTo("aapt")
// the aapt:attr should be pulled out as an attribute.
// i.e. 'android:text="@layout/$test__0"'
val attr1 = element.attributeList[0]
assertThat(attr1.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(attr1.name).isEqualTo("text")
assertThat(attr1.value).isEqualTo("@${getAttrName(inputFile!!, 0)}")
// The second should be pulled out as well.
// i.e. 'android:drawable="@layout/$test__1"'
val attr2 = element.attributeList[1]
assertThat(attr2.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(attr2.name).isEqualTo("drawable")
assertThat(attr2.value).isEqualTo("@${getAttrName(inputFile, 1)}")
val outlinedProto = processor.xmlResources[1].xmlProto
assertThat(outlinedProto).isNotNull()
assertThat(outlinedProto.hasElement()).isTrue()
val outlinedElement = outlinedProto.element
assertThat(outlinedElement.name).isEqualTo("View2")
// The outlined element inherits all active namespace declarations.
assertThat(outlinedElement.namespaceDeclarationList).hasSize(2)
assertThat(outlinedElement.attributeList).hasSize(1)
assertThat(outlinedElement.childList).hasSize(1)
val outlinedNamespace1 = outlinedElement.namespaceDeclarationList[0]
assertThat(outlinedNamespace1.uri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedNamespace1.prefix).isEqualTo("android")
val outlinedNamespace2 = outlinedElement.namespaceDeclarationList[1]
assertThat(outlinedNamespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(outlinedNamespace2.prefix).isEqualTo("aapt")
val outlinedAttr = outlinedElement.attributeList[0]
assertThat(outlinedAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedAttr.name).isEqualTo("text")
assertThat(outlinedAttr.value).isEqualTo("hey")
val outlinedChildNode = outlinedElement.childList[0]
assertThat(outlinedChildNode.hasElement()).isTrue()
val outlinedChild = outlinedChildNode.element
assertThat(outlinedChild.name).isEqualTo("View3")
assertThat(outlinedChild.namespaceDeclarationList).hasSize(0)
assertThat(outlinedChild.attributeList).hasSize(1)
assertThat(outlinedChild.childList).hasSize(0)
val outlinedChildAttr = outlinedChild.attributeList[0]
assertThat(outlinedChildAttr.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlinedChildAttr.name).isEqualTo("id")
assertThat(outlinedChildAttr.value).isEqualTo("hi")
val outlinedProto2 = processor.xmlResources[2].xmlProto
assertThat(outlinedProto2).isNotNull()
assertThat(outlinedProto2.hasElement()).isTrue()
val outlined2Element = outlinedProto2.element
assertThat(outlined2Element.name).isEqualTo("vector")
assertThat(outlined2Element.namespaceDeclarationList).hasSize(2)
assertThat(outlined2Element.attributeList).hasSize(0)
assertThat(outlined2Element.childList).hasSize(0)
val outlined2Namespace1 = outlinedElement.namespaceDeclarationList[0]
assertThat(outlined2Namespace1.uri)
.isEqualTo("http://schemas.android.com/apk/res/android")
assertThat(outlined2Namespace1.prefix).isEqualTo("android")
val outlined2Namespace2 = outlinedElement.namespaceDeclarationList[1]
assertThat(outlined2Namespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(outlined2Namespace2.prefix).isEqualTo("aapt")
}
@Test
fun testExtractNestedXmlResource() {
val input = """
<base_root xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="inline_xml">
<inline_root>
<aapt:attr name="nested_inline_xml">
<nested_inline_root/>
</aapt:attr>
<aapt:attr name="another_nested_inline_xml">
<root/>
</aapt:attr>
</inline_root>
</aapt:attr>
<aapt:attr name="turtles">
<root1>
<aapt:attr name="all">
<root2>
<aapt:attr name="the">
<root3>
<aapt:attr name="way">
<root4>
<aapt:attr name="down">
<root5/>
</aapt:attr>
</root4>
</aapt:attr>
</root3>
</aapt:attr>
</root2>
</aapt:attr>
</root1>
</aapt:attr>
</base_root>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
// The primary file and the 8 outlined aapt:attr's
assertThat(processor.xmlResources).hasSize(9)
}
@Test
fun testExtractIntoAppAttr() {
val input = """
<parent xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="app:foo">
<child />
</aapt:attr>
</parent>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(2)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("parent")
assertThat(element.namespaceDeclarationList).hasSize(2)
assertThat(element.attributeList).hasSize(1)
assertThat(element.childList).hasSize(0)
val namespace1 = element.namespaceDeclarationList[0]
assertThat(namespace1.uri).isEqualTo("http://schemas.android.com/apk/res-auto")
assertThat(namespace1.prefix).isEqualTo("app")
val namespace2 = element.namespaceDeclarationList[1]
assertThat(namespace2.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(namespace2.prefix).isEqualTo("aapt")
val attr1 = element.attributeList[0]
assertThat(attr1.namespaceUri)
.isEqualTo("http://schemas.android.com/apk/res-auto")
assertThat(attr1.name).isEqualTo("foo")
assertThat(attr1.value).isEqualTo("@${getAttrName(inputFile!!, 0)}")
}
@Test
fun testExtractIntoNoNamespaceAttr() {
val input = """
<parent xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="foo">
<child />
</aapt:attr>
</parent>
""".trimIndent()
val inputFile = processTest(input)
assertThat(inputFile).isNotNull()
assertThat(processor.xmlResources).hasSize(2)
val proto = processor.xmlResources[0].xmlProto
assertThat(proto).isNotNull()
assertThat(proto.hasElement()).isTrue()
val element = proto.element
assertThat(element.name).isEqualTo("parent")
assertThat(element.namespaceDeclarationList).hasSize(1)
assertThat(element.attributeList).hasSize(1)
assertThat(element.childList).hasSize(0)
val namespace = element.namespaceDeclarationList[0]
assertThat(namespace.uri).isEqualTo("http://schemas.android.com/aapt")
assertThat(namespace.prefix).isEqualTo("aapt")
val attr1 = element.attributeList[0]
assertThat(attr1.namespaceUri).isEqualTo("")
assertThat(attr1.name).isEqualTo("foo")
assertThat(attr1.value).isEqualTo("@${getAttrName(inputFile!!, 0)}")
}
@Test
fun failAttrRoot() {
val input = """
<aapt:attr xmlns:aapt="http://schemas.android.com/aapt" name="foo">
<child/>
</aapt:attr>
""".trimIndent()
assertThat(processTest(input)).isNull()
}
@Test
fun failConsecutiveNestedAttrDeclarations() {
val input ="""
<parent xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="foo">
<aapt:attr name="bar">
<child/>
</aapt:attr>
</aapt:attr>
</parent>
""".trimIndent()
assertThat(processTest(input)).isNull()
}
@Test
fun failToOverwriteExistingAttr() {
val input = """
<parent xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/hi">
<aapt:attr name="android:drawable">
<vector/>
</aapt:attr>
</parent>
""".trimIndent()
assertThat(processTest(input)).isNull()
}
@Test
fun failMultipleWritesToSameAttr() {
val input = """
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
<View2 android:text="hey">
<View3 android:id="hi" />
</View2>
</aapt:attr>
<aapt:attr name="android:text">
<View4 android:text="How's it going?">
<View5 android:id="How are you?" />
</View4>
</aapt:attr>
</View1>
""".trimIndent()
assertThat(processTest(input)).isNull()
}
@Test
fun failEmptyAttr() {
val input = """
<View1 xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:text">
</aapt:attr>
</View1>
""".trimIndent()
assertThat(processTest(input)).isNull()
}
}
@@ -0,0 +1,386 @@
package com.android.aaptcompiler
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.google.common.truth.Truth
import org.junit.Test
class XmlResourceBuilderTest {
private val fakeFile = ResourceFile(
parseNameOrFail("layout/foo"),
ConfigDescription(),
Source(""),
ResourceFile.Type.ProtoXml)
@Test
fun testBasicXmlFlattening() {
/*
* <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* android:orientation="vertical"/>
*/
val proto = XmlResourceBuilder(fakeFile)
.startElement("LinearLayout", "")
.addNamespaceDeclaration("http://schemas.android.com/apk/res/android", "android")
.addAttribute("layout_width", "http://schemas.android.com/apk/res/android", "match_parent")
.addAttribute("layout_height", "http://schemas.android.com/apk/res/android", "match_parent")
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
val root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("LinearLayout")
val nsDeclarations = root.getNamespaceDeclarationList()
Truth.assertThat(nsDeclarations).hasSize(1)
val androidNs = nsDeclarations[0]
Truth.assertThat(androidNs.getUri()).isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(androidNs.getPrefix()).isEqualTo("android")
val attributes = root.getAttributeList()
Truth.assertThat(attributes).hasSize(2)
val attribute1 = attributes[0]
Truth.assertThat(attribute1.getName()).isEqualTo("layout_width")
Truth.assertThat(attribute1.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(attribute1.getValue()).isEqualTo("match_parent")
val attribute2 = attributes[1]
Truth.assertThat(attribute2.getName()).isEqualTo("layout_height")
Truth.assertThat(attribute2.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(attribute2.getValue()).isEqualTo("match_parent")
Truth.assertThat(root.getChildList()).hasSize(0)
}
@Test
fun testNestedXmlFlattening() {
/*
* <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
* android:layout_width="match_parent"
* android:layout_height="match_parent"
* android:orientation="vertical">
* <TextView
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:text="@string/connect_request"/>
* <Button
* android:id="@+id/confirm"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:text="@string/confirm_button"/>
* <Button
* android:id="@+id/confirm"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:text="@string/cancel_button"/>
* </LinearLayout>
*/
val proto = XmlResourceBuilder(fakeFile)
.startElement("LinearLayout", "")
.addNamespaceDeclaration("http://schemas.android.com/apk/res/android", "android")
.addAttribute("layout_width", "http://schemas.android.com/apk/res/android", "match_parent")
.addAttribute("layout_height", "http://schemas.android.com/apk/res/android", "match_parent")
.startElement("TextView", "")
.addAttribute("layout_width", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute("layout_height", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute(
"text", "http://schemas.android.com/apk/res/android", "@string/connect_request")
.endElement()
.startElement("Button", "")
.addAttribute("id", "http://schemas.android.com/apk/res/android", "@+id/confirm")
.addAttribute("layout_width", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute("layout_height", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute("text", "http://schemas.android.com/apk/res/android", "@string/confirm_button")
.endElement()
.startElement("Button", "")
.addAttribute("id", "http://schemas.android.com/apk/res/android", "@+id/cancel")
.addAttribute("layout_width", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute("layout_height", "http://schemas.android.com/apk/res/android", "wrap_content")
.addAttribute("text", "http://schemas.android.com/apk/res/android", "@string/cancel_button")
.endElement()
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
val root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("LinearLayout")
val nsDeclarations = root.getNamespaceDeclarationList()
Truth.assertThat(nsDeclarations).hasSize(1)
val androidNs = nsDeclarations[0]
Truth.assertThat(androidNs.getUri()).isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(androidNs.getPrefix()).isEqualTo("android")
val attributes = root.getAttributeList()
Truth.assertThat(attributes).hasSize(2)
val attribute1 = attributes[0]
Truth.assertThat(attribute1.getName()).isEqualTo("layout_width")
Truth.assertThat(attribute1.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(attribute1.getValue()).isEqualTo("match_parent")
val attribute2 = attributes[1]
Truth.assertThat(attribute2.getName()).isEqualTo("layout_height")
Truth.assertThat(attribute2.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(attribute2.getValue()).isEqualTo("match_parent")
val childList = root.getChildList()
Truth.assertThat(childList).hasSize(3)
Truth.assertThat(childList[0].hasElement()).isTrue()
val child1 = childList[0].getElement()
Truth.assertThat(child1.getNamespaceUri()).isEmpty()
Truth.assertThat(child1.getName()).isEqualTo("TextView")
Truth.assertThat(child1.getNamespaceDeclarationList()).hasSize(0)
val child1Attributes = child1.getAttributeList()
Truth.assertThat(child1Attributes).hasSize(3)
val child1Attr1 = child1Attributes[0]
Truth.assertThat(child1Attr1.getName()).isEqualTo("layout_width")
Truth.assertThat(child1Attr1.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child1Attr1.getValue()).isEqualTo("wrap_content")
val child1Attr2 = child1Attributes[1]
Truth.assertThat(child1Attr2.getName()).isEqualTo("layout_height")
Truth.assertThat(child1Attr2.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child1Attr2.getValue()).isEqualTo("wrap_content")
val child1Attr3 = child1Attributes[2]
Truth.assertThat(child1Attr3.getName()).isEqualTo("text")
Truth.assertThat(child1Attr3.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child1Attr3.getValue()).isEqualTo("@string/connect_request")
Truth.assertThat(childList[1].hasElement()).isTrue()
val child2 = childList[1].getElement()
Truth.assertThat(child2.getNamespaceUri()).isEmpty()
Truth.assertThat(child2.getName()).isEqualTo("Button")
Truth.assertThat(child2.getNamespaceDeclarationList()).hasSize(0)
val child2Attributes = child2.getAttributeList()
Truth.assertThat(child2Attributes).hasSize(4)
val child2Attr1 = child2Attributes[0]
Truth.assertThat(child2Attr1.getName()).isEqualTo("id")
Truth.assertThat(child2Attr1.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child2Attr1.getValue()).isEqualTo("@+id/confirm")
val child2Attr2 = child2Attributes[1]
Truth.assertThat(child2Attr2.getName()).isEqualTo("layout_width")
Truth.assertThat(child2Attr2.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child2Attr2.getValue()).isEqualTo("wrap_content")
val child2Attr3 = child2Attributes[2]
Truth.assertThat(child2Attr3.getName()).isEqualTo("layout_height")
Truth.assertThat(child2Attr3.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child2Attr3.getValue()).isEqualTo("wrap_content")
val child2Attr4 = child2Attributes[3]
Truth.assertThat(child2Attr4.getName()).isEqualTo("text")
Truth.assertThat(child2Attr4.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child2Attr4.getValue()).isEqualTo("@string/confirm_button")
Truth.assertThat(childList[2].hasElement()).isTrue()
val child3 = childList[2].getElement()
Truth.assertThat(child3.getNamespaceUri()).isEmpty()
Truth.assertThat(child3.getName()).isEqualTo("Button")
Truth.assertThat(child3.getNamespaceDeclarationList()).hasSize(0)
val child3Attributes = child3.getAttributeList()
Truth.assertThat(child3Attributes).hasSize(4)
val child3Attr1 = child3Attributes[0]
Truth.assertThat(child3Attr1.getName()).isEqualTo("id")
Truth.assertThat(child3Attr1.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child3Attr1.getValue()).isEqualTo("@+id/cancel")
val child3Attr2 = child3Attributes[1]
Truth.assertThat(child3Attr2.getName()).isEqualTo("layout_width")
Truth.assertThat(child3Attr2.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child3Attr2.getValue()).isEqualTo("wrap_content")
val child3Attr3 = child3Attributes[2]
Truth.assertThat(child3Attr3.getName()).isEqualTo("layout_height")
Truth.assertThat(child3Attr3.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child3Attr3.getValue()).isEqualTo("wrap_content")
val child3Attr4 = child3Attributes[3]
Truth.assertThat(child3Attr4.getName()).isEqualTo("text")
Truth.assertThat(child3Attr4.getNamespaceUri())
.isEqualTo("http://schemas.android.com/apk/res/android")
Truth.assertThat(child3Attr4.getValue()).isEqualTo("@string/cancel_button")
}
@Test
fun testText() {
/*
* <test>text</test>
*/
var proto = XmlResourceBuilder(fakeFile)
.startElement("test", "")
.addText("text")
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
var root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("test")
Truth.assertThat(root.getNamespaceDeclarationList()).hasSize(0)
Truth.assertThat(root.getAttributeList()).hasSize(0)
Truth.assertThat(root.getChildList()).hasSize(1)
Truth.assertThat(root.getChildList()[0].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[0].getText()).isEqualTo("text")
// Consecutive text elements should be treated as a single element. XMLEventReader parses those
// as one event, even though they're divided by new lines.
/*
* <test>
* Text that is
* continued here.
* </test>
*/
proto = XmlResourceBuilder(fakeFile)
.startElement("test", "")
.addText("\nText that is\n continued here.")
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("test")
Truth.assertThat(root.getNamespaceDeclarationList()).hasSize(0)
Truth.assertThat(root.getAttributeList()).hasSize(0)
Truth.assertThat(root.getChildList()).hasSize(1)
Truth.assertThat(root.getChildList()[0].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[0].getText()).isEqualTo("\nText that is\n continued here.")
// Text elements should be considered separate if they have anything inbetween them.
/*
* <test>
* Text that is
* <!-- comment -->
* broken by comment.
* </test>
*/
proto = XmlResourceBuilder(fakeFile)
.startElement("test", "")
.addText("Text that is")
.addComment()
.addText("broken by comment.")
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("test")
Truth.assertThat(root.getNamespaceDeclarationList()).hasSize(0)
Truth.assertThat(root.getAttributeList()).hasSize(0)
Truth.assertThat(root.getChildList()).hasSize(2)
Truth.assertThat(root.getChildList()[0].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[0].getText()).isEqualTo("Text that is")
Truth.assertThat(root.getChildList()[1].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[1].getText()).isEqualTo("broken by comment.")
/*
* <test>
* Text that is
* <child/>
* broken by child.
* </test>
*/
proto = XmlResourceBuilder(fakeFile)
.startElement("test", "")
.addText("Text that is")
.startElement("child", "")
.endElement()
.addText("broken by child.")
.endElement()
.build()
.xmlProto
Truth.assertThat(proto.hasElement()).isTrue()
root = proto.getElement()
Truth.assertThat(root.getNamespaceUri()).isEmpty()
Truth.assertThat(root.getName()).isEqualTo("test")
Truth.assertThat(root.getNamespaceDeclarationList()).hasSize(0)
Truth.assertThat(root.getAttributeList()).hasSize(0)
Truth.assertThat(root.getChildList()).hasSize(3)
Truth.assertThat(root.getChildList()[0].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[0].getText()).isEqualTo("Text that is")
Truth.assertThat(root.getChildList()[1].hasElement()).isTrue()
Truth.assertThat(root.getChildList()[2].hasElement()).isFalse()
Truth.assertThat(root.getChildList()[2].getText()).isEqualTo("broken by child.")
}
@Test
fun testNamespaceContext() {
/*
<LinearLayout xmlns:android="androidOuter">
<ListView xmlns:android="androidInner" xmlns:aapt="aapt">
</ListView>
<ListView2 xmlns:android="androidInner2">
<View xmlns:android="androidInnerInner" xmlns:something="something/something"/>
</ListView2>
</LinearLayout>
*/
val builder = XmlResourceBuilder(fakeFile)
val nsContext = builder.namespaceContext
builder.startElement("LinearLayout", "")
.addNamespaceDeclaration("androidOuter", "android")
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidOuter")
builder.startElement("ListView", "")
builder.addNamespaceDeclaration("androidInner", "android")
builder.addNamespaceDeclaration("aapt", "aapt")
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android", "aapt"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidInner")
Truth.assertThat(nsContext.uriForPrefix("aapt")).isEqualTo("aapt")
builder.endElement()
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidOuter")
builder.startElement("ListView2", "")
.addNamespaceDeclaration("androidInner2", "android")
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidInner2")
builder.startElement("View", "")
.addNamespaceDeclaration("androidInnerInner", "android")
.addNamespaceDeclaration("something/something", "something")
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android", "something"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidInnerInner")
Truth.assertThat(nsContext.uriForPrefix("something")).isEqualTo("something/something")
builder.endElement()
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidInner2")
builder.endElement()
Truth.assertThat(nsContext.namespacePrefixes()).isEqualTo(setOf("android"))
Truth.assertThat(nsContext.uriForPrefix("android")).isEqualTo("androidOuter")
builder.endElement()
Truth.assertThat(builder.isFinished()).isTrue()
Truth.assertThat(nsContext.namespacePrefixes()).isEmpty()
}
}
@@ -0,0 +1,275 @@
package com.android.aaptcompiler
import com.google.common.truth.Truth
import org.junit.Test
class XmlStringBuilderTest {
@Test
fun testStringBuilderWhitespaceRemoval() {
// XmlStringBuilder is aware of removing spaces on the ends of the build string when the string
// has no spans. Thus we need to add spans to shift the xml string.
val stringBuilder = XmlStringBuilder()
var spanString = stringBuilder
.startSpan("hi")
.append(" hey guys ")
.append(" this is so cool ")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(spanString.styleString.str).isEqualTo(" hey guys this is so cool ")
stringBuilder.clear()
spanString = stringBuilder
.startSpan("hi")
.append(" \" wow, so many \t")
.append("spaces. \"what? ")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(spanString.styleString.str)
.isEqualTo(" wow, so many \tspaces. what? ")
stringBuilder.clear()
spanString = stringBuilder
.startSpan("hi")
.append(" where \t ")
.append(" \nis the pie?")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(spanString.styleString.str).isEqualTo(" where is the pie?")
// If the string has no spans then the spaces at the end are trimmed. (up to the first/last
// quotation).
stringBuilder.clear()
var basicString = stringBuilder
.append(" \tOh where")
.append(" is \t my hairbrush?\n \t")
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str).isEqualTo("Oh where is my hairbrush?")
stringBuilder.clear()
basicString = stringBuilder
.append(" \" \tHey there. \" ")
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str).isEqualTo(" \tHey there. ")
stringBuilder.clear()
basicString =
stringBuilder.append(""" before Quote " inside quote " after Quote """).getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo(" before Quote inside quote after Quote ")
stringBuilder.clear()
basicString =
stringBuilder.append("""a"b"c""").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo("abc")
stringBuilder.clear()
basicString =
stringBuilder.append("""a"bc""").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo("abc")
stringBuilder.clear()
basicString =
stringBuilder.append("""ab"c""").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo("abc")
stringBuilder.clear()
basicString =
stringBuilder.append("""""""").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo("")
}
@Test
fun testStringBuilderEscaping() {
val stringBuilder = XmlStringBuilder()
var basicString = stringBuilder
.append("hey guys\\n ")
.append(" this \\t is so\\\\ cool")
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str)
.isEqualTo("hey guys\n this \t is so\\ cool")
stringBuilder.clear()
basicString = stringBuilder
.append("\\@\\?\\#\\\\\\'")
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str).isEqualTo("@?#\\\'")
}
@Test
fun testStringBuilderMisplacedQuote() {
val stringBuilder = XmlStringBuilder()
stringBuilder.append("they're coming")
Truth.assertThat(stringBuilder.error).isNotEmpty()
}
@Test
fun testStringBuilderUnicodeCodes() {
val stringBuilder = XmlStringBuilder()
val basicString = stringBuilder.append("\\u00AF\\u0AF0 whoa").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str).isEqualTo("\u00AF\u0AF0 whoa")
stringBuilder.clear()
stringBuilder.append("\\u00 yo")
Truth.assertThat(stringBuilder.error).isNotEmpty()
}
@Test
fun testStringBuilderPreserveSpacesRecordsDoubleQuotes() {
val stringBuilder = XmlStringBuilder(true)
val basicString = stringBuilder.append("\"").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(basicString.styleString.str).isEqualTo("\"")
}
@Test
fun testUnfinishedSpansCauseError() {
val stringBuilder = XmlStringBuilder(false)
val spanAttempt = stringBuilder.startSpan("b").append("hi").getFlattenedXml()
Truth.assertThat(stringBuilder.error).isNotEmpty()
Truth.assertThat(spanAttempt.success).isFalse()
stringBuilder.clear()
val spanAttempt2 = stringBuilder
.startSpan("b")
.append("how ")
.startSpan("i")
.append("are you?")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isNotEmpty()
Truth.assertThat(spanAttempt2.success).isFalse()
}
@Test
fun testFinishingMissingSpanCausesError() {
val stringBuilder = XmlStringBuilder(false)
val spanAttempt = stringBuilder.append("hi").endSpan().getFlattenedXml()
Truth.assertThat(stringBuilder.error).isNotEmpty()
Truth.assertThat(spanAttempt.success).isFalse()
}
@Test
fun testSpanCreated() {
val stringBuilder = XmlStringBuilder(false)
val spanAttempt = stringBuilder
.append("Hello, ")
.startSpan("b")
.append("my name is Bob.")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(spanAttempt.styleString.str).isEqualTo("Hello, my name is Bob.")
val spans = spanAttempt.styleString.spans
Truth.assertThat(spans).hasSize(1)
Truth.assertThat(spans[0].name).isEqualTo("b")
Truth.assertThat(spans[0].firstChar).isEqualTo(7)
// spans are inclusive of their last index.
Truth.assertThat(spans[0].lastChar).isEqualTo(21)
stringBuilder.clear()
val spanAttempt2 = stringBuilder
.startSpan("i")
.append("I\\'m running out of things ")
.startSpan("b")
.append("to type!")
.endSpan()
.append("...")
.endSpan()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(spanAttempt2.styleString.str)
.isEqualTo("I'm running out of things to type!...")
val spans2 = spanAttempt2.styleString.spans
Truth.assertThat(spans2).hasSize(2)
val firstSpan = spans2[0]
val lastSpan = spans2[1]
Truth.assertThat(firstSpan.name).isEqualTo("i")
Truth.assertThat(firstSpan.firstChar).isEqualTo(0)
Truth.assertThat(firstSpan.lastChar).isEqualTo(36)
Truth.assertThat(lastSpan.name).isEqualTo("b")
Truth.assertThat(lastSpan.firstChar).isEqualTo(26)
Truth.assertThat(lastSpan.lastChar).isEqualTo(33)
}
@Test
fun testUntranslatableSectionsCannotBeNested() {
val stringBuilder = XmlStringBuilder(false)
val untranslatableAttempt = stringBuilder
.startUntranslatable()
.append("This ")
.startUntranslatable()
.append("is ")
.endUntranslatable()
.endUntranslatable()
.append("an ")
.startUntranslatable()
.append("attempt")
.endUntranslatable()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isNotEmpty()
Truth.assertThat(untranslatableAttempt.success).isFalse()
}
@Test
fun testUnfinishedUntranslatableSections() {
val stringBuilder = XmlStringBuilder(false)
val untranslatableAttempt = stringBuilder
.startUntranslatable()
.append("Well, how about this?")
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isNotEmpty()
Truth.assertThat(untranslatableAttempt.success).isFalse()
}
@Test
fun testUntranslatableSections() {
val stringBuilder = XmlStringBuilder(false)
val untranslatableAttempt = stringBuilder
.append("This ")
.startUntranslatable()
.append("is ")
.endUntranslatable()
.append("an ")
.startUntranslatable()
.append("attempt.")
.endUntranslatable()
.getFlattenedXml()
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(untranslatableAttempt.styleString.str).isEqualTo("This is an attempt.")
val untranslatables = untranslatableAttempt.untranslatableSections
Truth.assertThat(untranslatables).hasSize(2)
Truth.assertThat(untranslatables[0].startIndex).isEqualTo(5)
// untranslatable sections are exclusive of their last index.
Truth.assertThat(untranslatables[0].endIndex).isEqualTo(8)
Truth.assertThat(untranslatables[1].startIndex).isEqualTo(11)
Truth.assertThat(untranslatables[1].endIndex).isEqualTo(19)
}
@Test
fun testHandleEscapeQuotationsBetweenTextChunks() {
val stringBuilder = XmlStringBuilder(false)
val flattendSample = stringBuilder
.append("[[198745]] You\\")
.append("'ve blocked. Tap to report and share more details.")
Truth.assertThat(stringBuilder.error).isEmpty()
Truth.assertThat(flattendSample.getFlattenedXml().styleString.str)
.isEqualTo("[[198745]] You've blocked. Tap to report and share more details.")
}
}
@@ -0,0 +1,984 @@
package com.android.aaptcompiler.android
import com.google.common.truth.Truth
import org.junit.Test
import java.lang.AssertionError
class ResTable_configTest {
@Test
fun testBlockMethods() {
val config = ResTableConfig(
52.hostToDevice(),
// imsi block
0x01020304.hostToDevice(),
// locale block
0x05060708.hostToDevice(),
// screenType block
0x090a0b0c.hostToDevice(),
// input block
0x0d0e0f10.hostToDevice(),
// grammaticalGender block
1.hostToDevice(),
// screenSize block
0x11121314.hostToDevice(),
// version block
0x15161718.hostToDevice(),
// screenConfig block
0x191a1b1c.hostToDevice(),
// screenSizeDp block
0x1d1e1f20.hostToDevice(),
// localeScript block
byteArrayOf(0x21, 0x22, 0x23, 0x24),
// localeVariant block
byteArrayOf(0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c),
// screenConfig2 block
0x2d2e2f30.hostToDevice(),
// localeNumberSystem block
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38))
Truth.assertThat(config.size).isEqualTo(52)
// Blocks are Little Endian and therefore the lower bits come first in the block. In other words
// if the mcc is the "first" part of the imsi block it should be equal to the lower two bytes of
// the imsi block. As the "smaller" bytes come first in little endian format.
// imsi block
Truth.assertThat(config.mcc).isEqualTo(0x0304)
Truth.assertThat(config.mnc).isEqualTo(0x0102)
Truth.assertThat(config.getImsi()).isEqualTo(0x01020304.hostToDevice())
// locale block
Truth.assertThat(config.language.toList()).isEqualTo(byteArrayOf(0x07, 0x08).toList())
Truth.assertThat(config.country.toList()).isEqualTo(byteArrayOf(0x05, 0x06).toList())
Truth.assertThat(config.getLocale()).isEqualTo(0x05060708.hostToDevice())
// screenType block
Truth.assertThat(config.orientation).isEqualTo(0x0c)
Truth.assertThat(config.touchscreen).isEqualTo(0x0b)
Truth.assertThat(config.density).isEqualTo(0x090a)
Truth.assertThat(config.getScreenType()).isEqualTo(0x090a0b0c.hostToDevice())
// input block
Truth.assertThat(config.keyboard).isEqualTo(0x10)
Truth.assertThat(config.navigation).isEqualTo(0x0f)
Truth.assertThat(config.inputFlags).isEqualTo(0x0e)
// Padding is ignored, and thus removed.
Truth.assertThat(config.getInput()).isEqualTo(0x000e0f10.hostToDevice())
// screenSize block
Truth.assertThat(config.screenWidth).isEqualTo(0x1314)
Truth.assertThat(config.screenHeight).isEqualTo(0x1112)
Truth.assertThat(config.getScreenSize()).isEqualTo(0x11121314.hostToDevice())
// version block
Truth.assertThat(config.sdkVersion).isEqualTo(0x1718)
Truth.assertThat(config.minorVersion).isEqualTo(0x1516)
Truth.assertThat(config.getVersion()).isEqualTo(0x15161718.hostToDevice())
// screenConfig block
Truth.assertThat(config.screenLayout).isEqualTo(0x1c)
Truth.assertThat(config.uiMode).isEqualTo(0x1b)
Truth.assertThat(config.smallestScreenWidthDp).isEqualTo(0x191a)
Truth.assertThat(config.getScreenConfig()).isEqualTo(0x191a1b1c.hostToDevice())
// screenSizeDp block
Truth.assertThat(config.screenWidthDp).isEqualTo(0x1f20)
Truth.assertThat(config.screenHeightDp).isEqualTo(0x1d1e)
Truth.assertThat(config.getScreenSizeDp()).isEqualTo(0x1d1e1f20.hostToDevice())
// localeScript block
Truth.assertThat(config.localeScript.toList())
.isEqualTo(byteArrayOf(0x21, 0x22, 0x23, 0x24).toList())
// localeVariant block
Truth.assertThat(config.localeVariant.toList())
.isEqualTo(byteArrayOf(0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c).toList())
// screenConfig2 block
Truth.assertThat(config.screenLayout2).isEqualTo(0x30)
Truth.assertThat(config.colorMode).isEqualTo(0x2f)
// padding is ignored and thus removed.
Truth.assertThat(config.getScreenConfig2()).isEqualTo(0x00002f30.hostToDevice())
Truth.assertThat(config.localeScriptWasComputed).isFalse()
// localeNumberSystem block
Truth.assertThat(config.localeNumberSystem.toList())
.isEqualTo(byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38).toList())
}
private fun matchTestUnequal(
default: ResTableConfig, first: ResTableConfig, second: ResTableConfig) {
// Default should match everything.
Truth.assertThat(default.match(first)).isTrue()
Truth.assertThat(default.match(second)).isTrue()
// Nothing specific should match default.
Truth.assertThat(first.match(default)).isFalse()
Truth.assertThat(second.match(default)).isFalse()
// everything should match itself.
Truth.assertThat(first.match(first)).isTrue()
Truth.assertThat(second.match(second)).isTrue()
// first and second are different
Truth.assertThat(first.match(second)).isFalse()
Truth.assertThat(second.match(first)).isFalse()
}
private fun matchTestOneWay(
default: ResTableConfig, matches: ResTableConfig, noMatch: ResTableConfig) {
// Default should match everything.
Truth.assertThat(default.match(matches)).isTrue()
Truth.assertThat(default.match(noMatch)).isTrue()
// Nothing specific should match default.
Truth.assertThat(matches.match(default)).isFalse()
Truth.assertThat(noMatch.match(default)).isFalse()
// everything should match itself.
Truth.assertThat(matches.match(matches)).isTrue()
Truth.assertThat(noMatch.match(noMatch)).isTrue()
// matches should match noMatch
// but not the other way around
Truth.assertThat(matches.match(noMatch)).isTrue()
Truth.assertThat(noMatch.match(matches)).isFalse()
}
@Test
fun testMatches() {
val default = ResTableConfig()
val mcc1 = ResTableConfig(mcc = 310)
val mcc2 = ResTableConfig(mcc = 311)
matchTestUnequal(default, mcc1, mcc2)
val mnc1 = ResTableConfig(mnc = 1)
val mnc2 = ResTableConfig(mnc = 2)
matchTestUnequal(default, mnc1, mnc2)
val lang1 = ResTableConfig(language = ResTableConfig.ENGLISH)
val lang2 = ResTableConfig(language = ResTableConfig.FILIPINO)
matchTestUnequal(default, lang1, lang2)
val layoutDir1 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.DIR_LTR)
val layoutDir2 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.DIR_RTL)
matchTestUnequal(default, layoutDir1, layoutDir2)
val screenSize1 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_SMALL)
val screenSize2 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE)
// Smaller screens can match larger ones.
matchTestOneWay(default, screenSize1, screenSize2)
val screenLong1 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SCREENLONG_NO)
val screenLong2 = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SCREENLONG_YES)
matchTestUnequal(default, screenLong1, screenLong2)
// test that having multiple specified layout values doesn't interfere with matching.
val screenLayout = ResTableConfig(
screenLayout = (ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE.toInt() or
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_YES.toInt()).toByte())
Truth.assertThat(screenSize1.match(screenLayout)).isTrue()
Truth.assertThat(screenLayout.match(screenSize1)).isFalse()
Truth.assertThat(screenLong2.match(screenLayout)).isTrue()
Truth.assertThat(screenLayout.match(screenLong2)).isFalse()
val uiType1 = ResTableConfig(uiMode = ResTableConfig.UI_MODE.TYPE_VR_HEADSET)
val uiType2 = ResTableConfig(uiMode = ResTableConfig.UI_MODE.TYPE_APPLIANCE)
matchTestUnequal(default, uiType1, uiType2)
val uiNight1 = ResTableConfig(uiMode = ResTableConfig.UI_MODE.NIGHT_NO)
val uiNight2 = ResTableConfig(uiMode = ResTableConfig.UI_MODE.NIGHT_YES)
matchTestUnequal(default, uiNight1, uiNight2)
val uiMode = ResTableConfig(
uiMode = (ResTableConfig.UI_MODE.NIGHT_YES.toInt() or
ResTableConfig.UI_MODE.TYPE_APPLIANCE.toInt()).toByte())
// test that having multiple specified uiMode values doesn't interfere with matching.
Truth.assertThat(uiType2.match(uiMode)).isTrue()
Truth.assertThat(uiMode.match(uiType2)).isFalse()
Truth.assertThat(uiNight2.match(uiMode)).isTrue()
Truth.assertThat(uiMode.match(uiNight2)).isFalse()
val screenRound1 =
ResTableConfig(screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES)
val screenRound2 =
ResTableConfig(screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_NO)
matchTestUnequal(default, screenRound1, screenRound2)
val hdr1 = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.HDR_YES)
val hdr2 = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.HDR_NO)
matchTestUnequal(default, hdr1, hdr2)
val wideGamut1 = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES)
val wideGamut2 = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.WIDE_GAMUT_NO)
matchTestUnequal(default, wideGamut1, wideGamut2)
val colorMode = ResTableConfig(
colorMode = (ResTableConfig.COLOR_MODE.HDR_NO.toInt() or
ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES.toInt()).toByte())
// test that having multiple specified colorMode values doesn't interfere with matching.
Truth.assertThat(hdr2.match(colorMode)).isTrue()
Truth.assertThat(colorMode.match(hdr2)).isFalse()
Truth.assertThat(wideGamut1.match(colorMode)).isTrue()
Truth.assertThat(colorMode.match(wideGamut1)).isFalse()
val screenWidthDp1 = ResTableConfig(screenWidthDp = 480)
val screenWidthDp2 = ResTableConfig(screenWidthDp = 960)
// smaller widths match larger widths
matchTestOneWay(default, screenWidthDp1, screenWidthDp2)
val screenHeightDp1 = ResTableConfig(screenHeightDp = 640)
val screenHeightDp2 = ResTableConfig(screenHeightDp = 1280)
// small heights match larger heights
matchTestOneWay(default, screenHeightDp1, screenHeightDp2)
val orientation1 = ResTableConfig(orientation = ResTableConfig.ORIENTATION.LAND)
val orientation2 = ResTableConfig(orientation = ResTableConfig.ORIENTATION.PORT)
matchTestUnequal(default, orientation1, orientation2)
val touchScreen1 = ResTableConfig(touchscreen = ResTableConfig.TOUCHSCREEN.FINGER)
val touchScreen2 = ResTableConfig(touchscreen = ResTableConfig.TOUCHSCREEN.NOTOUCH)
matchTestUnequal(default, touchScreen1, touchScreen2)
val navHidden1 = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.NAVHIDDEN_YES)
val navHidden2 = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.NAVHIDDEN_NO)
matchTestUnequal(default, navHidden1, navHidden2)
val keysHidden1 = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_YES)
val keysHidden2 = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_SOFT)
matchTestUnequal(default, keysHidden1, keysHidden2)
val keysHidden3 = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_NO)
// KEYSHIDDEN NO should match KEYSHIDDEN SOFT, but not the other way around.
Truth.assertThat(keysHidden2.match(keysHidden3)).isFalse()
Truth.assertThat(keysHidden3.match(keysHidden2)).isTrue()
val keyboard1 = ResTableConfig(keyboard = ResTableConfig.KEYBOARD.QWERTY)
val keyboard2 = ResTableConfig(keyboard = ResTableConfig.KEYBOARD.TWELVEKEY)
matchTestUnequal(default, keyboard1, keyboard2)
val navigation1 = ResTableConfig(navigation = ResTableConfig.NAVIGATION.DPAD)
val navigation2 = ResTableConfig(navigation = ResTableConfig.NAVIGATION.TRACKBALL)
matchTestUnequal(default, navigation1, navigation2)
val screenWidth1 = ResTableConfig(screenWidth = 480)
val screenWidth2 = ResTableConfig(screenWidth = 960)
// Smaller widths will match.
matchTestOneWay(default, screenWidth1, screenWidth2)
val screenHeight1 = ResTableConfig(screenHeight = 360)
val screenHeight2 = ResTableConfig(screenHeight = 720)
// Smaller heights will match.
matchTestOneWay(default, screenHeight1, screenHeight2)
val sdkVersion1 = ResTableConfig(sdkVersion = 16)
val sdkVersion2 = ResTableConfig(sdkVersion = 20)
// Lower sdk versions will match.
matchTestOneWay(default, sdkVersion1, sdkVersion2)
val minorVersion1 = ResTableConfig(minorVersion = 1)
val minorVersion2 = ResTableConfig(minorVersion = 2)
matchTestUnequal(default, minorVersion1, minorVersion2)
}
private fun testLocaleSpecificityGreater(greater: ResTableConfig, lesser: ResTableConfig) {
Truth.assertThat(greater.isLocaleMoreSpecificThan(lesser)).isGreaterThan(0)
Truth.assertThat(lesser.isLocaleMoreSpecificThan(greater)).isLessThan(0)
}
private fun testLocaleSpecificityEqual(first: ResTableConfig, second: ResTableConfig) {
Truth.assertThat(first.isLocaleMoreSpecificThan(second)).isEqualTo(0)
// if they are the 'exact' same, there is no reason to retest.
if (first !== second) {
Truth.assertThat(second.isLocaleMoreSpecificThan(first)).isEqualTo(0)
}
}
@Test
fun testLocaleIsMoreSpecificThan() {
val default = ResTableConfig()
val languageOnly = ResTableConfig(language = byteArrayOf('e'.toByte(), 'n'.toByte()))
val countryOnly = ResTableConfig(country = byteArrayOf('U'.toByte(), 'S'.toByte()))
val languageAndCountry = ResTableConfig(
language = byteArrayOf('e'.toByte(), 'n'.toByte()),
country = byteArrayOf('U'.toByte(), 'S'.toByte()))
val localeVariant = ResTableConfig(
localeVariant = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08))
val scriptComputed = ResTableConfig(
localeScriptWasComputed = true, localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04))
val localeScript = ResTableConfig(
localeScript = byteArrayOf(0x01, 0x02, 0x03, 0x04))
val numberingSystem = ResTableConfig(
localeNumberSystem = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08))
// configs are consistent with themselves.
testLocaleSpecificityEqual(default, default)
testLocaleSpecificityEqual(languageOnly, languageOnly)
testLocaleSpecificityEqual(countryOnly, countryOnly)
testLocaleSpecificityEqual(languageAndCountry, languageAndCountry)
testLocaleSpecificityEqual(localeVariant, localeVariant)
testLocaleSpecificityEqual(scriptComputed, scriptComputed)
testLocaleSpecificityEqual(localeScript, localeScript)
testLocaleSpecificityEqual(numberingSystem, numberingSystem)
// Everything is more specific than default.
testLocaleSpecificityGreater(languageOnly, default)
testLocaleSpecificityGreater(countryOnly, default)
testLocaleSpecificityGreater(languageAndCountry, default)
testLocaleSpecificityGreater(localeVariant, default)
testLocaleSpecificityGreater(localeScript, default)
testLocaleSpecificityGreater(numberingSystem, default)
// If script is computed and that is the only difference, it is the same as default.
testLocaleSpecificityEqual(scriptComputed, default)
// Language is considered before anything else.
testLocaleSpecificityGreater(languageOnly, countryOnly)
testLocaleSpecificityGreater(languageAndCountry, countryOnly)
testLocaleSpecificityGreater(languageOnly, localeVariant)
testLocaleSpecificityGreater(languageAndCountry, localeVariant)
testLocaleSpecificityGreater(languageOnly, scriptComputed)
testLocaleSpecificityGreater(languageAndCountry, scriptComputed)
testLocaleSpecificityGreater(languageOnly, localeScript)
testLocaleSpecificityGreater(languageAndCountry, localeScript)
testLocaleSpecificityGreater(languageOnly, numberingSystem)
testLocaleSpecificityGreater(languageAndCountry, numberingSystem)
// Country is next most important.
testLocaleSpecificityGreater(languageAndCountry, languageOnly)
testLocaleSpecificityGreater(countryOnly, localeVariant)
testLocaleSpecificityGreater(countryOnly, scriptComputed)
testLocaleSpecificityGreater(countryOnly, localeScript)
testLocaleSpecificityGreater(countryOnly, numberingSystem)
// localeVariant is next most
testLocaleSpecificityGreater(localeVariant, scriptComputed)
testLocaleSpecificityGreater(localeVariant, localeScript)
testLocaleSpecificityGreater(localeVariant, numberingSystem)
// localeScript is next most
testLocaleSpecificityGreater(localeScript, scriptComputed)
testLocaleSpecificityGreater(localeScript, numberingSystem)
// localeScript being computed is not considered. Therefore numbering system is next on the list
testLocaleSpecificityGreater(numberingSystem, scriptComputed)
// finally test that unequal but matching scripts do not have higher specificity.
val equalConfig1 = ResTableConfig(language = ResTableConfig.FILIPINO)
val equalConfig2 = ResTableConfig(language = ResTableConfig.TAGALOG)
testLocaleSpecificityEqual(equalConfig1, equalConfig2)
}
private fun testSpecificityTransitive(vararg greatestToLeast: ResTableConfig) {
for (config in greatestToLeast) {
// Configs are not more specific than themselves
Truth.assertThat(config.isMoreSpecificThan(config)).isFalse()
}
for (i in 0.until(greatestToLeast.size)) {
for (j in (i+1).until(greatestToLeast.size)) {
val greater = greatestToLeast[i]
val lesser = greatestToLeast[j]
try {
Truth.assertThat(greater.isMoreSpecificThan(lesser)).isTrue()
Truth.assertThat(lesser.isMoreSpecificThan(greater)).isFalse()
} catch (e: AssertionError) {
throw AssertionError(
"Failed with greater = \"$greater\" at index $i and lesser = \"$lesser\" at index $j.",
e)
}
}
}
}
@Test
fun testIsMoreSpecificThan() {
val mcc = ResTableConfig(mcc = 310)
val mnc = ResTableConfig(mnc = 1)
val locale = ResTableConfig(language = ResTableConfig.ENGLISH)
val layoutDir = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.DIR_LTR)
val smallestScreenWidthDp = ResTableConfig(smallestScreenWidthDp = 640)
val screenWidthDp = ResTableConfig(screenWidthDp = 640)
val screenHeightDp = ResTableConfig(screenHeightDp = 480)
val screenRound =
ResTableConfig(screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES)
val hdr = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.HDR_YES)
val wideGamut = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.WIDE_GAMUT_NO)
val orientation = ResTableConfig(orientation = ResTableConfig.ORIENTATION.LAND)
val uiModeType = ResTableConfig(uiMode = ResTableConfig.UI_MODE.TYPE_VR_HEADSET)
val uiModeNight = ResTableConfig(uiMode = ResTableConfig.UI_MODE.NIGHT_YES)
val touchScreen = ResTableConfig(touchscreen = ResTableConfig.TOUCHSCREEN.FINGER)
val keysHidden = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_YES)
val navHidden = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.NAVHIDDEN_YES)
val keyboard = ResTableConfig(keyboard = ResTableConfig.KEYBOARD.QWERTY)
val navigation = ResTableConfig(navigation = ResTableConfig.NAVIGATION.DPAD)
val screenWidth = ResTableConfig(screenWidth = 480)
val screenHeight = ResTableConfig(screenHeight = 640)
val sdkVersion = ResTableConfig(sdkVersion = 16)
val minorVersion = ResTableConfig(minorVersion = 2)
val default = ResTableConfig()
// Test specificity by priority.
testSpecificityTransitive(
mcc,
mnc,
locale,
layoutDir,
smallestScreenWidthDp,
screenWidthDp,
screenHeightDp,
screenRound,
hdr,
wideGamut,
orientation,
uiModeType,
uiModeNight,
touchScreen,
keysHidden,
navHidden,
keyboard,
navigation,
screenWidth,
screenHeight,
sdkVersion,
minorVersion,
default)
}
private fun testLocaleCompareTransitive(vararg firstToLast: ResTableConfig) {
for (config in firstToLast) {
// Things should be equal to themselves.
Truth.assertThat(config.compareLocales(config)).isEqualTo(0)
}
for (i in 0.until(firstToLast.size)) {
for (j in (i+1).until(firstToLast.size)) {
val less = firstToLast[i]
val more = firstToLast[j]
try {
Truth.assertThat(less.compareLocales(more)).isLessThan(0)
Truth.assertThat(more.compareLocales(less)).isGreaterThan(0)
} catch (e: AssertionError) {
throw AssertionError(
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e)
}
}
}
}
@Test
fun testLocaleCompare() {
val default = ResTableConfig()
val defaultWithVariant = ResTableConfig(localeVariant = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
val localeNoCountry1 = ResTableConfig(language = ResTableConfig.ENGLISH)
val localeNoCountryWithScript1 = ResTableConfig(
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 4))
val localeNoCountryWithScript2 = ResTableConfig(
language = ResTableConfig.ENGLISH, localeScript = byteArrayOf(1, 2, 3, 5))
val localeNoCountry2 = ResTableConfig(language = byteArrayOf('f'.toByte(), 'r'.toByte()))
val localeNoCountryWithScript3 = ResTableConfig(
language = byteArrayOf('f'.toByte(), 'r'.toByte()), localeScript = byteArrayOf(1, 2, 3, 4))
val localeNoLang = ResTableConfig(country = ResTableConfig.UNITED_STATES)
val localeBasic = ResTableConfig(
language = ResTableConfig.ENGLISH, country = ResTableConfig.UNITED_STATES)
val localeBasicWithScript = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4))
val localeBasicWithScriptAndVariant = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
val localeBasicWithScriptAndVariant2 = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
val localeBasicWithScriptVariantAndNumberSystem = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18),
localeNumberSystem = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8))
val localeBasicWithScriptVariantAndNumberSystem2 = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf(1, 2, 3, 4),
localeVariant = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
testLocaleCompareTransitive(
default,
defaultWithVariant,
localeNoCountry1,
localeNoCountryWithScript1,
localeNoCountryWithScript2,
localeNoCountry2,
localeNoCountryWithScript3,
localeNoLang,
localeBasic,
localeBasicWithScript,
localeBasicWithScriptAndVariant,
localeBasicWithScriptAndVariant2,
localeBasicWithScriptVariantAndNumberSystem,
localeBasicWithScriptVariantAndNumberSystem2)
// Ensure computed scripts do not affect results.
val localeBasicWithComputedScript = ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScriptWasComputed = true,
localeScript = byteArrayOf(1, 2, 3, 4))
Truth.assertThat(localeBasic.compareLocales(localeBasicWithComputedScript)).isEqualTo(0)
}
private fun testCompareTransitive(vararg firstToLast: ResTableConfig) {
for (config in firstToLast) {
// Things should be equal to themselves.
Truth.assertThat(config.compareTo(config)).isEqualTo(0)
}
for (i in 0.until(firstToLast.size)) {
for (j in (i+1).until(firstToLast.size)) {
val less = firstToLast[i]
val more = firstToLast[j]
try {
Truth.assertThat(less.compareTo(more)).isLessThan(0)
Truth.assertThat(more.compareTo(less)).isGreaterThan(0)
} catch (e: AssertionError) {
throw AssertionError(
"Failed with less = \"$less\" at index $i and more = \"$more\" at index $j.", e)
}
}
}
}
@Test
fun testCompare() {
val default = ResTableConfig()
val screenSizeDp = ResTableConfig(screenWidthDp = 240, screenHeightDp = 320)
val screenSizeDp2 = ResTableConfig(screenWidthDp = 480, screenHeightDp = 640)
val smallestScreenWidthDp = ResTableConfig(smallestScreenWidthDp = 480)
val smallestScreenWidthDp2 = ResTableConfig(smallestScreenWidthDp = 960)
val uiMode = ResTableConfig(uiMode = ResTableConfig.UI_MODE.NIGHT_YES)
val colorMode = ResTableConfig(colorMode = ResTableConfig.COLOR_MODE.WIDE_GAMUT_NO)
val screenLayout2 = ResTableConfig(
screenLayout2 = ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES)
val screenLayout = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE)
val version = ResTableConfig(sdkVersion = 22)
val screenSize = ResTableConfig(screenWidth = 480, screenHeight = 320)
val input = ResTableConfig(inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_YES)
val screenType = ResTableConfig(orientation = ResTableConfig.ORIENTATION.PORT)
// Ensure density matters for ordering.
val screenType2 = ResTableConfig(density = 240)
val locale = ResTableConfig(language = ResTableConfig.ENGLISH)
val imsi = ResTableConfig(mcc = 310)
testCompareTransitive(
default,
screenSizeDp,
screenSizeDp2,
smallestScreenWidthDp,
smallestScreenWidthDp2,
uiMode,
colorMode,
screenLayout2,
screenLayout,
version,
screenSize,
input,
screenType,
screenType2,
locale,
imsi)
}
@Test
fun testDiff() {
val default = ResTableConfig()
val first = ResTableConfig(mcc = 310, mnc = 10, screenWidth = 480, screenHeight = 640)
val second = ResTableConfig(mcc = 310, language = ResTableConfig.ENGLISH, screenHeight = 480)
val third = ResTableConfig(
sdkVersion = 23,
screenWidth = 480,
screenHeight = 640,
screenLayout = ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE)
Truth.assertThat(default.diff(default)).isEqualTo(0)
Truth.assertThat(first.diff(first)).isEqualTo(0)
Truth.assertThat(second.diff(second)).isEqualTo(0)
Truth.assertThat(third.diff(third)).isEqualTo(0)
val firstDefault =
ResTableConfig.CONFIG_MCC or ResTableConfig.CONFIG_MNC or ResTableConfig.CONFIG_SCREEN_SIZE
Truth.assertThat(first.diff(default)).isEqualTo(firstDefault)
Truth.assertThat(default.diff(first)).isEqualTo(firstDefault)
val secondDefault =
ResTableConfig.CONFIG_MCC or
ResTableConfig.CONFIG_LOCALE or
ResTableConfig.CONFIG_SCREEN_SIZE
Truth.assertThat(second.diff(default)).isEqualTo(secondDefault)
Truth.assertThat(default.diff(second)).isEqualTo(secondDefault)
val thirdDefault =
ResTableConfig.CONFIG_VERSION or
ResTableConfig.CONFIG_SCREEN_SIZE or
ResTableConfig.CONFIG_SCREEN_LAYOUT
Truth.assertThat(third.diff(default)).isEqualTo(thirdDefault)
Truth.assertThat(default.diff(third)).isEqualTo(thirdDefault)
val firstSecond =
ResTableConfig.CONFIG_MNC or
ResTableConfig.CONFIG_LOCALE or
ResTableConfig.CONFIG_SCREEN_SIZE
Truth.assertThat(first.diff(second)).isEqualTo(firstSecond)
Truth.assertThat(second.diff(first)).isEqualTo(firstSecond)
val firstThird =
ResTableConfig.CONFIG_MCC or
ResTableConfig.CONFIG_MNC or
ResTableConfig.CONFIG_VERSION or
ResTableConfig.CONFIG_SCREEN_LAYOUT
Truth.assertThat(first.diff(third)).isEqualTo(firstThird)
Truth.assertThat(third.diff(first)).isEqualTo(firstThird)
val secondThird =
ResTableConfig.CONFIG_MCC or
ResTableConfig.CONFIG_LOCALE or
ResTableConfig.CONFIG_SCREEN_SIZE or
ResTableConfig.CONFIG_VERSION or
ResTableConfig.CONFIG_SCREEN_LAYOUT
Truth.assertThat(second.diff(third)).isEqualTo(secondThird)
Truth.assertThat(third.diff(second)).isEqualTo(secondThird)
// layout dir is separate from ResTableConfig.CONFIG_SCREEN_LAYOUT
val layoutDir = ResTableConfig(screenLayout = ResTableConfig.SCREEN_LAYOUT.DIR_LTR)
val diff = ResTableConfig.CONFIG_LAYOUTDIR
Truth.assertThat(default.diff(layoutDir)).isEqualTo(diff)
Truth.assertThat(layoutDir.diff(default)).isEqualTo(diff)
}
private fun isBetterLocaleTransitive(
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig) {
for (config in leastToGreatest) {
// no config is better than itself.
Truth.assertThat(config.isLocaleBetterThan(config, requested)).isFalse()
}
for (i in 0.until(leastToGreatest.size)) {
for (j in (i+1).until(leastToGreatest.size)) {
val worse = leastToGreatest[i]
val better = leastToGreatest[j]
try {
Truth.assertThat(worse.isLocaleBetterThan(better, requested)).isFalse()
Truth.assertThat(better.isLocaleBetterThan(worse, requested)).isTrue()
} catch (e: AssertionError) {
throw AssertionError(
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e)
}
}
}
}
@Test
fun testIsBetterLocale() {
val requested = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
val default = ResTableConfig()
val country = ResTableConfig(country = byteArrayOf(1, 2))
val language1 = ResTableConfig(language = ResTableConfig.TAGALOG)
val language2 = ResTableConfig(language = ResTableConfig.FILIPINO)
val languageAndCountry =
ResTableConfig(language = ResTableConfig.FILIPINO, country = byteArrayOf(1, 2))
val localeNumberSystem = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
val localeVariant = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10))
val almostTotalMatch = ResTableConfig(
language = ResTableConfig.TAGALOG,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 10),
localeNumberSystem = byteArrayOf(11, 12, 13, 14, 15, 16, 17, 18))
isBetterLocaleTransitive(
requested,
default,
country,
language1,
language2,
languageAndCountry,
localeNumberSystem,
localeVariant,
almostTotalMatch,
requested)
// Test that if the locale variant doesn't match it doesn't count.
val localeVariantFail = ResTableConfig(
language = ResTableConfig.FILIPINO,
country = byteArrayOf(1, 2),
localeVariant = byteArrayOf(3, 4, 5, 6, 7, 8, 9, 11))
Truth.assertThat(localeVariantFail.isLocaleBetterThan(localeNumberSystem, requested)).isFalse()
Truth.assertThat(localeNumberSystem.isLocaleBetterThan(localeVariantFail, requested)).isTrue()
Truth.assertThat(localeVariantFail.isLocaleBetterThan(languageAndCountry, requested)).isFalse()
}
private fun isBetterTransitive(
requested: ResTableConfig, vararg leastToGreatest: ResTableConfig) {
for (config in leastToGreatest) {
// no config is better than itself.
Truth.assertThat(config.isBetterThan(config, requested)).isFalse()
}
for (i in 0.until(leastToGreatest.size)) {
for (j in (i+1).until(leastToGreatest.size)) {
val worse = leastToGreatest[i]
val better = leastToGreatest[j]
try {
Truth.assertThat(worse.isBetterThan(better, requested)).isFalse()
Truth.assertThat(better.isBetterThan(worse, requested)).isTrue()
} catch (e: AssertionError) {
throw AssertionError(
"Failed with worse = \"$worse\" at index $i and better = \"$better\" at index $j.", e)
}
}
}
}
@Test
fun testIsBetterThan() {
val requested = ResTableConfig(
52.hostToDevice(),
// imsi block
0x41424344.hostToDevice(),
// locale block
0x01020304.hostToDevice(),
// screenType block
0x494a4b4c.hostToDevice(),
// input block
0x4d4e4f10.hostToDevice(),
// grammaticalGender block
1.hostToDevice(),
// screenSize block
0x11121314.hostToDevice(),
// version block
0x15161718.hostToDevice(),
// screenConfig block
0x191affff.hostToDevice(),
// screenSizeDp block
0x1d1e1f20.hostToDevice(),
// localeScript block
byteArrayOf(0x21, 0x22, 0x23, 0x24),
// localeVariant block
byteArrayOf(0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c),
// screenConfig2 block
0xffffffff.toInt().hostToDevice(),
// localeNumberSystem block
byteArrayOf(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38))
val default = ResTableConfig()
val minorVersion = ResTableConfig(minorVersion = requested.minorVersion)
val sdkVersion1 = ResTableConfig(sdkVersion = 0x0020)
// higher sdkVersion will match better.
val sdkVersion2 = ResTableConfig(sdkVersion = requested.sdkVersion)
val screenSize1 = ResTableConfig(screenWidth = requested.screenWidth)
val screenSize2 = ResTableConfig(
screenWidth = requested.screenWidth, screenHeight = requested.screenHeight)
val navigation = ResTableConfig(navigation = requested.navigation)
val keyboard = ResTableConfig(keyboard = requested.keyboard)
val navHidden = ResTableConfig(
inputFlags = (requested.inputFlags.toInt() and
ResTableConfig.INPUT_FLAGS.NAVHIDDEN_MASK).toByte())
val keysHiddenAlmostMatch = ResTableConfig(
inputFlags = ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_NO)
val keysHiddenMatch = ResTableConfig(
inputFlags = (requested.inputFlags.toInt() and
ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_MASK).toByte())
val touchscreen = ResTableConfig(touchscreen = requested.touchscreen)
// scaling up is worse than scaling down.
val densityLow = ResTableConfig(density = requested.density - 20)
val densityHigh = ResTableConfig(density = requested.density + 20)
val densityEqual = ResTableConfig(density = requested.density)
val nightMode = ResTableConfig(
uiMode = (requested.uiMode.toInt() and
ResTableConfig.UI_MODE.NIGHT_MASK).toByte())
val uiType = ResTableConfig(
uiMode = (requested.uiMode.toInt() and
ResTableConfig.UI_MODE.TYPE_MASK).toByte())
val orientation = ResTableConfig(orientation = requested.orientation)
val hdr = ResTableConfig(
colorMode = (requested.colorMode.toInt() and
ResTableConfig.COLOR_MODE.HDR_MASK).toByte())
val gamut = ResTableConfig(
colorMode = (requested.colorMode.toInt() and
ResTableConfig.COLOR_MODE.WIDE_GAMUT_MASK).toByte())
val round = ResTableConfig(
screenLayout2 = (requested.screenLayout2.toInt() and
ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_MASK).toByte())
val long = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_MASK).toByte())
val sizeFlag = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.SIZE_MASK).toByte())
val screenSizeDp1 = ResTableConfig(screenWidthDp = requested.screenWidthDp)
val screenSizeDp2 = ResTableConfig(
screenWidthDp = requested.screenWidthDp, screenHeightDp = requested.screenHeightDp)
val smallestScreenWidth1 = ResTableConfig(
smallestScreenWidthDp = requested.smallestScreenWidthDp - 20)
// Closer to requested will match better.
val smallestScreenWidth2 = ResTableConfig(
smallestScreenWidthDp = requested.smallestScreenWidthDp)
val screenDir = ResTableConfig(
screenLayout = (requested.screenLayout.toInt() and
ResTableConfig.SCREEN_LAYOUT.DIR_MASK).toByte())
val mnc = ResTableConfig(mnc = requested.mnc)
val mcc = ResTableConfig(mcc = requested.mcc)
isBetterTransitive(
requested,
default,
minorVersion,
sdkVersion1,
sdkVersion2,
screenSize1,
screenSize2,
navigation,
keyboard,
navHidden,
keysHiddenAlmostMatch,
keysHiddenMatch,
touchscreen,
densityLow,
densityHigh,
densityEqual,
nightMode,
uiType,
orientation,
hdr,
gamut,
round,
long,
sizeFlag,
screenSizeDp1,
screenSizeDp2,
smallestScreenWidth1,
smallestScreenWidth2,
screenDir,
mnc,
mcc,
requested)
}
@Test
fun testToString() {
val default = ResTableConfig()
Truth.assertThat(default.toString()).isEqualTo("DEFAULT")
val config1 = ResTableConfig(mcc = 120)
Truth.assertThat(config1.toString()).isEqualTo("mcc120")
val config2 = ResTableConfig(mnc = 120)
Truth.assertThat(config2.toString()).isEqualTo("mnc120")
val config3 = ResTableConfig(language = ResTableConfig.ENGLISH)
Truth.assertThat(config3.toString()).isEqualTo("en")
val config4 =
ResTableConfig(language = ResTableConfig.ENGLISH, country = ResTableConfig.UNITED_STATES)
Truth.assertThat(config4.toString()).isEqualTo("en-rUS")
val config5 =
ResTableConfig(
language = ResTableConfig.ENGLISH,
localeScript = byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()))
Truth.assertThat(config5.toString()).isEqualTo("b+en+latn")
val config6 =
ResTableConfig(
language = ResTableConfig.ENGLISH,
country = ResTableConfig.UNITED_STATES,
localeScript = byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()),
localeVariant = byteArrayOf('h'.toByte(), 'i'.toByte(), 0, 0, 0, 0, 0, 0),
localeNumberSystem = byteArrayOf(
't'.toByte(), 'h'.toByte(), 'e'.toByte(), 'r'.toByte(), 'e'.toByte(), 0, 0, 0))
Truth.assertThat(config6.toString()).isEqualTo("b+en+latn+US+hi+u+nu+there")
val config7 = ResTableConfig(screenWidth = 480, screenHeight = 640)
Truth.assertThat(config7.toString()).isEqualTo("480x640")
val config8 = ResTableConfig(screenWidthDp = 480, screenHeightDp = 640)
Truth.assertThat(config8.toString()).isEqualTo("w480dp-h640dp")
val config9 =
ResTableConfig(
52,
310,
100,
ResTableConfig.ENGLISH,
ResTableConfig.UNITED_STATES,
ResTableConfig.ORIENTATION.LAND,
ResTableConfig.TOUCHSCREEN.FINGER,
ResTableConfig.DENSITY.XXHIGH,
ResTableConfig.KEYBOARD.QWERTY,
ResTableConfig.NAVIGATION.DPAD,
(ResTableConfig.INPUT_FLAGS.NAVHIDDEN_YES.toInt() or
ResTableConfig.INPUT_FLAGS.KEYSHIDDEN_SOFT.toInt()).toByte(),
ResTableConfig.GRAMMATICAL_GENDER.FEMININE,
0,
0,
13,
2,
(ResTableConfig.SCREEN_LAYOUT.DIR_LTR.toInt() or
ResTableConfig.SCREEN_LAYOUT.SIZE_LARGE.toInt() or
ResTableConfig.SCREEN_LAYOUT.SCREENLONG_YES.toInt()).toByte(),
(ResTableConfig.UI_MODE.NIGHT_NO.toInt() or
ResTableConfig.UI_MODE.TYPE_DESK.toInt()).toByte(),
480,
480,
640,
byteArrayOf('l'.toByte(), 'a'.toByte(), 't'.toByte(), 'n'.toByte()),
byteArrayOf('h'.toByte(), 'i'.toByte(), 0, 0, 0, 0, 0, 0),
ResTableConfig.SCREEN_LAYOUT2.SCREENROUND_YES,
(ResTableConfig.COLOR_MODE.HDR_YES.toInt() or
ResTableConfig.COLOR_MODE.WIDE_GAMUT_YES.toInt()).toByte(),
false,
byteArrayOf('t'.toByte(), 'h'.toByte(), 'e'.toByte(), 'r'.toByte(), 'e'.toByte(), 0, 0, 0))
Truth.assertThat(config9.toString()).isEqualTo(
"mcc310-mnc100-b+en+latn+US+hi+u+nu+there-feminine-ldltr-sw480dp-w480dp-h640dp-large-" +
"long-round-widecg-highdr-land-desk-notnight-xxhdpi-finger-keyssoft-qwerty-navhidden-dpad-" +
"v13.2")
}
}
@@ -0,0 +1,272 @@
package com.android.aaptcompiler.android
import com.google.common.truth.Truth
import org.junit.Test
class ResourceTypesTest {
private fun testStringToIntCase(input: String, expectedValue: Int, expectedHex: Boolean = false) {
val result = stringToInt(input)
val expectedOutput =
ResValue(
if(expectedHex) ResValue.DataType.INT_HEX else ResValue.DataType.INT_DEC,
expectedValue)
Truth.assertThat(result).isEqualTo(expectedOutput)
}
private fun testStringToIntFailure(input: String) {
val result = stringToInt(input)
Truth.assertThat(result).isNull()
}
@Test
fun testStringToInt() {
testStringToIntFailure("")
testStringToIntFailure(" ")
testStringToIntFailure("\t\n")
testStringToIntFailure("abcd")
testStringToIntFailure("10abcd")
testStringToIntFailure("42 42")
testStringToIntFailure("- 42")
testStringToIntFailure("-")
testStringToIntFailure("0x")
testStringToIntFailure("0xnope")
testStringToIntFailure("0X42")
testStringToIntFailure("0x42 0x42")
testStringToIntFailure("-0x0")
testStringToIntFailure("-0x42")
testStringToIntFailure("- 0x42")
// Note that u" 42" would pass. This preserves the old behavior, but it may not be desired.
testStringToIntFailure("42 ")
testStringToIntFailure("0x42 ")
// Decimal cases.
testStringToIntCase("0", 0)
testStringToIntCase("-0", 0)
testStringToIntCase("42", 42)
testStringToIntCase(" 42", 42)
testStringToIntCase("-42", -42)
testStringToIntCase("\n -42", -42)
testStringToIntCase("042", 42)
testStringToIntCase("-042", -42)
// Hex cases.
testStringToIntCase("0x0", 0x0, true)
testStringToIntCase("0x42", 0x42, true)
testStringToIntCase("\t0x42", 0x42, true)
// Just Before overflow cases
testStringToIntCase("2147483647", Int.MAX_VALUE)
testStringToIntCase("-2147483648", Int.MIN_VALUE)
testStringToIntCase("0xffffffff", -1, true)
// Overflow cases:
testStringToIntFailure("2147483648")
testStringToIntFailure("-2147483649")
testStringToIntFailure("0x1ffffffff")
}
private fun testStringToFloatCaseFloat(input: String, expectedValue: Float) {
val result = stringToFloat(input)
val expectedOutput = ResValue(ResValue.DataType.FLOAT, expectedValue.toRawBits())
Truth.assertThat(result).isEqualTo(expectedOutput)
}
private fun testStringToFloatCaseFixed(
input: String,
expectedMantissa: Int,
expectedUnitType: Int,
expectedFormat: Int,
isFraction: Boolean = false) {
val result = stringToFloat(input)
val expectedValue = (expectedUnitType shl ResValue.ComplexFormat.UNIT_SHIFT) or
(expectedFormat shl ResValue.ComplexFormat.RADIX_SHIFT) or
(expectedMantissa shl ResValue.ComplexFormat.MANTISSA_SHIFT)
val expectedOutput = ResValue(
if(isFraction) ResValue.DataType.FRACTION else ResValue.DataType.DIMENSION,
expectedValue)
Truth.assertThat(result).isEqualTo(expectedOutput)
}
private fun testStringToFloatFailure(input: String) {
Truth.assertThat(stringToFloat(input)).isNull()
}
@Test
fun testStringToFloat() {
testStringToFloatFailure("")
testStringToFloatFailure(" ")
testStringToFloatFailure("\t\n")
testStringToFloatFailure("abcd")
testStringToFloatFailure("10abcd")
testStringToFloatFailure("42 42")
testStringToFloatFailure("- 42")
testStringToFloatFailure("-")
testStringToFloatFailure(".")
testStringToFloatFailure("+")
testStringToFloatFailure("+.")
testStringToFloatFailure("e3")
testStringToFloatFailure("0x")
testStringToFloatFailure("0xnope")
testStringToFloatFailure("0x42 0x42")
testStringToFloatFailure("- 0x42")
testStringToFloatFailure("-0xp2")
// failures of
testStringToFloatFailure("px")
testStringToFloatFailure("dip")
// 'd' could be interpreted as part of the suffix or hex, so it fails.
testStringToFloatFailure("0x02dp")
testStringToFloatFailure("0x10 px")
testStringToFloatFailure("10 px")
// Decimal Floating Point Cases:
testStringToFloatCaseFloat("0", 0f)
testStringToFloatCaseFloat(".0", 0f)
testStringToFloatCaseFloat("0.", 0f)
testStringToFloatCaseFloat("-0", -0f)
testStringToFloatCaseFloat("+0", 0f)
testStringToFloatCaseFloat("42", 42f)
testStringToFloatCaseFloat("+42", 42f)
testStringToFloatCaseFloat("-42", -42f)
testStringToFloatCaseFloat("314159e-5", 3.14159f)
testStringToFloatCaseFloat("1e2", 100f)
testStringToFloatCaseFloat(".42e+2", 42f)
// Hex Floating Point Cases:
testStringToFloatCaseFloat("0x0", 0f)
testStringToFloatCaseFloat("+0xa", 10f)
testStringToFloatCaseFloat("-0x02a", -42f)
testStringToFloatCaseFloat("-0x7.8", -7.5f)
testStringToFloatCaseFloat("0xfp-2", 3.75f)
testStringToFloatCaseFloat("0x21p2", 132f)
// capital letters work fine here
testStringToFloatCaseFloat("0X1.FBP+3", 15.84375f)
// mix and match is fine too.
testStringToFloatCaseFloat("0xa.fP2", 43.75f)
// Fixed Point Cases:
testStringToFloatCaseFixed(
"0px",
0,
ResValue.ComplexFormat.UNIT_PX,
ResValue.ComplexFormat.RADIX_23p0)
testStringToFloatCaseFixed(
"10dp",
10,
ResValue.ComplexFormat.UNIT_DIP,
ResValue.ComplexFormat.RADIX_23p0)
testStringToFloatCaseFixed(
"10dip",
10,
ResValue.ComplexFormat.UNIT_DIP,
ResValue.ComplexFormat.RADIX_23p0)
testStringToFloatCaseFixed(
".5in",
// mantissa = 0x0.8 << 23
0x400000,
ResValue.ComplexFormat.UNIT_IN,
ResValue.ComplexFormat.RADIX_0p23)
testStringToFloatCaseFixed(
"0x10sp",
16,
ResValue.ComplexFormat.UNIT_SP,
ResValue.ComplexFormat.RADIX_23p0)
testStringToFloatCaseFixed(
// Specifying the exponent "p" should allow dp to be used as a unit.
"0x10p0dp",
16,
ResValue.ComplexFormat.UNIT_DIP,
ResValue.ComplexFormat.RADIX_23p0)
testStringToFloatCaseFixed(
"100%",
1,
ResValue.ComplexFormat.UNIT_FRACTION,
ResValue.ComplexFormat.RADIX_23p0,
true)
// Test some 8p15 fixed point cases.
testStringToFloatCaseFixed(
"520%p",
// mantissa = 5.2 << 15. Note: 1/5 = .0011 repeating
0x29999,
ResValue.ComplexFormat.UNIT_FRACTION_PARENT,
ResValue.ComplexFormat.RADIX_8p15,
true)
testStringToFloatCaseFixed(
"3375%",
0x10e000,
ResValue.ComplexFormat.UNIT_FRACTION,
ResValue.ComplexFormat.RADIX_8p15,
true)
// Test some 16p7 fixed point cases
testStringToFloatCaseFixed(
"256.5px",
// 256.5 << 7
0x8040,
ResValue.ComplexFormat.UNIT_PX,
ResValue.ComplexFormat.RADIX_16p7)
testStringToFloatCaseFixed(
"-500.125pt",
// 24 bit 2's complement of 0xfa10
0xff05f0,
ResValue.ComplexFormat.UNIT_PT,
ResValue.ComplexFormat.RADIX_16p7)
// Min value checks
testStringToFloatCaseFixed(
// 1 * 2^(-23)
"0x1p-23mm",
1,
ResValue.ComplexFormat.UNIT_MM,
ResValue.ComplexFormat.RADIX_0p23)
// test rounding of fixed point
testStringToFloatCaseFixed(
// 1 * 2^(-24) should round up to 1 * 2^(-23)
"0x1p-24mm",
1,
ResValue.ComplexFormat.UNIT_MM,
ResValue.ComplexFormat.RADIX_0p23)
testStringToFloatCaseFixed(
// -1 * 2^(-24)
"-0x1p-24in",
-1 and ResValue.ComplexFormat.MANTISSA_MASK,
ResValue.ComplexFormat.UNIT_IN,
ResValue.ComplexFormat.RADIX_0p23)
testStringToFloatCaseFixed(
"0.2mm",
0x19999a,
ResValue.ComplexFormat.UNIT_MM,
ResValue.ComplexFormat.RADIX_0p23)
// Test overflow of fixed points.
testStringToFloatCaseFixed(
"0x7fffffmm",
0x7fffff,
ResValue.ComplexFormat.UNIT_MM,
ResValue.ComplexFormat.RADIX_23p0)
// Positive overflow bleeds into negative values
testStringToFloatCaseFixed(
"0x1p23pt",
0x800000,
ResValue.ComplexFormat.UNIT_PT,
ResValue.ComplexFormat.RADIX_23p0)
// Test underflow of fixed points.
testStringToFloatCaseFixed(
"-0x800000mm",
0x800000,
ResValue.ComplexFormat.UNIT_MM,
ResValue.ComplexFormat.RADIX_23p0)
// Negative underflow bleeds into positive values.
testStringToFloatCaseFixed(
"-0x800001pt",
0x7fffff,
ResValue.ComplexFormat.UNIT_PT,
ResValue.ComplexFormat.RADIX_23p0)
}
}
@@ -0,0 +1,116 @@
package com.android.aaptcompiler.buffer
import com.google.common.truth.Truth
import org.junit.Test
class BigBufferTest {
@Test
fun testAllocateSingleBlock() {
val buffer = BigBuffer(4)
val entry = buffer.nextBlock(2)
Truth.assertThat(entry.size).isEqualTo(2)
Truth.assertThat(entry.start).isEqualTo(0)
Truth.assertThat(buffer.size).isEqualTo(2)
}
@Test
fun testSameBlockIfNextAllocationFits() {
val buffer = BigBuffer(16)
val entry1 = buffer.nextBlock(8)
val entry2 = buffer.nextBlock(4)
Truth.assertThat(entry1.size).isEqualTo(8)
Truth.assertThat(entry1.start).isEqualTo(0)
Truth.assertThat(entry2.size).isEqualTo(4)
Truth.assertThat(entry2.start).isEqualTo(8)
Truth.assertThat(entry1.block).isEqualTo(entry2.block)
Truth.assertThat(buffer.size).isEqualTo(12)
}
@Test
fun testAllocateBlockLargerThanBlockSize() {
val buffer = BigBuffer(16)
val entry = buffer.nextBlock(32)
Truth.assertThat(entry.size).isEqualTo(32)
Truth.assertThat(entry.start).isEqualTo(0)
Truth.assertThat(buffer.size).isEqualTo(32)
}
@Test
fun testAppendAndMoveBlock() {
val buffer = BigBuffer(16)
val entry1 = buffer.nextBlock(4)
entry1.writeInt(33, 0)
val buffer2 = BigBuffer(16)
val entry2 = buffer2.nextBlock(4)
entry2.writeInt(44, 0)
buffer.append(buffer2)
Truth.assertThat(buffer2.size).isEqualTo(0)
Truth.assertThat(buffer.size).isEqualTo(8)
Truth.assertThat(buffer.block(0).readInt(0)).isEqualTo(33)
Truth.assertThat(buffer.block(1).readInt(0)).isEqualTo(44)
}
@Test
fun testPadAndAlign() {
val buffer = BigBuffer(16)
buffer.nextBlock(2)
Truth.assertThat(buffer.size).isEqualTo(2)
buffer.pad(2)
Truth.assertThat(buffer.size).isEqualTo(4)
buffer.align4()
Truth.assertThat(buffer.size).isEqualTo(4)
buffer.pad(2)
Truth.assertThat(buffer.size).isEqualTo(6)
buffer.align4()
Truth.assertThat(buffer.size).isEqualTo(8)
}
@Test
fun testToByteArray() {
val buffer = BigBuffer(8)
val entry1 = buffer.nextBlock(4)
val entry2 = buffer.nextBlock(8)
Truth.assertThat(buffer.blocks).hasSize(2)
entry1.writeByte(0x0f, 0)
entry1.writeByte(0xf0.toByte(), 1)
entry2.writeByte(0x11, 4)
entry2.writeByte(0x22, 5)
val result = buffer.toBytes()
Truth.assertThat(result.size).isEqualTo(12)
Truth.assertThat(result[0]).isEqualTo(0x0f)
Truth.assertThat(result[1]).isEqualTo(0xf0.toByte())
Truth.assertThat(result[8]).isEqualTo(0x11)
Truth.assertThat(result[9]).isEqualTo(0x22)
}
@Test
fun testWriteAndRead() {
val buffer = BigBuffer()
val entry = buffer.nextBlock(200)
entry.writeByte(124, 0)
Truth.assertThat(entry.readByte(0)).isEqualTo(124)
entry.writeShort(0x1234, 0)
Truth.assertThat(entry.readShort(0)).isEqualTo(0x1234)
entry.writeInt(32768, 0)
Truth.assertThat(entry.readInt(0)).isEqualTo(32768)
}
}
@@ -0,0 +1,830 @@
package com.android.aaptcompiler.proto
import com.android.aapt.Resources
import com.android.aaptcompiler.ArrayResource
import com.android.aaptcompiler.AttributeResource
import com.android.aaptcompiler.BasicString
import com.android.aaptcompiler.BinaryPrimitive
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.FileReference
import com.android.aaptcompiler.Id
import com.android.aaptcompiler.Overlayable
import com.android.aaptcompiler.OverlayableItem
import com.android.aaptcompiler.Plural
import com.android.aaptcompiler.RawString
import com.android.aaptcompiler.Reference
import com.android.aaptcompiler.ResourceFile
import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.Source
import com.android.aaptcompiler.Span
import com.android.aaptcompiler.StringPool
import com.android.aaptcompiler.Style
import com.android.aaptcompiler.StyleString
import com.android.aaptcompiler.Styleable
import com.android.aaptcompiler.StyledString
import com.android.aaptcompiler.Visibility
import com.android.aaptcompiler.android.ResStringPool
import com.android.aaptcompiler.android.ResValue
import com.android.aaptcompiler.buffer.BigBuffer
import com.android.aaptcompiler.getValue
import com.android.aaptcompiler.makeEmpty
import com.android.aaptcompiler.parse
import com.android.aaptcompiler.parseResourceName
import com.android.aaptcompiler.testutils.ResourceTableBuilder
import com.android.aaptcompiler.testutils.parseNameOrFail
import com.android.aaptcompiler.tryParseBool
import com.android.aaptcompiler.tryParseColor
import com.android.aaptcompiler.tryParseFloat
import com.android.aaptcompiler.tryParseInt
import com.android.aaptcompiler.tryParseNullOrEmpty
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.google.common.truth.Truth
import org.junit.Test
import java.nio.ByteBuffer
class ProtoSerializeTest {
private fun testSerializeDeserialize(primitive: BinaryPrimitive) {
val pbPrimitive = serializeBinPrimitiveToPb(primitive, null)
val deserialized = deserializeBinPrimitiveFromPb(pbPrimitive, null)
Truth.assertThat(deserialized).isEqualTo(primitive)
}
private fun testSerializeDeserialize(configDescription: ConfigDescription) {
val pbConfig = serializeConfigToPb(configDescription, null, null)
val deserialized = deserializeConfigFromPb(pbConfig, null)
Truth.assertThat(deserialized).isEqualTo(configDescription)
}
private fun testSerializeDeserialize(string: BasicString, givenPool: StringPool) {
val pbString = serializeStringToPb(string)
val deserialized = deserializeStringFromPb(pbString, givenPool)
Truth.assertThat(deserialized).isEqualTo(string)
}
private fun testSerializeDeserialize(string: RawString, givenPool: StringPool) {
val pbString = serializeRawToPb(string)
val deserialized = deserializeRawFromPb(pbString, givenPool)
Truth.assertThat(deserialized).isEqualTo(string)
}
private fun testSerializeDeserialize(string: StyledString, givenPool: StringPool) {
val pbString = serializeStyledStrToPb(string)
val deserialized = deserializeStyledStrFromPb(pbString, givenPool)
Truth.assertThat(deserialized).isEqualTo(string)
}
private fun testSerializeDeserialize(
file: FileReference, givenPool: StringPool, config: ConfigDescription) {
val pbFileRef = serializeFileRefToPb(file)
val deserialized = deserializeFileRefFromPb(pbFileRef, givenPool, config)
Truth.assertThat(deserialized).isEqualTo(file)
}
private fun testSerializeDeserialize(overlayableItem: OverlayableItem) {
val sourcePool = StringPool()
val overlayable = overlayableItem.overlayable
val pbOverlayableItem = serializeOverlayableToPb(
overlayableItem, mutableListOf(), Resources.ResourceTable.newBuilder(), sourcePool)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized = deserializeOverlayableFromPb(
pbOverlayableItem, overlayable, extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(overlayableItem)
Truth.assertThat(deserialized.source).isEqualTo(overlayableItem.source)
}
private fun testSerializeDeserialize(reference: Reference) {
val pbReference = serializeReferenceToPb(reference)
val deserialized = deserializeReferenceFromPb(pbReference, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(reference)
Truth.assertThat(deserialized.isPrivate).isEqualTo(reference.isPrivate)
Truth.assertThat(deserialized.id).isEqualTo(reference.id)
}
private fun testSerializeDeserialize(attr: AttributeResource) {
val sourcePool = StringPool()
val pbAttr = serializeAttrToPb(attr, sourcePool)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized = deserializeAttrFromPb(pbAttr, extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(attr)
for (index in attr.symbols.indices) {
val original = attr.symbols[index]
val translated = deserialized.symbols[index]
Truth.assertThat(original.symbol.source).isEqualTo(translated.symbol.source)
Truth.assertThat(original.symbol.comment).isEqualTo(translated.symbol.comment)
}
}
private fun testSerializeDeserialize(style: Style, givenPool: StringPool) {
val sourcePool = StringPool()
val pbStyle = serializeStyleToPb(style, sourcePool, null)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized =
deserializeStyleFromPb(pbStyle, givenPool, ConfigDescription(), extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(style)
if (style.parent != null) {
Truth.assertThat(deserialized.parent).isNotNull()
Truth.assertThat(deserialized.parent!!.source).isEqualTo(style.parent!!.source)
}
for (index in style.entries.indices) {
val original = style.entries[index]
val translated = deserialized.entries[index]
Truth.assertThat(translated.key.source).isEqualTo(original.key.source)
Truth.assertThat(translated.key.comment).isEqualTo(original.key.comment)
// When deserialized, the key's metadata is reflected in the value as well.
Truth.assertThat(translated.value!!.source).isEqualTo(translated.key.source)
Truth.assertThat(translated.value!!.comment).isEqualTo(translated.key.comment)
}
}
private fun testSerializeDeserialize(array: ArrayResource, givenPool: StringPool) {
val sourcePool = StringPool()
val pbArray = serializeArrayToPb(array, sourcePool, null)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized = deserializeArrayFromPb(
pbArray, givenPool, ConfigDescription(), extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(array)
for (index in array.elements.indices) {
val original = array.elements[index]
val translated = deserialized.elements[index]
Truth.assertThat(original.source).isEqualTo(translated.source)
Truth.assertThat(original.comment).isEqualTo(translated.comment)
}
}
private fun testSerializeDeserialize(styleable: Styleable) {
val sourcePool = StringPool()
val pbStyleable = serializeStyleableToPb(styleable, sourcePool)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized = deserializeStyleableFromPb(pbStyleable, extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(styleable)
for (index in styleable.entries.indices) {
val original = styleable.entries[index]
val translated = styleable.entries[index]
Truth.assertThat(original.source).isEqualTo(translated.source)
Truth.assertThat(original.comment).isEqualTo(translated.comment)
}
}
private fun testSerializeDeserialize(plural: Plural, givenPool: StringPool) {
val sourcePool = StringPool()
val pbPlural = serializePluralToPb(plural, sourcePool, null)
val buffer = BigBuffer(1028)
sourcePool.flattenUtf16(buffer, null)
val extractedSources = ResStringPool.get(ByteBuffer.wrap(buffer.toBytes()), buffer.size)
val deserialized =
deserializePluralFromPb(pbPlural, givenPool, ConfigDescription(), extractedSources, null)
Truth.assertThat(deserialized).isNotNull()
Truth.assertThat(deserialized!!).isEqualTo(plural)
for (index in plural.values.indices) {
val original = plural.values[index]
val translated = deserialized.values[index]
if (original != null) {
Truth.assertThat(original.source).isEqualTo(translated!!.source)
Truth.assertThat(original.comment).isEqualTo(translated.comment)
}
}
}
@Test
fun testSerializeBinaryPrimitive() {
// Boolean type
testSerializeDeserialize(tryParseBool("true")!!)
// Null type
testSerializeDeserialize(
BinaryPrimitive(ResValue(ResValue.DataType.NULL, ResValue.NullFormat.UNDEFINED)))
// Empty type
testSerializeDeserialize(tryParseNullOrEmpty("@empty") as BinaryPrimitive)
// Int types
testSerializeDeserialize(tryParseInt("0xabcd")!!)
testSerializeDeserialize(tryParseInt("-1024")!!)
// Color types
testSerializeDeserialize(tryParseColor("#ff0000ff")!!)
testSerializeDeserialize(tryParseColor("#00ffff")!!)
testSerializeDeserialize(tryParseColor("#f000")!!)
testSerializeDeserialize(tryParseColor("#840")!!)
// Float types
testSerializeDeserialize(tryParseFloat("3.14")!!)
testSerializeDeserialize(tryParseFloat("0x1.ff")!!)
// Dimension types
testSerializeDeserialize(tryParseFloat("20dp")!!)
testSerializeDeserialize(tryParseFloat("0x19p0dp")!!)
// Fraction types
testSerializeDeserialize(tryParseFloat("20%")!!)
testSerializeDeserialize(tryParseFloat("80%p")!!)
}
@Test
fun testSerializeConfig() {
// Empty config.
testSerializeDeserialize(parse(""))
testSerializeDeserialize(parse("mcc123"))
testSerializeDeserialize(parse("mnc02"))
testSerializeDeserialize(parse("mnc002"))
testSerializeDeserialize(parse("mnc123"))
testSerializeDeserialize(parse("en"))
testSerializeDeserialize(parse("en-rGB"))
testSerializeDeserialize(parse("b+en+GB"))
testSerializeDeserialize(parse("ldltr"))
testSerializeDeserialize(parse("ldrtl"))
testSerializeDeserialize(parse("sw3600dp"))
testSerializeDeserialize(parse("w300dp"))
testSerializeDeserialize(parse("h400dp"))
testSerializeDeserialize(parse("small"))
testSerializeDeserialize(parse("normal"))
testSerializeDeserialize(parse("large"))
testSerializeDeserialize(parse("xlarge"))
testSerializeDeserialize(parse("long"))
testSerializeDeserialize(parse("notlong"))
testSerializeDeserialize(parse("round"))
testSerializeDeserialize(parse("notround"))
testSerializeDeserialize(parse("widecg"))
testSerializeDeserialize(parse("nowidecg"))
testSerializeDeserialize(parse("highdr"))
testSerializeDeserialize(parse("lowdr"))
testSerializeDeserialize(parse("port"))
testSerializeDeserialize(parse("land"))
testSerializeDeserialize(parse("square"))
testSerializeDeserialize(parse("desk"))
testSerializeDeserialize(parse("car"))
testSerializeDeserialize(parse("television"))
testSerializeDeserialize(parse("appliance"))
testSerializeDeserialize(parse("watch"))
testSerializeDeserialize(parse("vrheadset"))
testSerializeDeserialize(parse("night"))
testSerializeDeserialize(parse("notnight"))
testSerializeDeserialize(parse("300dpi"))
testSerializeDeserialize(parse("hdpi"))
testSerializeDeserialize(parse("notouch"))
testSerializeDeserialize(parse("stylus"))
testSerializeDeserialize(parse("finger"))
testSerializeDeserialize(parse("keysexposed"))
testSerializeDeserialize(parse("keyshidden"))
testSerializeDeserialize(parse("keyssoft"))
testSerializeDeserialize(parse("nokeys"))
testSerializeDeserialize(parse("qwerty"))
testSerializeDeserialize(parse("12key"))
testSerializeDeserialize(parse("navhidden"))
testSerializeDeserialize(parse("navexposed"))
testSerializeDeserialize(parse("nonav"))
testSerializeDeserialize(parse("dpad"))
testSerializeDeserialize(parse("trackball"))
testSerializeDeserialize(parse("wheel"))
testSerializeDeserialize(parse("300x200"))
testSerializeDeserialize(parse("v8"))
testSerializeDeserialize(
parse("mcc310-pl-sw720dp-normal-long-port-night-xhdpi-keyssoft-qwerty-navexposed-nonav"))
testSerializeDeserialize(
parse("mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-" +
"land-car-night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23"))
testSerializeDeserialize(parse("neuter"))
testSerializeDeserialize(parse("feminine"))
testSerializeDeserialize(parse("masculine"))
}
@Test
fun testSerializeStrings() {
val originalPool = StringPool()
val newPool = StringPool()
testSerializeDeserialize(BasicString(originalPool.makeRef("one")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("two")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("three")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("three")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("four")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("four")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("four")), newPool)
testSerializeDeserialize(BasicString(originalPool.makeRef("four")), newPool)
Truth.assertThat(newPool.size()).isEqualTo(4)
testSerializeDeserialize(
StyledString(
originalPool.makeRef(
StyleString(
"Hello World!",
listOf(Span("b", 0, 4), Span("i", 6, 10)))),
listOf()),
newPool)
Truth.assertThat(newPool.size()).isEqualTo(7)
val fileRef = FileReference(originalPool.makeRef("values/strings.xml"))
fileRef.type = ResourceFile.Type.ProtoXml
testSerializeDeserialize(fileRef, newPool, ConfigDescription())
Truth.assertThat(newPool.size()).isEqualTo(8)
testSerializeDeserialize(RawString(originalPool.makeRef("raw")), newPool)
Truth.assertThat(newPool.size()).isEqualTo(9)
}
@Test
fun testOverlayableSerialization() {
val overlayable = Overlayable("name", "actor", Source("res/values/overlayable.xml", 17))
val overlayableItem = OverlayableItem(
overlayable,
OverlayableItem.Policy.SYSTEM or OverlayableItem.Policy.PRODUCT,
"comment",
Source("res/values/overlayable.xml", 19))
val overlayableItem2 = OverlayableItem(overlayable)
testSerializeDeserialize(overlayableItem)
testSerializeDeserialize(overlayableItem2)
}
@Test
fun testReferenceSerialization() {
val reference = Reference(parseResourceName("android:string/foo")!!.resourceName)
testSerializeDeserialize(reference)
val reference2 = Reference(parseResourceName("android:string/bar")!!.resourceName)
reference2.isPrivate = true
reference2.id = 0x01010000
testSerializeDeserialize(reference2)
}
@Test
fun testAttrSerialization() {
val emptyAttribute = AttributeResource(Resources.Attribute.FormatFlags.ENUM_VALUE)
testSerializeDeserialize(emptyAttribute)
val attributeExample = AttributeResource(Resources.Attribute.FormatFlags.FLAGS_VALUE)
attributeExample.minInt = 0
attributeExample.maxInt = 1 shl 16
attributeExample.symbols.add(
AttributeResource.Symbol(
Reference(parseNameOrFail("android:attr/bar")),
1,
ResValue.DataType.INT_DEC.byteValue))
attributeExample.symbols.add(
AttributeResource.Symbol(
Reference(parseNameOrFail("android:attr/baz")),
2,
ResValue.DataType.INT_DEC.byteValue))
attributeExample.symbols.add(
AttributeResource.Symbol(
Reference(parseNameOrFail("android:attr/bat")),
4,
ResValue.DataType.INT_DEC.byteValue))
testSerializeDeserialize(attributeExample)
}
@Test
fun testStyleSerialization() {
val emptyStyle = Style()
val oldPool = StringPool()
val newPool = StringPool()
testSerializeDeserialize(emptyStyle, newPool)
Truth.assertThat(newPool.size()).isEqualTo(0)
val styleExampleNoParent = Style()
styleExampleNoParent.entries.add(
Style.Entry(
Reference(parseResourceName("android:string/foo")!!.resourceName),
BasicString(oldPool.makeRef("Hello there!"))))
val keyReference = Reference(parseResourceName("android:bool/bar")!!.resourceName)
keyReference.source = Source("res/values/style.xml", 30)
keyReference.comment = "Whether or not the bar state can be active."
styleExampleNoParent.entries.add(
Style.Entry(
keyReference,
tryParseBool("true")))
testSerializeDeserialize(styleExampleNoParent, newPool)
Truth.assertThat(newPool.size()).isEqualTo(1)
}
@Test
fun testStyleableSerialization() {
val emptyStyleable = Styleable()
testSerializeDeserialize(emptyStyleable)
val styleableExample = Styleable()
val reference = Reference(parseResourceName("android:attr/myAttribute")!!.resourceName)
reference.source = Source("res/values/styleable.xml", 86)
styleableExample.entries.add(reference)
testSerializeDeserialize(styleableExample)
}
@Test
fun testArraySerialization() {
val emptyArray = ArrayResource()
val oldPool = StringPool()
val newPool = StringPool()
testSerializeDeserialize(emptyArray, newPool)
Truth.assertThat(newPool.size()).isEqualTo(0)
val arrayExample = ArrayResource()
val basicString = BasicString(oldPool.makeRef("Here we go."))
basicString.source = Source("res/values/hello.xml", 29)
basicString.comment = "I don't know what's happening."
arrayExample.elements.add(basicString)
arrayExample.elements.add(Reference(parseResourceName("android:attr/hi")!!.resourceName))
arrayExample.elements.add(Id())
testSerializeDeserialize(arrayExample, newPool)
Truth.assertThat(newPool.size()).isEqualTo(1)
}
@Test
fun testPluralSerialization() {
val emptyPlural = Plural()
val oldPool = StringPool()
val newPool = StringPool()
testSerializeDeserialize(emptyPlural, newPool)
Truth.assertThat(newPool.size()).isEqualTo(0)
val pluralExample = Plural()
val oneApple = BasicString(oldPool.makeRef("apple"))
oneApple.source = Source("res/values/strings.xml", 29)
pluralExample.setValue(Plural.Type.ONE, oneApple)
val otherApples = BasicString(oldPool.makeRef("apples"))
otherApples.source = Source("res/values/strings.xml", 30)
otherApples.comment = "Hello world."
pluralExample.setValue(Plural.Type.OTHER, otherApples)
val manyApples = BasicString(oldPool.makeRef("all the apples"))
// No source on purpose.
pluralExample.setValue(Plural.Type.MANY, manyApples)
testSerializeDeserialize(pluralExample, newPool)
Truth.assertThat(newPool.size()).isEqualTo(3)
}
@Test
fun testSerializeSinglePackage() {
val table = ResourceTableBuilder()
.setPackageId("com.app.a", 0x7f)
.addFileReference("com.app.a:layout/main", "res/layout/main.xml", 0x7f020000)
.addReference("com.app.a:layout/other", "com.app.a:layout/main", 0x7f020001)
.addString("com.app.a:string/text", "hi")
.addValue("com.app.a:id/foo", Id())
.setSymbolState("com.app.a:bool/foo", ResourceVisibility.UNDEFINED, 0, true)
.build()
val publicSymbol = Visibility(level = ResourceVisibility.PUBLIC)
Truth.assertThat(
table.setVisibilityWithId(
parseNameOrFail("com.app.a:layout/main"),
publicSymbol,
0x7f020000)).isTrue()
val id = getValue(table, "com.app.a:id/foo") as? Id
Truth.assertThat(id).isNotNull()
// Make a plural.
val plural = Plural()
plural.setValue(Plural.Type.ONE, BasicString(table.stringPool.makeRef("one")))
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:plurals/hey"),
ConfigDescription(),
"",
plural)).isTrue()
// Make a styled string.
val styleString = StyleString("hello", listOf(Span("b", 0, 4)))
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:string/styled"),
ConfigDescription(),
"",
StyledString(table.stringPool.makeRef(styleString), listOf()))).isTrue()
// Make a resource with different products.
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:integer/one"),
parse("land"),
"",
BinaryPrimitive(ResValue(ResValue.DataType.INT_DEC, 123)))).isTrue()
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:integer/one"),
parse("land"),
"tablet",
BinaryPrimitive(ResValue(ResValue.DataType.INT_HEX, 321)))).isTrue()
// Make a reference with both resource name and resource ID.
// The reference should point to a resource outside of this table to test that both name and id
// get serialized.
val expectedRef = Reference(parseNameOrFail("android:layout/main"))
expectedRef.id = 0x01020000
Truth.assertThat(
table.addResource(
parseNameOrFail("com.app.a:layout/abc"), ConfigDescription(), "", expectedRef)).isTrue()
// Make an overlayable resource.
val overlayableItem = OverlayableItem(
Overlayable("OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40)),
source = Source("res/values/overlayable.xml", 42))
Truth.assertThat(
table.setOverlayable(
parseNameOrFail("com.app.a:integer/overlayable"), overlayableItem)).isTrue()
val pbTable = serializeTableToPb(table, null)
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(pbTable, newTable, null)).isTrue()
val newId = getValue(newTable, "com.app.a:id/foo") as? Id
Truth.assertThat(newId).isNotNull()
Truth.assertThat(newId!!.weak).isEqualTo(id!!.weak)
val newLayout = newTable.findResource(parseNameOrFail("com.app.a:layout/main"))
Truth.assertThat(newLayout).isNotNull()
Truth.assertThat(newLayout!!.group.visibility).isEqualTo(ResourceVisibility.PUBLIC)
Truth.assertThat(newLayout.entry.visibility.level).isEqualTo(ResourceVisibility.PUBLIC)
val newBool = newTable.findResource(parseNameOrFail("com.app.a:bool/foo"))
Truth.assertThat(newBool).isNotNull()
Truth.assertThat(newBool!!.entry.visibility.level).isEqualTo(ResourceVisibility.UNDEFINED)
Truth.assertThat(newBool.entry.allowNew).isNotNull()
// Find the product dependent values.
val newPrimitive =
getValue(newTable, "com.app.a:integer/one", parse("land"), "tablet") as? BinaryPrimitive
Truth.assertThat(newPrimitive).isNotNull()
Truth.assertThat(newPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_HEX)
Truth.assertThat(newPrimitive.resValue.data).isEqualTo(321)
val newPrimitive2 =
getValue(newTable, "com.app.a:integer/one", parse("land")) as? BinaryPrimitive
Truth.assertThat(newPrimitive2).isNotNull()
Truth.assertThat(newPrimitive2!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_DEC)
Truth.assertThat(newPrimitive2.resValue.data).isEqualTo(123)
val newActualRef = getValue(newTable, "com.app.a:layout/abc") as? Reference
Truth.assertThat(newActualRef).isNotNull()
Truth.assertThat(newActualRef!!.name).isNotNull()
Truth.assertThat(newActualRef.id).isNotNull()
Truth.assertThat(newActualRef).isEqualTo(expectedRef)
val newActualFileRef = getValue(newTable, "com.app.a:layout/main") as? FileReference
Truth.assertThat(newActualFileRef).isNotNull()
val newStyledString = getValue(newTable, "com.app.a:string/styled") as? StyledString
Truth.assertThat(newStyledString).isNotNull()
Truth.assertThat(newStyledString!!.ref.value()).isEqualTo("hello")
Truth.assertThat(newStyledString.ref.spans()).hasSize(1)
val newSpan = newStyledString.ref.spans()[0]
Truth.assertThat(newSpan.name.value()).isEqualTo("b")
Truth.assertThat(newSpan.firstChar).isEqualTo(0)
Truth.assertThat(newSpan.lastChar).isEqualTo(4)
val newOverlayable = newTable.findResource(parseNameOrFail("com.app.a:integer/overlayable"))
Truth.assertThat(newOverlayable).isNotNull()
val actualOverlayableItem = newOverlayable!!.entry.overlayable
Truth.assertThat(actualOverlayableItem).isNotNull()
Truth.assertThat(actualOverlayableItem!!.overlayable.name).isEqualTo("OverlayableName")
Truth.assertThat(actualOverlayableItem.overlayable.actor).isEqualTo("overlay://theme")
Truth.assertThat(actualOverlayableItem.overlayable.source)
.isEqualTo(Source("res/values/overlayable.xml", 40))
Truth.assertThat(actualOverlayableItem.policies).isEqualTo(OverlayableItem.Policy.NONE)
Truth.assertThat(actualOverlayableItem.source)
.isEqualTo(Source("res/values/overlayable.xml", 42))
}
@Test
fun serializeAndDeserializeTableOfPrimitives() {
val table = ResourceTableBuilder()
.addValue("android:bool/boolean_true", tryParseBool("True")!!)
.addValue("android:bool/boolean_false", tryParseBool("False")!!)
.addValue("android:color/color_rgb8", tryParseColor("#AABBCC")!!)
.addValue("android:color/color_argb8", tryParseColor("#11223344")!!)
.addValue("android:color/color_rgb4", tryParseColor("#DEF")!!)
.addValue("android:color/color_argb4", tryParseColor("#5678")!!)
.addValue("android:integer/integer_444", tryParseInt("444")!!)
.addValue("android:integer/integer_neg333", tryParseInt("-333")!!)
.addValue("android:integer/hex_int_abcd", tryParseInt("0xABCD")!!)
.addValue("android:dimen/dimen_1_39mm", tryParseFloat("1.39mm")!!)
.addValue("android:fraction/fraction_27", tryParseFloat("27%")!!)
.addValue("android:integer/null", makeEmpty())
.build()
val pbTable = serializeTableToPb(table, null)
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(pbTable, newTable, null)).isTrue()
var binPrimitive = getValue(newTable, "android:bool/boolean_true") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_BOOLEAN)
Truth.assertThat(binPrimitive.resValue.data).isNotEqualTo(0)
binPrimitive = getValue(newTable, "android:bool/boolean_false") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_BOOLEAN)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0)
binPrimitive = getValue(newTable, "android:color/color_rgb8") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_RGB8)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0xffaabbcc.toInt())
binPrimitive = getValue(newTable, "android:color/color_argb8") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_ARGB8)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0x11223344)
binPrimitive = getValue(newTable, "android:color/color_rgb4") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_RGB4)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0xffddeeff.toInt())
binPrimitive = getValue(newTable, "android:color/color_argb4") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_COLOR_ARGB4)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0x55667788)
binPrimitive = getValue(newTable, "android:integer/integer_444") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_DEC)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(444)
binPrimitive = getValue(newTable, "android:integer/integer_neg333") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_DEC)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(-333)
binPrimitive = getValue(newTable, "android:integer/hex_int_abcd") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.INT_HEX)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(0xabcd)
binPrimitive = getValue(newTable, "android:dimen/dimen_1_39mm") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.DIMENSION)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(tryParseFloat("1.39mm")!!.resValue.data)
binPrimitive = getValue(newTable, "android:fraction/fraction_27") as? BinaryPrimitive
Truth.assertThat(binPrimitive).isNotNull()
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.FRACTION)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(tryParseFloat("27%")!!.resValue.data)
binPrimitive = getValue(newTable, "android:integer/null") as? BinaryPrimitive
Truth.assertThat(binPrimitive!!.resValue.dataType).isEqualTo(ResValue.DataType.NULL)
Truth.assertThat(binPrimitive.resValue.data).isEqualTo(ResValue.NullFormat.EMPTY)
}
@Test
fun serializeAndDeserializeOverlayable() {
val foo = OverlayableItem(
Overlayable("CustomizableResources", "overlay://customization", Source.EMPTY),
OverlayableItem.Policy.SYSTEM or OverlayableItem.Policy.PRODUCT)
val bar = OverlayableItem(
Overlayable("TaskBar", "overlay://theme", Source.EMPTY),
OverlayableItem.Policy.PUBLIC or OverlayableItem.Policy.VENDOR)
val baz = OverlayableItem(
Overlayable("FrontPack", "overlay://theme", Source.EMPTY),
OverlayableItem.Policy.PUBLIC)
val boz = OverlayableItem(
Overlayable("IconPack", "overlay://theme", Source.EMPTY),
OverlayableItem.Policy.SIGNATURE or OverlayableItem.Policy.ODM or OverlayableItem.Policy.OEM)
val biz = OverlayableItem(
Overlayable("Other", "overlay://customization", Source.EMPTY),
comment = "comment")
val table = ResourceTableBuilder()
.setOverlayable("com.app.a:bool/foo", foo)
.setOverlayable("com.app.a:bool/bar", bar)
.setOverlayable("com.app.a:bool/baz", baz)
.setOverlayable("com.app.a:bool/boz", boz)
.setOverlayable("com.app.a:bool/biz", biz)
.addValue("com.app.a:bool/fiz", tryParseBool("true")!!)
.build()
val pbTable = serializeTableToPb(table, null)
val newTable = ResourceTable()
Truth.assertThat(deserializeTableFromPb(pbTable, newTable, null)).isTrue()
var searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/foo"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNotNull()
var item = searchResult.entry.overlayable!!
Truth.assertThat(item.overlayable.name).isEqualTo("CustomizableResources")
Truth.assertThat(item.overlayable.actor).isEqualTo("overlay://customization")
Truth.assertThat(item.policies)
.isEqualTo(OverlayableItem.Policy.SYSTEM or OverlayableItem.Policy.PRODUCT)
searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/bar"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNotNull()
item = searchResult.entry.overlayable!!
Truth.assertThat(item.overlayable.name).isEqualTo("TaskBar")
Truth.assertThat(item.overlayable.actor).isEqualTo("overlay://theme")
Truth.assertThat(item.policies)
.isEqualTo(OverlayableItem.Policy.PUBLIC or OverlayableItem.Policy.VENDOR)
searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/baz"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNotNull()
item = searchResult.entry.overlayable!!
Truth.assertThat(item.overlayable.name).isEqualTo("FrontPack")
Truth.assertThat(item.overlayable.actor).isEqualTo("overlay://theme")
Truth.assertThat(item.policies).isEqualTo(OverlayableItem.Policy.PUBLIC)
searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/boz"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNotNull()
item = searchResult.entry.overlayable!!
Truth.assertThat(item.overlayable.name).isEqualTo("IconPack")
Truth.assertThat(item.overlayable.actor).isEqualTo("overlay://theme")
Truth.assertThat(item.policies)
.isEqualTo(
OverlayableItem.Policy.SIGNATURE or
OverlayableItem.Policy.ODM or
OverlayableItem.Policy.OEM)
searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/biz"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNotNull()
item = searchResult.entry.overlayable!!
Truth.assertThat(item.overlayable.name).isEqualTo("Other")
Truth.assertThat(item.overlayable.actor).isEqualTo("overlay://customization")
Truth.assertThat(item.policies).isEqualTo(OverlayableItem.Policy.NONE)
Truth.assertThat(item.comment).isEqualTo("comment")
searchResult = newTable.findResource(parseNameOrFail("com.app.a:bool/fiz"))
Truth.assertThat(searchResult).isNotNull()
Truth.assertThat(searchResult!!.entry.overlayable).isNull()
}
}
@@ -0,0 +1,108 @@
package com.android.aaptcompiler.testutils
import com.android.aaptcompiler.AllowNew
import com.android.aaptcompiler.BasicString
import com.android.aaptcompiler.ConfigDescription
import com.android.aaptcompiler.FileReference
import com.android.aaptcompiler.Id
import com.android.aaptcompiler.OverlayableItem
import com.android.aaptcompiler.Reference
import com.android.aaptcompiler.ResourceName
import com.android.aaptcompiler.ResourceTable
import com.android.aaptcompiler.Source
import com.android.aaptcompiler.Value
import com.android.aaptcompiler.Visibility
import com.android.aaptcompiler.parseResourceName
import com.itsaky.androidide.layoutlib.resources.ResourceVisibility
import com.google.common.truth.Truth
import java.io.File
fun parseNameOrFail(name: String): ResourceName {
val resNameInfo = parseResourceName(name)
Truth.assertThat(resNameInfo).isNotNull()
return resNameInfo!!.resourceName
}
class ResourceTableBuilder {
private var table = ResourceTable()
fun build(): ResourceTable {
val result = table
table = ResourceTable()
return result
}
fun setPackageId(packageName: String, id: Byte): ResourceTableBuilder {
val resourcePackage = table.createPackage(packageName, id)
Truth.assertThat(resourcePackage).isNotNull()
return this
}
fun addSimple(name: String, id: Int, config: ConfigDescription = ConfigDescription()) =
addValue(name, Id(), id, config)
fun addReference(name: String, ref: String, id: Int = 0) =
addValue(name, Reference(parseNameOrFail(ref)), id)
fun addString(
name: String, value: String, id: Int = 0, config: ConfigDescription = ConfigDescription()) =
addValue(name, BasicString(table.stringPool.makeRef(value)), id, config)
fun addFileReference(
name: String,
path: String,
id: Int = 0,
config: ConfigDescription = ConfigDescription(),
file: File? = null): ResourceTableBuilder {
val fileRef = FileReference(table.stringPool.makeRef(path))
fileRef.file = file
return addValue(name, fileRef, id, config)
}
fun addValue(
name: String,
value: Value,
id: Int = 0,
config: ConfigDescription = ConfigDescription()): ResourceTableBuilder {
Truth.assertThat(
table.addResourceWithIdMangled(
parseNameOrFail(name),
id,
config,
"",
value)).isTrue()
return this
}
fun setSymbolState(
name: String,
level: ResourceVisibility,
id: Int,
allowNew: Boolean): ResourceTableBuilder {
val resName = parseNameOrFail(name)
val visibility = Visibility(level = level)
Truth.assertThat(
table.setVisibilityWithIdMangled(
resName,
visibility,
id)).isTrue()
if (allowNew) {
Truth.assertThat(
table.setAllowNewMangled(
resName,
AllowNew(Source(""), ""))).isTrue()
}
return this
}
fun setOverlayable(name: String, overlayable: OverlayableItem): ResourceTableBuilder {
Truth.assertThat(
table.setOverlayable(
parseNameOrFail(name),
overlayable)).isTrue()
return this
}
}
@@ -0,0 +1,79 @@
package com.android.aaptcompiler.testutils
import android.aapt.pb.internal.ResourcesInternal
import com.android.aapt.Resources
import com.android.aaptcompiler.Container
import com.google.common.base.Preconditions
import com.google.protobuf.CodedInputStream
import java.io.File
class ContainerReader(input: File) {
val numEntries: Int
val inputStream = CodedInputStream.newInstance(input.inputStream())
val entries: List<ContainerEntry>
init {
Preconditions.checkState(
inputStream.readFixed32() == Container.FORMAT_MAGIC,
"File has invalid format."
)
Preconditions.checkState(
inputStream.readFixed32() == Container.FORMAT_VERSION,
"File has invalid version."
)
numEntries = inputStream.readFixed32()
val tempEntries = mutableListOf<ContainerEntry>()
for (i in 0.until(numEntries)) {
val entryType = inputStream.readFixed32()
val entry = when (entryType) {
Container.EntryType.RES_TABLE.value -> gatherTableEntry(inputStream)
else -> gatherFileEntry(inputStream)
}
tempEntries.add(entry)
}
entries = tempEntries.toList()
}
companion object {
fun gatherTableEntry(input: CodedInputStream): ContainerEntry {
val size = input.readFixed64()
val tableBytes = input.readRawBytes(size.toInt())
val table = Resources.ResourceTable.parseFrom(tableBytes)
val padding = (4 - size % 4) % 4
input.readRawBytes(padding.toInt())
return TableEntry(size, table)
}
fun gatherFileEntry(input: CodedInputStream): ContainerEntry {
val totalSize = input.readFixed64()
val fileHeaderSize = input.readFixed32()
val dataSize = input.readFixed64()
val fileHeaderBytes = input.readRawBytes(fileHeaderSize)
val compiledFile = ResourcesInternal.CompiledFile.parseFrom(fileHeaderBytes)
val headerPadding = (4 - fileHeaderSize % 4) % 4
input.readRawBytes(headerPadding)
val dataBytes = input.readRawBytes(dataSize.toInt())
val dataPadding = (4 - dataSize % 4) % 4
input.readRawBytes(dataPadding.toInt())
val expectedTotalSize = 12 + fileHeaderSize + headerPadding + dataSize + dataPadding
Preconditions.checkState(expectedTotalSize == totalSize)
return FileEntry(totalSize, compiledFile, dataBytes)
}
}
}
open class ContainerEntry(val size: Long)
class TableEntry(size: Long, val table: Resources.ResourceTable): ContainerEntry(size)
class FileEntry(size: Long, val header: ResourcesInternal.CompiledFile,val data: ByteArray):
ContainerEntry(size)