mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(aaptcompiler): add unit tests from upstream
This commit is contained in:
@@ -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)
|
||||
}
|
||||
+132
@@ -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))
|
||||
}
|
||||
}
|
||||
+335
@@ -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)
|
||||
}
|
||||
}
|
||||
+253
@@ -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 < 1 minute ago",
|
||||
"[Šţåŕţéð &ļţ; 1 ḿîñûţé åĝö one two three]",
|
||||
Pseudolocalizer.Method.ACCENT)
|
||||
|
||||
simpleTest(
|
||||
"Started < 1 minute ago",
|
||||
"[Šţåŕţéð < 1 ḿîñûţé åĝö one two three]",
|
||||
Pseudolocalizer.Method.ACCENT)
|
||||
|
||||
simpleTest(
|
||||
"Started > 1 minute ago",
|
||||
"[Šţåŕţéð > 1 ḿîñûţé åĝö one two three]",
|
||||
Pseudolocalizer.Method.ACCENT)
|
||||
|
||||
simpleTest(
|
||||
"Started > 1 minute ago",
|
||||
"[Šţåŕţéð > 1 ḿîñûţé åĝö one two three]",
|
||||
Pseudolocalizer.Method.ACCENT)
|
||||
|
||||
simpleTest(
|
||||
"Started < 1 minute ago",
|
||||
"Started < 1 minute ago",
|
||||
Pseudolocalizer.Method.NONE)
|
||||
|
||||
simpleTest(
|
||||
"Started < 1 minute ago",
|
||||
"Started < 1 minute ago",
|
||||
Pseudolocalizer.Method.NONE)
|
||||
|
||||
simpleTest(
|
||||
"hello\n <\n world\n",
|
||||
"${bidiWordStart}hello$bidiWordEnd\n " +
|
||||
"${bidiWordStart}<$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 <i>happy</i> world",
|
||||
"[ĥéļļö <;i>ĥåþþý<;/i> ŵöŕļð 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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+641
@@ -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になります。"
|
||||
}
|
||||
}
|
||||
+1364
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
}
|
||||
}
|
||||
+386
@@ -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()
|
||||
}
|
||||
}
|
||||
+275
@@ -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.")
|
||||
}
|
||||
}
|
||||
+984
@@ -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")
|
||||
}
|
||||
}
|
||||
+272
@@ -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)
|
||||
}
|
||||
}
|
||||
+116
@@ -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)
|
||||
}
|
||||
}
|
||||
+830
@@ -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()
|
||||
}
|
||||
}
|
||||
+108
@@ -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
|
||||
}
|
||||
}
|
||||
+79
@@ -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)
|
||||
Reference in New Issue
Block a user