Reworked TapParser to be compliant and more robust.

Added another test case from #571.

Updated the output of TapParser::processTestLine.

Broke TapParser::parse down in simpler methods.

TapParser: ignore leading garbage and properly complain on missing TAP log.

TapParser: detect and report duplicated TAP log.

TapParser: got rid of the "test" and "suite" values.

They are only available with PHPUnit.

TapParser: append the message from yaml diagnostic to existing message.

Reworked the dispaly of test results.

PHPUnit plugin: pretty print test data.
This commit is contained in:
Adirelle 2015-03-03 20:10:55 +01:00 committed by Tobias van Beek
parent 2c43cd1cac
commit 9d4116e3c9
14 changed files with 587 additions and 105 deletions

View file

@ -194,14 +194,20 @@ Services</a> sektionen under dit Bitbucket-repository.',
'end' => 'Slut',
'from' => 'Fra',
'to' => 'Til',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Resultat',
'ok' => 'OK',
'took_n_seconds' => 'Tog %d sekunder',
'build_created' => 'Build Oprettet',
'build_started' => 'Build Startet',
'build_finished' => 'Build Afsluttet',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Navn',

View file

@ -192,14 +192,20 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'end' => 'Ende',
'from' => 'Von',
'to' => 'Bis',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Resultat',
'ok' => 'OK',
'took_n_seconds' => 'Benötigte %d Sekunden',
'build_created' => 'Build erstellt',
'build_started' => 'Build gestartet',
'build_finished' => 'Build abgeschlossen',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Name',

View file

@ -194,14 +194,20 @@ Services</a> του Bitbucket αποθετηρίου σας.',
'end' => 'Τέλος',
'from' => 'Από',
'to' => 'Προς',
'suite' => 'Σουίτα',
'test' => 'Τέστ',
'result' => 'Αποτέλεσμα',
'ok' => 'ΟΚ',
'took_n_seconds' => 'Χρειάστηκαν %d δευτερόλεπτα',
'build_created' => 'Η κατασκευή δημιουργήθηκε',
'build_started' => 'Η κατασκευή άρχισε',
'build_finished' => 'Η κατασκευή ολοκληρώθηκε',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Όνομα',

View file

@ -198,14 +198,20 @@ PHPCI',
'end' => 'End',
'from' => 'From',
'to' => 'To',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Result',
'ok' => 'OK',
'took_n_seconds' => 'Took %d seconds',
'build_created' => 'Build Created',
'build_started' => 'Build Started',
'build_finished' => 'Build Finished',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Name',

View file

@ -195,14 +195,20 @@ PHPCI',
'end' => 'Fin',
'from' => 'À partir de',
'to' => 'jusque',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Resultat',
'ok' => 'OK',
'took_n_seconds' => 'Exécuté en %d secondes',
'build_created' => 'Build créé',
'build_started' => 'Build démarré',
'build_finished' => 'Build terminé',
'test_message' => 'Message',
'test_no_message' => 'Pas de message',
'test_success' => 'Réussi(s): %d',
'test_fail' => 'Echec(s): %d',
'test_skipped' => 'Passé(s): %d',
'test_error' => 'Erreurs: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Nom',

View file

@ -197,14 +197,20 @@ PHPCI',
'end' => 'Finisci',
'from' => 'Da',
'to' => 'A',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Risultati',
'ok' => 'OK',
'took_n_seconds' => 'Sono stati impiegati %d seconds',
'build_created' => 'Build Creata',
'build_started' => 'Build Avviata',
'build_finished' => 'Build Terminata',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Nome',

View file

@ -195,14 +195,20 @@ Services</a> sectie van je Bitbucket repository toegevoegd worden.',
'end' => 'Einde',
'from' => 'Van',
'to' => 'Tot',
'suite' => 'Suite',
'test' => 'Test',
'result' => 'Resultaat',
'ok' => 'OK',
'took_n_seconds' => 'Duurde %d seconden',
'build_created' => 'Build aangemaakt',
'build_started' => 'Build gestart',
'build_finished' => 'Build beëindigd',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Naam',

