I would suggest that our artifacts directory look something like this :<br><br><span style="font-family: courier new,monospace;">&lt;project&gt;/build-&lt;label&gt;/build.log</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /build_status.&lt;status&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /sections/change_set.html<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /.change_set.html<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /test_results.html
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /something.html<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /something else.txt<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /unit test coverage/...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /other stuff.html<br><br></span>so, the only &quot;special&quot; files are 
build.log, build_status.*, and the sections directory<br><br>anything else in the artifacts directory will be displayed as a link from the build page (maybe these should all live in a folder /links/?) - this is implemented now
<br><br>anything in /sections will be displayed as a collapsible section on the build page, if it&#39;s an html file, we include it outright, otherwise we either put it in a pre section or format it so that it looks as if we did.
<br><br>how&#39;s that sound?<br><br>Jeremy<br><br>PS. Thanks for the patch, Randall!<br><br style="font-family: courier new,monospace;"><div><span class="gmail_quote">On 2/10/07, <b class="gmail_sendername">Alexey Verkhovsky
</b> &lt;<a href="mailto:averkhov@thoughtworks.com">averkhov@thoughtworks.com</a>&gt; wrote:</span><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">

<br><font face="sans-serif" size="2">Thanks a lot!</font>
<span class="q"><br>
<br><tt><font size="2">&gt; Is this the appropriate way to submit patches?</font></tt>
<br></span><font face="sans-serif" size="2">Yes, until we have a more appropriate
way (aka a bug tracker).</font>
<br>
<br><font face="sans-serif" size="2">This submission raises a question that
was at the back of my mind ever since we had Test::Unit results formatter
in this codebase.</font>
<br><font face="sans-serif" size="2">Custom formatting of build log is going
to be in demand. How do we pluginize it? </font>
<br>
<br><font face="sans-serif" size="2">How about this solution:</font>
<br><font face="sans-serif" size="2">* Let&#39;s make formatting of build log
a builder&#39;s responsibility.</font>
<br><font face="sans-serif" size="2">* This can be done by a builder plugin
in response to :build_finished event</font>
<br>
<br><font face="sans-serif" size="2">Another question then arises: what do
we want to do with intermediate build products? We don&#39;t really want to
delete them (troubleshooting), but we also don&#39;t want to display links
to them on the dashboard. I think, use hidden files for that. </font>
<br>
<br><font face="sans-serif" size="2">I.e., you start with build.log file.
Format it and write the output into build.log.html (without html and body
tags though, because we are displaying it on the dashboard page). It&#39;s
also a good time to merge it with the changeset etc. Finally, hide the
originals from the dashboard by renaming build.log to .build.log and changeset.log
to .changeset.log.</font>
<br>
<br><font face="sans-serif" size="2">Thoughts?</font>
<br>
<br><font face="sans-serif" size="2">Best regards,</font>
<br><font face="sans-serif" size="2">Alex</font>
<br>
<br>
<br>
<br>
<br>
<br>
<table width="100%">
<tbody><tr valign="top">
<td width="40%"><font face="sans-serif" size="1"><b>Randall Potter &lt;<a href="mailto:rpotter@anl.gov" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">rpotter@anl.gov</a>&gt;</b>
</font>
<br><font face="sans-serif" size="1">Sent by: <a href="mailto:cruisecontrolrb-developers-bounces@rubyforge.org" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">cruisecontrolrb-developers-bounces@rubyforge.org
</a></font>
<p><font face="sans-serif" size="1">02/10/2007 12:23 AM</font>
</p></td><td width="59%">
<table width="100%">
<tbody><tr valign="top">
<td>
<div align="right"><font face="sans-serif" size="1">To</font></div>
</td><td><font face="sans-serif" size="1"><a href="mailto:cruisecontrolrb-developers@rubyforge.org" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">cruisecontrolrb-developers@rubyforge.org</a></font>

</td></tr><tr valign="top">
<td>
<div align="right"><font face="sans-serif" size="1">cc</font></div>
</td><td>
<br></td></tr><tr valign="top">
<td>
<div align="right"><font face="sans-serif" size="1">Subject</font></div>
</td><td><font face="sans-serif" size="1">[Cruisecontrolrb-developers] [patch]
rspec support (partial)</font></td></tr></tbody></table>
<br>
<table>
<tbody><tr valign="top">
<td>
<br></td><td><br></td></tr></tbody></table>
<br></td></tr></tbody></table><div><span class="e" id="q_110aca6f1796f164_3">
<br>
<br>
<br><tt><font size="2">story:<br>
 &nbsp; &nbsp;As a Dev, I would like CC.rb to display rspec failures in
