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