1 package org.testng; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Map; 6 import java.util.Vector; 7 8 import org.testng.collections.Lists; 9 import org.testng.collections.Maps; 10 import org.testng.util.Strings; 11 12 /** 13 * This class is used for test methods to log messages that will be 14 * included in the HTML reports generated by TestNG. 15 * <br> 16 * <br> 17 * <b>Implementation details.</b> 18 * <br> 19 * <br> 20 * The reporter keeps a combined output of strings (in m_output) and also 21 * a record of which method output which line. In order to do this, callers 22 * specify what the current method is with setCurrentTestResult() and the 23 * Reporter maintains a mapping of each test result with a list of integers. 24 * These integers are indices in the combined output (avoids duplicating 25 * the output). 26 * 27 * Created on Nov 2, 2005 28 * @author cbeust 29 */ 30 public class Reporter { 31 // when tests are run in parallel, each thread may be working with different 32 // 'current test result'. Also, this value should be inherited if the test code 33 // spawns its own thread. 34 private static ThreadLocal<ITestResult> m_currentTestResult = new InheritableThreadLocal<>(); 35 36 /** 37 * All output logged in a sequential order. 38 */ 39 private static List<String> m_output = new Vector<>(); 40 41 /** The key is the hashCode of the ITestResult */ 42 private static Map<Integer, List<Integer>> m_methodOutputMap = Maps.newHashMap(); 43 44 private static boolean m_escapeHtml = false; 45 //This variable is responsible for persisting all output that is yet to be associated with any 46 //valid TestResult objects. 47 private static ThreadLocal<List<String>> m_orphanedOutput = new InheritableThreadLocal<>(); 48 setCurrentTestResult(ITestResult m)49 public static void setCurrentTestResult(ITestResult m) { 50 m_currentTestResult.set(m); 51 } 52 getOutput()53 public static List<String> getOutput() { 54 return m_output; 55 } 56 57 /** 58 * Erase the content of all the output generated so far. 59 */ clear()60 public static void clear() { 61 m_methodOutputMap.clear(); 62 m_output.clear(); 63 } 64 65 /** 66 * @param escapeHtml If true, use HTML entities for special HTML characters (<, >, &, ...). 67 */ setEscapeHtml(boolean escapeHtml)68 public static void setEscapeHtml(boolean escapeHtml) { 69 m_escapeHtml = escapeHtml; 70 } 71 log(String s, ITestResult m)72 private static synchronized void log(String s, ITestResult m) { 73 // Escape for the HTML reports 74 if (m_escapeHtml) { 75 s = Strings.escapeHtml(s); 76 } 77 78 if (m == null) { 79 //Persist the output temporarily into a Threadlocal String list. 80 if (m_orphanedOutput.get() == null) { 81 m_orphanedOutput.set(new ArrayList<String>()); 82 } 83 m_orphanedOutput.get().add(s); 84 return; 85 } 86 87 // synchronization needed to ensure the line number and m_output are updated atomically 88 int n = getOutput().size(); 89 90 List<Integer> lines = m_methodOutputMap.get(m.hashCode()); 91 if (lines == null) { 92 lines = Lists.newArrayList(); 93 m_methodOutputMap.put(m.hashCode(), lines); 94 } 95 96 // Check if there was already some orphaned output for the current thread. 97 if (m_orphanedOutput.get() != null) { 98 n = n + m_orphanedOutput.get().size(); 99 getOutput().addAll(m_orphanedOutput.get()); 100 // Since we have already added all of the orphaned output to the current 101 // TestResult, lets clear it off 102 m_orphanedOutput.remove(); 103 } 104 lines.add(n); 105 getOutput().add(s); 106 } 107 108 /** 109 * Log the passed string to the HTML reports 110 * @param s The message to log 111 */ log(String s)112 public static void log(String s) { 113 log(s, getCurrentTestResult()); 114 } 115 116 /** 117 * Log the passed string to the HTML reports if the current verbosity 118 * is equal or greater than the one passed in parameter. If logToStandardOut 119 * is true, the string will also be printed on standard out. 120 * 121 * @param s The message to log 122 * @param level The verbosity of this message 123 * @param logToStandardOut Whether to print this string on standard 124 * out too 125 */ log(String s, int level, boolean logToStandardOut)126 public static void log(String s, int level, boolean logToStandardOut) { 127 if (TestRunner.getVerbose() >= level) { 128 log(s, getCurrentTestResult()); 129 if (logToStandardOut) { 130 System.out.println(s); 131 } 132 } 133 } 134 135 /** 136 * Log the passed string to the HTML reports. If logToStandardOut 137 * is true, the string will also be printed on standard out. 138 * 139 * @param s The message to log 140 * @param logToStandardOut Whether to print this string on standard 141 * out too 142 */ log(String s, boolean logToStandardOut)143 public static void log(String s, boolean logToStandardOut) { 144 log(s, getCurrentTestResult()); 145 if (logToStandardOut) { 146 System.out.println(s); 147 } 148 } 149 /** 150 * Log the passed string to the HTML reports if the current verbosity 151 * is equal or greater than the one passed in parameter 152 * 153 * @param s The message to log 154 * @param level The verbosity of this message 155 */ log(String s, int level)156 public static void log(String s, int level) { 157 if (TestRunner.getVerbose() >= level) { 158 log(s, getCurrentTestResult()); 159 } 160 } 161 162 /** 163 * @return the current test result. 164 */ getCurrentTestResult()165 public static ITestResult getCurrentTestResult() { 166 return m_currentTestResult.get(); 167 } 168 getOutput(ITestResult tr)169 public static synchronized List<String> getOutput(ITestResult tr) { 170 List<String> result = Lists.newArrayList(); 171 if (tr == null) { 172 //guard against a possible NPE in scenarios wherein the test result object itself could be a null value. 173 return result; 174 } 175 List<Integer> lines = m_methodOutputMap.get(tr.hashCode()); 176 if (lines != null) { 177 for (Integer n : lines) { 178 result.add(getOutput().get(n)); 179 } 180 } 181 182 return result; 183 } 184 } 185