Skip to content

Commit a3c0ce3

Browse files
authored
Feature/limit test histories (#1510)
1 parent f2ad293 commit a3c0ce3

File tree

9 files changed

+186
-8
lines changed

9 files changed

+186
-8
lines changed

FitNesseRoot/FitNesse/ReleaseNotes/content.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
!2 Pending Changes
22
* Allow usage of JDK > 18 ([[1513][https://github.com/unclebob/fitnesse/pull/1513]]).
3+
* Added a new configuration to limit the total amount of test histories per page. ([[1510][https://github.com/unclebob/fitnesse/pull/1510]])
34

45
!2 20240219
56
* Added the defined values to the overview of all variables in scope. ([[1472][https://github.com/unclebob/fitnesse/pull/1472]])

FitNesseRoot/FitNesse/UserGuide/AdministeringFitNesse/ConfigurationFile/content.txt

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ There are more properties which can be used to tweak parts of FitNesse:
178178
* '''test.history.days''' - The number of days to keep test results around. Cleaned up after a new test run.
179179
* '''test.history.path''' - Location to store the test results. The default location is ''!-FitNesseRoot-!/files/testResults''.
180180
* '''TestHistory.purgeOptions''' - A comma separated list of the age, in number of days, to offer as purge options on the ''Test History'' page.
181+
* '''TestHistory.maxCount''' - The number of histories to keep around per page. Cleaned up after a new test run.
181182
* Any variable that can be defined on a wiki page.
182183

183184
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.

FitNesseRoot/FitNesse/UserGuide/WritingAcceptanceTests/TestHistory/content.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The test files contain the XML that describes the test run. The format of this
1414
!4 Purging
1515
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]]).
1616

17-
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.
17+
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.
1818

1919
!4 Comparing History
2020
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.

plugins.properties

+5
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,8 @@ test.history.days=1
6767
# The value 0 represents 'Purge all'.
6868
# Enabling the property without setting any value, removes the offered options completely.
6969
#TestHistory.purgeOptions=0,7,30
70+
71+
##
72+
# The number represents how many test histories will be kept per page.
73+
# Test histories will be deleted when new histories are being created.
74+
#TestHistory.maxCount=30

src/fitnesse/ConfigurationParameter.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public enum ConfigurationParameter {
4444
LOCALHOST_ONLY("LocalhostOnly"),
4545
MAXIMUM_WORKERS("MaximumWorkers"),
4646
THEME("Theme"),
47-
PURGE_OPTIONS("TestHistory.purgeOptions");
47+
PURGE_OPTIONS("TestHistory.purgeOptions"),
48+
TESTHISTORY_MAX_COUNT("TestHistory.maxCount");
4849

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

src/fitnesse/reporting/history/HistoryPurger.java

+49-6
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@
88
import java.io.IOException;
99
import java.text.ParseException;
1010
import java.text.SimpleDateFormat;
11+
import java.util.Arrays;
12+
import java.util.Comparator;
1113
import java.util.Date;
1214
import java.util.logging.Level;
1315
import java.util.logging.Logger;
1416

17+
import org.apache.commons.lang3.StringUtils;
18+
1519
import static java.lang.String.format;
1620

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

2024
private final File resultsDirectory;
2125
private final Date expirationDate;
26+
private Integer testhistoryCount;
2227

2328
public HistoryPurger(File resultsDirectory, int days) {
2429
this.resultsDirectory = resultsDirectory;
@@ -37,14 +42,34 @@ public void deleteTestHistoryOlderThanDays(WikiPagePath path) {
3742
for (File file : files) {
3843
String fileName = file.getName();
3944
if (fileName.equals(pageName) || fileName.startsWith(subPagePrefix)) {
40-
deleteIfExpired(file);
45+
delete(file);
46+
}
47+
}
48+
}
49+
50+
public void deleteTestHistoryByCount(WikiPagePath path, String testhistoryMaxCount) {
51+
if (testhistoryMaxCount == null
52+
|| !StringUtils.isNumeric(testhistoryMaxCount)) {
53+
LOG.fine(
54+
"The given testhistoryMaxCount must not be null and it has to be a valid number");
55+
return;
56+
}
57+
58+
this.testhistoryCount = Integer.parseInt(testhistoryMaxCount);
59+
String pageName = path.toString();
60+
String subPagePrefix = pageName + ".";
61+
File[] files = FileUtil.getDirectoryListing(resultsDirectory);
62+
for (File file : files) {
63+
String fileName = file.getName();
64+
if (fileName.equals(pageName) || fileName.startsWith(subPagePrefix)) {
65+
delete(file);
4166
}
4267
}
4368
}
4469

4570
private void deleteExpiredFiles(File[] files) {
4671
for (File file : files)
47-
deleteIfExpired(file);
72+
delete(file);
4873
}
4974

5075
public Date getDateDaysAgo(int days) {
@@ -54,20 +79,24 @@ public Date getDateDaysAgo(int days) {
5479
return daysEarlier;
5580
}
5681

57-
private void deleteIfExpired(File file) {
82+
private void delete(File file) {
5883
try {
5984
if (file.isDirectory()) {
60-
deleteDirectoryIfExpired(file);
85+
deleteDirectory(file);
6186
} else
6287
deleteFileIfExpired(file);
6388
} catch (IOException e) {
6489
LOG.log(Level.INFO, format("Unable to remove test history file %s", file.getPath()));
6590
}
6691
}
6792

68-
private void deleteDirectoryIfExpired(File file) throws IOException {
93+
private void deleteDirectory(File file) throws IOException {
6994
File[] files = FileUtil.listFiles(file);
70-
deleteExpiredFiles(files);
95+
if(testhistoryCount != null) {
96+
deleteFilesIfCountReached(files);
97+
} else {
98+
deleteExpiredFiles(files);
99+
}
71100
if (FileUtil.isEmpty(file)) {
72101
FileUtil.deleteFileSystemDirectory(file);
73102
}
@@ -80,6 +109,20 @@ private void deleteFileIfExpired(File file) throws IOException {
80109
FileUtil.deleteFile(file);
81110
}
82111

112+
private void deleteFilesIfCountReached(File[] files) throws IOException {
113+
// Only delete histories when there are more histories than the count expects
114+
if((files.length - this.testhistoryCount) > 0) {
115+
// Sorting the files to have them in ascending order of creation
116+
Arrays.sort(files, Comparator.comparing(file -> getDateFromPageHistoryFileName(file.getName()), Date::compareTo));
117+
File[] filesToDelete = new File[files.length - this.testhistoryCount];
118+
119+
System.arraycopy(files, 0, filesToDelete, 0, files.length - this.testhistoryCount);
120+
for (File fileToDelete : filesToDelete) {
121+
FileUtil.deleteFile(fileToDelete);
122+
}
123+
}
124+
}
125+
83126
private Date getDateFromPageHistoryFileName(String name) {
84127
try {
85128
return tryExtractDateFromTestHistoryName(name);

src/fitnesse/reporting/history/SuiteHistoryFormatter.java

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fitnesse.reporting.history;
22

3+
import fitnesse.ConfigurationParameter;
34
import fitnesse.FitNesseContext;
45
import fitnesse.reporting.BaseFormatter;
56
import fitnesse.testrunner.WikiTestPageUtil;
@@ -124,6 +125,15 @@ public void close() throws IOException {
124125
FileUtil.close(writer);
125126
}
126127
}
128+
129+
String testhistoryMaxCount = context.getProperties()
130+
.getProperty(ConfigurationParameter.TESTHISTORY_MAX_COUNT.getKey());
131+
// The given number of days (0) is irrelevant here since we purge the
132+
// history by their amount
133+
HistoryPurger historyPurger = new HistoryPurger(
134+
context.getTestHistoryDirectory(), 0);
135+
historyPurger.deleteTestHistoryByCount(getPage().getFullPath(),
136+
testhistoryMaxCount);
127137
}
128138

129139
@Override

src/fitnesse/reporting/history/TestXmlFormatter.java

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Released under the terms of the CPL Common Public License version 1.0.
33
package fitnesse.reporting.history;
44

5+
import fitnesse.ConfigurationParameter;
56
import fitnesse.FitNesseContext;
67
import fitnesse.reporting.BaseFormatter;
78
import fitnesse.testrunner.WikiTestPageUtil;
@@ -175,6 +176,15 @@ protected void setTotalRunTimeOnReport(TimeMeasurement totalTimeMeasurement) {
175176

176177
protected void writeResults() throws IOException {
177178
writeResults(writerFactory.getWriter(context, getPage(), getPageCounts(), totalTimeMeasurement.startedAt()));
179+
180+
String testhistoryMaxCount = context.getProperties()
181+
.getProperty(ConfigurationParameter.TESTHISTORY_MAX_COUNT.getKey());
182+
// The given number of days (0) is irrelevant here since we purge the
183+
// history by their amount
184+
HistoryPurger historyPurger = new HistoryPurger(
185+
context.getTestHistoryDirectory(), 0);
186+
historyPurger.deleteTestHistoryByCount(getPage().getFullPath(),
187+
testhistoryMaxCount);
178188
}
179189

180190
@Override

test/fitnesse/reporting/history/HistoryPurgerTest.java

+107
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import fitnesse.util.Clock;
44
import fitnesse.util.DateAlteringClock;
55
import fitnesse.wiki.PathParser;
6+
import fitnesse.wiki.WikiPagePath;
7+
68
import org.junit.After;
79
import org.junit.Before;
810
import org.junit.Test;
@@ -12,6 +14,7 @@
1214
import java.io.IOException;
1315
import java.text.ParseException;
1416
import java.text.SimpleDateFormat;
17+
import java.util.ArrayList;
1518
import java.util.Arrays;
1619
import java.util.Date;
1720
import java.util.List;
@@ -65,6 +68,70 @@ public void shouldBeAbleToDeleteSomeTestHistory() throws Exception {
6568
assertNotNull(pageHistory.get(makeDate("20090615000000")));
6669
assertNull(pageHistory.get(makeDate("20090614000000")));
6770
}
71+
72+
@Test
73+
public void shouldBeAbleToDeleteSomeTestHistoryByCount() throws Exception {
74+
File pageDirectory = addPageDirectory("SomePage");
75+
generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});
76+
77+
historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "2");
78+
79+
TestHistory history = new TestHistory(resultsDirectory);
80+
PageHistory pageHistory = history.getPageHistory("SomePage");
81+
assertEquals(2, pageHistory.size());
82+
assertNotNull(pageHistory.get(makeDate("20090616000000")));
83+
assertNull(pageHistory.get(makeDate("20090615000000")));
84+
}
85+
86+
@Test
87+
public void shouldBeAbleToDeleteAllTestHistoryByCount() throws Exception {
88+
File pageDirectory = addPageDirectory("SomePage");
89+
generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});
90+
91+
historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "0");
92+
93+
TestHistory history = new TestHistory(resultsDirectory);
94+
PageHistory pageHistory = history.getPageHistory("SomePage");
95+
assertNull(pageHistory);
96+
}
97+
98+
@Test
99+
public void shouldNotDeleteAnyTestHistoryByCount() throws Exception {
100+
File pageDirectory = addPageDirectory("SomePage");
101+
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});
102+
103+
historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), String.valueOf(testResults.size() + 2));
104+
105+
TestHistory history = new TestHistory(resultsDirectory);
106+
PageHistory pageHistory = history.getPageHistory("SomePage");
107+
assertEquals(testResults.size(), pageHistory.size());
108+
assertNotNull(pageHistory.get(makeDate("20090614000000")));
109+
assertNotNull(pageHistory.get(makeDate("20090617000000")));
110+
}
111+
112+
@Test
113+
public void shouldNotDeleteAnyTestHistoryByCountBecauseOfNullProperty() throws Exception {
114+
File pageDirectory = addPageDirectory("SomePage");
115+
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});
116+
117+
historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), null);
118+
119+
TestHistory history = new TestHistory(resultsDirectory);
120+
PageHistory pageHistory = history.getPageHistory("SomePage");
121+
assertEquals(testResults.size(), pageHistory.size());
122+
}
123+
124+
@Test
125+
public void shouldNotDeleteAnyTestHistoryByCountBecauseOfNotNumberProperty() throws Exception {
126+
File pageDirectory = addPageDirectory("SomePage");
127+
List<File> testResults = generateTestResults(pageDirectory, new int[] {7, 5, 6, 4});
128+
129+
historyPurger.deleteTestHistoryByCount(new WikiPagePath(new String[] {"SomePage"}), "NotANumber");
130+
131+
TestHistory history = new TestHistory(resultsDirectory);
132+
PageHistory pageHistory = history.getPageHistory("SomePage");
133+
assertEquals(testResults.size(), pageHistory.size());
134+
}
68135

