1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.util; 18 19 import static org.junit.Assert.assertArrayEquals; 20 import static org.junit.Assert.assertEquals; 21 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 22 import static org.xmlpull.v1.XmlPullParser.END_TAG; 23 import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT; 24 import static org.xmlpull.v1.XmlPullParser.START_TAG; 25 import static org.xmlpull.v1.XmlPullParser.TEXT; 26 27 import android.os.PersistableBundle; 28 29 import androidx.test.runner.AndroidJUnit4; 30 31 import com.android.internal.util.XmlUtils; 32 import com.android.modules.utils.TypedXmlPullParser; 33 import com.android.modules.utils.TypedXmlSerializer; 34 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 38 import java.io.ByteArrayInputStream; 39 import java.io.ByteArrayOutputStream; 40 import java.nio.charset.StandardCharsets; 41 import java.util.Arrays; 42 43 @RunWith(AndroidJUnit4.class) 44 public class XmlTest { 45 @Test testLargeValues_Normal()46 public void testLargeValues_Normal() throws Exception { 47 doLargeValues(XmlUtils.makeTyped(Xml.newSerializer()), 48 XmlUtils.makeTyped(Xml.newPullParser())); 49 } 50 51 @Test testLargeValues_Fast()52 public void testLargeValues_Fast() throws Exception { 53 doLargeValues(Xml.newFastSerializer(), 54 Xml.newFastPullParser()); 55 } 56 57 @Test testLargeValues_FastIndenting()58 public void testLargeValues_FastIndenting() throws Exception { 59 final TypedXmlSerializer out = Xml.newFastSerializer(); 60 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 61 doLargeValues(out, 62 Xml.newFastPullParser()); 63 } 64 65 @Test testLargeValues_Binary()66 public void testLargeValues_Binary() throws Exception { 67 doLargeValues(Xml.newBinarySerializer(), 68 Xml.newBinaryPullParser()); 69 } 70 71 /** 72 * Verify that we can write and read large {@link String} and {@code byte[]} 73 * without issues. 74 */ doLargeValues(TypedXmlSerializer out, TypedXmlPullParser in)75 private static void doLargeValues(TypedXmlSerializer out, TypedXmlPullParser in) 76 throws Exception { 77 final char[] chars = new char[65_534]; 78 Arrays.fill(chars, '!'); 79 80 final String string = new String(chars); 81 final byte[] bytes = string.getBytes(); 82 assertEquals(chars.length, bytes.length); 83 84 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 85 out.setOutput(os, StandardCharsets.UTF_8.name()); 86 out.startTag(null, "tag"); 87 out.attribute(null, "string", string); 88 out.attributeBytesBase64(null, "bytes", bytes); 89 out.endTag(null, "tag"); 90 out.flush(); 91 92 final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); 93 in.setInput(is, StandardCharsets.UTF_8.name()); 94 assertNext(in, START_TAG, "tag"); 95 assertEquals(2, in.getAttributeCount()); 96 assertEquals(string, in.getAttributeValue(null, "string")); 97 assertArrayEquals(bytes, in.getAttributeBytesBase64(null, "bytes")); 98 } 99 100 @Test testPersistableBundle_Normal()101 public void testPersistableBundle_Normal() throws Exception { 102 doPersistableBundle(XmlUtils.makeTyped(Xml.newSerializer()), 103 XmlUtils.makeTyped(Xml.newPullParser())); 104 } 105 106 @Test testPersistableBundle_Fast()107 public void testPersistableBundle_Fast() throws Exception { 108 doPersistableBundle(Xml.newFastSerializer(), 109 Xml.newFastPullParser()); 110 } 111 112 @Test testPersistableBundle_FastIndenting()113 public void testPersistableBundle_FastIndenting() throws Exception { 114 final TypedXmlSerializer out = Xml.newFastSerializer(); 115 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 116 doPersistableBundle(out, 117 Xml.newFastPullParser()); 118 } 119 120 @Test testPersistableBundle_Binary()121 public void testPersistableBundle_Binary() throws Exception { 122 doPersistableBundle(Xml.newBinarySerializer(), 123 Xml.newBinaryPullParser()); 124 } 125 126 /** 127 * Verify that a complex {@link PersistableBundle} can be serialized out and 128 * then parsed in with the original structure intact. 129 */ doPersistableBundle(TypedXmlSerializer out, TypedXmlPullParser in)130 private static void doPersistableBundle(TypedXmlSerializer out, TypedXmlPullParser in) 131 throws Exception { 132 final PersistableBundle expected = buildPersistableBundle(); 133 final byte[] raw = doPersistableBundleWrite(out, expected); 134 135 // Yes, this string-based check is fragile, but kindofEquals() is broken 136 // when working with nested objects and arrays 137 final PersistableBundle actual = doPersistableBundleRead(in, raw); 138 assertEquals(expected.toString(), actual.toString()); 139 } 140 buildPersistableBundle()141 static PersistableBundle buildPersistableBundle() { 142 final PersistableBundle outer = new PersistableBundle(); 143 144 outer.putBoolean("boolean", true); 145 outer.putInt("int", 42); 146 outer.putLong("long", 43L); 147 outer.putDouble("double", 44d); 148 outer.putString("string", "com.example <and></and> & more"); 149 150 outer.putBooleanArray("boolean[]", new boolean[] { true, false, true }); 151 outer.putIntArray("int[]", new int[] { 42, 43, 44 }); 152 outer.putLongArray("long[]", new long[] { 43L, 44L, 45L }); 153 outer.putDoubleArray("double[]", new double[] { 43d, 44d, 45d }); 154 outer.putStringArray("string[]", new String[] { "foo", "bar", "baz" }); 155 156 outer.putString("nullString", null); 157 outer.putObject("nullObject", null); 158 outer.putIntArray("nullArray", null); 159 160 final PersistableBundle nested = new PersistableBundle(); 161 nested.putString("nested_key", "nested_value"); 162 outer.putPersistableBundle("nested", nested); 163 164 return outer; 165 } 166 doPersistableBundleWrite(TypedXmlSerializer out, PersistableBundle bundle)167 static byte[] doPersistableBundleWrite(TypedXmlSerializer out, PersistableBundle bundle) 168 throws Exception { 169 // We purposefully omit START/END_DOCUMENT events here to verify correct 170 // behavior of what PersistableBundle does internally 171 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 172 out.setOutput(os, StandardCharsets.UTF_8.name()); 173 out.startTag(null, "bundle"); 174 bundle.saveToXml(out); 175 out.endTag(null, "bundle"); 176 out.flush(); 177 return os.toByteArray(); 178 } 179 doPersistableBundleRead(TypedXmlPullParser in, byte[] raw)180 static PersistableBundle doPersistableBundleRead(TypedXmlPullParser in, byte[] raw) 181 throws Exception { 182 final ByteArrayInputStream is = new ByteArrayInputStream(raw); 183 in.setInput(is, StandardCharsets.UTF_8.name()); 184 in.next(); 185 return PersistableBundle.restoreFromXml(in); 186 } 187 188 @Test testVerify_Normal()189 public void testVerify_Normal() throws Exception { 190 doVerify(XmlUtils.makeTyped(Xml.newSerializer()), 191 XmlUtils.makeTyped(Xml.newPullParser())); 192 } 193 194 @Test testVerify_Fast()195 public void testVerify_Fast() throws Exception { 196 doVerify(Xml.newFastSerializer(), 197 Xml.newFastPullParser()); 198 } 199 200 @Test testVerify_FastIndenting()201 public void testVerify_FastIndenting() throws Exception { 202 final TypedXmlSerializer out = Xml.newFastSerializer(); 203 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 204 doVerify(out, 205 Xml.newFastPullParser()); 206 } 207 208 @Test testVerify_Binary()209 public void testVerify_Binary() throws Exception { 210 doVerify(Xml.newBinarySerializer(), 211 Xml.newBinaryPullParser()); 212 } 213 214 /** 215 * Verify that example test data is correctly serialized and parsed 216 * end-to-end using the given objects. 217 */ doVerify(TypedXmlSerializer out, TypedXmlPullParser in)218 private static void doVerify(TypedXmlSerializer out, TypedXmlPullParser in) throws Exception { 219 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 220 out.setOutput(os, StandardCharsets.UTF_8.name()); 221 doVerifyWrite(out); 222 out.flush(); 223 224 final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); 225 in.setInput(is, StandardCharsets.UTF_8.name()); 226 doVerifyRead(in); 227 } 228 229 private static final String TEST_STRING = "com☃example��typical☃package��name"; 230 private static final String TEST_STRING_EMPTY = ""; 231 private static final byte[] TEST_BYTES = new byte[] { 0, 1, 2, 3, 4, 3, 2, 1, 0 }; 232 private static final byte[] TEST_BYTES_EMPTY = new byte[0]; 233 doVerifyWrite(TypedXmlSerializer out)234 static void doVerifyWrite(TypedXmlSerializer out) throws Exception { 235 out.startDocument(StandardCharsets.UTF_8.name(), true); 236 out.startTag(null, "one"); 237 { 238 out.startTag(null, "two"); 239 { 240 out.attribute(null, "string", TEST_STRING); 241 out.attribute(null, "stringEmpty", TEST_STRING_EMPTY); 242 out.attributeBytesHex(null, "bytesHex", TEST_BYTES); 243 out.attributeBytesHex(null, "bytesHexEmpty", TEST_BYTES_EMPTY); 244 out.attributeBytesBase64(null, "bytesBase64", TEST_BYTES); 245 out.attributeBytesBase64(null, "bytesBase64Empty", TEST_BYTES_EMPTY); 246 out.attributeInt(null, "int", 43); 247 out.attributeIntHex(null, "intHex", 44); 248 out.attributeLong(null, "long", 45L); 249 out.attributeLongHex(null, "longHex", 46L); 250 out.attributeFloat(null, "float", 47f); 251 out.attributeDouble(null, "double", 48d); 252 out.attributeBoolean(null, "boolean", true); 253 out.attribute(null, "stringNumber", "49"); 254 } 255 out.endTag(null, "two"); 256 257 out.startTag(null, "three"); 258 { 259 out.text("foo"); 260 out.startTag(null, "four"); 261 { 262 } 263 out.endTag(null, "four"); 264 out.text("bar"); 265 out.text("baz"); 266 } 267 out.endTag(null, "three"); 268 } 269 out.endTag(null, "one"); 270 out.endDocument(); 271 } 272 doVerifyRead(TypedXmlPullParser in)273 static void doVerifyRead(TypedXmlPullParser in) throws Exception { 274 assertEquals(START_DOCUMENT, in.getEventType()); 275 assertDepth(in, 0); 276 assertNext(in, START_TAG, "one"); 277 assertDepth(in, 1); 278 { 279 assertNext(in, START_TAG, "two"); 280 assertDepth(in, 2); 281 { 282 assertEquals(14, in.getAttributeCount()); 283 assertEquals(TEST_STRING, 284 in.getAttributeValue(null, "string")); 285 assertEquals(TEST_STRING_EMPTY, 286 in.getAttributeValue(null, "stringEmpty")); 287 assertArrayEquals(TEST_BYTES, 288 in.getAttributeBytesHex(null, "bytesHex")); 289 assertArrayEquals(TEST_BYTES_EMPTY, 290 in.getAttributeBytesHex(null, "bytesHexEmpty")); 291 assertArrayEquals(TEST_BYTES, 292 in.getAttributeBytesBase64(null, "bytesBase64")); 293 assertArrayEquals(TEST_BYTES_EMPTY, 294 in.getAttributeBytesBase64(null, "bytesBase64Empty")); 295 296 assertEquals(43, in.getAttributeInt(null, "int")); 297 assertEquals(44, in.getAttributeIntHex(null, "intHex")); 298 assertEquals(45L, in.getAttributeLong(null, "long")); 299 assertEquals(46L, in.getAttributeLongHex(null, "longHex")); 300 assertEquals(47f, in.getAttributeFloat(null, "float"), 0.01); 301 assertEquals(48d, in.getAttributeDouble(null, "double"), 0.01); 302 assertEquals(true, in.getAttributeBoolean(null, "boolean")); 303 304 // Also verify that typed values are available as strings 305 assertEquals("000102030403020100", in.getAttributeValue(null, "bytesHex")); 306 assertEquals("AAECAwQDAgEA", in.getAttributeValue(null, "bytesBase64")); 307 assertEquals("43", in.getAttributeValue(null, "int")); 308 assertEquals("2c", in.getAttributeValue(null, "intHex")); 309 assertEquals("45", in.getAttributeValue(null, "long")); 310 assertEquals("2e", in.getAttributeValue(null, "longHex")); 311 assertEquals("true", in.getAttributeValue(null, "boolean")); 312 313 // And that raw strings can be parsed too 314 assertEquals("49", in.getAttributeValue(null, "stringNumber")); 315 assertEquals(49, in.getAttributeInt(null, "stringNumber")); 316 } 317 assertNext(in, END_TAG, "two"); 318 assertDepth(in, 2); 319 320 assertNext(in, START_TAG, "three"); 321 assertDepth(in, 2); 322 { 323 assertNext(in, TEXT, null); 324 assertDepth(in, 2); 325 assertEquals("foo", in.getText().trim()); 326 assertNext(in, START_TAG, "four"); 327 assertDepth(in, 3); 328 { 329 assertEquals(0, in.getAttributeCount()); 330 } 331 assertNext(in, END_TAG, "four"); 332 assertDepth(in, 3); 333 assertNext(in, TEXT, null); 334 assertDepth(in, 2); 335 assertEquals("barbaz", in.getText().trim()); 336 } 337 assertNext(in, END_TAG, "three"); 338 assertDepth(in, 2); 339 } 340 assertNext(in, END_TAG, "one"); 341 assertDepth(in, 1); 342 assertNext(in, END_DOCUMENT, null); 343 assertDepth(in, 0); 344 } 345 assertNext(TypedXmlPullParser in, int token, String name)346 static void assertNext(TypedXmlPullParser in, int token, String name) throws Exception { 347 // We're willing to skip over empty text regions, which some 348 // serializers emit transparently 349 int event; 350 while ((event = in.next()) == TEXT && in.getText().trim().length() == 0) { 351 } 352 assertEquals("next", token, event); 353 assertEquals("getEventType", token, in.getEventType()); 354 assertEquals("getName", name, in.getName()); 355 } 356 assertDepth(TypedXmlPullParser in, int depth)357 static void assertDepth(TypedXmlPullParser in, int depth) throws Exception { 358 assertEquals("getDepth", depth, in.getDepth()); 359 } 360 } 361