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/limit test histories #1510

Merged
merged 5 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -178,6 +178,7 @@ There are more properties which can be used to tweak parts of FitNesse:
* '''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.purgeOptions''' - A comma separated list of the age, in number of days, to offer as purge options on the ''Test History'' page.
* '''TestHistory.maxCount''' - The number of histories to keep around per page. Cleaned up after a new test run.
* 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 @@ -14,7 +14,7 @@ The test files contain the XML that describes the test run. The format of this
!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 change the ''TestHistory.purgeOptions'' in the [[configuration file][<UserGuide.AdministeringFitNesse.ConfigurationFile]] to allow additional purge options, 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.
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, you can configure the property ''TestHistory.maxCount'' in the [[configuration file][<UserGuide.AdministeringFitNesse.ConfigurationFile]] to limit the number of histories to keep.

!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
5 changes: 5 additions & 0 deletions plugins.properties
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,8 @@ test.history.days=1
# The value 0 represents 'Purge all'.
# Enabling the property without setting any value, removes the offered options completely.
#TestHistory.purgeOptions=0,7,30

##
# The number represents how many test histories will be kept per page.
# Test histories will be deleted when new histories are being created.
#TestHistory.maxCount=30
3 changes: 2 additions & 1 deletion src/fitnesse/ConfigurationParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public enum ConfigurationParameter {
LOCALHOST_ONLY("LocalhostOnly"),
MAXIMUM_WORKERS("MaximumWorkers"),
THEME("Theme"),
PURGE_OPTIONS("TestHistory.purgeOptions");
PURGE_OPTIONS("TestHistory.purgeOptions"),
TESTHISTORY_MAX_COUNT("TestHistory.maxCount");

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

Expand Down
55 changes: 49 additions & 6 deletions src/fitnesse/reporting/history/HistoryPurger.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
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;

import org.apache.commons.lang3.StringUtils;

import static java.lang.String.format;

public class HistoryPurger {
private static final Logger LOG = Logger.getLogger(HistoryPurger.class.getName());

private final File resultsDirectory;
private final Date expirationDate;
private Integer testhistoryCount;

public HistoryPurger(File resultsDirectory, int days) {
this.resultsDirectory = resultsDirectory;
Expand All @@ -37,14 +42,34 @@ public void deleteTestHistoryOlderThanDays(WikiPagePath path) {
for (File file : files) {
String fileName = file.getName();
if (fileName.equals(pageName) || fileName.startsWith(subPagePrefix)) {
deleteIfExpired(file);
delete(file);
}
}
}

public void deleteTestHistoryByCount(WikiPagePath path, String testhistoryMaxCount) {
if (testhistoryMaxCount == null
|| !StringUtils.isNumeric(testhistoryMaxCount)) {
LOG.fine(
"The given testhistoryMaxCount must not be null and it has to be a valid number");
return;
}

this.testhistoryCount = Integer.parseInt(testhistoryMaxCount);
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)) {
delete(file);
}
}
}

private void deleteExpiredFiles(File[] files) {
for (File file : files)
deleteIfExpired(file);
delete(file);
}

public Date getDateDaysAgo(int days) {
Expand All @@ -54,20 +79,24 @@ public Date getDateDaysAgo(int days) {
return daysEarlier;
}

private void deleteIfExpired(File file) {
private void delete(File file) {
try {
if (file.isDirectory()) {
deleteDirectoryIfExpired(file);
deleteDirectory(file);
} else
deleteFileIfExpired(file);
} catch (IOException e) {
LOG.log(Level.INFO, format("Unable to remove test history file %s", file.getPath()));
}
}

private void deleteDirectoryIfExpired(File file) throws IOException {
private void deleteDirectory(File file) throws IOException {
File[] files = FileUtil.listFiles(file);
deleteExpiredFiles(files);
if(testhistoryCount != null) {
deleteFilesIfCountReached(files);
} else {
deleteExpiredFiles(files);
}
if (FileUtil.isEmpty(file)) {
FileUtil.deleteFileSystemDirectory(file);
}
Expand All @@ -80,6 +109,20 @@ private void deleteFileIfExpired(File file) throws IOException {
FileUtil.deleteFile(file);
}

private void deleteFilesIfCountReached(File[] files) throws IOException {
// Only delete histories when there are more histories than the count expects
if((files.length - this.testhistoryCount) > 0) {
// Sorting the files to have them in ascending order of creation
Arrays.sort(files, Comparator.comparing(file -> getDateFromPageHistoryFileName(file.getName()), Date::compareTo));
File[] filesToDelete = new File[files.length - this.testhistoryCount];

System.arraycopy(files, 0, filesToDelete, 0, files.length - this.testhistoryCount);
for (File fileToDelete : filesToDelete) {
FileUtil.deleteFile(fileToDelete);
}
}
}

private Date getDateFromPageHistoryFileName(String name) {
try {
return tryExtractDateFromTestHistoryName(name);
Expand Down
10 changes: 10 additions & 0 deletions src/fitnesse/reporting/history/SuiteHistoryFormatter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fitnesse.reporting.history;

import fitnesse.ConfigurationParameter;
import fitnesse.FitNesseContext;
import fitnesse.reporting.BaseFormatter;
import fitnesse.testrunner.WikiTestPageUtil;
Expand Down Expand Up @@ -124,6 +125,15 @@ public void close() throws IOException {
FileUtil.close(writer);
}
}

String testhistoryMaxCount = context.getProperties()
.getProperty(ConfigurationParameter.TESTHISTORY_MAX_COUNT.getKey());
// 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(),
testhistoryMaxCount);
}

@Override
Expand Down
10 changes: 10 additions & 0 deletions 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 Down Expand Up @@ -175,6 +176,15 @@ protected void setTotalRunTimeOnReport(TimeMeasurement totalTimeMeasurement) {

protected void writeResults() throws IOException {
writeResults(writerFactory.getWriter(context, getPage(), getPageCounts(), totalTimeMeasurement.startedAt()));

String testhistoryMaxCount = context.getProperties()
.getProperty(ConfigurationParameter.TESTHISTORY_MAX_COUNT.getKey());
// 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(),
testhistoryMaxCount);
}

@Override
Expand Down
107 changes: 107 additions & 0 deletions test/fitnesse/reporting/history/HistoryPurgerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import fitnesse.util.Clock;
import fitnesse.util.DateAlteringClock;
import fitnesse.wiki.PathParser;
import fitnesse.wiki.WikiPagePath;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -12,6 +14,7 @@
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
Expand Down Expand Up @@ -65,6 +68,70 @@ public void shouldBeAbleToDeleteSomeTestHistory() throws Exception {
assertNotNull(pageHistory.get(makeDate("20090615000000")));
assertNull(pageHistory.get(makeDate("20090614000000")));
}

@Test
public void shouldBeAbleToDeleteSomeTestHistoryByCount() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});

historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "2");

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertEquals(2, pageHistory.size());
assertNotNull(pageHistory.get(makeDate("20090616000000")));
assertNull(pageHistory.get(makeDate("20090615000000")));
}

