• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> &amp; 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