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
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Command Router
|
|
3
|
+
* Main CLI logic with commander.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Command } = require('commander');
|
|
7
|
+
const testCommand = require('./commands/test');
|
|
8
|
+
const mutateCommand = require('./commands/mutate');
|
|
9
|
+
const reportCommand = require('./commands/report');
|
|
10
|
+
const queryCommand = require('./commands/query');
|
|
11
|
+
const analyzeCommand = require('./commands/analyze');
|
|
12
|
+
const pkg = require('../../package.json');
|
|
13
|
+
|
|
14
|
+
async function run(argv) {
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('jest-lineage')
|
|
19
|
+
.description('Comprehensive test analytics with lineage tracking and mutation testing')
|
|
20
|
+
.version(pkg.version, '-v, --version', 'Display version number');
|
|
21
|
+
|
|
22
|
+
// Test command - Run Jest with lineage tracking
|
|
23
|
+
program
|
|
24
|
+
.command('test [jest-args...]')
|
|
25
|
+
.description('Run Jest tests with lineage tracking enabled')
|
|
26
|
+
.option('--no-lineage', 'Disable lineage tracking')
|
|
27
|
+
.option('--no-performance', 'Disable performance tracking')
|
|
28
|
+
.option('--no-quality', 'Disable quality analysis')
|
|
29
|
+
.option('--config <path>', 'Path to Jest config file')
|
|
30
|
+
.option('--quiet, -q', 'Suppress console output')
|
|
31
|
+
.action(testCommand);
|
|
32
|
+
|
|
33
|
+
// Mutate command - Run mutation testing standalone
|
|
34
|
+
program
|
|
35
|
+
.command('mutate')
|
|
36
|
+
.description('Run mutation testing on existing lineage data')
|
|
37
|
+
.option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
|
|
38
|
+
.option('--threshold <number>', 'Mutation score threshold (%)', '80')
|
|
39
|
+
.option('--timeout <ms>', 'Timeout per mutation (ms)', '5000')
|
|
40
|
+
.option('--debug', 'Create debug mutation files instead of running tests')
|
|
41
|
+
.option('--debug-dir <path>', 'Directory for debug files', './mutations-debug')
|
|
42
|
+
.option('--operators <list>', 'Comma-separated mutation operators to enable')
|
|
43
|
+
.option('--verbose', 'Enable debug logging')
|
|
44
|
+
.action(mutateCommand);
|
|
45
|
+
|
|
46
|
+
// Report command - Generate HTML report
|
|
47
|
+
program
|
|
48
|
+
.command('report')
|
|
49
|
+
.description('Generate HTML report from existing lineage data')
|
|
50
|
+
.option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
|
|
51
|
+
.option('--output <path>', 'Output HTML file path', 'test-lineage-report.html')
|
|
52
|
+
.option('--open', 'Open report in browser after generation')
|
|
53
|
+
.option('--format <type>', 'Report format (html, json)', 'html')
|
|
54
|
+
.action(reportCommand);
|
|
55
|
+
|
|
56
|
+
// Query command - Query test coverage
|
|
57
|
+
program
|
|
58
|
+
.command('query <file> [line]')
|
|
59
|
+
.description('Query which tests cover specific files or lines')
|
|
60
|
+
.option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
|
|
61
|
+
.option('--json', 'Output as JSON')
|
|
62
|
+
.option('--format <type>', 'Output format (table, list, json)', 'table')
|
|
63
|
+
.action(queryCommand);
|
|
64
|
+
|
|
65
|
+
// Analyze command - Full workflow
|
|
66
|
+
program
|
|
67
|
+
.command('analyze')
|
|
68
|
+
.description('Full workflow: test + mutation + report')
|
|
69
|
+
.option('--config <path>', 'Path to Jest config file')
|
|
70
|
+
.option('--threshold <number>', 'Mutation score threshold (%)', '80')
|
|
71
|
+
.option('--output <path>', 'Output HTML file path', 'test-lineage-report.html')
|
|
72
|
+
.option('--open', 'Open report in browser')
|
|
73
|
+
.option('--skip-tests', 'Skip running tests (use existing data)')
|
|
74
|
+
.option('--skip-mutation', 'Skip mutation testing')
|
|
75
|
+
.option('--no-lineage', 'Disable lineage tracking')
|
|
76
|
+
.option('--no-performance', 'Disable performance tracking')
|
|
77
|
+
.option('--no-quality', 'Disable quality analysis')
|
|
78
|
+
.action(analyzeCommand);
|
|
79
|
+
|
|
80
|
+
// Show help if no command provided
|
|
81
|
+
if (argv.length <= 2) {
|
|
82
|
+
program.help();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Parse and execute
|
|
86
|
+
await program.parseAsync(argv);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { run };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Loader
|
|
3
|
+
* Load and merge configuration from multiple sources
|
|
4
|
+
* Priority: CLI args > env vars > config file > package.json > defaults
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { loadConfig } = require('../../config');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load full configuration with proper priority
|
|
13
|
+
* @param {object} cliOptions - Options from CLI arguments
|
|
14
|
+
* @returns {object} Merged configuration
|
|
15
|
+
*/
|
|
16
|
+
function loadFullConfig(cliOptions = {}) {
|
|
17
|
+
// Start with defaults from config.js (includes env var processing)
|
|
18
|
+
let config = loadConfig();
|
|
19
|
+
|
|
20
|
+
// Load from package.json if available
|
|
21
|
+
const pkgConfig = loadPackageJsonConfig();
|
|
22
|
+
if (pkgConfig) {
|
|
23
|
+
config = { ...config, ...pkgConfig };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Override with CLI options (highest priority)
|
|
27
|
+
const cliConfig = mapCliOptionsToConfig(cliOptions);
|
|
28
|
+
config = { ...config, ...cliConfig };
|
|
29
|
+
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Map CLI options to config object
|
|
35
|
+
* @param {object} cliOptions - CLI options
|
|
36
|
+
* @returns {object} Config object
|
|
37
|
+
*/
|
|
38
|
+
function mapCliOptionsToConfig(cliOptions) {
|
|
39
|
+
const config = {};
|
|
40
|
+
|
|
41
|
+
// Feature toggles
|
|
42
|
+
if (cliOptions.lineage === false) config.enableLineageTracking = false;
|
|
43
|
+
if (cliOptions.performance === false) config.enablePerformanceTracking = false;
|
|
44
|
+
if (cliOptions.quality === false) config.enableQualityAnalysis = false;
|
|
45
|
+
|
|
46
|
+
// Mutation testing settings
|
|
47
|
+
if (cliOptions.threshold !== undefined) {
|
|
48
|
+
config.mutationThreshold = parseInt(cliOptions.threshold);
|
|
49
|
+
}
|
|
50
|
+
if (cliOptions.timeout !== undefined) {
|
|
51
|
+
config.mutationTimeout = parseInt(cliOptions.timeout);
|
|
52
|
+
}
|
|
53
|
+
if (cliOptions.debug === true) {
|
|
54
|
+
config.debugMutations = true;
|
|
55
|
+
}
|
|
56
|
+
if (cliOptions.debugDir !== undefined) {
|
|
57
|
+
config.debugMutationDir = cliOptions.debugDir;
|
|
58
|
+
}
|
|
59
|
+
if (cliOptions.operators !== undefined) {
|
|
60
|
+
// Parse comma-separated operators
|
|
61
|
+
const operators = cliOptions.operators.split(',').map(o => o.trim());
|
|
62
|
+
config.mutationOperators = {
|
|
63
|
+
arithmetic: operators.includes('arithmetic'),
|
|
64
|
+
comparison: operators.includes('comparison'),
|
|
65
|
+
logical: operators.includes('logical'),
|
|
66
|
+
conditional: operators.includes('conditional'),
|
|
67
|
+
assignment: operators.includes('assignment'),
|
|
68
|
+
literals: operators.includes('literals'),
|
|
69
|
+
returns: operators.includes('returns'),
|
|
70
|
+
increments: operators.includes('increments')
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Output settings
|
|
75
|
+
if (cliOptions.output !== undefined) {
|
|
76
|
+
config.outputFile = cliOptions.output;
|
|
77
|
+
}
|
|
78
|
+
if (cliOptions.verbose === true) {
|
|
79
|
+
config.enableDebugLogging = true;
|
|
80
|
+
}
|
|
81
|
+
if (cliOptions.quiet === true) {
|
|
82
|
+
config.enableConsoleOutput = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Skip options
|
|
86
|
+
if (cliOptions.skipMutation === true) {
|
|
87
|
+
config.enableMutationTesting = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return config;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Load config from package.json "jest-lineage" field
|
|
95
|
+
* @returns {object|null} Config from package.json or null
|
|
96
|
+
*/
|
|
97
|
+
function loadPackageJsonConfig() {
|
|
98
|
+
try {
|
|
99
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
100
|
+
if (fs.existsSync(pkgPath)) {
|
|
101
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
102
|
+
return pkg['jest-lineage'] || null;
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Silently ignore errors
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
loadFullConfig,
|
|
112
|
+
mapCliOptionsToConfig,
|
|
113
|
+
loadPackageJsonConfig
|
|
114
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lineage Data Loader
|
|
3
|
+
* Load and validate .jest-lineage-data.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load lineage data from file
|
|
12
|
+
* @param {string} dataPath - Path to lineage data file
|
|
13
|
+
* @returns {object} Parsed lineage data
|
|
14
|
+
*/
|
|
15
|
+
function loadLineageData(dataPath = '.jest-lineage-data.json') {
|
|
16
|
+
const resolvedPath = path.resolve(process.cwd(), dataPath);
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Lineage data file not found: ${chalk.yellow(resolvedPath)}\n\n` +
|
|
21
|
+
`${chalk.cyan('Hint:')} Run ${chalk.green('jest-lineage test')} first to generate lineage data.`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
27
|
+
const data = JSON.parse(content);
|
|
28
|
+
|
|
29
|
+
// Validate structure
|
|
30
|
+
if (!data.timestamp || !Array.isArray(data.tests)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid lineage data format in ${chalk.yellow(resolvedPath)}\n\n` +
|
|
33
|
+
`Expected format: { timestamp: number, tests: array }`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (data.tests.length === 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`No test data found in ${chalk.yellow(resolvedPath)}\n\n` +
|
|
40
|
+
`The file exists but contains no test results. Run ${chalk.green('jest-lineage test')} to generate data.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return data;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.name === 'SyntaxError') {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Failed to parse lineage data file: ${chalk.yellow(resolvedPath)}\n\n` +
|
|
49
|
+
`The file contains invalid JSON. Error: ${error.message}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Process raw lineage data into format needed by MutationTester
|
|
58
|
+
* @param {object} rawData - Raw data from .jest-lineage-data.json
|
|
59
|
+
* @returns {object} Processed lineage data { filePath: { lineNumber: [testInfo, ...] } }
|
|
60
|
+
*/
|
|
61
|
+
function processLineageDataForMutation(rawData) {
|
|
62
|
+
const processed = {};
|
|
63
|
+
|
|
64
|
+
if (!rawData.tests) {
|
|
65
|
+
return processed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
rawData.tests.forEach((test) => {
|
|
69
|
+
if (!test.coverage) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const coverageKeys = Object.keys(test.coverage);
|
|
74
|
+
|
|
75
|
+
coverageKeys.forEach((lineKey) => {
|
|
76
|
+
// Parse line key: "file.ts:lineNumber"
|
|
77
|
+
const [filePath, lineNumber, ...suffixes] = lineKey.split(':');
|
|
78
|
+
|
|
79
|
+
// Skip metadata entries (depth, performance, meta) - only process basic line coverage
|
|
80
|
+
if (!lineNumber || suffixes.length > 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!processed[filePath]) {
|
|
85
|
+
processed[filePath] = {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!processed[filePath][lineNumber]) {
|
|
89
|
+
processed[filePath][lineNumber] = [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
processed[filePath][lineNumber].push({
|
|
93
|
+
testName: test.name,
|
|
94
|
+
testType: test.type || 'it',
|
|
95
|
+
testFile: test.testFile || 'unknown',
|
|
96
|
+
executionCount: test.coverage[lineKey] || 1
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return processed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if lineage data file exists
|
|
106
|
+
* @param {string} dataPath - Path to check
|
|
107
|
+
* @returns {boolean} True if file exists
|
|
108
|
+
*/
|
|
109
|
+
function lineageDataExists(dataPath = '.jest-lineage-data.json') {
|
|
110
|
+
const resolvedPath = path.resolve(process.cwd(), dataPath);
|
|
111
|
+
return fs.existsSync(resolvedPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
loadLineageData,
|
|
116
|
+
processLineageDataForMutation,
|
|
117
|
+
lineageDataExists
|
|
118
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Runner
|
|
3
|
+
* Orchestrate Jest execution with proper environment variables
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Run Jest with lineage tracking enabled
|
|
12
|
+
* @param {object} options - Jest run options
|
|
13
|
+
* @returns {Promise<object>} Result object with success status and exit code
|
|
14
|
+
*/
|
|
15
|
+
async function runJest(options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
args = [], // Jest arguments
|
|
18
|
+
config = null, // Path to Jest config
|
|
19
|
+
enableLineage = true, // Enable lineage tracking
|
|
20
|
+
enablePerformance = true, // Enable performance tracking
|
|
21
|
+
enableQuality = true, // Enable quality analysis
|
|
22
|
+
enableMutation = false, // Enable mutation mode
|
|
23
|
+
cwd = process.cwd(), // Working directory
|
|
24
|
+
stdio = 'inherit', // Stdio handling
|
|
25
|
+
quiet = false // Suppress output
|
|
26
|
+
} = options;
|
|
27
|
+
|
|
28
|
+
// Build Jest command
|
|
29
|
+
const jestPath = 'jest'; // Use npx/global jest
|
|
30
|
+
const jestArgs = [...args];
|
|
31
|
+
|
|
32
|
+
// Add config if specified
|
|
33
|
+
if (config) {
|
|
34
|
+
jestArgs.push('--config', config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Ensure coverage is collected (required for lineage tracking)
|
|
38
|
+
if (!jestArgs.includes('--coverage') && !jestArgs.includes('--no-coverage')) {
|
|
39
|
+
jestArgs.push('--coverage');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Set environment variables for lineage tracking
|
|
43
|
+
const env = {
|
|
44
|
+
...process.env,
|
|
45
|
+
JEST_LINEAGE_ENABLED: enableLineage ? 'true' : 'false',
|
|
46
|
+
JEST_LINEAGE_TRACKING: enableLineage ? 'true' : 'false',
|
|
47
|
+
JEST_LINEAGE_PERFORMANCE: enablePerformance ? 'true' : 'false',
|
|
48
|
+
JEST_LINEAGE_QUALITY: enableQuality ? 'true' : 'false',
|
|
49
|
+
JEST_LINEAGE_MUTATION: enableMutation ? 'true' : 'false',
|
|
50
|
+
JEST_LINEAGE_MUTATION_TESTING: 'false', // Not in mutation testing mode
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (!quiet) {
|
|
54
|
+
console.log(chalk.cyan('\nš§Ŗ Running Jest with lineage tracking...\n'));
|
|
55
|
+
console.log(chalk.gray(`Command: ${jestPath} ${jestArgs.join(' ')}\n`));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const jest = spawn(jestPath, jestArgs, {
|
|
60
|
+
cwd,
|
|
61
|
+
env,
|
|
62
|
+
stdio,
|
|
63
|
+
shell: true
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
jest.on('close', (code) => {
|
|
67
|
+
if (code === 0) {
|
|
68
|
+
if (!quiet) {
|
|
69
|
+
console.log(chalk.green('\nā
Tests completed successfully'));
|
|
70
|
+
}
|
|
71
|
+
resolve({ success: true, exitCode: code });
|
|
72
|
+
} else {
|
|
73
|
+
if (!quiet) {
|
|
74
|
+
console.log(chalk.red(`\nā Tests failed with exit code ${code}`));
|
|
75
|
+
}
|
|
76
|
+
resolve({ success: false, exitCode: code });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
jest.on('error', (error) => {
|
|
81
|
+
console.error(chalk.red('\nā Failed to run Jest:'), error.message);
|
|
82
|
+
console.error(chalk.yellow('\nMake sure Jest is installed:'));
|
|
83
|
+
console.error(chalk.gray(' npm install --save-dev jest\n'));
|
|
84
|
+
reject(error);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validate that Jest is available
|
|
91
|
+
* @returns {boolean} True if Jest is available
|
|
92
|
+
*/
|
|
93
|
+
function isJestAvailable() {
|
|
94
|
+
try {
|
|
95
|
+
require.resolve('jest');
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
runJest,
|
|
104
|
+
isJestAvailable
|
|
105
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output Formatter
|
|
3
|
+
* Format console output with colors and formatting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Print success message
|
|
11
|
+
* @param {string} message - Success message
|
|
12
|
+
*/
|
|
13
|
+
function success(message) {
|
|
14
|
+
console.log(chalk.green(`ā
${message}`));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Print error message
|
|
19
|
+
* @param {string} message - Error message
|
|
20
|
+
*/
|
|
21
|
+
function error(message) {
|
|
22
|
+
console.error(chalk.red(`ā ${message}`));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Print warning message
|
|
27
|
+
* @param {string} message - Warning message
|
|
28
|
+
*/
|
|
29
|
+
function warning(message) {
|
|
30
|
+
console.log(chalk.yellow(`ā ļø ${message}`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Print info message
|
|
35
|
+
* @param {string} message - Info message
|
|
36
|
+
*/
|
|
37
|
+
function info(message) {
|
|
38
|
+
console.log(chalk.cyan(`ā¹ļø ${message}`));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Print section header
|
|
43
|
+
* @param {string} title - Section title
|
|
44
|
+
*/
|
|
45
|
+
function section(title) {
|
|
46
|
+
console.log(chalk.bold.cyan(`\n${title}`));
|
|
47
|
+
console.log(chalk.gray('ā'.repeat(title.length + 2)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a spinner for long operations
|
|
52
|
+
* @param {string} text - Spinner text
|
|
53
|
+
* @returns {object} Ora spinner instance
|
|
54
|
+
*/
|
|
55
|
+
function spinner(text) {
|
|
56
|
+
return ora({
|
|
57
|
+
text,
|
|
58
|
+
color: 'cyan',
|
|
59
|
+
spinner: 'dots'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Print mutation results summary
|
|
65
|
+
* @param {object} results - Mutation test results
|
|
66
|
+
*/
|
|
67
|
+
function printMutationSummary(results) {
|
|
68
|
+
section('𧬠Mutation Testing Results');
|
|
69
|
+
|
|
70
|
+
console.log(`š ${chalk.bold('Total Mutations:')} ${results.totalMutations}`);
|
|
71
|
+
console.log(`${chalk.green('ā
Killed:')} ${results.killedMutations}`);
|
|
72
|
+
console.log(`${chalk.red('š“ Survived:')} ${results.survivedMutations}`);
|
|
73
|
+
console.log(`${chalk.yellow('ā° Timeout:')} ${results.timeoutMutations || 0}`);
|
|
74
|
+
console.log(`${chalk.gray('ā Error:')} ${results.errorMutations || 0}`);
|
|
75
|
+
console.log(`${chalk.bold.cyan('šÆ Mutation Score:')} ${chalk.bold(results.mutationScore.toFixed(1))}%`);
|
|
76
|
+
|
|
77
|
+
if (results.mutationScore >= 80) {
|
|
78
|
+
console.log(chalk.green('\nā
Excellent mutation score!'));
|
|
79
|
+
} else if (results.mutationScore >= 60) {
|
|
80
|
+
console.log(chalk.yellow('\nā ļø Good mutation score, but room for improvement'));
|
|
81
|
+
} else {
|
|
82
|
+
console.log(chalk.red('\nā Low mutation score - consider improving test quality'));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Print lineage data summary
|
|
88
|
+
* @param {object} data - Lineage data
|
|
89
|
+
*/
|
|
90
|
+
function printLineageDataSummary(data) {
|
|
91
|
+
const testCount = data.tests.length;
|
|
92
|
+
const failedTests = data.tests.filter(t => t.failed).length;
|
|
93
|
+
const passedTests = testCount - failedTests;
|
|
94
|
+
|
|
95
|
+
console.log(chalk.cyan(`\nš Lineage data loaded:`));
|
|
96
|
+
console.log(` ${chalk.green('ā')} ${passedTests} tests passed`);
|
|
97
|
+
if (failedTests > 0) {
|
|
98
|
+
console.log(` ${chalk.red('ā')} ${failedTests} tests failed`);
|
|
99
|
+
}
|
|
100
|
+
console.log(` ${chalk.gray('š
')} Generated: ${new Date(data.timestamp).toLocaleString()}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Format file path for display
|
|
105
|
+
* @param {string} filePath - File path
|
|
106
|
+
* @returns {string} Formatted path
|
|
107
|
+
*/
|
|
108
|
+
function formatPath(filePath) {
|
|
109
|
+
const cwd = process.cwd();
|
|
110
|
+
if (filePath.startsWith(cwd)) {
|
|
111
|
+
return chalk.gray(filePath.replace(cwd, '.'));
|
|
112
|
+
}
|
|
113
|
+
return chalk.gray(filePath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
success,
|
|
118
|
+
error,
|
|
119
|
+
warning,
|
|
120
|
+
info,
|
|
121
|
+
section,
|
|
122
|
+
spinner,
|
|
123
|
+
printMutationSummary,
|
|
124
|
+
printLineageDataSummary,
|
|
125
|
+
formatPath
|
|
126
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Example file to demonstrate call depth tracking
|
|
2
|
+
|
|
3
|
+
export function directFunction(x: number): number {
|
|
4
|
+
return x * 2; // This will be depth 1 when called directly from tests
|
|
5
|
+
}
|
|
6
|
+
export function oneLevel(x: number): number {
|
|
7
|
+
return directFunction(x) + 1; // directFunction will be depth 2 here
|
|
8
|
+
}
|
|
9
|
+
export function twoLevels(x: number): number {
|
|
10
|
+
return oneLevel(x) + 1; // directFunction will be depth 3 here
|
|
11
|
+
}
|
|
12
|
+
export function threeLevels(x: number): number {
|
|
13
|
+
return twoLevels(x) + 1; // directFunction will be depth 4 here
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Complex function that calls multiple other functions
|
|
17
|
+
export function complexFunction(x: number): number {
|
|
18
|
+
const a = directFunction(x); // depth 2
|
|
19
|
+
const b = oneLevel(x); // directFunction will be depth 3 here
|
|
20
|
+
const c = twoLevels(x); // directFunction will be depth 4 here
|
|
21
|
+
return a + b + c;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Recursive function to test deep call stacks
|
|
25
|
+
export function recursiveFunction(n: number, depth: number = 0): number {
|
|
26
|
+
if (depth >= 3) {
|
|
27
|
+
return directFunction(n); // This will be very deep
|
|
28
|
+
}
|
|
29
|
+
return recursiveFunction(n, depth + 1) + 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Global array to hold leaked memory - this will cause actual memory leaks
|
|
33
|
+
const memoryLeakStorage: any[] = [];
|
|
34
|
+
export function memoryLeakFunction(size: number): number {
|
|
35
|
+
// Create large objects and store them globally (this leaks memory!)
|
|
36
|
+
const largeObject = {
|
|
37
|
+
id: Date.now(),
|
|
38
|
+
data: new Array(size).fill(0).map((_, i) => ({
|
|
39
|
+
index: i,
|
|
40
|
+
value: Math.random(),
|
|
41
|
+
timestamp: new Date(),
|
|
42
|
+
largeString: 'x'.repeat(1000),
|
|
43
|
+
// 1KB string per item
|
|
44
|
+
metadata: {
|
|
45
|
+
created: Date.now(),
|
|
46
|
+
processed: false,
|
|
47
|
+
tags: ['memory', 'leak', 'test', 'large'],
|
|
48
|
+
history: new Array(100).fill(0).map(() => Math.random())
|
|
49
|
+
}
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Store in global array - this prevents garbage collection (memory leak!)
|
|
54
|
+
memoryLeakStorage.push(largeObject);
|
|
55
|
+
|
|
56
|
+
// Also call our tracked function
|
|
57
|
+
return directFunction(size);
|
|
58
|
+
}
|
|
59
|
+
export function clearMemoryLeaks(): number {
|
|
60
|
+
const count = memoryLeakStorage.length;
|
|
61
|
+
memoryLeakStorage.length = 0; // Clear the array
|
|
62
|
+
return count;
|
|
63
|
+
}
|
|
64
|
+
export function getMemoryLeakCount(): number {
|
|
65
|
+
return memoryLeakStorage.length;
|
|
66
|
+
}
|