00c73a64b53bb13b1f1a35dc25c3ae881bb83ec1
[idea/community.git] / java / testFramework / src / com / intellij / debugger / impl / OutputChecker.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.debugger.impl;
17
18 import com.intellij.execution.process.ProcessOutputTypes;
19 import com.intellij.idea.IdeaLogger;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.PathManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.projectRoots.Sdk;
25 import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
26 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
27 import com.intellij.openapi.util.Key;
28 import com.intellij.openapi.util.SystemInfo;
29 import com.intellij.openapi.util.ThrowableComputable;
30 import com.intellij.openapi.util.io.FileUtilRt;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.openapi.util.text.StringUtilRt;
33 import com.intellij.openapi.vfs.CharsetToolkit;
34 import com.intellij.util.containers.HashMap;
35 import org.jetbrains.annotations.NotNull;
36 import org.junit.Assert;
37
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.net.InetAddress;
41 import java.net.UnknownHostException;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.regex.Matcher;
47 import java.util.regex.Pattern;
48
49 public class OutputChecker {
50   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.OutputChecker");
51   private static final String JDK_HOME_STR = "!JDK_HOME!";
52   private static final String TEST_JDK_HOME_STR = "!TEST_JDK!";
53
54   protected final String myAppPath;
55
56   public static final Key[] OUTPUT_ORDER = new Key[] {
57     ProcessOutputTypes.SYSTEM, ProcessOutputTypes.STDOUT, ProcessOutputTypes.STDERR
58   };
59   private Map<Key, StringBuffer> myBuffers;
60   protected String myTestName;
61
62
63   //ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
64   private static final Pattern JDI_BUG_OUTPUT_PATTERN_1 =
65     Pattern.compile("ERROR\\:\\s+JDWP\\s+Unable\\s+to\\s+get\\s+JNI\\s+1\\.2\\s+environment,\\s+jvm-\\>GetEnv\\(\\)\\s+return\\s+code\\s+=\\s+-2\n");
66   //JDWP exit error AGENT_ERROR_NO_JNI_ENV(183):  [../../../src/share/back/util.c:820]
67   private static final Pattern JDI_BUG_OUTPUT_PATTERN_2 =
68     Pattern.compile("JDWP\\s+exit\\s+error\\s+AGENT_ERROR_NO_JNI_ENV.*\\]\n");
69
70   public OutputChecker(String appPath) {
71     myAppPath = appPath;
72   }
73
74   public void init(String testName) {
75     IdeaLogger.ourErrorsOccurred = null;
76
77     testName = Character.toLowerCase(testName.charAt(0)) + testName.substring(1);
78     myTestName = testName;
79     synchronized (this) {
80       myBuffers = new HashMap<Key, StringBuffer>();
81     }
82   }
83
84   public void print(String s, Key outputType) {
85     synchronized (this) {
86       if (myBuffers != null) {
87         StringBuffer buffer = myBuffers.get(outputType);
88         if (buffer == null) {
89           myBuffers.put(outputType, buffer = new StringBuffer());
90         }
91         buffer.append(s);
92       }
93     }
94   }
95
96   public void println(String s, Key outputType) {
97     print(s + "\n", outputType);
98   }
99
100   public void checkValid(Sdk jdk) throws Exception {
101     checkValid(jdk, false);
102   }
103
104   public void checkValid(Sdk jdk, boolean sortClassPath) throws Exception {
105     if (IdeaLogger.ourErrorsOccurred != null) {
106       throw IdeaLogger.ourErrorsOccurred;
107     }
108
109     String actual = preprocessBuffer(jdk, buildOutputString(), sortClassPath);
110
111     File outs = new File(myAppPath + File.separator + "outs");
112     assert outs.exists() || outs.mkdirs() : outs;
113
114     File outFile = new File(outs, myTestName + ".out");
115     if (!outFile.exists()) {
116       if (SystemInfo.isWindows) {
117         final File winOut = new File(outs, myTestName + ".win.out");
118         if (winOut.exists()) {
119           outFile = winOut;
120         }
121       }
122       else if (SystemInfo.isUnix) {
123         final File unixOut = new File(outs, myTestName + ".unx.out");
124         if (unixOut.exists()) {
125           outFile = unixOut;
126         }
127       }
128     }
129
130     if (!outFile.exists()) {
131       FileOutputStream fos = new FileOutputStream(outFile, false);
132       try {
133         fos.write(actual.getBytes());
134       }
135       finally {
136         fos.close();
137       }
138       LOG.error("Test file created " + outFile.getPath() + "\n" + "**************** Don't forget to put it into VCS! *******************");
139     }
140     else {
141       String originalText = FileUtilRt.loadFile(outFile, CharsetToolkit.UTF8);
142       String expected = StringUtilRt.convertLineSeparators(originalText);
143       if (!expected.equals(actual)) {
144         System.out.println("expected:");
145         System.out.println(originalText);
146         System.out.println("actual:");
147         System.out.println(actual);
148
149         final int len = Math.min(expected.length(), actual.length());
150         if (expected.length() != actual.length()) {
151           System.out.println("Text sizes differ: expected " + expected.length() + " but actual: " + actual.length());
152         }
153         if (expected.length() > len) {
154           System.out.println("Rest from expected text is: \"" + expected.substring(len) + "\"");
155         }
156         else if (actual.length() > len) {
157           System.out.println("Rest from actual text is: \"" + actual.substring(len) + "\"");
158         }
159
160         Assert.assertEquals(originalText, actual);
161       }
162     }
163   }
164
165   private synchronized String buildOutputString() {
166     final StringBuilder result = new StringBuilder();
167     for (Key key : OUTPUT_ORDER) {
168       final StringBuffer buffer = myBuffers.get(key);
169       if (buffer != null) {
170         result.append(buffer.toString());
171       }
172     }
173     return result.toString();
174   }
175
176   private String preprocessBuffer(final Sdk testJdk, final String buffer, final boolean sortClassPath) throws Exception {
177     Application application = ApplicationManager.getApplication();
178
179     if (application == null) return buffer;
180
181     return application.runReadAction(new ThrowableComputable<String, Exception>() {
182       @Override
183       public String compute() throws UnknownHostException {
184         String internalJdkHome = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk().getHomeDirectory().getPath();
185         //System.out.println("internalJdkHome = " + internalJdkHome);
186
187         String result = buffer;
188         //System.out.println("Original Output = " + result);
189         final boolean shouldIgnoreCase = !SystemInfo.isFileSystemCaseSensitive;
190
191         result = StringUtil.replace(result, "\r\n", "\n");
192         result = StringUtil.replace(result, "\r", "\n");
193         result = replaceAdditionalInOutput(result);
194         final String testJdkHome = testJdk.getHomePath();
195         result = StringUtil.replace(result, testJdkHome.replace('/', File.separatorChar), TEST_JDK_HOME_STR, shouldIgnoreCase);
196         result = StringUtil.replace(result, testJdkHome, TEST_JDK_HOME_STR, shouldIgnoreCase);
197         result = StringUtil.replace(result, myAppPath, "!APP_PATH!", shouldIgnoreCase);
198         result = StringUtil.replace(result, myAppPath.replace(File.separatorChar, '/'), "!APP_PATH!", shouldIgnoreCase);
199         result = StringUtil.replace(result, JavaSdkUtil.getIdeaRtJarPath(), "!RT_JAR!", shouldIgnoreCase);
200         result = StringUtil.replace(result, JavaSdkUtil.getJunit4JarPath(), "!JUNIT4_JAR!", shouldIgnoreCase);
201         result = StringUtil.replace(result, InetAddress.getLocalHost().getCanonicalHostName(), "!HOST_NAME!", shouldIgnoreCase);
202         result = StringUtil.replace(result, InetAddress.getLocalHost().getHostName(), "!HOST_NAME!", shouldIgnoreCase);
203         result = StringUtil.replace(result, "127.0.0.1", "!HOST_NAME!", shouldIgnoreCase);
204         result = StringUtil.replace(result, JavaSdkUtil.getIdeaRtJarPath().replace('/', File.separatorChar), "!RT_JAR!", shouldIgnoreCase);
205         result = StringUtil.replace(result, internalJdkHome.replace('/', File.separatorChar), JDK_HOME_STR, shouldIgnoreCase);
206         result = StringUtil.replace(result, internalJdkHome, JDK_HOME_STR, shouldIgnoreCase);
207         result = StringUtil.replace(result, PathManager.getHomePath(), "!IDEA_HOME!", shouldIgnoreCase);
208         result = StringUtil.replace(result, "Process finished with exit code 255", "Process finished with exit code -1");
209
210         //          result = result.replaceAll(" +\n", "\n");
211         result = result.replaceAll("!HOST_NAME!:\\d*", "!HOST_NAME!:!HOST_PORT!");
212         result = result.replaceAll("at \\'.*?\\'", "at '!HOST_NAME!:PORT_NAME!'");
213         result = result.replaceAll("address: \\'.*?\\'", "address: '!HOST_NAME!:PORT_NAME!'");
214         result = result.replaceAll("file.*AppletPage.*\\.html", "file:/!APPLET_HTML!");
215         result = result.replaceAll("\"(!JDK_HOME!.*?)\"", "$1");
216         result = result.replaceAll("\"(!APP_PATH!.*?)\"", "$1");
217         result = result.replaceAll("\"(" + TEST_JDK_HOME_STR + ".*?)\"", "$1");
218
219         // unquote extra params
220         result = result.replaceAll("\"(-D.*)\"", "$1");
221
222         result = result.replaceAll("-Didea.launcher.port=\\d*", "-Didea.launcher.port=!IDEA_LAUNCHER_PORT!");
223         result = result.replaceAll("-Dfile.encoding=[\\w\\d-]*", "-Dfile.encoding=!FILE_ENCODING!");
224         result = result.replaceAll("\\((.*)\\:\\d+\\)", "($1:!LINE_NUMBER!)");
225
226         result = fixSlashes(result, TEST_JDK_HOME_STR);
227         result = fixSlashes(result, JDK_HOME_STR);
228
229         result = stripQuotesAroundClasspath(result);
230
231         final Matcher matcher = Pattern.compile("-classpath\\s+(\\S+)\\s+").matcher(result);
232         while (matcher.find()) {
233           final String classPath = matcher.group(1);
234           final String[] classPathElements = classPath.split(File.pathSeparator);
235
236           // Combine all JDK jars into one marker
237           List<String> classpathRes = new ArrayList<String>();
238           boolean hasJdkJars = false;
239           for (String element : classPathElements) {
240             if (!element.startsWith(JDK_HOME_STR)) {
241               classpathRes.add(element);
242             }
243             else {
244               hasJdkJars = true;
245             }
246           }
247           if (hasJdkJars) {
248             classpathRes.add("!JDK_JARS!");
249           }
250
251           if (sortClassPath) {
252             Collections.sort(classpathRes);
253           }
254
255           final String sortedPath = StringUtil.join(classpathRes, ";");
256           result = StringUtil.replace(result, classPath, sortedPath);
257         }
258
259         result = JDI_BUG_OUTPUT_PATTERN_1.matcher(result).replaceAll("");
260         result = JDI_BUG_OUTPUT_PATTERN_2.matcher(result).replaceAll("");
261
262         return result;
263       }
264     });
265   }
266
267   @NotNull
268   private static String fixSlashes(String text, final String jdkHomeMarker) {
269     int commandLineStart = text.indexOf(jdkHomeMarker);
270     while (commandLineStart != -1) {
271       final StringBuilder builder = new StringBuilder(text);
272       int i = commandLineStart + 1;
273       while (i < builder.length()) {
274         char c = builder.charAt(i);
275         if (c == '\n') break;
276         else if (c == File.separatorChar) builder.setCharAt(i, '\\');
277         i++;
278       }
279       text = builder.toString();
280       commandLineStart = text.indexOf(jdkHomeMarker, commandLineStart + 1);
281     }
282     return text;
283   }
284
285   protected String replaceAdditionalInOutput(String str) {
286     return str;
287   }
288
289   //do not depend on spaces in jdk path
290   private static String stripQuotesAroundClasspath(String result) {
291     final String clsp = "-classpath ";
292     int clspIdx = 0;
293     while (true) {
294       clspIdx = result.indexOf(clsp, clspIdx);
295       if (clspIdx <= -1) {
296         break;
297       }
298
299       final int spaceIdx = result.indexOf(" ", clspIdx + clsp.length());
300       if (spaceIdx > -1) {
301         result = result.substring(0, clspIdx) +
302                  clsp +
303                  StringUtil.stripQuotesAroundValue(result.substring(clspIdx + clsp.length(), spaceIdx)) +
304                  result.substring(spaceIdx);
305         clspIdx += clsp.length();
306       } else {
307         break;
308       }
309     }
310     return result;
311   }
312 }