Add a coverage layer to gr-diff

Change-Id: Iaa768824a0c32694a9f0dbfe29522b15bb7d7198
This commit is contained in:
brohlfs
2019-02-28 17:56:24 +01:00
committed by Ben Rohlfs
parent 3b78332bf8
commit b2577e4f78
6 changed files with 338 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
<!--
@license
Copyright (C) 2019 The Android Open Source Project
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.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<dom-module id="gr-coverage-layer">
<template>
</template>
<script src="../gr-diff-highlight/gr-annotation.js"></script>
<script src="gr-coverage-layer.js"></script>
</dom-module>

View File

@@ -0,0 +1,129 @@
/**
* @license
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
(function() {
'use strict';
/** @enum {string} */
Gerrit.CoverageType = {
/**
* start_character and end_character of the range will be ignored for this
* type.
*/
COVERED: 'COVERED',
/**
* start_character and end_character of the range will be ignored for this
* type.
*/
NOT_COVERED: 'NOT_COVERED',
PARTIALLY_COVERED: 'PARTIALLY_COVERED',
/**
* You don't have to use this. If there is no coverage information for a
* range, then it implicitly means NOT_INSTRUMENTED. start_character and
* end_character of the range will be ignored for this type.
*/
NOT_INSTRUMENTED: 'NOT_INSTRUMENTED',
};
/**
* @typedef {{
* side: string,
* type: Gerrit.CoverageType,
* code_range: Gerrit.Range,
* }}
*/
Gerrit.CoverageRange;
Polymer({
is: 'gr-coverage-layer',
properties: {
/**
* Must be sorted by code_range.start_line.
* Must only contain ranges that match the side.
*
* @type {!Array<!Gerrit.CoverageRange>}
*/
coverageRanges: Array,
side: String,
/**
* We keep track of the line number from the previous annotate() call,
* and also of the index of the coverage range that had matched.
* annotate() calls are coming in with increasing line numbers and
* coverage ranges are sorted by line number. So this is a very simple
* and efficient way for finding the coverage range that matches a given
* line number.
*/
_lineNumber: {
type: Number,
value: 0,
},
_index: {
type: Number,
value: 0,
},
},
/**
* Layer method to add annotations to a line.
*
* @param {!HTMLElement} el Not used for this layer.
* @param {!HTMLElement} lineNumberEl The <td> element with the line number.
* @param {!Object} line Not used for this layer.
*/
annotate(el, lineNumberEl, line) {
if (!lineNumberEl || !lineNumberEl.classList.contains(this.side)) {
return;
}
const elementLineNumber = parseInt(
lineNumberEl.getAttribute('data-value'), 10);
if (!elementLineNumber || elementLineNumber < 1) return;
// If the line number is smaller than before, then we have to reset our
// algorithm and start searching the coverage ranges from the beginning.
// That happens for example when you expand diff sections.
if (elementLineNumber < this._lineNumber) {
this._index = 0;
}
this._lineNumber = elementLineNumber;
// We simply loop through all the coverage ranges until we find one that
// matches the line number.
while (this._index < this.coverageRanges.length) {
const coverageRange = this.coverageRanges[this._index];
// If the line number has moved past the current coverage range, then
// try the next coverage range.
if (this._lineNumber > coverageRange.code_range.end_line) {
this._index++;
continue;
}
// If the line number has not reached the next coverage range (and the
// range before also did not match), then this line has not been
// instrumented. Nothing to do for this line.
if (this._lineNumber < coverageRange.code_range.start_line) {
return;
}
// The line number is within the current coverage range. Style it!
lineNumberEl.classList.add(coverageRange.type);
return;
}
},
});
})();

View File