View file

@ -198,14 +198,20 @@ Services</a> repozytoria Bitbucket.',
'end' => 'Koniec',
'from' => 'Od',
'to' => 'Do',
'suite' => 'Zestaw ',
'test' => 'Test',
'result' => 'Wynik',
'ok' => 'OK',
'took_n_seconds' => 'Zajęło %d sekund',
'build_created' => 'Budowanie Stworzone',
'build_started' => 'Budowanie Rozpoczęte',
'build_finished' => 'Budowanie Zakończone',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Nazwa',

View file

@ -193,14 +193,20 @@ PHPCI',
'end' => 'Конец',
'from' => 'Из',
'to' => 'В',
'suite' => 'Комплект',
'test' => 'Тест',
'result' => 'Результат',
'ok' => 'OK',
'took_n_seconds' => 'Заняло секунд: %d',
'build_created' => 'Сборка создана',
'build_started' => 'Сборка запущена',
'build_finished' => 'Сборка окончена',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Имя',

View file

@ -195,14 +195,20 @@ PHPCI',
'end' => 'Кінець',
'from' => 'Від',
'to' => 'До',
'suite' => 'Комплект',
'test' => 'Тест',
'result' => 'Результат',
'ok' => 'OK',
'took_n_seconds' => 'Зайняло %d секунд',
'build_created' => 'Збірка створена',
'build_started' => 'Збірка розпочата',
'build_finished' => 'Збірка завершена',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
// Users
'name' => 'Ім’я',

View file

