blob: 91ebc2a1e1d538e7918728fa20513f497849178c [file] [log] [blame]
/*
* 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 android.util;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static org.xmlpull.v1.XmlPullParser.TEXT;
import android.os.PersistableBundle;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class XmlTest {
@Test
public void testLargeValues_Normal() throws Exception {
doLargeValues(XmlUtils.makeTyped(Xml.newSerializer()),
XmlUtils.makeTyped(Xml.newPullParser()));
}
@Test
public void testLargeValues_Fast() throws Exception {
doLargeValues(Xml.newFastSerializer(),
Xml.newFastPullParser());
}
@Test
public void testLargeValues_FastIndenting() throws Exception {
final TypedXmlSerializer out = Xml.newFastSerializer();
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
doLargeValues(out,
Xml.newFastPullParser());
}
@Test
public void testLargeValues_Binary() throws Exception {
doLargeValues(Xml.newBinarySerializer(),
Xml.newBinaryPullParser());
}
/**
* Verify that we can write and read large {@link String} and {@code byte[]}
* without issues.
*/
private static void doLargeValues(TypedXmlSerializer out, TypedXmlPullParser in)
throws Exception {
final char[] chars = new char[65_534];
Arrays.fill(chars, '!');
final String string = new String(chars);
final byte[] bytes = string.getBytes();
assertEquals(chars.length, bytes.length);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
out.setOutput(os, StandardCharsets.UTF_8.name());
out.startTag(null, "tag");
out.attribute(null, "string", string);
out.attributeBytesBase64(null, "bytes", bytes);
out.endTag(null, "tag");
out.flush();
final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
in.setInput(is, StandardCharsets.UTF_8.name());
assertNext(in, START_TAG, "tag");
assertEquals(2, in.getAttributeCount());
assertEquals(string, in.getAttributeValue(null, "string"));
assertArrayEquals(bytes, in.getAttributeBytesBase64(null, "bytes"));
}
@Test
public void testPersistableBundle_Normal() throws Exception {
doPersistableBundle(XmlUtils.makeTyped(Xml.newSerializer()),
XmlUtils.makeTyped(Xml.newPullParser()));
}
@Test
public void testPersistableBundle_Fast() throws Exception {
doPersistableBundle(Xml.newFastSerializer(),
Xml.newFastPullParser());
}
@Test
public void testPersistableBundle_FastIndenting() throws Exception {
final TypedXmlSerializer out = Xml.newFastSerializer();
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
doPersistableBundle(out,
Xml.newFastPullParser());
}
@Test
public void testPersistableBundle_Binary() throws Exception {
doPersistableBundle(Xml.newBinarySerializer(),
Xml.newBinaryPullParser());
}
/**
* Verify that a complex {@link PersistableBundle} can be serialized out and
* then parsed in with the original structure intact.
*/
private static void doPersistableBundle(TypedXmlSerializer out, TypedXmlPullParser in)
throws Exception {
final PersistableBundle expected = buildPersistableBundle();
final byte[] raw = doPersistableBundleWrite(out, expected);
// Yes, this string-based check is fragile, but kindofEquals() is broken
// when working with nested objects and arrays
final PersistableBundle actual = doPersistableBundleRead(in, raw);
assertEquals(expected.toString(), actual.toString());
}
static PersistableBundle buildPersistableBundle() {
final PersistableBundle outer = new PersistableBundle();
outer.putBoolean("boolean", true);
outer.putInt("int", 42);
outer.putLong("long", 43L);
outer.putDouble("double", 44d);
outer.putString("string", "com.example <and></and> &amp; more");
outer.putBooleanArray("boolean[]", new boolean[] { true, false, true });
outer.putIntArray("int[]", new int[] { 42, 43, 44 });
outer.putLongArray("long[]", new long[] { 43L, 44L, 45L });
outer.putDoubleArray("double[]", new double[] { 43d, 44d, 45d });
outer.putStringArray("string[]", new String[] { "foo", "bar", "baz" });
outer.putString("nullString", null);
outer.putObject("nullObject", null);
outer.putIntArray("nullArray", null);
final PersistableBundle nested = new PersistableBundle();
nested.putString("nested_key", "nested_value");
outer.putPersistableBundle("nested", nested);
return outer;
}
static byte[] doPersistableBundleWrite(TypedXmlSerializer out, PersistableBundle bundle)
throws Exception {
// We purposefully omit START/END_DOCUMENT events here to verify correct
// behavior of what PersistableBundle does internally
final ByteArrayOutputStream os = new ByteArrayOutputStream();
out.setOutput(os, StandardCharsets.UTF_8.name());
out.startTag(null, "bundle");
bundle.saveToXml(out);
out.endTag(null, "bundle");
out.flush();
return os.toByteArray();
}
static PersistableBundle doPersistableBundleRead(TypedXmlPullParser in, byte[] raw)
throws Exception {
final ByteArrayInputStream is = new ByteArrayInputStream(raw);
in.setInput(is, StandardCharsets.UTF_8.name());
in.next();
return PersistableBundle.restoreFromXml(in);
}
@Test
public void testVerify_Normal() throws Exception {
doVerify(XmlUtils.makeTyped(Xml.newSerializer()),
XmlUtils.makeTyped(Xml.newPullParser()));
}
@Test
public void testVerify_Fast() throws Exception {
doVerify(Xml.newFastSerializer(),
Xml.newFastPullParser());
}
@Test
public void testVerify_FastIndenting() throws Exception {
final TypedXmlSerializer out = Xml.newFastSerializer();
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
doVerify(out,
Xml.newFastPullParser());
}
@Test
public void testVerify_Binary() throws Exception {
doVerify(Xml.newBinarySerializer(),
Xml.newBinaryPullParser());
}
/**
* Verify that example test data is correctly serialized and parsed
* end-to-end using the given objects.
*/
private static void doVerify(TypedXmlSerializer out, TypedXmlPullParser in) throws Exception {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
out.setOutput(os, StandardCharsets.UTF_8.name());
doVerifyWrite(out);
out.flush();
final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
in.setInput(is, StandardCharsets.UTF_8.name());
doVerifyRead(in);
}
private static final String TEST_STRING = "com☃example😀typical☃package😀name";
private static final String TEST_STRING_EMPTY = "";
private static final byte[] TEST_BYTES = new byte[] { 0, 1, 2, 3, 4, 3, 2, 1, 0 };
private static final byte[] TEST_BYTES_EMPTY = new byte[0];
static void doVerifyWrite(TypedXmlSerializer out) throws Exception {
out.startDocument(StandardCharsets.UTF_8.name(), true);
out.startTag(null, "one");
{
out.startTag(null, "two");
{
out.attribute(null, "string", TEST_STRING);
out.attribute(null, "stringEmpty", TEST_STRING_EMPTY);
out.attributeBytesHex(null, "bytesHex", TEST_BYTES);
out.attributeBytesHex(null, "bytesHexEmpty", TEST_BYTES_EMPTY);
out.attributeBytesBase64(null, "bytesBase64", TEST_BYTES);
out.attributeBytesBase64(null, "bytesBase64Empty", TEST_BYTES_EMPTY);
out.attributeInt(null, "int", 43);
out.attributeIntHex(null, "intHex", 44);
out.attributeLong(null, "long", 45L);
out.attributeLongHex(null, "longHex", 46L);
out.attributeFloat(null, "float", 47f);
out.attributeDouble(null, "double", 48d);
out.attributeBoolean(null, "boolean", true);
out.attribute(null, "stringNumber", "49");
}
out.endTag(null, "two");
out.startTag(null, "three");
{
out.text("foo");
out.startTag(null, "four");
{
}
out.endTag(null, "four");
out.text("bar");
out.text("baz");
}
out.endTag(null, "three");
}
out.endTag(null, "one");
out.endDocument();
}
static void doVerifyRead(TypedXmlPullParser in) throws Exception {
assertEquals(START_DOCUMENT, in.getEventType());
assertDepth(in, 0);
assertNext(in, START_TAG, "one");
assertDepth(in, 1);
{
assertNext(in, START_TAG, "two");
assertDepth(in, 2);
{
assertEquals(14, in.getAttributeCount());
assertEquals(TEST_STRING,
in.getAttributeValue(null, "string"));
assertEquals(TEST_STRING_EMPTY,
in.getAttributeValue(null, "stringEmpty"));
assertArrayEquals(TEST_BYTES,
in.getAttributeBytesHex(null, "bytesHex"));
assertArrayEquals(TEST_BYTES_EMPTY,
in.getAttributeBytesHex(null, "bytesHexEmpty"));
assertArrayEquals(TEST_BYTES,
in.getAttributeBytesBase64(null, "bytesBase64"));
assertArrayEquals(TEST_BYTES_EMPTY,
in.getAttributeBytesBase64(null, "bytesBase64Empty"));
assertEquals(43, in.getAttributeInt(null, "int"));
assertEquals(44, in.getAttributeIntHex(null, "intHex"));
assertEquals(45L, in.getAttributeLong(null, "long"));
assertEquals(46L, in.getAttributeLongHex(null, "longHex"));
assertEquals(47f, in.getAttributeFloat(null, "float"), 0.01);
assertEquals(48d, in.getAttributeDouble(null, "double"), 0.01);
assertEquals(true, in.getAttributeBoolean(null, "boolean"));
// Also verify that typed values are available as strings
assertEquals("000102030403020100", in.getAttributeValue(null, "bytesHex"));
assertEquals("AAECAwQDAgEA", in.getAttributeValue(null, "bytesBase64"));
assertEquals("43", in.getAttributeValue(null, "int"));
assertEquals("2c", in.getAttributeValue(null, "intHex"));
assertEquals("45", in.getAttributeValue(null, "long"));
assertEquals("2e", in.getAttributeValue(null, "longHex"));
assertEquals("true", in.getAttributeValue(null, "boolean"));
// And that raw strings can be parsed too
assertEquals("49", in.getAttributeValue(null, "stringNumber"));
assertEquals(49, in.getAttributeInt(null, "stringNumber"));
}
assertNext(in, END_TAG, "two");
assertDepth(in, 2);
assertNext(in, START_TAG, "three");
assertDepth(in, 2);
{
assertNext(in, TEXT, null);
assertDepth(in, 2);
assertEquals("foo", in.getText().trim());
assertNext(in, START_TAG, "four");
assertDepth(in, 3);
{
assertEquals(0, in.getAttributeCount());
}
assertNext(in, END_TAG, "four");
assertDepth(in, 3);
assertNext(in, TEXT, null);
assertDepth(in, 2);
assertEquals("barbaz", in.getText().trim());
}
assertNext(in, END_TAG, "three");
assertDepth(in, 2);
}
assertNext(in, END_TAG, "one");
assertDepth(in, 1);
assertNext(in, END_DOCUMENT, null);
assertDepth(in, 0);
}
static void assertNext(TypedXmlPullParser in, int token, String name) throws Exception {
// We're willing to skip over empty text regions, which some
// serializers emit transparently
int event;
while ((event = in.next()) == TEXT && in.getText().trim().length() == 0) {
}
assertEquals("next", token, event);
assertEquals("getEventType", token, in.getEventType());
assertEquals("getName", name, in.getName());
}
static void assertDepth(TypedXmlPullParser in, int depth) throws Exception {
assertEquals("getDepth", depth, in.getDepth());
}
}