/**
*
*/
package hudson.plugins.jswidgets;
import static org.junit.Assert.assertArrayEquals;
import hudson.model.Hudson;
import hudson.model.Project;
import hudson.model.User;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.HudsonTestCase;
import org.jvnet.hudson.test.recipes.LocalData;
import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.JavaScriptPage;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
public class PluginTest extends HudsonTestCase {
/** Our logger. */
private static final Logger LOG = Logger.getLogger(PluginTest.class.getName());
/**
*
*/
private static final int TOTAL_NUMBER_OF_RUNS = 7;
private static final String JAVA_SCRIPT_NEEDLE = "document.write(";
private WebClient webClient;
@Override
protected void setUp() throws Exception {
super.setUp();
webClient = createWebClient();
}
/** {@inheritDoc}.
* Deletes the hudson instance directory on teardown to avoid leakage of testdirectories.
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
final File rootDir = hudson.getRootDir();
LOG.info("Deleting " + rootDir + " in tearDown");
FileUtils.deleteDirectory(rootDir);
}
/**
* Test method for an existing job without any builds.
*
* @throws IOException
* @throws SAXException
*/
@LocalData
public void testJsHealthWithoutBuilds() throws IOException, SAXException {
final String greyIcon = "16x16/grey.gif";
final String relative = "/job/foo/" + JsConsts.URLNAME + "/health/";
final String jobDescription = "Just a small instance for testing";
checkJavaScriptOutput(greyIcon, relative);
checkJavaScriptOutput(jobDescription, relative);
checkHtmlOutput(greyIcon, relative);
checkHtmlOutput(jobDescription, relative);
}
@LocalData
public void testJsHealthWithBuilds() throws IOException, SAXException {
final String blueIcon = "16x16/blue.gif";
final String relative = "/job/bar/" + JsConsts.URLNAME + "/health/";
checkJavaScriptOutput(blueIcon, relative);
checkHtmlOutput(blueIcon, relative);
checkRowCount(webClient.goTo(relative + "?html=true"), 3);
}
@LocalData
public void testBuildHistoryWhereCountIsNull() throws IOException, SAXException {
final String htmlNeedle = "<div align=\"center\">";
final String relative = "/" + JsConsts.URLNAME + "/runs/";
checkHtmlOutput(htmlNeedle, relative);
checkJavaScriptOutput(htmlNeedle, relative);
checkRowCount(webClient.goTo(relative + "?html=true"), TOTAL_NUMBER_OF_RUNS);
}
/**
* @param htmlPage
* @param expectedRows
*/
private void checkRowCount(final HtmlPage htmlPage, final int expectedRows) {
final String xml = htmlPage.asXml();
final Pattern pattern = Pattern.compile("<tr>");
final Matcher matcher = pattern.matcher(xml);
int foundRows = 0;
while (matcher.find()) {
foundRows++;
}
assertEquals(xml, expectedRows, foundRows);
}
@LocalData
public void testBuildHistoryWhereCountIsGiven() throws IOException, SAXException {
checkRowCount(webClient.goTo("/" + JsConsts.URLNAME + "/runs/" + "?html=true&count=2"), 2);
}
@LocalData
public void testBuildHistoryWhereCountGreaterThanSizeOfRunlist() throws IOException, SAXException {
checkRowCount(webClient.goTo("/" + JsConsts.URLNAME + "/runs/" + "?html=true&count=100"), TOTAL_NUMBER_OF_RUNS);
}
@LocalData
public void testJsJobIndexAction() throws IOException, SAXException {
webClient.setJavaScriptEnabled(false); // TODO webClient chokes on the jshealth javascript right now
checkSubJelly("/job/foo", "health");
}
@LocalData
public void testJsBuildActionWithChanges() throws IOException, SAXException {
final String buildPath = "/job/svntest/4";
final String changesJelly = "changes";
final String changeLogNeedle = "Now with more text";
// Built on removed slave.
final String nodeName = "UNKNOWN";
testJsBuildAction(buildPath, changesJelly, changeLogNeedle, nodeName);
// test JsBuildAction.getChangeSetEntries
testJsBuildAction(buildPath, changesJelly, "#/trunk/foo", nodeName);
}
@Bug(4889)
@LocalData
public void testJsBuildActionWithChangesAfterReloadOfConfiguration() throws IOException, SAXException {
final String jobName = "bar";
final String jobPath = "/job/" + jobName + "/";
final String build3 = jobPath + "1";
checkJsWidgetsOnlyOnce(build3);
@SuppressWarnings("unchecked")
final Project project = Hudson.getInstance().getItemByFullName(jobName, Project.class);
project.save();
new JsJobAction(project);
checkJsWidgetsOnlyOnce(build3);
}
@Bug(5106)
@LocalData
public void testJsBuildActionWithAnApostroph() throws IOException, SAXException {
webClient.setJavaScriptEnabled(false); // TODO webClient chokes on the jshealth javascript right now
final String relative = "/job/bar/" + JsConsts.URLNAME + "/health/";
checkHtmlOutput("job with '3' builds", relative);
checkJavaScriptOutput("job with \\'3\\' builds", relative);
}
@LocalData
public void testJsBuildActionWithOutChanges() throws IOException, SAXException {
final String buildPath = "/job/svntest/2";
final String changesJelly = "changes";
final String changeLogNeedle = "No changes in this build";
final String nodeName = "master Hudson node";
testJsBuildAction(buildPath, changesJelly, changeLogNeedle, nodeName);
}
@LocalData
public void testJsRunListener() throws IOException, SAXException, InterruptedException {
final String buildPath = "/job/bar/4";
final String changesJelly = "changes";
final String changeLogNeedle = "No changes in this build";
final String nodeName = "master Hudson node";
webClient.goTo("/job/bar/build");
// Sleep 3 seconds to account for fast machines as the build might not be finished during test phase.
Thread.sleep(TimeUnit.MILLISECONDS.convert(5, TimeUnit.SECONDS));
testJsBuildAction(buildPath, changesJelly, changeLogNeedle, nodeName);
}
@LocalData
public void testJsProjectActionFactory() {
@SuppressWarnings("unchecked")
final List<Project> projects = Hudson.getInstance().getProjects();
assertTrue("Have not projects", projects.size() > 0);
@SuppressWarnings("unchecked")
final Project firstProject = projects.get(0);
new JsProjectActionFactory().createFor(firstProject);
new JsProjectActionFactory().createFor(firstProject);
assertEquals(1, firstProject.getActions(JsJobAction.class).size());
}
public void testSCMWithoutAffectedFilesImplementation() {
final Entry entry = new ChangeLogSet.Entry() {
@Override
public String getMsg() {
return "Jepp";
}
@Override
public User getAuthor() {
return null;
}
@Override
public Collection<String> getAffectedPaths() {
return Arrays.asList("/trunk/foo");
}
};
final JsBuildAction buildAction = new JsBuildAction(null);
assertArrayEquals(entry.getAffectedPaths().toArray(), buildAction.getChangeSetEntries(entry).toArray());
}
/**
* @param buildPath
* @param changesJelly
* @param changeLogNeedle
* @param nodeName
* @throws IOException
* @throws SAXException
*/
private void testJsBuildAction(final String buildPath, final String changesJelly, final String changeLogNeedle,
final String nodeName) throws IOException, SAXException {
webClient.setJavaScriptEnabled(false); // TODO webClient chokes on the javascript right now
checkSubJelly(buildPath, changesJelly);
final String indexPath = buildPath + "/" + JsConsts.URLNAME;
// Built on removed slave.
checkHtmlOutput(nodeName, indexPath);
final String changesPath = indexPath + "/changes/";
checkHtmlOutput(changeLogNeedle, changesPath);
checkJavaScriptOutput(changeLogNeedle, changesPath);
}
/**
* Checks the existence of the index-jelly entry and an additional specialized jelly referenced by jellyPath.
*
* @param objectPath
* @param jellyPath
* @throws IOException
* @throws SAXException
*/
private void checkSubJelly(final String objectPath, final String jellyPath) throws IOException, SAXException {
final HtmlPage jobPage = webClient.goTo(objectPath);
checkXpath(jobPage, "//img[contains(@src,\"" + JsConsts.ICONFILENAME + "\")]");
checkXpath(jobPage, "//a[contains(@href, \"" + objectPath + "/" + JsConsts.URLNAME + "\")]");
final String href = objectPath + "/" + JsConsts.URLNAME;
final HtmlPage jsIndexPage = (HtmlPage) jobPage.getAnchorByHref(href).click();
checkXpath(jsIndexPage, "//script[@type=\"text/javascript\" and contains(@src, \"" + href + "/" + jellyPath + "\")]");
}
/**
* @param htmlPage
* @param needleXPath
*/
private void checkXpath(final HtmlPage htmlPage, final String needleXPath) {
assertNotNull(needleXPath + " not found in " + htmlPage.asXml(), htmlPage.getFirstByXPath(needleXPath));
}
/**
* @param htmlNeedle
* @param htmlPage
* @throws SAXException
* @throws IOException
*/
private void checkHtmlOutput(final String htmlNeedle, final String relative) throws IOException, SAXException {
final HtmlPage htmlPage = webClient.goTo(relative + "?html=true");
checkHtmlOutput(htmlNeedle, htmlPage);
}
/**
* @param htmlNeedle
* @param htmlPage
*/
private void checkHtmlOutput(final String htmlNeedle, final HtmlPage htmlPage) {
final String html = htmlPage.asXml();
assertTrue(htmlNeedle + " not found in " + html, html.contains(htmlNeedle));
assertFalse(JAVA_SCRIPT_NEEDLE + " found in " + html, html.contains(JAVA_SCRIPT_NEEDLE));
}
/**
* @param htmlNeedle
* @param relative
* @throws IOException
* @throws SAXException
*/
private void checkJavaScriptOutput(final String htmlNeedle, final String relative) throws IOException, SAXException {
final JavaScriptPage jsPage = (JavaScriptPage) webClient.goTo(relative, "text/javascript");
checkJavaScriptOutput(htmlNeedle, jsPage);
}
/**
* @param htmlNeedle
* @param jsPage
*/
private void checkJavaScriptOutput(final String htmlNeedle, final JavaScriptPage jsPage) {
final String javaScript = jsPage.getContent().trim();
assertTrue(htmlNeedle + " not found in " + javaScript, javaScript.contains(htmlNeedle));
assertTrue(javaScript + " does not start with " + JAVA_SCRIPT_NEEDLE, javaScript.startsWith(JAVA_SCRIPT_NEEDLE));
}
/**
* Checks that the jsindex.png is only referenced once on the page.
* @param build
* @throws IOException
* @throws SAXException
*/
private void checkJsWidgetsOnlyOnce(final String build) throws IOException, SAXException {
final HtmlPage buildPage = webClient.goTo(build);
final String body = buildPage.asXml();
final String needle = "/plugin/jswidgets/img/jsindex.png";
assertEquals(body + " has more than one jsindex.png", body.indexOf(needle), body.lastIndexOf(needle));
}
}