@teamscale/coverage-collector 0.0.1-beta.5 → 0.0.1-beta.51
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/README.md +1 -6
- package/dist/package.json +19 -7
- 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 +31 -14
- package/dist/src/receiver/Session.d.ts +23 -5
- package/dist/src/receiver/Session.js +79 -15
- package/dist/src/storage/DataStorage.d.ts +39 -8
- 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 +55 -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 +24 -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 +15 -0
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ files in the [Teamscale Simple Coverage Format](https://docs.teamscale.com/refer
|
|
|
9
9
|
The Teamscale JavaScript Profiler consists of this Coverage Collector and the
|
|
10
10
|
[JavaScript Instrumenter](https://www.npmjs.com/package/@teamscale/javascript-instrumenter).
|
|
11
11
|
More details on using them (in combination) can be found
|
|
12
|
-
|
|
12
|
+
in the [Teamscale Documentation](https://docs.teamscale.com/howto/recording-test-coverage-for-javascript/).
|
|
13
13
|
|
|
14
14
|
The JavaScript Coverage Collector starts a server process that listens for
|
|
15
15
|
code coverage information from manually or automatically exercised (tested)
|
|
@@ -49,8 +49,3 @@ for example, by setting a corresponding environment variable.
|
|
|
49
49
|
export NODE_OPTIONS="$NODE_OPTIONS --max-old-space-size=8192"
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
## Publishing
|
|
53
|
-
|
|
54
|
-
The list of files to publish is defined by the `files` attribute in `package.json`.
|
|
55
|
-
The actual files packed by npm can be listed by running `npx npm-packlist`.
|
|
56
|
-
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamscale/coverage-collector",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.51",
|
|
4
4
|
"description": "Collector for JavaScript code coverage information",
|
|
5
5
|
"main": "dist/src/main.js",
|
|
6
6
|
"bin": "dist/src/main.js",
|
|
@@ -12,22 +12,30 @@
|
|
|
12
12
|
"url": "https://github.com/cqse/teamscale-javascript-profiler.git"
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
+
"prepublishOnly": "yarn clean && yarn build",
|
|
15
16
|
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
16
17
|
"build": "tsc",
|
|
17
18
|
"collector": "node dist/src/main.js",
|
|
18
|
-
"test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --
|
|
19
|
+
"test": "yarn build && NODE_OPTIONS='--experimental-vm-modules' jest --coverage --silent=true"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist/**/*"
|
|
22
23
|
],
|
|
23
24
|
"dependencies": {
|
|
24
|
-
"@cqse/commons": "^0.0.1-beta.
|
|
25
|
+
"@cqse/commons": "^0.0.1-beta.45",
|
|
25
26
|
"argparse": "^2.0.1",
|
|
26
|
-
"async": "^3.2.
|
|
27
|
+
"async": "^3.2.4",
|
|
28
|
+
"axios": "^0.24.0",
|
|
29
|
+
"bunyan": "^1.8.15",
|
|
30
|
+
"date-and-time": "^2.3.1",
|
|
31
|
+
"dotenv": "^14.1.0",
|
|
32
|
+
"express": "^4.18.1",
|
|
33
|
+
"form-data": "^4.0.0",
|
|
34
|
+
"mkdirp": "^1.0.4",
|
|
27
35
|
"rxjs": "^7.1.0",
|
|
28
|
-
"source-map": "^0.7.
|
|
36
|
+
"source-map": "^0.7.4",
|
|
37
|
+
"tmp": "^0.2.1",
|
|
29
38
|
"typescript-optional": "^2.0.1",
|
|
30
|
-
"winston": "^3.3.3",
|
|
31
39
|
"ws": "^7.4.5"
|
|
32
40
|
},
|
|
33
41
|
"devDependencies": {
|
|
@@ -35,14 +43,18 @@
|
|
|
35
43
|
"@babel/preset-env": "^7.14.1",
|
|
36
44
|
"@types/argparse": "^2.0.5",
|
|
37
45
|
"@types/async": "^3.2.6",
|
|
46
|
+
"@types/bunyan": "^1.8.8",
|
|
47
|
+
"@types/express": "^4.17.13",
|
|
38
48
|
"@types/jest": "^27.0.1",
|
|
49
|
+
"@types/mkdirp": "^1.0.2",
|
|
39
50
|
"@types/node": "^15.0.1",
|
|
40
51
|
"@types/source-map": "^0.5.7",
|
|
41
|
-
"@types/
|
|
52
|
+
"@types/tmp": "^0.2.3",
|
|
42
53
|
"@types/ws": "^7.4.2",
|
|
43
54
|
"babel-jest": "^27.2.0",
|
|
44
55
|
"esbuild": "^0.13.4",
|
|
45
56
|
"jest": "^27.2.0",
|
|
57
|
+
"mockttp": "^3.4.0",
|
|
46
58
|
"rimraf": "^3.0.2",
|
|
47
59
|
"ts-jest": "^27.0.5",
|
|
48
60
|
"ts-node": "^10.2.1",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { ConfigParameters } from './utils/ConfigParameters';
|
|
3
|
+
/**
|
|
4
|
+
* The main class of the Teamscale JavaScript Collector.
|
|
5
|
+
* Used to start the collector with a given configuration.
|
|
6
|
+
*/
|
|
7
|
+
export declare class App {
|
|
8
|
+
/**
|
|
9
|
+
* Parse the given command line arguments into a corresponding options object.
|
|
10
|
+
*/
|
|
11
|
+
private static parseArguments;
|
|
12
|
+
/**
|
|
13
|
+
* Construct the logger.
|
|
14
|
+
*/
|
|
15
|
+
private static buildLogger;
|
|
16
|
+
/**
|
|
17
|
+
* Entry point of the Teamscale JavaScript Profiler.
|
|
18
|
+
*/
|
|
19
|
+
static run(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Run the collector with the given configuration options.
|
|
22
|
+
*
|
|
23
|
+
* @param config - The configuration options to run the collector with.
|
|
24
|
+
*/
|
|
25
|
+
static runWithConfig(config: ConfigParameters): {
|
|
26
|
+
stop: () => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Start a timer for dumping the data, depending on the configuration.
|
|
30
|
+
*
|
|
31
|
+
* @param config - The config that determines whether to do the timed dump or not.
|
|
32
|
+
* @param storage - The storage with the information to dump.
|
|
33
|
+
* @param logger - The logger to use.
|
|
34
|
+
*/
|
|
35
|
+
private static maybeStartDumpTimer;
|
|
36
|
+
private static dumpCoverage;
|
|
37
|
+
private static uploadCoverage;
|
|
38
|
+
private static startControlServer;
|
|
39
|
+
}
|
package/dist/src/App.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
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);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.App = void 0;
|
|
30
|
+
const bunyan_1 = __importDefault(require("bunyan"));
|
|
31
|
+
const DataStorage_1 = require("./storage/DataStorage");
|
|
32
|
+
const CollectingServer_1 = require("./receiver/CollectingServer");
|
|
33
|
+
require("dotenv/config");
|
|
34
|
+
const fs = __importStar(require("fs"));
|
|
35
|
+
const ConfigParameters_1 = require("./utils/ConfigParameters");
|
|
36
|
+
const mkdirp_1 = __importDefault(require("mkdirp"));
|
|
37
|
+
const path_1 = __importDefault(require("path"));
|
|
38
|
+
const StdConsoleLogger_1 = require("./utils/StdConsoleLogger");
|
|
39
|
+
const PrettyFileLogger_1 = require("./utils/PrettyFileLogger");
|
|
40
|
+
const express_1 = __importDefault(require("express"));
|
|
41
|
+
const TeamscaleUpload_1 = require("./upload/TeamscaleUpload");
|
|
42
|
+
const CommonUpload_1 = require("./upload/CommonUpload");
|
|
43
|
+
const ArtifactoryUpload_1 = require("./upload/ArtifactoryUpload");
|
|
44
|
+
/**
|
|
45
|
+
* The main class of the Teamscale JavaScript Collector.
|
|
46
|
+
* Used to start the collector with a given configuration.
|
|
47
|
+
*/
|
|
48
|
+
class App {
|
|
49
|
+
/**
|
|
50
|
+
* Parse the given command line arguments into a corresponding options object.
|
|
51
|
+
*/
|
|
52
|
+
static parseArguments() {
|
|
53
|
+
const parser = (0, ConfigParameters_1.buildParameterParser)();
|
|
54
|
+
return parser.parse_args();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Construct the logger.
|
|
58
|
+
*/
|
|
59
|
+
static buildLogger(config) {
|
|
60
|
+
const logfilePath = config.log_to_file.trim();
|
|
61
|
+
mkdirp_1.default.sync(path_1.default.dirname(logfilePath));
|
|
62
|
+
const logLevel = config.log_level;
|
|
63
|
+
const logger = bunyan_1.default.createLogger({
|
|
64
|
+
name: 'Collector',
|
|
65
|
+
streams: [
|
|
66
|
+
// console output
|
|
67
|
+
{ level: logLevel, stream: new StdConsoleLogger_1.StdConsoleLogger(), type: 'raw' },
|
|
68
|
+
// default log file
|
|
69
|
+
{ level: logLevel, stream: new PrettyFileLogger_1.PrettyFileLogger(fs.createWriteStream(logfilePath)), type: 'raw' }
|
|
70
|
+
]
|
|
71
|
+
});
|
|
72
|
+
// If the given flag is set, we also log with a JSON-like format
|
|
73
|
+
if (config.json_log) {
|
|
74
|
+
logger.addStream({ level: logLevel, path: `${logfilePath}.json` });
|
|
75
|
+
}
|
|
76
|
+
return logger;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Entry point of the Teamscale JavaScript Profiler.
|
|
80
|
+
*/
|
|
81
|
+
static run() {
|
|
82
|
+
// Parse the command line arguments
|
|
83
|
+
const config = this.parseArguments();
|
|
84
|
+
App.runWithConfig(config);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Run the collector with the given configuration options.
|
|
88
|
+
*
|
|
89
|
+
* @param config - The configuration options to run the collector with.
|
|
90
|
+
*/
|
|
91
|
+
static runWithConfig(config) {
|
|
92
|
+
// Build the logger
|
|
93
|
+
const logger = this.buildLogger(config);
|
|
94
|
+
logger.info(`Starting collector in working directory "${process.cwd()}".`);
|
|
95
|
+
logger.info(`Logging "${config.log_level}" to "${config.log_to_file}".`);
|
|
96
|
+
// Prepare the storage and the server
|
|
97
|
+
const storage = new DataStorage_1.DataStorage(logger);
|
|
98
|
+
const server = new CollectingServer_1.WebSocketCollectingServer(config.port, storage, logger);
|
|
99
|
+
// Enable the remote control API if configured
|
|
100
|
+
const controlServerState = this.startControlServer(config, storage, logger);
|
|
101
|
+
// Start the server socket.
|
|
102
|
+
// ATTENTION: The server is executed asynchronously
|
|
103
|
+
const serverState = server.start();
|
|
104
|
+
// Optionally, start a timer that dumps the coverage after a N seconds
|
|
105
|
+
const timerState = this.maybeStartDumpTimer(config, storage, logger);
|
|
106
|
+
// Say bye bye on CTRL+C and exit the process
|
|
107
|
+
process.on('SIGINT', async () => {
|
|
108
|
+
// ... and do a final dump before.
|
|
109
|
+
await this.dumpCoverage(config, storage, logger);
|
|
110
|
+
logger.info('Bye bye.');
|
|
111
|
+
process.exit();
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
async stop() {
|
|
115
|
+
logger.info('Stopping the collector.');
|
|
116
|
+
timerState.stop();
|
|
117
|
+
await controlServerState.stop();
|
|
118
|
+
serverState.stop();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Start a timer for dumping the data, depending on the configuration.
|
|
124
|
+
*
|
|
125
|
+
* @param config - The config that determines whether to do the timed dump or not.
|
|
126
|
+
* @param storage - The storage with the information to dump.
|
|
127
|
+
* @param logger - The logger to use.
|
|
128
|
+
*/
|
|
129
|
+
static maybeStartDumpTimer(config, storage, logger) {
|
|
130
|
+
if (config.dump_after_mins > 0) {
|
|
131
|
+
logger.info(`Will dump coverage information every ${config.dump_after_mins} minute(s).`);
|
|
132
|
+
const timer = setInterval(async () => {
|
|
133
|
+
await this.dumpCoverage(config, storage, logger);
|
|
134
|
+
}, config.dump_after_mins * 1000 * 60);
|
|
135
|
+
process.on('SIGINT', () => {
|
|
136
|
+
// Stop the timed file dump
|
|
137
|
+
if (timer) {
|
|
138
|
+
clearInterval(timer);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
stop: () => clearInterval(timer)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
stop() {
|
|
147
|
+
// no timer to stop yet
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
static async dumpCoverage(config, storage, logger) {
|
|
152
|
+
try {
|
|
153
|
+
// 1. Write coverage to a file
|
|
154
|
+
const [coverageFile, lines] = storage.dumpToSimpleCoverageFile(config.dump_to_folder, new Date());
|
|
155
|
+
logger.info(`Dumped ${lines} lines of coverage to ${coverageFile}.`);
|
|
156
|
+
// 2. Upload to Teamscale or Artifactory if configured
|
|
157
|
+
if (config.teamscale_server_url || config.artifactory_server_url) {
|
|
158
|
+
await this.uploadCoverage(config, coverageFile, lines, logger);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
if (e instanceof CommonUpload_1.UploadError) {
|
|
163
|
+
logger.error(`Coverage upload failed. The coverage files on disk (inside the folder "${config.dump_to_folder}") were not deleted.
|
|
164
|
+
You can still upload them manually.`, e);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
logger.error('Coverage dump failed.', e);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
static async uploadCoverage(config, coverageFile, lines, logger) {
|
|
172
|
+
if (config.teamscale_server_url) {
|
|
173
|
+
await (0, TeamscaleUpload_1.uploadToTeamscale)(config, logger, coverageFile, lines);
|
|
174
|
+
}
|
|
175
|
+
if (config.artifactory_server_url) {
|
|
176
|
+
await (0, ArtifactoryUpload_1.uploadToArtifactory)(config, logger, coverageFile, lines);
|
|
177
|
+
}
|
|
178
|
+
// Delete coverage if upload was successful and keeping coverage files on disk was not configure by the user
|
|
179
|
+
if (!config.keep_coverage_files) {
|
|
180
|
+
fs.unlinkSync(coverageFile);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
static startControlServer(config, storage, logger) {
|
|
184
|
+
if (!config.enable_control_port) {
|
|
185
|
+
return {
|
|
186
|
+
async stop() {
|
|
187
|
+
// nothing to stop in this case
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const controlServer = (0, express_1.default)();
|
|
192
|
+
controlServer.use(express_1.default.text({}));
|
|
193
|
+
const serverSocket = controlServer.listen(config.enable_control_port);
|
|
194
|
+
controlServer.put('/partition', (request, response) => {
|
|
195
|
+
const targetPartition = request.body.trim();
|
|
196
|
+
config.teamscale_partition = targetPartition;
|
|
197
|
+
logger.info(`Switched the target partition to '${targetPartition}' via the control API.`);
|
|
198
|
+
response.sendStatus(200);
|
|
199
|
+
});
|
|
200
|
+
controlServer.post('/dump', async (request, response) => {
|
|
201
|
+
logger.info('Dumping coverage requested via the control API.');
|
|
202
|
+
await this.dumpCoverage(config, storage, logger);
|
|
203
|
+
response.sendStatus(200);
|
|
204
|
+
});
|
|
205
|
+
controlServer.put('/project', async (request, response) => {
|
|
206
|
+
const targetProject = request.body.trim();
|
|
207
|
+
config.teamscale_project = targetProject;
|
|
208
|
+
logger.info(`Switching the target project to '${targetProject}' via the control API.`);
|
|
209
|
+
response.sendStatus(200);
|
|
210
|
+
});
|
|
211
|
+
controlServer.put('/revision', async (request, response) => {
|
|
212
|
+
const targetRevision = request.body.trim();
|
|
213
|
+
config.teamscale_revision = targetRevision;
|
|
214
|
+
logger.info(`Switching the target revision to '${targetRevision}' via the control API.`);
|
|
215
|
+
response.sendStatus(200);
|
|
216
|
+
});
|
|
217
|
+
controlServer.put('/commit', async (request, response) => {
|
|
218
|
+
const targetCommit = request.body.trim();
|
|
219
|
+
config.teamscale_commit = targetCommit;
|
|
220
|
+
logger.info(`Switching the target commit to '${targetCommit}' via the control API.`);
|
|
221
|
+
response.sendStatus(200);
|
|
222
|
+
});
|
|
223
|
+
controlServer.put('/message', async (request, response) => {
|
|
224
|
+
const uploadMessage = request.body.trim();
|
|
225
|
+
config.teamscale_message = uploadMessage;
|
|
226
|
+
logger.info(`Switching the upload message to '${uploadMessage}' via the control API.`);
|
|
227
|
+
response.sendStatus(200);
|
|
228
|
+
});
|
|
229
|
+
controlServer.post('/reset', async (request, response) => {
|
|
230
|
+
storage.discardCollectedCoverage();
|
|
231
|
+
logger.info(`Discarding collected coverage information as requested via the control API.`);
|
|
232
|
+
response.sendStatus(200);
|
|
233
|
+
});
|
|
234
|
+
logger.info(`Control server enabled at port ${config.enable_control_port}`);
|
|
235
|
+
return {
|
|
236
|
+
async stop() {
|
|
237
|
+
return new Promise(resolve => {
|
|
238
|
+
serverSocket.close(() => resolve());
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
exports.App = App;
|
package/dist/src/main.d.ts
CHANGED
|
@@ -1,31 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
* The main class of the Teamscale JavaScript Collector.
|
|
4
|
-
* Used to start-up the collector for with a given configuration.
|
|
5
|
-
*/
|
|
6
|
-
export declare class Main {
|
|
7
|
-
/**
|
|
8
|
-
* Construct the object for parsing the command line arguments.
|
|
9
|
-
*/
|
|
10
|
-
private static buildParser;
|
|
11
|
-
/**
|
|
12
|
-
* Parse the given command line arguments into a corresponding options object.
|
|
13
|
-
*/
|
|
14
|
-
private static parseArguments;
|
|
15
|
-
/**
|
|
16
|
-
* Construct the logger.
|
|
17
|
-
*/
|
|
18
|
-
private static buildLogger;
|
|
19
|
-
/**
|
|
20
|
-
* Entry point of the Teamscale JavaScript Profiler.
|
|
21
|
-
*/
|
|
22
|
-
static run(): void;
|
|
23
|
-
/**
|
|
24
|
-
* Start a timer for dumping the data, depending on the configuration.
|
|
25
|
-
*
|
|
26
|
-
* @param config - The config that determines whether or not to do the timed dump.
|
|
27
|
-
* @param storage - The storage with the information to dump.
|
|
28
|
-
* @param logger - The logger to use.
|
|
29
|
-
*/
|
|
30
|
-
private static maybeStartDumpTimer;
|
|
31
|
-
}
|
|
2
|
+
export {};
|
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: 120
|
|
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];
|
|
@@ -60,9 +64,10 @@ class WebSocketCollectingServer {
|
|
|
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
|
|
@@ -75,9 +80,9 @@ class WebSocketCollectingServer {
|
|
|
75
80
|
}
|
|
76
81
|
});
|
|
77
82
|
// Handle incoming messages
|
|
78
|
-
webSocket.on('message', (message) => {
|
|
83
|
+
webSocket.on('message', async (message) => {
|
|
79
84
|
if (session && typeof message === 'string') {
|
|
80
|
-
this.handleMessage(session, message);
|
|
85
|
+
await 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,13 +104,13 @@ 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
109
|
if (message.startsWith(ProtocolMessageTypes.TYPE_SOURCEMAP)) {
|
|
98
|
-
this.handleSourcemapMessage(session, message.substring(1));
|
|
110
|
+
await this.handleSourcemapMessage(session, message.substring(1));
|
|
99
111
|
}
|
|
100
112
|
else if (message.startsWith(ProtocolMessageTypes.TYPE_COVERAGE)) {
|
|
101
|
-
this.handleCoverageMessage(session, message.substring(1));
|
|
113
|
+
await this.handleCoverageMessage(session, message.substring(1));
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
catch (e) {
|
|
@@ -112,13 +124,13 @@ class WebSocketCollectingServer {
|
|
|
112
124
|
* @param session - The session to handle the message for.
|
|
113
125
|
* @param body - The body of the message (to be parsed).
|
|
114
126
|
*/
|
|
115
|
-
handleSourcemapMessage(session, body) {
|
|
127
|
+
async handleSourcemapMessage(session, body) {
|
|
116
128
|
const fileIdSeparatorPosition = body.indexOf(INSTRUMENTATION_SUBJECT_SEPARATOR);
|
|
117
129
|
if (fileIdSeparatorPosition > -1) {
|
|
118
130
|
const fileId = body.substring(0, fileIdSeparatorPosition).trim();
|
|
119
131
|
this.logger.debug(`Received source map information for ${fileId}`);
|
|
120
132
|
const sourcemap = body.substring(fileIdSeparatorPosition + 1);
|
|
121
|
-
session.putSourcemap(fileId, sourcemap);
|
|
133
|
+
await session.putSourcemap(fileId, sourcemap);
|
|
122
134
|
}
|
|
123
135
|
}
|
|
124
136
|
/**
|
|
@@ -127,16 +139,21 @@ class WebSocketCollectingServer {
|
|
|
127
139
|
* @param session - The session to handle the message for.
|
|
128
140
|
* @param body - The body of the message (to be parsed).
|
|
129
141
|
*/
|
|
130
|
-
handleCoverageMessage(session, body) {
|
|
142
|
+
async handleCoverageMessage(session, body) {
|
|
131
143
|
var _a;
|
|
132
|
-
const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+)
|
|
144
|
+
const bodyPattern = /(?<fileId>\S+) (?<positions>((\d+:\d+(:\d+:\d+)?\s+)*(\d+:\d+(:\d+:\d+)?)))/;
|
|
133
145
|
const matches = bodyPattern.exec(body);
|
|
134
146
|
if (matches === null || matches === void 0 ? void 0 : matches.groups) {
|
|
135
147
|
const fileId = matches.groups.fileId;
|
|
136
148
|
const positions = ((_a = matches.groups.positions) !== null && _a !== void 0 ? _a : '').split(/\s+/);
|
|
137
149
|
for (const position of positions) {
|
|
138
|
-
const
|
|
139
|
-
|
|
150
|
+
const positionParts = position.split(':');
|
|
151
|
+
if (positionParts.length === 2) {
|
|
152
|
+
session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]));
|
|
153
|
+
}
|
|
154
|
+
else if (positionParts.length === 4) {
|
|
155
|
+
session.putCoverage(fileId, Number.parseInt(positionParts[0]), Number.parseInt(positionParts[1]), Number.parseInt(positionParts[2]), Number.parseInt(positionParts[3]));
|
|
156
|
+
}
|
|
140
157
|
}
|
|
141
158
|
}
|
|
142
159
|
}
|