Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | 20x 8x 8x 2x 2x 3x 3x 3x 3x 3x 3x 3x 2x 2x 1x 1x 2x 2x 2x 10x 10x 10x 10x 2x 2x 8x 2x 2x 6x 6x 6x 2x 2x 2x 2x 6x 3x 3x 3x 3x 3x 6x 6x 3x 3x 3x 3x 1x 1x 1x | /**
* File utilities for validation framework
* Core: checksum generation, file finding, manifest management
*/
import crypto from 'node:crypto';
import fs from 'fs-extra';
import path from 'node:path';
import glob from 'glob';
import { diffLines } from 'diff';
/**
* Generate checksum for file content
* @param {string|Buffer} content - File content
* @returns {string} MD5 checksum
*/
export function getChecksum(content) {
return crypto.createHash('md5').update(content).digest('hex');
}
/**
* Find files matching glob pattern
* @param {string} directory - Directory to search
* @param {string} pattern - Glob pattern
* @returns {string[]} Array of relative file paths
*/
export function findFiles(directory, pattern = '**/*') {
const files = glob.sync(pattern, {
cwd: directory,
nodir: true,
dot: false,
});
return files.sort();
}
/**
* Copy file preserving metadata
* @param {string} src - Source path
* @param {string} dest - Destination path
*/
export function copyFileWithMetadata(src, dest) {
fs.ensureDirSync(path.dirname(dest));
fs.copyFileSync(src, dest);
}
/**
* Create manifest for baseline
* @param {string} directory - Source directory
* @param {string} pattern - Glob pattern
* @returns {Object} Manifest object
*/
export function createManifest(directory, pattern = '**/*') {
const files = findFiles(directory, pattern);
const manifest = {
version: '1.0.0',
timestamp: new Date().toISOString(),
files: {},
};
for (const file of files) {
const fullPath = path.join(directory, file);
const content = fs.readFileSync(fullPath);
manifest.files[file] = {
checksum: getChecksum(content),
size: content.length,
};
}
return manifest;
}
/**
* Read manifest from baseline directory
* @param {string} baselineDir - Baseline directory path
* @returns {Object|null} Manifest object or null if not found
*/
export function readManifest(baselineDir) {
const manifestPath = path.join(baselineDir, 'manifest.json');
if (!fs.existsSync(manifestPath)) {
return null;
}
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
}
/**
* Write manifest to baseline directory
* @param {string} baselineDir - Baseline directory path
* @param {Object} manifest - Manifest object
*/
export function writeManifest(baselineDir, manifest) {
const manifestPath = path.join(baselineDir, 'manifest.json');
fs.ensureDirSync(baselineDir);
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
}
/**
* Compare two files and return diff information
* @param {string} baselinePath - Path to baseline file
* @param {string} currentPath - Path to current file
* @param {Function[]} beforeCompareHooks - Array of preprocessing functions
* @returns {Object} Comparison result
*/
export function compareFiles(baselinePath, currentPath, beforeCompareHooks = []) {
const baselineExists = fs.existsSync(baselinePath);
const currentExists = fs.existsSync(currentPath);
// Handle missing files
Iif (!baselineExists && !currentExists) {
return null;
}
if (!baselineExists) {
const currentContent = fs.readFileSync(currentPath, 'utf8');
return {
status: 'added',
baselineContent: '',
currentContent,
diff: [{ added: true, value: currentContent }],
linesAdded: currentContent.split('\n').length,
linesRemoved: 0,
};
}
if (!currentExists) {
const baselineContent = fs.readFileSync(baselinePath, 'utf8');
return {
status: 'removed',
baselineContent,
currentContent: '',
diff: [{ removed: true, value: baselineContent }],
linesAdded: 0,
linesRemoved: baselineContent.split('\n').length,
};
}
// Both files exist - compare
let baselineContent = fs.readFileSync(baselinePath, 'utf8');
let currentContent = fs.readFileSync(currentPath, 'utf8');
// Apply beforeCompare hooks for preprocessing
for (const hook of beforeCompareHooks) {
const result = hook(baselineContent, currentContent, baselinePath);
Eif (result) {
baselineContent = result.baseline || baselineContent;
currentContent = result.current || currentContent;
}
}
// Quick checksum comparison
if (getChecksum(baselineContent) === getChecksum(currentContent)) {
return {
status: 'identical',
baselineContent,
currentContent,
diff: [],
linesAdded: 0,
linesRemoved: 0,
};
}
// Detailed diff
const diff = diffLines(baselineContent, currentContent);
let linesAdded = 0;
let linesRemoved = 0;
diff.forEach((part) => {
const lines = part.value.split('\n').length - 1;
if (part.added) {
linesAdded += lines || 1;
} else if (Epart.removed) {
linesRemoved += lines || 1;
}
});
return {
status: 'different',
baselineContent,
currentContent,
diff,
linesAdded,
linesRemoved,
};
}
/**
* Get all files from both baseline and current directories
* @param {string} baselineDir - Baseline directory
* @param {string} currentDir - Current directory
* @param {string} pattern - Glob pattern
* @returns {Set<string>} Set of all relative file paths
*/
export function getAllFiles(baselineDir, currentDir, pattern = '**/*') {
const baselineFiles = fs.existsSync(baselineDir) ? findFiles(baselineDir, pattern) : [];
const currentFiles = fs.existsSync(currentDir) ? findFiles(currentDir, pattern) : [];
return new Set([...baselineFiles, ...currentFiles]);
}
|