&#39;Test <br>
Failures and Errors&#39;, so that I can use rspec.<br>
<br>
Is this the appropriate way to submit patches?<br>
<br>
Thank you in advance for your time,<br>
<br>
rpotter<br>
<br>
--- app/models/test_failure_parser.rb &nbsp; &nbsp;(revision 181)<br>
+++ app/models/test_failure_parser.rb &nbsp; &nbsp;(working copy)<br>
@@ -1,25 +1,51 @@<br>
 class TestFailureParser &nbsp; <br>
- &nbsp;TEST_NAME_REGEX = /\S+/<br>
- &nbsp;MESSAGE_REGEX = /\]\:\n([\s\S]+)/<br>
- &nbsp;STACK_TRACE_REGEX = /\[([\s\S]*?)\]\:/<br>
- &nbsp;TEST_FAILURE_BLOCK_REGEX = /^\s+\d+\) Failure:\n([\S\s]*?)\n\n/<br>
+<br>
+ &nbsp;TEST_FAILURE_BLOCK_REGEX = /^\s+?\d+\)(( Failure:)?\n[\S\s]*?)\n\n/<br>
+ <br>
+ &nbsp;SPEC_FAILURE_FORMAT = /^&#39;[\s\S]+&#39;\sFAILED[\s\S]*?/<br>
+ &nbsp;TEST_UNIT_FAILURE_FORMAT = / <br>
Failure:\n(\S+)\n?\s+?\[([\s\S]*?)\]:\n([\s\S]+)/<br>
+ <br>
 &nbsp; def get_test_failures(log)<br>
 &nbsp; &nbsp; testFailures = Array.new<br>
 &nbsp; &nbsp;<br>
 &nbsp; &nbsp; log.gsub(TEST_FAILURE_BLOCK_REGEX) do |text|<br>
 &nbsp; &nbsp; &nbsp; content = $1<br>
