jest-test-lineage-reporter 2.0.2 → 2.1.0
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 +252 -0
- package/bin/jest-lineage.js +20 -0
- package/package.json +14 -7
- package/src/__tests__/assertion-test.test.ts +59 -0
- package/src/__tests__/calculator.test.ts +30 -0
- package/src/__tests__/depth-example.test.ts +237 -0
- package/src/__tests__/gc-pressure-example.test.ts +169 -0
- package/src/__tests__/performance-example.test.ts +83 -0
- package/src/__tests__/quality-example.test.ts +122 -0
- package/src/__tests__/survived-mutations-example.test.ts +32 -0
- package/src/__tests__/truly-weak-example.test.ts +90 -0
- package/src/__tests__/weak-test-example.test.ts +222 -0
- package/src/calculator.ts +12 -0
- package/src/cli/commands/analyze.js +91 -0
- package/src/cli/commands/mutate.js +89 -0
- package/src/cli/commands/query.js +107 -0
- package/src/cli/commands/report.js +65 -0
- package/src/cli/commands/test.js +56 -0
- package/src/cli/index.js +89 -0
- package/src/cli/utils/config-loader.js +114 -0
- package/src/cli/utils/data-loader.js +118 -0
- package/src/cli/utils/jest-runner.js +105 -0
- package/src/cli/utils/output-formatter.js +126 -0
- package/src/depth-example.ts +66 -0
- package/src/gc-pressure-example.ts +158 -0
- package/src/global.d.ts +7 -0
- package/src/mcp/server.js +469 -0
- package/src/performance-example.ts +82 -0
- package/src/quality-example.ts +79 -0
- package/src/survived-mutations-example.ts +19 -0
- package/src/truly-weak-example.ts +37 -0
- package/src/weak-test-example.ts +91 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Examples demonstrating GC pressure and how to fix it
|
|
2
|
+
|
|
3
|
+
// BAD: Creates many small objects (causes GC pressure)
|
|
4
|
+
export function badGCPressureFunction(count: number): number {
|
|
5
|
+
let total = 0;
|
|
6
|
+
|
|
7
|
+
for (let i = 0; i < count; i++) {
|
|
8
|
+
// Creating new objects in a loop - BAD for GC
|
|
9
|
+
const tempObj = {
|
|
10
|
+
id: i,
|
|
11
|
+
value: Math.random(),
|
|
12
|
+
timestamp: Date.now(),
|
|
13
|
+
processed: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// More object creation
|
|
17
|
+
const result = {
|
|
18
|
+
input: tempObj,
|
|
19
|
+
output: tempObj.value * 2,
|
|
20
|
+
metadata: {
|
|
21
|
+
created: new Date(),
|
|
22
|
+
index: i
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
total += result.output;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// GOOD: Reuses objects (reduces GC pressure)
|
|
33
|
+
export function goodGCPressureFunction(count: number): number {
|
|
34
|
+
let total = 0;
|
|
35
|
+
|
|
36
|
+
// Reuse objects instead of creating new ones
|
|
37
|
+
const reusableObj = {
|
|
38
|
+
id: 0,
|
|
39
|
+
value: 0,
|
|
40
|
+
timestamp: 0,
|
|
41
|
+
processed: false
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const reusableResult = {
|
|
45
|
+
input: reusableObj,
|
|
46
|
+
output: 0,
|
|
47
|
+
metadata: {
|
|
48
|
+
created: new Date(),
|
|
49
|
+
index: 0
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < count; i++) {
|
|
54
|
+
// Reuse existing objects - GOOD for GC
|
|
55
|
+
reusableObj.id = i;
|
|
56
|
+
reusableObj.value = Math.random();
|
|
57
|
+
reusableObj.timestamp = Date.now();
|
|
58
|
+
reusableObj.processed = false;
|
|
59
|
+
|
|
60
|
+
reusableResult.output = reusableObj.value * 2;
|
|
61
|
+
reusableResult.metadata.index = i;
|
|
62
|
+
|
|
63
|
+
total += reusableResult.output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return total;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// BAD: String concatenation in loop (creates many temporary strings)
|
|
70
|
+
export function badStringConcatenation(items: string[]): string {
|
|
71
|
+
let result = '';
|
|
72
|
+
|
|
73
|
+
for (const item of items) {
|
|
74
|
+
// Each += creates a new string object - BAD for GC
|
|
75
|
+
result += item + ', ';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// GOOD: Use array join (single allocation)
|
|
82
|
+
export function goodStringConcatenation(items: string[]): string {
|
|
83
|
+
// Single allocation - GOOD for GC
|
|
84
|
+
return items.join(', ');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// BAD: Array operations that create many intermediate arrays
|
|
88
|
+
export function badArrayOperations(numbers: number[]): number[] {
|
|
89
|
+
return numbers
|
|
90
|
+
.map(x => x * 2) // Creates new array
|
|
91
|
+
.filter(x => x > 10) // Creates another new array
|
|
92
|
+
.map(x => x + 1) // Creates another new array
|
|
93
|
+
.filter(x => x % 2 === 0); // Creates final array
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// GOOD: Single pass with reduce (one allocation)
|
|
97
|
+
export function goodArrayOperations(numbers: number[]): number[] {
|
|
98
|
+
return numbers.reduce((acc: number[], x: number) => {
|
|
99
|
+
const doubled = x * 2;
|
|
100
|
+
if (doubled > 10) {
|
|
101
|
+
const incremented = doubled + 1;
|
|
102
|
+
if (incremented % 2 === 0) {
|
|
103
|
+
acc.push(incremented);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return acc;
|
|
107
|
+
}, []);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Object pool pattern for heavy reuse
|
|
111
|
+
class ObjectPool<T> {
|
|
112
|
+
private pool: T[] = [];
|
|
113
|
+
private createFn: () => T;
|
|
114
|
+
private resetFn: (obj: T) => void;
|
|
115
|
+
|
|
116
|
+
constructor(createFn: () => T, resetFn: (obj: T) => void, initialSize: number = 10) {
|
|
117
|
+
this.createFn = createFn;
|
|
118
|
+
this.resetFn = resetFn;
|
|
119
|
+
|
|
120
|
+
// Pre-allocate objects
|
|
121
|
+
for (let i = 0; i < initialSize; i++) {
|
|
122
|
+
this.pool.push(this.createFn());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
acquire(): T {
|
|
127
|
+
return this.pool.pop() || this.createFn();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
release(obj: T): void {
|
|
131
|
+
this.resetFn(obj);
|
|
132
|
+
this.pool.push(obj);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Example using object pool
|
|
137
|
+
export function objectPoolExample(count: number): number {
|
|
138
|
+
const pool = new ObjectPool(
|
|
139
|
+
() => ({ id: 0, value: 0, result: 0 }),
|
|
140
|
+
(obj) => { obj.id = 0; obj.value = 0; obj.result = 0; },
|
|
141
|
+
50
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
let total = 0;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < count; i++) {
|
|
147
|
+
const obj = pool.acquire();
|
|
148
|
+
obj.id = i;
|
|
149
|
+
obj.value = Math.random();
|
|
150
|
+
obj.result = obj.value * 2;
|
|
151
|
+
|
|
152
|
+
total += obj.result;
|
|
153
|
+
|
|
154
|
+
pool.release(obj); // Return to pool for reuse
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return total;
|
|
158
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for Jest Test Lineage Reporter
|
|
5
|
+
* Exposes test analytics functionality via Model Context Protocol
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
9
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
10
|
+
const {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema,
|
|
13
|
+
} = require('@modelcontextprotocol/sdk/types.js');
|
|
14
|
+
|
|
15
|
+
const { runJest } = require('../cli/utils/jest-runner');
|
|
16
|
+
const { loadLineageData, processLineageDataForMutation } = require('../cli/utils/data-loader');
|
|
17
|
+
const { loadFullConfig } = require('../cli/utils/config-loader');
|
|
18
|
+
const MutationTester = require('../MutationTester');
|
|
19
|
+
const TestCoverageReporter = require('../TestCoverageReporter');
|
|
20
|
+
|
|
21
|
+
// Create MCP server
|
|
22
|
+
const server = new Server(
|
|
23
|
+
{
|
|
24
|
+
name: 'jest-test-lineage-reporter',
|
|
25
|
+
version: '1.0.0',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
capabilities: {
|
|
29
|
+
tools: {},
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Define available tools
|
|
35
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
36
|
+
return {
|
|
37
|
+
tools: [
|
|
38
|
+
{
|
|
39
|
+
name: 'run_tests',
|
|
40
|
+
description: 'Run Jest tests with lineage tracking and generate coverage data',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: {
|
|
44
|
+
args: {
|
|
45
|
+
type: 'array',
|
|
46
|
+
items: { type: 'string' },
|
|
47
|
+
description: 'Jest command-line arguments (e.g., ["--watch", "--testPathPattern=calculator"])',
|
|
48
|
+
default: [],
|
|
49
|
+
},
|
|
50
|
+
enableLineage: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Enable lineage tracking',
|
|
53
|
+
default: true,
|
|
54
|
+
},
|
|
55
|
+
enablePerformance: {
|
|
56
|
+
type: 'boolean',
|
|
57
|
+
description: 'Enable performance tracking',
|
|
58
|
+
default: true,
|
|
59
|
+
},
|
|
60
|
+
enableQuality: {
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
description: 'Enable quality analysis',
|
|
63
|
+
default: true,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'run_mutation_testing',
|
|
70
|
+
description: 'Run mutation testing on existing lineage data to assess test effectiveness',
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
dataPath: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
description: 'Path to lineage data file',
|
|
77
|
+
default: '.jest-lineage-data.json',
|
|
78
|
+
},
|
|
79
|
+
threshold: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Minimum mutation score threshold (0-100)',
|
|
82
|
+
default: 80,
|
|
83
|
+
},
|
|
84
|
+
timeout: {
|
|
85
|
+
type: 'number',
|
|
86
|
+
description: 'Timeout per mutation in milliseconds',
|
|
87
|
+
default: 5000,
|
|
88
|
+
},
|
|
89
|
+
debug: {
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
description: 'Create debug mutation files instead of running tests',
|
|
92
|
+
default: false,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'generate_report',
|
|
99
|
+
description: 'Generate HTML report from existing lineage data',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
dataPath: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Path to lineage data file',
|
|
106
|
+
default: '.jest-lineage-data.json',
|
|
107
|
+
},
|
|
108
|
+
outputPath: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'Output HTML file path',
|
|
111
|
+
default: 'test-lineage-report.html',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'query_coverage',
|
|
118
|
+
description: 'Query which tests cover specific files or lines',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
file: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'File path to query (e.g., "src/calculator.ts")',
|
|
125
|
+
},
|
|
126
|
+
line: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description: 'Optional line number to query',
|
|
129
|
+
},
|
|
130
|
+
dataPath: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
description: 'Path to lineage data file',
|
|
133
|
+
default: '.jest-lineage-data.json',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ['file'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'analyze_full',
|
|
141
|
+
description: 'Run full workflow: tests, mutation testing, and generate report',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
skipTests: {
|
|
146
|
+
type: 'boolean',
|
|
147
|
+
description: 'Skip running tests (use existing data)',
|
|
148
|
+
default: false,
|
|
149
|
+
},
|
|
150
|
+
skipMutation: {
|
|
151
|
+
type: 'boolean',
|
|
152
|
+
description: 'Skip mutation testing',
|
|
153
|
+
default: false,
|
|
154
|
+
},
|
|
155
|
+
threshold: {
|
|
156
|
+
type: 'number',
|
|
157
|
+
description: 'Mutation score threshold',
|
|
158
|
+
default: 80,
|
|
159
|
+
},
|
|
160
|
+
outputPath: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: 'Output HTML file path',
|
|
163
|
+
default: 'test-lineage-report.html',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Handle tool execution
|
|
173
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
174
|
+
const { name, arguments: args } = request.params;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
switch (name) {
|
|
178
|
+
case 'run_tests': {
|
|
179
|
+
const result = await runJest({
|
|
180
|
+
args: args.args || [],
|
|
181
|
+
enableLineage: args.enableLineage !== false,
|
|
182
|
+
enablePerformance: args.enablePerformance !== false,
|
|
183
|
+
enableQuality: args.enableQuality !== false,
|
|
184
|
+
quiet: false,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: 'text',
|
|
191
|
+
text: JSON.stringify({
|
|
192
|
+
success: result.success,
|
|
193
|
+
exitCode: result.exitCode,
|
|
194
|
+
message: result.success
|
|
195
|
+
? 'Tests completed successfully. Lineage data saved to .jest-lineage-data.json'
|
|
196
|
+
: `Tests failed with exit code ${result.exitCode}`,
|
|
197
|
+
}, null, 2),
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case 'run_mutation_testing': {
|
|
204
|
+
const config = loadFullConfig({
|
|
205
|
+
threshold: args.threshold,
|
|
206
|
+
timeout: args.timeout,
|
|
207
|
+
debug: args.debug,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const rawData = loadLineageData(args.dataPath || '.jest-lineage-data.json');
|
|
211
|
+
const lineageData = processLineageDataForMutation(rawData);
|
|
212
|
+
|
|
213
|
+
const mutationTester = new MutationTester(config);
|
|
214
|
+
mutationTester.setLineageData(lineageData);
|
|
215
|
+
|
|
216
|
+
const results = await mutationTester.runMutationTesting();
|
|
217
|
+
|
|
218
|
+
await mutationTester.cleanup();
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: JSON.stringify({
|
|
225
|
+
success: true,
|
|
226
|
+
mutationScore: results.mutationScore,
|
|
227
|
+
totalMutations: results.totalMutations,
|
|
228
|
+
killedMutations: results.killedMutations,
|
|
229
|
+
survivedMutations: results.survivedMutations,
|
|
230
|
+
timeoutMutations: results.timeoutMutations || 0,
|
|
231
|
+
errorMutations: results.errorMutations || 0,
|
|
232
|
+
meetsThreshold: results.mutationScore >= (args.threshold || 80),
|
|
233
|
+
message: `Mutation testing complete. Score: ${results.mutationScore.toFixed(1)}%`,
|
|
234
|
+
}, null, 2),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case 'generate_report': {
|
|
241
|
+
const rawData = loadLineageData(args.dataPath || '.jest-lineage-data.json');
|
|
242
|
+
const lineageData = processLineageDataForMutation(rawData);
|
|
243
|
+
|
|
244
|
+
const reporter = new TestCoverageReporter(
|
|
245
|
+
{ rootDir: process.cwd() },
|
|
246
|
+
{ outputFile: args.outputPath || 'test-lineage-report.html' }
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
reporter.processLineageResults(lineageData, 'unknown');
|
|
250
|
+
await reporter.generateHtmlReport();
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
content: [
|
|
254
|
+
{
|
|
255
|
+
type: 'text',
|
|
256
|
+
text: JSON.stringify({
|
|
257
|
+
success: true,
|
|
258
|
+
outputPath: args.outputPath || 'test-lineage-report.html',
|
|
259
|
+
message: 'HTML report generated successfully',
|
|
260
|
+
}, null, 2),
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
case 'query_coverage': {
|
|
267
|
+
const rawData = loadLineageData(args.dataPath || '.jest-lineage-data.json');
|
|
268
|
+
const lineageData = processLineageDataForMutation(rawData);
|
|
269
|
+
|
|
270
|
+
const path = require('path');
|
|
271
|
+
const normalizedFile = path.normalize(args.file);
|
|
272
|
+
|
|
273
|
+
const matchingFiles = Object.keys(lineageData).filter(f =>
|
|
274
|
+
f.includes(normalizedFile) || normalizedFile.includes(path.basename(f))
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (matchingFiles.length === 0) {
|
|
278
|
+
return {
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: 'text',
|
|
282
|
+
text: JSON.stringify({
|
|
283
|
+
success: false,
|
|
284
|
+
error: `No coverage data found for file: ${args.file}`,
|
|
285
|
+
}, null, 2),
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const targetFile = matchingFiles[0];
|
|
292
|
+
const fileCoverage = lineageData[targetFile];
|
|
293
|
+
|
|
294
|
+
if (args.line) {
|
|
295
|
+
const lineNumber = args.line.toString();
|
|
296
|
+
if (!fileCoverage[lineNumber]) {
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: 'text',
|
|
301
|
+
text: JSON.stringify({
|
|
302
|
+
success: false,
|
|
303
|
+
error: `No coverage data for line ${args.line} in ${args.file}`,
|
|
304
|
+
}, null, 2),
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
content: [
|
|
312
|
+
{
|
|
313
|
+
type: 'text',
|
|
314
|
+
text: JSON.stringify({
|
|
315
|
+
success: true,
|
|
316
|
+
file: targetFile,
|
|
317
|
+
line: lineNumber,
|
|
318
|
+
tests: fileCoverage[lineNumber],
|
|
319
|
+
}, null, 2),
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
} else {
|
|
324
|
+
const lines = Object.keys(fileCoverage);
|
|
325
|
+
const totalTests = new Set(
|
|
326
|
+
lines.flatMap(lineNum => fileCoverage[lineNum].map(t => t.testName))
|
|
327
|
+
).size;
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: 'text',
|
|
333
|
+
text: JSON.stringify({
|
|
334
|
+
success: true,
|
|
335
|
+
file: targetFile,
|
|
336
|
+
linesCovered: lines.length,
|
|
337
|
+
totalTests,
|
|
338
|
+
coverageByLine: fileCoverage,
|
|
339
|
+
}, null, 2),
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case 'analyze_full': {
|
|
347
|
+
const results = {
|
|
348
|
+
steps: [],
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// Step 1: Run tests
|
|
352
|
+
if (!args.skipTests) {
|
|
353
|
+
const testResult = await runJest({
|
|
354
|
+
args: [],
|
|
355
|
+
enableLineage: true,
|
|
356
|
+
enablePerformance: true,
|
|
357
|
+
enableQuality: true,
|
|
358
|
+
quiet: false,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
results.steps.push({
|
|
362
|
+
step: 'tests',
|
|
363
|
+
success: testResult.success,
|
|
364
|
+
exitCode: testResult.exitCode,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
if (!testResult.success) {
|
|
368
|
+
results.success = false;
|
|
369
|
+
results.message = 'Tests failed';
|
|
370
|
+
return {
|
|
371
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Step 2: Mutation testing
|
|
377
|
+
if (!args.skipMutation) {
|
|
378
|
+
try {
|
|
379
|
+
const config = loadFullConfig({ threshold: args.threshold });
|
|
380
|
+
const rawData = loadLineageData('.jest-lineage-data.json');
|
|
381
|
+
const lineageData = processLineageDataForMutation(rawData);
|
|
382
|
+
|
|
383
|
+
const mutationTester = new MutationTester(config);
|
|
384
|
+
mutationTester.setLineageData(lineageData);
|
|
385
|
+
|
|
386
|
+
const mutationResults = await mutationTester.runMutationTesting();
|
|
387
|
+
await mutationTester.cleanup();
|
|
388
|
+
|
|
389
|
+
results.steps.push({
|
|
390
|
+
step: 'mutation',
|
|
391
|
+
success: mutationResults.mutationScore >= (args.threshold || 80),
|
|
392
|
+
mutationScore: mutationResults.mutationScore,
|
|
393
|
+
});
|
|
394
|
+
} catch (err) {
|
|
395
|
+
results.steps.push({
|
|
396
|
+
step: 'mutation',
|
|
397
|
+
success: false,
|
|
398
|
+
error: err.message,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Step 3: Generate report
|
|
404
|
+
try {
|
|
405
|
+
const rawData = loadLineageData('.jest-lineage-data.json');
|
|
406
|
+
const lineageData = processLineageDataForMutation(rawData);
|
|
407
|
+
|
|
408
|
+
const reporter = new TestCoverageReporter(
|
|
409
|
+
{ rootDir: process.cwd() },
|
|
410
|
+
{ outputFile: args.outputPath || 'test-lineage-report.html' }
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
reporter.processLineageResults(lineageData, 'unknown');
|
|
414
|
+
await reporter.generateHtmlReport();
|
|
415
|
+
|
|
416
|
+
results.steps.push({
|
|
417
|
+
step: 'report',
|
|
418
|
+
success: true,
|
|
419
|
+
outputPath: args.outputPath || 'test-lineage-report.html',
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
results.steps.push({
|
|
423
|
+
step: 'report',
|
|
424
|
+
success: false,
|
|
425
|
+
error: err.message,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
results.success = results.steps.every(s => s.success);
|
|
430
|
+
results.message = results.success
|
|
431
|
+
? 'Full analysis completed successfully'
|
|
432
|
+
: 'Some steps failed';
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
default:
|
|
440
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
441
|
+
}
|
|
442
|
+
} catch (error) {
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: 'text',
|
|
447
|
+
text: JSON.stringify({
|
|
448
|
+
success: false,
|
|
449
|
+
error: error.message,
|
|
450
|
+
stack: error.stack,
|
|
451
|
+
}, null, 2),
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
isError: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Start server
|
|
460
|
+
async function main() {
|
|
461
|
+
const transport = new StdioServerTransport();
|
|
462
|
+
await server.connect(transport);
|
|
463
|
+
console.error('Jest Test Lineage Reporter MCP server running on stdio');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
main().catch((error) => {
|
|
467
|
+
console.error('Server error:', error);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Example file to demonstrate CPU cycle and performance tracking
|
|
2
|
+
|
|
3
|
+
export function lightweightFunction(x: number): number {
|
|
4
|
+
return x + 1; // Very fast operation - minimal CPU cycles
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function mediumFunction(x: number): number {
|
|
8
|
+
let result = x;
|
|
9
|
+
let i = 0;
|
|
10
|
+
while (i < 100) { // Medium CPU usage
|
|
11
|
+
result = Math.sqrt(result + i);
|
|
12
|
+
i++;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function heavyFunction(x: number): number {
|
|
18
|
+
let result = x;
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i < 1000) { // Heavy CPU usage - many cycles (reduced for testing)
|
|
21
|
+
result = Math.sin(Math.cos(Math.sqrt(result + i)));
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function memoryIntensiveFunction(size: number): number[] {
|
|
28
|
+
const array = new Array(size); // Memory allocation
|
|
29
|
+
let i = 0;
|
|
30
|
+
while (i < size) {
|
|
31
|
+
array[i] = Math.random() * i; // Memory writes
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
return array;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function recursiveFunction(n: number): number {
|
|
38
|
+
if (n <= 1) {
|
|
39
|
+
return lightweightFunction(n); // Light operation at leaf
|
|
40
|
+
}
|
|
41
|
+
if (n > 5) return n; // Prevent exponential explosion
|
|
42
|
+
return recursiveFunction(n - 1) + recursiveFunction(n - 2); // Limited recursion
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function nestedCallsFunction(x: number): number {
|
|
46
|
+
const light = lightweightFunction(x); // Depth 2
|
|
47
|
+
const medium = mediumFunction(light); // Depth 2
|
|
48
|
+
const heavy = heavyFunction(medium); // Depth 2
|
|
49
|
+
return heavy;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function mixedPerformanceFunction(iterations: number): number {
|
|
53
|
+
let result = 0;
|
|
54
|
+
let i = 0;
|
|
55
|
+
|
|
56
|
+
while (i < iterations) {
|
|
57
|
+
if (i % 3 === 0) {
|
|
58
|
+
result += lightweightFunction(i); // Fast path
|
|
59
|
+
} else if (i % 3 === 1) {
|
|
60
|
+
result += mediumFunction(i); // Medium path
|
|
61
|
+
} else {
|
|
62
|
+
result += heavyFunction(i); // Slow path
|
|
63
|
+
}
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Function that demonstrates different performance characteristics
|
|
71
|
+
export function performanceVariableFunction(mode: 'fast' | 'medium' | 'slow'): number {
|
|
72
|
+
switch (mode) {
|
|
73
|
+
case 'fast':
|
|
74
|
+
return lightweightFunction(42);
|
|
75
|
+
case 'medium':
|
|
76
|
+
return mediumFunction(42);
|
|
77
|
+
case 'slow':
|
|
78
|
+
return heavyFunction(42);
|
|
79
|
+
default:
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|