package hudson.plugins.jobConfigHistory;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Assert;
import org.xml.sax.SAXException;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import hudson.XmlFile;
import hudson.maven.MavenModuleSet;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
import hudson.security.HudsonPrivateSecurityRealm;
/**
* @author jborghi@cisco.com
*
*/
public class JobConfigHistoryIT
extends
AbstractHudsonTestCaseDeletingInstanceDir {
private WebClient webClient;
// we need to sleep between saves so we don't overwrite the history
// directories
// (which are saved with a granularity of one second)
private static final int SLEEP_TIME = 1100;
private static final FileFilter DELETE_FILTER = new FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) {
file.listFiles(this);
}
file.delete();
return false;
}
};
@Override
public void before() throws Throwable {
super.before();
jenkins.setSecurityRealm(
new HudsonPrivateSecurityRealm(true, false, null));
webClient = new WebClient();
}
public void testJobConfigHistoryPreConfigured() {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
try {
final HtmlForm form = webClient.goTo("configure")
.getFormByName("config");
form.getInputByName("maxHistoryEntries").setValueAttribute("10");
form.getInputByName("saveModuleConfiguration").setChecked(false);
form.getInputByName("skipDuplicateHistory").setChecked(false);
form.getInputByName("excludePattern")
.setValueAttribute(JobConfigHistoryConsts.DEFAULT_EXCLUDE);
form.getInputByName("historyRootDir")
.setValueAttribute("jobConfigHistory");
form.getInputByValue("never").setChecked(true);
submit(form);
} catch (Exception e) {
Assert.fail("unable to configure Jenkins instance " + e);
}
Assert.assertEquals("Verify history entries to keep setting.", "10",
jch.getMaxHistoryEntries());
Assert.assertFalse("Verify Maven module configuration setting.",
jch.getSaveModuleConfiguration());
Assert.assertFalse("Verify skip duplicate history setting.",
jch.getSkipDuplicateHistory());
Assert.assertEquals("Verify configured history root directory.",
new File(jenkins.root + "/jobConfigHistory/"
+ JobConfigHistoryConsts.DEFAULT_HISTORY_DIR),
jch.getConfiguredHistoryRootDir());
Assert.assertEquals("Verify exclude pattern setting.",
JobConfigHistoryConsts.DEFAULT_EXCLUDE,
jch.getExcludePattern());
Assert.assertEquals("Verify build badges setting.", "never",
jch.getShowBuildBadges());
final XmlFile jenkinsConfig = new XmlFile(
new File(jenkins.getRootDir(), "config.xml"));
Assert.assertTrue("Verify a system level configuration is saveable.",
jch.isSaveable(jenkins, jenkinsConfig));
Assert.assertTrue("Verify system configuration history location",
getHistoryDir(jenkinsConfig).getParentFile()
.equals(jch.getConfiguredHistoryRootDir()));
testCreateRenameDeleteProject(jch);
try {
getHistoryDir(new XmlFile(new File("/tmp")));
Assert.fail(
"Verify IAE when attempting to get history dir for a file outside of JENKINS_ROOT.");
} catch (IllegalArgumentException e) {
Assert.assertNotNull("Expected IAE", e);
}
Assert.assertFalse(
"Verify false when testing if a file outside of JENKINS_ROOT is saveable.",
jch.isSaveable(null, new XmlFile(new File("/tmp/config.xml"))));
}
public void testJobConfigHistoryDefaults() throws IOException {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
Assert.assertNull(
"Verify number of history entries to keep default setting.",
jch.getMaxHistoryEntries());
Assert.assertFalse("Verify Maven module configuration default setting.",
jch.getSaveModuleConfiguration());
Assert.assertTrue("Verify skip duplicate history default setting.",
jch.getSkipDuplicateHistory());
Assert.assertNull("Verify unconfigured exclude pattern.",
jch.getExcludePattern());
Assert.assertEquals("Verify build badges setting.", "always",
jch.getShowBuildBadges());
final XmlFile jenkinsConfig = new XmlFile(
new File(jenkins.getRootDir(), "config.xml"));
Assert.assertTrue("Verify a system level configuration is saveable.",
jch.isSaveable(jenkins, jenkinsConfig));
// This would more naturally belong in
// JobConfigHistoryTest.testIsSaveable but Mockito chokes on
// MavenModuleSet.<clinit>:
MavenModuleSet mms = createMavenProject();
Assert.assertTrue("MavenModuleSet should be saved",
jch.isSaveable(mms, mms.getConfigFile()));
Assert.assertTrue("Verify system configuration history location",
getHistoryDir(jenkinsConfig).getParentFile()
.equals(jch.getConfiguredHistoryRootDir()));
testCreateRenameDeleteProject(jch);
}
public void testSkipDuplicateHistory()
throws IOException, SAXException, Exception {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
HtmlForm form = webClient.goTo("configure").getFormByName("config");
submit(form);
final FreeStyleProject project = createFreeStyleProject("testproject");
final JobConfigHistoryProjectAction projectAction = new JobConfigHistoryProjectAction(
project);
// clear out all history - setting to 1 will clear out all with the
// expectation that we are creating a new entry
jch.setMaxHistoryEntries("1");
project.save();
Thread.sleep(SLEEP_TIME);
// reset to empty value
jch.setMaxHistoryEntries("");
project.save();
Thread.sleep(SLEEP_TIME);
// TODO: why do we have 2 entries after the first save operation?
final int jobLengthBeforeSave = projectAction.getJobConfigs().size();
for (int i = 0; i < 5; i++) {
Thread.sleep(SLEEP_TIME);
project.save();
}
Thread.sleep(SLEEP_TIME);
Assert.assertEquals(
"Verify 2 project history entry after 5 duplicate saves.",
jobLengthBeforeSave, projectAction.getJobConfigs().size());
// system history test - skip duplicate history -hardcode path to Jenkins
// config
final File jenkinsConfigDir = new File(jenkins.root,
JobConfigHistoryConsts.DEFAULT_HISTORY_DIR + "/config");
final int configLengthBeforeSave = jenkinsConfigDir
.listFiles(HistoryFileFilter.INSTANCE).length;
for (int i = 0; i < 5; i++) {
Thread.sleep(SLEEP_TIME);
jenkins.save();
}
Assert.assertEquals(
"Verify system history has still only previous entries after 5 duplicate saves.",
configLengthBeforeSave,
jenkinsConfigDir.listFiles(HistoryFileFilter.INSTANCE).length);
// verify non-duplicate history is saved
project.setDescription("new description");
project.save();
Assert.assertEquals("Verify non duplicate project history saved.",
jobLengthBeforeSave + 1, projectAction.getJobConfigs().size());
// corrupt history record and verify new entry will be saved
final File[] historyDirs = getHistoryDir(project.getConfigFile())
.listFiles(HistoryFileFilter.INSTANCE);
Arrays.sort(historyDirs, Collections.reverseOrder());
(new File(historyDirs[0], "config.xml"))
.renameTo(new File(historyDirs[0], "config"));
Assert.assertTrue(
"Verify configuration is saveable when history is corrupted.",
jch.isSaveable(project, project.getConfigFile()));
// reconfigure to allow saving duplicate history
form = webClient.goTo("configure").getFormByName("config");
form.getInputByName("skipDuplicateHistory").setChecked(false);
submit(form);
// perform additional save and verify more than one history entries
// exist
Thread.sleep(SLEEP_TIME);
jenkins.save();
project.save();
Assert.assertTrue("Verify duplicate project history entries.",
projectAction.getJobConfigs().size() >= 2);
Assert.assertTrue("Verify duplicate system history entries.",
jenkinsConfigDir
.listFiles(HistoryFileFilter.INSTANCE).length > 1);
}
public void testFormValidation() {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
try {
final HtmlForm form = webClient.goTo("configure")
.getFormByName("config");
Assert.assertFalse(
"Check no error message present for history entry.",
form.getTextContent()
.contains("Enter a valid positive integer"));
form.getInputByName("maxHistoryEntries").setValueAttribute("-2");
Assert.assertTrue("Check error message on invalid history entry.",
form.getTextContent()
.contains("Enter a valid positive integer"));
Assert.assertFalse(
"Check no error messgae present for regexp excludePattern.",
form.getTextContent().contains("Invalid regexp"));
form.getInputByName("excludePattern").setValueAttribute("**");
Assert.assertTrue(
"Check error message on invalid regexp excludePattern.",
form.getTextContent().contains("Invalid regexp"));
submit(form);
Assert.assertEquals("Verify invalid regexp string is saved.", "**",
jch.getExcludePattern());
Assert.assertNull("Verify invalid regexp has not been loaded.",
jch.getExcludeRegexpPattern());
} catch (Exception e) {
Assert.fail("Unable to complete form validation: " + e);
}
}
public void testMaxHistoryEntries() {
try {
final HtmlForm form = webClient.goTo("configure")
.getFormByName("config");
form.getInputByName("maxHistoryEntries").setValueAttribute("5");
form.getInputByName("skipDuplicateHistory").setChecked(false);
submit(form);
final FreeStyleProject project = createFreeStyleProject(
"testproject");
final JobConfigHistoryProjectAction projectAction = new JobConfigHistoryProjectAction(
project);
Assert.assertTrue("Verify at least 1 history entry created.",
projectAction.getJobConfigs().size() >= 1);
for (int i = 0; i < 3; i++) {
Thread.sleep(SLEEP_TIME);
project.save();
}
Assert.assertTrue("Verify at least 4 history entries.",
projectAction.getJobConfigs().size() >= 4);
for (int i = 0; i < 3; i++) {
Thread.sleep(SLEEP_TIME);
project.save();
}
Assert.assertEquals(
"Verify no more than 5 history entries created + 1 'Created' entry that won't be deleted.",
5 + 1, projectAction.getJobConfigs().size());
} catch (Exception e) {
Assert.fail("Unable to complete max history entries test: " + e);
}
}
public void testAbsPathHistoryRootDir() throws Exception {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
// create a unique name, then delete the empty file - will be recreated
// later
final File root = File
.createTempFile("jobConfigHistory.test_abs_path", null)
.getCanonicalFile();
final String absolutePath = root.getPath();
root.delete();
final HtmlForm form = webClient.goTo("configure")
.getFormByName("config");
form.getInputByName("historyRootDir").setValueAttribute(absolutePath);
submit(form);
Assert.assertEquals("Verify history root configured at absolute path.",
new File(root, JobConfigHistoryConsts.DEFAULT_HISTORY_DIR),
jch.getConfiguredHistoryRootDir());
// save something
createFreeStyleProject();
Assert.assertTrue("Verify history root exists.", root.exists());
// cleanup - Jenkins doesn't know about these files we created
root.listFiles(DELETE_FILTER);
root.delete();
// not really needed, but helpful so we don't clutter the test host with
// unnecessary files
Assert.assertFalse("Verify cleanup of history files: " + root,
root.exists());
}
private void testCreateRenameDeleteProject(final JobConfigHistory jch) {
try {
final FreeStyleProject project = createFreeStyleProject(
"testproject");
final File jobHistoryRootFile = new File(
jch.getConfiguredHistoryRootDir(), "jobs");
final File expectedConfigDir = new File(jobHistoryRootFile,
"testproject");
Assert.assertEquals("Verify history dir configured as expected.",
expectedConfigDir, getHistoryDir(project.getConfigFile()));
Assert.assertTrue(
"Verify project config history directory created: "
+ expectedConfigDir,
expectedConfigDir.exists());
// since sometimes two history entries are created, we just check
// if one of them contains "Created"
boolean createdEntryFound = false;
for (File file : expectedConfigDir
.listFiles(HistoryFileFilter.INSTANCE)) {
if (new XmlFile(new File(file, "history.xml")).asString()
.contains("Created")) {
createdEntryFound = true;
break;
}
}
Assert.assertTrue(
"Verify one \'created\' history entry on creation.",
createdEntryFound);
final int historyEntryCount = expectedConfigDir
.listFiles(HistoryFileFilter.INSTANCE).length;
// sleep so we don't overwrite our existing history directory
Thread.sleep(SLEEP_TIME);
project.renameTo("renamed_testproject");
// verify rename moves the history directory as expected
Assert.assertFalse(
"Verify on rename old project config history directory removed.",
expectedConfigDir.exists());
final File newExpectedConfigDir = new File(expectedConfigDir
.toString().replace("testproject", "renamed_testproject"));
Assert.assertTrue(
"Verify renamed project config history created: "
+ newExpectedConfigDir,
newExpectedConfigDir.exists());
Assert.assertEquals("Verify two history entries after rename.",
historyEntryCount + 1, newExpectedConfigDir
.listFiles(HistoryFileFilter.INSTANCE).length);
// delete project and verify the history directory is gone
project.delete();
Assert.assertFalse(
"Verify on delete project config history directory removed(renamed): "
+ newExpectedConfigDir,
newExpectedConfigDir.exists());
String deletedDir = null;
for (File file : expectedConfigDir.getParentFile().listFiles()) {
if (file.getName().contains("renamed_testproject"
+ DeletedFileFilter.DELETED_MARKER)) {
deletedDir = file.getPath();
break;
}
}
Assert.assertTrue(
"Verify config history directory of deleted job exists.",
deletedDir != null);
Assert.assertTrue(
"Verify config history directory of deleted job is not empty",
(new File(deletedDir)).listFiles().length > 0);
boolean deletedEntryFound = false;
for (File file : (new File(deletedDir))
.listFiles(HistoryFileFilter.INSTANCE)) {
if (new XmlFile(new File(file, "history.xml")).asString()
.contains("Deleted")) {
deletedEntryFound = true;
break;
}
}
Assert.assertTrue("Verify one \'deleted\' history entry exists.",
deletedEntryFound);
} catch (IOException e) {
Assert.fail(
"Unable to complete project creation/rename test: " + e);
} catch (InterruptedException e) {
Assert.fail("Interrupted, unable to test project deletion: " + e);
}
}
/**
* Tests if project can still be built after the config history root dir has
* been changed. (I.e. the project exists but has no configs.)
*/
public void testChangedRootDir() {
try {
final FreeStyleProject project = createFreeStyleProject("bla");
final JobConfigHistoryProjectAction projectAction = new JobConfigHistoryProjectAction(
project);
Assert.assertTrue("Verify project history entry is not empty.",
projectAction.getJobConfigs().size() > 0);
final HtmlForm form = webClient.goTo("configure")
.getFormByName("config");
form.getInputByName("historyRootDir").setValueAttribute("newDir");
submit(form);
Assert.assertEquals("Verify project history entry is empty.", 0,
projectAction.getJobConfigs().size());
assertBuildStatus(Result.SUCCESS, project.scheduleBuild2(0).get());
project.save();
Thread.sleep(SLEEP_TIME);
Assert.assertTrue("Verify project history entry is not empty.",
projectAction.getJobConfigs().size() > 0);
assertBuildStatus(Result.SUCCESS, project.scheduleBuild2(0).get());
} catch (Exception e) {
Assert.fail("Unable to complete changed root dir test: " + e);
}
}
public void testInputOfMaxHistoryEntries() {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
// check good value
jch.setMaxHistoryEntries("5");
Assert.assertEquals("Verify maxHistoryEntries set to 5", "5",
jch.getMaxHistoryEntries());
// check negative value
jch.setMaxHistoryEntries("-1");
Assert.assertEquals("Verify maxHistoryEntries still set to 5", "5",
jch.getMaxHistoryEntries());
// check non-number value
jch.setMaxHistoryEntries("K");
Assert.assertEquals("Verify maxHistoryEntries still set to 5", "5",
jch.getMaxHistoryEntries());
// check empty value
jch.setMaxHistoryEntries("");
Assert.assertEquals("Verify maxHistoryEntries empty", "",
jch.getMaxHistoryEntries());
}
public void testInputOfMaxDaysToKeepEntries() {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
// check good value
jch.setMaxDaysToKeepEntries("5");
Assert.assertEquals("Verify maxDaysToKeepEntries set to 5", "5",
jch.getMaxDaysToKeepEntries());
// check negative value
jch.setMaxDaysToKeepEntries("-1");
Assert.assertEquals("Verify maxDaysToKeepEntries still set to 5", "5",
jch.getMaxDaysToKeepEntries());
// check non-number value
jch.setMaxDaysToKeepEntries("K");
Assert.assertEquals("Verify maxDaysToKeepEntries still set to 5", "5",
jch.getMaxDaysToKeepEntries());
// check empty value
jch.setMaxDaysToKeepEntries("");
Assert.assertEquals("Verify maxDaysToKeepEntries empty", "",
jch.getMaxDaysToKeepEntries());
}
private File getHistoryDir(XmlFile xmlFile) {
final JobConfigHistory jch = jenkins.getPlugin(JobConfigHistory.class);
final File configFile = xmlFile.getFile();
return ((FileHistoryDao) PluginUtils.getHistoryDao(jch))
.getHistoryDir(configFile);
}
}