- &nbsp; &nbsp;<br>
- &nbsp; &nbsp; &nbsp;begin<br>
- &nbsp; &nbsp; &nbsp; &nbsp;test_name = content.match(TEST_NAME_REGEX).to_s
&nbsp; &nbsp; <br>
- &nbsp; &nbsp; &nbsp; &nbsp;message = content.match(MESSAGE_REGEX)[1]<br>
- &nbsp; &nbsp; &nbsp; &nbsp;stack_trace = content.match(STACK_TRACE_REGEX)[1]<br>
- &nbsp; &nbsp; &nbsp; <br>
- &nbsp; &nbsp; &nbsp; &nbsp;testFailures &lt;&lt; TestErrorEntry.create_failure(test_name,
<br>
message, stack_trace)<br>
- &nbsp; &nbsp; &nbsp;rescue<br>
- &nbsp; &nbsp; &nbsp; &nbsp;# Do Nothing, Pattern does not match<br>
- &nbsp; &nbsp; &nbsp;end &nbsp; &nbsp; <br>
+ &nbsp; &nbsp; <br>
+ &nbsp; &nbsp; &nbsp;if content.match(TEST_UNIT_FAILURE_FORMAT)<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;testFailures &lt;&lt; parse_as_test_unit(content)<br>
+ &nbsp; &nbsp; &nbsp;elsif content.gsub(&quot;\n&quot;,&#39;&#39;).match(SPEC_FAILURE_FORMAT)<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;testFailures &lt;&lt; parse_as_rspec(content)<br>
+ &nbsp; &nbsp; &nbsp;else<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;# &nbsp;no failures or we don&#39;t recognize
it<br>
+ &nbsp; &nbsp; &nbsp;end<br>
+ &nbsp; &nbsp; <br>
 &nbsp; &nbsp; end<br>
 &nbsp; &nbsp;<br>
 &nbsp; &nbsp; testFailures<br>
 &nbsp; end<br>
-end<br>
\ No newline at end of file<br>
+ <br>
+ &nbsp;private<br>
+ <br>
+ &nbsp;# &nbsp;parse Test::Unit log message and return TestErrorEntry<br>
+ &nbsp;def parse_as_test_unit(content)<br>
+ &nbsp; <br>
+ &nbsp; &nbsp;test_name = content.match(TEST_UNIT_FAILURE_FORMAT)[1].to_s
&nbsp; &nbsp; <br>
+ &nbsp; &nbsp;message = content.match(TEST_UNIT_FAILURE_FORMAT)[3]<br>
+ &nbsp; &nbsp;stack_trace = content.match(TEST_UNIT_FAILURE_FORMAT)[2]<br>
+ &nbsp; <br>
+ &nbsp; &nbsp;TestErrorEntry.create_failure(test_name, message, stack_trace)<br>
+ &nbsp;end<br>
+ <br>
+ &nbsp;# &nbsp;parse rspec log message and return TestErrorEntry<br>
+ &nbsp;def parse_as_rspec(content)<br>
+ &nbsp; <br>
+ &nbsp; &nbsp;spec_arr = content.split(&quot;\n&quot;)<br>
+ &nbsp; <br>
+ &nbsp; &nbsp;test_name = spec_arr[1].match(/^&#39;([\s\S]+)&#39; FAILED$/)[1].to_s
&nbsp; &nbsp; <br>
+ &nbsp; &nbsp;message = spec_arr[2].to_s<br>
+ &nbsp; &nbsp;stack_trace = spec_arr.slice(3..spec_arr.length).join(&quot;\n&quot;)<br>
+ &nbsp; <br>
+ &nbsp; &nbsp;TestErrorEntry.create_failure(test_name, message, stack_trace)<br>
+ &nbsp;end<br>
+ <br>
+end<br>
<br>
--- test/unit/test_failure_parser_test.rb &nbsp; &nbsp;(revision 181)<br>
+++ working_copy/test/unit/test_failure_parser_test.rb &nbsp; &nbsp;(working
copy)<br>
@@ -77,6 +77,34 @@<br>
 83 tests, 185 assertions, 2 failures, 0 errors<br>
 EOF<br>
 <br>
+LOG_OUTPUT_WITH_NO_SPEC_FAILURE = &lt;&lt;EOF<br>
+<br>
+.............................................................<br>
+<br>
+Finished in 1.199703 seconds<br>
+<br>
+61 specifications, 0 failures<br>
+EOF<br>
+<br>
+LOG_OUTPUT_WITH_SPEC_FAILURE = &lt;&lt;EOF<br>
+/usr/local/bin/spec:17:Warning: require_gem is obsolete. Use gem instead.<br>
+<br>
+....F....<br>
+<br>
+1)<br>
+&#39;Given a request to edit with a week and year the controller should <br>
return a hash of one blank row if data ! found&#39; FAILED<br>
+nil should be a kind of Array<br>
+./spec/controllers/consultant_controller_spec.rb:47:<br>
+/cruisecontrol/builds/ccs/work/config/../lib/tasks/cruise.rake:36:in `run&#39;<br>
+/cruisecontrol/builds/ccs/work/config/../lib/tasks/cruise.rake:47:<br>
+/cruisecontrol/tasks/cc_build.rake:32:<br>
+-e:1:<br>
+<br>
+Finished in 0.484439 seconds<br>
+<br>
+9 specifications, 1 failure<br>
+EOF<br>
+<br>
 &nbsp; def test_should_not_find_test_failures_with_a_build_with_test_errors<br>
 &nbsp; &nbsp; testFailures = <br>
TestFailureParser.new.get_test_failures(LOG_OUTPUT_WITH_TEST_ERRORS)<br>
 &nbsp; &nbsp; assert_equal 0, testFailures.length<br>
@@ -87,19 +115,30 @@<br>
 &nbsp; &nbsp; assert_equal 0, testFailures.length &nbsp; &nbsp; &nbsp;
<br>
 &nbsp; end<br>
 &nbsp;<br>
+ &nbsp;def test_should_find_no_spec_failures_with_successful_build<br>
+ &nbsp; &nbsp;testFailures = <br>
TestFailureParser.new.get_test_failures(LOG_OUTPUT_WITH_NO_SPEC_FAILURE)<br>
+ &nbsp; &nbsp;assert_equal 0, testFailures.length &nbsp; &nbsp; &nbsp;
<br>
+ &nbsp;end<br>
+ <br>
 &nbsp; def test_should_find_test_failures<br>
 &nbsp; &nbsp; testFailures = <br>
TestFailureParser.new.get_test_failures(LOG_OUTPUT_WITH_TEST_FAILURE)<br>
 &nbsp; &nbsp; assert_equal 2, testFailures.length<br>
 &nbsp; &nbsp; assert_equal expected_first_test_failure, testFailures[0]<br>
 &nbsp; &nbsp; assert_equal expected_second_test_failure, testFailures[1]<br>
 &nbsp; end<br>
- &nbsp; &nbsp; &nbsp; <br>
+ <br>
+ &nbsp;def test_should_find_spec_failures<br>
+ &nbsp; &nbsp;testFailures = <br>
TestFailureParser.new.get_test_failures(LOG_OUTPUT_WITH_SPEC_FAILURE)<br>
+ &nbsp; &nbsp;assert_equal 1, testFailures.length<br>
+ &nbsp; &nbsp;assert_equal expected_first_spec_failure, testFailures[0]<br>
+ &nbsp;end<br>
+ <br>
 &nbsp; def test_should_correctly_parse_mocha_test_failures<br>
 &nbsp; &nbsp; testFailures = <br>
TestFailureParser.new.get_test_failures(LOG_OUTPUT_WITH_MOCK_TEST_FAILURE)<br>
 &nbsp; &nbsp; assert_equal 1, testFailures.length<br>
 &nbsp; &nbsp; assert_equal expected_mock_test_failure, testFailures[0]<br>
 &nbsp; end<br>
- &nbsp; &nbsp; &nbsp; <br>
+ <br>
 &nbsp; def expected_first_test_failure<br>
 &nbsp; &nbsp; <br>
TestErrorEntry.create_failure(&quot;test_should_fail(SubversionLogParserTest)&quot;,<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;&lt;1&gt; expected
but was\n&lt;\&quot;abc\&quot;&gt;.&quot;,<br>
@@ -121,5 +160,15 @@<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>
&quot;#&lt;Mocha::Mock:0x-245ec74a&gt;.force_build_if_requested - expected
calls: <br>
1, actual calls: 2&quot;,<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>
&quot;./test/unit/polling_scheduler_test.rb:44&quot;)<br>
 &nbsp; end<br>
+<br>
+ &nbsp;def expected_first_spec_failure<br>
+ &nbsp; &nbsp;TestErrorEntry.create_failure(&quot;Given a request to edit
with a week <br>
and year the controller should return a hash of one blank row if data !
<br>
found&quot;,<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;nil should be a kind
of Array&quot;,<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>
&quot;./spec/controllers/consultant_controller_spec.rb:47:\n&quot; +<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>
&quot;/cruisecontrol/builds/ccs/work/config/../lib/tasks/cruise.rake:36:in
<br>
`run&#39;\n&quot; +<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>
&quot;/cruisecontrol/builds/ccs/work/config/../lib/tasks/cruise.rake:47:\n&quot;
+<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<br>
&quot;/cruisecontrol/tasks/cc_build.rake:32:\n&quot; +<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;-e:1:&quot;)<br>
+ &nbsp;end<br>
 &nbsp;<br>
 end<br>
\ No newline at end of file<br>
<br>
<br>
_______________________________________________<br>
Cruisecontrolrb-developers mailing list<br>
<a href="mailto:Cruisecontrolrb-developers@rubyforge.org" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">Cruisecontrolrb-developers@rubyforge.org</a><br>
<a href="http://rubyforge.org/mailman/listinfo/cruisecontrolrb-developers" target="_blank" onclick="return top.js.OpenExtLink(window,event,this)">http://rubyforge.org/mailman/listinfo/cruisecontrolrb-developers</a><br>
</font></tt>
<br></span></div><br>_______________________________________________<br>Cruisecontrolrb-developers mailing list<br><a onclick="return top.js.OpenExtLink(window,event,this)" href="mailto:Cruisecontrolrb-developers@rubyforge.org">
Cruisecontrolrb-developers@rubyforge.org</a><br><a onclick="return top.js.OpenExtLink(window,event,this)" href="http://rubyforge.org/mailman/listinfo/cruisecontrolrb-developers" target="_blank">http://rubyforge.org/mailman/listinfo/cruisecontrolrb-developers
</a><br><br></blockquote></div><br>