/******************************************************************************* * * Copyright (c) 2004-2009, Oracle Corporation * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * * * *******************************************************************************/ package org.jvnet.hudson.test; import junit.framework.TestCase; import junit.framework.TestResult; import junit.framework.TestSuite; import org.apache.commons.io.FileUtils; import org.dom4j.Document; import org.dom4j.io.SAXReader; import org.jvnet.hudson.test.junit.GroupedTest; import org.kohsuke.stapler.MetaClassLoader; import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff; import java.io.File; import java.net.URL; import java.util.Collection; import java.util.Enumeration; import java.util.concurrent.Callable; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; import org.hudsonci.xpath.XPath; import org.hudsonci.xpath.XPathException; /** * Builds up a {@link TestSuite} for performing static syntax checks on Jelly * scripts. * * @author Kohsuke Kawaguchi */ public class JellyTestSuiteBuilder { /** * Given a jar file or a class file directory, recursively search all the * Jelly files and build a {@link TestSuite} that performs static syntax * checks. */ public static TestSuite build(File res) throws Exception { TestSuite ts = new JellyTestSuite(); final JellyClassLoaderTearOff jct = new MetaClassLoader(JellyTestSuiteBuilder.class.getClassLoader()).loadTearOff(JellyClassLoaderTearOff.class); if (res.isDirectory()) { for (final File jelly : (Collection<File>) FileUtils.listFiles(res, new String[]{"jelly"}, true)) { ts.addTest(new JellyCheck(jelly.toURI().toURL(), jct)); } } if (res.getName().endsWith(".jar")) { String jarUrl = res.toURI().toURL().toExternalForm(); JarFile jf = new JarFile(res); Enumeration<JarEntry> e = jf.entries(); while (e.hasMoreElements()) { JarEntry ent = e.nextElement(); if (ent.getName().endsWith(".jelly")) { ts.addTest(new JellyCheck(new URL("jar:" + jarUrl + "!/" + ent.getName()), jct)); } } jf.close(); } return ts; } private static class JellyCheck extends TestCase { private final URL jelly; private final JellyClassLoaderTearOff jct; public JellyCheck(URL jelly, JellyClassLoaderTearOff jct) { super(jelly.getPath()); this.jelly = jelly; this.jct = jct; } @Override protected void runTest() throws Exception { jct.createContext().compileScript(jelly); Document dom = new SAXReader().read(jelly); checkLabelFor(dom); // TODO: what else can we check statically? use of taglibs? } /** * Makes sure that <label for=...> is not used inside config.jelly * nor global.jelly */ private void checkLabelFor(Document dom) { if (isConfigJelly() || isGlobalJelly()) { try { if (!new XPath("//label[@for]").selectNodes(dom).isEmpty()) { throw new AssertionError("<label for=...> shouldn't be used because it doesn't work " + "when the configuration item is repeated. Use <label class=\"attach-previous\"> " + "to have your label attach to the previous DOM node instead."); } } catch (XPathException ex) { throw new AssertionError("XPathException evaluating \"//label[@for]\""); } } } private boolean isConfigJelly() { return jelly.toString().endsWith("/config.jelly"); } private boolean isGlobalJelly() { return jelly.toString().endsWith("/global.jelly"); } } /** * Execute all the Jelly tests in a servlet request handling context. To do * so, we reuse HudsonTestCase */ private static final class JellyTestSuite extends GroupedTest { HudsonTestCase h = new HudsonTestCase("Jelly test wrapper") { }; @Override protected void setUp() throws Exception { h.setUp(); } @Override protected void tearDown() throws Exception { h.tearDown(); } private void doTests(TestResult result) throws Exception { super.runGroupedTests(result); } @Override protected void runGroupedTests(final TestResult result) throws Exception { h.executeOnServer(new Callable<Object>() { // this code now inside a request handling thread public Object call() throws Exception { doTests(result); return null; } }); } } }