@@ -0,0 +1,138 @@
<!DOCTYPE html>
<!--
@license
Copyright (C) 2019 The Android Open Source Project
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.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<title>gr-coverage-layer</title>
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../gr-diff/gr-diff-line.js"></script>
<link rel="import" href="../../../test/common-test-setup.html"/>
<link rel="import" href="gr-coverage-layer.html">
<script>void(0);</script>
<test-fixture id="basic">
<template>
<gr-coverage-layer></gr-coverage-layer>
</template>
</test-fixture>
<script>
suite('gr-coverage-layer', () => {
let element;
setup(() => {
const initialCoverageRanges = [
{
type: 'COVERED',
side: 'right',
code_range: {
start_line: 1,
end_line: 2,
},
},
{
type: 'NOT_COVERED',
side: 'right',
code_range: {
start_line: 3,
end_line: 4,
},
},
{
type: 'PARTIALLY_COVERED',
side: 'right',
code_range: {
start_line: 5,
end_line: 6,
},
},
{
type: 'NOT_INSTRUMENTED',
side: 'right',
code_range: {
start_line: 8,
end_line: 9,
},
},
];
element = fixture('basic');
element.coverageRanges = initialCoverageRanges;
element.side = 'right';
});
suite('annotate', () => {
function createLine(lineNumber) {
lineEl = document.createElement('div');
lineEl.setAttribute('data-side', 'right');
lineEl.setAttribute('data-value', lineNumber);
lineEl.className = 'right';
return lineEl;
}
function checkLine(lineNumber, className, opt_negated) {
const line = createLine(lineNumber);
element.annotate(undefined, line, undefined);
let contains = line.classList.contains(className);
if (opt_negated) contains = !contains;
assert.isTrue(contains);
}
test('line 1-2 are covered', () => {
checkLine(1, 'COVERED');
checkLine(2, 'COVERED');
});
test('line 3-4 are not covered', () => {
checkLine(3, 'NOT_COVERED');
checkLine(4, 'NOT_COVERED');
});
test('line 5-6 are partially covered', () => {
checkLine(5, 'PARTIALLY_COVERED');
checkLine(6, 'PARTIALLY_COVERED');
});
test('line 7 is implicitly not instrumented', () => {
checkLine(7, 'COVERED', true);
checkLine(7, 'NOT_COVERED', true);
checkLine(7, 'PARTIALLY_COVERED', true);
checkLine(7, 'NOT_INSTRUMENTED', true);
});
test('line 8-9 are not instrumented', () => {
checkLine(8, 'NOT_INSTRUMENTED');
checkLine(9, 'NOT_INSTRUMENTED');
});
test('coverage correct, if annotate is called out of order', () => {
checkLine(8, 'NOT_INSTRUMENTED');
checkLine(1, 'COVERED');
checkLine(5, 'PARTIALLY_COVERED');
checkLine(3, 'NOT_COVERED');
checkLine(6, 'PARTIALLY_COVERED');
checkLine(4, 'NOT_COVERED');
checkLine(9, 'NOT_INSTRUMENTED');
checkLine(2, 'COVERED');
});
});
});
</script>

View File

@@ -16,6 +16,7 @@ limitations under the License.
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
<link rel="import" href="../gr-coverage-layer/gr-coverage-layer.html">
<link rel="import" href="../gr-diff-processor/gr-diff-processor.html">
<link rel="import" href="../gr-ranged-comment-layer/gr-ranged-comment-layer.html">
<link rel="import" href="../gr-syntax-layer/gr-syntax-layer.html">
@@ -31,6 +32,14 @@ limitations under the License.
<gr-syntax-layer
id="syntaxLayer"
diff="[[diff]]"></gr-syntax-layer>
<gr-coverage-layer
id="coverageLayerLeft"
coverage-ranges="[[_leftCoverageRanges]]"
side="left"></gr-coverage-layer>
<gr-coverage-layer
id="coverageLayerRight"
coverage-ranges="[[_rightCoverageRanges]]"
side="right"></gr-coverage-layer>
<gr-diff-processor
id="processor"
groups="{{_groups}}"></gr-diff-processor>
@@ -108,6 +117,19 @@ limitations under the License.
type: Array,
value: () => [],
},
/** @type {!Array<!Gerrit.CoverageRange>} */
coverageRanges: {
type: Array,
value: () => [],
},
_leftCoverageRanges: {
type: Array,
computed: '_computeLeftCoverageRanges(coverageRanges)',
},
_rightCoverageRanges: {
type: Array,
computed: '_computeRightCoverageRanges(coverageRanges)',
},
/**
* The promise last returned from `render()` while the asynchronous
* rendering is running - `null` otherwise. Provides a `cancel()`
@@ -125,6 +147,14 @@ limitations under the License.
'_groupsChanged(_groups.splices)',
],
_computeLeftCoverageRanges(coverageRanges) {
return coverageRanges.filter(range => range && range.side === 'left');
},
_computeRightCoverageRanges(coverageRanges) {
return coverageRanges.filter(range => range && range.side === 'right');
},
render(keyLocations, prefs) {
// Setting up annotation layers must happen after plugins are
// installed, and |render| satisfies the requirement, however,
@@ -184,6 +214,8 @@ limitations under the License.
this._createIntralineLayer(),
this._createTabIndicatorLayer(),
this.$.rangeLayer,
this.$.coverageLayerLeft,
this.$.coverageLayerRight,
];
// Get layers from plugins (if any).

View File

@@ -270,6 +270,15 @@ limitations under the License.
.newlineWarning.hidden {
display: none;
}
.lineNum.COVERED {
background-color: #E0F2F1;
}
.lineNum.NOT_COVERED {
background-color: #FFD1A4;
}
.lineNum.PARTIALLY_COVERED {
background: linear-gradient(to right bottom, #FFD1A4 0%, #FFD1A4 50%, #E0F2F1 50%, #E0F2F1 100%);
}
</style>
<style include="gr-syntax-theme"></style>
<div id="diffHeader" hidden$="[[_computeDiffHeaderHidden(_diffHeaderItems)]]">
@@ -289,6 +298,7 @@ limitations under the License.
<gr-diff-builder
id="diffBuilder"
comment-ranges="[[_commentRanges]]"
coverage-ranges="[[coverageRanges]]"
project-name="[[projectName]]"
diff="[[diff]]"
diff-path="[[path]]"

View File

@@ -167,6 +167,11 @@
type: Array,
value: () => [],
},
/** @type {!Array<!Gerrit.CoverageRange>} */
coverageRanges: {
type: Array,
value: () => [],
},
lineWrapping: {
type: Boolean,
value: false,