Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add limit to test histories #1493

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ There are more properties which can be used to tweak parts of FitNesse:
* '''VersionsController.days''' - number of days to keep old page versions around when using the Zip file based versions controller.
* '''test.history.days''' - The number of days to keep test results around. Cleaned up after a new test run.
* '''test.history.path''' - Location to store the test results. The default location is ''!-FitNesseRoot-!/files/testResults''.
* '''TestHistory.maxCount''' - The number of Test Histories that will be kept for each individual page.
* '''TestHistory.purgeTime''' - A comma separated list of numbers for purge options on the ''Test History'' page.
* Any variable that can be defined on a wiki page.

The Slim test system has a set of [[custom properties][<UserGuide.WritingAcceptanceTests.SliM]] that can either be set on a page or in the configuration file.
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ The test result files are named using the following scheme !style_code(YYYYMMDDH

The test files contain the XML that describes the test run. The format of this XML is identical to the XML packet returned by the format=xml flag when you run a test. (See <UserGuide.RestfulTests).

You also have the possibility to disable !style_code(Test Histories) for a selected page by going to the !style_code(?properties) page and check the ''disableTestHistory'' option.

!4 Purging
There are buttons at the top of the ''Test History'' page that allow you to purge old history files. You have your choice of ''all'', ''>7 days'', or ''>30 days''. If you want to purge a different number of days, you can use the RESTful URL form. (See [[!-RestfulServices-!][<UserGuide.AdministeringFitNesse.RestfulServices]]).
There are buttons at the top of the ''Test History'' page that allow you to purge old history files. You have your choice of ''all'', ''> 30 days'', ''> 60 days'', or ''> 90 days''. If you want to purge a different number of days, you can change the ''TestHistory.purgeTime'' in the [[configuration file][<UserGuide.AdministeringFitNesse.ConfigurationFile]] to allow additional purge times or you can use the RESTful URL form. (See [[!-RestfulServices-!][<UserGuide.AdministeringFitNesse.RestfulServices]])

You can also clean up the test history right after a test execution. To do so, configure a property ''test.history.days'' in the [[configuration file][<UserGuide.AdministeringFitNesse.ConfigurationFile]] or as a [[page variable][<UserGuide.FitNesseWiki.MarkupLanguageReference.MarkupVariables]] and assign it the number of days you want to keep history.
Additionally to this, you can configure a number to the property ''TestHistory.maxCount''. This will define how many histories will be kept for each page.

!4 Comparing History
When viewing the history for a page, you can select any two test results by clicking in their checkboxes. Then, if you click the ''Compare'' button, you will be shown the two test results side-by-side along with an indicator that tells you if the results are identical or not.
Expand Down
10 changes: 10 additions & 0 deletions plugins.properties
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,13 @@ CustomComparators=inverse:fitnesse.slim.test.InverseComparator
##
# Number of days to keep history
test.history.days=1

##
# Deletes Test Histories that exceeds the given number. They are deleted when new histories are being created.
#TestHistory.maxCount=10

##
# The given list of numbers represent the time values for purging test histories.
# Test histories older than the given number are being deleted.
# The value 0 represents 'Purge all'.
#TestHistory.purgeTime=0,30,60,90
4 changes: 3 additions & 1 deletion src/fitnesse/ConfigurationParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public enum ConfigurationParameter {
CONTEXT_ROOT("ContextRoot"),
LOCALHOST_ONLY("LocalhostOnly"),
MAXIMUM_WORKERS("MaximumWorkers"),
THEME("Theme");
THEME("Theme"),
TESTHISTORY_MAX_COUNT("TestHistory.maxCount"),
PURGE_TIME("TestHistory.purgeTime");

private static final Logger LOG = Logger.getLogger(ConfigurationParameter.class.getName());

Expand Down
52 changes: 52 additions & 0 deletions src/fitnesse/reporting/history/HistoryPurger.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -94,4 +96,54 @@ private Date tryExtractDateFromTestHistoryName(String testHistoryName) throws Pa
String dateString = testHistoryName.split("_")[0];
return dateFormat.parse(dateString);
}

public void deleteTestHistoryByCount(WikiPagePath path, int testHistoryCount) {
String pageName = path.toString();
String subPagePrefix = pageName + ".";
File[] files = FileUtil.getDirectoryListing(resultsDirectory);
for (File file : files) {
String fileName = file.getName();
if (fileName.equals(pageName) || fileName.startsWith(subPagePrefix)) {
deleteIfCountReached(file, testHistoryCount);
}
}
}

private void deleteIfCountReached(File file, int testHistoryCount) {
try {
if (file.isDirectory()) {
deleteDirectoryIfCountReached(file, testHistoryCount);
} else {
deleteFilesIfCountReached(new File[] {file}, testHistoryCount);
}
} catch (IOException e) {
LOG.log(Level.SEVERE, e.getMessage(), e);
LOG.log(Level.INFO, format("Unable to remove test history file %s", file.getPath()));
}
}

private void deleteDirectoryIfCountReached(File file, int testHistoryCount) throws IOException {
File[] files = FileUtil.listFiles(file);
if(testHistoryCount > 0) {
deleteFilesIfCountReached(files, testHistoryCount);
}
// Deleting the folder if it is empty
if (FileUtil.isEmpty(file)) {
FileUtil.deleteFileSystemDirectory(file);
}
}

private void deleteFilesIfCountReached(File[] files, int testHistoryCount) throws IOException {
// Sorting the files to have them in ascending order of creation
Arrays.sort(files, Comparator.comparingLong(file -> getDateFromPageHistoryFileName(file.getName()).getTime()));
// Only delete histories when there are more histories than the count expects
if((files.length - testHistoryCount) > 0) {
File[] filesToDelete = new File[files.length - testHistoryCount];
// Putting all files up to the count in a list for deletion
System.arraycopy(files, 0, filesToDelete, 0, files.length - testHistoryCount);
for (File fileToDelete : filesToDelete) {
FileUtil.deleteFile(fileToDelete);
}
}
}
}
16 changes: 15 additions & 1 deletion src/fitnesse/reporting/history/TestXmlFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.reporting.history;

