1 package net.avcompris.guixer.core;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.base.Preconditions.checkState;
5 import static com.google.common.collect.Maps.newHashMap;
6 import static com.google.common.io.Resources.getResource;
7 import static com.google.common.io.Resources.toByteArray;
8 import static org.apache.commons.lang3.CharEncoding.UTF_8;
9 import static org.apache.commons.lang3.StringUtils.isBlank;
10 import static org.joda.time.DateTimeZone.UTC;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.File;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.lang.reflect.Method;
17 import java.util.Map;
18 import java.util.Properties;
19
20 import javax.annotation.Nullable;
21
22 import org.joda.time.DateTime;
23
24 import net.avcompris.domdumper.Dumper;
25 import net.avcompris.domdumper.XMLDumpers;
26
27 final class DumperLogger implements Logger {
28
29 private static String GUIXER_VERSION;
30
31 private static final Map<File, DumperLogger> LOGGERS = newHashMap();
32
33 private final Context context;
34 private final File logXmlFile;
35 private final Dumper dumper;
36 private Dumper commandDumper;
37 private Dumper stepDumper;
38 private long currentStartedAtMs;
39
40 private DumperLogger(final Context context, final File logXmlFile) throws IOException {
41
42 this.context = checkNotNull(context, "context");
43 this.logXmlFile = checkNotNull(logXmlFile, "logXmlFile");
44
45 final DateTime now = new DateTime().withZone(UTC);
46
47 dumper = XMLDumpers.newDumper("test", logXmlFile, UTF_8)
48 .addAttribute("guixerVersion", getGuixerVersion())
49 .addAttribute("startedAt", now.toString())
50 .addAttribute("startedAtMs", Long.toString(now.getMillis()))
51 .addAttribute("seleniumServerURL", context.getSeleniumServerURL())
52 .addAttribute("seleniumDesiredCapabilities", context.getSeleniumDesiredCapabilities())
53 .addAttribute("baseURL", context.getBaseURL());
54 }
55
56 @Override
57 public void setTestContext(final Class<?> testClass, final Method testMethod) throws IOException {
58
59 checkNotNull(testClass, "testClass");
60 checkNotNull(testMethod, "testMethod");
61
62 dumper
63 .addAttribute("testClassName", testClass.getName())
64 .addAttribute("testClassSimpleName", testClass.getSimpleName())
65 .addAttribute("testMethodName", testMethod.getName());
66 }
67
68 @Override
69 public int hashCode() {
70
71 return logXmlFile.hashCode();
72 }
73
74 @Override
75 public boolean equals(@Nullable final Object arg) {
76
77 if (arg == null || !(arg instanceof DumperLogger)) {
78
79 return false;
80 }
81
82 return logXmlFile.equals(((DumperLogger) arg).logXmlFile);
83 }
84
85 @Override
86 public String toString() {
87
88 return "[" + logXmlFile.getAbsolutePath() + "]";
89 }
90
91 public static DumperLogger getLogger(final Context context) throws IOException {
92
93 checkNotNull(context, "context");
94
95 final File dir = context.getSubDir();
96
97 if (!dir.isDirectory()) {
98
99 throw new FileNotFoundException("dir should be a directory: " + dir.getCanonicalPath());
100 }
101
102 final File logXmlFile = new File(dir, "log.xml");
103
104 if (logXmlFile.isFile() && LOGGERS.containsKey(logXmlFile)) {
105
106 return LOGGERS.get(logXmlFile);
107 }
108
109 final DumperLoggergger.html#DumperLogger">DumperLogger logger = new DumperLogger(context, logXmlFile);
110
111 LOGGERS.put(logXmlFile, logger);
112
113 return logger;
114 }
115
116 public void close() throws IOException {
117
118 commandDumper = null;
119
120 dumper.close();
121
122 LOGGERS.remove(logXmlFile);
123 }
124
125 public void startCommand(@Nullable final String actionShortDescription) throws IOException {
126
127 commandDumper = dumper.addElement("command");
128
129 if (actionShortDescription != null) {
130
131 commandDumper.addAttribute("actionShortDescription", actionShortDescription);
132 }
133 }
134
135 public void startStep(final String label) throws IOException {
136
137 checkNotNull(label, "label");
138
139 checkState(commandDumper != null,
140 "commandDumper should not be null; startCommand() should have been called prior to this call");
141
142 stepDumper = commandDumper.addElement("step");
143
144 currentStartedAtMs = System.currentTimeMillis();
145
146 stepDumper.addElement("startedAtMs")
147 .addCharacters(Long.toString(currentStartedAtMs));
148
149 stepDumper.addElement("literal").addCharacters(label);
150 }
151
152 public void endStep() throws IOException {
153
154 checkState(stepDumper != null,
155 "stepDumper should not be null; startStep() should have been called prior to this call");
156
157 stepDumper.addElement("index")
158 .addCharacters(Integer.toString(context.getStepCount()));
159
160 final long endedAtMs = System.currentTimeMillis();
161 final long elapsedMs = endedAtMs - currentStartedAtMs;
162
163 stepDumper.addElement("endedAtMs")
164 .addCharacters(Long.toString(endedAtMs));
165
166 stepDumper.addElement("elapsedMs")
167 .addCharacters(Long.toString(elapsedMs));
168
169 stepDumper = null;
170 }
171
172 public void error(final Throwable throwable) throws IOException {
173
174 checkNotNull(throwable, "throwable");
175
176 checkState(stepDumper != null,
177 "stepDumper should not be null; startStep() should have been called prior to this call");
178
179 stepDumper.addElement("error")
180 .addCharacters(throwable.toString());
181
182 final long endedAtMs = System.currentTimeMillis();
183 final long elapsedMs = endedAtMs - currentStartedAtMs;
184
185 stepDumper.addElement("endedAtMs")
186 .addCharacters(Long.toString(endedAtMs));
187
188 stepDumper.addElement("elapsedMs")
189 .addCharacters(Long.toString(elapsedMs));
190
191 stepDumper = null;
192 }
193
194 private static String getGuixerVersion() throws IOException {
195
196 if (GUIXER_VERSION != null) {
197
198 return GUIXER_VERSION;
199 }
200
201 final Properties properties = new Properties();
202
203 final String resourceName = "appinfo.properties";
204
205 properties.load(new ByteArrayInputStream(toByteArray(getResource(resourceName))));
206
207 GUIXER_VERSION = properties.getProperty("guixerVersion");
208
209 if (isBlank(GUIXER_VERSION)) {
210 throw new IOException("Cannot read GUIXER_VERSION from: " + resourceName);
211 }
212
213 return GUIXER_VERSION;
214 }
215 }