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 android.util.XmlTest.assertNext;
20 import static android.util.XmlTest.buildPersistableBundle;
21 import static android.util.XmlTest.doPersistableBundleRead;
22 import static android.util.XmlTest.doPersistableBundleWrite;
23 import static android.util.XmlTest.doVerifyRead;
24 import static android.util.XmlTest.doVerifyWrite;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.xmlpull.v1.XmlPullParser.START_TAG;
28 
29 import android.os.PersistableBundle;
30 
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import com.android.modules.utils.TypedXmlPullParser;
34 import com.android.modules.utils.TypedXmlSerializer;
35 
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileOutputStream;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.nio.charset.StandardCharsets;
47 
48 @RunWith(AndroidJUnit4.class)
49 public class BinaryXmlTest {
50     /**
51      * Verify that we can write and read large numbers of interned
52      * {@link String} values.
53      */
54     @Test
testLargeInterned_Binary()55     public void testLargeInterned_Binary() throws Exception {
56         // We're okay with the tag itself being interned
57         final int count = (1 << 16) - 2;
58 
59         final TypedXmlSerializer out = Xml.newBinarySerializer();
60         final ByteArrayOutputStream os = new ByteArrayOutputStream();
61         out.setOutput(os, StandardCharsets.UTF_8.name());
62         out.startTag(null, "tag");
63         for (int i = 0; i < count; i++) {
64             out.attribute(null, "name" + i, "value");
65         }
66         out.endTag(null, "tag");
67         out.flush();
68 
69         final TypedXmlPullParser in = Xml.newBinaryPullParser();
70         final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
71         in.setInput(is, StandardCharsets.UTF_8.name());
72         assertNext(in, START_TAG, "tag");
73         assertEquals(count, in.getAttributeCount());
74     }
75 
76     @Test
testTranscode_FastToBinary()77     public void testTranscode_FastToBinary() throws Exception {
78         doTranscode(Xml.newFastSerializer(), Xml.newFastPullParser(),
79                 Xml.newBinarySerializer(), Xml.newBinaryPullParser());
80     }
81 
82     @Test
testTranscode_BinaryToFast()83     public void testTranscode_BinaryToFast() throws Exception {
84         doTranscode(Xml.newBinarySerializer(), Xml.newBinaryPullParser(),
85                 Xml.newFastSerializer(), Xml.newFastPullParser());
86     }
87 
88     /**
89      * Verify that a complex {@link PersistableBundle} can be transcoded using
90      * the two given formats with the original structure intact.
91      */
doTranscode(TypedXmlSerializer firstOut, TypedXmlPullParser firstIn, TypedXmlSerializer secondOut, TypedXmlPullParser secondIn)92     private static void doTranscode(TypedXmlSerializer firstOut, TypedXmlPullParser firstIn,
93             TypedXmlSerializer secondOut, TypedXmlPullParser secondIn) throws Exception {
94         final PersistableBundle expected = buildPersistableBundle();
95         final byte[] firstRaw = doPersistableBundleWrite(firstOut, expected);
96 
97         // Perform actual transcoding between the two formats
98         final ByteArrayInputStream is = new ByteArrayInputStream(firstRaw);
99         firstIn.setInput(is, StandardCharsets.UTF_8.name());
100         final ByteArrayOutputStream os = new ByteArrayOutputStream();
101         secondOut.setOutput(os, StandardCharsets.UTF_8.name());
102         Xml.copy(firstIn, secondOut);
103 
104         // Yes, this string-based check is fragile, but kindofEquals() is broken
105         // when working with nested objects and arrays
106         final PersistableBundle actual = doPersistableBundleRead(secondIn, os.toByteArray());
107         assertEquals(expected.toString(), actual.toString());
108     }
109 
110     @Test
testResolve_File()111     public void testResolve_File() throws Exception {
112         {
113             final File file = File.createTempFile("fast", ".xml");
114             try (OutputStream os = new FileOutputStream(file)) {
115                 TypedXmlSerializer xml = Xml.newFastSerializer();
116                 xml.setOutput(os, StandardCharsets.UTF_8.name());
117                 doVerifyWrite(xml);
118             }
119             try (InputStream is = new FileInputStream(file)) {
120                 doVerifyRead(Xml.resolvePullParser(is));
121             }
122         }
123         {
124             final File file = File.createTempFile("binary", ".xml");
125             try (OutputStream os = new FileOutputStream(file)) {
126                 TypedXmlSerializer xml = Xml.newBinarySerializer();
127                 xml.setOutput(os, StandardCharsets.UTF_8.name());
128                 doVerifyWrite(xml);
129             }
130             try (InputStream is = new FileInputStream(file)) {
131                 doVerifyRead(Xml.resolvePullParser(is));
132             }
133         }
134     }
135 
136     @Test
testResolve_Memory()137     public void testResolve_Memory() throws Exception {
138         {
139             final byte[] data;
140             try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
141                 TypedXmlSerializer xml = Xml.newFastSerializer();
142                 xml.setOutput(os, StandardCharsets.UTF_8.name());
143                 doVerifyWrite(xml);
144                 data = os.toByteArray();
145             }
146             try (InputStream is = new ByteArrayInputStream(data) {
147                 @Override
148                 public boolean markSupported() {
149                     return false;
150                 }
151             }) {
152                 doVerifyRead(Xml.resolvePullParser(is));
153             }
154         }
155         {
156             final byte[] data;
157             try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
158                 TypedXmlSerializer xml = Xml.newBinarySerializer();
159                 xml.setOutput(os, StandardCharsets.UTF_8.name());
160                 doVerifyWrite(xml);
161                 data = os.toByteArray();
162             }
163             try (InputStream is = new ByteArrayInputStream(data) {
164                 @Override
165                 public boolean markSupported() {
166                     return false;
167                 }
168             }) {
169                 doVerifyRead(Xml.resolvePullParser(is));
170             }
171         }
172     }
173 }
174