import fitnesse.ConfigurationParameter;
import fitnesse.FitNesseContext;
import fitnesse.reporting.BaseFormatter;
import fitnesse.testrunner.WikiTestPageUtil;
Expand All @@ -20,7 +21,10 @@
import fitnesse.util.TimeMeasurement;
import fitnesse.wiki.PageData;
import fitnesse.wiki.WikiPage;
import fitnesse.wiki.WikiPageProperty;
import fitnesse.wiki.WikiPageUtil;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;

Expand Down Expand Up @@ -174,7 +178,17 @@ protected void setTotalRunTimeOnReport(TimeMeasurement totalTimeMeasurement) {
}

protected void writeResults() throws IOException {
writeResults(writerFactory.getWriter(context, getPage(), getPageCounts(), totalTimeMeasurement.startedAt()));
if (!getPage().getData().getProperties().has(WikiPageProperty.DISABLE_TESTHISTORY)) {
writeResults(writerFactory.getWriter(context, getPage(), getPageCounts(), totalTimeMeasurement.startedAt()));
}

// Delete histories if they exceed the max count for the page
String testhistoryMaxCount = context.getProperties().getProperty(ConfigurationParameter.TESTHISTORY_MAX_COUNT.getKey());
if(testhistoryMaxCount != null && StringUtils.isNumeric(testhistoryMaxCount)) {
// The given number of days (0) is irrelevant here since we purge the history by their amount
HistoryPurger historyPurger = new HistoryPurger(context.getTestHistoryDirectory(), 0);
historyPurger.deleteTestHistoryByCount(getPage().getFullPath(), Integer.parseInt(testhistoryMaxCount));
}
}

@Override
Expand Down
31 changes: 31 additions & 0 deletions src/fitnesse/resources/javascript/fitnesse.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,37 @@ $(document)
}
});

// When checking purgeGlobal then change the href elements to send true
$(document)
.on('change', '.testHistory #purgeGlobal', function() {
const purgeGlobal = "&purgeGlobal=true";
let elems = $("a[href^='?responder=purgeHistory']");

if(this.checked) {
elems.each((index, link) => {
// Only adjust the href if it was not already adjusted
if(!link.href.includes(purgeGlobal)) {
link.href = link.href.substring(link.href.indexOf("?")) + purgeGlobal;
}
});
} else {
elems.each((index, link) => {
// Only adjust the href if it was adjusted before
if(link.href.includes(purgeGlobal)) {
link.href = link.href.substring(link.href.indexOf("?"), link.href.length - purgeGlobal.length);
}
});
}
});

// When clicking on a purge link then ask before deletion
function purgeConfirmation(event) {
if(!confirm('Are you sure you want to purge the test histories?')) {
event.preventDefault();
return false;
}
}

