/* * Copyright (C) 2012 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 com.android.dvlib; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import java.util.Stack; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import junit.framework.TestCase; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; public class DeviceSchemaTest extends TestCase { //---- actual tests ----- public void testValidXml_v1() throws Exception { InputStream xml = DeviceSchemaTest.class.getResourceAsStream("devices.xml"); assertTrue(xml.markSupported()); xml.mark(500000); // set mark to beginning of stream // Check schema version assertEquals(1, DeviceSchema.getXmlSchemaVersion(xml)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean result = DeviceSchema.validate(xml, baos, null); String output = baos.toString().trim(); assertTrue( String.format("Validation Assertion Failed, XML failed to validate when it was expected to pass\n%s\n", output), result); assertTrue(String.format( "Regex Assertion Failed\nExpected No Output\nActual: %s\n", baos.toString().trim()), baos.toString().trim().isEmpty()); } public void testValidXml_v2() throws Exception { InputStream xml = DeviceSchemaTest.class.getResourceAsStream("devices_v2.xml"); assertTrue(xml.markSupported()); xml.mark(500000); // set mark to beginning of stream // Check schema version assertEquals(2, DeviceSchema.getXmlSchemaVersion(xml)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean result = DeviceSchema.validate(xml, baos, null); String output = baos.toString().trim(); assertTrue( String.format("Validation Assertion Failed, XML failed to validate when it was expected to pass\n%s\n", output), result); assertTrue(String.format( "Regex Assertion Failed\nExpected No Output\nActual: %s\n", baos.toString().trim()), baos.toString().trim().isEmpty()); } public void testNoHardware() throws Exception { String regex = "Error: cvc-complex-type.2.4.a: Invalid content was found starting with " + "element 'd:software'.*"; checkFailure("devices_no_hardware.xml", regex); } public void testNoSoftware() throws Exception { String regex = "Error: cvc-complex-type.2.4.a: Invalid content was found starting with " + "element 'd:state'.*"; checkFailure("devices_no_software.xml", regex); } public void testNoDefault() throws Exception { String regex = "Error: No default state for device Galaxy Nexus.*"; checkFailure("devices_no_default.xml", regex); } public void testTooManyDefaults() throws Exception { String regex = "Error: More than one default state for device Galaxy Nexus.*"; checkFailure("devices_too_many_defaults.xml", regex); } public void testNoStates() throws Exception { String regex = "Error: cvc-complex-type.2.4.b: The content of element 'd:device' is not " + "complete.*\nError: No default state for device Galaxy Nexus.*"; checkFailure("devices_no_states.xml", regex); } public void testBadMechanism() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_MECHANISM, "fanger"); checkFailure(replacements, "Error: cvc-enumeration-valid: Value 'fanger' is not " + "facet-valid.*\nError: cvc-type.3.1.3: The value 'fanger' of element " + "'d:mechanism' is not valid.*"); } public void testNegativeXdpi() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_XDPI, "-1.0"); checkFailure(replacements, "Error: cvc-minInclusive-valid: Value '-1.0'.*\n" + "Error: cvc-type.3.1.3: The value '-1.0' of element 'd:xdpi' is not valid.*"); } public void testNegativeYdpi() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_YDPI, "-1"); checkFailure(replacements, "Error: cvc-minInclusive-valid: Value '-1'.*\n" + "Error: cvc-type.3.1.3: The value '-1' of element 'd:ydpi' is not valid.*"); } public void testNegativeDiagonalLength() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_DIAGONAL_LENGTH, "-1.0"); checkFailure(replacements, "Error: cvc-minInclusive-valid: Value '-1.0'.*\n" + "Error: cvc-type.3.1.3: The value '-1.0' of element 'd:diagonal-length'.*"); } public void testInvalidOpenGLVersion() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_GL_VERSION, "2"); checkFailure(replacements, "Error: cvc-pattern-valid: Value '2' is not facet-valid.*\n" + "Error: cvc-type.3.1.3: The value '2' of element 'd:gl-version' is not valid.*"); } public void testEmptyOpenGLExtensions() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_GL_EXTENSIONS, ""); checkSuccess(replacements); } public void testEmptySensors() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_SENSORS, ""); checkSuccess(replacements); } public void testEmptyNetworking() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_NETWORKING, ""); checkSuccess(replacements); } public void testEmptyCpu() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_CPU, ""); checkFailure(replacements, "Error: cvc-minLength-valid: Value '' with length = '0'.*\n" + "Error: cvc-type.3.1.3: The value '' of element 'd:cpu' is not valid.*"); } public void testEmptyGpu() throws Exception { Map<String, String> replacements = new HashMap<String, String>(); replacements.put(DeviceSchema.NODE_GPU, ""); checkFailure(replacements, "Error: cvc-minLength-valid: Value '' with length = '0'.*\n" + "Error: cvc-type.3.1.3: The value '' of element 'd:gpu' is not valid.*"); } //---- helper methods ----- private void checkFailure(Map<String, String> replacements, String regex) throws Exception { // Generate XML stream with replacements InputStream xmlStream = getReplacedStream(replacements); assertTrue(xmlStream.markSupported()); xmlStream.mark(500000); // set mark to beginning of stream ByteArrayOutputStream baos = new ByteArrayOutputStream(); assertFalse( "Validation Assertion Failed, XML failed to validate when it was expected to pass\n", DeviceSchema.validate(xmlStream, baos, null)); String actual = baos.toString().trim(); actual = actual.replace("\r\n", "\n"); // Fix Windows CRLF assertTrue( String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, actual), actual.matches(regex)); } private void checkFailure(String resource, String regex) throws Exception { InputStream xml = DeviceSchemaTest.class.getResourceAsStream(resource); assertTrue(xml.markSupported()); xml.mark(500000); // set mark to beginning of stream ByteArrayOutputStream baos = new ByteArrayOutputStream(); assertFalse("Validation Assertion Failed, XML validated when it was expected to fail\n", DeviceSchema.validate(xml, baos, null)); String actual = baos.toString().trim(); actual = actual.replace("\r\n", "\n"); // Fix Windows CRLF assertTrue( String.format("Regex Assertion Failed:\nExpected: %s\nActual: %s\n", regex, actual), actual.matches(regex)); } private void checkSuccess(Map<String, String> replacements) throws Exception { InputStream xmlStream = getReplacedStream(replacements); assertTrue(xmlStream.markSupported()); xmlStream.mark(500000); // set mark to beginning of stream ByteArrayOutputStream baos = new ByteArrayOutputStream(); assertTrue(DeviceSchema.validate(xmlStream, baos, null)); assertTrue(baos.toString().trim().matches("")); } public static InputStream getReplacedStream(Map<String, String> replacements) throws Exception { InputStream xml = DeviceSchema.class.getResourceAsStream("devices_minimal.xml"); SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); SAXParser parser = factory.newSAXParser(); ReplacementHandler replacer = new ReplacementHandler(replacements); parser.parse(xml, replacer); Document doc = replacer.getGeneratedDocument(); Transformer tf = TransformerFactory.newInstance().newTransformer(); // Add indents so we're closer to user generated output tf.setOutputProperty(OutputKeys.INDENT, "yes"); DOMSource source = new DOMSource(doc); StringWriter out = new StringWriter(); StreamResult result = new StreamResult(out); tf.transform(source, result); return new ByteArrayInputStream(out.toString().getBytes("UTF-8")); } /** * Reads in a valid devices XML file and if an element tag is in the * replacements map, it replaces its text content with the corresponding * value. Note this has no concept of namespaces or hierarchy, so it will * replace the contents any and all elements with the specified tag name. */ private static class ReplacementHandler extends DefaultHandler { private Element mCurrElement = null; private Document mDocument; private final Stack<Element> mElementStack = new Stack<Element>(); private final Map<String, String> mPrefixes = new HashMap<String, String>(); private final Map<String, String> mReplacements; private final StringBuilder mStringAccumulator = new StringBuilder(); public ReplacementHandler(Map<String, String> replacements) { mReplacements = replacements; } @Override public void startDocument() { try { mDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException e) { fail(e.getMessage()); } } @Override public void startElement(String uri, String localName, String name, Attributes attributes) { Element element = mDocument.createElement(name); for (int i = 0; i < attributes.getLength(); i++) { element.setAttribute(attributes.getQName(i), attributes.getValue(i)); } for (String key : mPrefixes.keySet()) { element.setAttribute(XMLConstants.XMLNS_ATTRIBUTE + ":" + key, mPrefixes.get(key)); } mPrefixes.clear(); if (mCurrElement != null) { mElementStack.push(mCurrElement); } mCurrElement = element; } @Override public void startPrefixMapping(String prefix, String uri) throws SAXException { mPrefixes.put(prefix, uri); } @Override public void characters(char[] ch, int start, int length) { mStringAccumulator.append(ch, start, length); } @Override public void endElement(String uri, String localName, String name) throws SAXException { if (mReplacements.containsKey(localName)) { mCurrElement.appendChild(mDocument.createTextNode(mReplacements.get(localName))); } else { String content = mStringAccumulator.toString().trim(); if (!content.isEmpty()) { mCurrElement.appendChild(mDocument.createTextNode(content)); } } if (mElementStack.empty()) { mDocument.appendChild(mCurrElement); mCurrElement = null; } else { Element parent = mElementStack.pop(); parent.appendChild(mCurrElement); mCurrElement = parent; } mStringAccumulator.setLength(0); } @Override public void error(SAXParseException e) { fail(e.getMessage()); } @Override public void fatalError(SAXParseException e) { fail(e.getMessage()); } public Document getGeneratedDocument() { return mDocument; } } }