🐛 Fix tests and test actions usage

This commit is contained in:
Tw93
2025-08-22 07:21:22 +08:00
parent c6a29a2982
commit 7b605ee3a4
6 changed files with 274 additions and 415 deletions

View File

@@ -1,295 +1,78 @@
# Pake CLI Test Suite
# Pake CLI 测试
这个目录包含了简化统一的 Pake CLI 工具测试套件。
统一的 CLI 构建测试套件,验证多平台打包功能
## 快速开始
## 运行测试
```bash
# 运行所有测试 (unit + integration + builder)
npm test
# 或者直接运行
node tests/index.js
```
## 测试结构
## 测试内容
```tree
tests/
├── index.js # 🎯 主测试运行器 (默认测试)
├── github.js # 🔧 GitHub Actions 集成测试
├── build.js # 🏗️ GitHub.com 构建测试
├── complete.js # ✅ 完整端到端构建测试
├── config.js # ⚙️ 测试配置
└── README.md # 📖 说明文档
```
### 单元测试6个
## 测试类别
- 版本命令 (`--version`)
- 帮助命令 (`--help`)
- URL 验证(有效/无效链接)
- 参数验证(数字类型检查)
- CLI 响应时间(<2秒
- Weekly URL 可访问性
### 1. 主测试套件 (`index.js`)
### 集成测试3个
包含核心功能测试,通过 `npm test` 运行:
- 进程生成和管理
- 文件系统权限检查
- 依赖包解析验证
**单元测试 (Unit Tests)**
### 构建测试3个
- ✅ 版本和帮助命令
- ✅ 参数和 URL 验证
- ✅ 数字参数验证
- ✅ 响应时间检查
- ✅ URL 可访问性
- 平台检测macOS/Windows/Linux
- 架构检测Intel/ARM64
- 文件命名模式验证
**集成测试 (Integration Tests)**
### 真实构建测试(重点)
- ✅ 进程生成
- ✅ 文件系统权限
- ✅ 依赖解析
**macOS**: 🔥 多架构构建Universal Binary
**构建测试 (Builder Tests)**
- 编译 Intel + Apple Silicon 双架构
- 检测 `.app` 文件生成:`GitHubMultiArch.app`
- 备用检测:`src-tauri/target/universal-apple-darwin/release/bundle/macos/`
- 验证通用二进制:`file` 命令检查架构
- ✅ 平台检测 (macOS/Windows/Linux)
- ✅ 架构检测
- ✅ 文件命名模式
**Windows**: 单架构构建
### 2. GitHub Actions 测试 (`github.js`)
- 检测 EXE 文件:`src-tauri/target/x86_64-pc-windows-msvc/release/pake.exe`
- 检测 MSI 安装包:`src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi`
专门测试 GitHub Actions 集成功能:
**Linux**: 单架构构建
- ✅ npm 包安装测试
- ✅ 环境变量模拟
- ✅ 配置清理逻辑
- ✅ 图标获取逻辑
- ✅ 平台特定构建检测
- ✅ 构建命令生成
- ✅ 工作流配置验证
- ✅ Rust 特性标志验证
- ✅ 配置验证逻辑
- ✅ GitHub.com 构建模拟
- ✅ 实际构建脚本测试
- 检测 DEB 包:`src-tauri/target/release/bundle/deb/*.deb`
- 检测 AppImage`src-tauri/target/release/bundle/appimage/*.AppImage`
### 3. 快速构建测试 (`build.js`)
## 为什么重点测试多架构?
GitHub.com 专用快速构建验证
多架构构建是最复杂、最容易出错的环节
- ✅ CLI 构建过程
- ✅ 配置生成
- ✅ 编译启动验证
- 需要同时编译两个架构x86_64 + aarch64
- 生成通用二进制文件技术复杂
- 架构兼容性问题频发
- Apple Silicon 迁移期关键功能
### 4. 完整构建测试 (`complete.js`)
## 测试结果
端到端的完整构建流程:
- ✅ GitHub.com 完整打包
- ✅ 应用包结构验证
- ✅ 构建阶段跟踪
- ✅ 文件生成验证
## 测试命令
| 命令 | 描述 | 覆盖范围 | 持续时间 |
| ---------------------------------- | -------------------- | ----------------------------- | ----------- |
| `npm test` | **真实完整构建测试** | 完整 GitHub.com 应用打包 | **~8 分钟** |
| `node tests/index.js` | 基础测试套件 | Unit + Integration + Builder | ~30 秒 |
| `node tests/index.js --real-build` | 真实构建测试 | 完整 GitHub.com 应用打包 | ~8 分钟 |
| `node tests/github.js` | GitHub Actions 测试 | 12 个 GitHub Actions 专项测试 | ~2 分钟 |
| `node tests/build.js` | 快速构建测试 | GitHub.com 构建验证 | ~3 分钟 |
| `node tests/complete.js` | 完整构建测试 | 端到端完整构建流程 | ~10 分钟 |
## 高级用法
```bash
# 运行特定测试类别
node tests/index.js --unit --integration # 只运行单元和集成测试
node tests/index.js --builder # 只运行构建测试
node tests/index.js --quick # 快速测试模式
# 运行专项测试
node tests/index.js --real-build # 真实完整构建测试
node tests/index.js --pake-cli # GitHub Actions 专项测试
node tests/index.js --e2e # 端到端测试
# 获取帮助
node tests/index.js --help
```
## 测试配置
测试使用 `config.js` 中的配置:
```javascript
export const TIMEOUTS = {
QUICK: 3000, // 快速命令
MEDIUM: 10000, // 验证测试
LONG: 300000, // 构建测试
};
export const TEST_URLS = {
GITHUB: "https://github.com",
WEEKLY: "https://weekly.tw93.fun",
VALID: "https://example.com",
INVALID: "not://a/valid[url]",
};
```
## 手动测试场景
### 基础用法
```bash
# 测试基本应用创建
node dist/cli.js https://github.com --name "GitHub"
# 测试自定义尺寸
node dist/cli.js https://github.com --name "GitHub" --width 1200 --height 800
# 测试调试模式
node dist/cli.js https://github.com --name "DebugApp" --debug
```
### 高级功能
```bash
# 测试远程 CDN 图标
node dist/cli.js https://weekly.tw93.fun --name "Weekly" --icon "https://gw.alipayobjects.com/os/k/fw/weekly.icns"
# 测试注入文件
echo "body { background: #f0f0f0; }" > test.css
echo "console.log('injected');" > test.js
node dist/cli.js https://github.com --name "InjectionApp" --inject ./test.css,./test.js
# 测试全屏应用
node dist/cli.js https://github.com --name "FullGitHub" --fullscreen
# 测试系统托盘集成
node dist/cli.js https://github.com --name "TrayGitHub" --show-system-tray
```
### 平台特定 (macOS)
```bash
# 测试通用二进制
node dist/cli.js https://github.com --name "GitHub" --multi-arch
# 测试隐藏标题栏
node dist/cli.js https://github.com --name "ImmersiveGitHub" --hide-title-bar
```
## GitHub Actions 集成
项目使用简化的 GitHub Actions 工作流:
### 当前工作流:
- **`quality-and-test.yml`** - 运行所有测试、代码格式化和 Rust 质量检查
- **`claude-unified.yml`** - Claude AI 集成用于代码审查和协助
- **`release.yml`** - 处理发布、应用构建和 Docker 发布
- **`pake-cli.yaml`** - 手动 CLI 构建工作流
- **`pake_build_single_app.yaml`** - 可重用的单应用构建工作流
### 集成示例:
```yaml
# 推送/PR 时自动测试
- name: Run Quality & Tests
run: npm test
# 手动 CLI 构建
- name: Build CLI
run: npm run cli:build
```
总计:**13 个测试**,全部通过表示 CLI 功能正常。
## 故障排除
### 常见问题
**CLI 文件不存在**:运行 `npm run cli:build`
1. **"CLI file not found"**
**测试超时**:构建测试需要较长时间完成
```bash
npm run cli:build
```
**构建失败**:检查 Rust 工具链 `rustup update`
2. **"Permission denied"**
**权限错误**:确保有写入权限
```bash
chmod +x tests/index.js
```
## 开发建议
3. **"Timeout errors"**
- 在 `config.js` 中增加超时时间
- 检查系统资源
### 调试模式
使用调试输出运行测试:
```bash
DEBUG=1 npm test
# 或
CI=1 node tests/index.js --quick
```
## 性能预期
| 平台 | 首次构建 | 后续构建 | 内存使用 |
| --------- | -------- | -------- | -------- |
| M1 Mac | 2-3 分钟 | 30-45秒 | ~200MB |
| Intel Mac | 3-4 分钟 | 45-60秒 | ~250MB |
| Linux | 4-5 分钟 | 60-90秒 | ~300MB |
| Windows | 5-6 分钟 | 90-120秒 | ~350MB |
## 添加新测试
### 在主测试套件中添加单元测试
```javascript
// 在 index.js 的 runUnitTests() 方法中
await this.runTest(
"我的新测试",
() => {
// 测试逻辑
return true; // 或 false
},
TIMEOUTS.QUICK,
);
```
### 添加 GitHub Actions 测试
```javascript
// 在 github.js 中
runner.addTest(
"我的 GitHub Actions 测试",
async () => {
// 异步测试逻辑
return await someAsyncOperation();
},
TIMEOUTS.MEDIUM,
"测试描述",
);
```
## 贡献指南
添加新功能时:
1. 为新功能添加单元测试
2. 为新工作流添加集成测试
3. 更新手动测试场景
4. 提交前运行完整测试套件
```bash
# 提交前测试流程
npm run cli:build
npm test
node tests/github.js # 可选GitHub Actions 测试
node tests/complete.js # 可选:完整构建测试
```
## 测试覆盖
- **单元测试**: 12 个核心功能测试
- **GitHub Actions**: 12 个专项集成测试
- **构建验证**: 完整的端到端构建流程测试
- **平台支持**: macOS, Windows, Linux
- **架构支持**: Intel, ARM64, Universal (macOS)
通过 `npm test` 可快速验证核心功能,专项测试可按需运行以验证特定场景。
提交代码前建议运行 `npm test` 确保所有平台构建正常。

View File

@@ -69,15 +69,15 @@ class PakeTestRunner {
}
if (realBuild && !quick) {
console.log("\n🏗 Running Real Build Test...");
await this.runRealBuildTest();
testCount++;
// Add multi-arch test on macOS
// On macOS, prefer multi-arch test as it's more likely to catch issues
if (process.platform === "darwin") {
console.log("\n🔧 Running Multi-Arch Build Test...");
console.log("\n🏗️ Running Real Build Test (Multi-Arch)...");
await this.runMultiArchBuildTest();
testCount++;
} else {
console.log("\n🏗 Running Real Build Test...");
await this.runRealBuildTest();
testCount++;
}
}
@@ -619,53 +619,27 @@ class PakeTestRunner {
linux: {
app: path.join(
config.PROJECT_ROOT,
`src-tauri/target/release/bundle/deb/${testName.toLowerCase()}_*.deb`,
`src-tauri/target/release/bundle/deb/*.deb`,
),
installer: path.join(
config.PROJECT_ROOT,
`src-tauri/target/release/bundle/appimage/${testName.toLowerCase()}_*.AppImage`,
`src-tauri/target/release/bundle/appimage/*.AppImage`,
),
},
win32: {
app: path.join(config.PROJECT_ROOT, `${testName}.exe`),
installer: path.join(config.PROJECT_ROOT, `${testName}.msi`),
app: path.join(
config.PROJECT_ROOT,
`src-tauri/target/x86_64-pc-windows-msvc/release/pake.exe`,
),
installer: path.join(
config.PROJECT_ROOT,
`src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi`,
),
},
};
const platform = process.platform;
const expectedFiles = outputFiles[platform] || outputFiles.darwin;
// Helper function to check if files exist (handles wildcards for Linux)
const checkFileExists = (filePath) => {
if (filePath.includes("*")) {
const dir = path.dirname(filePath);
const pattern = path.basename(filePath);
try {
const files = fs.readdirSync(dir);
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
return files.some((file) => regex.test(file));
} catch {
return false;
}
}
return fs.existsSync(filePath);
};
const getActualFilePath = (filePath) => {
if (filePath.includes("*")) {
const dir = path.dirname(filePath);
const pattern = path.basename(filePath);
try {
const files = fs.readdirSync(dir);
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
const match = files.find((file) => regex.test(file));
return match ? path.join(dir, match) : filePath;
} catch {
return filePath;
}
}
return filePath;
};
console.log(`🔧 Starting real build test for GitHub.com...`);
console.log(`📝 Expected output: ${expectedFiles.app}`);
@@ -716,19 +690,21 @@ class PakeTestRunner {
// Real timeout - 8 minutes for actual build
const timeout = setTimeout(() => {
const appExists = checkFileExists(expectedFiles.app);
const installerExists = checkFileExists(expectedFiles.installer);
const appExists = this.checkFileExists(expectedFiles.app);
const installerExists = this.checkFileExists(
expectedFiles.installer,
);
if (appExists) {
console.log(
" 🎉 Build completed successfully (app file exists)!",
);
console.log(
` 📱 App location: ${getActualFilePath(expectedFiles.app)}`,
` 📱 App location: ${this.getActualFilePath(expectedFiles.app)}`,
);
if (installerExists) {
console.log(
` 💿 Installer location: ${getActualFilePath(expectedFiles.installer)}`,
` 💿 Installer location: ${this.getActualFilePath(expectedFiles.installer)}`,
);
}
console.log(" ✨ Build artifacts preserved for inspection");
@@ -747,9 +723,11 @@ class PakeTestRunner {
child.on("close", (code) => {
clearTimeout(timeout);
const appExists = checkFileExists(expectedFiles.app);
const installerExists = checkFileExists(expectedFiles.installer);
const actualAppPath = getActualFilePath(expectedFiles.app);
const appExists = this.checkFileExists(expectedFiles.app);
const installerExists = this.checkFileExists(
expectedFiles.installer,
);
const actualAppPath = this.getActualFilePath(expectedFiles.app);
const actualInstallerPath = getActualFilePath(
expectedFiles.installer,
);
@@ -797,6 +775,18 @@ class PakeTestRunner {
const appFile = path.join(config.PROJECT_ROOT, `${testName}.app`);
const dmgFile = path.join(config.PROJECT_ROOT, `${testName}.dmg`);
// Also check universal binary locations
const universalAppFile = path.join(
config.PROJECT_ROOT,
`src-tauri/target/universal-apple-darwin/release/bundle/macos/${testName}.app`,
);
// Check for DMG file in universal target (fallback if app target not working)
const universalDmgFile = path.join(
config.PROJECT_ROOT,
`src-tauri/target/universal-apple-darwin/release/bundle/dmg/${testName}_*.dmg`,
);
console.log(`🔧 Starting multi-arch build test for GitHub.com...`);
console.log(`📝 Expected output: ${appFile}`);
console.log(`🏗️ Building Universal Binary (Intel + Apple Silicon)`);
@@ -810,6 +800,8 @@ class PakeTestRunner {
env: {
...process.env,
PAKE_CREATE_APP: "1",
HDIUTIL_QUIET: "1",
HDIUTIL_NO_AUTOOPEN: "1",
},
});
@@ -855,22 +847,39 @@ class PakeTestRunner {
// Multi-arch builds take longer - 12 minutes timeout
const timeout = setTimeout(() => {
const appExists = fs.existsSync(appFile);
const universalAppExists = fs.existsSync(universalAppFile);
const dmgExists = fs.existsSync(dmgFile);
const universalDmgExists = this.checkFileExists(universalDmgFile);
if (appExists) {
if (
appExists ||
universalAppExists ||
dmgExists ||
universalDmgExists
) {
console.log(" 🎉 Multi-arch build completed successfully!");
console.log(` 📱 App location: ${appFile}`);
if (appExists || universalAppExists) {
const actualAppFile = appExists ? appFile : universalAppFile;
console.log(` 📱 App location: ${actualAppFile}`);
}
if (dmgExists) {
console.log(` 💿 DMG location: ${dmgFile}`);
}
if (universalDmgExists) {
const actualDmgFile = this.getActualFilePath(universalDmgFile);
console.log(` 💿 Universal DMG location: ${actualDmgFile}`);
}
console.log(" 🔀 Universal binary preserved for inspection");
child.kill("SIGTERM");
resolve(true);
} else {
console.log(
" ❌ Multi-arch build timeout - no app file generated",
" ❌ Multi-arch build timeout - no output files generated",
);
console.log(` 📍 Expected location: ${appFile}`);
console.log(` 📍 Expected: ${appFile}`);
console.log(` 📍 Or: ${universalAppFile}`);
console.log(` 📍 Or: ${dmgFile}`);
console.log(` 📍 Or: ${universalDmgFile}`);
child.kill("SIGTERM");
reject(new Error("Multi-arch build test timeout"));
}
@@ -880,22 +889,36 @@ class PakeTestRunner {
clearTimeout(timeout);
const appExists = fs.existsSync(appFile);
const universalAppExists = fs.existsSync(universalAppFile);
const dmgExists = fs.existsSync(dmgFile);
const universalDmgExists = this.checkFileExists(universalDmgFile);
if (appExists) {
if (
appExists ||
universalAppExists ||
dmgExists ||
universalDmgExists
) {
console.log(
" 🎉 Multi-arch build test SUCCESS: Universal binary generated!",
);
console.log(` 📱 App location: ${appFile}`);
if (appExists || universalAppExists) {
const actualAppFile = appExists ? appFile : universalAppFile;
console.log(` 📱 App location: ${actualAppFile}`);
}
if (dmgExists) {
console.log(` 💿 DMG location: ${dmgFile}`);
}
if (universalDmgExists) {
const actualDmgFile = this.getActualFilePath(universalDmgFile);
console.log(` 💿 Universal DMG location: ${actualDmgFile}`);
}
console.log(" 🔀 Universal binary preserved for inspection");
// Verify it's actually a universal binary
try {
const fileOutput = execSync(
`file "${appFile}/Contents/MacOS/pake"`,
`file "${actualAppFile}/Contents/MacOS/pake"`,
{ encoding: "utf8" },
);
if (fileOutput.includes("universal binary")) {
@@ -913,13 +936,18 @@ class PakeTestRunner {
}
resolve(true);
} else if (code === 0 && buildStarted && compilationStarted) {
} else if (buildStarted && compilationStarted) {
// If build started and compilation happened, but no output files found
console.log(
" ⚠️ Multi-arch build process completed but no app file found",
" ⚠️ Multi-arch build process completed but no output files found",
);
console.log(` 📍 Expected location: ${appFile}`);
console.log(` 📍 Expected: ${appFile}`);
console.log(` 📍 Or: ${universalAppFile}`);
console.log(` 📍 Or: ${dmgFile}`);
console.log(` 📍 Or: ${universalDmgFile}`);
resolve(false);
} else {
// Only reject if the build never started or failed early
reject(
new Error(`Multi-arch build test failed with code ${code}`),
);
@@ -985,6 +1013,38 @@ class PakeTestRunner {
);
}
// Helper function to check if files exist (handles wildcards)
checkFileExists(filePath) {
if (filePath.includes("*")) {
const dir = path.dirname(filePath);
const pattern = path.basename(filePath);
try {
const files = fs.readdirSync(dir);
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
return files.some((file) => regex.test(file));
} catch {
return false;
}
}
return fs.existsSync(filePath);
}
getActualFilePath(filePath) {
if (filePath.includes("*")) {
const dir = path.dirname(filePath);
const pattern = path.basename(filePath);
try {
const files = fs.readdirSync(dir);
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
const match = files.find((file) => regex.test(file));
return match ? path.join(dir, match) : filePath;
} catch {
return filePath;
}
}
return filePath;
}
trackTempFile(filepath) {
this.tempFiles.push(filepath);
}
@@ -1076,15 +1136,15 @@ Options:
--builder Run builder tests (default)
--pake-cli Run pake-cli GitHub Actions tests
--e2e, --full Run end-to-end tests
--real-build Run complete real build test (8+ minutes)
--real-build Run complete real build test (8+ minutes on non-macOS, 12+ minutes multi-arch on macOS)
--quick Run only essential tests (fast)
--help, -h Show this help message
Examples:
npm test # Run all default tests
node tests/index.js # Run all default tests
npm test # Run all default tests (multi-arch on macOS)
node tests/index.js # Run all default tests (multi-arch on macOS)
node tests/index.js --quick # Quick test (30 seconds)
node tests/index.js --real-build # Complete build test (8+ minutes)
node tests/index.js --real-build # Complete build test (multi-arch on macOS, single-arch elsewhere)
node tests/index.js --pake-cli # GitHub Actions tests
node tests/index.js --e2e # Full end-to-end tests
node tests/index.js --unit --integration # Specific tests only