/**
* Notify user when changing page while test execution is in progress.
*/
Expand Down
9 changes: 9 additions & 0 deletions src/fitnesse/resources/templates/propertiesPage.vm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@
<label for="$security"><input type="checkbox" id="$security" name="$security"#checked( $security )/>$security</label>
#end
</fieldset>

#if ($testhistoryTypes.size() > 0)
<fieldset>
<legend>Test History:</legend>
#foreach( $testhistoryType in $testhistoryTypes )
<label for="$testhistoryType"><input type="checkbox" id="$testhistoryType" name="$testhistoryType"#checked( $testhistoryType )/>$testhistoryType</label>
#end
</fieldset>
#end
</div>

<div class="virtual-wiki-properties">
Expand Down
9 changes: 6 additions & 3 deletions src/fitnesse/resources/templates/testHistory.vm
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
#set($noHistory = true)
<div>
<a class="button" href="?responder=overview">View as Overview</a>
<a class="button" href="?responder=purgeHistory&days=30">Purge &gt; 30 days</a>
<a class="button" href="?responder=purgeHistory&days=7">Purge &gt; 7 days</a>
<a class="button" href="?responder=purgeHistory&days=0">Purge all</a>
<a class="button" href="$viewLocation">Cancel</a>
<label for="hidePassedTests"><input type="checkbox" id="hidePassedTests"/>Hide passed tests</label>
</div>
<div>
#foreach($purgeTime in $purgeTimes)
<a class="button" href="?responder=purgeHistory&days=$purgeTime" onclick="purgeConfirmation(event)">Purge#if ($purgeTime == 0) all#else &gt; $purgeTime days#end</a>
#end
<label for="purgeGlobal"><input type="checkbox" id="purgeGlobal" />Purge global</label>
</div>

<table>
<tr>
Expand Down
10 changes: 9 additions & 1 deletion src/fitnesse/responders/editing/PropertiesResponder.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@
import java.util.List;
import java.util.Set;