@ -2,7 +2,9 @@
namespace PHPCI\Plugin\Util;
use Exception;
use PHPCI\Helper\Lang;
use Symfony\Component\Yaml\Yaml;
/**
* Processes TAP format strings into usable test result data.
@ -10,18 +12,41 @@ use PHPCI\Helper\Lang;
*/
class TapParser
{
const TEST_COUNTS_PATTERN = '/([0-9]+)\.\.([0-9]+)/';
const TEST_LINE_PATTERN = '/(ok|not ok)\s+[0-9]+\s+\-\s+([^\n]+)::([^\n]+)/';
const TEST_MESSAGE_PATTERN = '/message\:\s+\'([^\']+)\'/';
const TEST_COVERAGE_PATTERN = '/Generating code coverage report/';
const TEST_SKIP_PATTERN = '/ok\s+[0-9]+\s+\-\s+#\s+SKIP/';
const TEST_COUNTS_PATTERN = '/^\d+\.\.(\d+)/';
const TEST_LINE_PATTERN = '/^(ok|not ok)(?:\s+\d+)?(?:\s+\-)?\s*(.*?)(?:\s*#\s*(skip|todo)\s*(.*))?\s*$/i';
const TEST_YAML_START = '/^(\s*)---/';
const TEST_DIAGNOSTIC = '/^#/';
/**
* @var string
*/
protected $tapString;
/**
* @var int
*/
protected $failures = 0;
/**
* @var array
*/
protected $lines;
/**
* @var integer
*/
protected $lineNumber;
/**
* @var integer
*/
protected $testCount;
/**
* @var array
*/
protected $results;
/**
* Create a new TAP parser for a given string.
* @param string $tapString The TAP format string to be parsed.
@ -38,81 +63,175 @@ class TapParser
{
// Split up the TAP string into an array of lines, then
// trim all of the lines so there's no leading or trailing whitespace.
$lines = explode("\n", $this->tapString);
$lines = array_map(function ($line) {
return trim($line);
}, $lines);
$this->lines = array_map('rtrim', explode("\n", $this->tapString));
$this->lineNumber = 0;
// Check TAP version:
$versionLine = array_shift($lines);
$this->testCount = false;
$this->results = array();
if ($versionLine != 'TAP version 13') {
throw new \Exception(Lang::get('tap_version'));
$header = $this->findTapLog();
$line = $this->nextLine();
if ($line === $header) {
throw new Exception("Duplicated TAP log, please check the configration.");
}
if (isset($lines[count($lines) - 1]) && preg_match(self::TEST_COVERAGE_PATTERN, $lines[count($lines) - 1])) {
array_pop($lines);
if ($lines[count($lines) - 1] == "") {
array_pop($lines);
while ($line !== false && ($this->testCount === false || count($this->results) < $this->testCount)) {
$this->parseLine($line);
$line = $this->nextLine();
}
if (count($this->results) !== $this->testCount) {
throw new Exception(Lang::get('tap_error'));
}
return $this->results;
}
/** Looks for the start of the TAP log in the string.
*
* @return string The TAP header line.
*
* @throws Exception if no TAP log is found or versions mismatch.
*/
protected function findTapLog()
{
// Look for the beggning of the TAP output
do {
$header = $this->nextLine();
} while ($header !== false && substr($header, 0, 12) !== 'TAP version ');
//
if ($header === false) {
throw new Exception('No TAP log found, please check the configuration.');
} elseif ($header !== 'TAP version 13') {
throw new Exception(Lang::get('tap_version'));
}
return $header;
}
/** Fetch the next line.
*
* @return string|false The next line or false if the end has been reached.
*/
protected function nextLine()
{
if ($this->lineNumber < count($this->lines)) {
return $this->lines[$this->lineNumber++];
}
return false;
}
/** Parse a single line.
*
* @param string $line
*/
protected function parseLine($line)
{
if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) {
$this->testCount = intval($matches[1]);
} elseif (preg_match(self::TEST_DIAGNOSTIC, $line)) {
return;
} elseif (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
$this->results[] = $this->processTestLine(
$matches[1],
isset($matches[2]) ? $matches[2] : '',
isset($matches[3]) ? $matches[3] : null,
isset($matches[4]) ? $matches[4] : null
);
} elseif (preg_match(self::TEST_YAML_START, $line, $matches)) {
$diagnostic = $this->processYamlBlock($matches[1]);
$test = array_pop($this->results);
if (isset($test['message'], $diagnostic['message'])) {
$test['message'] .= PHP_EOL . $diagnostic['message'];
unset($diagnostic['message']);
}
$this->results[] = array_replace($test, $diagnostic);
} else {
throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line));
}
$matches = array();
$totalTests = 0;
if (preg_match(self::TEST_COUNTS_PATTERN, $lines[0], $matches)) {
array_shift($lines);
$totalTests = (int) $matches[2];
}
if (isset($lines[count($lines) - 1]) &&
preg_match(self::TEST_COUNTS_PATTERN, $lines[count($lines) - 1], $matches)) {
array_pop($lines);
$totalTests = (int) $matches[2];
}
$rtn = $this->processTestLines($lines);
if ($totalTests != count($rtn)) {
throw new \Exception(Lang::get('tap_error'));
}
return $rtn;
}
/**
* Process the individual test lines within a TAP string.
* @param $lines
* Process an individual test line.
*
* @param string $result
* @param string $message
* @param string $directive
* @param string $reason
*
* @return array
*/
protected function processTestLines($lines)
protected function processTestLine($result, $message, $directive, $reason)
{
$rtn = array();
$test = array(
'pass' => true,
'message' => $message,
'severity' => 'success',
);
foreach ($lines as $line) {
$matches = array();
if (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
$ok = ($matches[1] == 'ok' ? true : false);
if (!$ok) {
$this->failures++;
}
$item = array(
'pass' => $ok,
'suite' => $matches[2],
'test' => $matches[3],
);
$rtn[] = $item;
} elseif (preg_match(self::TEST_SKIP_PATTERN, $line, $matches)) {
$rtn[] = array('message' => 'SKIP');
} elseif (preg_match(self::TEST_MESSAGE_PATTERN, $line, $matches)) {
$rtn[count($rtn) - 1]['message'] = $matches[1];
}
if ($result !== 'ok') {
$test['pass'] = false;
$test['severity'] = substr($message, 0, 6) === 'Error:' ? 'error' : 'fail';
$this->failures++;
}
return $rtn;
if ($directive) {
$test = $this->processDirective($test, $directive, $reason);
}
return $test;
}
/** Process an indented Yaml block.
*
* @param string $indent The block indentation to ignore.
*
* @return array The processed Yaml content.
*/
protected function processYamlBlock($indent)
{
$startLine = $this->lineNumber+1;
$endLine = $indent.'...';
$yamlLines = array();
do {
$line = $this->nextLine();
if ($line === false) {
throw new Exception(Lang::get('tap_error_endless_yaml', $startLine));
} elseif ($line === $endLine) {
break;
}
$yamlLines[] = substr($line, strlen($indent));
} while (true);
return Yaml::parse(join("\n", $yamlLines));
}
/** Process a TAP directive
*
* @param array $test
* @param string $directive
* @param string $reason
* @return array
*/
protected function processDirective($test, $directive, $reason)
{
$test['severity'] = strtolower($directive) === 'skip' ? 'skipped' : 'todo';
if (!empty($reason)) {
if (!empty($test['message'])) {
$test['message'] .= ', '.$test['severity'].': ';
}
$test['message'] .= $reason;
}
return $test;
}
/**

View file

@ -5,22 +5,254 @@ use PHPCI\Plugin\Util\TapParser;
class TapParserTest extends \PHPUnit_Framework_TestCase
{
public function testSkipped()
public function testSimple()
{
$content = <<<TAP
Leading garbage !
TAP version 13
ok 1 - SomeTest::testAnother
not ok
1..2
Trailing garbage !
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array('pass' => true, 'severity' => 'success', 'message' => 'SomeTest::testAnother'),
array('pass' => false, 'severity' => 'fail', 'message' => ''),
), $result);
$this->assertEquals(1, $parser->getTotalFailures());
}
/**
* @expectedException \Exception
* @expectedExceptionMessageRegExp /No TAP/
*/
public function testNoTapData()
{
$content = <<<TAP
Only garbage !
TAP;
$parser = new TapParser($content);
$parser->parse();
}
/**
* @expectedException \Exception
* @expectedExceptionMessageRegExp /Duplicated TAP/
*/
public function testDuplicateOutput()
{
$content = <<<TAP
TAP version 13
TAP version 13
ok 1 - SomeTest::testAnother
ok 1 - SomeTest::testAnother
not ok - Failure: SomeTest::testAnother
not ok - Failure: SomeTest::testAnother
not ok 3 - Error: SomeTest::testAnother
not ok 3 - Error: SomeTest::testAnother
1..3
1..3
TAP;
$parser = new TapParser($content);
$parser->parse();
}
public function testSuiteAndTest()
{
$content = <<<TAP
TAP version 13
ok 1 - SomeTest::testAnother
ok 2 - # SKIP
not ok - Failure: SomeTest::testAnother
not ok 3 - Error: SomeTest::testAnother
1..3
Trailing garbage !
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array('pass' => true, 'severity' => 'success', 'message' => 'SomeTest::testAnother',),
array('pass' => false, 'severity' => 'fail', 'message' => 'Failure: SomeTest::testAnother'),
array('pass' => false, 'severity' => 'error', 'message' => 'Error: SomeTest::testAnother'),
), $result);
$this->assertEquals(2, $parser->getTotalFailures());
}
public function testSkipped()
{
$content = <<<TAP
TAP version 13
ok 1 - # SKIP
ok 2 - # SKIP foobar
ok 3 - foo # SKIP bar
1..3
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array('pass' => true, 'severity' => 'skipped', 'message' => ''),
array('pass' => true, 'severity' => 'skipped', 'message' => 'foobar'),
array('pass' => true, 'severity' => 'skipped', 'message' => 'foo, skipped: bar'),
), $result);
$this->assertEquals(0, $parser->getTotalFailures());
}
public function testTodo()
{
$content = <<<TAP
TAP version 13
ok 1 - SomeTest::testAnother # TODO really implement this test
ok 2 - # TODO really implement this test
ok 3 - this is a message # TODO really implement this test
ok 4 - # TODO
1..4
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array('pass' => true, 'severity' => 'todo', 'message' => 'SomeTest::testAnother, todo: really implement this test'),
array('pass' => true, 'severity' => 'todo', 'message' => 'really implement this test'),
array('pass' => true, 'severity' => 'todo', 'message' => 'this is a message, todo: really implement this test'),
array('pass' => true, 'severity' => 'todo', 'message' => ''),
), $result);
$this->assertEquals(0, $parser->getTotalFailures());
}
public function testYamlDiagnostic()
{
// From https://phpunit.de/manual/current/en/logging.html#logging.tap
$content = <<<TAP
TAP version 13
not ok 1 - FOO
---
message: BAR
...
1..1
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array(
'pass' => false,
'severity' => 'fail',
'message' => 'FOO' . PHP_EOL . 'BAR',
),
), $result);
$this->assertEquals(1, $parser->getTotalFailures());
}
public function testFailureAndError()
{
// From https://phpunit.de/manual/current/en/logging.html#logging.tap
$content = <<<TAP
TAP version 13
not ok 1 - Failure: testFailure::FailureErrorTest
not ok 2 - Error: testError::FailureErrorTest
1..2
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(array(
array('pass' => true, 'suite' => 'SomeTest', 'test' => 'testAnother'),
array('message' => 'SKIP'),
array(
'pass' => false,
'severity' => 'fail',
'message' => 'Failure: testFailure::FailureErrorTest',
),
array(
'pass' => false,
'severity' => 'error',
'message' => 'Error: testError::FailureErrorTest',
)
), $result);
$this->assertEquals(2, $parser->getTotalFailures());
}
/**
* @expectedException \Exception
*/
public function testGarbage()
{
$content = "Garbage !";
$parser = new TapParser($content);
$parser->parse();
}
/**
* @expectedException \Exception
*/
public function testInvalidTestCount()
{
$content = <<<TAP
TAP version 13
ok 1 - SomeTest::testAnother
not ok
1..5
TAP;
$parser = new TapParser($content);
$parser->parse();
}
/**
* @expectedException \Exception
*/
public function testEndlessYaml()
{
$content = <<<TAP
TAP version 13
ok 1 - SomeTest::testAnother
---
1..1
TAP;
$parser = new TapParser($content);
$parser->parse();
}
public function testCodeception()
{
$content = <<< TAP
TAP version 13
ok 1 - try to access the dashboard as a guest (Auth/GuestAccessDashboardAndRedirectCept)
ok 2 - see the login page (Auth/SeeLoginCept)
ok 3 - click forgot password and see the email form (Auth/SeeLoginForgotPasswordCept)
ok 4 - see powered by runmybusiness branding (Auth/ShouldSeePoweredByBrandingCept)
ok 5 - submit invalid credentials (Auth/SubmitLoginAndFailCept)
ok 6 - submit valid credentials and see the dashboard (Auth/SubmitLoginAndSucceedCept)
1..6
TAP;
$parser = new TapParser($content);
$result = $parser->parse();
$this->assertEquals(
array(
array('pass' => true, 'severity' => 'success', 'message' => 'try to access the dashboard as a guest (Auth/GuestAccessDashboardAndRedirectCept)'),
array('pass' => true, 'severity' => 'success', 'message' => 'see the login page (Auth/SeeLoginCept)'),
array('pass' => true, 'severity' => 'success', 'message' => 'click forgot password and see the email form (Auth/SeeLoginForgotPasswordCept)'),
array('pass' => true, 'severity' => 'success', 'message' => 'see powered by runmybusiness branding (Auth/ShouldSeePoweredByBrandingCept)'),
array('pass' => true, 'severity' => 'success', 'message' => 'submit invalid credentials (Auth/SubmitLoginAndFailCept)'),
array('pass' => true, 'severity' => 'success', 'message' => 'submit valid credentials and see the dashboard (Auth/SubmitLoginAndSucceedCept)'),
),
$result
);
$this->assertEquals(0, $parser->getTotalFailures());
}
}

