/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php * * 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.ide.eclipse.adt.internal.sdk; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import java.io.InputStream; import java.io.StringReader; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Validator; import junit.framework.TestCase; /** * Tests local validation of a Layout-Devices sample XMLs using an XML Schema validator. */ public class TestLayoutDevicesXsd extends TestCase { @Override protected void setUp() throws Exception { super.setUp(); } @Override protected void tearDown() throws Exception { super.tearDown(); } /** * A SAX error handler that captures the errors and warnings. * This allows us to capture *all* errors and just not get an exception on the first one. */ private static class CaptureErrorHandler implements ErrorHandler { private String mWarnings = ""; private String mErrors = ""; @SuppressWarnings("unused") public String getErrors() { return mErrors; } @SuppressWarnings("unused") public String getWarnings() { return mWarnings; } /** * Verifies if the handler captures some errors or warnings. * Prints them on stderr. * Also fails the unit test if any error was generated. */ public void verify() { if (mWarnings.length() > 0) { System.err.println(mWarnings); } if (mErrors.length() > 0) { System.err.println(mErrors); fail(mErrors); } } /** * @throws SAXException */ public void error(SAXParseException ex) throws SAXException { mErrors += "Error: " + ex.getMessage() + "\n"; } /** * @throws SAXException */ public void fatalError(SAXParseException ex) throws SAXException { mErrors += "Fatal Error: " + ex.getMessage() + "\n"; } /** * @throws SAXException */ public void warning(SAXParseException ex) throws SAXException { mWarnings += "Warning: " + ex.getMessage() + "\n"; } } // --- Helpers ------------ /** An helper that validates a string against an expected regexp. */ private void assertRegex(String expectedRegexp, String actualString) { assertNotNull(actualString); assertTrue( String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n", expectedRegexp, actualString), actualString.matches(expectedRegexp)); } public void checkFailure(String document, String regexp) throws Exception { Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception Validator validator = LayoutDevicesXsd.getValidator(null); try { validator.validate(source); } catch (SAXParseException e) { // We expect a parse expression referring to this grammar rule assertRegex(regexp, e.getMessage()); return; } // If we get here, the validator has not failed as we expected it to. fail(); } public void checkSuccess(String document) throws Exception { Source source = new StreamSource(new StringReader(document)); CaptureErrorHandler handler = new CaptureErrorHandler(); Validator validator = LayoutDevicesXsd.getValidator(null); validator.validate(source); handler.verify(); } // --- Tests ------------ /** Validate a valid sample using an InputStream */ public void testValidateLocalRepositoryFile() throws Exception { InputStream xmlStream = TestLayoutDevicesXsd.class.getResourceAsStream("config_sample.xml"); Source source = new StreamSource(xmlStream); CaptureErrorHandler handler = new CaptureErrorHandler(); Validator validator = LayoutDevicesXsd.getValidator(handler); validator.validate(source); handler.verify(); } /** A document should at least have a root to be valid */ public void testEmptyXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>", // expected failure "Premature end of file.*"); } /** A document with an unknown element. */ public void testUnknownContentXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:unknown />" + "</d:layout-devices>", // expected failure "cvc-complex-type.2.4.a: Invalid content was found.*"); } /** A document with an missing attribute in a device element. */ public void testIncompleteContentXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device />" + "</d:layout-devices>", // expected failure "cvc-complex-type.4: Attribute 'name' must appear on element 'd:device'."); } /** A document with a root element containing no device element is valid. */ public void testEmptyRootXml() throws Exception { checkSuccess( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" />"); } /** A document with an empty device element is not valid. */ public void testEmptyDeviceXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\"/>" + "</d:layout-devices>", // expected failure "cvc-complex-type.2.4.b: The content of element 'd:device' is not complete.*"); } /** A document with two default elements in a device element is not valid. */ public void testTwoDefaultsXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:default />" + " <d:default />" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*"); } /** The default elements must be defined before the config one. It's invalid if after. */ public void testDefaultConfigOrderXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:config name=\"must-be-after-default\" />" + " <d:default />" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-complex-type.2.4.a: Invalid content was found starting with element 'd:default'.*"); } /** Screen dimension cannot be 0. */ public void testScreenDimZeroXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:default>" + " <d:screen-dimension> <d:size>0</d:size> <d:size>1</d:size> </d:screen-dimension>" + " </d:default>" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-minInclusive-valid: Value '0' is not facet-valid with respect to minInclusive '1'.*"); } /** Screen dimension cannot be negative. */ public void testScreenDimNegativeXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:default>" + " <d:screen-dimension> <d:size>-5</d:size> <d:size>1</d:size> </d:screen-dimension>" + " </d:default>" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-minInclusive-valid: Value '-5' is not facet-valid with respect to minInclusive '1'.*"); } /** X/Y dpi cannot be 0. */ public void testXDpiZeroXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:default>" + " <d:xdpi>0</d:xdpi>" + " </d:default>" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-minExclusive-valid: Value '0' is not facet-valid with respect to minExclusive '0.0E1'.*"); } /** X/Y dpi cannot be negative. */ public void testXDpiNegativeXml() throws Exception { checkFailure( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:default>" + " <d:xdpi>-3.1415926538</d:xdpi>" + " </d:default>" + "</d:device>" + "</d:layout-devices>", // expected failure "cvc-minExclusive-valid: Value '-3.1415926538' is not facet-valid with respect to minExclusive '0.0E1'.*"); } /** WHitespace around token is accepted by the schema. */ public void testTokenWhitespaceXml() throws Exception { checkSuccess( // document "<?xml version=\"1.0\"?>" + "<d:layout-devices xmlns:d=\"http://schemas.android.com/sdk/android/layout-devices/1\" >" + "<d:device name=\"foo\">" + " <d:config name='foo'>" + " <d:screen-ratio> \n long \r </d:screen-ratio>" + " </d:config>" + "</d:device>" + "</d:layout-devices>"); } }