@teamscale/coverage-collector 0.0.1-beta.6 → 0.0.1-beta.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +176 -0
- package/README.md +5 -10
- package/dist/package.json +34 -24
- package/dist/src/App.d.ts +39 -0
- package/dist/src/App.js +244 -0
- package/dist/src/main.d.ts +1 -30
- package/dist/src/main.js +2 -115
- package/dist/src/receiver/CollectingServer.d.ts +5 -3
- package/dist/src/receiver/CollectingServer.js +41 -23
- package/dist/src/receiver/Session.d.ts +24 -5
- package/dist/src/receiver/Session.js +80 -16
- package/dist/src/storage/DataStorage.d.ts +40 -9
- package/dist/src/storage/DataStorage.js +68 -23
- package/dist/src/upload/ArtifactoryUpload.d.ts +6 -0
- package/dist/src/upload/ArtifactoryUpload.js +66 -0
- package/dist/src/upload/CommonUpload.d.ts +16 -0
- package/dist/src/upload/CommonUpload.js +56 -0
- package/dist/src/upload/ProxyUpload.d.ts +6 -0
- package/dist/src/upload/ProxyUpload.js +30 -0
- package/dist/src/upload/TeamscaleUpload.d.ts +6 -0
- package/dist/src/upload/TeamscaleUpload.js +54 -0
- package/dist/src/utils/ConfigParameters.d.ts +36 -0
- package/dist/src/utils/ConfigParameters.js +107 -0
- package/dist/src/utils/PrettyFileLogger.d.ts +13 -0
- package/dist/src/utils/PrettyFileLogger.js +25 -0
- package/dist/src/utils/QueryParameters.d.ts +9 -0
- package/dist/src/utils/QueryParameters.js +19 -0
- package/dist/src/utils/StdConsoleLogger.d.ts +5 -0
- package/dist/src/utils/StdConsoleLogger.js +16 -0
- package/package.json +39 -30
package/dist/src/main.js
CHANGED
|
@@ -1,118 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const argparse_1 = require("argparse");
|
|
10
|
-
const winston_1 = __importDefault(require("winston"));
|
|
11
|
-
const DataStorage_1 = require("./storage/DataStorage");
|
|
12
|
-
const CollectingServer_1 = require("./receiver/CollectingServer");
|
|
13
|
-
/**
|
|
14
|
-
* The main class of the Teamscale JavaScript Collector.
|
|
15
|
-
* Used to start-up the collector for with a given configuration.
|
|
16
|
-
*/
|
|
17
|
-
class Main {
|
|
18
|
-
/**
|
|
19
|
-
* Construct the object for parsing the command line arguments.
|
|
20
|
-
*/
|
|
21
|
-
static buildParser() {
|
|
22
|
-
const parser = new argparse_1.ArgumentParser({
|
|
23
|
-
description: 'Collector of the Teamscale JavaScript Profiler. Collects coverage information from a' +
|
|
24
|
-
'(headless) Web browser that executes code instrumented with our instrumenter.'
|
|
25
|
-
});
|
|
26
|
-
parser.add_argument('-v', '--version', { action: 'version', version: package_json_1.version });
|
|
27
|
-
parser.add_argument('-p', '--port', { help: 'The port to receive coverage information on.', default: 54678 });
|
|
28
|
-
parser.add_argument('-f', '--dump-to-file', { help: 'Target file', default: './coverage.simple' });
|
|
29
|
-
parser.add_argument('-l', '--log-to-file', { help: 'Log file', default: 'logs/collector-combined.log' });
|
|
30
|
-
parser.add_argument('-e', '--log-level', { help: 'Log level', default: 'info' });
|
|
31
|
-
parser.add_argument('-t', '--dump-after-mins', {
|
|
32
|
-
help: 'Dump the coverage information to the target file every N minutes.',
|
|
33
|
-
default: 2
|
|
34
|
-
});
|
|
35
|
-
parser.add_argument('-d', '--debug', {
|
|
36
|
-
help: 'Print received coverage information to the terminal?',
|
|
37
|
-
default: false
|
|
38
|
-
});
|
|
39
|
-
return parser;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Parse the given command line arguments into a corresponding options object.
|
|
43
|
-
*/
|
|
44
|
-
static parseArguments() {
|
|
45
|
-
const parser = this.buildParser();
|
|
46
|
-
return parser.parse_args();
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Construct the logger.
|
|
50
|
-
*/
|
|
51
|
-
static buildLogger(config) {
|
|
52
|
-
return winston_1.default.createLogger({
|
|
53
|
-
level: config.log_level,
|
|
54
|
-
format: winston_1.default.format.json(),
|
|
55
|
-
defaultMeta: {},
|
|
56
|
-
transports: [
|
|
57
|
-
new winston_1.default.transports.File({ filename: 'logs/collector-error.log', level: 'error' }),
|
|
58
|
-
new winston_1.default.transports.File({ filename: config.log_to_file.trim() }),
|
|
59
|
-
new winston_1.default.transports.Console({ format: winston_1.default.format.simple(), level: config.log_level })
|
|
60
|
-
]
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Entry point of the Teamscale JavaScript Profiler.
|
|
65
|
-
*/
|
|
66
|
-
static run() {
|
|
67
|
-
// Parse the command line arguments
|
|
68
|
-
const config = this.parseArguments();
|
|
69
|
-
// Build the logger
|
|
70
|
-
const logger = this.buildLogger(config);
|
|
71
|
-
logger.info(`Starting collector in working directory "${process.cwd()}".`);
|
|
72
|
-
logger.info(`Logging "${config.log_level}" to "${config.log_to_file}".`);
|
|
73
|
-
// Prepare the storage and the server
|
|
74
|
-
const storage = new DataStorage_1.DataStorage(logger);
|
|
75
|
-
const server = new CollectingServer_1.WebSocketCollectingServer(config.port, storage, logger);
|
|
76
|
-
// Start the server socket.
|
|
77
|
-
// ATTENTION: The server is executed asynchronously
|
|
78
|
-
server.start();
|
|
79
|
-
// Optionally, start a timer that dumps the coverage after a N seconds
|
|
80
|
-
this.maybeStartDumpTimer(config, storage, logger);
|
|
81
|
-
// Say bye bye on CTRL+C and exit the process
|
|
82
|
-
process.on('SIGINT', () => {
|
|
83
|
-
logger.info('Bye bye.');
|
|
84
|
-
process.exit();
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Start a timer for dumping the data, depending on the configuration.
|
|
89
|
-
*
|
|
90
|
-
* @param config - The config that determines whether or not to do the timed dump.
|
|
91
|
-
* @param storage - The storage with the information to dump.
|
|
92
|
-
* @param logger - The logger to use.
|
|
93
|
-
*/
|
|
94
|
-
static maybeStartDumpTimer(config, storage, logger) {
|
|
95
|
-
if (config.dump_after_mins > 0) {
|
|
96
|
-
const timer = setInterval(() => {
|
|
97
|
-
try {
|
|
98
|
-
const lines = storage.dumpToSimpleCoverageFile(config.dump_to_file);
|
|
99
|
-
logger.info(`Conducted periodic coverage dump with ${lines} lines to ${config.dump_to_file}.`);
|
|
100
|
-
}
|
|
101
|
-
catch (e) {
|
|
102
|
-
logger.error('Timed coverage dump failed.', e);
|
|
103
|
-
}
|
|
104
|
-
}, config.dump_after_mins * 1000 * 60);
|
|
105
|
-
process.on('SIGINT', () => {
|
|
106
|
-
// Stop the timed file dump
|
|
107
|
-
if (timer) {
|
|
108
|
-
clearInterval(timer);
|
|
109
|
-
}
|
|
110
|
-
// ... and do a final dump
|
|
111
|
-
const written = storage.dumpToSimpleCoverageFile(config.dump_to_file);
|
|
112
|
-
logger.info(`\nCaught interrupt signal. Written ${written} lines of the latest coverage.`);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
exports.Main = Main;
|
|
118
|
-
Main.run();
|
|
4
|
+
const App_1 = require("./App");
|
|
5
|
+
App_1.App.run();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IDataStorage } from '../storage/DataStorage';
|
|
2
|
-
import
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
3
|
/**
|
|
4
4
|
* Various constants that are used to exchange data between
|
|
5
5
|
* the instrumented application and the coverage collector.
|
|
@@ -18,7 +18,7 @@ export declare class WebSocketCollectingServer {
|
|
|
18
18
|
/**
|
|
19
19
|
* The WebSocket server component.
|
|
20
20
|
*/
|
|
21
|
-
private
|
|
21
|
+
private server;
|
|
22
22
|
/**
|
|
23
23
|
* The storage to put the received coverage information to for aggregation and further processing.
|
|
24
24
|
*/
|
|
@@ -38,7 +38,9 @@ export declare class WebSocketCollectingServer {
|
|
|
38
38
|
/**
|
|
39
39
|
* Start the server socket, handle sessions and dispatch messages.
|
|
40
40
|
*/
|
|
41
|
-
start():
|
|
41
|
+
start(): {
|
|
42
|
+
stop: () => void;
|
|
43
|
+
};
|
|
42
44
|
/**
|
|
43
45
|
* Handle a message from a client.
|
|
44
46
|
*
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -33,7 +37,7 @@ var ProtocolMessageTypes;
|
|
|
33
37
|
ProtocolMessageTypes["TYPE_SOURCEMAP"] = "s";
|
|
34
38
|
/** A message that provides coverage information */
|
|
35
39
|
ProtocolMessageTypes["TYPE_COVERAGE"] = "c";
|
|
36
|
-
})(ProtocolMessageTypes
|
|
40
|
+
})(ProtocolMessageTypes || (exports.ProtocolMessageTypes = ProtocolMessageTypes = {}));
|
|
37
41
|
/**
|
|
38
42
|
* Separates the instrumentation subject from the coverage information.
|
|
39
43
|
*/
|
|
@@ -54,15 +58,16 @@ class WebSocketCollectingServer {
|
|
|
54
58
|
commons_1.Contract.require(port > 0 && port < 65536, 'Port must be valid (range).');
|
|
55
59
|
this.storage = commons_1.Contract.requireDefined(storage);
|
|
56
60
|
this.logger = commons_1.Contract.requireDefined(logger);
|
|
57
|
-
this.server = new WebSocket.Server({ port
|
|
61
|
+
this.server = new WebSocket.Server({ port });
|
|
58
62
|
}
|
|
59
63
|
/**
|
|
60
64
|
* Start the server socket, handle sessions and dispatch messages.
|
|
61
65
|
*/
|
|
62
66
|
start() {
|
|
63
|
-
|
|
67
|
+
var _a, _b;
|
|
68
|
+
this.logger.info(`Starting server on port ${(_a = this.server) === null || _a === void 0 ? void 0 : _a.options.port}.`);
|
|
64
69
|
// Handle new connections from clients
|
|
65
|
-
this.server.on('connection', (webSocket, req) => {
|
|
70
|
+
(_b = this.server) === null || _b === void 0 ? void 0 : _b.on('connection', (webSocket, req) => {
|
|
66
71
|
let session = new Session_1.Session(req.socket, this.storage, this.logger);
|
|
67
72
|
this.logger.debug(`Connection from: ${req.socket.remoteAddress}`);
|
|
68
73
|
// Handle disconnecting clients
|
|
@@ -76,8 +81,8 @@ class WebSocketCollectingServer {
|
|
|
76
81
|
});
|
|
77
82
|
// Handle incoming messages
|
|
78
83
|
webSocket.on('message', (message) => {
|
|
79
|
-
if (session &&
|
|
80
|
-
this.handleMessage(session, message);
|
|
84
|
+
if (session && Buffer.isBuffer(message)) {
|
|
85
|
+
void this.handleMessage(session, message);
|
|
81
86
|
}
|
|
82
87
|
});
|
|
83
88
|
// Handle errors
|
|
@@ -85,6 +90,13 @@ class WebSocketCollectingServer {
|
|
|
85
90
|
this.logger.error('Error on server socket triggered.', e);
|
|
86
91
|
});
|
|
87
92
|
});
|
|
93
|
+
return {
|
|
94
|
+
stop: () => {
|
|
95
|
+
var _a;
|
|
96
|
+
(_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
|
|
97
|
+
this.server = null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
88
100
|
}
|
|
89
101
|
/**
|
|
90
102
|
* Handle a message from a client.
|
|
@@ -92,17 +104,18 @@ class WebSocketCollectingServer {
|
|
|
92
104
|
* @param session - The session that has been started for the client.
|
|
93
105
|
* @param message - The message to handle.
|
|
94
106
|
*/
|
|
95
|
-
handleMessage(session, message) {
|
|
107
|
+
async handleMessage(session, message) {
|
|
96
108
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
109
|
+
const messageType = message.toString('utf8', 0, 1);
|
|
110
|
+
if (messageType.startsWith(ProtocolMessageTypes.TYPE_SOURCEMAP)) {
|
|
111
|
+
await this.handleSourcemapMessage(session, message.subarray(1));
|
|
99
112
|
}
|
|
100
|
-
else if (
|
|
101
|
-
this.handleCoverageMessage(session, message.
|
|
113
|
+
else if (messageType.startsWith(ProtocolMessageTypes.TYPE_COVERAGE)) {
|
|
114
|
+
await this.handleCoverageMessage(session, message.subarray(1));
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
catch (e) {
|
|
105
|
-
this.logger.error(`Error while processing message starting with ${message.
|
|
118
|
+
this.logger.error(`Error while processing message starting with ${message.toString('utf8', 0, Math.min(50, message.length))}`);
|
|
106
119
|
this.logger.error(e.message);
|
|
107
120
|
}
|
|
108
121
|
}
|
|
@@ -112,13 +125,13 @@ class WebSocketCollectingServer {
|
|
|
112
125
|
* @param session - The session to handle the message for.
|
|
113
126
|
* @param body - The body of the message (to be parsed).
|
|
114
127
|
*/
|
|
115
|
-
handleSourcemapMessage(session, body) {
|
|
116
|
-
const fileIdSeparatorPosition = body.indexOf(INSTRUMENTATION_SUBJECT_SEPARATOR);
|
|
128
|
+
async handleSourcemapMessage(session, body) {
|
|
129
|
+
const fileIdSeparatorPosition = body.indexOf(INSTRUMENTATION_SUBJECT_SEPARATOR.charCodeAt(0));
|
|
117
130
|
if (fileIdSeparatorPosition > -1) {
|
|
118
|
-
const fileId = body.
|
|
131
|
+
const fileId = body.toString('utf8', 0, fileIdSeparatorPosition).trim();
|
|
119
132
|
this.logger.debug(`Received source map information for ${fileId}`);
|
|
120
|
-
const sourcemap = body.
|
|
121
|
-
session.putSourcemap(fileId, sourcemap);
|
|
133
|
+
const sourcemap = body.subarray(fileIdSeparatorPosition + 1);
|
|
134
|
+
await session.putSourcemap(fileId, sourcemap);
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
/**
|
|
@@ -127,16 +140,21 @@ class WebSocketCollectingServer {
|
|
|
127
140
|
* @param session - The session to handle the message for.
|
|
128
141
|
* @param body - The body of the message (to be parsed).
|
|
129
142
|
*/
|
|
130
|
-
handleCoverageMessage(session, body) {
|
|
143
|
+
async handleCoverageMessage(session, body) {
|
|
131
144
|
var _a;
|
|
132
|
-
const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+)
|
|
133
|
-
const matches = bodyPattern.exec(body);
|
|
145
|
+
const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+(:\d+:\d+)?\s+)*(\d+:\d+(:\d+:\d+)?)))/;
|
|
146
|
+
const matches = bodyPattern.exec(body.toString());
|
|
134
147
|
if (matches === null || matches === void 0 ? void 0 : matches.groups) {
|
|
135
148
|
const fileId = matches.groups.fileId;
|
|
136
149
|
const positions = ((_a = matches.groups.positions) !== null && _a !== void 0 ? _a : '').split(/\s+/);
|
|
137
150
|
for (const position of positions) {
|
|
138
|
-
const
|
|
139
|
-
|
|
151
|
+
const positionParts = position.split(':');
|
|
152
|
+
if (positionParts.length === 2) {
|
|
153
|
+
session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]));
|
|
154
|
+
}
|
|
155
|
+
else if (positionParts.length === 4) {
|
|
156
|
+
session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]), Number.parseInt(positionParts[3]));
|
|
157
|
+
}
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
160
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
2
3
|
import { Socket } from 'net';
|
|
3
4
|
import { IDataStorage } from '../storage/DataStorage';
|
|
4
|
-
import
|
|
5
|
+
import Logger from 'bunyan';
|
|
6
|
+
/**
|
|
7
|
+
* Coverage information that has not been mapped back to the
|
|
8
|
+
* original code using a source map.
|
|
9
|
+
*/
|
|
10
|
+
export type UnmappedCoverage = {
|
|
11
|
+
fileId: string;
|
|
12
|
+
startLine: number;
|
|
13
|
+
startColumn: number;
|
|
14
|
+
endLine: number;
|
|
15
|
+
endColumn: number;
|
|
16
|
+
};
|
|
5
17
|
/**
|
|
6
18
|
* The session maintains the relevant information for a client.
|
|
7
19
|
* One session is created for each client.
|
|
@@ -22,6 +34,10 @@ export declare class Session {
|
|
|
22
34
|
* server per browser window.
|
|
23
35
|
*/
|
|
24
36
|
private readonly sourceMaps;
|
|
37
|
+
/**
|
|
38
|
+
* Unmapped coverage information.
|
|
39
|
+
*/
|
|
40
|
+
private readonly unmappedCoverage;
|
|
25
41
|
/**
|
|
26
42
|
* The logger to use.
|
|
27
43
|
*/
|
|
@@ -43,10 +59,12 @@ export declare class Session {
|
|
|
43
59
|
* This method also conducts the mapping based on the source map.
|
|
44
60
|
*
|
|
45
61
|
* @param fileId - The identifier of the instrumented bundle (file).
|
|
46
|
-
* @param
|
|
47
|
-
* @param
|
|
62
|
+
* @param startLine - The line number within the bundle the range starts.
|
|
63
|
+
* @param startColumn - The column in the given `startLine` on that the range starts (inclusive).
|
|
64
|
+
* @param endLine - The line number within the bundle the range ends.
|
|
65
|
+
* @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
|
|
48
66
|
*/
|
|
49
|
-
putCoverage(fileId: string,
|
|
67
|
+
putCoverage(fileId: string, startLine: number, startColumn: number, endLine: number, endColumn: number): boolean;
|
|
50
68
|
/**
|
|
51
69
|
* Map to the original file position.
|
|
52
70
|
*
|
|
@@ -61,7 +79,8 @@ export declare class Session {
|
|
|
61
79
|
* @param fileId - The identifier of the file bundle.
|
|
62
80
|
* @param sourceMapText - The actual source map.
|
|
63
81
|
*/
|
|
64
|
-
putSourcemap(fileId: string, sourceMapText:
|
|
82
|
+
putSourcemap(fileId: string, sourceMapText: Buffer): Promise<void>;
|
|
83
|
+
private processUnmappedCoverageOf;
|
|
65
84
|
/**
|
|
66
85
|
* Destroy the session and free the memory it allocates.
|
|
67
86
|
* In particular the sourcemaps are freed (important to not run out of memory!).
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
3
|
if (k2 === undefined) k2 = k;
|
|
4
|
-
Object.
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
5
9
|
}) : (function(o, m, k, k2) {
|
|
6
10
|
if (k2 === undefined) k2 = k;
|
|
7
11
|
o[k2] = m[k];
|
|
@@ -40,6 +44,7 @@ class Session {
|
|
|
40
44
|
this.storage = commons_1.Contract.requireDefined(storage);
|
|
41
45
|
this.logger = commons_1.Contract.requireDefined(logger);
|
|
42
46
|
this.sourceMaps = new Map();
|
|
47
|
+
this.unmappedCoverage = new Map();
|
|
43
48
|
this.projectId = ''; // We currently only support coverage for one project.
|
|
44
49
|
}
|
|
45
50
|
/**
|
|
@@ -47,17 +52,66 @@ class Session {
|
|
|
47
52
|
* This method also conducts the mapping based on the source map.
|
|
48
53
|
*
|
|
49
54
|
* @param fileId - The identifier of the instrumented bundle (file).
|
|
50
|
-
* @param
|
|
51
|
-
* @param
|
|
55
|
+
* @param startLine - The line number within the bundle the range starts.
|
|
56
|
+
* @param startColumn - The column in the given `startLine` on that the range starts (inclusive).
|
|
57
|
+
* @param endLine - The line number within the bundle the range ends.
|
|
58
|
+
* @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
|
|
52
59
|
*/
|
|
53
|
-
putCoverage(fileId,
|
|
54
|
-
|
|
55
|
-
if
|
|
56
|
-
|
|
60
|
+
putCoverage(fileId, startLine, startColumn, endLine, endColumn) {
|
|
61
|
+
var _a, _b;
|
|
62
|
+
// Delay the mapping if the sourcemap has not yet arrived
|
|
63
|
+
if (!this.sourceMaps.has(fileId)) {
|
|
64
|
+
let unmappedForFile = this.unmappedCoverage.get(fileId);
|
|
65
|
+
if (!unmappedForFile) {
|
|
66
|
+
unmappedForFile = [];
|
|
67
|
+
this.unmappedCoverage.set(fileId, unmappedForFile);
|
|
68
|
+
}
|
|
69
|
+
unmappedForFile.push({ endColumn, endLine, fileId, startColumn, startLine });
|
|
70
|
+
return false;
|
|
57
71
|
}
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
let mapped = false;
|
|
73
|
+
// Iterate over the lines to scan
|
|
74
|
+
let line = startLine;
|
|
75
|
+
while (line <= endLine) {
|
|
76
|
+
// Determine the column range to consider for this line
|
|
77
|
+
let scanFromColumn;
|
|
78
|
+
if (line === startLine) {
|
|
79
|
+
scanFromColumn = startColumn;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
scanFromColumn = 0;
|
|
83
|
+
}
|
|
84
|
+
let scanToColumn;
|
|
85
|
+
if (line === endLine) {
|
|
86
|
+
scanToColumn = endColumn;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Since we do not know the length of the different lines, we assume
|
|
90
|
+
// all to end in the lager of `endColumn` and `startColumn`.
|
|
91
|
+
// A better estimate (or the correct value) is supposed to be implemented
|
|
92
|
+
// in context of TS-30077.
|
|
93
|
+
scanToColumn = Math.max(endColumn, startColumn);
|
|
94
|
+
}
|
|
95
|
+
let column = scanFromColumn;
|
|
96
|
+
let lastCoveredLine = -1;
|
|
97
|
+
while (column <= scanToColumn) {
|
|
98
|
+
const originalPosition = this.mapToOriginal(fileId, line, column);
|
|
99
|
+
if (originalPosition.line && originalPosition.source) {
|
|
100
|
+
if (lastCoveredLine !== originalPosition.line) {
|
|
101
|
+
this.storage.putCoverage(this.projectId, originalPosition.source, [originalPosition.line]);
|
|
102
|
+
mapped = true;
|
|
103
|
+
lastCoveredLine = originalPosition.line;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Step to the next column to map back to the original.
|
|
107
|
+
// `originalPosition.name` is the token on the position, that is, if it is present
|
|
108
|
+
// we increment the column by its length.
|
|
109
|
+
column = column + Math.max(1, (_b = (_a = originalPosition.name) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 1);
|
|
110
|
+
}
|
|
111
|
+
// And the next line
|
|
112
|
+
line++;
|
|
60
113
|
}
|
|
114
|
+
return mapped;
|
|
61
115
|
}
|
|
62
116
|
/**
|
|
63
117
|
* Map to the original file position.
|
|
@@ -82,14 +136,24 @@ class Session {
|
|
|
82
136
|
* @param fileId - The identifier of the file bundle.
|
|
83
137
|
* @param sourceMapText - The actual source map.
|
|
84
138
|
*/
|
|
85
|
-
putSourcemap(fileId, sourceMapText) {
|
|
86
|
-
const rawSourceMap = JSON.parse(sourceMapText);
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
this.sourceMaps.set(fileId,
|
|
90
|
-
|
|
91
|
-
|
|
139
|
+
async putSourcemap(fileId, sourceMapText) {
|
|
140
|
+
const rawSourceMap = JSON.parse(sourceMapText.toString());
|
|
141
|
+
try {
|
|
142
|
+
const sourceMapConsumer = await new sourceMap.SourceMapConsumer(rawSourceMap);
|
|
143
|
+
this.sourceMaps.set(fileId, sourceMapConsumer);
|
|
144
|
+
this.processUnmappedCoverageOf(fileId);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
92
147
|
this.logger.error(`Consuming source map failed! ${e}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
processUnmappedCoverageOf(fileId) {
|
|
151
|
+
var _a;
|
|
152
|
+
const unmapped = (_a = this.unmappedCoverage.get(fileId)) !== null && _a !== void 0 ? _a : [];
|
|
153
|
+
unmapped.forEach(entry => {
|
|
154
|
+
if (!this.putCoverage(entry.fileId, entry.startLine, entry.startColumn, entry.endLine, entry.endColumn)) {
|
|
155
|
+
this.storage.signalUnmappedCoverage(this.projectId);
|
|
156
|
+
}
|
|
93
157
|
});
|
|
94
158
|
}
|
|
95
159
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Logger from 'bunyan';
|
|
2
2
|
/**
|
|
3
3
|
* Lines covered for the specified file.
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
5
|
+
export type FileCoverage = {
|
|
6
6
|
/** Name of the file in the origin */
|
|
7
7
|
sourceFile: string;
|
|
8
8
|
/** Lines covered */
|
|
@@ -21,11 +21,14 @@ export interface IReadableStorage {
|
|
|
21
21
|
*/
|
|
22
22
|
getCoverageBySourceFile(project: string): IterableIterator<FileCoverage> | undefined;
|
|
23
23
|
/**
|
|
24
|
-
* Write the coverage to the specified file.
|
|
24
|
+
* Write the coverage to the specified file. A timestamp will be appended to the provided file path.
|
|
25
25
|
*
|
|
26
|
-
* @param
|
|
26
|
+
* @param coverageFolder - Full path of the file to write the coverage to.
|
|
27
|
+
* @param date - Date to use for the appended timestamp
|
|
28
|
+
*
|
|
29
|
+
* @return The number of lines written
|
|
27
30
|
*/
|
|
28
|
-
dumpToSimpleCoverageFile(
|
|
31
|
+
dumpToSimpleCoverageFile(coverageFolder: string, date: Date): [string, number];
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Storage interface for writing information.
|
|
@@ -46,6 +49,10 @@ export interface IWriteableStorage {
|
|
|
46
49
|
* @param project - The project to add the information to.
|
|
47
50
|
*/
|
|
48
51
|
signalUnmappedCoverage(project: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Discard the coverage information that has been collected up to this point.
|
|
54
|
+
*/
|
|
55
|
+
discardCollectedCoverage(): void;
|
|
49
56
|
}
|
|
50
57
|
/**
|
|
51
58
|
* Union of write and read interface.
|
|
@@ -89,7 +96,7 @@ export declare class DataStorage implements IDataStorage {
|
|
|
89
96
|
/**
|
|
90
97
|
* Coverage information by project.
|
|
91
98
|
*/
|
|
92
|
-
private
|
|
99
|
+
private coverageByProject;
|
|
93
100
|
/**
|
|
94
101
|
* Logger to use.
|
|
95
102
|
*/
|
|
@@ -98,6 +105,10 @@ export declare class DataStorage implements IDataStorage {
|
|
|
98
105
|
* Times unmapped coverage received.
|
|
99
106
|
*/
|
|
100
107
|
private timesUnmappedCoverage;
|
|
108
|
+
/**
|
|
109
|
+
* Date format for the timestamp appended to the coverage files
|
|
110
|
+
*/
|
|
111
|
+
readonly DATE_FORMAT = "YYYY-MM-DD-HH-mm-ss.SSS";
|
|
101
112
|
/**
|
|
102
113
|
* Constructs the data storage.
|
|
103
114
|
*
|
|
@@ -118,7 +129,7 @@ export declare class DataStorage implements IDataStorage {
|
|
|
118
129
|
*
|
|
119
130
|
* @param sourceFile - The file name to normalize, produced by the instrumenter.
|
|
120
131
|
*/
|
|
121
|
-
private normalizeSourceFileName;
|
|
132
|
+
private static normalizeSourceFileName;
|
|
122
133
|
/**
|
|
123
134
|
* {@inheritDoc IWriteableStorage.signalUnmappedCoverage}
|
|
124
135
|
*/
|
|
@@ -128,11 +139,31 @@ export declare class DataStorage implements IDataStorage {
|
|
|
128
139
|
*/
|
|
129
140
|
getCoverageBySourceFile(project: string): IterableIterator<FileCoverage> | undefined;
|
|
130
141
|
/**
|
|
131
|
-
*
|
|
142
|
+
* @inheritDoc
|
|
143
|
+
*/
|
|
144
|
+
dumpToSimpleCoverageFile(coverageFolder: string, date: Date): [string, number];
|
|
145
|
+
/**
|
|
146
|
+
* Set the collected coverage to 0 for all projects
|
|
147
|
+
* @private
|
|
132
148
|
*/
|
|
133
|
-
|
|
149
|
+
private resetCoverage;
|
|
150
|
+
/**
|
|
151
|
+
* Appends the timestamp given with date to the coverageFolder (before the file ending if there is one)
|
|
152
|
+
* @param coverageFolder Path to the coverage file
|
|
153
|
+
* @param date Represents the timestamp to be appended with the format {@link DataStorage.DATE_FORMAT}
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
private initCoverageFile;
|
|
157
|
+
/**
|
|
158
|
+
* Generate simple coverage format for the collected coverage
|
|
159
|
+
*/
|
|
160
|
+
private toSimpleCoverage;
|
|
134
161
|
/**
|
|
135
162
|
* {@inheritDoc IReadableStorage.getProjects}
|
|
136
163
|
*/
|
|
137
164
|
getProjects(): string[];
|
|
165
|
+
/**
|
|
166
|
+
* {@inheritDoc IWritableStorage.discardCollectedCoverage}
|
|
167
|
+
*/
|
|
168
|
+
discardCollectedCoverage(): void;
|
|
138
169
|
}
|