strange IOOBE in rare cases
[fedora-idea.git] / testFramework / src / com / intellij / testFramework / ExpectedHighlightingData.java
blobc969df056aaa16f2dc439216ed0789919a3b0562
1 /**
2 * @author cdr
3 */
4 package com.intellij.testFramework;
6 import com.intellij.codeHighlighting.Pass;
7 import com.intellij.codeInsight.daemon.LineMarkerInfo;
8 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
9 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
10 import com.intellij.codeInsight.daemon.impl.SeveritiesProvider;
11 import com.intellij.lang.annotation.HighlightSeverity;
12 import com.intellij.openapi.diagnostic.Logger;
13 import com.intellij.openapi.editor.Document;
14 import com.intellij.openapi.editor.RangeMarker;
15 import com.intellij.openapi.editor.markup.EffectType;
16 import com.intellij.openapi.editor.markup.GutterIconRenderer;
17 import com.intellij.openapi.editor.markup.TextAttributes;
18 import com.intellij.openapi.extensions.Extensions;
19 import com.intellij.openapi.util.Comparing;
20 import com.intellij.openapi.util.TextRange;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiFile;
24 import com.intellij.util.Function;
25 import gnu.trove.THashMap;
26 import gnu.trove.THashSet;
27 import junit.framework.Assert;
28 import org.jetbrains.annotations.NonNls;
30 import java.awt.*;
31 import java.lang.reflect.Field;
32 import java.util.*;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
36 public class ExpectedHighlightingData {
37 private static final Logger LOG = Logger.getInstance("#com.intellij.testFramework.ExpectedHighlightingData");
39 @NonNls private static final String ERROR_MARKER = "error";
40 @NonNls private static final String WARNING_MARKER = "warning";
41 @NonNls private static final String INFORMATION_MARKER = "weak_warning";
42 @NonNls private static final String INFO_MARKER = "info";
43 @NonNls private static final String END_LINE_HIGHLIGHT_MARKER = "EOLError";
44 @NonNls private static final String END_LINE_WARNING_MARKER = "EOLWarning";
45 @NonNls private static final String LINE_MARKER = "lineMarker";
47 private final PsiFile myFile;
48 @NonNls private static final String ANY_TEXT = "*";
49 String myText;
51 public static class ExpectedHighlightingSet {
52 private final boolean endOfLine;
53 final boolean enabled;
54 final Set<HighlightInfo> infos;
55 final HighlightInfoType defaultErrorType;
56 final HighlightSeverity severity;
58 public ExpectedHighlightingSet(HighlightInfoType defaultErrorType, HighlightSeverity severity, boolean endOfLine, boolean enabled) {
59 this.endOfLine = endOfLine;
60 this.enabled = enabled;
61 infos = new THashSet<HighlightInfo>();
62 this.defaultErrorType = defaultErrorType;
63 this.severity = severity;
66 @SuppressWarnings("WeakerAccess")
67 protected final Map<String,ExpectedHighlightingSet> highlightingTypes;
68 private final Map<RangeMarker, LineMarkerInfo> lineMarkerInfos = new THashMap<RangeMarker, LineMarkerInfo>();
70 public ExpectedHighlightingData(Document document,boolean checkWarnings, boolean checkInfos) {
71 this(document, checkWarnings, false, checkInfos);
74 public ExpectedHighlightingData(Document document,
75 boolean checkWarnings,
76 boolean checkWeakWarnings,
77 boolean checkInfos) {
78 this(document, checkWarnings, checkWeakWarnings, checkInfos, null);
81 public ExpectedHighlightingData(Document document,
82 boolean checkWarnings,
83 boolean checkWeakWarnings,
84 boolean checkInfos,
85 PsiFile file) {
86 myFile = file;
87 myText = document.getText();
88 highlightingTypes = new THashMap<String,ExpectedHighlightingSet>();
89 highlightingTypes.put(ERROR_MARKER, new ExpectedHighlightingSet(HighlightInfoType.ERROR, HighlightSeverity.ERROR, false, true));
90 highlightingTypes.put(WARNING_MARKER, new ExpectedHighlightingSet(HighlightInfoType.WARNING, HighlightSeverity.WARNING, false, checkWarnings));
91 highlightingTypes.put(INFORMATION_MARKER, new ExpectedHighlightingSet(HighlightInfoType.INFO, HighlightSeverity.INFO, false, checkWeakWarnings));
92 highlightingTypes.put("inject", new ExpectedHighlightingSet(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT, HighlightInfoType.INJECTED_FRAGMENT_SEVERITY, false, checkInfos));
93 highlightingTypes.put(INFO_MARKER, new ExpectedHighlightingSet(HighlightInfoType.TODO, HighlightSeverity.INFORMATION, false, checkInfos));
94 for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) {
95 for (HighlightInfoType type : provider.getSeveritiesHighlightInfoTypes()) {
96 final HighlightSeverity severity = type.getSeverity(null);
97 highlightingTypes.put(severity.toString(), new ExpectedHighlightingSet(type, severity , false, true));
100 highlightingTypes.put(END_LINE_HIGHLIGHT_MARKER, new ExpectedHighlightingSet(HighlightInfoType.ERROR, HighlightSeverity.ERROR, true, true));
101 highlightingTypes.put(END_LINE_WARNING_MARKER, new ExpectedHighlightingSet(HighlightInfoType.WARNING, HighlightSeverity.WARNING, true, checkWarnings));
102 initAdditionalHighlightingTypes();
103 extractExpectedLineMarkerSet(document);
104 extractExpectedHighlightsSet(document);
105 refreshLineMarkers();
108 private void refreshLineMarkers() {
109 for (Map.Entry<RangeMarker, LineMarkerInfo> entry : lineMarkerInfos.entrySet()) {
110 RangeMarker rangeMarker = entry.getKey();
111 int startOffset = rangeMarker.getStartOffset();
112 int endOffset = rangeMarker.getEndOffset();
113 final LineMarkerInfo value = entry.getValue();
114 LineMarkerInfo markerInfo = new LineMarkerInfo<PsiElement>(value.getElement(), new TextRange(startOffset,endOffset), null, value.updatePass, new Function<PsiElement,String>() {
115 public String fun(PsiElement psiElement) {
116 return value.getLineMarkerTooltip();
118 }, null, GutterIconRenderer.Alignment.RIGHT);
119 entry.setValue(markerInfo);
123 private void extractExpectedLineMarkerSet(Document document) {
124 String text = document.getText();
126 @NonNls String pat = ".*?((<" + LINE_MARKER + ")(?: descr=\"((?:[^\"\\\\]|\\\\\")*)\")?>)(.*)";
127 final Pattern p = Pattern.compile(pat, Pattern.DOTALL);
128 final Pattern pat2 = Pattern.compile("(.*?)(</" + LINE_MARKER + ">)(.*)", Pattern.DOTALL);
130 for (; ;) {
131 Matcher m = p.matcher(text);
132 if (!m.matches()) break;
133 int startOffset = m.start(1);
134 final String descr = m.group(3) != null ? m.group(3): ANY_TEXT;
135 String rest = m.group(4);
137 document.replaceString(startOffset, m.end(1), "");
139 final Matcher matcher2 = pat2.matcher(rest);
140 LOG.assertTrue(matcher2.matches(), "Cannot find closing </" + LINE_MARKER + ">");
141 String content = matcher2.group(1);
142 int endOffset = startOffset + matcher2.start(3);
143 String endTag = matcher2.group(2);
145 document.replaceString(startOffset, endOffset, content);
146 endOffset -= endTag.length();
148 LineMarkerInfo markerInfo = new LineMarkerInfo<PsiElement>(myFile, new TextRange(startOffset,endOffset), null, Pass.LINE_MARKERS, new Function<PsiElement,String>() {
149 public String fun(PsiElement psiElement) {
150 return descr;
152 }, null, GutterIconRenderer.Alignment.RIGHT);
154 lineMarkerInfos.put(document.createRangeMarker(startOffset, endOffset), markerInfo);
155 text = document.getText();
160 * Override in order to register special highlighting
162 protected void initAdditionalHighlightingTypes() {}
165 * remove highlights (bounded with <marker>...</marker>) from test case file
166 * @param document document to process
168 private void extractExpectedHighlightsSet(Document document) {
169 String text = document.getText();
171 final Set<String> markers = highlightingTypes.keySet();
172 String typesRegex = "";
173 for (String marker : markers) {
174 typesRegex += (typesRegex.length() == 0 ? "" : "|") + "(?:" + marker + ")";
177 // er...
178 // any code then <marker> (with optional descr="...") then any code then </marker> then any code
179 @NonNls String pat = ".*?(<(" + typesRegex + ")(?: descr=\"((?:[^\"\\\\]|\\\\\"|\\\\\\\\\")*)\")?(?: type=\"([0-9A-Z_]+)\")?(?: foreground=\"([0-9xa-f]+)\")?(?: background=\"([0-9xa-f]+)\")?(?: effectcolor=\"([0-9xa-f]+)\")?(?: effecttype=\"([A-Z]+)\")?(?: fonttype=\"([0-9]+)\")?(/)?>)(.*)";
180 //"(.+?)</" + marker + ">).*";
181 Pattern p = Pattern.compile(pat, Pattern.DOTALL);
182 Out:
183 for (; ;) {
184 Matcher m = p.matcher(text);
185 if (!m.matches()) break;
186 int startOffset = m.start(1);
187 String marker = m.group(2);
188 ExpectedHighlightingSet expectedHighlightingSet = highlightingTypes.get(marker);
190 while (!expectedHighlightingSet.enabled) {
191 if (!m.find()) break Out;
192 marker = m.group(2);
193 startOffset = m.start(1);
194 expectedHighlightingSet = highlightingTypes.get(marker);
196 int pos=3;
197 @NonNls String descr = m.group(pos++);
198 if (descr == null) {
199 // no descr means any string by default
200 descr = ANY_TEXT;
202 else if (descr.equals("null")) {
203 // explicit "null" descr
204 descr = null;
207 // replace: \\" to ", doesn't check symbol before sequence \\"
208 if (descr != null) {
209 descr = descr.replaceAll("\\\\\\\\\"", "\"");
212 String typeString = m.group(pos++);
213 String foregroundColor = m.group(pos++);
214 String backgroundColor = m.group(pos++);
215 String effectColor = m.group(pos++);
216 String effectType = m.group(pos++);
217 String fontType = m.group(pos++);
218 String closeTagMarker = m.group(pos++);
219 String rest = m.group(pos++);
221 String content;
222 int endOffset;
223 if (closeTagMarker == null) {
224 Pattern pat2 = Pattern.compile("(.*?)</" + marker + ">(.*)", Pattern.DOTALL);
225 final Matcher matcher2 = pat2.matcher(rest);
226 LOG.assertTrue(matcher2.matches(), "Cannot find closing </" + marker + ">");
227 content = matcher2.group(1);
228 endOffset = m.start(pos-1) + matcher2.start(2);
230 else {
231 // <XXX/>
232 content = "";
233 endOffset = m.start(pos-1);
236 document.replaceString(startOffset, endOffset, content);
237 TextAttributes forcedAttributes = null;
238 if (foregroundColor != null) {
239 forcedAttributes = new TextAttributes(Color.decode(foregroundColor), Color.decode(backgroundColor),
240 Color.decode(effectColor), EffectType.valueOf(effectType),
241 Integer.parseInt(fontType));
244 TextRange textRange = new TextRange(startOffset, startOffset + content.length());
246 HighlightInfoType type = WHATEVER;
248 if (typeString != null) {
249 try {
250 Field field = HighlightInfoType.class.getField(typeString);
251 type = (HighlightInfoType)field.get(null);
253 catch (Exception e) {
254 // ignore
256 LOG.assertTrue(type != null, "Wrong highlight type: " + typeString);
260 HighlightInfo highlightInfo = new HighlightInfo(forcedAttributes, type, textRange.getStartOffset(), textRange.getEndOffset(), descr,
261 descr, expectedHighlightingSet.severity, expectedHighlightingSet.endOfLine, null,
262 false);
263 expectedHighlightingSet.infos.add(highlightInfo);
264 text = document.getText();
268 private static final HighlightInfoType WHATEVER = new HighlightInfoType.HighlightInfoTypeImpl();
270 public Collection<HighlightInfo> getExtractedHighlightInfos(){
271 final Collection<HighlightInfo> result = new ArrayList<HighlightInfo>();
272 final Collection<ExpectedHighlightingSet> collection = highlightingTypes.values();
273 for (ExpectedHighlightingSet set : collection) {
274 result.addAll(set.infos);
276 return result;
279 public void checkLineMarkers(Collection<LineMarkerInfo> markerInfos, String text) {
280 String fileName = myFile == null ? "" : myFile.getName() + ": ";
281 String failMessage = "";
283 if (markerInfos != null) {
284 for (LineMarkerInfo info : markerInfos) {
285 if (!containsLineMarker(info, lineMarkerInfos.values())) {
286 final int startOffset = info.startOffset;
287 final int endOffset = info.endOffset;
289 int y1 = StringUtil.offsetToLineNumber(text, startOffset);
290 int y2 = StringUtil.offsetToLineNumber(text, endOffset);
291 int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
292 int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
294 if (failMessage.length() != 0) failMessage += '\n';
295 failMessage += fileName + "Extra line marker highlighted " +
296 "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
297 "(" + (x2 + 1) + ", " + (y2 + 1) + ")"
298 + ": '"+info.getLineMarkerTooltip()+"'"
304 for (LineMarkerInfo expectedLineMarker : lineMarkerInfos.values()) {
305 if (!containsLineMarker(expectedLineMarker, markerInfos)) {
306 final int startOffset = expectedLineMarker.startOffset;
307 final int endOffset = expectedLineMarker.endOffset;
309 int y1 = StringUtil.offsetToLineNumber(text, startOffset);
310 int y2 = StringUtil.offsetToLineNumber(text, endOffset);
311 int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
312 int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
314 if (failMessage.length() != 0) failMessage += '\n';
315 failMessage += fileName + "Line marker was not highlighted " +
316 "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
317 "(" + (x2 + 1) + ", " + (y2 + 1) + ")"
318 + ": '"+expectedLineMarker.getLineMarkerTooltip()+"'"
323 if (failMessage.length() > 0) Assert.assertTrue(failMessage, false);
326 private static boolean containsLineMarker(LineMarkerInfo info, Collection<LineMarkerInfo> where) {
327 final String infoTooltip = info.getLineMarkerTooltip();
329 for (LineMarkerInfo markerInfo : where) {
330 String markerInfoTooltip;
331 if (markerInfo.startOffset == info.startOffset &&
332 markerInfo.endOffset == info.endOffset &&
333 ( Comparing.equal(infoTooltip, markerInfoTooltip = markerInfo.getLineMarkerTooltip()) ||
334 ANY_TEXT.equals(markerInfoTooltip) ||
335 ANY_TEXT.equals(infoTooltip)
338 return true;
341 return false;
344 public void checkResult(Collection<HighlightInfo> infos, String text) {
345 String fileName = myFile == null ? "" : myFile.getName() + ": ";
346 String failMessage = "";
348 for (HighlightInfo info : infos) {
349 if (!expectedInfosContainsInfo(info)) {
350 final int startOffset = info.startOffset;
351 final int endOffset = info.endOffset;
352 String s = text.substring(startOffset, endOffset);
353 String desc = info.description;
355 int y1 = StringUtil.offsetToLineNumber(text, startOffset);
356 int y2 = StringUtil.offsetToLineNumber(text, endOffset);
357 int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
358 int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
360 if (failMessage.length() != 0) failMessage += '\n';
361 failMessage += fileName + "Extra text fragment highlighted " +
362 "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
363 "(" + (x2 + 1) + ", " + (y2 + 1) + ")" +
364 " :'" +
366 "'" + (desc == null ? "" : " (" + desc + ")")
367 + " [" + info.type + "]";
371 final Collection<ExpectedHighlightingSet> expectedHighlights = highlightingTypes.values();
372 for (ExpectedHighlightingSet highlightingSet : expectedHighlights) {
373 final Set<HighlightInfo> expInfos = highlightingSet.infos;
374 for (HighlightInfo expectedInfo : expInfos) {
375 if (!infosContainsExpectedInfo(infos, expectedInfo) && highlightingSet.enabled) {
376 final int startOffset = expectedInfo.startOffset;
377 final int endOffset = expectedInfo.endOffset;
378 String s = text.substring(startOffset, endOffset);
379 String desc = expectedInfo.description;
381 int y1 = StringUtil.offsetToLineNumber(text, startOffset);
382 int y2 = StringUtil.offsetToLineNumber(text, endOffset);
383 int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
384 int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
386 if (failMessage.length() != 0) failMessage += '\n';
387 failMessage += fileName + "Text fragment was not highlighted " +
388 "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
389 "(" + (x2 + 1) + ", " + (y2 + 1) + ")" +
390 " :'" +
392 "'" + (desc == null ? "" : " (" + desc + ")");
397 if (failMessage.length() > 0) {
398 compareTexts(infos, text, failMessage);
402 private void compareTexts(Collection<HighlightInfo> infos, String text, String failMessage) {
403 final ArrayList<HighlightInfo> list = new ArrayList<HighlightInfo>(infos);
404 Collections.sort(list, new Comparator<HighlightInfo>() {
405 public int compare(HighlightInfo o1, HighlightInfo o2) {
406 return o2.startOffset - o1.startOffset;
410 StringBuilder sb = new StringBuilder();
412 try {
413 int end = text.length();
414 for (HighlightInfo info : list) {
415 for (Map.Entry<String, ExpectedHighlightingSet> entry : highlightingTypes.entrySet()) {
416 final ExpectedHighlightingSet set = entry.getValue();
417 if(set.enabled
418 && set.severity == info.getSeverity()
419 //&& (set.defaultErrorType.equals(info.type))
420 && set.endOfLine == info.isAfterEndOfLine
422 final String severity = entry.getKey();
423 sb.insert(0, text.substring(info.endOffset, end));
424 sb.insert(0, "<"+
425 severity +" descr=\"" + info.description+"\">"+ text.substring(info.startOffset, info.endOffset)+"</"+
426 severity +">");
427 end = info.startOffset;
428 break;
432 sb.insert(0, text.substring(0, end));
434 catch (IndexOutOfBoundsException e) {
435 //sometimes (rarely) we have info offsets < 0
436 sb.insert(0, e.getMessage());
439 Assert.assertEquals(failMessage + "\n" , myText, sb.toString());
440 Assert.fail(failMessage);
443 private static boolean infosContainsExpectedInfo(Collection<HighlightInfo> infos, HighlightInfo expectedInfo) {
444 for (HighlightInfo info : infos) {
445 if (infoEquals(expectedInfo, info)) {
446 return true;
449 return false;
452 private boolean expectedInfosContainsInfo(HighlightInfo info) {
453 if (info.getTextAttributes(null) == TextAttributes.ERASE_MARKER) return true;
454 final Collection<ExpectedHighlightingSet> expectedHighlights = highlightingTypes.values();
455 for (ExpectedHighlightingSet highlightingSet : expectedHighlights) {
456 if (highlightingSet.severity != info.getSeverity()) continue;
457 if (!highlightingSet.enabled) return true;
458 final Set<HighlightInfo> infos = highlightingSet.infos;
459 for (HighlightInfo expectedInfo : infos) {
460 if (infoEquals(expectedInfo, info)) {
461 return true;
465 return false;
468 private static boolean infoEquals(HighlightInfo expectedInfo, HighlightInfo info) {
469 if (expectedInfo == info) return true;
470 return
471 info.getSeverity() == expectedInfo.getSeverity() &&
472 info.startOffset /*+ (info.isAfterEndOfLine ? 1 : 0)*/ == expectedInfo.startOffset &&
473 info.endOffset == expectedInfo.endOffset &&
474 info.isAfterEndOfLine == expectedInfo.isAfterEndOfLine &&
475 (expectedInfo.type == WHATEVER || expectedInfo.type.equals(info.type)) &&
476 (Comparing.strEqual(ANY_TEXT, expectedInfo.description) || Comparing.strEqual(info.description, expectedInfo.description))
477 && (expectedInfo.forcedTextAttributes == null || expectedInfo.getTextAttributes(null).equals(info.getTextAttributes(null)))