1   /*
2    * The MIT License
3    *
4    * Copyright (c) <2010> <tap4j>
5    * 
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   * 
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   * 
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  package org.tap4j.ext.junit;
25  
26  import java.io.PrintWriter;
27  import java.io.StringWriter;
28  import java.text.SimpleDateFormat;
29  import java.util.Date;
30  import java.util.StringTokenizer;
31  import java.util.regex.Matcher;
32  import java.util.regex.Pattern;
33  
34  import org.junit.runner.Description;
35  import org.yaml.snakeyaml.DumperOptions.LineBreak;
36  
37  /**
38   * JUnit YAMLish utility class.
39   * 
40   * @since 1.4.3
41   */
42  public final class JUnitYAMLishUtils {
43  
44      /**
45       * Date Format used to format a datetime in ISO-8061 for YAMLish diagnostic.
46       */
47      public static final SimpleDateFormat ISO_8061_DATE_FORMAT = new SimpleDateFormat(
48                                                                                       "yyyy-MM-dd'T'HH:mm:ss");
49  
50      public static final String LINE_SEPARATOR = LineBreak.UNIX.getString();
51  
52      /**
53       * Default hidden constructor.
54       */
55      private JUnitYAMLishUtils() {
56          super();
57      }
58  
59      /**
60       * Generate a message with the name of the tested method
61       * 
62       * @param testMethod
63       * @return test message
64       */
65      public static String getMessage(JUnitTestData testMethod) {
66          return "JUnit 4.0 Test " + testMethod.getDescription().getDisplayName();
67      }
68  
69      /**
70       * Get the severity of the test
71       * 
72       * @param testMethod
73       * @return severity
74       */
75      public static String getSeverity(JUnitTestData testMethod) {
76          String severity = "~";
77          if (testMethod.getFailException() != null) {
78              severity = "High";
79          }
80          return severity;
81      }
82  
83      /**
84       * @param testMethod
85       * @param testClass
86       * @return test source
87       */
88      public static String getSource(String testMethod, String testClass) {
89          return testClass + "#" + testMethod;
90      }
91  
92      /**
93       * Get a date time string
94       * 
95       * @return date time string
96       */
97      public static String getDatetime() {
98          long currentTimeMillis = System.currentTimeMillis();
99          Date date = new Date(currentTimeMillis);
100         return ISO_8061_DATE_FORMAT.format(date);
101     }
102 
103     /**
104      * Get the file name of the tested method
105      * 
106      * @param testMethod
107      * @return the file name
108      */
109     public static String getFile(JUnitTestData testMethod) {
110         return extractClassName(testMethod.getDescription());
111     }
112 
113     /**
114      * Get the line of the error in the exception info
115      * 
116      * @param testMethod
117      * @return line of the error in the exception info
118      */
119     public static String getLine(JUnitTestData testMethod) {
120         String line = "~";
121         Throwable testException = testMethod.getFailException();
122         if (testException != null) {
123             StringBuilder lookFor = new StringBuilder();
124             lookFor.append(extractClassName(testMethod.getDescription()));
125             lookFor.append('.');
126             lookFor.append(extractMethodName(testMethod.getDescription()));
127             lookFor.append('(');
128             lookFor.append(extractSimpleClassName(testMethod.getDescription()));
129             lookFor.append(".java:");
130 
131             StackTraceElement[] els = testException.getStackTrace();
132 
133             for (int i = 0; i < els.length; i++) {
134                 StackTraceElement el = els[i];
135                 line = getLineNumberFromExceptionTraceLine(el.toString(),
136                                                            lookFor.toString());
137                 if (line.equals("") == Boolean.FALSE) {
138                     break;
139                 }
140             }
141         }
142         return line;
143     }
144 
145     /**
146      * Get the error line number from the exception stack trace
147      * 
148      * @param exceptionTraceLine
149      * @param substrToSearch
150      * @return error line number
151      */
152     public static String getLineNumberFromExceptionTraceLine(String exceptionTraceLine,
153                                                              String substrToSearch) {
154         String lineNumber = "";
155         int index = exceptionTraceLine.indexOf(substrToSearch);
156         if (index >= 0) {
157             int length = substrToSearch.length() + index;
158             if (exceptionTraceLine.lastIndexOf(')') > length) {
159                 lineNumber = exceptionTraceLine
160                     .substring(length, exceptionTraceLine.lastIndexOf(')'));
161             }
162         }
163         return lineNumber;
164     }
165 
166     /**
167      * Get tested method name
168      * 
169      * @param testMethod
170      * @return tested method name
171      */
172     public static String getName(JUnitTestData testMethod) {
173         return extractMethodName(testMethod.getDescription());
174     }
175 
176     /**
177      * Get the error message from a given failed JUnit test result
178      * 
179      * @param testMethod
180      * @return error message from a given failed JUnit test result
181      */
182     public static String getError(JUnitTestData testMethod) {
183         String error = "~";
184         Throwable t = testMethod.getFailException();
185         if (t != null) {
186             error = t.getMessage();
187         }
188         return error;
189     }
190 
191     /**
192      * Get the backtrace from a given failed JUnit test result
193      * 
194      * @param testMethod
195      * @return Backtrace from a given failed JUnit test result
196      */
197     public static String getBacktrace(JUnitTestData testMethod) {
198         StringBuilder stackTrace = new StringBuilder();
199 
200         Throwable throwable = testMethod.getFailException();
201 
202         if (throwable != null) {
203             StringWriter sw = new StringWriter();
204             PrintWriter pw = new PrintWriter(sw);
205             throwable.printStackTrace(pw);
206             String stackTraceString = sw.toString();
207             stackTraceString = stackTraceString.trim().replaceAll("\\r\\n",
208                                                                   "\n");
209 
210             StringTokenizer st = new StringTokenizer(stackTraceString,
211                                                      LINE_SEPARATOR);
212 
213             while (st.hasMoreTokens()) {
214                 String stackTraceLine = st.nextToken();
215                 stackTrace.append(stackTraceLine);
216                 stackTrace.append(LINE_SEPARATOR);
217             }
218 
219         } else {
220             stackTrace.append('~');
221         }
222 
223         return stackTrace.toString();
224     }
225 
226     /**
227      * Extract the class name from a given junit test description
228      * 
229      * @param description
230      * @return a class name
231      */
232     public static String extractClassName(Description description) {
233         String displayName = description.getDisplayName();
234 
235         String regex = "^" + "[^\\(\\)]+" // non-parens
236                        + "\\((" // then an open-paren (start matching a group)
237                        + "[^\\\\(\\\\)]+" // non-parens
238                        + ")\\)" + "$";
239         // System.out.println(regex);
240         final Pattern parens = Pattern.compile(regex); // then a close-paren
241                                                        // (end group match)
242         Matcher m = parens.matcher(displayName);
243         if (!m.find()) {
244             return displayName;
245         }
246         return m.group(1);
247     }
248 
249     /**
250      * Extract the simple class name from a given junit test description
251      * 
252      * @param description
253      * @return a simple class name
254      */
255     public static String extractSimpleClassName(Description description) {
256         String simpleClassName = null;
257         String className = extractClassName(description);
258         String[] splitClassName = className.split("\\.");
259 
260         if (splitClassName.length > 0) {
261             simpleClassName = splitClassName[splitClassName.length - 1];
262         }
263 
264         return simpleClassName;
265     }
266 
267     /**
268      * Get the tested method name
269      * 
270      * @param description
271      * @return tested methode name
272      */
273     public static String extractMethodName(Description description) {
274         String methodName = null;
275         String[] splitDisplayName = description.getDisplayName().split("\\(");
276 
277         if (splitDisplayName.length > 0) {
278             methodName = splitDisplayName[0];
279         }
280 
281         return methodName;
282     }
283 }