-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathourFileCoverage.ts
More file actions
180 lines (166 loc) · 8.01 KB
/
ourFileCoverage.ts
File metadata and controls
180 lines (166 loc) · 8.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import * as vscode from 'vscode';
import logger from './logger';
import { IServerSpec } from '@intersystems-community/intersystems-servermanager';
import { makeRESTRequest } from './makeRESTRequest';
import { osAPI } from './extension';
export class OurFileCoverage extends vscode.FileCoverage {
public readonly name: string;
public readonly codeUnit: string;
private coverageIndex: number;
constructor(coverageIndex: number, name: string, codeUnit: string, uri: vscode.Uri, statementCoverage: vscode.TestCoverageCount, branchCoverage?: vscode.TestCoverageCount, declarationCoverage?: vscode.TestCoverageCount, includesTests?: vscode.TestItem[]) {
super(uri, statementCoverage, branchCoverage, declarationCoverage, includesTests);
this.coverageIndex = coverageIndex;
this.name = name;
this.codeUnit = codeUnit;
}
async loadDetailedCoverage(fromTestItem?: vscode.TestItem): Promise<vscode.FileCoverageDetail[]> {
logger.debug(`loadDetailedCoverage invoked for ${this.codeUnit} (${this.uri.toString()})`);
const detailedCoverage: vscode.FileCoverageDetail[] = [];
const server = await osAPI.asyncServerForUri(this.uri);
const serverSpec: IServerSpec = {
username: server.username,
password: server.password,
name: server.serverName,
webServer: {
host: server.host,
port: server.port,
pathPrefix: server.pathPrefix,
scheme: server.scheme
}
};
const namespace: string = server.namespace.toUpperCase();
// When ObjectScript extension spreads method arguments over multiple lines, we need to compute offsets
const mapOffsets: Map<string, number> = new Map();
if (vscode.workspace.getConfiguration('objectscript', this.uri).get<boolean>('multilineMethodArgs', false)) {
const response = await makeRESTRequest(
"POST",
serverSpec,
{ apiVersion: 1, namespace, path: "/action/query" },
{
query: "SELECT Name as Method, SUM( CASE WHEN $LENGTH(FormalSpec, ',') > 1 THEN $LENGTH(FormalSpec, ',') ELSE 0 END ) OVER ( ORDER BY SequenceNumber ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS Offset FROM %Dictionary.MethodDefinition WHERE parent = ?",
parameters: [this.name],
},
);
if (response) {
response?.data?.result?.content?.forEach(element => {
const methodName = element.Method;
const offset = Number(element.Offset);
mapOffsets.set(methodName, offset);
});
}
}
// Get map of lines to methods
const mapMethodsInCoverage: Map<number, string> = new Map();
const mapMethodsInDocument: Map<number, string> = new Map();
let response = await makeRESTRequest(
"POST",
serverSpec,
{ apiVersion: 1, namespace, path: "/action/query" },
{
query: "SELECT element_key Line, LineToMethodMap Method FROM TestCoverage_Data.CodeUnit_LineToMethodMap WHERE CodeUnit = ? ORDER BY Line",
parameters: [this.codeUnit],
},
);
if (response) {
response?.data?.result?.content?.forEach(element => {
const thisLine = Number(element.Line);
mapMethodsInCoverage.set(thisLine, element.Method);
mapMethodsInDocument.set(thisLine + (mapOffsets.get(element.Method) || 0), element.Method);
});
}
let testPath = 'all tests';
if (fromTestItem && serverSpec.username) {
// If a specific test item is provided, use its ID to determine the test path we want data from
const dottedClassname = fromTestItem.id.split(':')[3];
testPath = serverSpec.username.toLowerCase() + '\\' + dottedClassname.split('.').slice(0,-1).join('\\') + ':' + dottedClassname;
}
// Get the coverage results
response = await makeRESTRequest(
"POST",
serverSpec,
{ apiVersion: 1, namespace, path: "/action/query" },
{
query: `SELECT vscode_dc_testingmanager.CoverageManager_tmInt8Bitstring(cu.ExecutableLines) i8bsExecutableLines, vscode_dc_testingmanager.CoverageManager_tmInt8Bitstring(cov.CoveredLines) i8bsCoveredLines FROM TestCoverage_Data.CodeUnit cu, TestCoverage_Data.Coverage cov WHERE cu.Hash = cov.Hash AND Run = ? AND cu.Hash = ? AND TestPath = ?`,
parameters: [this.coverageIndex, this.codeUnit, testPath],
},
);
if (response) {
response?.data?.result?.content?.forEach(element => {
logger.debug(`getFileCoverageResults element: ${JSON.stringify(element)}`);
// Process the Uint8Bitstring values for executable and covered lines
const i8bsExecutableLines = element.i8bsExecutableLines;
const i8bsCoveredLines = element.i8bsCoveredLines;
let offset = 0; // We will add this to line number in coverage results to get line number in document, adjusted for multiline method arguments
for (let lineChunk = 0; lineChunk < i8bsExecutableLines.length; lineChunk++) {
const executableLines = i8bsExecutableLines.charCodeAt(lineChunk);
const coveredLines = i8bsCoveredLines.charCodeAt(lineChunk);
for (let bitIndex = 0; bitIndex < 8; bitIndex++) {
const lineNumberOfCoverage = lineChunk * 8 + bitIndex + 1;
// On a method declaration line we should recompute the offset
const method = mapMethodsInCoverage.get(lineNumberOfCoverage);
if (method) {
offset = (mapOffsets.get(method) || offset);
}
if ((executableLines & (1 << bitIndex)) !== 0) {
const isCovered = (coveredLines & (1 << bitIndex)) !== 0;
const lineNumberOfDocument = lineNumberOfCoverage + offset;
const range = new vscode.Range(new vscode.Position(lineNumberOfDocument - 1, 0), new vscode.Position(lineNumberOfDocument - 1, Number.MAX_VALUE));
const statementCoverage = new vscode.StatementCoverage(isCovered, range);
detailedCoverage.push(statementCoverage);
}
}
}
});
}
// Add declaration (method) coverage
response = await makeRESTRequest(
"POST",
serverSpec,
{ apiVersion: 1, namespace, path: "/action/query" },
{
query: `
SELECT clm.element_key AS StartLine,
clm.LineToMethodMap AS Method,
CASE
WHEN md.Description IS NULL THEN 0
ELSE $LENGTH(md.Description, CHAR(13))
END AS DescriptionLineCount
FROM TestCoverage_Data.CodeUnit_LineToMethodMap clm
JOIN TestCoverage_Data.CodeUnit cu ON clm.CodeUnit = cu.Hash
LEFT JOIN %Dictionary.MethodDefinition md ON md.Name = clm.LineToMethodMap
AND md.parent = cu.Name
WHERE clm.CodeUnit = ?
ORDER BY StartLine
`,
parameters: [this.codeUnit],
},
);
if (response) {
let previousMethod = "";
let previousStartLine = 0;
let startOffset = 0;
let endOffset = 0;
response?.data?.result?.content?.forEach(element => {
const currentMethod = element.Method;
const currentStartLine = Number(element.StartLine);
const descriptionLineCount = Number(element.DescriptionLineCount);
if (previousMethod && previousStartLine) {
const start = new vscode.Position(previousStartLine - 1 + startOffset, 0);
const end = new vscode.Position(currentStartLine - 2 + endOffset - descriptionLineCount, Number.MAX_VALUE);
detailedCoverage.push(new vscode.DeclarationCoverage(previousMethod, true, new vscode.Range(start, end)));
}
startOffset = endOffset;
endOffset = (mapOffsets.get(currentMethod) || endOffset);
previousMethod = currentMethod;
previousStartLine = currentStartLine - descriptionLineCount;
});
// Add the final method (if any)
if (previousMethod && previousStartLine) {
const start = new vscode.Position(previousStartLine - 1 + startOffset, 0);
const end = new vscode.Position(Number.MAX_VALUE, Number.MAX_VALUE); // Hack that will cover the rest of the file, not just the the final method
detailedCoverage.push(new vscode.DeclarationCoverage(previousMethod, true, new vscode.Range(start, end)));
}
}
return detailedCoverage;
}
}