@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
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { Socket } from 'net';
|
|
3
3
|
import { IDataStorage } from '../storage/DataStorage';
|
|
4
|
-
import
|
|
4
|
+
import Logger from 'bunyan';
|
|
5
|
+
/**
|
|
6
|
+
* Coverage information that has not been mapped back to the
|
|
7
|
+
* original code using a source map.
|
|
8
|
+
*/
|
|
9
|
+
export declare type UnmappedCoverage = {
|
|
10
|
+
fileId: string;
|
|
11
|
+
startLine: number;
|
|
12
|
+
startColumn: number;
|
|
13
|
+
endLine: number;
|
|
14
|
+
endColumn: number;
|
|
15
|
+
};
|
|
5
16
|
/**
|
|
6
17
|
* The session maintains the relevant information for a client.
|
|
7
18
|
* One session is created for each client.
|
|
@@ -22,6 +33,10 @@ export declare class Session {
|
|
|
22
33
|
* server per browser window.
|
|
23
34
|
*/
|
|
24
35
|
private readonly sourceMaps;
|
|
36
|
+
/**
|
|
37
|
+
* Unmapped coverage information.
|
|
38
|
+
*/
|
|
39
|
+
private readonly unmappedCoverage;
|
|
25
40
|
/**
|
|
26
41
|
* The logger to use.
|
|
27
42
|
*/
|
|
@@ -43,10 +58,12 @@ export declare class Session {
|
|
|
43
58
|
* This method also conducts the mapping based on the source map.
|
|
44
59
|
*
|
|
45
60
|
* @param fileId - The identifier of the instrumented bundle (file).
|
|
46
|
-
* @param
|
|
47
|
-
* @param
|
|
61
|
+
* @param startLine - The line number within the bundle the range starts.
|
|
62
|
+
* @param startColumn - The column in the given `startLine` on that the range starts (inclusive).
|
|
63
|
+
* @param endLine - The line number within the bundle the range ends.
|
|
64
|
+
* @param endColumn - The column in the given `startLine` on that the range ends (inclusive).
|
|
48
65
|
*/
|
|
49
|
-
putCoverage(fileId: string,
|
|
66
|
+
putCoverage(fileId: string, startLine: number, startColumn: number, endLine: number, endColumn: number): boolean;
|
|
50
67
|
/**
|
|
51
68
|
* Map to the original file position.
|
|
52
69
|
*
|
|
@@ -61,7 +78,8 @@ export declare class Session {
|
|
|
61
78
|
* @param fileId - The identifier of the file bundle.
|
|
62
79
|
* @param sourceMapText - The actual source map.
|
|
63
80
|
*/
|
|
64
|
-
putSourcemap(fileId: string, sourceMapText: string): void
|
|
81
|
+
putSourcemap(fileId: string, sourceMapText: string): Promise<void>;
|
|
82
|
+
private processUnmappedCoverageOf;
|
|
65
83
|
/**
|
|
66
84
|
* Destroy the session and free the memory it allocates.
|
|
67
85
|
* 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) {
|
|
139
|
+
async putSourcemap(fileId, sourceMapText) {
|
|
86
140
|
const rawSourceMap = JSON.parse(sourceMapText);
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
this.sourceMaps.set(fileId,
|
|
90
|
-
|
|
91
|
-
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Logger from 'bunyan';
|
|
2
2
|
/**
|
|
3
3
|
* Lines covered for the specified file.
|
|
4
4
|
*/
|
|
@@ -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
|
}
|
|
@@ -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];
|
|
@@ -18,10 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
18
22
|
__setModuleDefault(result, mod);
|
|
19
23
|
return result;
|
|
20
24
|
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
21
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
29
|
exports.DataStorage = exports.ProjectCoverage = void 0;
|
|
23
30
|
const commons_1 = require("@cqse/commons");
|
|
24
31
|
const fs = __importStar(require("fs"));
|
|
32
|
+
const path_1 = __importDefault(require("path"));
|
|
33
|
+
const dat = __importStar(require("date-and-time"));
|
|
25
34
|
/**
|
|
26
35
|
* The coverage information received for one particular project.
|
|
27
36
|
*/
|
|
@@ -77,6 +86,10 @@ class DataStorage {
|
|
|
77
86
|
* @param logger - The logger to use.
|
|
78
87
|
*/
|
|
79
88
|
constructor(logger) {
|
|
89
|
+
/**
|
|
90
|
+
* Date format for the timestamp appended to the coverage files
|
|
91
|
+
*/
|
|
92
|
+
this.DATE_FORMAT = 'YYYY-MM-DD-HH-mm-ss.SSS';
|
|
80
93
|
this.coverageByProject = new Map();
|
|
81
94
|
this.logger = commons_1.Contract.requireDefined(logger);
|
|
82
95
|
this.timesUnmappedCoverage = 0;
|
|
@@ -89,7 +102,7 @@ class DataStorage {
|
|
|
89
102
|
* @param coveredOriginalLines - The lines covered in the file.
|
|
90
103
|
*/
|
|
91
104
|
putCoverage(project, sourceFilePath, coveredOriginalLines) {
|
|
92
|
-
const uniformPath =
|
|
105
|
+
const uniformPath = DataStorage.normalizeSourceFileName(sourceFilePath);
|
|
93
106
|
let projectCoverage = this.coverageByProject.get(project);
|
|
94
107
|
if (!projectCoverage) {
|
|
95
108
|
projectCoverage = new ProjectCoverage(project);
|
|
@@ -104,7 +117,7 @@ class DataStorage {
|
|
|
104
117
|
*
|
|
105
118
|
* @param sourceFile - The file name to normalize, produced by the instrumenter.
|
|
106
119
|
*/
|
|
107
|
-
normalizeSourceFileName(sourceFile) {
|
|
120
|
+
static normalizeSourceFileName(sourceFile) {
|
|
108
121
|
return (0, commons_1.removePrefix)('webpack:///', sourceFile.replace('\\', '/'));
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
@@ -125,29 +138,55 @@ class DataStorage {
|
|
|
125
138
|
return projectCoverage === null || projectCoverage === void 0 ? void 0 : projectCoverage.getCoverage();
|
|
126
139
|
}
|
|
127
140
|
/**
|
|
128
|
-
*
|
|
141
|
+
* @inheritDoc
|
|
129
142
|
*/
|
|
130
|
-
dumpToSimpleCoverageFile(
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
dumpToSimpleCoverageFile(coverageFolder, date) {
|
|
144
|
+
const [lines, content] = this.toSimpleCoverage();
|
|
145
|
+
const coverageFolderTrimmed = coverageFolder.trim();
|
|
146
|
+
const finalFilePath = this.initCoverageFile(coverageFolderTrimmed, date);
|
|
147
|
+
fs.writeFileSync(finalFilePath, content, { flag: 'w', encoding: 'utf8' });
|
|
148
|
+
this.resetCoverage();
|
|
149
|
+
return [finalFilePath, lines];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Set the collected coverage to 0 for all projects
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
resetCoverage() {
|
|
156
|
+
this.coverageByProject = new Map();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Appends the timestamp given with date to the coverageFolder (before the file ending if there is one)
|
|
160
|
+
* @param coverageFolder Path to the coverage file
|
|
161
|
+
* @param date Represents the timestamp to be appended with the format {@link DataStorage.DATE_FORMAT}
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
initCoverageFile(coverageFolder, date) {
|
|
165
|
+
if (!fs.existsSync(coverageFolder)) {
|
|
166
|
+
fs.mkdirSync(coverageFolder);
|
|
167
|
+
}
|
|
168
|
+
const formattedDate = dat.format(date, this.DATE_FORMAT);
|
|
169
|
+
return path_1.default.join(coverageFolder, `coverage-${formattedDate}.simple`);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Generate simple coverage format for the collected coverage
|
|
173
|
+
*/
|
|
174
|
+
toSimpleCoverage() {
|
|
175
|
+
const result = [];
|
|
176
|
+
commons_1.Contract.require(this.getProjects().length < 2, 'Only one project supported to be handled in parallel.');
|
|
177
|
+
for (const project of this.getProjects()) {
|
|
178
|
+
const projectCoverage = this.getCoverageBySourceFile(project);
|
|
179
|
+
if (!projectCoverage) {
|
|
180
|
+
return [0, ''];
|
|
181
|
+
}
|
|
182
|
+
for (const entry of projectCoverage) {
|
|
183
|
+
result.push(DataStorage.normalizeSourceFileName(entry.sourceFile));
|
|
184
|
+
for (const lineNo of entry.coveredLines) {
|
|
185
|
+
result.push(String(lineNo));
|
|
144
186
|
}
|
|
145
187
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const [lines, content] = toSimpleCoverage();
|
|
149
|
-
fs.writeFileSync(filePath.trim(), content, { flag: 'w', encoding: 'utf8' });
|
|
150
|
-
return lines;
|
|
188
|
+
}
|
|
189
|
+
return [result.length, result.join('\n')];
|
|
151
190
|
}
|
|
152
191
|
/**
|
|
153
192
|
* {@inheritDoc IReadableStorage.getProjects}
|
|
@@ -155,5 +194,11 @@ class DataStorage {
|
|
|
155
194
|
getProjects() {
|
|
156
195
|
return Array.from(this.coverageByProject.keys());
|
|
157
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* {@inheritDoc IWritableStorage.discardCollectedCoverage}
|
|
199
|
+
*/
|
|
200
|
+
discardCollectedCoverage() {
|
|
201
|
+
this.coverageByProject.clear();
|
|
202
|
+
}
|
|
158
203
|
}
|
|
159
204
|
exports.DataStorage = DataStorage;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigParameters } from '../utils/ConfigParameters';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
/**
|
|
4
|
+
* Uploads a coverage file to artifactory with the provided configuration.
|
|
5
|
+
*/
|
|
6
|
+
export declare function uploadToArtifactory(config: ConfigParameters, logger: Logger, coverageFile: string, lines: number): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadToArtifactory = void 0;
|
|
7
|
+
const CommonUpload_1 = require("./CommonUpload");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const ProxyUpload_1 = require("./ProxyUpload");
|
|
10
|
+
/**
|
|
11
|
+
* Uploads a coverage file to artifactory with the provided configuration.
|
|
12
|
+
*/
|
|
13
|
+
async function uploadToArtifactory(config, logger, coverageFile, lines) {
|
|
14
|
+
if (!(config.artifactory_access_token || (config.artifactory_user && config.artifactory_password))) {
|
|
15
|
+
throw new CommonUpload_1.UploadError('API key or user name and password must be configured!');
|
|
16
|
+
}
|
|
17
|
+
if (lines === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
logger.info('Preparing upload to Artifactory');
|
|
21
|
+
const form = (0, CommonUpload_1.prepareFormData)(coverageFile);
|
|
22
|
+
await performArtifactoryUpload(config, form, logger);
|
|
23
|
+
}
|
|
24
|
+
exports.uploadToArtifactory = uploadToArtifactory;
|
|
25
|
+
async function performArtifactoryUpload(config, form, logger) {
|
|
26
|
+
var _a;
|
|
27
|
+
if (!config.teamscale_commit) {
|
|
28
|
+
throw new CommonUpload_1.UploadError('The "--teamscale-commit" option must be set with a valid branch and timestamp.');
|
|
29
|
+
}
|
|
30
|
+
const branchAndTimestamp = config.teamscale_commit.split(':');
|
|
31
|
+
let url = `${(_a = config.artifactory_server_url) === null || _a === void 0 ? void 0 : _a.replace(/\/$/, '')}/uploads/${branchAndTimestamp[0]}/${branchAndTimestamp[1]}`;
|
|
32
|
+
if (config.teamscale_revision) {
|
|
33
|
+
url = url + `-${config.teamscale_revision}`;
|
|
34
|
+
}
|
|
35
|
+
url = url + `/${config.teamscale_partition}/simple`;
|
|
36
|
+
if (config.artifactory_path_suffix !== undefined) {
|
|
37
|
+
url = `${url}/${config.artifactory_path_suffix}`;
|
|
38
|
+
}
|
|
39
|
+
url = `${url}/report.simple`;
|
|
40
|
+
await (0, CommonUpload_1.performUpload)(url, form, prepareArtifactoryConfig(config, form), axios_1.default.put, logger);
|
|
41
|
+
}
|
|
42
|
+
function prepareArtifactoryConfig(config, form) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const proxyConfig = (0, ProxyUpload_1.extractProxyOptions)(config);
|
|
45
|
+
if (config.artifactory_access_token) {
|
|
46
|
+
return {
|
|
47
|
+
headers: {
|
|
48
|
+
Accept: '*/*',
|
|
49
|
+
'X-JFrog-Art-Api': config.artifactory_access_token,
|
|
50
|
+
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
51
|
+
},
|
|
52
|
+
proxy: proxyConfig
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
auth: {
|
|
57
|
+
username: (_a = config.artifactory_user) !== null && _a !== void 0 ? _a : 'no username provided',
|
|
58
|
+
password: (_b = config.artifactory_password) !== null && _b !== void 0 ? _b : 'no password provided'
|
|
59
|
+
},
|
|
60
|
+
headers: {
|
|
61
|
+
Accept: '*/*',
|
|
62
|
+
'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`
|
|
63
|
+
},
|
|
64
|
+
proxy: proxyConfig
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import FormData from 'form-data';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
4
|
+
/**
|
|
5
|
+
* Error that is thrown when the upload failed
|
|
6
|
+
*/
|
|
7
|
+
export declare class UploadError extends Error {
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Prepares the form data from a given configuration file for the upload.
|
|
11
|
+
*/
|
|
12
|
+
export declare function prepareFormData(coverageFile: string): FormData;
|
|
13
|
+
/**
|
|
14
|
+
* Uploads a coverage file with the provided configuration.
|
|
15
|
+
*/
|
|
16
|
+
export declare function performUpload(url: string, form: FormData, config: AxiosRequestConfig<FormData>, uploadFunction: <T = any, R = AxiosResponse<T>, D = FormData>(url: string, data?: D, config?: AxiosRequestConfig<D>) => Promise<R>, logger: Logger): Promise<void>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.performUpload = exports.prepareFormData = exports.UploadError = void 0;
|
|
7
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const util_1 = require("util");
|
|
10
|
+
/**
|
|
11
|
+
* Error that is thrown when the upload failed
|
|
12
|
+
*/
|
|
13
|
+
class UploadError extends Error {
|
|
14
|
+
}
|
|
15
|
+
exports.UploadError = UploadError;
|
|
16
|
+
/**
|
|
17
|
+
* Prepares the form data from a given configuration file for the upload.
|
|
18
|
+
*/
|
|
19
|
+
function prepareFormData(coverageFile) {
|
|
20
|
+
const form = new form_data_1.default();
|
|
21
|
+
form.append('report', fs_1.default.createReadStream(coverageFile), 'coverage.simple');
|
|
22
|
+
return form;
|
|
23
|
+
}
|
|
24
|
+
exports.prepareFormData = prepareFormData;
|
|
25
|
+
/**
|
|
26
|
+
* Uploads a coverage file with the provided configuration.
|
|
27
|
+
*/
|
|
28
|
+
async function performUpload(url, form, config, uploadFunction, logger) {
|
|
29
|
+
try {
|
|
30
|
+
const response = await uploadFunction(url, form, config);
|
|
31
|
+
logger.info(`Upload finished with code ${response.status}.`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.response) {
|
|
35
|
+
const response = error.response;
|
|
36
|
+
if (response.status >= 400) {
|
|
37
|
+
throw new UploadError(`Upload failed with code ${response.status}: ${response.statusText}. Response Data: ${response.data}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
logger.info(`Upload with status code ${response.status} finished.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if (error.request) {
|
|
44
|
+
throw new UploadError(`Upload request did not receive a response.`);
|
|
45
|
+
}
|
|
46
|
+
if (error.message) {
|
|
47
|
+
logger.debug(`Something went wrong when uploading data: ${error.message}. Details of the error: ${(0, util_1.inspect)(error)}`);
|
|
48
|
+
throw new UploadError(`Something went wrong when uploading data: ${error.message}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new UploadError(`Something went wrong when uploading data: ${(0, util_1.inspect)(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.performUpload = performUpload;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigParameters } from "../utils/ConfigParameters";
|
|
2
|
+
import { AxiosProxyConfig } from "axios";
|
|
3
|
+
/**
|
|
4
|
+
* Creates an AxiosProxyConfig object if proxy variables are provided.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractProxyOptions(config: ConfigParameters): AxiosProxyConfig | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.extractProxyOptions = void 0;
|
|
7
|
+
const url_1 = __importDefault(require("url"));
|
|
8
|
+
/**
|
|
9
|
+
* Creates an AxiosProxyConfig object if proxy variables are provided.
|
|
10
|
+
*/
|
|
11
|
+
function extractProxyOptions(config) {
|
|
12
|
+
if (config.http_proxy) {
|
|
13
|
+
// Expected format: http://username:password@host:port/
|
|
14
|
+
// See https://nodejs.org/api/url.html#url-strings-and-url-objects for URL parsing
|
|
15
|
+
const proxyAddress = new url_1.default.URL(config.http_proxy);
|
|
16
|
+
const proxyConfig = {
|
|
17
|
+
protocol: proxyAddress.protocol.replace(':', ''),
|
|
18
|
+
host: proxyAddress.hostname,
|
|
19
|
+
port: +proxyAddress.port,
|
|
20
|
+
};
|
|
21
|
+
if (proxyAddress.username && proxyAddress.password) {
|
|
22
|
+
proxyConfig.auth = {
|
|
23
|
+
username: proxyAddress.username,
|
|
24
|
+
password: proxyAddress.password
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return proxyConfig;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.extractProxyOptions = extractProxyOptions;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ConfigParameters } from '../utils/ConfigParameters';
|
|
2
|
+
import Logger from 'bunyan';
|
|
3
|
+
/**
|
|
4
|
+
* Uploads a coverage file to Teamscale with the provided configuration.
|
|
5
|
+
*/
|
|
6
|
+
export declare function uploadToTeamscale(config: ConfigParameters, logger: Logger, coverageFile: string, lines: number): Promise<void>;
|