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
;
31 import java
.lang
.reflect
.Field
;
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
= "*";
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
,
78 this(document
, checkWarnings
, checkWeakWarnings
, checkInfos
, null);
81 public ExpectedHighlightingData(Document document
,
82 boolean checkWarnings
,
83 boolean checkWeakWarnings
,
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
);
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
) {
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
+ ")";
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
);
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
;
193 startOffset
= m
.start(1);
194 expectedHighlightingSet
= highlightingTypes
.get(marker
);
197 @NonNls String descr
= m
.group(pos
++);
199 // no descr means any string by default
202 else if (descr
.equals("null")) {
203 // explicit "null" descr
207 // replace: \\" to ", doesn't check symbol before sequence \\"
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
++);
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);
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) {
250 Field field
= HighlightInfoType
.class.getField(typeString
);
251 type
= (HighlightInfoType
)field
.get(null);
253 catch (Exception e
) {
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,
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
);
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
)
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) + ")" +
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) + ")" +
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();
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();
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
));
425 severity
+" descr=\"" + info
.description
+"\">"+ text
.substring(info
.startOffset
, info
.endOffset
)+"</"+
427 end
= info
.startOffset
;
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
)) {
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
)) {
468 private static boolean infoEquals(HighlightInfo expectedInfo
, HighlightInfo info
) {
469 if (expectedInfo
== info
) return true;
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)))