View file

@ -84,4 +84,11 @@
padding: 0;
font-size: inherit;
line-height: inherit;
}
}
#phpunit-data th div { margin: 0 0.5em; }
#phpunit-data .success td { background: none; color: #00a65a; }
#phpunit-data .fail td { background: none; color: #f56954; }
#phpunit-data .error td { background: none; color: #f56954; }
#phpunit-data .skipped td { background: none; color: #e08e0b; }
#phpunit-data .todo td { background: none; color: #00c0ef; }

View file

@ -6,6 +6,13 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
displayOnUpdate: false,
box: true,
rendered: false,
statusMap: {
success : 'ok',
fail: 'remove',
error: 'warning-sign',
todo: 'info-sign',
skipped: 'exclamation-sign'
},
register: function() {
var self = this;
@ -21,6 +28,11 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
query();
}
});
$(document).on('click', '#phpunit-data .test-toggle', function(ev) {
var input = $(ev.target);
$('#phpunit-data tbody ' + input.data('target')).toggle(input.prop('checked'));
});
},
render: function() {
@ -28,7 +40,7 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
return $('<div class="table-responsive"><table class="table" id="phpunit-data">' +
'<thead>' +
'<tr>' +
' <th>'+Lang.get('test')+'</th>' +
' <th>'+Lang.get('test_message')+'</th>' +
'</tr>' +
'</thead><tbody></tbody></table></div>');
},
@ -43,7 +55,9 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
this.lastData = e.queryData;
var tests = this.lastData[0].meta_value;
var thead = $('#phpunit-data thead tr');
var tbody = $('#phpunit-data tbody');
thead.empty().append('<th>'+Lang.get('test_message')+'</th>');
tbody.empty();
if (tests.length == 0) {
@ -51,24 +65,74 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
return;
}
var counts = { success: 0, fail: 0, error: 0, skipped: 0, todo: 0 }, total = 0;
for (var i in tests) {
var row = $('<tr>' +
'<td><strong>'+tests[i].suite+'' +
'::'+tests[i].test+'</strong><br>' +
''+(tests[i].message || '')+'</td>' +
'</tr>');
if (!tests[i].pass) {
row.addClass('danger');
} else {
row.addClass('success');
}
tbody.append(row);
var severity = tests[i].severity || 'success',
message = tests[i].message || ('<i>' + Lang.get('test_no_message') + '</i>');
counts[severity]++;
total++;
tbody.append(
'<tr class="'+ severity + '">' +
'<td colspan="3">' +
'<div>' + message + '</div>' +
(tests[i].data ? '<div>' + this.repr(tests[i].data) + '</div>' : '') +
'</td>' +
'</tr>'
);
}
var checkboxes = $('<th/>');
thead.append(checkboxes).append('<th>' + Lang.get('test_total', total) + '</th>');
for (var key in counts) {
var count = counts[key];
if(count > 0) {
checkboxes.append(
'<div style="float:left" class="' + key + '"><input type="checkbox" class="test-toggle" data-target=".' + key + '" ' +
(key !== 'success' ? ' checked' : '') + '/>&nbsp;' +
Lang.get('test_'+key, count)+ '</div> '
);
}
}
tbody.find('.success').hide();
$('#build-phpunit-errors').show();
},
repr: function(data)
{
switch(typeof(data)) {
case 'boolean':
return '<span class="boolean">' + (data ? 'true' : 'false') + '</span>';
case 'string':
return '<span class="string">"' + data + '"</span>';
case 'undefined': case null:
return '<span class="null">null</span>';
case 'object':
var rows = [];
if(data instanceof Array) {
for(var i in data) {
rows.push('<tr><td colspan="3">' + this.repr(data[i]) + ',</td></tr>');
}
} else {
for(var key in data) {
rows.push(
'<tr>' +
'<td>' + this.repr(key) + '</td>' +
'<td>=&gt;</td>' +
'<td>' + this.repr(data[key]) + ',</td>' +
'</tr>');
}
}
return '<table>' +
'<tr><th colspan="3">array(</th></tr>' +
rows.join('') +
'<tr><th colspan="3">)</th></tr>' +
'</table>';
}
return '???';
}
});