Lines 42-89
Link Here
|
42 |
package org.netbeans.modules.hudson.impl; |
42 |
package org.netbeans.modules.hudson.impl; |
43 |
|
43 |
|
44 |
import java.awt.Toolkit; |
44 |
import java.awt.Toolkit; |
45 |
import java.awt.event.ActionEvent; |
|
|
46 |
import java.io.FileNotFoundException; |
45 |
import java.io.FileNotFoundException; |
47 |
import java.io.IOException; |
|
|
48 |
import java.util.ArrayList; |
49 |
import java.util.List; |
50 |
import java.util.Stack; |
51 |
import java.util.logging.Level; |
46 |
import java.util.logging.Level; |
52 |
import java.util.logging.Logger; |
47 |
import java.util.logging.Logger; |
53 |
import java.util.regex.Matcher; |
|
|
54 |
import java.util.regex.Pattern; |
55 |
import javax.swing.AbstractAction; |
56 |
import javax.swing.Action; |
57 |
import org.netbeans.api.java.classpath.GlobalPathRegistry; |
58 |
import org.netbeans.api.project.Project; |
59 |
import org.netbeans.modules.gsf.testrunner.api.CallstackFrameNode; |
60 |
import org.netbeans.modules.gsf.testrunner.api.DiffViewAction; |
61 |
import org.netbeans.modules.gsf.testrunner.api.Manager; |
62 |
import org.netbeans.modules.gsf.testrunner.api.TestMethodNode; |
63 |
import org.netbeans.modules.gsf.testrunner.api.TestRunnerNodeFactory; |
64 |
import org.netbeans.modules.gsf.testrunner.api.TestSession; |
65 |
import org.netbeans.modules.gsf.testrunner.api.TestSuite; |
66 |
import org.netbeans.modules.gsf.testrunner.api.Testcase; |
67 |
import org.netbeans.modules.gsf.testrunner.api.TestsuiteNode; |
68 |
import org.netbeans.modules.gsf.testrunner.api.Trouble; |
69 |
import org.netbeans.modules.hudson.api.ConnectionBuilder; |
48 |
import org.netbeans.modules.hudson.api.ConnectionBuilder; |
70 |
import org.netbeans.modules.hudson.api.HudsonJob; |
49 |
import org.netbeans.modules.hudson.api.HudsonJob; |
71 |
import org.netbeans.modules.hudson.api.HudsonJobBuild; |
50 |
import org.netbeans.modules.hudson.api.HudsonJobBuild; |
72 |
import org.netbeans.modules.hudson.api.HudsonMavenModuleBuild; |
51 |
import org.netbeans.modules.hudson.api.HudsonMavenModuleBuild; |
|
|
52 |
import org.netbeans.modules.hudson.api.ui.FailureDataDisplayer; |
53 |
import org.netbeans.modules.hudson.api.ui.FailureDataDisplayer.Case; |
54 |
import org.netbeans.modules.hudson.api.ui.FailureDataDisplayer.Suite; |
73 |
import org.netbeans.modules.hudson.spi.BuilderConnector; |
55 |
import org.netbeans.modules.hudson.spi.BuilderConnector; |
74 |
import org.netbeans.modules.hudson.spi.HudsonLogger; |
|
|
75 |
import org.netbeans.modules.hudson.ui.actions.Hyperlinker; |
76 |
import org.netbeans.modules.hudson.ui.actions.OpenUrlAction; |
77 |
import org.netbeans.modules.hudson.ui.interfaces.OpenableInBrowser; |
78 |
import org.openide.awt.StatusDisplayer; |
56 |
import org.openide.awt.StatusDisplayer; |
79 |
import org.openide.filesystems.FileObject; |
|
|
80 |
import org.openide.filesystems.FileUtil; |
81 |
import org.openide.util.Lookup; |
82 |
import org.openide.util.NbBundle; |
57 |
import org.openide.util.NbBundle; |
83 |
import org.openide.util.RequestProcessor; |
58 |
import org.openide.util.RequestProcessor; |
84 |
import org.openide.windows.IOProvider; |
|
|
85 |
import org.openide.windows.InputOutput; |
86 |
import org.openide.windows.OutputWriter; |
87 |
import org.openide.xml.XMLUtil; |
59 |
import org.openide.xml.XMLUtil; |
88 |
import org.xml.sax.Attributes; |
60 |
import org.xml.sax.Attributes; |
89 |
import org.xml.sax.InputSource; |
61 |
import org.xml.sax.InputSource; |
Lines 95-149
Link Here
|
95 |
* |
67 |
* |
96 |
* @author jhavlin |
68 |
* @author jhavlin |
97 |
*/ |
69 |
*/ |
98 |
public class HudsonFailureDisplayer extends BuilderConnector.FailureDisplayer { |
70 |
@NbBundle.Messages({ |
|
|
71 |
"no_test_result=No test result found for this build."}) |
72 |
public class HudsonFailureDataProvider extends BuilderConnector.FailureDataProvider { |
99 |
|
73 |
|
100 |
private static final Logger LOG = Logger.getLogger( |
74 |
private static final Logger LOG = Logger.getLogger( |
101 |
HudsonFailureDisplayer.class.getName()); |
75 |
HudsonFailureDataProvider.class.getName()); |
102 |
private static final RequestProcessor RP = new RequestProcessor( |
|
|
103 |
HudsonFailureDisplayer.class); |
104 |
private static final Pattern ASSERTION_FAILURE = Pattern.compile( |
105 |
"(?m)junit[.]framework[.](AssertionFailedError|(Array)?ComparisonFailure)|java[.]lang[.]AssertionError($|: )"); //NOI18N |
106 |
|
76 |
|
107 |
@Override |
77 |
@Override |
108 |
public void showFailures(final HudsonJobBuild build) { |
78 |
public void showFailures(final HudsonJobBuild build, |
|
|
79 |
final FailureDataDisplayer displayer) { |
109 |
new RequestProcessor(build.getUrl() + "failures").post( // NOI18N |
80 |
new RequestProcessor(build.getUrl() + "failures").post( // NOI18N |
110 |
new Runnable() { |
81 |
new Runnable() { |
111 |
@Override |
82 |
@Override |
112 |
public void run() { |
83 |
public void run() { |
113 |
showBuildFailures(build.getJob(), build.getUrl(), |
84 |
showBuildFailures(build.getJob(), build.getUrl(), displayer); |
114 |
build.getDisplayName()); |
|
|
115 |
} |
85 |
} |
116 |
}); |
86 |
}); |
117 |
} |
87 |
} |
118 |
|
88 |
|
119 |
@Override |
89 |
@Override |
120 |
public void showFailures(final HudsonMavenModuleBuild moduleBuild) { |
90 |
public void showFailures(final HudsonMavenModuleBuild moduleBuild, |
|
|
91 |
final FailureDataDisplayer displayer) { |
121 |
new RequestProcessor(moduleBuild.getUrl() + "failures").post( // NOI18N |
92 |
new RequestProcessor(moduleBuild.getUrl() + "failures").post( // NOI18N |
122 |
new Runnable() { |
93 |
new Runnable() { |
123 |
@Override |
94 |
@Override |
124 |
public void run() { |
95 |
public void run() { |
125 |
showBuildFailures( |
96 |
showBuildFailures(moduleBuild.getBuild().getJob(), |
126 |
moduleBuild.getBuild().getJob(), moduleBuild.getUrl(), |
97 |
moduleBuild.getUrl(), displayer); |
127 |
moduleBuild.getBuildDisplayName()); |
|
|
128 |
} |
98 |
} |
129 |
}); |
99 |
}); |
130 |
} |
100 |
} |
131 |
|
101 |
|
132 |
@NbBundle.Messages({ |
102 |
private void showBuildFailures(HudsonJob job, String url, |
133 |
"# {0} - job #build", "ShowFailures.title={0} Test Failures", |
103 |
FailureDataDisplayer displayer) { |
134 |
"# {0} - class & method name of failed test", "# {1} - suite name of failed test", "ShowFailures.from_suite={0} (from {1})", |
104 |
|
135 |
"LBL_GotoSource=Go to Source", |
|
|
136 |
"no_test_result=No test result found for this build.", |
137 |
"# {0} - Java source file resource path", "no_source_to_hyperlink=Could not find {0} among open projects." |
138 |
}) |
139 |
public void showBuildFailures(HudsonJob job, String url, String displayName) { |
140 |
try { |
105 |
try { |
141 |
XMLReader parser = XMLUtil.createXMLReader(); |
106 |
XMLReader parser = XMLUtil.createXMLReader(); |
142 |
parser.setContentHandler(new ContentHandler(job, url, displayName)); |
107 |
parser.setContentHandler(new ContentHandler(displayer)); |
143 |
// XXX could use ?tree (would be faster) if there were an alternate object for failed tests only |
108 |
// XXX could use ?tree (would be faster) if there were an alternate object for failed tests only |
144 |
String u = url + "testReport/api/xml?xpath=//suite[case/errorStackTrace]&wrapper=failures"; //NOI18N |
109 |
String u = url + "testReport/api/xml?xpath=//suite[case/errorStackTrace]&wrapper=failures"; //NOI18N |
145 |
InputSource source = new InputSource(new ConnectionBuilder().job(job).url(u).connection().getInputStream()); |
110 |
InputSource source = new InputSource(new ConnectionBuilder().job(job).url(u).connection().getInputStream()); |
146 |
source.setSystemId(u); |
111 |
source.setSystemId(u); |
|
|
112 |
displayer.open(); |
147 |
parser.parse(source); |
113 |
parser.parse(source); |
148 |
} catch (FileNotFoundException x) { |
114 |
} catch (FileNotFoundException x) { |
149 |
Toolkit.getDefaultToolkit().beep(); |
115 |
Toolkit.getDefaultToolkit().beep(); |
Lines 156-221
Link Here
|
156 |
|
122 |
|
157 |
private class ContentHandler extends DefaultHandler { |
123 |
private class ContentHandler extends DefaultHandler { |
158 |
|
124 |
|
159 |
public ContentHandler(HudsonJob job, String url, String displayName) { |
125 |
private StringBuilder buf; |
160 |
this.url = url; |
126 |
private final FailureDataDisplayer displayer; |
161 |
this.displayName = displayName; |
|
|
162 |
this.hyperlinker = new Hyperlinker(job); |
163 |
this.session = createTestSession(displayName); |
164 |
} |
165 |
private String url; |
166 |
private String displayName; |
167 |
InputOutput io; |
168 |
StringBuilder buf; |
169 |
Hyperlinker hyperlinker; |
170 |
TestSession session; |
171 |
Project project; |
172 |
|
127 |
|
173 |
private TestSession createTestSession(String displayName) { |
128 |
public ContentHandler(FailureDataDisplayer displayer) { |
174 |
|
129 |
this.displayer = displayer; |
175 |
// Store reference to project here, as reference in test session |
|
|
176 |
// is weak. |
177 |
this.project = new Project() { |
178 |
public @Override |
179 |
FileObject getProjectDirectory() { |
180 |
return FileUtil.createMemoryFileSystem().getRoot(); |
181 |
} |
182 |
|
183 |
public @Override |
184 |
Lookup getLookup() { |
185 |
return Lookup.EMPTY; |
186 |
} |
187 |
}; |
188 |
TestRunnerNodeFactory testRunnerNodeFactory = |
189 |
new HudsonTestRunnerNodeFactory(); |
190 |
return new TestSession(displayName, project, |
191 |
TestSession.SessionType.TEST, testRunnerNodeFactory); |
192 |
} |
193 |
|
194 |
private void prepareOutput() { |
195 |
if (io == null) { |
196 |
String title = Bundle.ShowFailures_title(displayName); |
197 |
io = IOProvider.getDefault().getIO(title, new Action[0]); |
198 |
io.select(); |
199 |
Manager.getInstance().testStarted(session); |
200 |
} |
201 |
} |
202 |
|
203 |
class Suite { |
204 |
|
205 |
String name; |
206 |
String stdout; |
207 |
String stderr; |
208 |
Stack<Case> cases = new Stack<Case>(); |
209 |
List<Case> casesDone = new ArrayList<Case>(); |
210 |
long duration; |
211 |
} |
212 |
|
213 |
class Case { |
214 |
|
215 |
String className; |
216 |
String name; |
217 |
String errorStackTrace; |
218 |
long duration; |
219 |
} |
130 |
} |
220 |
|
131 |
|
221 |
long parseDuration(String d) { |
132 |
long parseDuration(String d) { |
Lines 228-243
Link Here
|
228 |
return 0; |
139 |
return 0; |
229 |
} |
140 |
} |
230 |
} |
141 |
} |
231 |
Stack<Suite> suites = new Stack<Suite>(); |
142 |
|
|
|
143 |
Suite suite = null; |
144 |
Case caze = null; |
232 |
|
145 |
|
233 |
public @Override |
146 |
public @Override |
234 |
void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
147 |
void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { |
235 |
if (qName.matches("errorStackTrace|stdout|stderr|name|className")) { //NOI18N |
148 |
if (qName.matches("errorStackTrace|stdout|stderr|name|className")) { //NOI18N |
236 |
buf = new StringBuilder(); |
149 |
buf = new StringBuilder(); |
237 |
} else if (qName.equals("suite")) { //NOI18N |
150 |
} else if (qName.equals("suite")) { //NOI18N |
238 |
suites.push(new Suite()); |
151 |
suite = new Suite(); |
239 |
} else if (qName.equals("case") && !suites.empty()) { //NOI18N |
152 |
caze = null; |
240 |
suites.peek().cases.push(new Case()); |
153 |
} else if (qName.equals("case") && suite != null) { //NOI18N |
|
|
154 |
caze = new Case(); |
241 |
} |
155 |
} |
242 |
} |
156 |
} |
243 |
|
157 |
|
Lines 250-434
Link Here
|
250 |
|
164 |
|
251 |
public @Override |
165 |
public @Override |
252 |
void endElement(String uri, String localName, String qName) throws SAXException { |
166 |
void endElement(String uri, String localName, String qName) throws SAXException { |
253 |
if (suites.empty()) { |
167 |
if (suite == null) { |
254 |
return; |
168 |
return; |
255 |
} |
169 |
} |
256 |
Suite s = suites.peek(); |
|
|
257 |
String text = buf != null && buf.length() > 0 ? buf.toString() : null; |
170 |
String text = buf != null && buf.length() > 0 ? buf.toString() : null; |
258 |
buf = null; |
171 |
buf = null; |
259 |
if (s.cases.empty()) { // suite level |
172 |
if (caze == null) { // suite level |
260 |
if (qName.equals("stdout")) { // NOI18N |
173 |
if (qName.equals("stdout")) { // NOI18N |
261 |
s.stdout = text; |
174 |
suite.setStdout(text); |
262 |
} else if (qName.equals("stderr")) { // NOI18N |
175 |
} else if (qName.equals("stderr")) { // NOI18N |
263 |
s.stderr = text; |
176 |
suite.setStderr(text); |
264 |
} else if (qName.equals("name")) { // NOI18N |
177 |
} else if (qName.equals("name")) { // NOI18N |
265 |
s.name = text; |
178 |
suite.setName(text); |
266 |
} else if (qName.equals("duration")) { // NOI18N |
179 |
} else if (qName.equals("duration")) { // NOI18N |
267 |
s.duration = parseDuration(text); |
180 |
suite.setDuration(parseDuration(text)); |
268 |
} |
181 |
} |
269 |
} else { // case level |
182 |
} else { // case level |
270 |
Case c = s.cases.peek(); |
|
|
271 |
if (qName.equals("errorStackTrace")) { // NOI18N |
183 |
if (qName.equals("errorStackTrace")) { // NOI18N |
272 |
c.errorStackTrace = text; |
184 |
caze.setErrorStackTrace(text); |
273 |
} else if (qName.equals("name")) { // NOI18N |
185 |
} else if (qName.equals("name")) { // NOI18N |
274 |
c.name = text; |
186 |
caze.setName(text); |
275 |
} else if (qName.equals("className")) { // NOI18N |
187 |
} else if (qName.equals("className")) { // NOI18N |
276 |
c.className = text; |
188 |
caze.setClassName(text); |
277 |
} else if (qName.equals("duration")) { // NOI18N |
189 |
} else if (qName.equals("duration")) { // NOI18N |
278 |
c.duration = parseDuration(text); |
190 |
caze.setDuration(parseDuration(text)); |
279 |
} |
191 |
} |
280 |
} |
192 |
} |
281 |
if (qName.equals("suite")) { // NOI18N |
193 |
if (qName.equals("case")) { //NOI18N |
|
|
194 |
suite.addCase(caze); |
195 |
caze = null; |
196 |
} else if (qName.equals("suite")) { // NOI18N |
282 |
try { |
197 |
try { |
283 |
show(s); |
198 |
displayer.showSuite(suite); |
284 |
} catch (IOException x) { |
199 |
} catch (Exception x) { |
285 |
LOG.log(Level.FINE, null, x); |
200 |
LOG.log(Level.FINE, null, x); |
286 |
} |
201 |
} |
287 |
suites.pop(); |
202 |
suite = null; |
288 |
} else if (qName.equals("case")) { // NOI18N |
|
|
289 |
s.casesDone.add(s.cases.pop()); |
290 |
} |
291 |
} |
292 |
|
293 |
void show(Suite s) throws IOException { |
294 |
prepareOutput(); |
295 |
OutputWriter out = io.getOut(); |
296 |
OutputWriter err = io.getErr(); |
297 |
TestSuite suite = new TestSuite(s.name); |
298 |
session.addSuite(suite); |
299 |
Manager.getInstance().displaySuiteRunning(session, suite.getName()); |
300 |
if (s.stderr != null) { |
301 |
// XXX TR window does not seem to show only stdio from selected suite |
302 |
Manager.getInstance().displayOutput(session, s.stderr, true); |
303 |
} |
304 |
if (s.stdout != null) { |
305 |
Manager.getInstance().displayOutput(session, s.stdout, false); |
306 |
} |
307 |
for (final Case c : s.casesDone) { |
308 |
if (c.errorStackTrace == null) { |
309 |
continue; |
310 |
} |
311 |
String name = c.className + "." + c.name; //NOI18N |
312 |
String shortName = c.name; |
313 |
if (s.name != null && !s.name.equals(c.className)) { |
314 |
shortName = name; |
315 |
name = Bundle.ShowFailures_from_suite(name, s.name); |
316 |
} |
317 |
println(); |
318 |
out.println("[" + name + "]"); // XXX use color printing to make it stand out? //NOI18N |
319 |
show(c.errorStackTrace, /* err is too hard to read */ out); |
320 |
Testcase test = new Testcase(shortName, null, session); |
321 |
test.setClassName(c.className); |
322 |
Trouble trouble = new Trouble(!ASSERTION_FAILURE.matcher(c.errorStackTrace).lookingAt()); |
323 |
trouble.setStackTrace(c.errorStackTrace.split("\r?\n")); //NOI18N |
324 |
// XXX call setComparisonFailure if matches "expected:<...> but was:<...>" |
325 |
test.setTrouble(trouble); |
326 |
LOG.log(Level.FINE, "got {0} as {1}", new Object[]{name, test.getStatus()}); //NOI18N |
327 |
test.setTimeMillis(c.duration); |
328 |
session.addTestCase(test); |
329 |
} |
330 |
if (s.stderr != null || s.stdout != null) { |
331 |
println(); |
332 |
show(s.stderr, err); |
333 |
show(s.stdout, out); |
334 |
} |
335 |
Manager.getInstance().displayReport(session, session.getReport(s.duration)); |
336 |
} |
337 |
boolean firstLine = true; |
338 |
|
339 |
void println() { |
340 |
if (firstLine) { |
341 |
firstLine = false; |
342 |
} else { |
343 |
io.getOut().println(); |
344 |
} |
345 |
} |
346 |
|
347 |
void show(String lines, OutputWriter w) { |
348 |
if (lines == null) { |
349 |
return; |
350 |
} |
351 |
for (String line : lines.split("\r\n?|\n")) { //NOI18N |
352 |
hyperlinker.handleLine(line, w); |
353 |
} |
203 |
} |
354 |
} |
204 |
} |
355 |
|
205 |
|
356 |
@Override |
206 |
@Override |
357 |
public void endDocument() throws SAXException { |
207 |
public void endDocument() throws SAXException { |
358 |
if (io != null) { |
208 |
displayer.close(); |
359 |
io.getOut().close(); |
|
|
360 |
io.getErr().close(); |
361 |
Manager.getInstance().sessionFinished(session); |
362 |
} |
363 |
} |
364 |
|
365 |
private class HudsonTestRunnerNodeFactory extends TestRunnerNodeFactory { |
366 |
|
367 |
public HudsonTestRunnerNodeFactory() { |
368 |
} |
369 |
|
370 |
public @Override |
371 |
TestsuiteNode createTestSuiteNode(String suiteName, boolean filtered) { |
372 |
// XXX could add OpenableInBrowser |
373 |
return new TestsuiteNode(suiteName, filtered); |
374 |
} |
375 |
|
376 |
public @Override |
377 |
org.openide.nodes.Node createTestMethodNode(final Testcase testcase, |
378 |
Project project) { |
379 |
return new TestMethodNode(testcase, project) { |
380 |
public @Override |
381 |
Action[] getActions(boolean context) { |
382 |
return new Action[]{ |
383 |
OpenUrlAction.forOpenable(new OpenableInBrowser() { |
384 |
public @Override |
385 |
String getUrl() { |
386 |
return url + "testReport/" |
387 |
+ testcase.getClassName().replaceFirst("[.][^.]+$", "") + "/" + testcase.getClassName().replaceFirst(".+[.]", "") + "/" + testcase.getName() + "/"; //NOI18N |
388 |
} |
389 |
}), |
390 |
new DiffViewAction(testcase),}; |
391 |
} |
392 |
}; |
393 |
} |
394 |
|
395 |
public @Override |
396 |
org.openide.nodes.Node createCallstackFrameNode(String frameInfo, String displayName) { |
397 |
return new CallstackFrameNode(frameInfo, displayName) { |
398 |
public @Override |
399 |
Action getPreferredAction() { |
400 |
return new AbstractAction(Bundle.LBL_GotoSource()) { |
401 |
public @Override |
402 |
void actionPerformed(ActionEvent e) { |
403 |
// XXX should have utility API to parse stack traces |
404 |
final Matcher m = Pattern.compile("\tat (.+[.])[^.]+[.][^.]+[(]([^.]+[.]java):([0-9]+)[)]").matcher(frameInfo); //NOI18N |
405 |
if (m.matches()) { |
406 |
final String resource = m.group(1).replace('.', '/') + m.group(2); |
407 |
RP.post(new Runnable() { |
408 |
@Override |
409 |
public void run() { |
410 |
FileObject f = GlobalPathRegistry.getDefault().findResource(resource); |
411 |
LOG.log(Level.FINER, "matched {0} -> {1}", new Object[]{resource, f}); //NOI18N |
412 |
if (f != null) { |
413 |
HudsonLogger.Helper.openAt(f, Integer.parseInt(m.group(3)) - 1, -1, true); |
414 |
} else { |
415 |
StatusDisplayer.getDefault().setStatusText(Bundle.no_source_to_hyperlink(resource)); |
416 |
} |
417 |
} |
418 |
}); |
419 |
} else { |
420 |
LOG.log(Level.FINER, "no match for {0}", frameInfo); //NOI18N |
421 |
} |
422 |
} |
423 |
}; |
424 |
} |
425 |
|
426 |
public @Override |
427 |
Action[] getActions(boolean context) { |
428 |
return new Action[]{getPreferredAction()}; |
429 |
} |
430 |
}; |
431 |
} |
432 |
} |
209 |
} |
433 |
} |
210 |
} |
434 |
} |
211 |
} |