blob: c691f9fb1fc93d1f7893a6015bee9f24b2580c52 [file] [log] [blame]
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
'use strict';
// Controllers
angular.module('cherry.batchResultComparison', [])
// Batch result view.
.controller('BatchResultComparisonCtrl', ['$scope', '$location', '$stateParams', 'rtdb', 'rpc', function($scope, $location, $stateParams, rtdb, rpc)
{
var numBatchResultsLoaded = 0;
var numTestCaseListsLoaded = 0;
var numTestCaseHeadersLoaded = 0;
var totalNumTestCases = 0;
var initiallySelectedNodePath = $stateParams.testGroupPath || $stateParams.testCasePath || '';
var testCaseResultsDifferent = function(node)
{
for (var ndx = 1; ndx < $scope.batchResultIds.length; ndx++)
{
if (node.existsInTree[ndx] !== node.existsInTree[0])
return true;
}
var result0 = testCaseListResult($scope.testCaseHeaders[$scope.batchResultIds[0] + node.headerIdSuffix]);
for (var ndx = 1; ndx < $scope.batchResultIds.length; ndx++)
{
var result = testCaseListResult($scope.testCaseHeaders[$scope.batchResultIds[ndx] + node.headerIdSuffix]);
if (result !== result0)
return true;
}
return false;
};
var batchResultUpdateFunc = function(batchResultNdx)
{
return function(objType, objId, value)
{
if ($scope.batchResults[batchResultNdx] === undefined)
numBatchResultsLoaded++;
$scope.batchResults[batchResultNdx] = value;
if (numBatchResultsLoaded === $scope.batchResultIds.length)
{
var batchResultsStr = '';
for (var ndx = 0; ndx < $scope.batchResults.length; ndx++)
{
if (ndx > 0)
batchResultsStr += ndx < $scope.batchResults.length-1 ? ', ' : ' and ';
batchResultsStr += $scope.batchResults[ndx].name;
}
$scope.batchResultNamesStr = batchResultsStr;
}
};
};
var testCaseHeaderUpdate = function(objType, objId, header)
{
if (!$scope.testCaseHeaders.hasOwnProperty(objId))
numTestCaseHeadersLoaded++;
$scope.testCaseHeaders[objId] = header;
if (numTestCaseListsLoaded === $scope.batchResultIds.length && numTestCaseHeadersLoaded === totalNumTestCases)
$scope.isInitialized = true;
};
var testCaseListUpdateFunc = function(batchResultNdx)
{
return function(objType, objId, list)
{
if ($scope.testCaseTrees[batchResultNdx] === undefined)
{
numTestCaseListsLoaded++;
totalNumTestCases += list.paths.length;
}
$scope.testCaseTrees[batchResultNdx] = genTestCaseTree(list.paths, 'All Results')
if (numTestCaseListsLoaded === $scope.batchResultIds.length)
{
$scope.unionTestCaseTree = genUnionTestCaseTree($scope.testCaseTrees);
setTestCaseTreeInitialDepthExpansion($scope.unionTestCaseTree, 2);
setTestCaseTreeInitialPathExpansion($scope.unionTestCaseTree, initiallySelectedNodePath);
filterTestCaseTree($scope.unionTestCaseTree, function(node) { return true; });
}
for (var ndx = 0; ndx < list.paths.length; ndx++)
rtdb.bind('TestCaseHeader', $scope.batchResultIds[batchResultNdx] + '/' + list.paths[ndx], $scope, { onUpdate: testCaseHeaderUpdate });
};
}
// Indexed by batch result index
$scope.batchResultIds = JSON.parse($stateParams.batchResultIds);
$scope.batchResults = [];
$scope.testCaseTrees = [];
for (var ndx = 0; ndx < $scope.batchResultIds.length; ndx++)
{
var resultId = $scope.batchResultIds[ndx];
rtdb.bind('BatchResult', resultId, $scope, { onUpdate: batchResultUpdateFunc(ndx) });
rtdb.bind('TestCaseList', resultId, $scope, { onUpdate: testCaseListUpdateFunc(ndx) });
}
// Indexed by test case header ID
$scope.testCaseHeaders = {};
// Will be set to true when all relevant objects have been received from server.
$scope.isInitialized = false;
$scope.initProgress = function()
{
if (numTestCaseListsLoaded === $scope.batchResultIds.length)
return numTestCaseHeadersLoaded / totalNumTestCases;
else
return 0.0;
};
$scope.booleanFilters =
[
{
description: 'Different',
preserveNode: function(node) { return node.type === 'testGroup' || testCaseResultsDifferent(node); }
},
{
description: 'Finished',
preserveNode: function(node)
{
for (var ndx = 0; ndx < $scope.batchResultIds.length; ndx++)
{
if (!node.existsInTree[ndx])
return false;
if (node.type === 'testCase')
{
var header = $scope.testCaseHeaders[$scope.batchResultIds[ndx] + node.headerIdSuffix];
if (header.result === '' && !isTestStatusCodeFinished(header.status))
return false;
}
}
return true;
}
},
{
description: 'Non-passed',
preserveNode: function(node)
{
if (node.type === 'testGroup')
return true;
for (var ndx = 0; ndx < $scope.batchResultIds.length; ndx++)
{
if (!node.existsInTree[ndx])
return true;
var header = $scope.testCaseHeaders[$scope.batchResultIds[ndx] + node.headerIdSuffix];
if (header.status !== TEST_STATUS_CODE.PASS)
return true;
}
return false;
}
},
];
$scope.testCasePathFilter = '';
$scope.booleanFilterEnabled = [];
for (var ndx = 0; ndx < $scope.booleanFilters.length; ndx++)
$scope.booleanFilterEnabled.push(false);
var refilter = function()
{
if ($scope.unionTestCaseTree)
{
var and = function(p0, p1) { return function(node) { return p0(node) && p1(node); }; };
var totalFilterFunc = function(node) { return true; };
for (var ndx = 0; ndx < $scope.booleanFilters.length; ndx++)
{
if ($scope.booleanFilterEnabled[ndx])
totalFilterFunc = and(totalFilterFunc, $scope.booleanFilters[ndx].preserveNode);
}
var pathFilterLower = $scope.testCasePathFilter.toLowerCase();
totalFilterFunc = and(totalFilterFunc, function(node) { return node.type !== 'testCase' || node.path.toLowerCase().indexOf(pathFilterLower) !== -1; });
filterTestCaseTree($scope.unionTestCaseTree, totalFilterFunc);
}
}
$scope.$watchCollection('booleanFilterEnabled', refilter);
$scope.$watch('testCasePathFilter', refilter);
// A bit of a hack(?) to access $stateParams in the template.
$scope.stateParams = $stateParams;
$scope.titleExpr('"comparison of " + batchResultNamesStr')
}])
;
// Given an array of trees (as generated by genTestCaseTree), returns a tree
// that contains all the cases combined. The tree is of the same format as
// returned by genTestCaseTree, except that each node contains an additional
// 'existsInTree' member such that n.existsInTree[ndx] is true/false according
// to whether testCaseTrees[ndx] contains that node.
function genUnionTestCaseTree(testCaseTrees)
{
var result = [];
var resultTopLevelLabels = {};
var treeNodesByLabel = [];
for (var treeNdx = 0; treeNdx < testCaseTrees.length; treeNdx++)
{
var tree = testCaseTrees[treeNdx];
treeNodesByLabel[treeNdx] = {};
for (var nodeNdx = 0; nodeNdx < tree.length; nodeNdx++)
{
var node = tree[nodeNdx];
treeNodesByLabel[treeNdx][node.label] = node;
}
}
for (var treeNdx = 0; treeNdx < testCaseTrees.length; treeNdx++)
{
var tree = testCaseTrees[treeNdx];
for (var nodeNdx = 0; nodeNdx < tree.length; nodeNdx++)
{
var node = tree[nodeNdx];
if (!resultTopLevelLabels.hasOwnProperty(node.label))
{
resultTopLevelLabels[node.label] = true;
var nodePerTree = [];
var nodeExistsInTree = [];
for (var ndx = 0; ndx < testCaseTrees.length; ndx++)
{
nodeExistsInTree[ndx] = treeNodesByLabel[ndx].hasOwnProperty(node.label);
nodePerTree[ndx] = nodeExistsInTree[ndx] && treeNodesByLabel[ndx][node.label];
}
if (node.type === 'testCase')
result.push(angular.extend({}, node, { existsInTree: nodeExistsInTree }));
else if (node.type === 'testGroup')
{
var subTrees = [];
for (var ndx = 0; ndx < testCaseTrees.length; ndx++)
{
var n = nodePerTree[ndx];
subTrees.push(n ? n.children : []);
}
result.push(angular.extend({}, node, { existsInTree: nodeExistsInTree, children: genUnionTestCaseTree(subTrees) }));
}
else
throw new Error('genUnionTestCaseTree: bad node type: ' + node.type);
}
}
}
return result;
}