Showing posts with label css. Show all posts
Showing posts with label css. Show all posts

Wednesday, July 04, 2007

Making Useful PHPUnit results

So you have about eleven billion test results, and you need a pretty way to quickly see failures.
What you need is a continous integration environment, and if you don't want to have to stick things into a database, I have the answer for you.

PHPUnit 3.0.6 provides (the first working) version of test-results as JSON.
Basically, you run:
phpunit --log-json results.js MyTest.php


And out comes JSON.

The next step is pretty simple - you just need a bit of Prototype magic, some css, and some love.
What you end up with is:
Our Tests

Here's the javascript, php, and css behind it. I'll leave it up to you to find nice icons.

<?php
$file = dirname(__FILE__) . '/results.js';
if (!file_exists($file)) {
die("No unit tests have been run?");
}
$json = file_get_contents($file);

//Ugh
$json = '[' . str_replace("}{", "},{", $json) . ']';

?>
<html>
<head>
<script src="/prototype.js"></script>
</head>
<body>
<style>
table {
border: 1px solid rgb(240, 240, 240);
}

.error {
border: 1px solid orange;
}

.fail {
border: 1px solid red;
}

.pass {
border: 1px solid green;
}

.cell {
width: 200px !important;
text-align: center;
padding: 0.5em;
}
.cell img {
display: block;
margin: auto;
}

h4 {
font-weight: normal !important;
font-style: italic;
}
</style>
<script type="text/javascript">
var total_passes = 0;
var total_failures = 0;
var total_errors = 0;
var total_skipped = 0;
var total_incomplete= 0;

function renderSuiteStart(item) {
var h2 = document.createElement("H2");
h2.appendChild(document.createTextNode(item.suite));

var p = document.createElement("p");
p.appendChild(document.createTextNode(item.tests + " tests in suite"));

var div = document.createElement("div");
var table = document.createElement("table");

div.id = item.suite;
table.id = item.suite + "_table";
table.className = '';

div.appendChild(h2);
div.appendChild(p);
div.appendChild(table);

$('main').appendChild(div);
}

function renderTest(item) {
var h3 = document.createElement("H3");
var h4 = document.createElement("H4");
h3.appendChild(document.createTextNode(item.test));

var p = document.createElement("p");
p.appendChild(document.createTextNode(item.time + " tests in suite"));

var tr = document.createElement("TR");
var th = document.createElement("TH");
var message = document.createElement("TD");
var detail = document.createElement("TD");
var time = document.createElement("TD");
var img = document.createElement("IMG");
var backtrace = document.createElement("OL");


time.appendChild(document.createTextNode(item.time));


img.alt = item.status;
message.className = item.status + " cell";

switch (item.status) {
case 'error':
if (item.message == "Skipped Test") {
message.className = "skipped cell";
total_skipped++;
} else if (item.message == "Incomplete Test") {
message.className = "incomplete cell";
total_incomplete++;
} else {
total_errors++;
}
img.src = '/presentation/common/img/emblem-important-small.gif';
break;
case 'fail':
total_failures++;
img.src = '/presentation/common/img/cross.gif';
break;
default:
total_passes++;
img.src = '/presentation/common/img/check.gif';
break;
}

item.trace.each(
function(trace) {
var li = document.createElement("LI");
var text = trace.file + "(line " + trace.line + "): " + trace.class + trace.type + trace.function + "()";
li.appendChild(document.createTextNode(text));

backtrace.appendChild(li);
}
);

h4.appendChild(document.createTextNode(item.test));
detail.appendChild(h4);
detail.appendChild(backtrace);

message.appendChild(img);
message.appendChild(document.createTextNode(item.message));
message.style.width = "300px";

tr.appendChild(message);
tr.appendChild(detail);


$(item.suite + "_table").appendChild(tr);
}

function renderResults() {
var data = $A(<?php print $json; ?>);

data.each(function(item) {
switch (item.event) {
case 'suiteStart':
renderSuiteStart(item);
break;
case 'test':
renderTest(item);
}
});

renderTOC();
}

