Files
everything-is-suitable/scripts/smart-test-selector.ts
T
张翔 08ea5fbe98 feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
2026-03-28 14:37:29 +08:00

232 lines
5.6 KiB
TypeScript

import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { testMapping, moduleToTests } from '../config/test-mapping.config';
export interface TestSelectionResult {
selectedTests: string[];
affectedModules: string[];
changedFiles: string[];
analysisReport: string;
}
export class SmartTestSelector {
private projectRoot: string;
constructor(projectRoot: string = process.cwd()) {
this.projectRoot = projectRoot;
}
/**
* 根据代码变更选择测试用例
*/
selectTestsByChanges(
changedFiles: string[],
options: {
includeRelated?: boolean;
priority?: 'high' | 'medium' | 'low' | 'all';
testLevel?: 'smoke' | 'functional' | 'all';
} = {}
): TestSelectionResult {
const {
includeRelated = true,
priority = 'all',
testLevel = 'all',
} = options;
const selectedTests = new Set<string>();
const affectedModules = new Set<string>();
// 分析每个变更文件
for (const file of changedFiles) {
const normalizedPath = this.normalizePath(file);
const mapping = this.findMapping(normalizedPath);
if (mapping) {
// 添加直接关联的测试
mapping.tests.forEach(test => selectedTests.add(test));
mapping.modules.forEach(module => affectedModules.add(module));
// 如果启用关联分析,添加相关模块的测试
if (includeRelated) {
this.addRelatedTests(mapping.modules, selectedTests, affectedModules);
}
}
}
// 根据优先级过滤
const filteredTests = this.filterByPriority(
Array.from(selectedTests),
priority
);
// 根据测试级别过滤
const finalTests = this.filterByTestLevel(filteredTests, testLevel);
// 生成分析报告
const analysisReport = this.generateAnalysisReport({
changedFiles,
affectedModules: Array.from(affectedModules),
selectedTests: finalTests,
});
return {
selectedTests: finalTests,
affectedModules: Array.from(affectedModules),
changedFiles,
analysisReport,
};
}
/**
* 从Git获取变更文件
*/
getChangedFilesFromGit(
baseBranch: string = 'origin/main',
headBranch: string = 'HEAD'
): string[] {
try {
const output = execSync(
`git diff --name-only ${baseBranch}...${headBranch}`,
{ encoding: 'utf-8', cwd: this.projectRoot }
);
return output
.split('\n')
.filter(file => file.trim() && this.isSourceFile(file));
} catch (error) {
console.error('Failed to get changed files from git:', error);
return [];
}
}
/**
* 规范化文件路径
*/
private normalizePath(filePath: string): string {
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
}
/**
* 查找文件对应的测试映射
*/
private findMapping(normalizedPath: string) {
// 精确匹配
if (testMapping[normalizedPath]) {
return testMapping[normalizedPath];
}
// 模糊匹配(支持通配符)
for (const [pattern, mapping] of Object.entries(testMapping)) {
if (this.matchPattern(normalizedPath, pattern)) {
return mapping;
}
}
return null;
}
/**
* 简单的模式匹配
*/
private matchPattern(path: string, pattern: string): boolean {
const regex = new RegExp(
'^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$'
);
return regex.test(path);
}
/**
* 添加相关测试
*/
private addRelatedTests(
modules: string[],
selectedTests: Set<string>,
affectedModules: Set<string>
): void {
for (const module of modules) {
const relatedTests = moduleToTests[module];
if (relatedTests) {
relatedTests.forEach(test => selectedTests.add(test));
}
}
}
/**
* 根据优先级过滤测试
*/
private filterByPriority(
tests: string[],
priority: 'high' | 'medium' | 'low' | 'all'
): string[] {
if (priority === 'all') {
return tests;
}
const priorityMap = {
high: ['@p0', '@smoke'],
medium: ['@p1', '@functional'],
low: ['@p2', '@edge'],
};
const targetTags = priorityMap[priority];
return tests.filter(test =>
targetTags.some(tag => test.includes(tag))
);
}
/**
* 根据测试级别过滤
*/
private filterByTestLevel(
tests: string[],
level: 'smoke' | 'functional' | 'all'
): string[] {
if (level === 'all') {
return tests;
}
const levelMap = {
smoke: '@smoke',
functional: '@functional',
};
const targetTag = levelMap[level];
return tests.filter(test => test.includes(targetTag));
}
/**
* 判断是否为源代码文件
*/
private isSourceFile(filePath: string): boolean {
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.vue', '.java'];
return extensions.some(ext => filePath.endsWith(ext));
}
/**
* 生成分析报告
*/
private generateAnalysisReport(data: {
changedFiles: string[];
affectedModules: string[];
selectedTests: string[];
}): string {
return `
# 智能测试选择分析报告
## 变更文件 (${data.changedFiles.length}个)
${data.changedFiles.map(f => `- ${f}`).join('\n')}
## 受影响模块 (${data.affectedModules.length}个)
${data.affectedModules.map(m => `- ${m}`).join('\n')}
## 选中测试用例 (${data.selectedTests.length}个)
${data.selectedTests.map(t => `- ${t}`).join('\n')}
## 执行建议
- 优先执行冒烟测试(@smoke标签)
- 然后执行功能测试(@functional标签)
- 最后执行边缘场景测试(@edge标签)
`.trim();
}
}