Skip to content

Commit

Permalink
Merge pull request #1209 from fhoeben/rerunFailures
Browse files Browse the repository at this point in the history
Allow rerun of failed tests (only)
  • Loading branch information
fhoeben committed Apr 17, 2019
2 parents e840c9e + 01d23b7 commit 72bc4f9
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -22,6 +22,7 @@ FitNesseRoot/properties
FitNesseRoot/ErrorLogs/*
FitNesseRoot/files/testResults/*
FitNesseRoot/RecentChanges*
FitNesseRoot/RerunLastFailures.wiki
junit*.properties
FitNesseRoot/updateDoNotCopyOverList
FitNesseRoot/updateList
Expand Down
1 change: 1 addition & 0 deletions FitNesseRoot/FitNesse/ReleaseNotes/content.txt
@@ -1,4 +1,5 @@
!2 ${FITNESSE_VERSION}
* Allow rerun of failed tests (only) from suite results page ([[1209][https://github.com/unclebob/fitnesse/pull/1209]])
* Issues fixed:
* Fix search page ([[1207][https://github.com/unclebob/fitnesse/issues/1207]])

Expand Down
2 changes: 0 additions & 2 deletions src/fitnesse/reporting/Formatter.java
Expand Up @@ -3,8 +3,6 @@
import fitnesse.testsystems.TestSystemListener;

/**
* This factory is instantiated as FitNesse component, hence can use constructor arguments.
*
* Optionally implement java.io.Closeable and/or fitnesse.testrunner.TestsRunnerListener.
*/
public interface Formatter extends TestSystemListener {
Expand Down
91 changes: 91 additions & 0 deletions src/fitnesse/reporting/RerunSuiteFormatter.java
@@ -0,0 +1,91 @@
package fitnesse.reporting;

import fitnesse.testsystems.TestPage;
import fitnesse.testsystems.TestSummary;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.logging.Logger;

/**
* Creates a wiki page with all pages that either failed or threw exception
*/
public class RerunSuiteFormatter extends BaseFormatter implements Closeable {
private static final Logger LOG = Logger.getLogger(RerunSuiteFormatter.class.getName());

private final File wikiFile;
private final PrintWriter pw;
private int errorCount = 0;

public RerunSuiteFormatter(File targetFile) throws IOException {
wikiFile = targetFile;
if (!wikiFile.getParentFile().exists()) {
wikiFile.getParentFile().mkdirs();
} else if (wikiFile.exists()) {
wikiFile.delete();
}
LOG.fine("Rerun suite will be made in: " + wikiFile.getAbsolutePath());
pw = new PrintWriter(wikiFile, "utf-8");
}

@Override
public void testComplete(TestPage testPage, TestSummary testSummary) {
if (testSummary.getExceptions() > 0) {
recordFailure(testPage);
} else if (testSummary.getWrong() > 0) {
recordFailure(testPage);
}
}

@Override
public int getErrorCount() {
return errorCount;
}

@Override
public void close() {
pw.close();
// no content -> remove file
if (getErrorCount() == 0 && wikiFile.exists()) {
wikiFile.delete();
}
}

protected void recordFailure(TestPage testPage) {
String testPageName = testPage.getName();
if (!"SuiteSetUp".equals(testPageName)
&& !"SuiteTearDown".equals(testPageName)) {
errorCount++;
if (errorCount == 1) {
appendHeader(pw);
}
appendPageFailure(pw, testPage);
pw.flush();
}
}

protected void appendHeader(PrintWriter writer) {
writer.append("---\n" +
"Help: Lists tests failed during last run, so they can be run again (without running all tests that passed).\n" +
"Suite\n" +
"---\n" +
"\n");
writer.append("!note This page is automatically generated when running tests. ");
writer.append("It will be overwritten by the next Suite or Test execution.\n\n");
writer.append("Tests failed (first failure was at ");
writer.append(LocalDateTime.now().toString());
writer.append("):\n\n");
}

protected void appendPageFailure(PrintWriter writer, TestPage testPage) {
String pagePath = testPage.getFullPath();
writer.append("!see [[");
writer.append(pagePath);
writer.append("][.");
writer.append(pagePath);
writer.append("]]\n");
}
}
3 changes: 3 additions & 0 deletions src/fitnesse/resources/templates/errorNavigator.vm
Expand Up @@ -6,4 +6,7 @@
<span class="btn-text"><input id="error-nav-text" type="text" size="3"> of <span id="error-nav-max"></span></span>
<button id="error-nav-next" class="btn btn-default">&raquo;</button>
</div>
#if ($rerunPage)
<a class="btn btn-default" href="$rerunPage?suite">Rerun Failed</a>
#end
</div>
21 changes: 21 additions & 0 deletions src/fitnesse/responders/run/SuiteResponder.java
Expand Up @@ -49,6 +49,8 @@ public class SuiteResponder extends ChunkingResponder implements SecureResponder
private final WikiImporter wikiImporter;
private SuiteHistoryFormatter suiteHistoryFormatter;

private Formatter rerunFormatter;

private PageData data;
private String testRunId;
private BaseFormatter mainFormatter;
Expand Down Expand Up @@ -94,6 +96,7 @@ protected void doSending() throws Exception {
data = page.getData();

createMainFormatter();
rerunFormatter = createRerunFormatter();

if (isInteractive()) {
makeHtml().render(response.getWriter(), request);
Expand Down Expand Up @@ -171,6 +174,9 @@ private HtmlPage makeHtml() {
htmlPage.setErrorNavTemplate("errorNavigator");
htmlPage.put("multipleTestsRun", isMultipleTestsRun());
WikiImportingResponder.handleImportProperties(htmlPage, page);
if (rerunFormatter != null) {
htmlPage.put("rerunPage", getRerunPageName());
}

return htmlPage;
}
Expand Down Expand Up @@ -211,6 +217,9 @@ private boolean isMultipleTestsRun() {

protected void addFormatters(MultipleTestsRunner runner) {
runner.addTestSystemListener(mainFormatter);
if (rerunFormatter != null) {
runner.addTestSystemListener(rerunFormatter);
}
if (withSuiteHistoryFormatter()) {
addHistoryFormatter(runner);
} else {
Expand Down Expand Up @@ -249,6 +258,18 @@ private void createMainFormatter() {
}
}

protected Formatter createRerunFormatter() throws IOException {
return new RerunSuiteFormatter(getRerunPageFile());
}

protected File getRerunPageFile() {
return new File(context.getRootPagePath(), getRerunPageName() + ".wiki");
}

protected String getRerunPageName() {
return "RerunLastFailures";
}

protected String getTitle() {
return "Test Results";
}
Expand Down
132 changes: 132 additions & 0 deletions test/fitnesse/reporting/RerunSuiteFormatterTest.java
@@ -0,0 +1,132 @@
package fitnesse.reporting;

import fitnesse.testrunner.WikiTestPage;
import fitnesse.testsystems.ExecutionResult;
import fitnesse.testsystems.TestSummary;
import fitnesse.wiki.PathParser;
import fitnesse.wiki.WikiPage;
import fitnesse.wiki.WikiPageUtil;
import fitnesse.wiki.fs.InMemoryPage;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.util.List;

import static org.junit.Assert.*;

public class RerunSuiteFormatterTest {
private final WikiPage wikiPageRoot = InMemoryPage.makeRoot("root");
private final WikiPage wikiSuitePage = WikiPageUtil.addPage(wikiPageRoot, PathParser.parse("Suite"), "suite content");

private File targetFile;
private RerunSuiteFormatter formatter;

@Before
public void setUp() throws IOException {
targetFile = File.createTempFile("root", "rerun.wiki");
formatter = new RerunSuiteFormatter(targetFile);
}

@After
public void tearDown() {
if (targetFile != null && targetFile.exists()) {
targetFile.delete();
}
}

@Test
public void noErrorOrFailedNoFile() throws IOException {
sendTestComplete(ExecutionResult.PASS, "PassedPage");
formatter.close();

assertEquals(0, formatter.getErrorCount());

assertFalse(targetFile.exists());
}

@Test
public void addRefOnError() throws IOException {
sendTestComplete(ExecutionResult.ERROR, "ErrorPage");
formatter.close();

assertEquals(1, formatter.getErrorCount());

List<String> xrefs = getReferencedPages();
assertEquals(".Suite.ErrorPage", xrefs.get(0));
}

@Test
public void addRefOnFailure() throws IOException {
sendTestComplete(ExecutionResult.FAIL, "FailedPage");
formatter.close();

assertEquals(1, formatter.getErrorCount());

List<String> xrefs = getReferencedPages();
assertEquals(".Suite.FailedPage", xrefs.get(0));
}

@Test
public void addRefsOnMultiple() throws IOException {
sendTestComplete(ExecutionResult.FAIL, "FailedPage");
sendTestComplete(ExecutionResult.PASS, "PassedPage");
sendTestComplete(ExecutionResult.ERROR, "ErrorPage");
sendTestComplete(ExecutionResult.FAIL, "FailedPage2");
formatter.close();

assertEquals(3, formatter.getErrorCount());

List<String> xrefs = getReferencedPages();
assertEquals(".Suite.FailedPage", xrefs.get(0));
assertEquals(".Suite.ErrorPage", xrefs.get(1));
assertEquals(".Suite.FailedPage2", xrefs.get(2));
}

@Test
public void addRefPagenameNotWikiWord() throws IOException {
sendTestComplete(ExecutionResult.FAIL, "Failedpage");
formatter.close();

assertEquals(1, formatter.getErrorCount());

List<String> xrefs = getReferencedPages();
assertEquals(".Suite.Failedpage", xrefs.get(0));
}

@Test
public void suiteSetUpAndSuiteTearDownAreIgnored() throws IOException {
sendTestComplete(ExecutionResult.ERROR, "SuiteSetUp");
sendTestComplete(ExecutionResult.ERROR, "ErrorPage");
sendTestComplete(ExecutionResult.ERROR, "SuiteTearDown");
formatter.close();

assertEquals(1, formatter.getErrorCount());

List<String> xrefs = getReferencedPages();
assertEquals(".Suite.ErrorPage", xrefs.get(0));
}

private List<String> getReferencedPages() throws IOException {
String rerunPageContent = FileUtil.getFileContent(targetFile);
assertTrue(rerunPageContent.startsWith("---\nHelp: "));

WikiPage root = InMemoryPage.makeRoot("RooT");
WikiPage page = WikiPageUtil.addPage(root, PathParser.parse("PageName"), rerunPageContent);
return WikiPageUtil.getXrefPages(page);
}

private void sendTestComplete(ExecutionResult result, String pageName) {
WikiPage wikiTestPage = WikiPageUtil.addPage(wikiSuitePage, PathParser.parse(pageName), "content");
formatter.testComplete(new WikiTestPage(wikiTestPage), createTestSummary(result));
}

private TestSummary createTestSummary(ExecutionResult fail) {
TestSummary testSummary = new TestSummary();
testSummary.add(fail);
return testSummary;
}
}
24 changes: 24 additions & 0 deletions test/fitnesse/responders/run/SuiteResponderTest.java
Expand Up @@ -28,6 +28,7 @@
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import util.FileUtil;

import static org.junit.Assert.*;
import static util.RegexTestCase.*;
Expand Down Expand Up @@ -133,6 +134,26 @@ public void testWithTwoTests() throws Exception {
assertSubString("name=\"TestTwo2\"", results);
assertSubString("PassFixture", results);
assertSubString("FailFixture", results);

assertSubString("RerunLastFailures", results);
assertSubString("Rerun Failed", results);
File rerunPage = responder.getRerunPageFile();
assertTrue(rerunPage.exists());
String rerunPageContent = FileUtil.getFileContent(rerunPage);
assertSubString("SuitePage.TestTwo", rerunPageContent);
assertNotSubString("TestOne", rerunPageContent);

// execute rerun suite
String rerunPageName = responder.getRerunPageName();
request = new MockRequest();
request.setResource(rerunPageName);
responder = new SuiteResponder();
suite = WikiPageUtil.addPage(root, PathParser.parse(rerunPageName), rerunPageContent);
responder.page = suite;

String rerunresults = runSuite();
assertSubString("href=\\\"#SuitePage.TestTwo1\\\"", rerunresults);
assertNotSubString("TestOne", rerunresults);
}

@Test
Expand All @@ -153,6 +174,9 @@ public void testWithPrunedPage() throws Exception {
assertNotSubString("id=\"TestTwo2\"", results);
assertSubString("PassFixture", results);
assertNotSubString("FailFixture", results);

File rerunPage = responder.getRerunPageFile();
assertFalse(rerunPage.exists());
}

@Test
Expand Down

0 comments on commit 72bc4f9

Please sign in to comment.