@Test
public void shouldBeAbleToDeleteAllTestHistoryByCount() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});

historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "0");

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertNull(pageHistory);
}

@Test
public void shouldNotDeleteAnyTestHistoryByCount() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});

historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), String.valueOf(testResults.size() + 2));

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertEquals(testResults.size(), pageHistory.size());
assertNotNull(pageHistory.get(makeDate("20090614000000")));
assertNotNull(pageHistory.get(makeDate("20090617000000")));
}

@Test
public void shouldNotDeleteAnyTestHistoryByCountBecauseOfNullProperty() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});

historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), null);

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertEquals(testResults.size(), pageHistory.size());
}

@Test
public void shouldNotDeleteAnyTestHistoryByCountBecauseOfNotNumberProperty() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});

historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "NotANumber");

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertEquals(testResults.size(), pageHistory.size());
}

@Test
public void shouldBeAbleToDeletePagesFromASuite() throws Exception {
Expand Down Expand Up @@ -97,6 +164,38 @@ public void shouldBeAbleToDeletePagesFromASuite() throws Exception {
assertNotNull(pageHistory.get(makeDate("20090615000000")));
assertNotNull(pageHistory.get(makeDate("20090614000000")));
}

@Test
public void shouldBeAbleToDeletePagesFromASuiteByCount() throws Exception {
File pageDirectory = addPageDirectory("SomePage");
addTestResult(pageDirectory, "20090614000000_1_0_0_0");
addTestResult(pageDirectory, "20090615000000_1_0_0_0");

File subPageDirectory = addPageDirectory("SomePage.SubPage");
addTestResult(subPageDirectory, "20090614000000_1_0_0_0");
addTestResult(subPageDirectory, "20090615000000_1_0_0_0");

File otherPageDirectory = addPageDirectory("OtherPage");
addTestResult(otherPageDirectory, "20090614000000_1_0_0_0");
addTestResult(otherPageDirectory, "20090615000000_1_0_0_0");

historyPurger.deleteTestHistoryByCount(PathParser.parse("SomePage"), "1");

TestHistory history = new TestHistory(resultsDirectory);
PageHistory pageHistory = history.getPageHistory("SomePage");
assertNotNull(pageHistory.get(makeDate("20090615000000")));
assertNull(pageHistory.get(makeDate("20090614000000")));

pageHistory = history.getPageHistory("SomePage.SubPage");
assertEquals(1, pageHistory.size());
assertNotNull(pageHistory.get(makeDate("20090615000000")));
assertNull(pageHistory.get(makeDate("20090614000000")));

pageHistory = history.getPageHistory("OtherPage");
assertEquals(2, pageHistory.size());
assertNotNull(pageHistory.get(makeDate("20090615000000")));
assertNotNull(pageHistory.get(makeDate("20090614000000")));
}

@Test
public void shouldDeletePageHistoryDirectoryIfEmptiedByPurge() throws Exception {
Expand Down Expand Up @@ -161,4 +260,12 @@ private Date makeDate(String dateString) throws ParseException {
return date;
}

private List<File> generateTestResults(File pageDirectory, int[] dayValues)
throws IOException {
List<File> testResults = new ArrayList<>();
for (int dayValue : dayValues) {
testResults.add(addTestResult(pageDirectory, "2009061" + dayValue + "000000_1_0_0_0"));
}
return testResults;
}
}
Loading