From 096b99cdfc4f6ee17a09aa09c77a650be4877e06 Mon Sep 17 00:00:00 2001 From: Juha Sippola Date: Mon, 21 Sep 2015 15:39:07 +0300 Subject: Qt Metrics 2 (v0.23): Testfunction page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New page to list failed or skipped testrows for selected testfunction in selected testset and configuration. Change-Id: I64e7b7acf1a6001c08d0f11460f063bef279d607 Reviewed-by: Tony Sarajärvi --- non-puppet/qtmetrics2/index.php | 57 ++++- non-puppet/qtmetrics2/src/Database.php | 64 ++++- non-puppet/qtmetrics2/src/Factory.php | 37 ++- non-puppet/qtmetrics2/src/TestrowRun.php | 102 ++++++++ non-puppet/qtmetrics2/src/test/DatabaseTest.php | 44 +++- non-puppet/qtmetrics2/src/test/FactoryTest.php | 38 ++- non-puppet/qtmetrics2/templates/about.html | 2 +- non-puppet/qtmetrics2/templates/testfunction.html | 280 ++++++++++++++++++++++ 8 files changed, 616 insertions(+), 8 deletions(-) create mode 100644 non-puppet/qtmetrics2/src/TestrowRun.php create mode 100644 non-puppet/qtmetrics2/templates/testfunction.html diff --git a/non-puppet/qtmetrics2/index.php b/non-puppet/qtmetrics2/index.php index 545d0e6..8cef994 100644 --- a/non-puppet/qtmetrics2/index.php +++ b/non-puppet/qtmetrics2/index.php @@ -34,7 +34,7 @@ /** * Qt Metrics API - * @since 08-09-2015 + * @since 09-09-2015 * @author Juha Sippola */ @@ -488,8 +488,7 @@ $app->get('/testset/:testset/:project/:conf', function($testset, $project, $conf array('name' => $project, 'link' => $testsetProjectRoute . '/' . $project), array('name' => $conf, 'link' => $confProjectRoute . '/' . urlencode($conf) . '/' . $project) ); - $confProjectRoute = str_replace('/:conf/:testsetproject', '', Slim\Slim::getInstance()->urlFor('conf_testsetproject')); - $testfunctionRoute = 'testfunction'; // TODO: Replace later with $testfunctionRoute = str_replace('/:testfunction', '', Slim\Slim::getInstance()->urlFor('testfunction')); + $testfunctionRoute = str_replace('/:testfunction/:testset/:project/:conf', '', Slim\Slim::getInstance()->urlFor('testfunction')); $app->render('testset_testfunctions.html', array( 'root' => Slim\Slim::getInstance()->urlFor('root'), 'breadcrumb' => $breadcrumb, @@ -522,6 +521,58 @@ $app->get('/testset/:testset/:project/:conf', function($testset, $project, $conf } })->name('testset_testfunctions'); +/** + * UI route: /testfunction/:testfunction/:testset/:project/:conf (GET) + */ + +$app->get('/testfunction/:testfunction/:testset/:project/:conf', function($testfunction, $testset, $project, $conf) use($app) +{ + $testfunction = strip_tags($testfunction); + $testset = strip_tags($testset); + $project = strip_tags($project); + $conf = strip_tags($conf); + if (Factory::checkTestset($testset)) { + $testsetTestfunctionRoute = str_replace('/:testset/:project/:conf', '', Slim\Slim::getInstance()->urlFor('testset_testfunctions')); + $testsetRoute = str_replace('/:testset/:project', '', Slim\Slim::getInstance()->urlFor('testset')); + $testsetProjectRoute = str_replace('/:project', '', Slim\Slim::getInstance()->urlFor('testsetproject')); + $confProjectRoute = str_replace('/:conf/:testsetproject', '', Slim\Slim::getInstance()->urlFor('conf_testsetproject')); + $ini = Factory::conf(); + $breadcrumb = array( + array('name' => 'home', 'link' => Slim\Slim::getInstance()->urlFor('root')), + array('name' => 'overview', 'link' => Slim\Slim::getInstance()->urlFor('overview')), + array('name' => $project, 'link' => $testsetProjectRoute . '/' . $project), + array('name' => $conf, 'link' => $confProjectRoute . '/' . urlencode($conf) . '/' . $project), + array('name' => $testset, 'link' => $testsetTestfunctionRoute . '/' . $testset . '/' . $project . '/' . urlencode($conf)) + ); + $app->render('testfunction.html', array( + 'root' => Slim\Slim::getInstance()->urlFor('root'), + 'breadcrumb' => $breadcrumb, + 'refreshed' => Factory::db()->getDbRefreshed() . ' (GMT)', + 'masterProject' => $ini['master_build_project'], + 'masterState' => $ini['master_build_state'], + 'conf' => $conf, + 'testset' => $testset, + 'testfunction' => $testfunction, + 'projectRuns' => Factory::createProjectRuns( + $ini['master_build_project'], + $ini['master_build_state']), // managed as objects + 'testrowRuns' => Factory::createTestrowRunsInConf( + $testfunction, + $testset, + $project, + $conf, + $ini['master_build_project'], + $ini['master_build_state']) // managed as objects + )); + } else { + $app->render('empty.html', array( + 'root' => Slim\Slim::getInstance()->urlFor('root'), + 'message' => '404 Not Found' + )); + $app->response()->status(404); + } +})->name('testfunction'); + /** * UI route: /sitemap (GET) */ diff --git a/non-puppet/qtmetrics2/src/Database.php b/non-puppet/qtmetrics2/src/Database.php index 3700b78..b8909b0 100644 --- a/non-puppet/qtmetrics2/src/Database.php +++ b/non-puppet/qtmetrics2/src/Database.php @@ -34,7 +34,7 @@ /** * Database class - * @since 08-09-2015 + * @since 09-09-2015 * @author Juha Sippola */ @@ -1198,6 +1198,68 @@ class Database { return $result; } + /** + * Get results for failed and skipped testrows in specified configuration builds and project by branch + * Only the fail/skip and xpass/xfail results are listed + * @param string $testfunction + * @param string $testset + * @param string $testsetProject + * @param string $conf + * @param string $runProject + * @param string $runState + * @return array (string branch, string build_key, string testrow, string result, string timestamp) + */ + public function getTestrowConfResultsByBranch($testfunction, $testset, $testsetProject, $conf, $runProject, $runState) + { + $result = array(); + $query = $this->db->prepare(" + SELECT + branch.name AS branch, + project_run.build_key, + testrow.name AS testrow, + testrow_run.result, + project_run.timestamp + FROM testrow_run + INNER JOIN testrow ON testrow_run.testrow_id = testrow.id + INNER JOIN testfunction_run ON testrow_run.testfunction_run_id = testfunction_run.id + INNER JOIN testfunction ON testfunction_run.testfunction_id = testfunction.id + INNER JOIN testset_run ON testfunction_run.testset_run_id = testset_run.id + INNER JOIN testset ON testset_run.testset_id = testset.id + INNER JOIN project ON testset.project_id = project.id + INNER JOIN conf_run ON testset_run.conf_run_id = conf_run.id + INNER JOIN conf ON conf_run.conf_id = conf.id + INNER JOIN project_run ON conf_run.project_run_id = project_run.id + INNER JOIN branch ON project_run.branch_id = branch.id + WHERE + (testrow_run.result LIKE '%fail' OR testrow_run.result LIKE '%skip' OR testrow_run.result LIKE '%x%') AND + testfunction.name = ? AND + testset.name = ? AND + project.name = ? AND + conf.name = ? AND + project_run.project_id = (SELECT id FROM project WHERE name = ?) AND + project_run.state_id = (SELECT id FROM state WHERE name = ?) + ORDER BY branch.name, testrow.name, project_run.build_key DESC; + "); + $query->execute(array( + $testfunction, + $testset, + $testsetProject, + $conf, + $runProject, + $runState + )); + while($row = $query->fetch(PDO::FETCH_ASSOC)) { + $result[] = array( + 'branch' => $row['branch'], + 'buildKey' => $row['build_key'], + 'testrow' => $row['testrow'], + 'result' => $row['result'], + 'timestamp' => $row['timestamp'] + ); + } + return $result; + } + /** * Get the timestamp when database last refreshed * @return string (timestamp) diff --git a/non-puppet/qtmetrics2/src/Factory.php b/non-puppet/qtmetrics2/src/Factory.php index fe35142..cabecac 100644 --- a/non-puppet/qtmetrics2/src/Factory.php +++ b/non-puppet/qtmetrics2/src/Factory.php @@ -34,7 +34,7 @@ /** * Factory class - * @since 08-09-2015 + * @since 09-09-2015 * @author Juha Sippola */ @@ -47,6 +47,7 @@ require_once 'ConfRun.php'; require_once 'Testset.php'; require_once 'TestsetRun.php'; require_once 'TestfunctionRun.php'; +require_once 'TestrowRun.php'; class Factory { @@ -437,6 +438,40 @@ class Factory { return $objects; } + /** + * Create TestrowRun objects in a configuration for those in database + * @param string $testfunction + * @param string $testset + * @param string $testsetProject + * @param string $conf + * @param string $runProject + * @param string $runState + * @return array TestfunctionRun objects + */ + public static function createTestrowRunsInConf($testfunction, $testset, $testsetProject, $conf, $runProject, $runState) + { + $objects = array(); + $dbEntries = self::db()->getTestrowConfResultsByBranch($testfunction, $testset, $testsetProject, $conf, $runProject, $runState); + foreach($dbEntries as $entry) { + $obj = new TestrowRun( + $entry['testrow'], + $testfunction, + $testset, + $testsetProject, + $runProject, + $entry['branch'], + $runState, + $entry['buildKey'], + $conf, + TestrowRun::stripResult($entry['result']), + TestrowRun::isBlacklisted($entry['result']), + $entry['timestamp'] + ); + $objects[] = $obj; + } + return $objects; + } + /** * Get the date that was n days before the last database refresh date. * @param int $days diff --git a/non-puppet/qtmetrics2/src/TestrowRun.php b/non-puppet/qtmetrics2/src/TestrowRun.php new file mode 100644 index 0000000..cda6a1e --- /dev/null +++ b/non-puppet/qtmetrics2/src/TestrowRun.php @@ -0,0 +1,102 @@ +name = $name; + $this->testfunctionName = $testfunctionName; + } + + /** + * Get name of the testrow. + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get short name of the testfunction. + * @return string + */ + public function getShortName() + { + if (strlen($this->name) > parent::SHORT_NAME_LENGTH) + return substr($this->name, 0, parent::SHORT_NAME_LENGTH - 10) . '...' . substr($this->name, -7); + else + return $this->name; + } + + /** + * Get name of the testfunction. + * @return string + */ + public function getTestfunctionName() + { + return $this->testfunctionName; + } + +} + +?> diff --git a/non-puppet/qtmetrics2/src/test/DatabaseTest.php b/non-puppet/qtmetrics2/src/test/DatabaseTest.php index 7ce99df..06ec0ef 100644 --- a/non-puppet/qtmetrics2/src/test/DatabaseTest.php +++ b/non-puppet/qtmetrics2/src/test/DatabaseTest.php @@ -38,7 +38,7 @@ require_once(__DIR__.'/../Factory.php'); * Database unit test class * Some of the tests require the test data as inserted into database with qtmetrics_insert.sql * @example To run (in qtmetrics root directory): php /phpunit.phar ./src/test - * @since 08-09-2015 + * @since 09-09-2015 * @author Juha Sippola */ @@ -984,6 +984,48 @@ class DatabaseTest extends PHPUnit_Framework_TestCase ); } + /** + * Test getTestrowConfResultsByBranch + * @dataProvider testGetTestrowConfResultsByBranchData + */ + public function testGetTestrowConfResultsByBranch($testfunction, $testset, $testsetProject, $conf, $runProject, $runState, $exp_branch, $exp_testrow, $exp_key, $has_data) + { + $branches = array(); + $keys = array(); + $testrows = array(); + $db = Factory::db(); + $result = $db->getTestrowConfResultsByBranch($testfunction, $testset, $testsetProject, $conf, $runProject, $runState); + foreach($result as $row) { + $this->assertArrayHasKey('branch', $row); + $this->assertArrayHasKey('buildKey', $row); + $this->assertArrayHasKey('testrow', $row); + $this->assertArrayHasKey('result', $row); + $this->assertArrayHasKey('timestamp', $row); + $branches[] = $row['branch']; + $keys[] = $row['buildKey']; + $testrows[] = $row['testrow']; + } + if ($has_data) { + $this->assertNotEmpty($result); + $this->assertContains($exp_branch, $branches); + $this->assertContains($exp_key, $keys); + $this->assertContains($exp_testrow, $testrows); + } else { + $this->assertEmpty($result); + } + } + public function testGetTestrowConfResultsByBranchData() + { + return array( + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', 'monospace', '1346', 1), // xpass + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', 'sans-serif', '1346', 1), // xfail + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', 'serif', '1346', 1), // bskip + array('binaryAscii', 'tst_qftp', 'qtbase', 'linux-g++_developer-build_qtnamespace_qtlibinfix_Ubuntu_11.10_x64', 'Qt5', 'state', 'dev', 'WithSocks5ProxyAndSession', '1023', 1), // fail + array('httpServerFiles', 'tst_networkselftest', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', '', '', '', 0), // no fail or skip + array('defaultFamily', 'tst_qfont', 'qtbase', 'invalid', 'Qt5', 'state', '', '', '', 0) + ); + } + /** * Test getDbRefreshed */ diff --git a/non-puppet/qtmetrics2/src/test/FactoryTest.php b/non-puppet/qtmetrics2/src/test/FactoryTest.php index 5e24833..0987cb2 100644 --- a/non-puppet/qtmetrics2/src/test/FactoryTest.php +++ b/non-puppet/qtmetrics2/src/test/FactoryTest.php @@ -37,7 +37,7 @@ require_once(__DIR__.'/../Factory.php'); /** * Factory unit test class * @example To run (in qtmetrics root directory): php /phpunit.phar ./src/test - * @since 08-09-2015 + * @since 09-09-2015 * @author Juha Sippola */ @@ -458,6 +458,42 @@ class FactoryTest extends PHPUnit_Framework_TestCase ); } + /** + * Test createTestrowRunsInConf + * @dataProvider testCreateTestrowRunsInConfData + */ + public function testCreateTestrowRunsInConf($testfunction, $testset, $testsetProject, $conf, $runProject, $runState, $exp_branch, $exp_buildKey, $exp_testrow, $has_data) + { + $branches = array(); + $buildKeys = array(); + $testrows = array(); + $runs = Factory::createTestrowRunsInConf($testfunction, $testset, $testsetProject, $conf, $runProject, $runState); + foreach($runs as $run) { + $this->assertTrue($run instanceof TestfunctionRun); + $branches[] = $run->getBranchName(); + $buildKeys[] = $run->getBuildKey(); + $testrows[] = $run->getName(); + } + if ($has_data) { + $this->assertContains($exp_branch, $branches); + $this->assertContains($exp_buildKey, $buildKeys); + $this->assertContains($exp_testrow, $testrows); + } else { + $this->assertEmpty($runs); + } + } + public function testCreateTestrowRunsInConfData() + { + return array( + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', '1346', 'monospace', 1), // xpass + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', '1346', 'sans-serif', 1), // xfail + array('defaultFamily', 'tst_qfont', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', 'stable', '1346', 'serif', 1), // bskip + array('binaryAscii', 'tst_qftp', 'qtbase', 'linux-g++_developer-build_qtnamespace_qtlibinfix_Ubuntu_11.10_x64', 'Qt5', 'state', 'dev', '1023', 'WithSocks5ProxyAndSession', 1), // fail + array('httpServerFiles', 'tst_networkselftest', 'qtbase', 'macx-clang_developer-build_OSX_10.8', 'Qt5', 'state', '', '', '', 0), // no fail or skip + array('defaultFamily', 'tst_qfont', 'qtbase', 'invalid', 'Qt5', 'state', '', '', '', 0) + ); + } + /** * Test getSinceDate * @dataProvider testGetSinceDateData diff --git a/non-puppet/qtmetrics2/templates/about.html b/non-puppet/qtmetrics2/templates/about.html index 7c37008..5f83d77 100644 --- a/non-puppet/qtmetrics2/templates/about.html +++ b/non-puppet/qtmetrics2/templates/about.html @@ -43,4 +43,4 @@

This is Qt Metrics revision 2 with redesigned UI and database.

These pages are still under construction and therefore the views and functionality is limited.

See the backlog for development items currently identified or in progress.

-

Version 0.22 (9-Sep-2015)

+

Version 0.23 (9-Sep-2015)

diff --git a/non-puppet/qtmetrics2/templates/testfunction.html b/non-puppet/qtmetrics2/templates/testfunction.html new file mode 100644 index 0000000..6c1f616 --- /dev/null +++ b/non-puppet/qtmetrics2/templates/testfunction.html @@ -0,0 +1,280 @@ +{# +############################################################################# +## +## Copyright (C) 2015 The Qt Company Ltd. +## Contact: http://www.qt.io/licensing/ +## +## This file is part of the Quality Assurance module of the Qt Toolkit. +## +## $QT_BEGIN_LICENSE:LGPL21$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see http://www.qt.io/terms-conditions. For further +## information use the contact form at http://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 2.1 or version 3 as published by the Free +## Software Foundation and appearing in the file LICENSE.LGPLv21 and +## LICENSE.LGPLv3 included in the packaging of this file. Please review the +## following information to ensure the GNU Lesser General Public License +## requirements will be met: https://www.gnu.org/licenses/lgpl.html and +## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +## +## As a special exception, The Qt Company gives you certain additional +## rights. These rights are described in The Qt Company LGPL Exception +## version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +## +## $QT_END_LICENSE$ +## +############################################################################# + +/** + * Testfunction page + * @since 09-09-2015 + * @author Juha Sippola + */ + +#} + +{% include "header.html" %} + +{# projectRuns as ProjectRun objects +/** + * @var ProjectRun[] projectRuns + */ +#} + +{# testrowRuns as TestrowRun objects +/** + * @var TestrowRun[] testrowRuns + */ +#} + + + +
+
+ +
+ +{# Check if any runs available #} +{% set runsAvailable = 0 %} +{% for run in testrowRuns %} +{% set runsAvailable = 1 %} +{% endfor %} + +{##### Title #####} + +

+{{ testfunction }} + +{{ refreshed }} +

+ +{##### Info well #####} + +
+
+ Testfunction
+
    +
  • Testrow Results in Branches shows the {{ testfunction }} failed and skipped results in configuration +{{ conf }} by branch on {{ masterProject }} {{ masterState }} builds +
      +
    • flags: b = blacklisted flag set for the testrow on the latest build shown
    • +
    • results: = {{ constant('TestrowRun::RESULT_FAILURE') }}, + = {{ constant('TestrowRun::RESULT_SUCCESS_UNEXPECTED') }}, + = {{ constant('TestrowRun::RESULT_FAILURE_EXPECTED') }}, + = {{ constant('TestrowRun::RESULT_SKIP') }}
    • +
    +
  • +
  • Details on the runs are available as tooltip on result icon
  • +
+
+
+ +{% if runsAvailable %} + +{##### Results in Branches #####} + +
+
+

Testrow Results in Branches (failures and skipped only)

+
+
+ +{# Get branches #} +{% set branches = [] %} +{% for run in projectRuns %} +{% if run.getBranchName not in branches %} +{% set branches = branches|merge([run.getBranchName]) %} +{% endif %} +{% endfor %} + +{# Loop all the branches #} +{% for branch in branches %} + +{# Get all build keys, dates and log links #} +{% set buildKey = '' %} +{% set buildKeys = [] %} +{% set dates = [] %} +{% for run in projectRuns %} +{% if run.getBranchName == branch %} +{% if buildKey != run.getBuildKey %} +{% set buildKey = run.getBuildKey %} +{% set buildKeys = buildKeys|merge([run.getBuildKey]) %} +{% set dates = dates|merge([run.getTimestamp]) %} +{% endif %} +{% endif %} +{% endfor %} + +{# Check if testrow run for this branch #} +{% set testrowBranch = 0 %} +{% for run in testrowRuns if run.getBranchName == branch %} +{% set testrowBranch = 1 %} +{% endfor %} + +{# Show branch if testrow run for it #} +{% if testrowBranch %} +
+
+

{{ branch }}

+
+
+
+ + + + + +{% for key, buildKey in buildKeys %} + +{% endfor %} + + + +{% set testrowPrev = '' %} +{% set buildKeyIndexPrinted = -1 %} +{% set buildKeyFound = 0 %} +{% for run in testrowRuns if run.getBranchName == branch %} + +{# New row for each testrow #} +{% if testrowPrev != run.getName %} +{# Close previous row #} +{% if testrowPrev != '' %} +{# Fill empty cells at the end of the row #} +{% for key, buildKey in buildKeys %} +{% if key > buildKeyIndexPrinted %} + +{% endif %} +{% endfor %} + +{% endif %} + + + +{# Flags for the latest build #} + +{% set buildKeyIndexPrinted = -1 %} +{% endif %} + +{# Result per build key #} +{% set buildKeyFound = 0 %} +{% for key, buildKey in buildKeys %} +{# Print each column only once (checked based on column index key and buildKeyFound flag) #} +{% if key > buildKeyIndexPrinted and not buildKeyFound %} +{% if buildKey == run.getBuildKey %} +{# Print result #} +{% if run.getResult == constant('TestfunctionRun::RESULT_FAILURE') %} +{% set resultIcon = 'glyphicon glyphicon-remove red' %} +{% elseif run.getResult == constant('TestfunctionRun::RESULT_FAILURE_EXPECTED') %} +{% set resultIcon = 'glyphicon glyphicon-remove-sign green' %} +{% elseif run.getResult == constant('TestfunctionRun::RESULT_SUCCESS_UNEXPECTED') %} +{% set resultIcon = 'glyphicon glyphicon-ok-sign red' %} +{% elseif run.getResult == constant('TestfunctionRun::RESULT_SKIP') %} +{% set resultIcon = 'glyphicon glyphicon-ban-circle gray' %} +{% else %} +{% set resultIcon = '' %} +{% endif %} +{% if (run.getDuration / 10) > 60 %} +{% set durationFormatted = ' (00:' ~ ((run.getDuration/10)|round)|date("i:s") ~ ')' %} +{% else %} +{% set durationFormatted = '' %} +{% endif %} + +{% set buildKeyFound = 1 %} +{% else %} +{# Print empty cell #} + +{% endif %} +{% set buildKeyIndexPrinted = key %} +{% endif %}{# key #} +{% endfor %}{# key #} +{% set testrowPrev = run.getName %} +{% endfor %}{# run #} + +{# Close last row (also fill empty cells at the end of the row) #} +{% for key, buildKey in buildKeys %} +{% if key > buildKeyIndexPrinted %} + +{% endif %} +{% endfor %}{# key #} + + +
{{ testfunction }}flags +{% if buildKey|length > 6 %} +{{ buildKey|slice(0, 4) }}...
+{% else %} +{{ buildKey }}
+{% endif %} +{{ dates[key]|date("m-d") }} +
+{% if run.getName|length > constant('TestrowRun::SHORT_NAME_LENGTH') %} +{{ run.getShortName }} +{% else %} +{{ run.getName }} +{% endif %} + +{% if run.getBlacklisted %} +b +{% endif %} + + +
+
{# .table-responsive #} +
{# .panel-body #} +
{# .panel... #} +{% endif %}{# testrowBranch #} +{% endfor %}{# branch #} + +{% else %}{# runsAvailable #} + +{% endif %}{# runsAvailable #} +
{# .col... #} +
{# .row #} +
{# /container-fluid #} + +{% include "footer.html" %} + +{# Local scripts for this page #} + + +{% include "close.html" %} -- cgit v1.2.3