diff options
author | Juha Sippola <juhasippola@outlook.com> | 2015-09-23 12:23:59 +0300 |
---|---|---|
committer | Tony Sarajärvi <tony.sarajarvi@theqtcompany.com> | 2015-09-23 09:40:41 +0000 |
commit | 7054f29267bb4f3dec16b60217bbf28d61435aaa (patch) | |
tree | a8649b2e7a7933a9891ec1abacd67923b495b5c7 | |
parent | 1f60410f51f86287ebe9c8694d92df139e830b6f (diff) |
Qt Metrics 2 (v0.34): Bpassed test rows for testset
New page to list blacklisted passed test rows since
a specific date for one testset. This can be opened
from the testset page via a button.
Change-Id: I82acf6d03ad860f61360f8ede18787cfd9eb44aa
Reviewed-by: Tony Sarajärvi <tony.sarajarvi@theqtcompany.com>
-rw-r--r-- | non-puppet/qtmetrics2/index.php | 74 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/scripts/ajax.js | 27 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/Database.php | 70 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/Factory.php | 30 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/Testrow.php | 107 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/test/DatabaseTest.php | 42 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/test/FactoryTest.php | 25 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/src/test/TestrowTest.php | 156 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/about.html | 4 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/testfunction.html | 11 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/testfunctions_bpass.html | 7 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/testfunctions_bpass_data.html | 138 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/testrows_bpass.html | 111 | ||||
-rw-r--r-- | non-puppet/qtmetrics2/templates/testset.html | 8 |
14 files changed, 728 insertions, 82 deletions
diff --git a/non-puppet/qtmetrics2/index.php b/non-puppet/qtmetrics2/index.php index 98bb7a3..4156976 100644 --- a/non-puppet/qtmetrics2/index.php +++ b/non-puppet/qtmetrics2/index.php @@ -34,7 +34,7 @@ /** * Qt Metrics API - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -518,7 +518,10 @@ $app->get('/data/test/bpassed', function() use($app) 'testsetRoute' => Slim\Slim::getInstance()->urlFor('root') . 'testset', 'lastDays' => $ini['blacklisted_pass_last_days'], 'sinceDate' => $since, - 'testfunctions' => Factory::createTestfunctions( + 'list' => 'functions', + 'testset' => '', + 'project' => '', + 'tests' => Factory::createTestfunctions( Factory::LIST_BPASSES, '', '', @@ -570,13 +573,15 @@ $app->get('/data/test/bpassed/:testset/:project', function($testset, $project) u $ini = Factory::conf(); $days = intval($ini['blacklisted_pass_last_days']) - 1; $since = Factory::getSinceDate($days); + $testsetRoute = str_replace('/:testset/:project', '', Slim\Slim::getInstance()->urlFor('testset')); $app->render('testfunctions_bpass_data.html', array( - 'testsetRoute' => Slim\Slim::getInstance()->urlFor('root') . 'testset', + 'testsetRoute' => $testsetRoute, 'lastDays' => $ini['blacklisted_pass_last_days'], 'sinceDate' => $since, + 'list' => 'functions', 'testset' => $testset, 'project' => $project, - 'testfunctions' => Factory::createTestfunctions( + 'tests' => Factory::createTestfunctions( Factory::LIST_BPASSES, $testset, $project, @@ -586,6 +591,65 @@ $app->get('/data/test/bpassed/:testset/:project', function($testset, $project) u }); /** + * UI route: /test/bpassed/testrows/:testset/:project (GET) + */ + +$app->get('/test/bpassed/testrows/:testset/:project', function($testset, $project) use($app) +{ + $ini = Factory::conf(); + $dbStatus = Factory::db()->getDbRefreshStatus(); + if (Factory::checkTestset($testset)) { + $days = intval($ini['blacklisted_pass_last_days']) - 1; + $since = Factory::getSinceDate($days); + $testsetRoute = str_replace('/:testset/:project', '', Slim\Slim::getInstance()->urlFor('testset')); + $breadcrumb = array( + array('name' => 'home', 'link' => Slim\Slim::getInstance()->urlFor('root')), + array('name' => $testset, 'link' => $testsetRoute . '/' . $testset . '/' . $project) + ); + $app->render('testrows_bpass.html', array( + 'root' => Slim\Slim::getInstance()->urlFor('root'), + 'dbStatus' => $dbStatus, + 'refreshed' => $dbStatus['refreshed'] . ' (GMT)', + 'breadcrumb' => $breadcrumb, + 'lastDays' => $ini['blacklisted_pass_last_days'], + 'sinceDate' => $since, + 'testset' => $testset, + 'project' => $project, + 'masterProject' => $ini['master_build_project'], + 'masterState' => $ini['master_build_state'] + )); + } else { + $app->render('empty.html', array( + 'root' => Slim\Slim::getInstance()->urlFor('root'), + 'dbStatus' => $dbStatus, + 'message' => '404 Not Found' + )); + $app->response()->status(404); + } +})->name('bpassedtestsetTestrows'); + +$app->get('/data/test/bpassed/testrows/:testset/:project', function($testset, $project) use($app) +{ + $ini = Factory::conf(); + $days = intval($ini['blacklisted_pass_last_days']) - 1; + $since = Factory::getSinceDate($days); + $testfunctionRoute = str_replace('/:testfunction/:testset/:project/:conf', '', Slim\Slim::getInstance()->urlFor('testfunction')); + $app->render('testfunctions_bpass_data.html', array( + 'testfunctionRoute' => $testfunctionRoute, + 'lastDays' => $ini['blacklisted_pass_last_days'], + 'sinceDate' => $since, + 'list' => 'rows', + 'testset' => $testset, + 'project' => $project, + 'tests' => Factory::createTestrows( + $testset, + $project, + $ini['master_build_project'], + $ini['master_build_state']) // managed as objects + )); +}); + +/** * UI route: /testset/:testset/:project (GET) */ @@ -602,6 +666,7 @@ $app->get('/testset/:testset/:project', function($testset, $project) use($app) $testsetTestfunctionsRoute = str_replace('/:testset/:project/:conf', '', Slim\Slim::getInstance()->urlFor('testset_testfunctions')); $testsetProjectRoute = str_replace('/:project', '', Slim\Slim::getInstance()->urlFor('testsetproject')); $bpassedTestsetRoute = str_replace('/:testset/:project', '', Slim\Slim::getInstance()->urlFor('bpassedtestset')); + $bpassedtestsetTestrowsRoute = str_replace('/:testset/:project', '', Slim\Slim::getInstance()->urlFor('bpassedtestsetTestrows')); $app->render('testset.html', array( 'root' => Slim\Slim::getInstance()->urlFor('root'), 'dbStatus' => $dbStatus, @@ -610,6 +675,7 @@ $app->get('/testset/:testset/:project', function($testset, $project) use($app) 'testsetTestfunctionsRoute' => $testsetTestfunctionsRoute, 'testsetProjectRoute' => $testsetProjectRoute, 'bpassedTestsetRoute' => $bpassedTestsetRoute, + 'bpassedtestsetTestrowsRoute' => $bpassedtestsetTestrowsRoute, 'lastDaysFailures' => $ini['top_failures_last_days'], 'lastDaysFlaky' => $ini['flaky_testsets_last_days'], 'sinceDateFailures' => Factory::getSinceDate(intval($ini['top_failures_last_days']) - 1), diff --git a/non-puppet/qtmetrics2/scripts/ajax.js b/non-puppet/qtmetrics2/scripts/ajax.js index 7851819..294194b 100644 --- a/non-puppet/qtmetrics2/scripts/ajax.js +++ b/non-puppet/qtmetrics2/scripts/ajax.js @@ -34,20 +34,22 @@ /** * Ajax route calls - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ $(function () { - // Get all div ids on a page to call correct routes var div; var divs = []; + var project; + var testset; + var url; + + // Get all div ids on a page to call correct routes $(".container-fluid").find("div").each(function(){ divs.push(this.id); }); // Testset project / latest status - var project; - var testset; if ($.inArray('testset_project_data_latest', divs) > -1) { project = $('#project').html(); $.ajax({ @@ -118,7 +120,6 @@ $(function () { if ($.inArray('testfunctions_blacklisted_passed_data', divs) > -1) { testset = $('#testset').html(); project = $('#project').html(); - var url; if (testset === '') { url = "data/test/bpassed"; } else { @@ -135,4 +136,20 @@ $(function () { }); } + // Blacklisted passed testrows + if ($.inArray('testrows_blacklisted_passed_data', divs) > -1) { + testset = $('#testset').html(); + project = $('#project').html(); + url = "data/test/bpassed/testrows/" + testset + "/" + project; + $.ajax({ + url: url, + dataType: "html", + cache: true + }) + .done(function( html ) { + console.log(this.url + " done"); + $('#testrows_blacklisted_passed_data').html(html); + }); + } + }); diff --git a/non-puppet/qtmetrics2/src/Database.php b/non-puppet/qtmetrics2/src/Database.php index 33b77b8..a3b7f1b 100644 --- a/non-puppet/qtmetrics2/src/Database.php +++ b/non-puppet/qtmetrics2/src/Database.php @@ -34,7 +34,7 @@ /** * Database class - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -933,6 +933,72 @@ class Database { } /** + * Get counts of blacklisted passed testrows for a testset in specified builds since specified date + * Only the testfunctions that are blacklisted, are only passed and have been run since the specified date are listed + * @param string $testset + * @param string $project + * @param string $runProject + * @param string $runState + * @param string $date + * @return array (string name, string testfunction, string testset, string project, string conf, int bpassed, int btotal) + */ + public function getTestrowsBlacklistedPassedCountsTestset($testset, $project, $runProject, $runState, $date) + { + $result = array(); + $query = $this->db->prepare(" + SELECT + testrow.name AS testrow, + testfunction.name AS testfunction, + testset.name AS testset, + project.name AS project, + conf.name AS conf, + COUNT(CASE WHEN testrow_run.result IN ('bpass', 'bxfail') THEN testrow_run.result END) AS bpassed, + COUNT(CASE WHEN testrow_run.result LIKE '%' THEN testrow_run.result END) AS btotal + 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 + INNER JOIN state ON project_run.state_id = state.id + WHERE + project_run.project_id = (SELECT id FROM project WHERE name = ?) AND + project_run.state_id = (SELECT id FROM state WHERE name = ?) AND + testset.name = ? AND + project.name = ? AND + project_run.timestamp >= ? AND + branch.archived = 0 + GROUP BY testrow.name, testfunction.name, testset.name, project.name, conf.name + ORDER BY project.name, testset.name, testfunction.name, testrow.name, conf.name; + "); + $query->bindParam(1, $runProject); + $query->bindParam(2, $runState); + $query->bindParam(3, $testset); + $query->bindParam(4, $project); + $query->bindParam(5, $date); + $query->execute(); + while($row = $query->fetch(PDO::FETCH_ASSOC)) { + if ($row['bpassed'] === $row['btotal']) { // return only those where only bpasses + $result[] = array( + 'name' => $row['testrow'], + 'testfunction' => $row['testfunction'], + 'testset' => $row['testset'], + 'project' => $row['project'], + 'conf' => $row['conf'], + 'bpassed' => $row['bpassed'], + 'btotal' => $row['btotal'] + ); + } + } + return $result; + } + + /** * Get project run data by branch * @param string $runProject * @param string $runState @@ -1434,7 +1500,7 @@ class Database { 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 + (testrow_run.result LIKE '%fail' OR testrow_run.result LIKE '%skip' OR testrow_run.result LIKE '%x%' OR testrow_run.result LIKE 'b%') AND testfunction.name = ? AND testset.name = ? AND project.name = ? AND diff --git a/non-puppet/qtmetrics2/src/Factory.php b/non-puppet/qtmetrics2/src/Factory.php index 7867154..7bce306 100644 --- a/non-puppet/qtmetrics2/src/Factory.php +++ b/non-puppet/qtmetrics2/src/Factory.php @@ -34,7 +34,7 @@ /** * Factory class - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -48,6 +48,7 @@ require_once 'Testset.php'; require_once 'TestsetRun.php'; require_once 'Testfunction.php'; require_once 'TestfunctionRun.php'; +require_once 'Testrow.php'; require_once 'TestrowRun.php'; class Factory { @@ -310,7 +311,7 @@ class Factory { if ($listType === self::LIST_BPASSES) { $days = intval($ini['blacklisted_pass_last_days']) - 1; $since = self::getSinceDate($days); - if ($testset === '') + if (empty($testset)) $dbEntries = self::db()->getTestfunctionsBlacklistedPassedCounts($runProject, $runState, $since); else $dbEntries = self::db()->getTestfunctionsBlacklistedPassedCountsTestset($testset, $project, $runProject, $runState, $since); @@ -324,6 +325,31 @@ class Factory { } /** + * Create Testrow objects for those in database (with bpassed counts) + * List is limited by date (since) and length, and for specified builds only + * @param string $testset + * @param string $project + * @param string $runProject + * @param string $runState + * @return array Testfunction objects + */ + public static function createTestrows($testset, $project, $runProject, $runState) + { + $objects = array(); + $ini = self::conf(); + // Blacklisted passed list (from specified builds only) + $days = intval($ini['blacklisted_pass_last_days']) - 1; + $since = self::getSinceDate($days); + $dbEntries = self::db()->getTestrowsBlacklistedPassedCountsTestset($testset, $project, $runProject, $runState, $since); + foreach($dbEntries as $entry) { + $obj = new Testrow($entry['name'], $entry['testfunction'], $entry['testset'], $entry['project'], $entry['conf']); + $obj->setBlacklistedCounts($entry['bpassed'], $entry['btotal']); + $objects[] = $obj; + } + return $objects; + } + + /** * Create ProjectRun objects for those in database * @param string $runProject * @param string $runState diff --git a/non-puppet/qtmetrics2/src/Testrow.php b/non-puppet/qtmetrics2/src/Testrow.php new file mode 100644 index 0000000..39f69c3 --- /dev/null +++ b/non-puppet/qtmetrics2/src/Testrow.php @@ -0,0 +1,107 @@ +<?php +############################################################################# +## +## 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$ +## +############################################################################# + +/** + * Testrow class + * @since 23-09-2015 + * @author Juha Sippola + */ + +class Testrow extends Testfunction { + + /** + * Testfunction name. + * @var string + */ + private $testfunctionName; + + /** + * Testrow constructor. + * @param string $name + * @param string $testfunctionName + * @param string $testsetName + * @param string $testsetProjectName + * @param string $confName + */ + public function __construct($name, $testfunctionName, $testsetName, $testsetProjectName, $confName) { + parent::__construct($testfunctionName, $testsetName, $testsetProjectName, $confName); + $this->name = $name; + $this->testfunctionName = $testfunctionName; + } + + /** + * Get name of the testrow. + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get short name of the testrow. + * @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; + } + + /** + * Get short name of the testfunction. + * @return string + */ + public function getTestfunctionShortName() + { + if (strlen($this->testfunctionName) > parent::SHORT_NAME_LENGTH) + return substr($this->testfunctionName, 0, parent::SHORT_NAME_LENGTH - 10) . '...' . substr($this->testfunctionName, -7); + else + return $this->testfunctionName; + } + +} + +?> diff --git a/non-puppet/qtmetrics2/src/test/DatabaseTest.php b/non-puppet/qtmetrics2/src/test/DatabaseTest.php index d5e4ad5..942cf38 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 <path-to-phpunit>/phpunit.phar ./src/test - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -718,6 +718,46 @@ class DatabaseTest extends PHPUnit_Framework_TestCase } /** + * Test getTestrowsBlacklistedPassedCountsTestset + * @dataProvider testGetTestrowsBlacklistedPassedCountsTestsetData + */ + public function testGetTestrowsBlacklistedPassedCountsTestset($testset, $project, $runProject, $runState, $date, $exp_testrow, $exp_excluded_testrow, $exp_testrow_count_min, $exp_bpassed_min) + { + $testrows = array(); + $bpassed = 0; + $db = Factory::db(); + $result = $db->getTestrowsBlacklistedPassedCountsTestset($testset, $project, $runProject, $runState, $date); + foreach($result as $row) { + $this->assertArrayHasKey('name', $row); + $this->assertArrayHasKey('testset', $row); + $this->assertArrayHasKey('testfunction', $row); + $this->assertArrayHasKey('project', $row); + $this->assertArrayHasKey('conf', $row); + $this->assertArrayHasKey('bpassed', $row); + $this->assertArrayHasKey('btotal', $row); + $this->assertEquals($row['btotal'], $row['bpassed']); + $testrows[] = $row['name']; + $bpassed += $row['bpassed']; + } + $this->assertGreaterThanOrEqual($exp_testrow_count_min, count($testrows)); + if ($exp_testrow_count_min > 0) { + $this->assertNotEmpty($result); + $this->assertContains($exp_testrow, $testrows); + $this->assertNotContains($exp_excluded_testrow, $testrows); + $this->assertGreaterThanOrEqual($exp_bpassed_min, $bpassed); + } + } + public function testGetTestrowsBlacklistedPassedCountsTestsetData() + { + return array( + array('tst_qfont', 'qtbase', 'Qt5', 'state', '2013-05-01', 'cursive', 'serif', 1, 1), // in test data cursive has bpassed and serif doesn't + array('tst_qftp', 'qtbase', 'Qt5', 'state', '2013-05-01', '', '', 0, 0), + array('tst_qfont', 'qtbase', 'Qt5', 'state', '2013-05-29', '', '', 0, 0), + array('tst_qfont', 'qtbase', 'Qt5', 'state', '2999-05-29', '', '', 0, 0) + ); + } + + /** * Test getProjectBuildsByBranch * @dataProvider testGetProjectBuildsByBranchData */ diff --git a/non-puppet/qtmetrics2/src/test/FactoryTest.php b/non-puppet/qtmetrics2/src/test/FactoryTest.php index 96530f7..72271b2 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 <path-to-phpunit>/phpunit.phar ./src/test - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -314,6 +314,29 @@ class FactoryTest extends PHPUnit_Framework_TestCase } /** + * Test createTestrows + * @dataProvider testCreateTestrowsData + */ + public function testCreateTestrows($testset, $project, $runProject, $runState) + { + $testrows = Factory::createTestrows($testset, $project, $runProject, $runState); + foreach($testrows as $testrow) { + $this->assertTrue($testrow instanceof Testrow); + $blacklisted = $testrow->getBlacklistedCounts(); + $this->assertNotNull($blacklisted); + $this->assertArrayHasKey('bpassed', $blacklisted); + $this->assertArrayHasKey('btotal', $blacklisted); + } + } + public function testCreateTestrowsData() + { + return array( + array('tst_qfont', 'qtbase', 'Qt5', 'state'), + array('tst_qftp', 'qtbase', 'Qt5', 'state') + ); + } + + /** * Test createProjectRuns * @dataProvider testCreateProjectRunsData */ diff --git a/non-puppet/qtmetrics2/src/test/TestrowTest.php b/non-puppet/qtmetrics2/src/test/TestrowTest.php new file mode 100644 index 0000000..f52c404 --- /dev/null +++ b/non-puppet/qtmetrics2/src/test/TestrowTest.php @@ -0,0 +1,156 @@ +<?php +############################################################################# +## +## 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$ +## +############################################################################# + +require_once(__DIR__.'/../Factory.php'); + +/** + * Testrow unit test class + * @example To run (in qtmetrics root directory): php <path-to-phpunit>/phpunit.phar ./src/test + * @since 23-09-2015 + * @author Juha Sippola + */ + +class TestrowTest extends PHPUnit_Framework_TestCase +{ + + /** + * Test getName, getShortName, getTestsetName, getTestsetProjectName, getConfName + * @dataProvider testGetNameData + */ + public function testGetName($name, $shortName, $testfunction, $testset, $project, $conf) + { + $testrow = new Testrow($name, $testfunction, $testset, $project, $conf); + $this->assertEquals($name, $testrow->getName()); + $this->assertEquals($shortName, $testrow->getShortName()); + $this->assertEquals($testfunction, $testrow->getTestfunctionName()); + $this->assertEquals($testset, $testrow->getTestsetName()); + $this->assertEquals($project, $testrow->getTestsetProjectName()); + $this->assertEquals($conf, $testrow->getConfName()); + } + public function testGetNameData() + { + return array( + array( + 'cursive', + 'cursive', + 'defaultFamily', + 'tst_qfont', + 'QtBase', + 'macx-clang_developer-build_OSX_10.8'), + array( + 'my_testrow', + 'my_testrow', + 'my_testfunction', + 'my_testset', + 'my_project', + 'my_conf'), + array( + 'my_long_testrow_name_that_has_over_50_letters_in_it', + 'my_long_testrow_name_that_has_over_50_le...s_in_it', + 'my_testfunction', + 'my_testset', + 'my_project', + 'my_conf') + ); + } + + /** + * Test setResultCounts and getResultCounts + * @dataProvider testGetResultCountsData + */ + public function testGetTestsetResultCounts($name, $testset, $project, $conf, $passed, $failed, $skipped) + { + $testfunction = new Testfunction($name, $testset, $project, $conf); + $this->assertTrue($testfunction instanceof Testfunction); + // Counts not set + $result = $testfunction->getResultCounts(); + $this->assertArrayHasKey('passed', $result); + $this->assertArrayHasKey('failed', $result); + $this->assertArrayHasKey('skipped', $result); + $this->assertNull($result['passed']); + $this->assertNull($result['failed']); + $this->assertNull($result['skipped']); + // Counts set + $testfunction->setResultCounts($passed, $failed, $skipped); + $result = $testfunction->getResultCounts(); + $this->assertArrayHasKey('passed', $result); + $this->assertArrayHasKey('failed', $result); + $this->assertArrayHasKey('skipped', $result); + $this->assertEquals($passed, $result['passed']); + $this->assertEquals($failed, $result['failed']); + $this->assertEquals($skipped, $result['skipped']); + } + public function testGetResultCountsData() + { + return array( + array('cleanupTestCase', 'tst_qftp', 'QtBase', 'macx-clang_developer-build_OSX_10.8', 1, 2, 3), + array('cleanupTestCase', 'tst_qftp', 'Qt5', 'macx-clang_developer-build_OSX_10.8', 123456, 654321, 111222), + array('my_testfunction', 'my_testfunction', 'my_project', 'my_conf', 7, 14, 7) + ); + } + + /** + * Test setBlacklistedCounts and getBlacklistedCounts + * @dataProvider testGetBlacklistedCountsData + */ + public function testGetBlacklistedCounts($name, $testset, $project, $conf, $bpassed, $btotal) + { + $testfunction = new Testfunction($name, $testset, $project, $conf); + $this->assertTrue($testfunction instanceof Testfunction); + // Counts not set + $result = $testfunction->getBlacklistedCounts(); + $this->assertArrayHasKey('bpassed', $result); + $this->assertArrayHasKey('btotal', $result); + $this->assertNull($result['bpassed']); + $this->assertNull($result['btotal']); + // Counts set + $testfunction->setBlacklistedCounts($bpassed, $btotal); + $result = $testfunction->getBlacklistedCounts(); + $this->assertArrayHasKey('bpassed', $result); + $this->assertArrayHasKey('btotal', $result); + $this->assertEquals($bpassed, $result['bpassed']); + $this->assertEquals($btotal, $result['btotal']); + } + public function testGetBlacklistedCountsData() + { + return array( + array('cleanupTestCase', 'tst_qftp', 'QtBase', 'macx-clang_developer-build_OSX_10.8', 1, 2), + array('cleanupTestCase', 'tst_qftp', 'Qt5', 'macx-clang_developer-build_OSX_10.8', 123456, 654321), + array('my_testfunction', 'my_testfunction', 'my_project', 'my_conf', 7, 14) + ); + } + +} + +?> diff --git a/non-puppet/qtmetrics2/templates/about.html b/non-puppet/qtmetrics2/templates/about.html index d1d72d5..044d95a 100644 --- a/non-puppet/qtmetrics2/templates/about.html +++ b/non-puppet/qtmetrics2/templates/about.html @@ -34,7 +34,7 @@ /** * About window content - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -52,4 +52,4 @@ and the global Qt developer community are the target audience. For detailed desc <p>See the <strong><a href="https://wiki.qt.io/Qt_Metrics_2_Backlog" target="_blank">backlog</a></strong> for development items currently identified or in progress.</p> -<p><small>Version 0.33 (22-Sep-2015)</small></p> +<p><small>Version 0.34 (23-Sep-2015)</small></p> diff --git a/non-puppet/qtmetrics2/templates/testfunction.html b/non-puppet/qtmetrics2/templates/testfunction.html index 26017db..14d285b 100644 --- a/non-puppet/qtmetrics2/templates/testfunction.html +++ b/non-puppet/qtmetrics2/templates/testfunction.html @@ -34,7 +34,7 @@ /** * Test function page (list of test rows) - * @since 17-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -95,7 +95,8 @@ <li>results: <span class="glyphicon glyphicon-remove red"></span> = {{ constant('TestrowRun::RESULT_FAILURE') }}, <span class="glyphicon glyphicon-ok-sign red"></span> = {{ constant('TestrowRun::RESULT_SUCCESS_UNEXPECTED') }}, <span class="glyphicon glyphicon-remove-sign green"></span> = {{ constant('TestrowRun::RESULT_FAILURE_EXPECTED') }}, -<span class="glyphicon glyphicon-ban-circle gray"></span> = {{ constant('TestrowRun::RESULT_SKIP') }}</li> +<span class="glyphicon glyphicon-ban-circle gray"></span> = {{ constant('TestrowRun::RESULT_SKIP') }}, +<span class="glyphicon glyphicon-ok green"></span> = {{ constant('TestrowRun::RESULT_SUCCESS') }} (blacklisted only)</li> </ul> </li> <li>Details on the runs are available as tooltip on result icon</li> @@ -109,7 +110,7 @@ <div class="panel panel-primary"> <div class="panel-heading"> -<h4 class="panel-title bold">Test Row Results in Branches <small>(failures and skipped only)</small></h4> +<h4 class="panel-title bold">Test Row Results in Branches <small>(failures, skipped or blacklisted only)</small></h4> </div> </div> @@ -233,7 +234,9 @@ {% if key > buildKeyIndexPrinted and not buildKeyFound %} {% if buildKey == run.getBuildKey %} {# Print result #} -{% if run.getResult == constant('TestfunctionRun::RESULT_FAILURE') %} +{% if run.getResult == constant('TestfunctionRun::RESULT_SUCCESS') %} +{% set resultIcon = 'glyphicon glyphicon-ok green' %} +{% elseif 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' %} diff --git a/non-puppet/qtmetrics2/templates/testfunctions_bpass.html b/non-puppet/qtmetrics2/templates/testfunctions_bpass.html index 91ed345..37f665c 100644 --- a/non-puppet/qtmetrics2/templates/testfunctions_bpass.html +++ b/non-puppet/qtmetrics2/templates/testfunctions_bpass.html @@ -34,7 +34,7 @@ /** * Blacklisted passes (testfunctions) page - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -59,7 +59,7 @@ <h1 class="page-header"> <span id="testset">{{ testset }}</span> <span id="project" class="hidden">{{ project }}</span> -Test Function Blacklisted Passes +Blacklisted Test Function Passes <button type="button" class="btn btn-xs btn-info" data-toggle="collapse" data-target="#info" aria-expanded="false" aria-controls="info"> <span class="glyphicon glyphicon-info-sign"></span> </button> @@ -100,6 +100,9 @@ and configuration. The testset link opens a page where the test functions are li <div class="alert alert-warning" role="alert"> <span class="glyphicon glyphicon-time"></span> <strong>Please wait:</strong> Extracting the data will be ready in less than a minute! </div> +<div class="alert alert-info" role="alert"> +<span class="glyphicon glyphicon-info-sign"></span> <strong>Tip:</strong> You can quickly check both the blacklisted test function passes and test row passes under each testset. +</div> {% endif %} </div> {# testfunctions_blacklisted_passed_data #} diff --git a/non-puppet/qtmetrics2/templates/testfunctions_bpass_data.html b/non-puppet/qtmetrics2/templates/testfunctions_bpass_data.html index 7fd55d7..3fb0123 100644 --- a/non-puppet/qtmetrics2/templates/testfunctions_bpass_data.html +++ b/non-puppet/qtmetrics2/templates/testfunctions_bpass_data.html @@ -33,8 +33,8 @@ ############################################################################# /** - * Blacklisted passes (testfunctions) data - * @since 22-09-2015 + * Blacklisted passes (testfunctions or testrows) data + * @since 23-09-2015 * @author Juha Sippola */ @@ -43,36 +43,41 @@ {# Failed/passed bar area size in px #} {% set BAR_AREA = 120 %} -{# testfunctions as Testfunction objects +{# tests as Testfunction objects /** - * @var Testfunction[] testfunctions + * @var Testfunction[] tests + */ +#} +{# tests as Testrow objects +/** + * @var Testrow[] tests */ #} {# Calculate max result count for the bar #} {% set prevTestsetName = '' %} {% set prevProjectName = '' %} -{% set testfunctionCount = 0 %} +{% set testCount = 0 %} {% set maxCount = 1 %} -{% for testfunction in testfunctions %} -{% if (testfunction.getTestsetName == prevTestsetName) and (testfunction.getTestsetProjectName == prevProjectName) %} -{% set testfunctionCount = testfunctionCount + 1 %} +{% for test in tests %} +{% if (test.getTestsetName == prevTestsetName) and (test.getTestsetProjectName == prevProjectName) %} +{% set testCount = testCount + 1 %} {% else %} -{% if testfunctionCount > maxCount %} -{% set maxCount = testfunctionCount %} +{% if testCount > maxCount %} +{% set maxCount = testCount %} {% endif %} -{% set testfunctionCount = 1 %} +{% set testCount = 1 %} {% endif %} -{% set prevTestsetName = testfunction.getTestsetName %} -{% set prevProjectName = testfunction.getTestsetProjectName %} +{% set prevTestsetName = test.getTestsetName %} +{% set prevProjectName = test.getTestsetProjectName %} {% endfor %} -{% if testfunctionCount > maxCount %} -{% set maxCount = testfunctionCount %} +{% if testCount > maxCount %} +{% set maxCount = testCount %} {% endif %} {##### Summary #####} -{% if testfunctionCount > 0 %} +{% if testCount > 0 %} <div class="panel panel-primary"> <div class="panel-heading"> @@ -94,28 +99,28 @@ {# Print testsets #} {% set prevTestsetName = '' %} {% set prevProjectName = '' %} -{% set testfunctionCount = 0 %} +{% set testCount = 0 %} {% set bar = 0 %} -{% for testfunction in testfunctions %} +{% for test in tests %} {# First row #} {% if prevTestsetName == '' %} <tr> -<td>{{ testfunction.getTestsetName }}</td> -<td>{{ testfunction.getTestsetProjectName }}</td> -{% set testfunctionCount = 1 %} +<td>{{ test.getTestsetName }}</td> +<td>{{ test.getTestsetProjectName }}</td> +{% set testCount = 1 %} {# Same testset: Increase the counter #} -{% elseif (testfunction.getTestsetName == prevTestsetName) and (testfunction.getTestsetProjectName == prevProjectName) %} -{% set testfunctionCount = testfunctionCount + 1 %} +{% elseif (test.getTestsetName == prevTestsetName) and (test.getTestsetProjectName == prevProjectName) %} +{% set testCount = testCount + 1 %} {# New testset: Print count for previous one and start new row #} {% else %} -{% set bar = ((BAR_AREA/maxCount) * testfunctionCount)|round(0, 'floor') %} -{% if (testfunctionCount > 0) and (bar == 0) %} +{% set bar = ((BAR_AREA/maxCount) * testCount)|round(0, 'floor') %} +{% if (testCount > 0) and (bar == 0) %} {% set bar = 1 %} {% endif %} -<td class="leftBorder center">{{ testfunctionCount }}</td> +<td class="leftBorder center">{{ testCount }}</td> <td class="center showInLargeDisplay"> <div> <div class="floatLeft blueBackground" style="width: {{ bar }}px"> </div> @@ -123,21 +128,21 @@ </td> </tr> <tr> -<td>{{ testfunction.getTestsetName }}</td> -<td>{{ testfunction.getTestsetProjectName }}</td> -{% set testfunctionCount = 1 %} +<td>{{ test.getTestsetName }}</td> +<td>{{ test.getTestsetProjectName }}</td> +{% set testCount = 1 %} {% endif %} -{% set prevTestsetName = testfunction.getTestsetName %} -{% set prevProjectName = testfunction.getTestsetProjectName %} -{% endfor %}{# testfunction #} +{% set prevTestsetName = test.getTestsetName %} +{% set prevProjectName = test.getTestsetProjectName %} +{% endfor %}{# test #} {# Print count for last one #} -{% if testfunctionCount > 0 %} -{% set bar = ((BAR_AREA/maxCount) * testfunctionCount)|round(0, 'floor') %} -{% if (testfunctionCount > 0) and (bar == 0) %} +{% if testCount > 0 %} +{% set bar = ((BAR_AREA/maxCount) * testCount)|round(0, 'floor') %} +{% if (testCount > 0) and (bar == 0) %} {% set bar = 1 %} {% endif %} -<td class="leftBorder center">{{ testfunctionCount }}</td> +<td class="leftBorder center">{{ testCount }}</td> <td class="center showInLargeDisplay"> <div> <div class="floatLeft blueBackground" style="width: {{ bar }}px"> </div> @@ -152,7 +157,7 @@ </div> {# .panel-body #} </div> {# .panel... #} -{% endif %}{# testfunctionCount #} +{% endif %}{# testCount #} {##### List #####} @@ -161,13 +166,16 @@ <h4 class="panel-title bold">Blacklisted Passes <small>(last {{ lastDays }} days since {{ sinceDate }})</small></h4> </div> -{% if testfunctionCount > 0 %} +{% if testCount > 0 %} <div class="panel-body"> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> +{% if list == 'rows' %} +<th>test row</th> +{% endif %} <th>test function</th> <th>testset</th> <th class="showInLargeDisplay">project</th> @@ -176,40 +184,56 @@ </thead> <tbody> -{# Print testfunctions #} -{% for testfunction in testfunctions %} +{# Print tests #} +{% for test in tests %} <tr> -{# Testfunction name #} -{% if testfunction.getName|length > constant('Testfunction::SHORT_NAME_LENGTH') %} -<td><span class="clickOnTouch" data-toggle="tooltip" data-placement="top" title="{{ testfunction.getName }}">{{ testfunction.getShortName }}</span></td> -{% else %} -<td>{{ testfunction.getName }}</td> -{% endif %} +{# Name #} +{% if test.getName|length > constant('Testfunction::SHORT_NAME_LENGTH') %} +<td><span class="clickOnTouch" data-toggle="tooltip" data-placement="top" title="{{ test.getName }}">{{ test.getShortName }}</span></td> +{% else %} +<td>{{ test.getName }}</td> +{% endif %} + +{# Testfunction name for a testrow (with a link to testfunction page) #} +{% if list == 'rows' %} +{% set link = testfunctionRoute ~ '/' ~ test.getTestfunctionName|url_encode ~ '/' ~ test.getTestsetName|url_encode ~ '/' ~ test.getTestsetProjectName|url_encode ~ '/' ~ test.getConfName|url_encode %} +<td><a href="{{ link }}"> +{% if test.getTestfunctionName|length > constant('Testfunction::SHORT_NAME_LENGTH') %} +<span class="clickOnTouch" data-toggle="tooltip" data-placement="top" title="{{ test.getTestfunctionName }}">{{ test.getTestfunctionShortName }}</span> +{% else %} +{{ test.getTestfunctionName }} +{% endif %} +</a></td> +{% endif %} -{# Testset name #} -{% set link = testsetRoute ~ '/' ~ testfunction.getTestsetName|url_encode ~ '/' ~ testfunction.getTestsetProjectName|url_encode ~ '/' ~ testfunction.getConfName|url_encode %} -<td><a href="{{ link }}">{{ testfunction.getTestsetName }}</a></td> +{# Testset name (with a link to testset page for testfunctions list) #} +{% if list == 'functions' %} +{% set link = testsetRoute ~ '/' ~ test.getTestsetName|url_encode ~ '/' ~ test.getTestsetProjectName|url_encode ~ '/' ~ test.getConfName|url_encode %} +<td><a href="{{ link }}">{{ test.getTestsetName }}</a></td> +{% else %} +<td>{{ test.getTestsetName }}</td> +{% endif %} {# Project name #} -<td class="showInLargeDisplay">{{ testfunction.getTestsetProjectName }}</td> +<td class="showInLargeDisplay">{{ test.getTestsetProjectName }}</td> {# Conf name #} -<td class="showInLargeDisplay">{{ testfunction.getConfName }}</td> +<td class="showInLargeDisplay">{{ test.getConfName }}</td> </tr> -{% endfor %}{# testfunction #} +{% endfor %}{# test #} </tbody> </table> </div> {# .table-responsive #} </div> {# .panel-body #} -{% endif %}{# testfunctionCount #} +{% endif %}{# testCount #} </div> {# .panel... #} -{% if testfunctionCount == 0 %} +{% if testCount == 0 %} <div class="alert alert-info" role="alert"> -The testset {{ testset }} ({{ project }}) either does not have any blacklisted test functions, -or there are failed or skipped blacklisted test functions since {{ sinceDate }}! +The testset {{ testset }} ({{ project }}) either does not have any blacklisted test {{ list }}, +or there are failed or skipped blacklisted test {{ list }} since {{ sinceDate }}! </div> -{% endif %}{# testfunctionCount #} +{% endif %}{# testCount #} diff --git a/non-puppet/qtmetrics2/templates/testrows_bpass.html b/non-puppet/qtmetrics2/templates/testrows_bpass.html new file mode 100644 index 0000000..7dede8a --- /dev/null +++ b/non-puppet/qtmetrics2/templates/testrows_bpass.html @@ -0,0 +1,111 @@ +{# +############################################################################# +## +## 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$ +## +############################################################################# + +/** + * Blacklisted passes (testrows) page + * @since 23-09-2015 + * @author Juha Sippola + */ + +#} + +{% include "header.html" %} + +<ol class="breadcrumb"> +{% for link in breadcrumb %} +<li><a href="{{ link.link }}">{{ link.name }}</a></li> +{% endfor %} +<li class="active">blacklisted test row passes</li> +</ol> + +<div class="container-fluid"> +<div class="row"> + +<div class="col-sm-12 col-md-12 main"> + +{##### Title #####} + +<h1 class="page-header"> +<span id="testset">{{ testset }}</span> +<span id="project" class="hidden">{{ project }}</span> +Blacklisted Test Row Passes +<button type="button" class="btn btn-xs btn-info" data-toggle="collapse" data-target="#info" aria-expanded="false" aria-controls="info"> +<span class="glyphicon glyphicon-info-sign"></span> +</button> +<small>{{ refreshed }}</small> +</h1> + +{##### Info well #####} + +<div class="collapse" id="info"> +<div class="well infoWell"> +<span class="glyphicon glyphicon-info-sign"></span> <strong>Test Row Blacklisted Passes</strong><br> +<ul> +<li>Lists the test rows in <strong>{{ masterProject }} {{ masterState }}</strong> builds where tagged +as blacklisted but have not failed or skipped during the last {{ lastDays }} days.</li> +<li>In these cases the blacklisted tag could maybe be removed in the CI so that the test row would be +tested normally in the future builds.</li> +<li><strong>Summary</strong> shows the count of test rows in each testset (any configuration or branch) +with blacklisted passes</li> +<li><strong>Blacklisted Passes</strong> shows the list of test rows with blacklisted passes by testset +and configuration. The testset link opens a page where the test rows are listed by branch.</li> +</ul> +</div> +</div> + +{##### List #####} + +<div id="testrows_blacklisted_passed_data"> +<div class="panel panel-primary"> +<div class="panel-heading"> +<h4 class="panel-title bold">Blacklisted Passes <small>(last {{ lastDays }} days since {{ sinceDate }})</small></h4> +</div> +</div> +<div class="progress data_loading"> +<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"> +</div> +</div> +</div> {# testrows_blacklisted_passed_data #} + +</div> {# .col... #} +</div> {# .row #} +</div> {# .container-fluid #} + +{% include "footer.html" %} + +{# Local scripts for this page #} +<script src="scripts/ajax.js"></script> +<script src="scripts/tooltip.js"></script> + +{% include "close.html" %} diff --git a/non-puppet/qtmetrics2/templates/testset.html b/non-puppet/qtmetrics2/templates/testset.html index 817e783..9fd4900 100644 --- a/non-puppet/qtmetrics2/templates/testset.html +++ b/non-puppet/qtmetrics2/templates/testset.html @@ -34,7 +34,7 @@ /** * Testset page (list of configurations) - * @since 22-09-2015 + * @since 23-09-2015 * @author Juha Sippola */ @@ -124,7 +124,11 @@ and their configuration on <strong>{{ masterProject }} {{ masterState }}</strong <div> <div class="btn-group"> {% set link = bpassedTestsetRoute ~ '/' ~ testset.getName|url_encode ~ '/' ~ testset.getProjectName|url_encode %} -<a class="btn btn-primary btn-xs" href="{{ link }}" role="button">test function blacklisted passes</a> +<a class="btn btn-primary btn-xs" href="{{ link }}" role="button">blacklisted passes for<br>test functions</a> +</div> +<div class="btn-group"> +{% set link = bpassedtestsetTestrowsRoute ~ '/' ~ testset.getName|url_encode ~ '/' ~ testset.getProjectName|url_encode %} +<a class="btn btn-primary btn-xs" href="{{ link }}" role="button">blacklisted passes for<br>test rows</a> </div> </div> <hr> |