69136
@Test
70137
public void shouldBeAbleToDeletePagesFromASuite() throws Exception {
@@ -97,6 +164,38 @@ public void shouldBeAbleToDeletePagesFromASuite() throws Exception {
97164
assertNotNull(pageHistory.get(makeDate("20090615000000")));
98165
assertNotNull(pageHistory.get(makeDate("20090614000000")));
99166
}
167+
168+
@Test
169+
public void shouldBeAbleToDeletePagesFromASuiteByCount() throws Exception {
170+
File pageDirectory = addPageDirectory("SomePage");
171+
addTestResult(pageDirectory, "20090614000000_1_0_0_0");
172+
addTestResult(pageDirectory, "20090615000000_1_0_0_0");
173+
174+
File subPageDirectory = addPageDirectory("SomePage.SubPage");
175+
addTestResult(subPageDirectory, "20090614000000_1_0_0_0");
176+
addTestResult(subPageDirectory, "20090615000000_1_0_0_0");
177+
178+
File otherPageDirectory = addPageDirectory("OtherPage");
179+
addTestResult(otherPageDirectory, "20090614000000_1_0_0_0");
180+
addTestResult(otherPageDirectory, "20090615000000_1_0_0_0");
181+
182+
historyPurger.deleteTestHistoryByCount(PathParser.parse("SomePage"), "1");
183+
184+
TestHistory history = new TestHistory(resultsDirectory);
185+
PageHistory pageHistory = history.getPageHistory("SomePage");
186+
assertNotNull(pageHistory.get(makeDate("20090615000000")));
187+
assertNull(pageHistory.get(makeDate("20090614000000")));
188+
189+
pageHistory = history.getPageHistory("SomePage.SubPage");
190+
assertEquals(1, pageHistory.size());
191+
assertNotNull(pageHistory.get(makeDate("20090615000000")));
192+
assertNull(pageHistory.get(makeDate("20090614000000")));
193+
194+
pageHistory = history.getPageHistory("OtherPage");
195+
assertEquals(2, pageHistory.size());
196+
assertNotNull(pageHistory.get(makeDate("20090615000000")));
197+
assertNotNull(pageHistory.get(makeDate("20090614000000")));
198+
}
100199

101200
@Test
102201
public void shouldDeletePageHistoryDirectoryIfEmptiedByPurge() throws Exception {
@@ -161,4 +260,12 @@ private Date makeDate(String dateString) throws ParseException {
161260
return date;
162261
}
163262

263+
private List<File> generateTestResults(File pageDirectory, int[] dayValues)
264+
throws IOException {
265+
List<File> testResults = new ArrayList<>();
266+
for (int dayValue : dayValues) {
267+
testResults.add(addTestResult(pageDirectory, "2009061" + dayValue + "000000_1_0_0_0"));
268+
}
269+
return testResults;
270+
}
164271
}

0 commit comments

Comments
 (0)