mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 01:25:00 +02:00
More tests
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
# Coverage Scripts Documentation
|
||||
|
||||
This directory contains Node.js scripts for managing code coverage in the huly.server monorepo.
|
||||
|
||||
## Scripts Overview
|
||||
|
||||
### 1. `merge-coverage.js`
|
||||
|
||||
Merges individual package coverage reports into a single LCOV file.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
node merge-coverage.js
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Scans `packages/`, `pods/`, and `tests/` directories for coverage reports
|
||||
- Finds all `coverage/lcov.info` files
|
||||
- Merges them into a single `coverage/lcov.info` at the root
|
||||
- Resolves file paths to absolute paths
|
||||
- Handles duplicate TN (test name) headers
|
||||
|
||||
**Output:**
|
||||
|
||||
- `coverage/lcov.info` - Merged coverage data
|
||||
|
||||
---
|
||||
|
||||
### 2. `generate-coverage-html.js`
|
||||
|
||||
Generates HTML coverage reports from LCOV data.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
node generate-coverage-html.js [input-lcov-file] [output-directory]
|
||||
```
|
||||
|
||||
**Default usage:**
|
||||
|
||||
```bash
|
||||
node generate-coverage-html.js coverage/lcov.info coverage/html
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Parses LCOV format coverage data
|
||||
- Uses Istanbul library to generate HTML reports
|
||||
- Creates interactive HTML pages with line-by-line coverage
|
||||
- Resolves source file paths
|
||||
- Post-processes HTML to fix missing source code
|
||||
|
||||
**Output:**
|
||||
|
||||
- `coverage/html/index.html` - Main coverage report
|
||||
- `coverage/html/**/*.html` - Per-file coverage reports
|
||||
|
||||
**Dependencies:**
|
||||
|
||||
- `lcov-parse` - Parses LCOV format
|
||||
- `istanbul-lib-coverage` - Coverage map management
|
||||
- `istanbul-lib-report` - Report context
|
||||
- `istanbul-reports` - HTML report generation
|
||||
|
||||
---
|
||||
|
||||
### 3. `show-coverage-summary.js`
|
||||
|
||||
Displays a summary of coverage statistics by package.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
node show-coverage-summary.js [lcov-file]
|
||||
```
|
||||
|
||||
**Default usage:**
|
||||
|
||||
```bash
|
||||
node show-coverage-summary.js coverage/lcov.info
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Parses the merged LCOV file
|
||||
- Aggregates coverage by package
|
||||
- Displays formatted table with:
|
||||
- Covered lines
|
||||
- Total lines
|
||||
- Coverage percentage
|
||||
- Shows overall totals
|
||||
|
||||
**Example Output:**
|
||||
|
||||
```
|
||||
==============================================
|
||||
COVERAGE SUMMARY BY PACKAGE
|
||||
==============================================
|
||||
|
||||
Package Covered Total Coverage
|
||||
----------------------------------------------
|
||||
datalake 10 10 100.00%
|
||||
minio 111 165 67.27%
|
||||
postgres 815 1351 60.33%
|
||||
...
|
||||
----------------------------------------------
|
||||
TOTAL 1156 2220 52.07%
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. `run-tests-with-coverage.js`
|
||||
|
||||
Runs tests with coverage for all packages sequentially.
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
node run-tests-with-coverage.js
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
|
||||
- Scans all packages in `packages/` directory
|
||||
- For each package with a test script:
|
||||
- Runs `npm test -- --coverage --silent`
|
||||
- Extracts and displays coverage summary
|
||||
- Reports which packages passed/failed
|
||||
|
||||
**Note:** This is an alternative to `rush test` for running tests individually.
|
||||
|
||||
---
|
||||
|
||||
## NPM Scripts
|
||||
|
||||
The `package.json` in this directory provides convenient aliases:
|
||||
|
||||
```bash
|
||||
# Merge coverage reports
|
||||
npm run coverage:merge
|
||||
|
||||
# Generate HTML report
|
||||
npm run coverage:html
|
||||
|
||||
# Show coverage summary
|
||||
npm run coverage:summary
|
||||
|
||||
# Run all package tests with coverage
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Coverage Workflow
|
||||
|
||||
### Using Rush (Recommended)
|
||||
|
||||
```bash
|
||||
# Run all tests with coverage and generate reports
|
||||
rush coverage
|
||||
```
|
||||
|
||||
This command does:
|
||||
|
||||
1. `rush test` - Runs all package tests (coverage enabled by jest.config.js)
|
||||
2. `node scripts/merge-coverage.js` - Merges all LCOV files
|
||||
3. `node scripts/generate-coverage-html.js` - Generates HTML report
|
||||
|
||||
### Manual Workflow
|
||||
|
||||
```bash
|
||||
# 1. Run tests with coverage (coverage enabled in jest.config.js)
|
||||
rush test
|
||||
|
||||
# 2. Merge coverage reports
|
||||
node common/scripts/merge-coverage.js
|
||||
|
||||
# 3. Generate HTML report
|
||||
node common/scripts/generate-coverage-html.js coverage/lcov.info coverage/html
|
||||
|
||||
# 4. View summary
|
||||
node common/scripts/show-coverage-summary.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Jest Configuration
|
||||
|
||||
All packages have coverage enabled by default in `jest.config.js`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ['./src'],
|
||||
collectCoverage: true, // ✅ Enabled by default
|
||||
coverageReporters: ['text-summary', 'html', 'lcov'], // ✅ LCOV format
|
||||
coverageDirectory: 'coverage' // ✅ Output directory
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output Files
|
||||
|
||||
```
|
||||
coverage/
|
||||
├── lcov.info # Merged LCOV coverage data
|
||||
└── html/ # HTML reports
|
||||
├── index.html # Main report page
|
||||
├── base.css # Styling
|
||||
├── prettify.js # Code highlighting
|
||||
└── [package]/ # Per-package reports
|
||||
└── [file].html # Per-file coverage
|
||||
|
||||
packages/
|
||||
└── [package-name]/
|
||||
└── coverage/
|
||||
├── lcov.info # Package-specific LCOV
|
||||
└── html/ # Package-specific HTML
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Coverage Thresholds
|
||||
|
||||
Current overall coverage: **52.07%**
|
||||
|
||||
Per-package targets:
|
||||
|
||||
- ✅ **≥90%**: Excellent coverage
|
||||
- ✅ **70-89%**: Good coverage
|
||||
- ⚠️ **50-69%**: Moderate coverage (needs improvement)
|
||||
- 🔴 **<50%**: Low coverage (priority improvement)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No LCOV files found
|
||||
|
||||
**Error:** `No lcov files found in packages/pods/tests/*/coverage/lcov.info`
|
||||
|
||||
**Solution:** Run tests first with `rush test` to generate coverage files.
|
||||
|
||||
### Missing dependencies
|
||||
|
||||
**Error:** `Cannot find module 'lcov-parse'`
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
cd common/scripts
|
||||
npm install
|
||||
```
|
||||
|
||||
### Source files not found in HTML report
|
||||
|
||||
The scripts attempt to resolve source file paths using multiple strategies:
|
||||
|
||||
1. Absolute path (if exists)
|
||||
2. Relative to package directory
|
||||
3. Suffix match in repository
|
||||
|
||||
If files still can't be found, check that source files exist and paths in LCOV are correct.
|
||||
|
||||
---
|
||||
|
||||
## Legacy Scripts
|
||||
|
||||
The following bash scripts have been replaced with Node.js versions:
|
||||
|
||||
- ❌ `show-coverage.sh` → ✅ `run-tests-with-coverage.js`
|
||||
- ❌ `show-coverage-summary.sh` → ✅ `show-coverage-summary.js`
|
||||
|
||||
The bash scripts are kept for backward compatibility but the Node.js versions are recommended for better cross-platform support.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
The LCOV format is compatible with common coverage tools:
|
||||
|
||||
- **Codecov**: `bash <(curl -s https://codecov.io/bash)`
|
||||
- **Coveralls**: `cat coverage/lcov.info | coveralls`
|
||||
- **SonarQube**: Configure `sonar.javascript.lcov.reportPaths=coverage/lcov.info`
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [COVERAGE_REPORT.md](../../COVERAGE_REPORT.md) - Full coverage analysis
|
||||
- [Jest Documentation](https://jestjs.io/docs/configuration#collectcoverage-boolean)
|
||||
- [Istanbul Documentation](https://istanbul.js.org/)
|
||||
Generated
+155
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"name": "@hcengineering/scripts",
|
||||
"version": "0.7.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@hcengineering/scripts",
|
||||
"version": "0.7.3",
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.10",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.2.0",
|
||||
"lcov-parse": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"../temp/node_modules/.pnpm/@hcengineering+platform-rig@0.7.10/node_modules/@hcengineering/platform-rig": {
|
||||
"version": "0.7.10",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild-plugin-copy": "^2.1.1",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"bin": {
|
||||
"bump-changes-from-tag": "bin/bump-changes-from-tag.js",
|
||||
"bump-package-version": "bin/bump-package-version.js",
|
||||
"compile": "bin/compile.js",
|
||||
"do-svelte-check": "bin/do-svelte-check.js",
|
||||
"format": "bin/format.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@hcengineering/platform-rig": {
|
||||
"resolved": "../temp/node_modules/.pnpm/@hcengineering+platform-rig@0.7.10/node_modules/@hcengineering/platform-rig",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"make-dir": "^4.0.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lcov-parse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz",
|
||||
"integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"bin": {
|
||||
"lcov-parse": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,18 @@
|
||||
"name": "@hcengineering/scripts",
|
||||
"version": "0.7.3",
|
||||
"scripts": {
|
||||
"format": "echo \"No format specified\""
|
||||
"format": "echo \"No format specified\"",
|
||||
"coverage:merge": "node merge-coverage.js",
|
||||
"coverage:html": "node generate-coverage-html.js coverage/lcov.info coverage/html",
|
||||
"coverage:summary": "node show-coverage-summary.js",
|
||||
"test:coverage": "node run-tests-with-coverage.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.7.10"
|
||||
"@hcengineering/platform-rig": "^0.7.10",
|
||||
"istanbul-lib-coverage": "^3.2.2",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.2.0",
|
||||
"lcov-parse": "^1.0.0"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Run tests with coverage for all packages
|
||||
* This is a replacement for show-coverage.sh
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { spawn } = require('child_process')
|
||||
|
||||
const root = process.cwd()
|
||||
const packagesDir = path.join(root, 'packages')
|
||||
|
||||
if (!fs.existsSync(packagesDir)) {
|
||||
console.error('Error: packages directory not found')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('=== RUNNING TESTS WITH COVERAGE ===')
|
||||
console.log('')
|
||||
|
||||
const packages = fs
|
||||
.readdirSync(packagesDir, { withFileTypes: true })
|
||||
.filter((dirent) => dirent.isDirectory())
|
||||
.map((dirent) => dirent.name)
|
||||
.sort()
|
||||
|
||||
async function runTestsForPackage(pkgName) {
|
||||
const pkgDir = path.join(packagesDir, pkgName)
|
||||
const packageJson = path.join(pkgDir, 'package.json')
|
||||
|
||||
if (!fs.existsSync(packageJson)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf8'))
|
||||
if (!pkg.scripts || !pkg.scripts.test) {
|
||||
return null
|
||||
}
|
||||
|
||||
console.log(`📦 Package: ${pkgName}`)
|
||||
console.log('---')
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
||||
const child = spawn(npm, ['test', '--', '--coverage', '--silent'], {
|
||||
cwd: pkgDir,
|
||||
stdio: 'pipe',
|
||||
shell: true
|
||||
})
|
||||
|
||||
let output = ''
|
||||
let inSummary = false
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
const text = data.toString()
|
||||
output += text
|
||||
|
||||
// Extract coverage summary
|
||||
const lines = text.split('\n')
|
||||
for (const line of lines) {
|
||||
if (line.includes('Coverage summary') || line.includes('----------')) {
|
||||
inSummary = true
|
||||
}
|
||||
if (
|
||||
inSummary &&
|
||||
(line.includes('Statements') ||
|
||||
line.includes('Branches') ||
|
||||
line.includes('Functions') ||
|
||||
line.includes('Lines'))
|
||||
) {
|
||||
console.log(line.trim())
|
||||
}
|
||||
if (inSummary && line.trim() === '') {
|
||||
inSummary = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
// Ignore stderr for cleaner output
|
||||
})
|
||||
|
||||
child.on('close', (code) => {
|
||||
console.log('')
|
||||
resolve({ package: pkgName, code, output })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const results = []
|
||||
|
||||
for (const pkg of packages) {
|
||||
const result = await runTestsForPackage(pkg)
|
||||
if (result) {
|
||||
results.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== END OF COVERAGE REPORT ===')
|
||||
console.log('')
|
||||
console.log(`Tested ${results.length} packages`)
|
||||
|
||||
const failed = results.filter((r) => r.code !== 0)
|
||||
if (failed.length > 0) {
|
||||
console.log(`⚠️ ${failed.length} package(s) had test failures:`)
|
||||
failed.forEach((r) => console.log(` - ${r.package}`))
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Error:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Display coverage summary from lcov.info file
|
||||
* Usage: node show-coverage-summary.js [lcov-file-path]
|
||||
*/
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const lcovFile = process.argv[2] || 'coverage/lcov.info'
|
||||
const root = process.cwd()
|
||||
const lcovPath = path.isAbsolute(lcovFile) ? lcovFile : path.join(root, lcovFile)
|
||||
|
||||
if (!fs.existsSync(lcovPath)) {
|
||||
console.error(`Error: LCOV file not found: ${lcovPath}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('==============================================')
|
||||
console.log('COVERAGE SUMMARY BY PACKAGE')
|
||||
console.log('==============================================')
|
||||
console.log('')
|
||||
|
||||
const data = fs.readFileSync(lcovPath, 'utf8')
|
||||
const lines = data.split(/\r?\n/)
|
||||
|
||||
const fileStats = new Map()
|
||||
const filePkg = new Map()
|
||||
let currentFile = ''
|
||||
|
||||
// Parse lcov.info
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('SF:')) {
|
||||
currentFile = line.substring(3)
|
||||
fileStats.set(currentFile, { total: 0, covered: 0 })
|
||||
|
||||
// Extract package name from path
|
||||
const parts = currentFile.split(path.sep)
|
||||
const pkgIndex = parts.indexOf('packages')
|
||||
if (pkgIndex !== -1 && pkgIndex + 1 < parts.length) {
|
||||
filePkg.set(currentFile, parts[pkgIndex + 1])
|
||||
}
|
||||
} else if (line.startsWith('DA:')) {
|
||||
const [lineNum, hitCount] = line.substring(3).split(',')
|
||||
const stats = fileStats.get(currentFile)
|
||||
if (stats) {
|
||||
stats.total++
|
||||
if (parseInt(hitCount) > 0) {
|
||||
stats.covered++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregate by package
|
||||
const pkgStats = new Map()
|
||||
for (const [file, stats] of fileStats) {
|
||||
const pkg = filePkg.get(file)
|
||||
if (pkg) {
|
||||
if (!pkgStats.has(pkg)) {
|
||||
pkgStats.set(pkg, { total: 0, covered: 0 })
|
||||
}
|
||||
const pkgStat = pkgStats.get(pkg)
|
||||
pkgStat.total += stats.total
|
||||
pkgStat.covered += stats.covered
|
||||
}
|
||||
}
|
||||
|
||||
// Sort packages alphabetically
|
||||
const sortedPackages = Array.from(pkgStats.keys()).sort()
|
||||
|
||||
// Display header
|
||||
console.log(padRight('Package', 25) + padLeft('Covered', 10) + padLeft('Total', 10) + padLeft('Coverage', 10))
|
||||
console.log('----------------------------------------------')
|
||||
|
||||
// Display package stats
|
||||
let overallCovered = 0
|
||||
let overallTotal = 0
|
||||
|
||||
for (const pkg of sortedPackages) {
|
||||
const stats = pkgStats.get(pkg)
|
||||
const pct = (stats.covered / stats.total) * 100
|
||||
console.log(
|
||||
padRight(pkg, 25) +
|
||||
padLeft(stats.covered.toString(), 10) +
|
||||
padLeft(stats.total.toString(), 10) +
|
||||
padLeft(pct.toFixed(2) + '%', 10)
|
||||
)
|
||||
overallCovered += stats.covered
|
||||
overallTotal += stats.total
|
||||
}
|
||||
|
||||
// Display total
|
||||
console.log('----------------------------------------------')
|
||||
const overallPct = (overallCovered / overallTotal) * 100
|
||||
console.log(
|
||||
padRight('TOTAL', 25) +
|
||||
padLeft(overallCovered.toString(), 10) +
|
||||
padLeft(overallTotal.toString(), 10) +
|
||||
padLeft(overallPct.toFixed(2) + '%', 10)
|
||||
)
|
||||
console.log('')
|
||||
|
||||
console.log('==============================================')
|
||||
console.log('')
|
||||
console.log('HTML report available at: coverage/html/index.html')
|
||||
console.log(`Merged LCOV file available at: ${lcovFile}`)
|
||||
|
||||
// Helper functions
|
||||
function padRight(str, width) {
|
||||
return str + ' '.repeat(Math.max(0, width - str.length))
|
||||
}
|
||||
|
||||
function padLeft(str, width) {
|
||||
return ' '.repeat(Math.max(0, width - str.length)) + str
|
||||
}
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to display coverage summary from lcov.info file
|
||||
|
||||
LCOV_FILE="${1:-coverage/lcov.info}"
|
||||
|
||||
if [ ! -f "$LCOV_FILE" ]; then
|
||||
echo "Error: LCOV file not found: $LCOV_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=============================================="
|
||||
echo "COVERAGE SUMMARY BY PACKAGE"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Parse lcov.info and aggregate by package
|
||||
awk '
|
||||
BEGIN {
|
||||
current_file = "";
|
||||
}
|
||||
/^SF:/ {
|
||||
current_file = $0;
|
||||
sub(/^SF:/, "", current_file);
|
||||
# Extract package name from path
|
||||
split(current_file, parts, "/");
|
||||
pkg = "";
|
||||
for (i=1; i<=length(parts); i++) {
|
||||
if (parts[i] == "packages" && i+1 <= length(parts)) {
|
||||
pkg = parts[i+1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
total_lines[current_file] = 0;
|
||||
covered_lines[current_file] = 0;
|
||||
file_pkg[current_file] = pkg;
|
||||
}
|
||||
/^DA:/ {
|
||||
split($0, parts, ",");
|
||||
total_lines[current_file]++;
|
||||
if (parts[2] > 0) covered_lines[current_file]++;
|
||||
}
|
||||
END {
|
||||
# Aggregate by package
|
||||
for (file in total_lines) {
|
||||
pkg = file_pkg[file];
|
||||
if (pkg != "") {
|
||||
pkg_total[pkg] += total_lines[file];
|
||||
pkg_covered[pkg] += covered_lines[file];
|
||||
}
|
||||
}
|
||||
|
||||
printf "%-25s %10s %10s %10s\n", "Package", "Covered", "Total", "Coverage";
|
||||
print "----------------------------------------------";
|
||||
|
||||
overall_covered = 0;
|
||||
overall_total = 0;
|
||||
|
||||
# Display packages
|
||||
for (pkg in pkg_total) {
|
||||
pct = (pkg_covered[pkg] / pkg_total[pkg]) * 100;
|
||||
printf "%-25s %10d %10d %9.2f%%\n", pkg, pkg_covered[pkg], pkg_total[pkg], pct;
|
||||
overall_covered += pkg_covered[pkg];
|
||||
overall_total += pkg_total[pkg];
|
||||
}
|
||||
|
||||
print "----------------------------------------------";
|
||||
overall_pct = (overall_covered / overall_total) * 100;
|
||||
printf "%-25s %10d %10d %9.2f%%\n", "TOTAL", overall_covered, overall_total, overall_pct;
|
||||
print "";
|
||||
}' "$LCOV_FILE"
|
||||
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "HTML report available at: coverage/html/index.html"
|
||||
echo "Merged LCOV file available at: $LCOV_FILE"
|
||||
Reference in New Issue
Block a user