allow to view images diff by clicking on hyperlink in test execution console output

This commit is contained in:
Dmitry Batrak
2015-08-12 12:22:01 +03:00
parent 30bd9e5659
commit 528108ddd4
11 changed files with 162 additions and 42 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ public class ComparisonFailureData {
private final String myExpected;
private final String myActual;
private final String myFilePath;
private final String myActualFilePath;
private static Map EXPECTED = new HashMap();
private static Map ACTUAL = new HashMap();
@@ -56,9 +57,14 @@ public class ComparisonFailureData {
}
public ComparisonFailureData(String expected, String actual, String filePath) {
this(expected, actual, filePath, null);
}
public ComparisonFailureData(String expected, String actual, String filePath, String actualFilePath) {
myExpected = expected;
myActual = actual;
myFilePath = filePath;
myActualFilePath = actualFilePath;
}
public static void registerSMAttributes(ComparisonFailureData notification,
@@ -79,6 +85,10 @@ public class ComparisonFailureData {
if (filePath != null) {
attrs.put("expectedFile", filePath);
}
final String actualFilePath = notification.getActualFilePath();
if (actualFilePath != null) {
attrs.put("actualFile", actualFilePath);
}
final int expectedIdx = trace.indexOf("expected");
final String comparisonFailureMessage;
if (expectedIdx > 0) {
@@ -114,6 +124,10 @@ public class ComparisonFailureData {
return myFilePath;
}
public String getActualFilePath() {
return myActualFilePath;
}
public String getExpected() {
return myExpected;
}
@@ -125,7 +139,8 @@ public class ComparisonFailureData {
public static ComparisonFailureData create(Throwable assertion) {
if (assertion instanceof FileComparisonFailure) {
final FileComparisonFailure comparisonFailure = (FileComparisonFailure)assertion;
return new ComparisonFailureData(comparisonFailure.getExpected(), comparisonFailure.getActual(), comparisonFailure.getFilePath());
return new ComparisonFailureData(comparisonFailure.getExpected(), comparisonFailure.getActual(),
comparisonFailure.getFilePath(), comparisonFailure.getActualFilePath());
}
try {
return new ComparisonFailureData(getExpected(assertion), getActual(assertion));
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,18 +23,28 @@ public class FileComparisonFailure extends ComparisonFailure implements KnownExc
private final String myExpected;
private final String myActual;
private final String myFilePath;
private final String myActualFilePath;
public FileComparisonFailure(String message, String expected, String actual, String filePath) {
this(message, expected, actual, filePath, null);
}
public FileComparisonFailure(String message, String expected, String actual, String expectedFilePath, String actualFilePath) {
super(message, expected, actual);
myExpected = expected;
myActual = actual;
myFilePath = filePath;
myFilePath = expectedFilePath;
myActualFilePath = actualFilePath;
}
public String getFilePath() {
return myFilePath;
}
public String getActualFilePath() {
return myActualFilePath;
}
public String getExpected() {
return myExpected;
}
@@ -24,6 +24,7 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.rt.execution.junit.FileComparisonFailure;
import com.intellij.testFramework.MockFontLayoutService;
import com.intellij.testFramework.TestDataFile;
import com.intellij.testFramework.TestDataPath;
@@ -45,7 +46,7 @@ public class EditorPaintingTest extends AbstractEditorTest {
public void testWholeLineHighlighterAtDocumentEnd() throws Exception {
initText("foo");
myEditor.getMarkupModel().addRangeHighlighter(0, 3, HighlighterLayer.WARNING,
myEditor.getMarkupModel().addRangeHighlighter(0, 3, HighlighterLayer.WARNING,
new TextAttributes(null, Color.red, null, null, Font.PLAIN),
HighlighterTargetArea.LINES_IN_RANGE);
checkResult();
@@ -68,7 +69,7 @@ public class EditorPaintingTest extends AbstractEditorTest {
private void checkResult() throws IOException {
checkResult(getTestName(true) + ".png");
}
private void checkResult(@TestDataFile String expectedResultFileName) throws IOException {
myEditor.putUserData(EditorImpl.DO_DOCUMENT_UPDATE_TEST, Boolean.TRUE);
myEditor.getSettings().setAdditionalLinesCount(0);
@@ -90,14 +91,16 @@ public class EditorPaintingTest extends AbstractEditorTest {
File fileWithExpectedResult = getTestDataFile(expectedResultFileName);
if (fileWithExpectedResult.exists()) {
BufferedImage expectedResult = ImageIO.read(fileWithExpectedResult);
assertEquals(generateMessage("Unexpected image width", fileWithExpectedResult, image),
expectedResult.getWidth(), image.getWidth());
assertEquals(generateMessage("Unexpected image height", fileWithExpectedResult, image),
expectedResult.getHeight(), image.getHeight());
if (expectedResult.getWidth() != image.getWidth()) {
fail("Unexpected image width", fileWithExpectedResult, image);
}
if (expectedResult.getHeight() != image.getHeight()) {
fail("Unexpected image height", fileWithExpectedResult, image);
}
for (int i = 0; i < expectedResult.getWidth(); i++) {
for (int j = 0; j < expectedResult.getHeight(); j++) {
if (expectedResult.getRGB(i, j) != image.getRGB(i, j)) {
fail(generateMessage("Unexpected image contents", fileWithExpectedResult, image));
fail("Unexpected image contents", fileWithExpectedResult, image);
}
}
}
@@ -107,15 +110,17 @@ public class EditorPaintingTest extends AbstractEditorTest {
ImageIO.write(image, "png", fileWithExpectedResult);
}
else {
fail(generateMessage("Test data is missing", fileWithExpectedResult, image));
fail("Test data is missing", fileWithExpectedResult, image);
}
}
}
private String generateMessage(String text, File expectedResultsFile, BufferedImage actualImage) throws IOException {
private void fail(String message, File expectedResultsFile, BufferedImage actualImage) throws IOException {
File savedImage = FileUtil.createTempFile(getName(), ".png", false);
addTmpFileToKeep(savedImage);
ImageIO.write(actualImage, "png", savedImage);
return text + "\nExpected image: " + expectedResultsFile.getAbsolutePath() + "\nActual image: " + savedImage.getAbsolutePath() + "\n";
throw new FileComparisonFailure(message, expectedResultsFile.getAbsolutePath(), savedImage.getAbsolutePath(),
expectedResultsFile.getAbsolutePath(), savedImage.getAbsolutePath());
}
private static File getFontFile() {
@@ -179,7 +184,7 @@ public class EditorPaintingTest extends AbstractEditorTest {
myBitmapFont.draw(myDelegate, c, x, y);
}
}
// font which, once created, should be rendered identically on all platforms
private static class BitmapFont {
private static final float FONT_SIZE = 12;
@@ -187,7 +192,7 @@ public class EditorPaintingTest extends AbstractEditorTest {
private static final int CHAR_HEIGHT = 12;
private static final int CHAR_DESCENT = 2;
private static final int NUM_CHARACTERS = 128;
private final BufferedImage myImage;
private BitmapFont(BufferedImage image) {
@@ -383,7 +383,8 @@ public class GeneralToSMTRunnerEventsConvertor extends GeneralTestEventsProcesso
}
testProxy.setTestComparisonFailed(localizedMessage, stackTrace,
comparisionFailureActualText, comparisionFailureExpectedText, testFailedEvent.getFilePath());
comparisionFailureActualText, comparisionFailureExpectedText,
testFailedEvent.getFilePath(), testFailedEvent.getActualFilePath());
} else if (comparisionFailureActualText == null && comparisionFailureExpectedText == null) {
testProxy.setTestFailed(localizedMessage, stackTrace, isTestError);
} else {
@@ -338,6 +338,7 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
@NonNls private static final String ATTR_KEY_TEXT = "text";
@NonNls private static final String ATTR_KEY_ERROR_DETAILS = "errorDetails";
@NonNls private static final String ATTR_KEY_EXPECTED_FILE_PATH = "expectedFile";
@NonNls private static final String ATTR_KEY_ACTUAL_FILE_PATH = "actualFile";
@NonNls public static final String CUSTOM_STATUS = "customProgressStatus";
@NonNls private static final String ATTR_KEY_TEST_TYPE = "type";
@@ -420,7 +421,9 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
public void visitTestFailed(@NotNull final TestFailed testFailed) {
final Map<String, String> attributes = testFailed.getAttributes();
final boolean testError = attributes.get(ATTR_KEY_TEST_ERROR) != null;
TestFailedEvent testFailedEvent = new TestFailedEvent(testFailed, testError, attributes.get(ATTR_KEY_EXPECTED_FILE_PATH));
TestFailedEvent testFailedEvent = new TestFailedEvent(testFailed, testError,
attributes.get(ATTR_KEY_EXPECTED_FILE_PATH),
attributes.get(ATTR_KEY_ACTUAL_FILE_PATH));
fireOnTestFailure(testFailedEvent);
}
@@ -468,8 +468,17 @@ public class SMTestProxy extends AbstractTestProxy {
@NotNull final String actualText,
@NotNull final String expectedText,
@Nullable final String filePath) {
setTestComparisonFailed(localizedMessage, stackTrace, actualText, expectedText, filePath, null);
}
public void setTestComparisonFailed(@NotNull final String localizedMessage,
@Nullable final String stackTrace,
@NotNull final String actualText,
@NotNull final String expectedText,
@Nullable final String expectedFilePath,
@Nullable final String actualFilePath) {
setStacktraceIfNotSet(stackTrace);
myState = new TestComparisionFailedState(localizedMessage, stackTrace, actualText, expectedText, filePath);
myState = new TestComparisionFailedState(localizedMessage, stackTrace, actualText, expectedText, expectedFilePath, actualFilePath);
fireOnNewPrintable(myState);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,12 +28,16 @@ public class TestFailedEvent extends TreeNodeEvent {
private final String myComparisonFailureActualText;
private final String myComparisonFailureExpectedText;
private final String myFilePath;
private final String myActualFilePath;
private final long myDurationMillis;
public TestFailedEvent(@NotNull TestFailed testFailed, boolean testError) {
this(testFailed, testError, null);
}
public TestFailedEvent(@NotNull TestFailed testFailed, boolean testError, String filePath) {
this(testFailed, testError, filePath, null);
}
public TestFailedEvent(@NotNull TestFailed testFailed, boolean testError, String filePath, String actualFilePath) {
super(testFailed.getTestName(), TreeNodeEvent.getNodeId(testFailed));
if (testFailed.getFailureMessage() == null) throw new NullPointerException();
myLocalizedFailureMessage = testFailed.getFailureMessage();
@@ -42,6 +46,7 @@ public class TestFailedEvent extends TreeNodeEvent {
myComparisonFailureActualText = testFailed.getActual();
myComparisonFailureExpectedText = testFailed.getExpected();
myFilePath = filePath;
myActualFilePath = actualFilePath;
myDurationMillis = parseDuration(testFailed.getAttributes().get("duration"));
}
@@ -89,6 +94,7 @@ public class TestFailedEvent extends TreeNodeEvent {
myComparisonFailureActualText = comparisonFailureActualText;
myComparisonFailureExpectedText = comparisonFailureExpectedText;
myFilePath = expectedTextFilePath;
myActualFilePath = null;
myDurationMillis = durationMillis;
}
@@ -129,6 +135,10 @@ public class TestFailedEvent extends TreeNodeEvent {
return myFilePath;
}
public String getActualFilePath() {
return myActualFilePath;
}
/**
* @return the test duration in milliseconds, or -1 if undefined
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,8 +46,17 @@ public class TestComparisionFailedState extends TestFailedState {
@NotNull final String actualText,
@NotNull final String expectedText,
@Nullable final String filePath) {
this(localizedMessage, stackTrace, actualText, expectedText, filePath, null);
}
public TestComparisionFailedState(@Nullable final String localizedMessage,
@Nullable final String stackTrace,
@NotNull final String actualText,
@NotNull final String expectedText,
@Nullable final String expectedFilePath,
@Nullable final String actualFilePath) {
super(localizedMessage, stackTrace);
myHyperlink = new DiffHyperlink(expectedText, actualText, filePath);
myHyperlink = new DiffHyperlink(expectedText, actualText, expectedFilePath, actualFilePath, true);
myErrorMsgPresentation = StringUtil.isEmptyOrSpaces(localizedMessage) ? "" : localizedMessage;
myStacktracePresentation = StringUtil.isEmptyOrSpaces(stackTrace) ? "" : stackTrace;
@@ -107,6 +107,7 @@ public abstract class UsefulTestCase extends TestCase {
};
protected static String ourPathToKeep = null;
private List<String> myPathsToKeep = new ArrayList<String>();
private CodeStyleSettings myOldCodeStyleSettings;
private String myTempDir;
@@ -156,11 +157,11 @@ public abstract class UsefulTestCase extends TestCase {
finally {
if (shouldContainTempFiles()) {
FileUtil.resetCanonicalTempPathCache(ORIGINAL_TEMP_DIR);
if (ourPathToKeep != null && FileUtil.isAncestor(myTempDir, ourPathToKeep, false)) {
if (hasTmpFilesToKeep()) {
File[] files = new File(myTempDir).listFiles();
if (files != null) {
for (File file : files) {
if (!FileUtil.pathsEqual(file.getPath(), ourPathToKeep)) {
if (!shouldKeepTmpFile(file)) {
FileUtil.delete(file);
}
}
@@ -176,6 +177,23 @@ public abstract class UsefulTestCase extends TestCase {
super.tearDown();
}
protected void addTmpFileToKeep(File file) {
myPathsToKeep.add(file.getPath());
}
private boolean hasTmpFilesToKeep() {
return ourPathToKeep != null && FileUtil.isAncestor(myTempDir, ourPathToKeep, false) || !myPathsToKeep.isEmpty();
}
private boolean shouldKeepTmpFile(File file) {
String path = file.getPath();
if (FileUtil.pathsEqual(path, ourPathToKeep)) return true;
for (String pathToKeep : myPathsToKeep) {
if (FileUtil.pathsEqual(path, pathToKeep)) return true;
}
return false;
}
private static final Set<String> DELETE_ON_EXIT_HOOK_DOT_FILES;
private static final Class DELETE_ON_EXIT_HOOK_CLASS;
static {
@@ -1,3 +1,18 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.testframework.actions;
import com.intellij.diff.DiffContentFactory;
@@ -13,6 +28,7 @@ import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.testframework.stacktrace.DiffHyperlink;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
@@ -48,28 +64,32 @@ public class TestDiffRequestProcessor extends DiffRequestProcessor {
try {
String title = hyperlink.getDiffTitle();
String title1;
String title2 = ExecutionBundle.message("diff.content.actual.title");
DiffContent content1;
DiffContent content2 = DiffContentFactory.getInstance().create(hyperlink.getRight(), null);
Pair<String, DiffContent> content1 = createContentWithTitle("diff.content.expected.title",
hyperlink.getLeft(), hyperlink.getFilePath());
Pair<String, DiffContent> content2 = createContentWithTitle("diff.content.actual.title",
hyperlink.getRight(), hyperlink.getActualFilePath());
String filePath = hyperlink.getFilePath();
final VirtualFile vFile;
if (filePath != null && (vFile = LocalFileSystem.getInstance().findFileByPath(filePath)) != null) {
title1 = ExecutionBundle.message("diff.content.expected.title") + " (" + vFile.getPresentableUrl() + ")";
content1 = DiffContentFactory.getInstance().create(getProject(), vFile);
}
else {
title1 = ExecutionBundle.message("diff.content.expected.title");
content1 = DiffContentFactory.getInstance().create(hyperlink.getLeft(), null);
}
return new SimpleDiffRequest(title, content1, content2, title1, title2);
return new SimpleDiffRequest(title, content1.second, content2.second, content1.first, content2.first);
}
catch (Exception e) {
return new ErrorDiffRequest(e);
}
}
private Pair<String, DiffContent> createContentWithTitle(String titleKey, String contentString, String contentFilePath) {
String title;
DiffContent content;
VirtualFile vFile;
if (contentFilePath != null && (vFile = LocalFileSystem.getInstance().findFileByPath(contentFilePath)) != null) {
title = ExecutionBundle.message(titleKey) + " (" + vFile.getPresentableUrl() + ")";
content = DiffContentFactory.getInstance().create(getProject(), vFile);
}
else {
title = ExecutionBundle.message(titleKey);
content = DiffContentFactory.getInstance().create(contentString, null);
}
return Pair.create(title, content);
}
//
// Navigation
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ public class DiffHyperlink implements Printable {
protected final String myExpected;
protected final String myActual;
protected final String myFilePath;
protected final String myActualFilePath;
private boolean myPrintOneLine;
private final HyperlinkInfo myDiffHyperlink = new DiffHyperlinkInfo();
@@ -57,12 +58,25 @@ public class DiffHyperlink implements Printable {
final String actual,
final String filePath,
boolean printOneLine) {
this(expected, actual, filePath, null, printOneLine);
}
public DiffHyperlink(final String expected,
final String actual,
final String expectedFilePath,
final String actualFilePath,
boolean printOneLine) {
myExpected = expected;
myActual = actual;
myFilePath = filePath == null ? null : filePath.replace(File.separatorChar, '/');
myFilePath = normalizeSeparators(expectedFilePath);
myActualFilePath = normalizeSeparators(actualFilePath);
myPrintOneLine = printOneLine;
}
private static String normalizeSeparators(String filePath) {
return filePath == null ? null : filePath.replace(File.separatorChar, '/');
}
/**
* Use {@link ViewAssertEqualsDiffAction#openDiff(DataContext, DiffHyperlink)}
*/
@@ -99,6 +113,10 @@ public class DiffHyperlink implements Printable {
public String getFilePath() {
return myFilePath;
}
public String getActualFilePath() {
return myActualFilePath;
}
public void printOn(final Printer printer) {
if (!hasMoreThanOneLine(myActual.trim()) && !hasMoreThanOneLine(myExpected.trim()) && myPrintOneLine) {
@@ -127,6 +145,7 @@ public class DiffHyperlink implements Printable {
if (myActual != null ? !myActual.equals(hyperlink.myActual) : hyperlink.myActual != null) return false;
if (myExpected != null ? !myExpected.equals(hyperlink.myExpected) : hyperlink.myExpected != null) return false;
if (myFilePath != null ? !myFilePath.equals(hyperlink.myFilePath) : hyperlink.myFilePath != null) return false;
if (myActualFilePath != null ? !myActualFilePath.equals(hyperlink.myActualFilePath) : hyperlink.myActualFilePath != null) return false;
return true;
}
@@ -136,6 +155,7 @@ public class DiffHyperlink implements Printable {
int result = myExpected != null ? myExpected.hashCode() : 0;
result = 31 * result + (myActual != null ? myActual.hashCode() : 0);
result = 31 * result + (myFilePath != null ? myFilePath.hashCode() : 0);
result = 31 * result + (myActualFilePath != null ? myActualFilePath.hashCode() : 0);
return result;
}