This is something I came up with testing some views on the plane down to San Jose today. The feature is twofold:
A) /_utils/couch_tests.html now takes an optional query string, which tells it an extra JavaScript file to load. The default (CouchDB's functional tests, can be accessed via http://localhost:5984/_utils/couch_tests.html?script/couch_tests.js but the reason I added it is that you can use path like /_utils/couch_tests.html?/twitter-client/_design%2Ftwitter-client/tests.js to load an attachment as the source of the tests variable. I haven't run svn up on my public CouchDB instance, or I'd link to a running setup. This is what my couchdb-twitter-client view tests look like: http://github.com/jchris/couchdb-twitter-client/tree/master/_attachments/tests.js B) When browsing a document in Futon that has an attachment called tests.js, a link is provided for running the tests using the Futon test runner. Here's what it looks like: http://img.skitch.com/20081117-q4s5ypatrtqk8a4tpmki59249c.png Hope y'all like this. Please suggest (and patch) improvements! Chris On Mon, Nov 17, 2008 at 2:14 PM, <[EMAIL PROTECTED]> wrote: > Author: jchris > Date: Mon Nov 17 14:14:14 2008 > New Revision: 718409 > > URL: http://svn.apache.org/viewvc?rev=718409&view=rev > Log: > factored couch_test_runner.js out from couch_tests.js, made it usuable for > unit testing views from design docs > > Added: > incubator/couchdb/trunk/share/www/script/couch_test_runner.js (with > props) > Modified: > incubator/couchdb/trunk/share/www/couch_tests.html > incubator/couchdb/trunk/share/www/index.html > incubator/couchdb/trunk/share/www/script/browse.js > incubator/couchdb/trunk/share/www/script/couch_tests.js > > Modified: incubator/couchdb/trunk/share/www/couch_tests.html > URL: > http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/couch_tests.html?rev=718409&r1=718408&r2=718409&view=diff > ============================================================================== > --- incubator/couchdb/trunk/share/www/couch_tests.html [utf-8] (original) > +++ incubator/couchdb/trunk/share/www/couch_tests.html [utf-8] Mon Nov 17 > 14:14:14 2008 > @@ -22,20 +22,18 @@ > <script src="script/jquery.js?1.2.6"></script> > <script src="script/couch.js?0.8.0"></script> > <script src="script/pprint.js?0.8.0"></script> > + <script src="script/couch_test_runner.js"></script> > <script> > - $(document).ready(function() { > + $(function() { > + updateTestsListing(); > + $("#toolbar button.run").click(runAllTests); > + if (window != parent) parent.updateNavigation(); > $("#toolbar button.load").click(function() { > location.reload(true); > }); > }); > - </script> > - <script src="script/couch_tests.js"></script> > - <script> > - $(document).ready(function() { > - updateTestsListing(); > - $("#toolbar button.run").click(runAllTests); > - if (window != parent) parent.updateNavigation(); > - }); > + var testsPath = document.location.toString().split('?')[1]; > + loadTests(testsPath||"script/couch_tests.js") > </script> > </head> > <body> > > Modified: incubator/couchdb/trunk/share/www/index.html > URL: > http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/index.html?rev=718409&r1=718408&r2=718409&view=diff > ============================================================================== > --- incubator/couchdb/trunk/share/www/index.html [utf-8] (original) > +++ incubator/couchdb/trunk/share/www/index.html [utf-8] Mon Nov 17 14:14:14 > 2008 > @@ -87,7 +87,7 @@ > <li><a href="browse/index.html" target="content">Overview</a></li> > <li><a href="replicator.html" target="content">Replicator</a></li> > <li><a href="config.html" target="content">Configuration</a></li> > - <li><a href="couch_tests.html" target="content">Test Suite</a></li> > + <li><a href="couch_tests.html?script/couch_tests.js" > target="content">Test Suite</a></li> > </ul></li> > <li><span>Recent Databases</span> > <ul id="dbs"></ul> > > Modified: incubator/couchdb/trunk/share/www/script/browse.js > URL: > http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/browse.js?rev=718409&r1=718408&r2=718409&view=diff > ============================================================================== > --- incubator/couchdb/trunk/share/www/script/browse.js [utf-8] (original) > +++ incubator/couchdb/trunk/share/www/script/browse.js [utf-8] Mon Nov 17 > 14:14:14 2008 > @@ -873,12 +873,18 @@ > } > > function _renderAttachmentItem(name, attachment) { > + var attachmentHref = db.uri + encodeURIComponent(docId) > + + "/" + encodeURIComponent(name); > var li = $("<li></li>"); > $("<a href='' title='Download file' target='_top'></a>").text(name) > - .attr("href", db.uri + encodeURIComponent(docId) + "/" + > encodeURIComponent(name)) > + .attr("href", attachmentHref) > .wrapInner("<tt></tt>").appendTo(li); > $("<span>()</span>").text("" + prettyPrintSize(attachment.length) + > ", " + attachment.content_type).addClass("info").appendTo(li); > + if (name == "tests.js") { > + li.find('span.info').append(', <a href="/_utils/couch_tests.html?' > + + attachmentHref + '">open in test runner</a>'); > + } > _initAttachmentItem(name, attachment, li); > return li; > } > > Added: incubator/couchdb/trunk/share/www/script/couch_test_runner.js > URL: > http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch_test_runner.js?rev=718409&view=auto > ============================================================================== > --- incubator/couchdb/trunk/share/www/script/couch_test_runner.js (added) > +++ incubator/couchdb/trunk/share/www/script/couch_test_runner.js Mon Nov 17 > 14:14:14 2008 > @@ -0,0 +1,181 @@ > +// Licensed under the Apache License, Version 2.0 (the "License"); you may > not > +// use this file except in compliance with the License. You may obtain a > copy > +// of the License at > +// > +// http://www.apache.org/licenses/LICENSE-2.0 > +// > +// Unless required by applicable law or agreed to in writing, software > +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT > +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the > +// License for the specific language governing permissions and limitations > under > +// the License. > + > +// *********************** Test Framework of Sorts ************************* > // > + > +function loadTests(url) { > + document.write('<script src="'+url+'"></script>'); > +}; > + > +function patchTest(fun) { > + var source = fun.toString(); > + var output = ""; > + var i = 0; > + var testMarker = "T(" > + while (i < source.length) { > + var testStart = source.indexOf(testMarker, i); > + if (testStart == -1) { > + output = output + source.substring(i, source.length); > + break; > + } > + var testEnd = source.indexOf(");", testStart); > + var testCode = source.substring(testStart + testMarker.length, testEnd); > + output += source.substring(i, testStart) + "T(" + testCode + "," + > JSON.stringify(testCode); > + i = testEnd; > + } > + try { > + return eval("(" + output + ")"); > + } catch (e) { > + return null; > + } > +} > + > +function runAllTests() { > + var rows = $("#tests tbody.content tr"); > + $("td", rows).html(" "); > + $("td.status", > rows).removeClass("error").removeClass("failure").removeClass("success").text("not > run"); > + var offset = 0; > + function runNext() { > + if (offset < rows.length) { > + var row = rows.get(offset); > + runTest($("th button", row).get(0), function() { > + offset += 1; > + setTimeout(runNext, 1000); > + }); > + } > + } > + runNext(); > +} > + > +var numFailures = 0; > +var currentRow = null; > + > +function runTest(button, callback, debug) { > + if (currentRow != null) { > + alert("Can not run multiple tests simultaneously."); > + return; > + } > + var row = currentRow = $(button).parents("tr").get(0); > + $("td.status", > row).removeClass("error").removeClass("failure").removeClass("success"); > + $("td", row).html(" "); > + var testFun = tests[row.id]; > + function run() { > + numFailures = 0; > + var start = new Date().getTime(); > + try { > + if (debug == undefined || !debug) { > + testFun = patchTest(testFun) || testFun; > + } > + testFun(debug); > + var status = numFailures > 0 ? "failure" : "success"; > + } catch (e) { > + var status = "error"; > + if ($("td.details ol", row).length == 0) { > + $("<ol></ol>").appendTo($("td.details", row)); > + } > + $("<li><b>Exception raised:</b> <code class='error'></code></li>") > + .find("code").text(JSON.stringify(e)).end() > + .appendTo($("td.details ol", row)); > + if (debug) { > + currentRow = null; > + throw e; > + } > + } > + if ($("td.details ol", row).length) { > + $("<a href='#'>Run with debugger</a>").click(function() { > + runTest(this, undefined, true); > + }).prependTo($("td.details ol", row)); > + } > + var duration = new Date().getTime() - start; > + $("td.status", row).removeClass("running").addClass(status).text(status); > + $("td.duration", row).text(duration + "ms"); > + updateTestsFooter(); > + currentRow = null; > + if (callback) callback(); > + } > + $("td.status", row).addClass("running").text("running…"); > + setTimeout(run, 100); > +} > + > +function showSource(cell) { > + var name = $(cell).text(); > + var win = window.open("", name, > "width=700,height=500,resizable=yes,scrollbars=yes"); > + win.document.title = name; > + > $("<pre></pre>").text(tests[name].toString()).appendTo(win.document.body).fadeIn(); > +} > + > +function updateTestsListing() { > + for (var name in tests) { > + if (!tests.hasOwnProperty(name)) continue; > + var testFunction = tests[name]; > + var row = $("<tr><th></th><td></td><td></td><td></td></tr>") > + .find("th").text(name).attr("title", "Show source").click(function() { > + showSource(this); > + }).end() > + .find("td:nth(0)").addClass("status").text("not run").end() > + .find("td:nth(1)").addClass("duration").html(" ").end() > + .find("td:nth(2)").addClass("details").html(" ").end(); > + $("<button type='button' class='run' title='Run > test'></button>").click(function() { > + this.blur(); > + runTest(this); > + return false; > + }).prependTo(row.find("th")); > + row.attr("id", name).appendTo("#tests tbody.content"); > + } > + $("#tests tr").removeClass("odd").filter(":odd").addClass("odd"); > + updateTestsFooter(); > +} > + > +function updateTestsFooter() { > + var tests = $("#tests tbody.content tr td.status"); > + var testsRun = tests.not(":contains('not run'))"); > + var testsFailed = testsRun.not(".success"); > + $("#tests tbody.footer td").text(testsRun.length + " of " + tests.length + > + " test(s) run, " + testsFailed.length + " failures"); > +} > + > +// Use T to perform a test that returns false on failure and if the test > fails, > +// display the line that failed. > +// Example: > +// T(MyValue==1); > +function T(arg1, arg2) { > + if (!arg1) { > + if (currentRow) { > + if ($("td.details ol", currentRow).length == 0) { > + $("<ol></ol>").appendTo($("td.details", currentRow)); > + } > + $("<li><b>Assertion failed:</b> <code class='failure'></code></li>") > + .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() > + .appendTo($("td.details ol", currentRow)); > + } > + numFailures += 1 > + } > +} > + > +function equals(a,b) { > + if (a === b) return true; > + try { > + return repr(a) === repr(b); > + } catch (e) { > + return false; > + } > +} > + > +function repr(val) { > + if (val === undefined) { > + return null; > + } else if (val === null) { > + return "null"; > + } else { > + return JSON.stringify(val); > + } > +} > \ No newline at end of file > > Propchange: incubator/couchdb/trunk/share/www/script/couch_test_runner.js > ------------------------------------------------------------------------------ > svn:eol-style = native > > Modified: incubator/couchdb/trunk/share/www/script/couch_tests.js > URL: > http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch_tests.js?rev=718409&r1=718408&r2=718409&view=diff > ============================================================================== > --- incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original) > +++ incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] Mon Nov > 17 14:14:14 2008 > @@ -2067,172 +2067,6 @@ > return docs; > } > > -// *********************** Test Framework of Sorts ************************* > // > - > -function patchTest(fun) { > - var source = fun.toString(); > - var output = ""; > - var i = 0; > - var testMarker = "T(" > - while (i < source.length) { > - var testStart = source.indexOf(testMarker, i); > - if (testStart == -1) { > - output = output + source.substring(i, source.length); > - break; > - } > - var testEnd = source.indexOf(");", testStart); > - var testCode = source.substring(testStart + testMarker.length, testEnd); > - output += source.substring(i, testStart) + "T(" + testCode + "," + > JSON.stringify(testCode); > - i = testEnd; > - } > - try { > - return eval("(" + output + ")"); > - } catch (e) { > - return null; > - } > -} > - > -function runAllTests() { > - var rows = $("#tests tbody.content tr"); > - $("td", rows).html(" "); > - $("td.status", > rows).removeClass("error").removeClass("failure").removeClass("success").text("not > run"); > - var offset = 0; > - function runNext() { > - if (offset < rows.length) { > - var row = rows.get(offset); > - runTest($("th button", row).get(0), function() { > - offset += 1; > - setTimeout(runNext, 1000); > - }); > - } > - } > - runNext(); > -} > - > -var numFailures = 0; > -var currentRow = null; > - > -function runTest(button, callback, debug) { > - if (currentRow != null) { > - alert("Can not run multiple tests simultaneously."); > - return; > - } > - var row = currentRow = $(button).parents("tr").get(0); > - $("td.status", > row).removeClass("error").removeClass("failure").removeClass("success"); > - $("td", row).html(" "); > - var testFun = tests[row.id]; > - function run() { > - numFailures = 0; > - var start = new Date().getTime(); > - try { > - if (debug == undefined || !debug) { > - testFun = patchTest(testFun) || testFun; > - } > - testFun(debug); > - var status = numFailures > 0 ? "failure" : "success"; > - } catch (e) { > - var status = "error"; > - if ($("td.details ol", row).length == 0) { > - $("<ol></ol>").appendTo($("td.details", row)); > - } > - $("<li><b>Exception raised:</b> <code class='error'></code></li>") > - .find("code").text(JSON.stringify(e)).end() > - .appendTo($("td.details ol", row)); > - if (debug) { > - currentRow = null; > - throw e; > - } > - } > - if ($("td.details ol", row).length) { > - $("<a href='#'>Run with debugger</a>").click(function() { > - runTest(this, undefined, true); > - }).prependTo($("td.details ol", row)); > - } > - var duration = new Date().getTime() - start; > - $("td.status", row).removeClass("running").addClass(status).text(status); > - $("td.duration", row).text(duration + "ms"); > - updateTestsFooter(); > - currentRow = null; > - if (callback) callback(); > - } > - $("td.status", row).addClass("running").text("running…"); > - setTimeout(run, 100); > -} > - > -function showSource(cell) { > - var name = $(cell).text(); > - var win = window.open("", name, > "width=700,height=500,resizable=yes,scrollbars=yes"); > - win.document.title = name; > - > $("<pre></pre>").text(tests[name].toString()).appendTo(win.document.body).fadeIn(); > -} > - > -function updateTestsListing() { > - for (var name in tests) { > - if (!tests.hasOwnProperty(name)) continue; > - var testFunction = tests[name]; > - var row = $("<tr><th></th><td></td><td></td><td></td></tr>") > - .find("th").text(name).attr("title", "Show source").click(function() { > - showSource(this); > - }).end() > - .find("td:nth(0)").addClass("status").text("not run").end() > - .find("td:nth(1)").addClass("duration").html(" ").end() > - .find("td:nth(2)").addClass("details").html(" ").end(); > - $("<button type='button' class='run' title='Run > test'></button>").click(function() { > - this.blur(); > - runTest(this); > - return false; > - }).prependTo(row.find("th")); > - row.attr("id", name).appendTo("#tests tbody.content"); > - } > - $("#tests tr").removeClass("odd").filter(":odd").addClass("odd"); > - updateTestsFooter(); > -} > - > -function updateTestsFooter() { > - var tests = $("#tests tbody.content tr td.status"); > - var testsRun = tests.not(":contains('not run'))"); > - var testsFailed = testsRun.not(".success"); > - $("#tests tbody.footer td").text(testsRun.length + " of " + tests.length + > - " test(s) run, " + testsFailed.length + " failures"); > -} > - > -// Use T to perform a test that returns false on failure and if the test > fails, > -// display the line that failed. > -// Example: > -// T(MyValue==1); > -function T(arg1, arg2) { > - if (!arg1) { > - if (currentRow) { > - if ($("td.details ol", currentRow).length == 0) { > - $("<ol></ol>").appendTo($("td.details", currentRow)); > - } > - $("<li><b>Assertion failed:</b> <code class='failure'></code></li>") > - .find("code").text((arg2 != null ? arg2 : arg1).toString()).end() > - .appendTo($("td.details ol", currentRow)); > - } > - numFailures += 1 > - } > -} > - > -function equals(a,b) { > - if (a === b) return true; > - try { > - return repr(a) === repr(b); > - } catch (e) { > - return false; > - } > -} > - > -function repr(val) { > - if (val === undefined) { > - return null; > - } else if (val === null) { > - return "null"; > - } else { > - return JSON.stringify(val); > - } > -} > - > function restartServer() { > var reply = CouchDB.request("POST", "/_restart"); > do { > > > -- Chris Anderson http://jchris.mfdz.com
