You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2012/09/30 13:25:30 UTC

svn commit: r1391998 - in /sis/trunk: ./ sis-utility/src/main/java/org/apache/sis/util/ sis-utility/src/test/java/org/apache/sis/internal/ sis-utility/src/test/java/org/apache/sis/internal/test/ sis-utility/src/test/java/org/apache/sis/test/

Author: desruisseaux
Date: Sun Sep 30 11:25:29 2012
New Revision: 1391998

URL: http://svn.apache.org/viewvc?rev=1391998&view=rev
Log:
Initial commit of org.apache.sis.test package, including a JUnit TestRunner taking in account the @Dependency annotation.

Added:
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/
    sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/
    sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java   (with props)
    sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java   (with props)
Modified:
    sis/trunk/NOTICE
    sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Debug.java

Modified: sis/trunk/NOTICE
URL: http://svn.apache.org/viewvc/sis/trunk/NOTICE?rev=1391998&r1=1391997&r2=1391998&view=diff
==============================================================================
--- sis/trunk/NOTICE (original)
+++ sis/trunk/NOTICE Sun Sep 30 11:25:29 2012
@@ -4,3 +4,5 @@ Copyright 2010-2012 The Apache Software 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
 
+The test suite includes software developed by
+the JUnit community (http://github.com/junit-team/junit.contrib/)

Modified: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Debug.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Debug.java?rev=1391998&r1=1391997&r2=1391998&view=diff
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Debug.java (original)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Debug.java Sun Sep 30 11:25:29 2012
@@ -28,7 +28,15 @@ import java.lang.annotation.RetentionPol
  * is defined in order to make easier to find which debugging tools are available in case of
  * problem. See the "<cite>Use</cite>" javadoc link for a list of annotated classes and methods.
  *
- * @author Martin Desruisseaux (Geomatys)
+ * {@section <code>Object.toString()</code> policy}
+ * Subclasses override the {@link Object#toString()} method for various purposes, sometime providing
+ * content targeted to the end user (e.g. {@link java.lang.CharSequence}) and sometime providing
+ * debugging information for developers only. In the Apache SIS library, <code>toString()</code>
+ * methods without <code>@Debug</code> annotation shall be understandable by the end users,
+ * while <code>toString()</code> methods with <code>@Debug</code> annotation is targeted to
+ * developers and may change in any future version.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.19)
  * @version 0.3
  * @module

Added: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java (added)
+++ sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.util;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * Annotates code containing workarounds for bugs or limitations in an external library.
+ * This is marker annotation for source code only, in order to keep trace of code to revisit
+ * when new versions of external libraries become available.
+ *
+ * {@note When only a portion of a method contains a workaround and the annotation can not be
+ * applied to that specific part, than it is applied to the whole method. Developers need to
+ * refer to code comments in order to locate the specific part.}
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.15)
+ * @version 0.3
+ * @module
+ */
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD,
+         ElementType.FIELD, ElementType.LOCAL_VARIABLE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Workaround {
+    /**
+     * A string identifying the library having a bug or limitation.
+     * Examples: {@code "JDK"}, {@code "NetCDF"}, {@code "JUnit"}, {@code "SIS"}.
+     *
+     * @return An identifier of the library having a bug or limitation.
+     */
+    String library();
+
+    /**
+     * The last library version on which the bug has been verified.
+     * The bug may have existed before, and may still exist later.
+     *
+     * @return The library version on which the bug has been observed.
+     */
+    String version();
+}

