Return-Path: Delivered-To: apmail-cocoon-users-archive@www.apache.org Received: (qmail 39730 invoked from network); 25 Jun 2005 07:57:55 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (209.237.227.199) by minotaur.apache.org with SMTP; 25 Jun 2005 07:57:55 -0000 Received: (qmail 42251 invoked by uid 500); 25 Jun 2005 07:57:49 -0000 Delivered-To: apmail-cocoon-users-archive@cocoon.apache.org Received: (qmail 42196 invoked by uid 500); 25 Jun 2005 07:57:49 -0000 Mailing-List: contact users-help@cocoon.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: users@cocoon.apache.org List-Id: Delivered-To: mailing list users@cocoon.apache.org Received: (qmail 42183 invoked by uid 99); 25 Jun 2005 07:57:49 -0000 Received: from asf.osuosl.org (HELO asf.osuosl.org) (140.211.166.49) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 25 Jun 2005 00:57:49 -0700 X-ASF-Spam-Status: No, hits=0.0 required=10.0 tests= X-Spam-Check-By: apache.org Received-SPF: neutral (asf.osuosl.org: local policy) Received: from [80.68.89.4] (HELO bm.codeconsult.ch) (80.68.89.4) by apache.org (qpsmtpd/0.29) with SMTP; Sat, 25 Jun 2005 00:57:49 -0700 Received: (qmail 13986 invoked from network); 25 Jun 2005 07:57:44 -0000 Received: from unknown (HELO ?192.168.1.33?) (212.147.126.138) by bm.codeconsult.ch with SMTP; 25 Jun 2005 07:57:44 -0000 Mime-Version: 1.0 (Apple Message framework v619.2) In-Reply-To: <42BB0CA5.6060603@d-haven.org> References: <42BAB51D.80608@d-haven.org> <42BB0CA5.6060603@d-haven.org> Content-Type: multipart/signed; micalg=sha1; boundary=Apple-Mail-9--354865429; protocol="application/pkcs7-signature" Message-Id: <1120b35f14fbd551b2b4b52cf79e283d@apache.org> From: Bertrand Delacretaz Subject: Re: TDD with Cocoon? Date: Sat, 25 Jun 2005 09:57:35 +0200 To: users@cocoon.apache.org X-Mailer: Apple Mail (2.619.2) X-Virus-Checked: Checked by ClamAV on apache.org X-Spam-Rating: minotaur.apache.org 1.6.2 0/1000/N --Apple-Mail-9--354865429 Content-Type: multipart/mixed; boundary=Apple-Mail-8--354865674 --Apple-Mail-8--354865674 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=ISO-8859-1; format=flowed Le 23 juin 05, =E0 21:25, Berin Loritsch a =E9crit : >> ...b) JUnit test cases which run in a Generator, for higher-level=20 >> 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=20= simple Class.forName() on a generator parameter), and if the TestSuite=20= implements an interface of mine, passes it a custom environment object=20= like: public static class TestEnvironment { public final Logger logger; public final componentManager WhateverContainerYoureUsing; TestEnvironment(Logger x,WhateverContainerYoureUsing w) { logger =3D x; componentManager =3D w; } } Then, the Generator creates a junit.textui.ResultPrinter which writes=20 results to the Generators' ContentHandler. That's it, nothing fancy really. But it allows the TestSuite to be=20 fairly decoupled from the Cocoon-specific stuff, while having access to=20= components and services via the above TestEnvironment (although,=20 rereading it now, the TestSuite could also come directly from the=20 component manager to reach the same goal). The nice thing is that this allows you to run certain tests right in a=20= deployed app, in the exact same environment that will be used for=20 production, just by calling an URL. It's a bit of a sharp knife, you want to know what your tests are doing=20= before enabling this, but I find it very useful to be able to run=20 non-destructive tests on *the* system. To be safe, tests that might=20 cause harm to a production system are designed to check that they're=20 running on a test environment and fail if not. I'm not very happy with my current JUnitTestGenerator, the XML output=20 is garbled when tests fail, but if you want to play with it and=20 hopefully improve it I enclose the code below. The problem is probably=20= caused by the delay in the ContentHandlerOutputStream, this looks a bit=20= silly now that I see it. You should be able to fix it easily if you=20 think it can be useful. -Bertrand --Apple-Mail-8--354865674 Content-Transfer-Encoding: 7bit Content-Type: text/plain; x-unix-mode=0644; name="JUnitTestingGenerator.java" Content-Disposition: attachment; filename=JUnitTestingGenerator.java 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 bdelacretaz@codeconsult.ch */ 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; } } } --Apple-Mail-8--354865674 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=US-ASCII; format=flowed --Apple-Mail-8--354865674-- --Apple-Mail-9--354865429 Content-Transfer-Encoding: base64 Content-Type: application/pkcs7-signature; name=smime.p7s Content-Disposition: attachment; filename=smime.p7s MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGRTCCAv4w ggJnoAMCAQICAw0p3DANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt YWlsIElzc3VpbmcgQ0EwHhcNMDQxMDA0MTQyMzU0WhcNMDUxMDA0MTQyMzU0WjBtMRMwEQYDVQQE EwpEZWxhY3JldGF6MREwDwYDVQQqEwhCZXJ0cmFuZDEcMBoGA1UEAxMTQmVydHJhbmQgRGVsYWNy ZXRhejElMCMGCSqGSIb3DQEJARYWYmRlbGFjcmV0YXpAYXBhY2hlLm9yZzCCASIwDQYJKoZIhvcN AQEBBQADggEPADCCAQoCggEBALXTKyJRGv/islEqVhG+iE8FSGe+4ikts9rca6OUPD4CJ+vYcXS5 arpv87lSoi0cyC8QkjB6MzsXzsraQjyO3EOeFOcNWN1XFe0Qc2uqVBSdYM6LtaSs4A1OYSweKKFK iNscMmlsIIKXvZ8xb4QmbZZSkklh5tWdJh+aNNV0m++NEUXRsqRffz6eHRmf0RjYmwxBig3KpzmH dPAcGTtDcI9O7p2OpQ7zPFAt9n4tMPx6makpU97HeGKqvWPIaaprQa/4nLhTcWDaZ1AHyFYNhzru gyRI0SDyp6OijuUZKXsaESJWEEySke18l6aZbsflzXpoCMTS9ce93fJ0uBFax/kCAwEAAaMzMDEw IQYDVR0RBBowGIEWYmRlbGFjcmV0YXpAYXBhY2hlLm9yZzAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3 DQEBBAUAA4GBAEBia6WrujKxJoVkme3aFszr/WmH4x+mIj+AmrAVG2tGB+RO0yCK/aal3wLzMBYn O6zr7TPW2PqOUmMGeP7Pgr5KSMn4rlfM+Hdfd/IvqCpHuWcqq/VAto47XF2cZyGNw32EdGx8IeCe 6BFaoRh6yJdS3GkOnbqSNCVTih/PdRlxMIIDPzCCAqigAwIBAgIBDTANBgkqhkiG9w0BAQUFADCB 0TELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du MRowGAYDVQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2 aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIENBMSswKQYJ KoZIhvcNAQkBFhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUuY29tMB4XDTAzMDcxNzAwMDAwMFoX DTEzMDcxNjIzNTk1OVowYjELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5n IChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBJc3N1aW5nIENB MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEpjxVc1X7TrnKmVoeaMB1BHCd3+n/ox7svc31 W/Iadr1/DDph8r9RzgHU5VAKMNcCY1osiRVwjt3J8CuFWqo/cVbLrzwLB+fxH5E2JCoTzyvV84J3 PQO+K/67GD4Hv0CAAmTXp6a7n2XRxSpUhQ9IBH+nttE8YQRAHmQZcmC3+wIDAQABo4GUMIGRMBIG A1UdEwEB/wQIMAYBAf8CAQAwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC50aGF3dGUuY29t L1RoYXd0ZVBlcnNvbmFsRnJlZW1haWxDQS5jcmwwCwYDVR0PBAQDAgEGMCkGA1UdEQQiMCCkHjAc MRowGAYDVQQDExFQcml2YXRlTGFiZWwyLTEzODANBgkqhkiG9w0BAQUFAAOBgQBIjNFQg+oLLswN o2asZw9/r6y+whehQ5aUnX9MIbj4Nh+qLZ82L8D0HFAgk3A8/a3hYWLD2ToZfoSxmRsAxRoLgnSe JVCUYsfbJ3FXJY3dqZw5jowgT2Vfldr394fWxghOrvbqNOUQGls1TXfjViF4gtwhGTXeJLHTHUb/ XV9lTzGCAucwggLjAgEBMGkwYjELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0 aW5nIChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBJc3N1aW5n IENBAgMNKdwwCQYFKw4DAhoFAKCCAVMwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG 9w0BCQUxDxcNMDUwNjI1MDc1NzM1WjAjBgkqhkiG9w0BCQQxFgQUJu+gBUIwQETOBBsuwL5R3etS Cf0weAYJKwYBBAGCNxAEMWswaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1 bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElzc3Vp bmcgQ0ECAw0p3DB6BgsqhkiG9w0BCRACCzFroGkwYjELMAkGA1UEBhMCWkExJTAjBgNVBAoTHFRo YXd0ZSBDb25zdWx0aW5nIChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQZXJzb25hbCBGcmVl bWFpbCBJc3N1aW5nIENBAgMNKdwwDQYJKoZIhvcNAQEBBQAEggEAGqLRqSSvamusrJyixfv+YmHM of8DPEGJFN7YRjxvlV5NJx7G/w/eFumsu2Or297J/U3WXEIAYj4CTOc28WcPAtfcDyQWowBbx0C0 yoaakvKpDjNZNEzD76uxXm8GO3L7EOLkGumGl7kA2OLnOaGALsCvzvatM/Mge9B5wZarJPTuI8/G zwFfErg0UWDxpolihEIaYBQI1nu6Qtv/okd+dwSFXVhpB/0YVVk1XxeKWMp81ejZp+NR99P27Afo Wn1woc2iBVdxmfTwU+0RDw0F9xAQVhcmYFL0dTZqAhtcWy8WMNJiSiQtA5cb0MaVsgQvHw1+eXmP 4bdkd5xwqwZs2QAAAAAAAA== --Apple-Mail-9--354865429--