function renderTOC() {
var fail = $A(document.getElementsByClassName('fail'));
var pass = $A(document.getElementsByClassName('pass'));
var error= $A(document.getElementsByClassName('error'));
var skipped= $A(document.getElementsByClassName('skipped'));
var incomplete= $A(document.getElementsByClassName('incomplete'));

var menu = document.createElement('p');
var checkbox_pass = document.createElement('input');
var checkbox_fail = document.createElement('input');
var checkbox_error = document.createElement('input');
var checkbox_skipped = document.createElement('input');
var checkbox_incomplete = document.createElement('input');

var label_pass = document.createElement('label');
var label_fail = document.createElement('label');
var label_error = document.createElement('label');
var label_skipped = document.createElement('label');
var label_incomplete = document.createElement('label');
var status_image;

menu.style.position = 'fixed';
menu.style.bottom = 0;
menu.style.right = "1.5em";
menu.style.backgroundColor = 'rgb(240, 240, 240)';
menu.style.padding = "1em;";
menu.style.fontWeight = 'bolder';

checkbox_pass.type = "checkbox";
checkbox_fail.type = "checkbox";
checkbox_error.type = "checkbox";
checkbox_skipped.type = "checkbox";
checkbox_incomplete.type = "checkbox";

checkbox_pass.checked = "checked";
checkbox_fail.checked = "checked";
checkbox_error.checked = "checked";
checkbox_skipped.checked = "checked";
checkbox_incomplete.checked = "checked";


checkbox_pass.onclick=function(){toggle(pass)};
checkbox_fail.onclick=function(){toggle(fail)};
checkbox_error.onclick=function(){toggle(error)};
checkbox_skipped.onclick=function(){toggle(skipped)};
checkbox_incomplete.onclick=function(){toggle(incomplete)};

label_pass.appendChild(checkbox_pass);
label_fail.appendChild(checkbox_fail);
label_error.appendChild(checkbox_error);
label_skipped.appendChild(checkbox_skipped);
label_incomplete.appendChild(checkbox_incomplete);

label_pass.appendChild(document.createTextNode("Passed (" + total_passes + ")"));
label_fail.appendChild(document.createTextNode("Failed (" + total_failures + ")"));
label_error.appendChild(document.createTextNode("Error (" + total_errors + ")"));
label_skipped.appendChild(document.createTextNode("Skipped (" + total_skipped + ")"));
label_incomplete.appendChild(document.createTextNode("Incomplete (" + total_incomplete + ")"));

label_pass.style.color = "green";
label_fail.style.color = "red";
label_error.style.color = "#FF6600";
label_skipped.style.color = "blue";
label_incomplete.style.color = "gray";

status_image = document.createElement("IMG");
status_image.style.display = "block";
status_image.style.margin = "auto";
if (total_failures > 0) {
status_image.src = "/presentation/common/img/emblem-unreadable.png";
} else {
status_image.src = "/presentation/common/img/compliance.png";
}
menu.appendChild(status_image);

menu.appendChild(label_pass);
menu.appendChild(label_fail);
menu.appendChild(label_error);
menu.appendChild(label_skipped);
menu.appendChild(label_incomplete);

checkbox_pass.onclick();
checkbox_pass.checked = "";

checkbox_skipped.onclick();
checkbox_skipped.checked = "";

checkbox_incomplete.onclick();
checkbox_incomplete.checked = "";

document.body.appendChild(menu);
}

function toggle(type) {
type.each(function(td) {
if (td.parentNode.style.display != 'none') {
td.parentNode.style.display = 'none';
} else {
td.parentNode.style.display = 'table-row';
}
});
}

function hide(type) {
type.each(function(td) {
td.parentNode.style.display = 'none';
});
}

function show(type) {
type.each(function(td) {
td.parentNode.style.display = 'table-row';
});
}

window.onload = renderResults;
</script>
<div id="main" class="contentbox">
<h1>Unit tests</h1>
<p><a href="results.txt">Raw results</a> | <a href="docs.html">Agile Documentation</a> | <a href="recent.php">Recent changes</a> | Generated: <?php print date("F j, Y, g:i a", filemtime($file)); ?> </p>
</div>
</body>
</html>

Monday, March 19, 2007

Adobe Apollo

So much hype and excitement, and so little to show for it.

No Apollo for Linux, and I just happen to be doing a dist-upgrade to fiesty... So no trying for about 12 hours.

So off I go to read all about it, and what do I find?


<mx:TabNavigator
id="ContentNavigator"
width="860" height="540"
x="10" y="10" paddingTop="5" paddingRight="5" paddingBottom="5" paddingLeft="5">

<mx:VBox label="Articles"
width="100%" height="100%"
verticalGap="0" paddingTop="10" paddingRight="10" paddingBottom="10" paddingLeft="10">
<mx:Box width="100%" height="33"
backgroundColor="#B8AF9C" cornerRadius="15" paddingBottom="5" paddingLeft="10" paddingRight="5" paddingTop="5">
<mx:Button label="Fetch RSS" click="RssService.send();RssLoader.height=30"/>
</mx:Box>
<mx:Box id="RssLoader"
width="100%" height="0"
backgroundColor="#B8AF9C" paddingTop="6" paddingRight="10" paddingBottom="5" paddingLeft="10">
<mx:ProgressBar id="rssProgress"
indeterminate="true"
barColor="#B8AF9C"
width="100%"
labelPlacement="center"
label="loading feed"/>
</mx:Box>
<mx:TileList id="FeedDisplay"
width="100%" height="100%"
maxColumns="1"
rowHeight="120" columnWidth="810"
itemRenderer="ItemRenderer"
dataProvider="{rssFeedData}"/>
</mx:VBox>

</mx:TabNavigator>



That looks suspiciously like far too much presentation logic in there. We've had CSS forever, why is there a mix of css-like attributes, javascript-like attributes and {funky templating}?
I'm beginning to think waiting for firefox 3 is a much better option, seeing as they already delivered offline cache support; while apollo doesn't even have it yet.