import static fitnesse.ConfigurationParameter.TESTHISTORY_MAX_COUNT;
import static fitnesse.wiki.PageData.ACTION_ATTRIBUTES;
import static fitnesse.wiki.PageData.NAVIGATION_ATTRIBUTES;
import static fitnesse.wiki.PageData.PAGE_TYPE_ATTRIBUTES;
import static fitnesse.wiki.PageData.SECURITY_ATTRIBUTES;
import static fitnesse.wiki.PageData.TESTHISTORY_ATTRIBUTES;
import static fitnesse.wiki.PageType.SUITE;
import static fitnesse.wiki.PageType.TEST;
import static fitnesse.wiki.WikiPageProperty.DISABLE_TESTHISTORY;
import static fitnesse.wiki.WikiPageProperty.EDIT;
import static fitnesse.wiki.WikiPageProperty.FILES;
import static fitnesse.wiki.WikiPageProperty.HELP;
Expand Down Expand Up @@ -99,7 +102,7 @@ private JSONObject makeJson() {
EDIT, PROPERTIES, VERSIONS, REFACTOR,
WHERE_USED, RECENT_CHANGES, SUITE.toString(),
PRUNE, SECURE_READ, SECURE_WRITE,
SECURE_TEST, FILES };
SECURE_TEST, FILES, DISABLE_TESTHISTORY };
for (String attribute : attributes)
addJsonAttribute(jsonObject, attribute);
if (pageData.hasAttribute(HELP)) {
Expand Down Expand Up @@ -180,6 +183,7 @@ private void makePropertiesForm() {
makeTestActionCheckboxesHtml();
makeNavigationCheckboxesHtml();
makeSecurityCheckboxesHtml();
makeTestHistoryCheckboxesHtml();
}

public void makePageTypeRadiosHtml(PageData pageData) {
Expand Down Expand Up @@ -262,6 +266,10 @@ public void makeSecurityCheckboxesHtml() {
html.put("securityTypes", SECURITY_ATTRIBUTES);
}

public void makeTestHistoryCheckboxesHtml() {
html.put("testhistoryTypes", TESTHISTORY_ATTRIBUTES);
}


public static class Symlink {

Expand Down
22 changes: 19 additions & 3 deletions src/fitnesse/responders/testHistory/PurgeHistoryResponder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.File;

import org.apache.commons.lang3.StringUtils;

import fitnesse.FitNesseContext;
import fitnesse.authentication.AlwaysSecureOperation;
import fitnesse.authentication.SecureOperation;
Expand All @@ -11,6 +13,7 @@
import fitnesse.http.SimpleResponse;
import fitnesse.reporting.history.HistoryPurger;
import fitnesse.responders.ErrorResponder;
import fitnesse.wiki.WikiPagePath;

public class PurgeHistoryResponder implements SecureResponder {

Expand All @@ -31,13 +34,26 @@ private SimpleResponse makeValidResponse() {
}

private void purgeHistory(Request request, FitNesseContext context) {
WikiPagePath currentPath = new WikiPagePath(request.getResource().split("\\."));
String purgeGlobalInput = request.getInput("purgeGlobal");

File resultsDirectory = context.getTestHistoryDirectory();
int days = getDaysInput(request);
deleteTestHistoryOlderThanDays(resultsDirectory, days);
// If purgeGlobal is not set then only delete current path
if (StringUtils.isBlank(purgeGlobalInput) || !Boolean.parseBoolean(purgeGlobalInput)) {
deleteTestHistoryOlderThanDays(resultsDirectory, days, currentPath);
} else {
deleteTestHistoryOlderThanDays(resultsDirectory, days, null);
}
}

public void deleteTestHistoryOlderThanDays(File resultsDirectory, int days) {
new HistoryPurger(resultsDirectory, days).deleteTestHistoryOlderThanDays();
public void deleteTestHistoryOlderThanDays(File resultsDirectory, int days, WikiPagePath path) {
HistoryPurger historyPurger = new HistoryPurger(resultsDirectory, days);
if (path != null) {
historyPurger.deleteTestHistoryOlderThanDays(path);
} else {
historyPurger.deleteTestHistoryOlderThanDays();
}
}

private Integer getDaysInput(Request request) {
Expand Down
5 changes: 5 additions & 0 deletions src/fitnesse/responders/testHistory/TestHistoryResponder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

import fitnesse.reporting.history.TestHistory;
import fitnesse.wiki.PathParser;

import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;

import fitnesse.ConfigurationParameter;
import fitnesse.FitNesseContext;
import fitnesse.authentication.SecureOperation;
import fitnesse.authentication.SecureReadOperation;
Expand Down Expand Up @@ -43,6 +46,8 @@ private Response makeTestHistoryResponse(TestHistory testHistory, Request reques
page.setNavTemplate("viewNav");
page.put("viewLocation", request.getResource());
page.put("testHistory", testHistory);
String purgeTimes = context.getProperties().getProperty(ConfigurationParameter.PURGE_TIME.getKey());
page.put("purgeTimes", StringUtils.isBlank(purgeTimes) ? new String[] { "0", "30", "60", "90" } : purgeTimes.split(","));
page.setMainTemplate("testHistory");
SimpleResponse response = new SimpleResponse();
response.setContent(page.html(request));
Expand Down
5 changes: 4 additions & 1 deletion src/fitnesse/wiki/PageData.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.stream.Stream;

import static fitnesse.wiki.PageType.*;

Expand Down Expand Up @@ -55,7 +56,9 @@ public class PageData implements ReadOnlyPageData, Serializable {
public static final String[] NAVIGATION_ATTRIBUTES = {
WikiPageProperty.RECENT_CHANGES, WikiPageProperty.FILES, WikiPageProperty.SEARCH };

public static final String[] NON_SECURITY_ATTRIBUTES = ArrayUtils.addAll(ACTION_ATTRIBUTES, NAVIGATION_ATTRIBUTES);
public static final String[] TESTHISTORY_ATTRIBUTES = { WikiPageProperty.DISABLE_TESTHISTORY };

public static final String[] NON_SECURITY_ATTRIBUTES = Stream.of(ACTION_ATTRIBUTES, NAVIGATION_ATTRIBUTES, TESTHISTORY_ATTRIBUTES).flatMap(Stream::of).toArray(String[]::new);

@Deprecated
public static final String PropertySECURE_READ = WikiPageProperty.SECURE_READ;
Expand Down
1 change: 1 addition & 0 deletions src/fitnesse/wiki/WikiPageProperty.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class WikiPageProperty implements Serializable {
public static final String VERSIONS = "Versions";
public static final String EDIT = "Edit";
public static final String SUITES = "Suites";
public static final String DISABLE_TESTHISTORY = "DisableTestHistory";

public static final String SECURE_READ = "secure-read";
public static final String SECURE_WRITE = "secure-write";
Expand Down
Loading