package org.testng.internal; import javax.annotation.Nullable; import org.testng.ITestNGMethod; import org.testng.TestNG; import org.testng.TestNGException; import org.testng.TestRunner; import org.testng.annotations.IConfigurationAnnotation; import org.testng.annotations.ITestAnnotation; import org.testng.collections.Lists; import org.testng.internal.annotations.AnnotationHelper; import org.testng.internal.annotations.IAnnotationFinder; import org.testng.log4testng.Logger; import org.testng.reporters.XMLStringBuffer; import org.testng.xml.XmlClass; import java.io.*; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * Helper methods to parse annotations. */ public final class Utils { private static final String LINE_SEP = System.getProperty("line.separator"); public static final char[] SPECIAL_CHARACTERS = {'*','/','\\','?','%',':',';','<','>','&','~','|'}; public static final char CHAR_REPLACEMENT = '_'; public static final char UNICODE_REPLACEMENT = 0xFFFD; private static final String FORMAT = String.format("[%s]", Utils.class.getSimpleName()); private static final Logger LOG = Logger.getLogger(Utils.class); private static final Map<Character, String> ESCAPES = new HashMap<Character, String>() { private static final long serialVersionUID = 1285607660247157523L; { put('<', "<"); put('>', ">"); put('\'', "'"); put('"', """); put('&', "&"); }}; /** * Hide constructor for utility class. */ private Utils() { // Hide constructor } /** * Splits the given String s into tokens where the separator is * either the space character or the comma character. For example, * if s is "a,b, c" this method returns {"a", "b", "c"} * * @param s the string to split * @return the split token * * @deprecated Unused */ @Deprecated public static String[] stringToArray(String s) { // TODO CQ would s.split() be a better way of doing this? StringTokenizer st = new StringTokenizer(s, " ,"); String[] result = new String[st.countTokens()]; for (int i = 0; i < result.length; i++) { result[i] = st.nextToken(); } return result; } public static XmlClass[] classesToXmlClasses(Class<?>[] classes) { List<XmlClass> result = Lists.newArrayList(); for (Class<?> cls : classes) { result.add(new XmlClass(cls, true /* load classes */)); } return result.toArray(new XmlClass[classes.length]); } /** * @deprecated Unused */ @Deprecated public static String[] parseMultiLine(String line) { List<String> vResult = Lists.newArrayList(); if (isStringNotBlank(line)) { StringTokenizer st = new StringTokenizer(line, " "); while (st.hasMoreTokens()) { vResult.add(st.nextToken()); } // Bug in split when passed " " : returns one too many result // result = line.split(" "); } return vResult.toArray(new String[vResult.size()]); } public static void writeUtf8File(@Nullable String outputDir, String fileName, XMLStringBuffer xsb, String prefix) { try { final File outDir = (outputDir != null) ? new File(outputDir) : new File("").getAbsoluteFile(); if (!outDir.exists()) { outDir.mkdirs(); } final File file = new File(outDir, fileName); if (!file.exists()) { file.createNewFile(); } try (final OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { if (prefix != null) { w.append(prefix); } xsb.toWriter(w); } } catch(IOException ex) { LOG.error(ex.getMessage(), ex); } } /** * Writes the content of the sb string to the file named filename in outDir encoding the output as UTF-8. * If outDir does not exist, it is created. * * @param outputDir the output directory (may not exist). If <tt>null</tt> then current directory is used. * @param fileName the filename * @param sb the file content */ public static void writeUtf8File(@Nullable String outputDir, String fileName, String sb) { final String outDirPath= outputDir != null ? outputDir : ""; final File outDir= new File(outDirPath); writeFile(outDir, fileName, escapeUnicode(sb), "UTF-8"); } /** * Writes the content of the sb string to the file named filename in outDir. If * outDir does not exist, it is created. * * @param outputDir the output directory (may not exist). If <tt>null</tt> then current directory is used. * @param fileName the filename * @param sb the file content */ public static void writeFile(@Nullable String outputDir, String fileName, String sb) { final String outDirPath= outputDir != null ? outputDir : ""; final File outDir= new File(outDirPath); writeFile(outDir, fileName, sb, null); } /** * Writes the content of the sb string to the file named filename in outDir. If * outDir does not exist, it is created. * * @param outDir the output directory (may not exist). If <tt>null</tt> then current directory is used. * @param fileName the filename * @param sb the file content */ private static void writeFile(@Nullable File outDir, String fileName, String sb, @Nullable String encoding) { try { if (outDir == null) { outDir = new File("").getAbsoluteFile(); } if (!outDir.exists()) { outDir.mkdirs(); } fileName = replaceSpecialCharacters(fileName); File outputFile = new File(outDir, fileName); outputFile.delete(); log(FORMAT, 3, "Attempting to create " + outputFile); log(FORMAT, 3, " Directory " + outDir + " exists: " + outDir.exists()); outputFile.createNewFile(); writeFile(outputFile, sb, encoding); } catch (IOException e) { if (TestRunner.getVerbose() > 1) { LOG.error(e.getMessage(),e); } else { log(FORMAT, 1, e.getMessage()); } } } private static void writeFile(File outputFile, String sb, @Nullable String encoding) { BufferedWriter fw = null; try { fw = openWriter(outputFile, encoding); fw.write(sb); Utils.log("", 3, "Creating " + outputFile.getAbsolutePath()); } catch(IOException ex) { if (TestRunner.getVerbose() > 1) { LOG.error("ERROR WHILE WRITING TO " + outputFile,ex); } else { log(FORMAT, 1, "Error while writing to " + outputFile + ": " + ex.getMessage()); } } finally { try { if (fw != null) { fw.close(); } } catch (IOException e) { // ignore } } } /** * Open a BufferedWriter for the specified file. If output directory doesn't * exist, it is created. If the output file exists, it is deleted. The output file is * created in any case. * @param outputDir output directory. If <tt>null</tt>, then current directory is used * @param fileName file name * @throws IOException if anything goes wrong while creating files. */ public static BufferedWriter openWriter(@Nullable String outputDir, String fileName) throws IOException { String outDirPath= outputDir != null ? outputDir : ""; File outDir= new File(outDirPath); if (!outDir.exists()) { outDir.mkdirs(); } fileName = replaceSpecialCharacters(fileName); File outputFile = new File(outDir, fileName); outputFile.delete(); return openWriter(outputFile, null); } private static BufferedWriter openWriter(File outputFile, @Nullable String encoding) throws IOException { if (!outputFile.exists()) { outputFile.createNewFile(); } OutputStreamWriter osw; if (null != encoding) { osw = new OutputStreamWriter(new FileOutputStream(outputFile), encoding); } else { osw = new OutputStreamWriter(new FileOutputStream(outputFile)); } return new BufferedWriter(osw); } private static void ppp(String s) { Utils.log("Utils", 0, s); } /** * @deprecated Unused */ @Deprecated public static void dumpMap(Map<?, ?> result) { LOG.info("vvvvv"); for (Map.Entry<?, ?> entry : result.entrySet()) { LOG.info(entry.getKey() + " => " + entry.getValue()); } LOG.info("^^^^^"); } /** * @deprecated Unused */ @Deprecated public static void dumpMethods(List<ITestNGMethod> allMethods) { ppp("======== METHODS:"); for (ITestNGMethod tm : allMethods) { ppp(" " + tm); } } /** * @deprecated Unused */ @Deprecated public static String[] dependentGroupsForThisMethodForTest(Method m, IAnnotationFinder finder) { List<String> vResult = Lists.newArrayList(); Class<?> cls = m.getDeclaringClass(); // Collect groups on the class ITestAnnotation tc = AnnotationHelper.findTest(finder, cls); if (null != tc) { for (String group : tc.getDependsOnGroups()) { vResult.add(group); } } // Collect groups on the method ITestAnnotation tm = AnnotationHelper.findTest(finder, m); if (null != tm) { String[] groups = tm.getDependsOnGroups(); for (String group : groups) { vResult.add(group); } } return vResult.toArray(new String[vResult.size()]); } /** * @deprecated Unused */ @Deprecated public static String[] groupsForThisMethodForTest(Method m, IAnnotationFinder finder) { List<String> vResult = Lists.newArrayList(); Class<?> cls = m.getDeclaringClass(); // Collect groups on the class ITestAnnotation tc = AnnotationHelper.findTest(finder, cls); if (null != tc) { for (String group : tc.getGroups()) { vResult.add(group); } } // Collect groups on the method ITestAnnotation tm = AnnotationHelper.findTest(finder, m); if (null != tm) { String[] groups = tm.getGroups(); for (String group : groups) { vResult.add(group); } } return vResult.toArray(new String[vResult.size()]); } /** * @deprecated Unused */ @Deprecated public static String[] groupsForThisMethodForConfiguration(Method m, IAnnotationFinder finder) { String[] result = {}; // Collect groups on the method ITestAnnotation tm = AnnotationHelper.findTest(finder, m); if (null != tm) { result = tm.getGroups(); } return result; } /** * @deprecated Unused */ @Deprecated public static String[] dependentGroupsForThisMethodForConfiguration(Method m, IAnnotationFinder finder) { String[] result = {}; // Collect groups on the method IConfigurationAnnotation tm = AnnotationHelper.findConfiguration(finder, m); if (null != tm) { result = tm.getDependsOnGroups(); } return result; } public static void log(String msg) { log("Utils", 2, msg); } /** * Logs the the message to System.out if level is greater than * or equal to TestRunner.getVerbose(). The message is logged as: * <pre> * "[cls] msg" * </pre> * * @param cls the class name to prefix the log message. * @param level the logging level of the message. * @param msg the message to log to System.out. */ public static void log(String cls, int level, String msg) { // Why this coupling on a static member of TestRunner.getVerbose()? if (TestRunner.getVerbose() >= level) { if (cls.length() > 0) { LOG.info("[" + cls + "] " + msg); } else { LOG.info(msg); } } } public static void error(String errorMessage) { LOG.error("[Error] " + errorMessage); } public static int calculateInvokedMethodCount(ITestNGMethod[] methods) { return methods.length; } public static int calculateInvokedMethodCount(List<ITestNGMethod> methods) { return methods.size(); } /** * Tokenize the string using the separator. */ public static String[] split(String string, String sep) { if ((string == null) || (string.length() == 0)) { return new String[0]; } // TODO How different is this from: // return string.split(sep); int start = 0; int idx = string.indexOf(sep, start); int len = sep.length(); List<String> strings = Lists.newArrayList(); while (idx != -1) { strings.add(string.substring(start, idx).trim()); start = idx + len; idx = string.indexOf(sep, start); } strings.add(string.substring(start).trim()); return strings.toArray(new String[strings.size()]); } /** * @deprecated Unused */ @Deprecated public static void logInvocation(String reason, Method thisMethod, Object[] parameters) { String clsName = thisMethod.getDeclaringClass().getName(); int n = clsName.lastIndexOf('.'); if (n >= 0) { clsName = clsName.substring(n + 1); } String methodName = clsName + '.' + thisMethod.getName(); if (TestRunner.getVerbose() >= 2) { StringBuilder paramString = new StringBuilder(); if (parameters != null) { for (Object p : parameters) { paramString.append(p.toString()).append(' '); } } log("", 2, "Invoking " + reason + methodName + '(' + paramString + ')'); } } public static void writeResourceToFile(File file, String resourceName, Class<?> clasz) throws IOException { InputStream inputStream = clasz.getResourceAsStream("/" + resourceName); if (inputStream == null) { LOG.error("Couldn't find resource on the class path: " + resourceName); return; } try { try (FileOutputStream outputStream = new FileOutputStream(file)) { int nread; byte[] buffer = new byte[4096]; while (0 < (nread = inputStream.read(buffer))) { outputStream.write(buffer, 0, nread); } } } finally { inputStream.close(); } } public static String defaultIfStringEmpty(String s, String defaultValue) { return isStringEmpty(s) ? defaultValue : s; } public static boolean isStringBlank(String s) { return s == null || "".equals(s.trim()); } public static boolean isStringEmpty(String s) { return s == null || "".equals(s); } public static boolean isStringNotBlank(String s) { return !isStringBlank(s); } public static boolean isStringNotEmpty(String s) { return !isStringEmpty(s); } /** * @return an array of two strings: the short stack trace and the long stack trace. * @deprecated - Please consider using : * <ul> * <li>{@link Utils#longStackTrace(Throwable, boolean)} - for getting full stack trace</li> * <li>{@link Utils#shortStackTrace(Throwable, boolean)} - for getting short stack trace</li> * </ul> */ @Deprecated public static String[] stackTrace(Throwable t, boolean toHtml) { return new String[] { shortStackTrace(t, toHtml), longStackTrace(t, toHtml) }; } /** * Helper that returns a short stack trace. * @param t - The {@link Throwable} exception * @param toHtml - <code>true</code> if the stacktrace should be translated to html as well * @return - A string that represents the short stack trace. */ public static String longStackTrace(Throwable t, boolean toHtml) { return buildStackTrace(t, toHtml, StackTraceType.FULL); } /** * Helper that returns a long stack trace. * @param t - The {@link Throwable} exception * @param toHtml - <code>true</code> if the stacktrace should be translated to html as well * @return - A string that represents the full stack trace. */ public static String shortStackTrace(Throwable t, boolean toHtml) { return buildStackTrace(t, toHtml, StackTraceType.SHORT); } private static String buildStackTrace(Throwable t, boolean toHtml, StackTraceType type) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.flush(); String stackTrace = sw.getBuffer().toString(); if (type == StackTraceType.SHORT && !isTooVerbose()) { stackTrace = filterTrace(sw.getBuffer().toString()); } if (toHtml) { stackTrace = escapeHtml(stackTrace); } return stackTrace; } private static boolean isTooVerbose() { return Boolean.getBoolean(TestNG.SHOW_TESTNG_STACK_FRAMES) || TestRunner.getVerbose() >= 2; } private enum StackTraceType { SHORT,FULL } public static String escapeHtml(String s) { if (s == null) { return null; } StringBuilder result = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); String nc = ESCAPES.get(c); if (nc != null) { result.append(nc); } else { result.append(c); } } return result.toString(); } public static String escapeUnicode(String s) { if (s == null) { return null; } StringBuilder result = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); char ca = Character.isDefined(c) ? c: UNICODE_REPLACEMENT; result.append(ca); } return result.toString(); } static String filterTrace(String trace) { StringReader stringReader = new StringReader(trace); BufferedReader bufferedReader = new BufferedReader(stringReader); StringBuilder buf = new StringBuilder(); try { // first line contains the thrown exception String line = bufferedReader.readLine(); if(line == null) { return ""; } buf.append(line).append(LINE_SEP); // // the stack frames of the trace // String[] excludedStrings = new String[] { "org.testng", "reflect", "org.gradle", "org.apache.maven.surefire" }; int excludedCount = 0; while((line = bufferedReader.readLine()) != null) { boolean isExcluded = false; for (String excluded : excludedStrings) { if(line.contains(excluded)) { isExcluded = true; excludedCount++; break; } } if (! isExcluded) { buf.append(line).append(LINE_SEP); } } if (excludedCount > 0) { buf.append("... Removed ").append(excludedCount).append(" stack frames"); } } catch(IOException ioex) { // do nothing } return buf.toString(); } public static String toString(Object object, Class<?> objectClass) { if(null == object) { return "null"; } final String toString= object.toString(); if(isStringEmpty(toString)) { return "\"\""; } else if (String.class.equals(objectClass)) { return "\"" + toString + '\"'; } else { return toString; } } public static String detailedMethodName(ITestNGMethod method, boolean fqn) { String tempName = annotationFormFor(method); if (! tempName.isEmpty()) { tempName += " "; } return tempName + (fqn ? method.toString() : method.getMethodName()); } /** * Given a TestNG method, returns the corresponding annotation based on the method type * @param method - An {@link ITestNGMethod} object. * @return - A String representation of the corresponding annotation. */ public static String annotationFormFor(ITestNGMethod method) { if(method.isBeforeSuiteConfiguration()) { return "@BeforeSuite"; } if(method.isBeforeTestConfiguration()) { return "@BeforeTest"; } if(method.isBeforeClassConfiguration()) { return "@BeforeClass"; } if(method.isBeforeGroupsConfiguration()) { return "@BeforeGroups"; } if(method.isBeforeMethodConfiguration()) { return "@BeforeMethod"; } if(method.isAfterMethodConfiguration()) { return "@AfterMethod"; } if(method.isAfterGroupsConfiguration()) { return "@AfterGroups"; } if(method.isAfterClassConfiguration()) { return "@AfterClass"; } if (method.isAfterTestConfiguration()) { return "@AfterTest"; } if(method.isAfterSuiteConfiguration()) { return "@AfterSuite"; } return ""; } public static String arrayToString(String[] strings) { StringBuilder result = new StringBuilder(""); if ((strings != null) && (strings.length > 0)) { for (int i = 0; i < strings.length; i++) { result.append(strings[i]); if (i < strings.length - 1) { result.append(", "); } } } return result.toString(); } /** * If the file name contains special characters like *,/,\ and so on, * exception will be thrown and report file will not be created.<br> * Special characters are platform specific and they are not same for * example on Windows and Macintosh. * is not allowed on Windows, but it is on Macintosh.<br> * In order to have the same behavior of testng on the all platforms, characters like * will * be replaced on all platforms whether they are causing the problem or not. * * @param fileName file name that could contain special characters. * @return fileName with special characters replaced */ public static String replaceSpecialCharacters(String fileName) { if (fileName == null || fileName.length() == 0) { return fileName; } for (char element : SPECIAL_CHARACTERS) { fileName = fileName.replace(element, CHAR_REPLACEMENT); } return fileName; } public static <T> String join(List<T> objects, String separator) { StringBuilder result = new StringBuilder(); for (int i = 0; i < objects.size(); i++) { if (i > 0) { result.append(separator); } result.append(objects.get(i).toString()); } return result.toString(); } /** * @deprecated Unused */ @Deprecated public static void copyFile(File from, File to) { to.getParentFile().mkdirs(); try (InputStream in = new FileInputStream(from); OutputStream out = new FileOutputStream(to)) { byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } catch(IOException e){ LOG.error(e.getMessage(),e); } } /** * @return a temporary file with the given content. */ public static File createTempFile(String content) { try { // Create temp file. File result = File.createTempFile("testng-tmp", ""); // Delete temp file when program exits. result.deleteOnExit(); // Write to temp file try (BufferedWriter out = new BufferedWriter(new FileWriter(result))) { out.write(content); } return result; } catch (IOException e) { throw new TestNGException(e); } } /** * Make sure that either we have an instance or if not, that the method is static */ public static void checkInstanceOrStatic(Object instance, Method method) { if (instance == null && method != null && ! Modifier.isStatic(method.getModifiers())) { throw new TestNGException("Can't invoke " + method + ": either make it static or add " + "a no-args constructor to your class"); } } public static void checkReturnType(Method method, Class<?>... returnTypes) { if (method == null) { return; } for (Class<?> returnType : returnTypes) { if (method.getReturnType() == returnType) { return; } } throw new TestNGException(method.getDeclaringClass().getName() + "." + method.getName() + " MUST return " + toString(returnTypes) + " but returns " + method.getReturnType().getName()); } private static String toString(Class<?>[] classes) { StringBuilder sb = new StringBuilder("[ "); for (int i=0; i<classes.length;) { Class<?> clazz = classes[i]; if (clazz.isArray()) { sb.append(clazz.getComponentType().getName()).append("[]"); } else { sb.append(clazz.getName()); } if (++i < classes.length) { // increment and compare sb.append(" or "); } } sb.append(" ]"); return sb.toString(); } /** * Returns the string representation of the specified object, transparently * handling null references and arrays. * * @param obj * the object * @return the string representation */ public static String toString(Object obj) { String result; if (obj != null) { if (obj instanceof boolean[]) { result = Arrays.toString((boolean[]) obj); } else if (obj instanceof byte[]) { result = Arrays.toString((byte[]) obj); } else if (obj instanceof char[]) { result = Arrays.toString((char[]) obj); } else if (obj instanceof double[]) { result = Arrays.toString((double[]) obj); } else if (obj instanceof float[]) { result = Arrays.toString((float[]) obj); } else if (obj instanceof int[]) { result = Arrays.toString((int[]) obj); } else if (obj instanceof long[]) { result = Arrays.toString((long[]) obj); } else if (obj instanceof Object[]) { result = Arrays.deepToString((Object[]) obj); } else if (obj instanceof short[]) { result = Arrays.toString((short[]) obj); } else { result = obj.toString(); } } else { result = "null"; } return result; } }