Le 23 juin 05, à 21:25, Berin Loritsch a écrit :

...b) JUnit test cases which run in a Generator, for higher-level tests which need access to more of the application's environment

Now, how does this work?  Do you have an example of that?

In the generate() method the Generator locates a JUnit TestSuite (via a simple Class.forName() on a generator parameter), and if the TestSuite implements an interface of mine, passes it a custom environment object like:

    public static class TestEnvironment {
        public final Logger logger;
        public final componentManager WhateverContainerYoureUsing;

        TestEnvironment(Logger x,WhateverContainerYoureUsing w) {
            logger = x;
            componentManager = w;
        }
    }

Then, the Generator creates a junit.textui.ResultPrinter which writes results to the Generators' ContentHandler.

That's it, nothing fancy really. But it allows the TestSuite to be fairly decoupled from the Cocoon-specific stuff, while having access to components and services via the above TestEnvironment (although, rereading it now, the TestSuite could also come directly from the component manager to reach the same goal).

The nice thing is that this allows you to run certain tests right in a deployed app, in the exact same environment that will be used for production, just by calling an URL.

It's a bit of a sharp knife, you want to know what your tests are doing before enabling this, but I find it very useful to be able to run non-destructive tests on *the* system. To be safe, tests that might cause harm to a production system are designed to check that they're running on a test environment and fail if not.

I'm not very happy with my current JUnitTestGenerator, the XML output is garbled when tests fail, but if you want to play with it and hopefully improve it I enclose the code below. The problem is probably caused by the delay in the ContentHandlerOutputStream, this looks a bit silly now that I see it. You should be able to fix it easily if you think it can be useful.

-Bertrand

package whatever;

import org.apache.cocoon.generation.AbstractGenerator;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.logger.Logger;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceException;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;

import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Map;

import junit.framework.TestSuite;
import junit.framework.Test;
import junit.framework.TestFailure;
import junit.textui.TestRunner;
import junit.textui.ResultPrinter;

/** Cocoon Generator which runs a JUnit TestSuite. A bit broken,
 *  XML output is not well-formed when tests fail. Feel free to improve!
 *
 *  @author [EMAIL PROTECTED]
 */
 
