/* * Copyright 2006 Edward Kuns * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Id: TestWithReferenceDocument.java 0000 2006-10-19 22:00:00Z ekuns $ */ package org.castor.xmlctf; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import junit.framework.TestCase; import org.castor.xmlctf.util.CTFUtils; import org.exolab.castor.tests.framework.testDescriptor.FailureType; import org.exolab.castor.tests.framework.testDescriptor.types.FailureStepType; import org.exolab.castor.xml.XMLContext; /** * Implements a test case that tests code written by the XML source generator. * This class uses the generated source to read and write an XML document, * comparing the XML document written against the reference document that was * originally read in. * <p> * The test follows this sequence: * * <ol> * <li>Unmarshals the given input file (if any).</li> * <li>Compare the result object with the provided object model (if any).</li> * <li>Marshals the object to a file.</li> * <li>Unmarshals the created file.</li> * <li>Check that the result object is equal to the start object.</li> * </ol> * * @author <a href="mailto:gignoux@kernelcenter.org">Sebastien Gignoux</a> * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> * @version $Revision: 0000 $ $Date: $ */ class TestWithReferenceDocument extends TestCase { /** We add this fixed string to the end of our testcase name. */ private static final String REFERENCE = "_ReferenceDocument"; /** We delegate all actions to this test case. */ private final XMLTestCase _delegate; /** Used only to retrieved the classloader. */ protected final CastorTestCase _test; /** The failure object that is not null is the test intends to fail. */ protected final FailureType _failure; /** Class name of the ObjectModelBuilder. */ protected final String _builderClassName; /** Header of the name of all our output files ... marshaled and dumped. */ protected final String _outputName; /** Input file for test XML. (May be null if the builder class is provided.) */ protected final String _inputName; /** Gold file filename, really only useful if a class builder and no input file is given. */ protected final String _goldFileName; /** * Blank constructor for this test case. This contructor is not useful, since * no delegate test case is provided. * @param name Name of our delegate test case */ TestWithReferenceDocument(String name) { super(name+REFERENCE); throw new IllegalArgumentException("You cannot use the name-only constructor"); } /** * Constructs a test case that when invoked will delegate to the provided * test case. * @param name Name of our delegate test case * @param tc */ TestWithReferenceDocument(String name, XMLTestCase tc) { super(name+REFERENCE); _delegate = tc; _test = tc._test; _failure = tc._failure; _outputName = tc._name.replace(' ', '_') + "-testWithReferenceDocument.xml"; _builderClassName = tc._unitTest.getObjectBuilder(); _inputName = tc._unitTest.getInput(); // Gold File should be set to input file if no gold file is specified // but some tests fail (genuine failures) so for now this is disabled. if (tc._unitTest.getGoldFile() != null) { _goldFileName = tc._unitTest.getGoldFile(); } else { _goldFileName = _inputName; } } /** * Provides setup for our delegated test case, depending on the type of * test case we are delegating for. * @throws Exception if anything goes wrong during setup */ protected void setUp() throws Exception { if (_delegate == null) { throw new IllegalStateException("No test specified to set up."); } _delegate.setXMLContext(new XMLContext()); _delegate.setUp(); // if (_delegate instanceof MarshallingFrameworkTestCase) { // ((MarshallingFrameworkTestCase)_delegate).setUp(); // } else if (_delegate instanceof SourceGeneratorTestCase) { // ((SourceGeneratorTestCase)_delegate).setUp(); // } } /** * Provides tear down for our delegated test case, depending on the type of * test case we are delegating for. * @throws Exception if anything goes wrong during teardown */ protected void tearDown() throws Exception { if (_delegate == null) { throw new IllegalStateException("No test specified to tear down."); } _delegate.tearDown(); // if (_delegate instanceof MarshallingFrameworkTestCase) { // ((MarshallingFrameworkTestCase)_delegate).tearDown(); // } else if (_delegate instanceof SourceGeneratorTestCase) { // ((SourceGeneratorTestCase)_delegate).tearDown(); // } } /** * Runs our test case using our delegate object where necessary. * <p> * FIXME: Fix this so it throws only specific and necessary exceptions * @throws Exception if anything goes wrong */ public void runTest() throws Exception { // FIXME - temporarily throws Exception if (_delegate == null) { throw new IllegalStateException("No test specified to be run."); } verbose("\n------------------------------"); verbose("Test with reference documents"); verbose("------------------------------\n"); if (_delegate._skip) { verbose("-->Skipping the test"); return; } // 1. Get reference document from unmarshaler + input file (if input file is provided) Object refUnmarshal; try { refUnmarshal = getUnmarshaledReference(); } catch (Exception e) { if (!_delegate.checkExceptionWasExpected(e, FailureStepType.UNMARSHAL_REFERENCE)) { fail("Exception Unmarshaling from disk " + e); } return; } // 2. Get reference document from builder (if builder is provided) -- should never fail Object refGenerated = getBuilderReference(); // 3. If we have two reference objects, make sure they are the same -- should never fail if (refUnmarshal != null && refGenerated != null) { compareReferenceObjects(refUnmarshal, refGenerated); } // 4. Pick our reference object (at least one should be non-null) final Object ref = (refUnmarshal != null) ? refUnmarshal : refGenerated; if (ref == null) { throw new Exception("There is no valid input file or hardcoded object in '" + _delegate._name + "'"); } // 5. Marshal our reference object to disk File marshal_output; try { marshal_output = _delegate.testMarshal(ref, _outputName); } catch (Exception e) { if (!_delegate.checkExceptionWasExpected(e, FailureStepType.MARSHAL_TO_DISK)) { fail("Exception Unmarshaling from disk " + e); } return; } if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null && _failure.getFailureStep().equals(FailureStepType.MARSHAL_TO_DISK)) { fail("Marshaling the reference document to disk was expected to fail, but succeeded"); return; } // 6. Compare marshaled document with gold file (if one was provided) if (_goldFileName != null && _goldFileName.length() > 0) { int result = CTFUtils.compare(_delegate._outputRootFile + "/" + _goldFileName, marshal_output.getAbsolutePath()); verbose("----> Compare marshaled document to gold file '" + _goldFileName + "': " + ((result == 0)?"OK":"### Failed ### ")); final boolean expectedToFail = _failure != null && _failure.getContent() && _failure.getFailureStep() != null && _failure.getFailureStep().equals(FailureStepType.COMPARE_TO_REFERENCE); if (_failure == null ||!_failure.getContent()) { assertEquals("The Marshaled object differs from the gold file", 0, result); } else if (expectedToFail) { assertTrue("The Marshaled object was expected to differ from the" + " gold file, but did not", result != 0); } } // 7. Marshal the Listener and compare it to the listener gold file, if any compareListenerToItsGoldFile(); // 8. Unmarshal the output file Object unmarshaledOutput; try { unmarshaledOutput = _delegate.testUnmarshal(marshal_output); } catch (Exception e) { if (!_delegate.checkExceptionWasExpected(e, FailureStepType.SECOND_UNMARSHAL)) { fail("Exception Unmarshaling from disk " + e); } return; } if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null && _failure.getFailureStep().equals(FailureStepType.SECOND_UNMARSHAL)) { fail("Second unmarshaling was expected to fail, but succeeded"); return; } // 9. Compare unmarshaled output file to ObjectModelBuilder if any. // TODO: Fix the tests that fail this comparison! // Right now many test classes (under xml/MasterTestSuite) do not override equals. // We could check "(ref instanceof CastorTestable)" except that several srcgen // tests fails this check. (Probably bugs!) For now we have this bogus // _builderClassName check. We ideally want to ALWAYS do this comparison. if (_builderClassName != null) { // the equals method must be overriden boolean result = unmarshaledOutput.equals(ref); if (result == false) { verbose("Make sure the reference object model overrides Object#equals"); } verbose("Compare to reference object: " + ((result)?"OK":" ### Failed ### ")); final FailureStepType step = _failure != null ? _failure.getFailureStep() : null; final boolean expectedToFail = _failure != null && _failure.getContent() && (step == null || step.equals(FailureStepType.SECOND_COMPARE)); if (_failure == null || !_failure.getContent()) { assertTrue("The initial reference object and the one resulting of the " + "marshal/unmarshal process are different", result); } else if (expectedToFail) { assertFalse("Comparing the reference object to the marshal+unmarshaled " + "one was expected to fail, but succeeded", result); } if (expectedToFail ^ result) { return; } } if (_failure != null && _failure.getContent()) { fail("The test with reference document was expected to fail, but passed"); } } /** * Returns a reference document as generated by unmarshaling the input * document provided, if one was provided. * @return a reference document from an unmarshaled input document * @throws Exception if anything goes wrong loading the input document */ private Object getUnmarshaledReference() throws Exception { InputStream _input = null; if (_inputName != null) { _input = _test.getClassLoader().getResourceAsStream(_inputName); assertNotNull("The input file '" + _inputName + "' cannot be found.", _input); } verbose("--> Unmarshaling '" + _inputName + "'\n"); Object refUnmarshal = null; if (_input != null) { refUnmarshal = _delegate.testUnmarshal(_input); _input.close(); } // If we didn't throw an exception, make sure we were supposed to succeed if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null && _failure.getFailureStep().equals(FailureStepType.UNMARSHAL_REFERENCE)) { fail("Unmarshaling the reference document was expected to fail, but succeeded"); } assertNotNull("Unmarshaling '" + _inputName + "' results in a NULL object.", refUnmarshal); return refUnmarshal; } /** * Returns a reference document as generated by the hard-coded * ObjectModelBuilder. * * @return a reference document as generated by the hard-coded * ObjectModelBuilder * @throws Exception if anything goes wrong executing creating the * ObjectModelBuilder or using it to create the reference * document. */ private Object getBuilderReference() throws Exception { Object generated = null; if (_builderClassName != null) { generated = _delegate.buildObjectModel(_builderClassName); assertNotNull("The generated object with '" + _builderClassName + "' is null", generated); } return generated; } /** * Make sure the ObjectModelBuilder object and the unmarshaled input document * created the same object. * @param refUnmarshal reference object created from unmarshaling an input document * @param refGenerated reference object created by an ObjectModelBuilder * @throws IOException if we get an error dumping the objects to disk. */ private void compareReferenceObjects(Object refUnmarshal, Object refGenerated) throws IOException { //the object model must override the equals method. boolean result = refGenerated.equals(refUnmarshal); verbose("----> Compare unmarshaled document to reference object: " + ((result)?"OK":"### Failed ### ")); if (result == false) { verbose("Make sure the reference object model overrides Object#equals"); } if (result == false && refGenerated instanceof CastorTestable) { // try to dump the unmarshaled object and the reference object FileWriter writer = new FileWriter(new File(_delegate._outputRootFile, _outputName + "-refModel.dump")); writer.write(((CastorTestable)refGenerated).dumpFields()); writer.close(); writer = new FileWriter(new File(_delegate._outputRootFile, _outputName + "-refUnmarshal.dump")); writer.write(((CastorTestable)refUnmarshal).dumpFields()); writer.close(); } assertTrue("The unmarshaled reference object differs from the hardcoded reference object.", result); } /** * Tests that the listener saw what it was supposed to see. If we have a * listener configured, marshal it to disk (now that it listened to the * previous marshal) and compare it to the listener gold file, if any. * <p> * We have to unregister the listener before we do anything. When we marshal * the MarshalListener, for every item marshaled the listener may grow and * may have more to marshal, and thus we may end up in an endless loop. For * example, a simple implementation of MarshalListener could log each * pre/post marshal invocation on a Vector to allow for later comparisons. * But this means that the object *being marshaled* keeps getting data added * to it during the marshaling! * * @throws Exception if anything goes wrong during the test */ private void compareListenerToItsGoldFile() throws Exception { if (_delegate._listenerGoldFile == null || _delegate._listener == null) { return; } verbose("Compare listener to gold file: " + _delegate._listenerGoldFile); // Get the listener (before we unregister it!) Object listener = _delegate._listener; // Unregister the listener _delegate._listener = null; File outputFile; try { outputFile = _delegate.testMarshal(listener, "Listener-" + _outputName); } catch (Exception e) { if (!_delegate.checkExceptionWasExpected(e, FailureStepType.LISTENER_COMPARISON)) { fail("Exception Unmarshaling from disk " + e); } return; } int result = CTFUtils.compare(_delegate._outputRootFile + "/" + _delegate._listenerGoldFile, outputFile.getAbsolutePath()); verbose("----> Compare marshaled document to gold file '" + _delegate._listenerGoldFile + "': " + ((result == 0)?"OK":"### Failed ### ")); if (_failure != null && _failure.getContent()) { // Are we are supposed to fail AT THIS STEP? If not, don't check if (_failure.getFailureStep() != null && _failure.getFailureStep().equals(FailureStepType.LISTENER_COMPARISON)) { assertTrue("The Marshaled Listener is supposed to differ from its gold file", result != 0); } } else { assertEquals("The Marshaled Listener differs from its gold file", 0, result); } } private void verbose(String message) { _delegate.verbose(message); } }