Propchange: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/main/java/org/apache/sis/util/Workaround.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.internal.test;
+
+import org.junit.*;
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests the {@link org.apache.sis.test.Assert} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+public final strictfp class AssertTest {
+    /**
+     * Tests the {@link Assert#assertMultilinesEquals(String, String)} method.
+     */
+    @Test
+    public void testAssertEqualsMultilines() {
+        assertMultilinesEquals("Line 1\nLine 2\r\nLine 3\n\rLine 5",
+                               "Line 1\rLine 2\nLine 3\n\nLine 5");
+    }
+
+    /**
+     * Tests the {@link Assert#assertSerializedEquals(Object)} method.
+     */
+    @Test
+    public void testSerialize() {
+        final String local = "Le silence éternel de ces espaces infinis m'effraie";
+        assertNotSame(local, assertSerializedEquals(local));
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/AssertTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.internal.test;
+
+import org.apache.sis.test.XMLComparator;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link XMLComparator} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.17)
+ * @version 0.3
+ * @module
+ */
+public final strictfp class XMLComparatorTest {
+    /**
+     * Tests the {@link XMLComparator#ignoredAttributes} and {@link XMLComparator#ignoredNodes}
+     * sets.
+     *
+     * @throws Exception Should never happen.
+     */
+    @Test
+    public void testIgnore() throws Exception {
+        final XMLComparator cmp = new XMLComparator(
+            "<body>\n" +
+            "  <form id=\"MyForm\">\n" +
+            "    <table cellpading=\"1\">\n" +
+            "      <tr><td>foo</td></tr>\n" +
+            "    </table>\n" +
+            "  </form>\n" +
+            "</body>",
+            "<body>\n" +
+            "  <form id=\"MyForm\">\n" +
+            "    <table cellpading=\"2\">\n" +
+            "      <tr><td>foo</td></tr>\n" +
+            "    </table>\n" +
+            "  </form>\n" +
+            "</body>");
+
+        ensureFail("Should fail because the \"cellpading\" attribute value is different.", cmp);
+
+        // Following comparison should not fail anymore.
+        cmp.ignoredAttributes.add("cellpading");
+        cmp.compare();
+
+        cmp.ignoredAttributes.clear();
+        cmp.ignoredAttributes.add("bgcolor");
+        ensureFail("The \"cellpading\" attribute should not be ignored anymore.", cmp);
+
+        // Ignore the table node, which contains the faulty attribute.
+        cmp.ignoredNodes.add("table");
+        cmp.compare();
+
+        // Ignore the form node and all its children.
+        cmp.ignoredNodes.clear();
+        cmp.ignoredNodes.add("form");
+        cmp.compare();
+    }
+
+    /**
+     * Ensures that the call to {@link XMLComparator#compare()} fails. This method is
+     * invoked in order to test that the comparator rightly detected an error that we
+     * were expected to detect.
+     *
+     * @param message The message for JUnit if the comparison does not fail.
+     * @param cmp The comparator on which to invoke {@link XMLComparator#compare()}.
+     */
+    private static void ensureFail(final String message, final XMLComparator cmp) {
+        try {
+            cmp.compare();
+        } catch (AssertionError e) {
+            return;
+        }
+        fail(message);
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/XMLComparatorTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * Self tests of the {@link org.apache.sis.test} package.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+package org.apache.sis.internal.test;

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/internal/test/package-info.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.test;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RectangularShape;
+import java.awt.image.RenderedImage;
+import javax.swing.tree.TreeNode;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.SAXException;
+
+import org.apache.sis.util.CharSequences;
+
+
+/**
+ * Assertion methods used by the SIS project in addition of the JUnit and GeoAPI assertions.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+public strictfp class Assert extends org.opengis.test.Assert {
+    /**
+     * For subclass constructor only.
+     */
+    protected Assert() {
+    }
+
+    /**
+     * Asserts that two strings are equal, ignoring the differences in EOL characters.
+     * The comparisons is performed one a line-by-line basis. For each line, leading
+     * and trailing spaces are ignored in order to make the comparison independent of
+     * indentation.
+     *
+     * @param expected The expected string.
+     * @param actual   The actual string.
+     */
+    public static void assertMultilinesEquals(final CharSequence expected, final CharSequence actual) {
+        assertArrayEquals(CharSequences.split(expected, '\n'), CharSequences.split(actual, '\n'));
+    }
+
+    /**
+     * Asserts that two strings are equal, ignoring the differences in EOL characters.
+     * The comparisons is performed one a line-by-line basis. For each line, leading
+     * and trailing spaces are ignored in order to make the comparison independent of
+     * indentation.
+     *
+     * @param message  The message to print in case of failure, or {@code null} if none.
+     * @param expected The expected string.
+     * @param actual   The actual string.
+     */
+    public static void assertMultilinesEquals(final String message, final CharSequence expected, final CharSequence actual) {
+        assertArrayEquals(message, CharSequences.split(expected, '\n'), CharSequences.split(actual, '\n'));
+    }
+
+    /**
+     * Ensures that a tree is equals to an other tree.
+     * This method invokes itself recursively for every child nodes.
+     *
+     * @param  expected The expected tree, or {@code null}.
+     * @param  actual   The tree to compare with the expected one, or {@code null}.
+     * @return The number of nodes.
+     */
+    public static int assertTreeEquals(final TreeNode expected, final TreeNode actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return 0;
+        }
+        int n = 1;
+        assertNotNull(actual);
+        assertEquals("isLeaf()",            expected.isLeaf(),            actual.isLeaf());
+        assertEquals("getAllowsChildren()", expected.getAllowsChildren(), actual.getAllowsChildren());
+        assertEquals("getChildCount()",     expected.getChildCount(),     actual.getChildCount());
+        @SuppressWarnings("unchecked") final Enumeration<? extends TreeNode> ec = expected.children();
+        @SuppressWarnings("unchecked") final Enumeration<? extends TreeNode> ac = actual  .children();
+
+        int childIndex = 0;
+        while (ec.hasMoreElements()) {
+            assertTrue("hasMoreElements()", ac.hasMoreElements());
+            final TreeNode nextExpected = ec.nextElement();
+            final TreeNode nextActual   = ac.nextElement();
+            final String message = "getChildAt(" + childIndex + ')';
+            assertSame(message, nextExpected, expected.getChildAt(childIndex));
+            assertSame(message, nextActual,   actual  .getChildAt(childIndex));
+            assertSame("getParent()", expected, nextExpected.getParent());
+            assertSame("getParent()", actual,   nextActual  .getParent());
+            assertSame("getIndex(TreeNode)", childIndex, expected.getIndex(nextExpected));
+            assertSame("getIndex(TreeNode)", childIndex, actual  .getIndex(nextActual));
+            n += assertTreeEquals(nextExpected, nextActual);
+            childIndex++;
+        }
+        assertFalse("hasMoreElements()", ac.hasMoreElements());
+        assertEquals("toString()", expected.toString(), actual.toString());
+        return n;
+    }
+
+    /**
+     * Parses two XML tree as DOM documents, and compares the nodes.
+     * The inputs given to this method can be any of the following types:
+     * <p>
+     * <ul>
+     *   <li>{@link org.w3c.dom.Node}; used directly without further processing.</li>
+     *   <li>{@link java.io.File}, {@link java.net.URL} or {@link java.net.URI}: the
+     *       stream is opened and parsed as a XML document.</li>
+     *   <li>{@link String}: The string content is parsed directly as a XML document.
+     *       Encoding <strong>must</strong> be UTF-8 (no other encoding is supported
+     *       by current implementation of this method).</li>
+     * </ul>
+     * <p>
+     * This method will ignore comments and the optional attributes given in arguments.
+     *
+     * @param  expected The expected XML document.
+     * @param  actual   The XML document to compare.
+     * @param  ignoredAttributes The fully-qualified names of attributes to ignore
+     *         (typically {@code "xmlns:*"} and {@code "xsi:schemaLocation"}).
+     *
+     * @see XMLComparator
+     */
+    public static void assertXmlEquals(final Object expected, final Object actual,
+            final String... ignoredAttributes)
+    {
+        assertXmlEquals(expected, actual, 0, ignoredAttributes);
+    }
+
+    /**
+     * Parses two XML tree as DOM documents, and compares the nodes with the given tolerance
+     * threshold for numerical values. The inputs given to this method can be any of the types
+     * documented {@linkplain #assertXmlEquals(Object, Object, String[]) above}. This method
+     * will ignore comments and the optional attributes given in arguments.
+     *
+     * @param  expected  The expected XML document.
+     * @param  actual    The XML document to compare.
+     * @param  tolerance The tolerance threshold for comparison of numerical values.
+     * @param  ignoredAttributes The fully-qualified names of attributes to ignore
+     *         (typically {@code "xmlns:*"} and {@code "xsi:schemaLocation"}).
+     *
+     * @see XMLComparator
+     */
+    public static void assertXmlEquals(final Object expected, final Object actual,
+            final double tolerance, final String... ignoredAttributes)
+    {
+        final XMLComparator comparator;
+        try {
+            comparator = new XMLComparator(expected, actual);
+        } catch (IOException e) {
+            // We don't throw directly those exceptions since failing to parse the XML file can
+            // be considered as part of test failures and the JUnit exception for such failures
+            // is AssertionError. Having no checked exception in "assert" methods allow us to
+            // declare the checked exceptions only for the library code being tested.
+            throw new AssertionError(e);
+        } catch (ParserConfigurationException e) {
+            throw new AssertionError(e);
+        } catch (SAXException e) {
+            throw new AssertionError(e);
+        }
+        comparator.tolerance = tolerance;
+        comparator.ignoreComments = true;
+        comparator.ignoredAttributes.addAll(Arrays.asList(ignoredAttributes));
+        comparator.compare();
+    }
+
+    /**
+     * Tests if the given {@code outer} shape contains the given {@code inner} rectangle.
+     * This method will also verify class consistency by invoking the {@code intersects}
+     * method, and by interchanging the arguments.
+     * <p>
+     * This method can be used for testing the {@code outer} implementation -
+     * it should not be needed for standard JDK implementations.
+     *
+     * @param outer The shape which is expected to contains the given rectangle.
+     * @param inner The rectangle which should be contained by the shape.
+     */
+    public static void assertContains(final RectangularShape outer, final Rectangle2D inner) {
+        assertTrue("outer.contains(inner)",   outer.contains  (inner));
+        assertTrue("outer.intersects(inner)", outer.intersects(inner));
+        if (outer instanceof Rectangle2D) {
+            assertTrue ("inner.intersects(outer)", inner.intersects((Rectangle2D) outer));
+            assertFalse("inner.contains(outer)",   inner.contains  ((Rectangle2D) outer));
+        }
+        assertTrue("outer.contains(centerX, centerY)",
+                outer.contains(inner.getCenterX(), inner.getCenterY()));
+    }
+
+    /**
+     * Tests if the given {@code r1} shape is disjoint with the given {@code r2} rectangle.
+     * This method will also verify class consistency by invoking the {@code contains}
+     * method, and by interchanging the arguments.
+     * <p>
+     * This method can be used for testing the {@code r1} implementation -
+     * it should not be needed for standard implementations.
+     *
+     * @param r1 The first shape to test.
+     * @param r2 The second rectangle to test.
+     */
+    public static void assertDisjoint(final RectangularShape r1, final Rectangle2D r2) {
+        assertFalse("r1.intersects(r2)", r1.intersects(r2));
+        assertFalse("r1.contains(r2)",   r1.contains(r2));
+        if (r1 instanceof Rectangle2D) {
+            assertFalse("r2.intersects(r1)", r2.intersects((Rectangle2D) r1));
+            assertFalse("r2.contains(r1)",   r2.contains  ((Rectangle2D) r1));
+        }
+        for (int i=0; i<9; i++) {
+            final double x, y;
+            switch (i % 3) {
+                case 0: x = r2.getMinX();    break;
+                case 1: x = r2.getCenterX(); break;
+                case 2: x = r2.getMaxX();    break;
+                default: throw new AssertionError(i);
+            }
+            switch (i / 3) {
+                case 0: y = r2.getMinY();    break;
+                case 1: y = r2.getCenterY(); break;
+                case 2: y = r2.getMaxY();    break;
+                default: throw new AssertionError(i);
+            }
+            assertFalse("r1.contains(" + x + ", " + y + ')', r1.contains(x, y));
+        }
+    }
+
+    /**
+     * Asserts that two rectangles have the same location and the same size.
+     *
+     * @param expected The expected rectangle.
+     * @param actual   The rectangle to compare with the expected one.
+     * @param tolx     The tolerance threshold on location along the <var>x</var> axis.
+     * @param toly     The tolerance threshold on location along the <var>y</var> axis.
+     */
+    public static void assertRectangleEquals(final RectangularShape expected,
+            final RectangularShape actual, final double tolx, final double toly)
+    {
+        assertEquals("Min X",    expected.getMinX(),    actual.getMinX(),    tolx);
+        assertEquals("Min Y",    expected.getMinY(),    actual.getMinY(),    toly);
+        assertEquals("Max X",    expected.getMaxX(),    actual.getMaxX(),    tolx);
+        assertEquals("Max Y",    expected.getMaxY(),    actual.getMaxY(),    toly);
+        assertEquals("Center X", expected.getCenterX(), actual.getCenterX(), tolx);
+        assertEquals("Center Y", expected.getCenterY(), actual.getCenterY(), toly);
+        assertEquals("Width",    expected.getWidth(),   actual.getWidth(),   tolx*2);
+        assertEquals("Height",   expected.getHeight(),  actual.getHeight(),  toly*2);
+    }
+
+    /**
+     * Asserts that two images have the same origin and the same size.
+     *
+     * @param expected The image having the expected size.
+     * @param actual   The image to compare with the expected one.
+     */
+    public static void assertBoundEquals(final RenderedImage expected, final RenderedImage actual) {
+        assertEquals("Min X",  expected.getMinX(),   actual.getMinX());
+        assertEquals("Min Y",  expected.getMinY(),   actual.getMinY());
+        assertEquals("Width",  expected.getWidth(),  actual.getWidth());
+        assertEquals("Height", expected.getHeight(), actual.getHeight());
+    }
+
+    /**
+     * Serializes the given object in memory, deserialize it and ensure that the deserialized
+     * object is equal to the original one. This method doesn't write anything to the disk.
+     * <p>
+     * If the serialization fails, then this method thrown a {@link AssertionError}
+     * as do the other JUnit assertion methods.
+     *
+     * @param  <T> The type of the object to serialize.
+     * @param  object The object to serialize.
+     * @return The deserialized object.
+     */
+    public static <T> T assertSerializedEquals(final T object) {
+        final Object deserialized;
+        try {
+            final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+            final ObjectOutputStream out = new ObjectOutputStream(buffer);
+            try {
+                out.writeObject(object);
+            } finally {
+                out.close();
+            }
+            // Now reads the object we just serialized.
+            final byte[] data = buffer.toByteArray();
+            final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(data));
+            try {
+                try {
+                    deserialized = in.readObject();
+                } catch (ClassNotFoundException e) {
+                    throw new AssertionError(e);
+                }
+            } finally {
+                in.close();
+            }
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+        // Compares with the original object and returns it.
+        @SuppressWarnings("unchecked")
+        final Class<? extends T> type = (Class<? extends T>) object.getClass();
+        assertEquals("Deserialized object not equal to the original one.", object, deserialized);
+        assertEquals("Deserialized object has a different hash code.",
+                object.hashCode(), deserialized.hashCode());
+        return type.cast(deserialized);
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Assert.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Identifies the dependencies of a test. If any of the dependencies of a test can not be
+ * executed successfully, (i.e. those tests failed, threw an error, or were skipped) then
+ * the annotated test will be skipped.
+ *
+ * @author  Stephen Connolly
+ * @since   0.3 (derived from <a href="http://github.com/junit-team/junit.contrib/tree/master/assumes">junit-team</a>)
+ * @version 0.3
+ * @module
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Dependency {
+    /**
+     * The names of test methods on which the annotated method depends.
+     *
+     * @return The names of test methods on which the annotated method depends.
+     */
+    public String[] value();
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/Dependency.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.test;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.logging.Logger;
+import java.util.logging.Handler;
+import java.util.logging.ConsoleHandler;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.io.Console;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.logging.Logging;
+
+import org.junit.AfterClass;
+import org.junit.runner.RunWith;
+
+
+/**
+ * Base class of Apache SIS tests (except the ones that extend GeoAPI tests).
+ * This base class provides some configuration commons to all subclasses.
+ *
+ * {@section Printing to the console}
+ * Subclasses should avoid printing to {@link System#out}. If nevertheless a test method
+ * produces some information considered worth to be known, consider using the following
+ * pattern instead:
+ *
+ * {@preformat java
+ *     if (out != null) {
+ *         out.println("Put here some information of particular interest.");
+ *     }
+ * }
+ *
+ * The above example uses the {@link #out} field, which will be set to a non-null value
+ * if the following option has been provided on the command line:
+ *
+ * <blockquote><code>-D{@value #VERBOSE_KEY}=true</code></blockquote>
+ *
+ * Developers can also optionally provide the following option, which is useful on Windows
+ * or MacOS platforms having a console encoding different than the system encoding:
+ *
+ * <blockquote><code>-D{@value #ENCODING_KEY}=UTF-8</code> (or any other valid encoding name)</blockquote>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.16)
+ * @version 0.3
+ * @module
+ */
+@RunWith(TestRunner.class)
+public abstract strictfp class TestCase {
+    /**
+     * The name of a system property for setting whatever the tests should provide verbose output.
+     * If this property is set to {@code true}, then the {@link #out} field will be set to a
+     * non-null value:
+     */
+    private static final String VERBOSE_KEY = "org.apache.sis.test.verbose";
+
+    /**
+     * The name of a system property for setting the encoding of test output.
+     * This property is used only if the {@link #VERBOSE_KEY} property is set
+     * to "{@code true}". If this property is not set, then the system encoding
+     * will be used.
+     */
+    private static final String ENCODING_KEY = "org.apache.sis.test.encoding";
+
+    /**
+     * If verbose outputs are enabled, the output writer where to print.
+     * Otherwise {@code null}.
+     * <p>
+     * This field will be assigned a non-null value if the {@value #VERBOSE_KEY}
+     * {@linkplain System#getProperties() system property} is set to {@code true}.
+     * The encoding will by the system-default, unless the {@value #ENCODING_KEY}
+     * system property has been set to a different value.
+     */
+    public static final PrintWriter out;
+
+    /**
+     * The buffer which is backing the {@linkplain #out} stream, or {@code null} if none.
+     */
+    private static final StringWriter buffer;
+
+    /**
+     * Sets the {@link #out} writer and its underlying {@link #buffer}.
+     */
+    static {
+        if (Boolean.getBoolean(VERBOSE_KEY)) {
+            out = new PrintWriter(buffer = new StringWriter());
+        } else {
+            buffer = null;
+            out = null;
+        }
+    }
+
+    /**
+     * Sets the encoding of the console logging handler, if an encoding has been specified.
+     * Note that we look specifically for {@link ConsoleHandler}; we do not generalize to
+     * {@link StreamHandler} because the log files may not be intended for being show in
+     * the console.
+     * <p>
+     * In case of failure to use the given encoding, we will just print a short error
+     * message and left the encoding unchanged.
+     */
+    static {
+        final String encoding = System.getProperty(ENCODING_KEY);
+        if (encoding != null) try {
+            for (Logger logger=Logger.getLogger("org.apache.sis"); logger!=null; logger=logger.getParent()) {
+                for (final Handler handler : logger.getHandlers()) {
+                    if (handler instanceof ConsoleHandler) {
+                        ((ConsoleHandler) handler).setEncoding(encoding);
+                    }
+                }
+                if (!logger.getUseParentHandlers()) {
+                    break;
+                }
+            }
+        } catch (UnsupportedEncodingException e) {
+            Logging.recoverableException(TestCase.class, "<clinit>", e);
+        }
+    }
+
+    /**
+     * Date parser and formatter using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
+     * and UTC time zone.
+     */
+    private static final DateFormat dateFormat;
+    static {
+        dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA);
+        dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+        dateFormat.setLenient(false);
+    };
+
+    /**
+     * Creates a new test case.
+     */
+    protected TestCase() {
+    }
+
+    /**
+     * Parses the date for the given string using the {@code "yyyy-MM-dd HH:mm:ss"} pattern
+     * in UTC timezone.
+     *
+     * @param  date The date as a {@link String}.
+     * @return The date as a {@link Date}.
+     */
+    public static Date date(final String date) {
+        ArgumentChecks.ensureNonNull("date", date);
+        try {
+            synchronized (dateFormat) {
+                return dateFormat.parse(date);
+            }
+        } catch (ParseException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Formats the given date using the {@code "yyyy-MM-dd HH:mm:ss"} pattern in UTC timezone.
+     *
+     * @param  date The date to format.
+     * @return The date as a {@link String}.
+     */
+    public static String format(final Date date) {
+        ArgumentChecks.ensureNonNull("date", date);
+        synchronized (dateFormat) {
+            return dateFormat.format(date);
+        }
+    }
+
+    /**
+     * If verbose output is enabled, flushes the {@link #out} stream to the
+     * {@linkplain System#console() console} after every tests in the class
+     * have been run.
+     * <p>
+     * This method is invoked automatically by JUnit and doesn't need to be invoked
+     * explicitely, unless the developer wants to flush the output at some specific
+     * point.
+     */
+    @AfterClass
+    public static void flushVerboseOutput() {
+        System.out.flush();
+        System.err.flush();
+        if (out == null) {
+            return;
+        }
+        synchronized (buffer) { // This is the lock used by the 'out' PrintWriter.
+            out.flush();
+            /*
+             * Get the text content and remove the trailing spaces
+             * (including line feeds), if any.
+             */
+            String content = buffer.toString();
+            int length = content.length();
+            do if (length == 0) return;
+            while (Character.isWhitespace(content.charAt(--length)));
+            content = content.substring(0, ++length);
+            /*
+             * Get the output writer, using the specified encoding if any.
+             */
+            PrintWriter writer = null;
+            final String encoding = System.getProperty(ENCODING_KEY);
+            if (encoding == null) {
+                final Console console = System.console();
+                if (console != null) {
+                    writer = console.writer();
+                }
+            }
+            if (writer == null) {
+                if (encoding != null) try {
+                    writer = new PrintWriter(new OutputStreamWriter(System.out, encoding));
+                } catch (UnsupportedEncodingException e) {
+                    // Ignore. We will use the default encoding.
+                }
+                if (writer == null) {
+                    writer = new PrintWriter(System.out);
+                }
+            }
+            writer.println(content);
+            writer.flush();
+            buffer.getBuffer().setLength(0);
+        }
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestCase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.test;
+
+import java.util.Set;
+import java.util.List;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.Comparator;
+import net.jcip.annotations.NotThreadSafe;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.manipulation.Sorter;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import static org.apache.sis.util.Arrays.resize;
+
+
+/**
+ * The SIS test runner for individual classes.
+ * This class extends the JUnit standard test runner with additional features:
+ * <p>
+ * <ul>
+ *   <li>Support of the {@link Dependency} annotation.</li>
+ * </ul>
+ * <p>
+ * This runner is not designed for parallel execution of tests.
+ *
+ * @author  Stephen Connolly
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from <a href="http://github.com/junit-team/junit.contrib/tree/master/assumes">junit-team</a>)
+ * @version 0.3
+ * @module
+ */
+@NotThreadSafe
+public final class TestRunner extends BlockJUnit4ClassRunner {
+    /**
+     * The test methods to be executed, sorted according their dependencies.
+     * This array is created by {@link #getFilteredChildren()} when first needed.
+     */
+    private FrameworkMethod[] filteredChildren;
+
+    /**
+     * The dependency methods that failed. This set will be created only when first needed.
+     *
+     * @see #addDependencyFailure(String)
+     */
+    private Set<String> dependencyFailures;
+
+    /**
+     * The listener to use for keeping trace of methods that failed.
+     */
+    final RunListener listener = new RunListener() {
+        @Override
+        public void testFailure(final Failure failure) {
+            addDependencyFailure(failure.getDescription().getMethodName());
+        }
+
+        @Override
+        public void testAssumptionFailure(final Failure failure) {
+            addDependencyFailure(failure.getDescription().getMethodName());
+        }
+
+        @Override
+        public void testIgnored(final Description description) {
+            addDependencyFailure(description.getMethodName());
+        }
+    };
+
+    /**
+     * Creates a {@code Corollaries} to run {@code klass}.
+     *
+     * @param  klass The class to run.
+     * @throws InitializationError If the test class is malformed.
+     */
+    public TestRunner(final Class<?> klass) throws InitializationError {
+        super(klass);
+    }
+
+    /**
+     * Returns the test methods to be executed, with dependencies sorted before dependant tests.
+     *
+     * @return The test method to be executed in dependencies order.
+     */
+    @Override
+    public List<FrameworkMethod> getChildren() {
+        return Arrays.asList(getFilteredChildren());
+    }
+
+    /**
+     * Returns the test methods to be executed, with dependencies sorted before dependant tests.
+     *
+     * @return The test method to be executed in dependencies order.
+     */
+    private FrameworkMethod[] getFilteredChildren() {
+        if (filteredChildren == null) {
+            final List<FrameworkMethod> children = super.getChildren();
+            filteredChildren = children.toArray(new FrameworkMethod[children.size()]);
+            sortDependantTestsLast(filteredChildren);
+        }
+        return filteredChildren;
+    }
+
+    /**
+     * Sorts the tests methods using the given sorter. The resulting order may not be totally
+     * conform to the sorter specification, since this method will ensure that dependencies
+     * are still sorted before dependant tests.
+     *
+     * @param sorter The sorter to use for sorting tests.
+     */
+    @Override
+    public void sort(final Sorter sorter) {
+        final FrameworkMethod[] children = getFilteredChildren();
+        for (final FrameworkMethod method : children) {
+            sorter.apply(method);
+        }
+        Arrays.sort(children, new Comparator<FrameworkMethod>() {
+            @Override
+            public int compare(FrameworkMethod o1, FrameworkMethod o2) {
+                return sorter.compare(describeChild(o1), describeChild(o2));
+            }
+        });
+        sortDependantTestsLast(children);
+        filteredChildren = children;
+    }
+
+    /**
+     * Sorts the given array of methods in dependencies order.
+     *
+     * @param methods The methods to sort.
+     */
+    private static void sortDependantTestsLast(final FrameworkMethod[] methods) {
+        Set<String> dependencies = null;
+        for (int i=methods.length-1; --i>=0;) {
+            final FrameworkMethod method = methods[i];
+            final Dependency depend = method.getAnnotation(Dependency.class);
+            if (depend != null) {
+                if (dependencies == null) {
+                    dependencies = new HashSet<String>();
+                }
+                dependencies.addAll(Arrays.asList(depend.value()));
+                for (int j=methods.length; --j>i;) {
+                    if (dependencies.contains(methods[j].getName())) {
+                        // Found a method j which is a dependency of i. Move i after j.
+                        // The order of other methods relative to j is left unchanged.
+                        System.arraycopy(methods, i+1, methods, i, j-i);
+                        methods[j] = method;
+                        break;
+                    }
+                }
+                dependencies.clear();
+            }
+        }
+    }
+
+    /**
+     * Removes tests that don't pass the parameter {@code filter}.
+     *
+     * @param  filter The filter to apply.
+     * @throws NoTestsRemainException If all tests are filtered out.
+     */
+    @Override
+    public void filter(final Filter filter) throws NoTestsRemainException {
+        int count = 0;
+        FrameworkMethod[] children = getFilteredChildren();
+        for (int i=0; i<children.length; i++) {
+            final FrameworkMethod method = children[i];
+            if (filter.shouldRun(describeChild(method))) {
+                try {
+                    filter.apply(method);
+                } catch (NoTestsRemainException e) {
+                    continue;
+                }
+                children[count++] = method;
+            }
+        }
+        if (count == 0) {
+            throw new NoTestsRemainException();
+        }
+        filteredChildren = resize(children, count);
+    }
+
+    /**
+     * Returns the {@link Statement} which will execute all the tests in the class given
+     * to the constructor.
+     *
+     * @param  notifier The object to notify about test results.
+     * @return The statement to execute for running the tests.
+     */
+    @Override
+    protected Statement childrenInvoker(final RunNotifier notifier) {
+        final Statement stmt = super.childrenInvoker(notifier);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                notifier.addListener(listener);
+                try {
+                    stmt.evaluate();
+                } finally {
+                    notifier.removeListener(listener);
+                }
+            }
+        };
+    }
+
+    /**
+     * Before to delegate to the {@linkplain BlockJUnit4ClassRunner#runChild default implementation},
+     * check if a dependency of the given method failed. In such case, the test will be ignored.
+     *
+     * @param method   The test method to execute.
+     * @param notifier The object to notify about test results.
+     */
+    @Override
+    protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
+        if (dependencyFailures != null) {
+            final Dependency assumptions = method.getAnnotation(Dependency.class);
+            if (assumptions != null) {
+                for (final String assumption : assumptions.value()) {
+                    if (dependencyFailures.contains(assumption)) {
+                        dependencyFailures.add(method.getName());
+                        notifier.fireTestIgnored(describeChild(method));
+                        return;
+                    }
+                }
+            }
+        }
+        super.runChild(method, notifier);
+    }
+
+    /**
+     * Declares that the given method failed. Other methods depending on this method
+     * will be ignored.
+     *
+     * @param methodName The name of the method that failed.
+     */
+    final void addDependencyFailure(final String methodName) {
+        if (dependencyFailures == null) {
+            dependencyFailures = new HashSet<String>();
+        }
+        dependencyFailures.add(methodName);
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/TestRunner.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,694 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.sis.test;
+
+import java.util.Set;
+import java.util.List;
+import java.util.HashSet;
+import java.util.ArrayList;
+import java.net.URI;
+import java.net.URL;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.CDATASection;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.SAXException;
+
+import org.apache.sis.util.ArgumentChecks;
+
+import static java.lang.StrictMath.*;
+import static org.opengis.test.Assert.*;
+
+
+/**
+ * Compares the XML document produced by a test method with the expected XML document.
+ * The two XML documents are specified at construction time. The comparison is performed
+ * by a call to the {@link #compare()} method. The execution is delegated to the various
+ * protected methods defined in this class, which can be overridden.
+ * <p>
+ * By default, this comparator expects the documents to contain the same elements and
+ * the same attributes (but the order of attributes may be different). However it is
+ * possible to:
+ * <p>
+ * <ul>
+ *   <li>Specify attributes to ignore in comparisons (see {@link #ignoredAttributes})</li>
+ * </ul>
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.17)
+ * @version 0.3
+ * @module
+ */
+public strictfp class XMLComparator {
+    /**
+     * The expected document.
+     */
+    private final Node expectedDoc;
+
+    /**
+     * The document resulting from the test method.
+     */
+    private final Node actualDoc;
+
+    /**
+     * {@code true} if the comments shall be ignored. The default value is {@code false}.
+     */
+    public boolean ignoreComments;
+
+    /**
+     * The fully-qualified name of attributes to ignore in comparisons. The name shall be in
+     * the form {@code "namespace:name"}, or only {@code "name"} if there is no name space.
+     * In order to ignore everything in a name space, use {@code "namespace:*"}.
+     * <p>
+     * For example in order to ignore the name space, type and schema location declaration,
+     * the following strings can be added in this set:
+     *
+     * {@preformat text
+     *   "xmlns:*", "xsi:schemaLocation", "xsi:type"
+     * }
+     *
+     * This set is initially empty. Users can add or remove elements in this set as they wish.
+     * The content of this set will be honored by the default {@link #compareAttributes(Node, Node)}
+     * implementation.
+     */
+    public final Set<String> ignoredAttributes;
+
+    /**
+     * The fully-qualified name of nodes to ignore in comparisons. The name shall be in the form
+     * {@code "namespace:name"}, or only {@code "name"} if there is no name space. In order to
+     * ignore everything in a name space, use {@code "namespace:*"}.
+     * <p>
+     * This set provides a way to ignore a node of the given name <em>and all its children</em>.
+     * In order to ignore a node but still compare its children, override the
+     * {@link #compareNode(Node, Node)} method instead.
+     * <p>
+     * This set is initially empty. Users can add or remove elements in this set as they wish.
+     * The content of this set will be honored by the default {@link #compareChildren(Node, Node)}
+     * implementation.
+     */
+    public final Set<String> ignoredNodes;
+
+    /**
+     * The tolerance threshold for comparisons of numerical values, or 0 for strict comparisons.
+     * The default value is 0.
+     */
+    public double tolerance;
+
+    /**
+     * Creates a new comparator for the given root nodes.
+     *
+     * @param expected The root node of the expected XML document.
+     * @param actual   The root node of the XML document to compare.
+     */
+    public XMLComparator(final Node expected, final Node actual) {
+        ArgumentChecks.ensureNonNull("expected", expected);
+        ArgumentChecks.ensureNonNull("actual",   actual);
+        expectedDoc       = expected;
+        actualDoc         = actual;
+        ignoredAttributes = new HashSet<String>();
+        ignoredNodes      = new HashSet<String>();
+    }
+
+    /**
+     * Creates a new comparator for the given inputs.
+     * The inputs can be any of the following types:
+     * <p>
+     * <ul>
+     *   <li>{@link Node}; used directly without further processing.</li>
+     *   <li>{@link File}, {@link URL} or {@link URI}: the stream is opened and parsed
+     *       as a XML document.</li>
+     *   <li>{@link String}: The string content is parsed directly as a XML document.
+     *       Encoding <strong>must</strong> be UTF-8 (no other encoding is supported
+     *       by current implementation of this method).</li>
+     * </ul>
+     *
+     * @param  expected  The expected XML document.
+     * @param  actual    The XML document to compare.
+     * @throws IOException If the stream can not be read.
+     * @throws ParserConfigurationException If a {@link DocumentBuilder} can not be created.
+     * @throws SAXException If an error occurred while parsing the XML document.
+     */
+    public XMLComparator(final Object expected, final Object actual)
+            throws IOException, ParserConfigurationException, SAXException
+    {
+        this((expected instanceof Node) ? (Node) expected : read(expected),
+               (actual instanceof Node) ? (Node) actual   : read(actual));
+    }
+
+    /**
+     * Convenience method to acquire a DOM document from an input. This convenience method
+     * uses the default JRE classes, so it may not be the fastest parsing method.
+     */
+    private static Document read(final Object input)
+            throws IOException, ParserConfigurationException, SAXException
+    {
+        final Document document;
+        final InputStream stream = toInputStream(input);
+        try {
+            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            final DocumentBuilder constructeur = factory.newDocumentBuilder();
+            document = constructeur.parse(stream);
+        } finally {
+            stream.close();
+        }
+        return document;
+    }
+
+    /**
+     * Converts the given object to a stream.
+     * See the constructor Javadoc for the list of allowed input type.
+     */
+    private static InputStream toInputStream(final Object input) throws IOException {
+        if (input instanceof InputStream) return (InputStream) input;
+        if (input instanceof File)        return new FileInputStream((File) input);
+        if (input instanceof URI)         return ((URI) input).toURL().openStream();
+        if (input instanceof URL)         return ((URL) input).openStream();
+        if (input instanceof String)      return new ByteArrayInputStream(input.toString().getBytes("UTF-8"));
+        throw new IOException("Can not handle input type: " + (input != null ? input.getClass() : input));
+    }
+
+    /**
+     * Compares the XML document specified at construction time. Before to invoke this
+     * method, users may consider to add some values to the {@link #ignoredAttributes}
+     * set.
+     */
+    public void compare() {
+        compareNode(expectedDoc, actualDoc);
+    }
+
+    /**
+     * Compares the two given nodes. This method delegates to one of the given methods depending
+     * on the expected node type:
+     * <p>
+     * <ul>
+     *   <li>{@link #compareCDATASectionNode(CDATASection, Node)}</li>
+     *   <li>{@link #compareTextNode(Text, Node)}</li>
+     *   <li>{@link #compareCommentNode(Comment, Node)}</li>
+     *   <li>{@link #compareProcessingInstructionNode(ProcessingInstruction, Node)}</li>
+     *   <li>For all other types, {@link #compareNames(Node, Node)} and
+     *       {@link #compareAttributes(Node, Node)}</li>
+     * </ul>
+     * <p>
+     * Then this method invokes itself recursively for every children,
+     * by a call to {@link #compareChildren(Node, Node)}.
+     *
+     * @param expected The expected node.
+     * @param actual The node to compare.
+     */
+    protected void compareNode(final Node expected, final Node actual) {
+        if (expected == null || actual == null) {
+            fail(formatErrorMessage(expected, actual));
+        }
+        /*
+         * Check text value for types:
+         * TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE
+         */
+        if (expected instanceof CDATASection) {
+            compareCDATASectionNode((CDATASection) expected, actual);
+        } else if (expected instanceof Text) {
+            compareTextNode((Text) expected, actual);
+        } else if (expected instanceof Comment) {
+            compareCommentNode((Comment) expected, actual);
+        } else if (expected instanceof ProcessingInstruction) {
+            compareProcessingInstructionNode((ProcessingInstruction) expected, actual);
+        } else if (expected instanceof Attr) {
+            compareAttributeNode((Attr) expected, actual);
+        } else {
+            compareNames(expected, actual);
+            compareAttributes(expected, actual);
+        }
+        /*
+         * Check child nodes recursivly if it's not an attribut.
+         */
+        if (expected.getNodeType() != Node.ATTRIBUTE_NODE) {
+            compareChildren(expected, actual);
+        }
+    }
+
+    /**
+     * Compares a node which is expected to be of {@link Text} type. The default implementation
+     * ensures that the given node is an instance of {@link Text}, then ensures that both nodes
+     * have the same names, attributes and text content.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual   The actual node.
+     */
+    protected void compareTextNode(final Text expected, final Node actual) {
+        assertInstanceOf("Actual node is not of the expected type.", Text.class, actual);
+        compareNames(expected, actual);
+        compareAttributes(expected, actual);
+        assertTextContentEquals(expected, actual);
+    }
+
+    /**
+     * Compares a node which is expected to be of {@link CDATASection} type. The default
+     * implementation ensures that the given node is an instance of {@link CDATASection},
+     * then ensures that both nodes have the same names, attributes and text content.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual   The actual node.
+     */
+    protected void compareCDATASectionNode(final CDATASection expected, final Node actual) {
+        assertInstanceOf("Actual node is not of the expected type.", CDATASection.class, actual);
+        compareNames(expected, actual);
+        compareAttributes(expected, actual);
+        assertTextContentEquals(expected, actual);
+    }
+
+    /**
+     * Compares a node which is expected to be of {@link Comment} type. The default
+     * implementation ensures that the given node is an instance of {@link Comment},
+     * then ensures that both nodes have the same names, attributes and text content.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual   The actual node.
+     */
+    protected void compareCommentNode(final Comment expected, final Node actual) {
+        assertInstanceOf("Actual node is not of the expected type.", Comment.class, actual);
+        compareNames(expected, actual);
+        compareAttributes(expected, actual);
+        assertTextContentEquals(expected, actual);
+    }
+
+    /**
+     * Compares a node which is expected to be of {@link ProcessingInstruction} type. The default
+     * implementation ensures that the given node is an instance of {@link ProcessingInstruction},
+     * then ensures that both nodes have the same names, attributes and text content.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual   The actual node.
+     */
+    protected void compareProcessingInstructionNode(final ProcessingInstruction expected, final Node actual) {
+        assertInstanceOf("Actual node is not of the expected type.", ProcessingInstruction.class, actual);
+        compareNames(expected, actual);
+        compareAttributes(expected, actual);
+        assertTextContentEquals(expected, actual);
+    }
+
+    /**
+     * Compares a node which is expected to be of {@link Attr} type. The default
+     * implementation ensures that the given node is an instance of {@link Attr},
+     * then ensures that both nodes have the same names and text content.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual   The actual node.
+     */
+    protected void compareAttributeNode(final Attr expected, final Node actual) {
+        assertInstanceOf("Actual node is not of the expected type.", Attr.class, actual);
+        compareNames(expected, actual);
+        compareAttributes(expected, actual);
+        assertTextContentEquals(expected, actual);
+    }
+
+    /**
+     * Compares the children of the given nodes. The node themselves are not compared.
+     * Children shall appear in the same order. Nodes having a name declared in the
+     * {@link #ignoredNodes} set are ignored.
+     * <p>
+     * Subclasses can override this method if they need a different processing.
+     *
+     * @param expected The expected node.
+     * @param actual The node for which to compare children.
+     */
+    protected void compareChildren(Node expected, Node actual) {
+        expected = firstNonEmptySibling(expected.getFirstChild());
+        actual   = firstNonEmptySibling(actual  .getFirstChild());
+        while (expected != null) {
+            compareNode(expected, actual);
+            expected = firstNonEmptySibling(expected.getNextSibling());
+            actual   = firstNonEmptySibling(actual  .getNextSibling());
+        }
+        if (actual != null) {
+            fail(formatErrorMessage(expected, actual));
+        }
+    }
+
+    /**
+     * Compares the names and name spaces of the given node.
+     * Subclasses can override this method if they need a different comparison.
+     *
+     * @param expected The node having the expected name and name space.
+     * @param actual The node to compare.
+     */
+    protected void compareNames(final Node expected, final Node actual) {
+        assertPropertyEquals("namespace", expected.getNamespaceURI(), actual.getNamespaceURI(), expected, actual);
+        assertPropertyEquals("name",      expected.getNodeName(),     actual.getNodeName(),     expected, actual);
+    }
+
+    /**
+     * Compares the attributes of the given nodes.
+     * Subclasses can override this method if they need a different comparison.
+     * <p>
+     * <strong>NOTE:</strong> Current implementation requires the number of attributes to be the
+     * same only if the {@link #ignoredAttributes} set is empty. If the {@code ignoredAttributes}
+     * set is not empty, then the actual node could have more attributes than the expected node;
+     * the extra attributes are ignored. This may change in a future version if it appears to be
+     * a problem in practice.
+     *
+     * @param expected The node having the expected attributes.
+     * @param actual The node to compare.
+     */
+    protected void compareAttributes(final Node expected, final Node actual) {
+        final NamedNodeMap expectedAttributes = expected.getAttributes();
+        final NamedNodeMap actualAttributes   = actual.getAttributes();
+        final int n = (expectedAttributes != null) ? expectedAttributes.getLength() : 0;
+        if (ignoredAttributes.isEmpty()) {
+            assertPropertyEquals("nbAttributes", n,
+                    (actualAttributes != null) ? actualAttributes.getLength() : 0, expected, actual);
+        }
+        for (int i=0; i<n; i++) {
+            final Node expAttr = expectedAttributes.item(i);
+            final String ns    = expAttr.getNamespaceURI();
+            final String name  = expAttr.getNodeName();
+            if (!isIgnored(ignoredAttributes, ns, name)) {
+                final Node actAttr;
+                if (ns == null) {
+                    actAttr = actualAttributes.getNamedItem(name);
+                } else {
+                    actAttr = actualAttributes.getNamedItemNS(ns, name);
+                }
+                compareNode(expAttr, actAttr);
+            }
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given node or attribute shall be ignored.
+     *
+     * @param ignored The set of node or attribute fully qualified names to ignore.
+     * @param ns      The node or attribute name space, or {@code null}.
+     * @param name    The node or attribute name.
+     * @return        {@coce true} if the node or attribute shall be ignored.
+     */
+    private static boolean isIgnored(final Set<String> ignored, String ns, final String name) {
+        if (!ignored.isEmpty()) {
+            if (ignored.contains((ns != null) ? ns + ':' + name : name)) {
+                // Ignore a specific node (for example "xsi:schemaLocation")
+                return true;
+            }
+            if (ns == null) {
+                final int s = name.indexOf(':');
+                if (s >= 1) {
+                    ns = name.substring(0, s);
+                }
+            }
+            if (ns != null && ignored.contains(ns + ":*")) {
+                // Ignore a full namespace (typically "xmlns:*")
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the first sibling of the given node having a non-empty text content, or {@code null}
+     * if none. This method first check the given node, then check all siblings. Attribute nodes are
+     * ignored.
+     *
+     * @param  node The node to check, or {@code null}.
+     * @return The first node having a non-empty text content, or {@code null} if none.
+     */
+    private Node firstNonEmptySibling(Node node) {
+        for (; node != null; node = node.getNextSibling()) {
+            if (!isIgnored(ignoredNodes, node.getNamespaceURI(), node.getNodeName())) {
+                switch (node.getNodeType()) {
+                    // For attribute node, continue the search unconditionally.
+                    case Node.ATTRIBUTE_NODE: continue;
+
+                    // For text node, continue the search if the node is empty.
+                    case Node.TEXT_NODE: {
+                        final String text = node.getTextContent();
+                        if (text == null || text.trim().isEmpty()) {
+                            continue;
+                        }
+                        break;
+                    }
+
+                    // Ignore comment nodes only if requested.
+                    case Node.COMMENT_NODE: {
+                        if (ignoreComments) {
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                // Found a node: stop the search.
+                break;
+            }
+        }
+        return node;
+    }
+
+    /**
+     * Verifies that the text content of the given nodes are equal.
+     *
+     * @param expected The node that contains the expected text.
+     * @param actual   The node that contains the actual text to verify.
+     */
+    protected void assertTextContentEquals(final Node expected, final Node actual) {
+        assertPropertyEquals("textContent", expected.getTextContent(), actual.getTextContent(), expected, actual);
+    }
+
+    /**
+     * Verifies that the given property (text or number) are equal, ignoring spaces. If they are
+     * not equal, then an error message is formatted using the given property name and nodes.
+     *
+     * @param propertyName The name of the property being compared (typically "name", "name space", etc.).
+     * @param expected     The property value from the expected node to compare.
+     * @param actual       The property value to compare to the expected one.
+     * @param expectedNode The node from which the expected property has been fetched.
+     * @param actualNode   The node being compared to the expected node.
+     */
+    protected void assertPropertyEquals(final String propertyName, Comparable<?> expected, Comparable<?> actual,
+            final Node expectedNode, final Node actualNode)
+    {
+        expected = trim(expected);
+        actual   = trim(actual);
+        if ((expected != actual) && (expected == null || !expected.equals(actual))) {
+            // Before to declare a test failure, compares again as numerical values if possible.
+            if (tolerance > 0 && abs(doubleValue(expected) - doubleValue(actual)) <= tolerance) {
+                return;
+            }
+            final String lineSeparator = System.getProperty("line.separator", "\n");
+            final StringBuilder buffer = new StringBuilder(1024).append("Expected ")
+                    .append(propertyName).append(" \"")
+                    .append(expected).append("\" but got \"")
+                    .append(actual).append("\" for nodes:")
+                    .append(lineSeparator);
+            formatErrorMessage(buffer, expectedNode, actualNode, lineSeparator);
+            fail(buffer.toString());
+        }
+    }
+
+    /**
+     * Trims the leading and trailing spaces in the given property
+     * if it is actually a {@link String} object.
+     */
+    private static Comparable<?> trim(final Comparable<?> property) {
+        return (property instanceof String) ? ((String) property).trim() : property;
+    }
+
+    /**
+     * Parses the given text as a number. If the given text is null or can not be parsed,
+     * returns {@code NaN}. This is used only if a {@linkplain #tolerance} threshold greater
+     * than zero has been provided.
+     */
+    private static double doubleValue(final Comparable<?> property) {
+        if (property instanceof Number) {
+            return ((Number) property).doubleValue();
+        }
+        if (property instanceof CharSequence) try {
+            return Double.parseDouble(property.toString());
+        } catch (NumberFormatException e) {
+            // Ignore, as specified in method javadoc.
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Formats an error message for a node mismatch. The message will contain a string
+     * representation of the expected and actual node.
+     *
+     * @param expected The expected node.
+     * @param result   The actual node.
+     * @return         An error message containing the expected and actual node.
+     */
+    protected String formatErrorMessage(final Node expected, final Node result) {
+        final String lineSeparator = System.getProperty("line.separator", "\n");
+        final StringBuilder buffer = new StringBuilder(256).append("Nodes are not equal:").append(lineSeparator);
+        formatErrorMessage(buffer, expected, result, lineSeparator);
+        return buffer.toString();
+    }
+
+    /**
+     * Formats in the given buffer an error message for a node mismatch.
+     *
+     * @param lineSeparator The platform-specific line separator.
+     */
+    private static void formatErrorMessage(final StringBuilder buffer, final Node expected,
+            final Node result, final String lineSeparator)
+    {
+        formatNode(buffer.append("Expected node: "), expected, lineSeparator);
+        formatNode(buffer.append("Actual node:   "), result,   lineSeparator);
+        buffer.append("Expected hierarchy:").append(lineSeparator);
+        final List<String> hierarchy = formatHierarchy(buffer, expected, null, lineSeparator);
+        buffer.append("Actual hierarchy:").append(lineSeparator);
+        formatHierarchy(buffer, result, hierarchy, lineSeparator);
+    }
+
+    /**
+     * Appends to the given buffer the string representation of the node hierarchy.
+     * The first line will contains the root of the tree. Other lines will contain
+     * the child down in the hierarchy until the given node, inclusive.
+     * <p>
+     * This method formats only a summary if the hierarchy is equals to the expected one.
+     *
+     * @param buffer        The buffer in which to append the formatted hierarchy.
+     * @param node          The node for which to format the parents.
+     * @param expected      The expected hierarchy, or {@code null} if unknown.
+     * @param lineSeparator The platform-specific line separator.
+     */
+    private static List<String> formatHierarchy(final StringBuilder buffer, Node node,
+            final List<String> expected, final String lineSeparator)
+    {
+        final List<String> hierarchy = new ArrayList<String>();
+        while (node != null) {
+            hierarchy.add(node.getNodeName());
+            node = node.getParentNode();
+        }
+        if (hierarchy.equals(expected)) {
+            buffer.append("\u2514\u2500Same as expected").append(lineSeparator);
+        } else {
+            int indent = 2;
+            for (int i=hierarchy.size(); --i>=0;) {
+                for (int j=indent; --j>=0;) {
+                    buffer.append('\u00A0');
+                }
+                buffer.append("\u2514\u2500").append(hierarchy.get(i)).append(lineSeparator);
+                indent += 4;
+            }
+        }
+        return hierarchy;
+    }
+
+    /**
+     * Appends to the given buffer a string representation of the given node.
+     * The string representation is terminated by a line feed.
+     *
+     * @param buffer        The buffer in which to append the formatted node.
+     * @param node          The node to format.
+     * @param lineSeparator The platform-specific line separator.
+     */
+    private static void formatNode(final StringBuilder buffer, final Node node, final String lineSeparator) {
+        if (node == null) {
+            buffer.append("(no node)").append(lineSeparator);
+            return;
+        }
+        // Format the text content, together with the text content of the
+        // child if there is exactly one child.
+        final String ns = node.getNamespaceURI();
+        if (ns != null) {
+            buffer.append(ns).append(':');
+        }
+        buffer.append(node.getNodeName());
+        boolean hasText = appendTextContent(buffer, node);
+        final NodeList children = node.getChildNodes();
+        int numChildren = 0;
+        if (children != null) {
+            numChildren = children.getLength();
+            if (numChildren == 1 && !hasText) {
+                hasText = appendTextContent(buffer, children.item(0));
+            }
+        }
+
+        // Format the number of children and the number of attributes, if any.
+        String separator = " (";
+        if (numChildren != 0) {
+            buffer.append(separator).append("nbChild=").append(numChildren);
+            separator = ", ";
+        }
+        final NamedNodeMap atts = node.getAttributes();
+        int numAtts = 0;
+        if (atts != null) {
+            numAtts = atts.getLength();
+            if (numAtts != 0) {
+                buffer.append(separator).append("nbAtt=").append(numAtts);
+                separator = ", ";
+            }
+        }
+        if (!separator.equals(" (")) {
+            buffer.append(')');
+        }
+
+        // Format all attributes, if any.
+        separator = " [";
+        for (int i=0; i<numAtts; i++) {
+            buffer.append(separator).append(atts.item(i));
+            separator = ", ";
+        }
+        if (!separator.equals(" [")) {
+            buffer.append(']');
+        }
+        buffer.append(lineSeparator);
+    }
+
+    /**
+     * Appends the text content of the given node only if the node is an instance of {@link Text}
+     * or related type ({@link CDATASection}, {@link Comment} or {@link ProcessingInstruction}).
+     * Otherwise this method does nothing.
+     *
+     * @param  buffer The buffer in which to append text content.
+     * @param  node   The node for which to append text content.
+     * @return {@code true} if a text has been formatted.
+     */
+    private static boolean appendTextContent(final StringBuilder buffer, final Node node) {
+        if (node instanceof Text ||
+            node instanceof Comment ||
+            node instanceof CDATASection ||
+            node instanceof ProcessingInstruction)
+        {
+            buffer.append("=\"").append(node.getTextContent()).append('"');
+            return true;
+        }
+        return false;
+    }
+}

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/XMLComparator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java?rev=1391998&view=auto
==============================================================================
--- sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java (added)
+++ sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java Sun Sep 30 11:25:29 2012
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * Tools for SIS tests. This package defines a base class, {@link org.apache.sis.test.TestCase},
+ * which is extended directly or indirectly by many (but not all) SIS tests.
+ * This package defines also an {@link org.apache.sis.test.Assert} class which extend the GeoAPI
+ * {@link org.opengis.test.Assert} (which itself extends the JUnit {@link org.junit.Assert} class)
+ * with the addition of assertion methods commonly used in SIS tests.
+ * <p>
+ * By default, successful tests do not produce any output. However it is possible to ask for
+ * verbose output, which is sometime useful for debugging purpose. This behavior is controlled
+ * by {@linkplain java.lang.System#getProperties() system properties} like below:
+ * <p>
+ * <ul>
+ *   <li><code>-D{@value org.apache.sis.test.TestBase#VERBOSE_KEY}=true</code>
+ *       for verbose console output.</li>
+ *   <li><code>-D{@value org.apache.sis.test.TestBase#ENCODING_KEY}=UTF-8</code>
+ *       (or any other valid encoding name) for the encoding of the above-cited
+ *       verbose output. If omitted, the platform encoding will be used.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+package org.apache.sis.test;

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/trunk/sis-utility/src/test/java/org/apache/sis/test/package-info.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain