Add a coverage layer to gr-diff
Change-Id: Iaa768824a0c32694a9f0dbfe29522b15bb7d7198
This commit is contained in:
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
})();
|
||||
@@ -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>
|
||||
@@ -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).
|
||||
|
||||
@@ -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]]"
|
||||
|
||||
@@ -167,6 +167,11 @@
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
/** @type {!Array<!Gerrit.CoverageRange>} */
|
||||
coverageRanges: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
lineWrapping: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
|
||||
Reference in New Issue
Block a user