public class JUnitTestGenerator extends AbstractGenerator implements 
Serviceable {

    /** namespaces for this generator */
    public static final String INSTANCE_NS = 
"http://codeconsult.ch/cocoon/generation/junit";;
    public static final String INSTANCE_PREFIX = "jut";
    public static final String INSTANCE_PREFIX_COLON = INSTANCE_PREFIX + ":";
    public static final String ROOT_ELEMENT = "junit-tests-results";

    public static final String TEST_CLASS_PARAM = "test-class";
    public static final String DEFAULT_TEST_CLASS_NAME = "NoTestClassNameSet";
    String testClassName = DEFAULT_TEST_CLASS_NAME;

    ServiceManager manager;

    /** environment for our TestCases */
    public static class TestEnvironment {
        public final Logger logger;
        public final ServiceManager serviceManager;

        TestEnvironment(Logger x,ServiceManager y) {
            logger = x;
            serviceManager = y;
        }
    }

    /** Test classes which implement this receive a TestEnvironment */
    public static interface TestEnvironmentConsumer {
        void setTestEnvironment(TestEnvironment te);
    }

    /** JUnit ResultPrinter which indicates when each test starts */
    private class TestInfoResultPrinter extends ResultPrinter {
        private final PrintStream m_ps;
        final String TEST_ELEMENT = "junit-test";
        final String TEST_NAME_ATTR = "name";

        public TestInfoResultPrinter(PrintStream ps)  {
            super(ps);
            m_ps = ps;
        }

        public void printDefect(TestFailure testFailure, int i) {
            final String SEPARATOR = "\n\n";
            try {
                m_ps.write(SEPARATOR.getBytes());
            } catch(IOException ioe) {
                getLogger().warn("Exception in printDefect",ioe);
            }
            super.printDefect(testFailure, i);
        }

        public void endTest(Test test) {
            super.endTest(test);

            m_ps.flush();
            try {
                
contentHandler.endElement(INSTANCE_NS,TEST_ELEMENT,INSTANCE_PREFIX_COLON + 
TEST_ELEMENT);
            } catch(SAXException se) {
                getLogger().warn("Exception in endTest()",se);
            }
        }

        /** Start an element when Test starts */
        public void startTest(Test test) {
            m_ps.flush();
            try {
                final AttributesImpl attr = new AttributesImpl();
                attr.addCDATAAttribute(TEST_NAME_ATTR,test.toString());
                
contentHandler.startElement(INSTANCE_NS,TEST_ELEMENT,INSTANCE_PREFIX_COLON + 
TEST_ELEMENT,attr);
            } catch(SAXException se) {
                getLogger().warn("Exception in endTest()",se);
            }

            super.startTest(test);
        }
    }

    /** process sitemap parameters */
    public void setup(SourceResolver sourceResolver, Map map, String s, 
Parameters parameters) throws ProcessingException, SAXException, IOException {
        try {
            testClassName = parameters.getParameter(TEST_CLASS_PARAM);
        } catch(ParameterException pe) {
            throw new ProcessingException("error reading parameters",pe);
        }

        if(testClassName==null) {
            throw new ProcessingException("Missing '" + TEST_CLASS_PARAM + "' 
parameter");
        }
    }

    /** reset to initial state */
    public void recycle() {
        super.recycle();
        testClassName = DEFAULT_TEST_CLASS_NAME;
    }

    /** write document structure and call runTests */
    public void generate() throws IOException, SAXException, 
ProcessingException {
        final Attributes emptyAttr = new AttributesImpl();

        contentHandler.startDocument();
        contentHandler.startPrefixMapping(INSTANCE_PREFIX,INSTANCE_NS);

        
contentHandler.startElement(INSTANCE_NS,ROOT_ELEMENT,INSTANCE_PREFIX_COLON + 
ROOT_ELEMENT, emptyAttr);

        try {
            runTests();
        } catch(ProcessingException pe) {
            throw pe;
        } catch(Exception e) {
            throw new ProcessingException("Exception in runTests()",e);
        }

        
contentHandler.endElement(INSTANCE_NS,ROOT_ELEMENT,INSTANCE_PREFIX_COLON + 
ROOT_ELEMENT);

        contentHandler.endPrefixMapping(INSTANCE_PREFIX);
        contentHandler.endDocument();
    }

    /** run JUnit tests, write results to our contentHandler */
    private void runTests() throws Exception {
        // instantiate TestSuite
        Object tso;
        TestSuite ts = null;
        try {
            tso = Class.forName(testClassName).newInstance();
            ts = (TestSuite)tso;
        } catch(Exception e) {
            throw new ProcessingException("Unable to instantiate TestSuite 
class " + testClassName,e);
        }

        if(tso instanceof TestEnvironmentConsumer) {
            if(getLogger().isDebugEnabled()) {
                getLogger().debug("Passing TestEnvironment to " + 
ts.getClass().getName());
            }
            ((TestEnvironmentConsumer)tso).setTestEnvironment(new 
TestEnvironment(getLogger(),manager));
        } else {
            if(getLogger().isDebugEnabled()) {
                getLogger().debug(tso.getClass().getName() + " is not a 
TestEnvironmentConsumer, TestEnvironment not provided");
            }
        }

        final OutputStream os = new 
ContentHandlerOutputStream((contentHandler));
        try {
            final TestRunner tr = new TestRunner(new TestInfoResultPrinter(new 
PrintStream(os)));
            tr.doRun(ts);
        } finally {
            os.flush();
        }
    }

    /** store our ServiceManager */
    public void service(ServiceManager serviceManager) throws ServiceException {
        manager = serviceManager;
    }
}

/** OutputStream which writes to a ContentHandler, but not more often than once 
per second */
class ContentHandlerOutputStream extends OutputStream {
    private final ContentHandler m_contentHandler;
    private ByteArrayOutputStream m_buffer;
    private long m_lastWrite;
    public static final long WRITE_INTERVAL_MSEC = 1000L;

    public ContentHandlerOutputStream (ContentHandler ch) {
        m_contentHandler = ch;
    }

    /** Outputstream interface
     *  Write to our sink in more than one second has elapsed since last write.
     *  Make it synced for safety, this is for tests anyway, doesn't need super 
performance
     */
    public synchronized void write(int b) throws IOException {
        if(m_buffer == null) m_buffer = new ByteArrayOutputStream(4096);
        m_buffer.write(b);

        if(System.currentTimeMillis() - m_lastWrite > WRITE_INTERVAL_MSEC) {
            flush();
            m_lastWrite = System.currentTimeMillis();
        }
    }

    public void flush() throws IOException {
        if(m_buffer!=null) {
            final char [] toWrite = m_buffer.toString().toCharArray();
            try {
                m_contentHandler.characters(toWrite,0,toWrite.length);
            } catch(SAXException se) {
                throw new IOException("SAXException on ContentHandler: " + se);
            }
            m_buffer = null;
        }
    }
}



Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to