added tests
This commit is contained in:
@@ -0,0 +1,460 @@
|
|||||||
|
name: Test CI Templates
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *' # Run daily at 2 AM
|
||||||
|
workflow_dispatch: # Manual trigger
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-syntax:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install yamllint
|
||||||
|
run: pip install yamllint
|
||||||
|
|
||||||
|
- name: Validate YAML syntax
|
||||||
|
run: |
|
||||||
|
echo "Validating workflow YAML syntax..."
|
||||||
|
find .gitea/workflows -name "*.yml" -exec yamllint {} \;
|
||||||
|
|
||||||
|
- name: Check workflow structure
|
||||||
|
run: |
|
||||||
|
echo "Checking workflow structure..."
|
||||||
|
for file in .gitea/workflows/**/*.yml; do
|
||||||
|
echo "Checking $file..."
|
||||||
|
if ! grep -q "workflow_call" "$file"; then
|
||||||
|
echo "Error: $file is missing workflow_call trigger"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -q "inputs:" "$file"; then
|
||||||
|
echo "Warning: $file has no inputs defined"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
test-python-code-quality:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate-syntax
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
include:
|
||||||
|
- test-type: "basic"
|
||||||
|
skip-mypy: false
|
||||||
|
skip-pyupgrade: false
|
||||||
|
- test-type: "skip-mypy"
|
||||||
|
skip-mypy: true
|
||||||
|
skip-pyupgrade: false
|
||||||
|
- test-type: "skip-pyupgrade"
|
||||||
|
skip-mypy: false
|
||||||
|
skip-pyupgrade: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create test Python project
|
||||||
|
run: |
|
||||||
|
mkdir -p test-project/src/test_package
|
||||||
|
cd test-project
|
||||||
|
|
||||||
|
# Create pyproject.toml
|
||||||
|
cat > pyproject.toml << 'EOF'
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "test-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Test package for CI templates"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"pyupgrade>=3.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create source code
|
||||||
|
cat > src/test_package/__init__.py << 'EOF'
|
||||||
|
"""Test package for CI template validation."""
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
def hello_world() -> str:
|
||||||
|
"""Return a greeting message."""
|
||||||
|
return "Hello, World!"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > src/test_package/main.py << 'EOF'
|
||||||
|
"""Main module for test package."""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def process_items(items: List[str]) -> List[str]:
|
||||||
|
"""Process a list of items."""
|
||||||
|
return [item.upper() for item in items]
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_items = ["hello", "world"]
|
||||||
|
result = process_items(test_items)
|
||||||
|
print(result)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Test Python code quality workflow
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
working-directory: "./test-project"
|
||||||
|
skip-mypy: ${{ matrix.skip-mypy }}
|
||||||
|
skip-pyupgrade: ${{ matrix.skip-pyupgrade }}
|
||||||
|
uv-extras: "dev"
|
||||||
|
|
||||||
|
test-python-pytest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate-syntax
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11", "3.12"]
|
||||||
|
coverage: [true, false]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create test Python project with tests
|
||||||
|
run: |
|
||||||
|
mkdir -p test-pytest-project/src/test_package tests
|
||||||
|
cd test-pytest-project
|
||||||
|
|
||||||
|
# Create pyproject.toml
|
||||||
|
cat > pyproject.toml << 'EOF'
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "test-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Test package for pytest CI template"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
test = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py"]
|
||||||
|
addopts = ["--strict-markers", "--strict-config"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create source code
|
||||||
|
cat > src/test_package/__init__.py << 'EOF'
|
||||||
|
"""Test package for pytest template validation."""
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > src/test_package/calculator.py << 'EOF'
|
||||||
|
"""Simple calculator for testing."""
|
||||||
|
|
||||||
|
def add(a: float, b: float) -> float:
|
||||||
|
"""Add two numbers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def multiply(a: float, b: float) -> float:
|
||||||
|
"""Multiply two numbers."""
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
def divide(a: float, b: float) -> float:
|
||||||
|
"""Divide two numbers."""
|
||||||
|
if b == 0:
|
||||||
|
raise ValueError("Cannot divide by zero")
|
||||||
|
return a / b
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create tests
|
||||||
|
cat > tests/test_calculator.py << 'EOF'
|
||||||
|
"""Tests for calculator module."""
|
||||||
|
import pytest
|
||||||
|
from test_package.calculator import add, multiply, divide
|
||||||
|
|
||||||
|
def test_add():
|
||||||
|
assert add(2, 3) == 5
|
||||||
|
assert add(-1, 1) == 0
|
||||||
|
assert add(0.1, 0.2) == pytest.approx(0.3)
|
||||||
|
|
||||||
|
def test_multiply():
|
||||||
|
assert multiply(2, 3) == 6
|
||||||
|
assert multiply(-2, 3) == -6
|
||||||
|
assert multiply(0, 5) == 0
|
||||||
|
|
||||||
|
def test_divide():
|
||||||
|
assert divide(6, 2) == 3
|
||||||
|
assert divide(5, 2) == 2.5
|
||||||
|
|
||||||
|
def test_divide_by_zero():
|
||||||
|
with pytest.raises(ValueError, match="Cannot divide by zero"):
|
||||||
|
divide(5, 0)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Test pytest workflow
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
working-directory: "./test-pytest-project"
|
||||||
|
coverage-package: ${{ matrix.coverage && 'test_package' || '' }}
|
||||||
|
pytest-args: "--verbose"
|
||||||
|
uv-extras: "test"
|
||||||
|
|
||||||
|
test-web-formatting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate-syntax
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: ["18", "20"]
|
||||||
|
check-yaml: [true, false]
|
||||||
|
check-json: [true, false]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create test web project
|
||||||
|
run: |
|
||||||
|
mkdir -p test-web-project/src/components
|
||||||
|
cd test-web-project
|
||||||
|
|
||||||
|
# Create package.json
|
||||||
|
cat > package.json << 'EOF'
|
||||||
|
{
|
||||||
|
"name": "test-web-project",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test project for web formatting template"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create JavaScript files
|
||||||
|
cat > src/index.js << 'EOF'
|
||||||
|
// Test JavaScript file for formatting
|
||||||
|
const greeting = "Hello, World!";
|
||||||
|
|
||||||
|
function greet(name) {
|
||||||
|
return `Hello, ${name}!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { greet };
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > src/components/Button.js << 'EOF'
|
||||||
|
// Button component
|
||||||
|
export default function Button({ children, onClick }) {
|
||||||
|
return (
|
||||||
|
<button onClick={onClick} className="btn">
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create JSON file
|
||||||
|
cat > config.json << 'EOF'
|
||||||
|
{
|
||||||
|
"name": "test-config",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"settings": {
|
||||||
|
"debug": true,
|
||||||
|
"timeout": 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create YAML file
|
||||||
|
cat > config.yml << 'EOF'
|
||||||
|
name: test-config
|
||||||
|
version: 1.0.0
|
||||||
|
settings:
|
||||||
|
debug: true
|
||||||
|
timeout: 5000
|
||||||
|
features:
|
||||||
|
- feature1
|
||||||
|
- feature2
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create Prettier config
|
||||||
|
cat > .prettierrc.json << 'EOF'
|
||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Test web formatting workflow
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
working-directory: "./test-web-project"
|
||||||
|
check-yaml: ${{ matrix.check-yaml }}
|
||||||
|
check-json: ${{ matrix.check-json }}
|
||||||
|
|
||||||
|
test-monorepo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate-syntax
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create monorepo structure
|
||||||
|
run: |
|
||||||
|
mkdir -p monorepo-test/{backend,frontend}
|
||||||
|
|
||||||
|
# Backend Python project
|
||||||
|
cd monorepo-test/backend
|
||||||
|
cat > pyproject.toml << 'EOF'
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "backend-service"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["ruff>=0.1.0", "mypy>=1.0.0"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p src/backend_service
|
||||||
|
cat > src/backend_service/__init__.py << 'EOF'
|
||||||
|
"""Backend service package."""
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Frontend project
|
||||||
|
cd ../frontend
|
||||||
|
cat > package.json << 'EOF'
|
||||||
|
{
|
||||||
|
"name": "frontend-app",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p src
|
||||||
|
cat > src/App.js << 'EOF'
|
||||||
|
export default function App() {
|
||||||
|
return <div>Hello from frontend!</div>;
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Test backend code quality
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
working-directory: "./monorepo-test/backend"
|
||||||
|
uv-extras: "dev"
|
||||||
|
|
||||||
|
- name: Test frontend formatting
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
working-directory: "./monorepo-test/frontend"
|
||||||
|
|
||||||
|
test-edge-cases:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: validate-syntax
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Test minimal Python project
|
||||||
|
run: |
|
||||||
|
mkdir minimal-python
|
||||||
|
cd minimal-python
|
||||||
|
cat > pyproject.toml << 'EOF'
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "minimal"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = []
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p src/minimal
|
||||||
|
cat > src/minimal/__init__.py << 'EOF'
|
||||||
|
pass
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Test with minimal configuration
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
working-directory: "./minimal-python"
|
||||||
|
skip-mypy: true
|
||||||
|
skip-pyupgrade: true
|
||||||
|
|
||||||
|
test-results:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test-python-code-quality, test-python-pytest, test-web-formatting, test-monorepo, test-edge-cases]
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Report test results
|
||||||
|
run: |
|
||||||
|
echo "## CI Template Test Results" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
if [ "${{ needs.test-python-code-quality.result }}" == "success" ]; then
|
||||||
|
echo "✅ Python Code Quality: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "❌ Python Code Quality: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ needs.test-python-pytest.result }}" == "success" ]; then
|
||||||
|
echo "✅ Python Pytest: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "❌ Python Pytest: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ needs.test-web-formatting.result }}" == "success" ]; then
|
||||||
|
echo "✅ Web Formatting: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "❌ Web Formatting: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ needs.test-monorepo.result }}" == "success" ]; then
|
||||||
|
echo "✅ Monorepo Support: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "❌ Monorepo Support: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${{ needs.test-edge-cases.result }}" == "success" ]; then
|
||||||
|
echo "✅ Edge Cases: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "❌ Edge Cases: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
+389
@@ -0,0 +1,389 @@
|
|||||||
|
# CI Template Testing Guide
|
||||||
|
|
||||||
|
This document explains how to test and validate the CI workflow templates in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The testing infrastructure includes:
|
||||||
|
|
||||||
|
- **Automated validation workflow** (`.gitea/workflows/validate-templates.yml`)
|
||||||
|
- **Manual validation script** (`scripts/validate-templates.sh`)
|
||||||
|
- **Test projects** (`test-projects/`)
|
||||||
|
- **Matrix testing** across multiple configurations
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Run Local Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make the script executable
|
||||||
|
chmod +x scripts/validate-templates.sh
|
||||||
|
|
||||||
|
# Validate all workflows
|
||||||
|
./scripts/validate-templates.sh
|
||||||
|
|
||||||
|
# List available templates
|
||||||
|
./scripts/validate-templates.sh list
|
||||||
|
|
||||||
|
# Show help
|
||||||
|
./scripts/validate-templates.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Test with Sample Projects
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to a test project
|
||||||
|
cd test-projects/python-basic
|
||||||
|
|
||||||
|
# Install dependencies (if using UV)
|
||||||
|
uv sync --all-extras
|
||||||
|
|
||||||
|
# Run local tests
|
||||||
|
uv run pytest
|
||||||
|
uv run mypy .
|
||||||
|
uv run ruff check .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Infrastructure
|
||||||
|
|
||||||
|
### 1. Validation Workflow (`.gitea/workflows/validate-templates.yml`)
|
||||||
|
|
||||||
|
Automatically runs on every push and pull request:
|
||||||
|
|
||||||
|
- **Matrix testing** across Python versions (3.10, 3.11, 3.12)
|
||||||
|
- **Template validation** for all workflow files
|
||||||
|
- **Integration testing** with sample projects
|
||||||
|
- **Documentation checks**
|
||||||
|
|
||||||
|
**Triggered by:**
|
||||||
|
|
||||||
|
- Push to `main` branch
|
||||||
|
- Pull requests
|
||||||
|
- Manual dispatch
|
||||||
|
- Daily schedule (2 AM UTC)
|
||||||
|
|
||||||
|
**Test matrix:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
template: ["code-quality", "pytest"]
|
||||||
|
include:
|
||||||
|
- template: "formatting"
|
||||||
|
python-version: "3.12" # Only test once for web template
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Validation Script (`scripts/validate-templates.sh`)
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
- YAML syntax validation with `yamllint`
|
||||||
|
- Workflow structure validation with `yq`
|
||||||
|
- Reusable workflow detection
|
||||||
|
- Input parameter validation
|
||||||
|
- Documentation checks
|
||||||
|
- Common issue detection
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic validation
|
||||||
|
./scripts/validate-templates.sh
|
||||||
|
|
||||||
|
# List all templates
|
||||||
|
./scripts/validate-templates.sh list
|
||||||
|
|
||||||
|
# Help
|
||||||
|
./scripts/validate-templates.sh help
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
|
||||||
|
- `yamllint` (optional): `pip install yamllint`
|
||||||
|
- `yq` (optional): `brew install yq` (macOS) or `apt install yq` (Ubuntu)
|
||||||
|
|
||||||
|
**Note:** The script works without dependencies but provides more detailed validation when they're available.
|
||||||
|
|
||||||
|
### 3. Test Projects
|
||||||
|
|
||||||
|
#### Python Basic (`test-projects/python-basic/`)
|
||||||
|
|
||||||
|
**Purpose:** Test Python-specific templates
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
python-basic/
|
||||||
|
├── .gitea/workflows/
|
||||||
|
│ └── test-templates.yml # Tests both code-quality and pytest templates
|
||||||
|
├── src/test_package/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── calculator.py # Sample module with type hints
|
||||||
|
│ └── utils.py # Utility functions
|
||||||
|
├── tests/
|
||||||
|
│ ├── test_calculator.py # Pytest tests
|
||||||
|
│ └── test_utils.py # More tests
|
||||||
|
├── pyproject.toml # UV-compatible project config
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
- Type hints for mypy testing
|
||||||
|
- Pytest with coverage
|
||||||
|
- Ruff-compatible code style
|
||||||
|
- UV package management
|
||||||
|
|
||||||
|
#### Web Basic (`test-projects/web-basic/`)
|
||||||
|
|
||||||
|
**Purpose:** Test web formatting template
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-basic/
|
||||||
|
├── .gitea/workflows/
|
||||||
|
│ └── test-formatting.yml # Tests formatting template
|
||||||
|
├── package.json # Node.js project
|
||||||
|
├── index.js # JavaScript file
|
||||||
|
├── config.yml # YAML file for validation
|
||||||
|
├── .prettierrc.json # Prettier configuration
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
- Prettier configuration
|
||||||
|
- YAML validation
|
||||||
|
- JSON validation
|
||||||
|
- Node.js setup
|
||||||
|
|
||||||
|
## Testing Workflow Templates
|
||||||
|
|
||||||
|
### 1. Local Testing with Act
|
||||||
|
|
||||||
|
Install and use Act to test workflows locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Act
|
||||||
|
brew install act # macOS
|
||||||
|
# or
|
||||||
|
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
|
||||||
|
|
||||||
|
# Test a specific workflow
|
||||||
|
act -W .gitea/workflows/python/code-quality.yml
|
||||||
|
|
||||||
|
# Test with inputs
|
||||||
|
act workflow_call -W .gitea/workflows/python/code-quality.yml \
|
||||||
|
--input python-version=3.11 \
|
||||||
|
--input working-directory=./test-projects/python-basic
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Branch Testing
|
||||||
|
|
||||||
|
Test changes on feature branches:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create feature branch
|
||||||
|
git checkout -b feature/improve-python-template
|
||||||
|
|
||||||
|
# Make changes to templates
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Update test project to use feature branch
|
||||||
|
# In test-projects/python-basic/.gitea/workflows/test-templates.yml:
|
||||||
|
# uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@feature/improve-python-template
|
||||||
|
|
||||||
|
# Push and test
|
||||||
|
git push origin feature/improve-python-template
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Matrix Testing
|
||||||
|
|
||||||
|
Test multiple configurations:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Example matrix test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12"]
|
||||||
|
os: ["ubuntu-latest", "ubuntu-20.04"]
|
||||||
|
working-directory: [".", "./src"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
|
||||||
|
### Workflow File Validation
|
||||||
|
|
||||||
|
- [ ] Valid YAML syntax
|
||||||
|
- [ ] Contains required fields: `name`, `on`, `jobs`
|
||||||
|
- [ ] Uses `workflow_call` trigger for reusable workflows
|
||||||
|
- [ ] All inputs have descriptions and types
|
||||||
|
- [ ] No hardcoded values that should be inputs
|
||||||
|
- [ ] Proper defaults for optional inputs
|
||||||
|
|
||||||
|
### Template Testing
|
||||||
|
|
||||||
|
- [ ] All input combinations work correctly
|
||||||
|
- [ ] Error handling for invalid inputs
|
||||||
|
- [ ] Backwards compatibility maintained
|
||||||
|
- [ ] Documentation updated
|
||||||
|
- [ ] Test projects updated
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
|
||||||
|
- [ ] Works with real projects
|
||||||
|
- [ ] Performance is acceptable
|
||||||
|
- [ ] No conflicts between templates
|
||||||
|
- [ ] Secrets handling works correctly
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### 1. YAML Syntax Errors
|
||||||
|
|
||||||
|
**Problem:** Invalid YAML syntax
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Error: YAML syntax validation failed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
- Use `yamllint` to check syntax
|
||||||
|
- Validate indentation (use spaces, not tabs)
|
||||||
|
- Check for missing quotes around strings with special characters
|
||||||
|
|
||||||
|
### 2. Missing Required Fields
|
||||||
|
|
||||||
|
**Problem:** Workflow missing required fields
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Error: Missing required field 'on' in workflow.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
- Ensure all workflows have `name`, `on`, and `jobs` fields
|
||||||
|
- For reusable workflows, use `workflow_call` trigger
|
||||||
|
|
||||||
|
### 3. Input Validation Issues
|
||||||
|
|
||||||
|
**Problem:** Missing input descriptions or types
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Warning: Input 'python-version' missing description
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
inputs:
|
||||||
|
python-version:
|
||||||
|
description: 'Python version to use (e.g., "3.12")'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Test Project Issues
|
||||||
|
|
||||||
|
**Problem:** Tests fail in sample projects
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Error: Module not found
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
- Check `pyproject.toml` configuration
|
||||||
|
- Verify package structure
|
||||||
|
- Ensure dependencies are correctly specified
|
||||||
|
- Check import paths
|
||||||
|
|
||||||
|
## CI/CD Best Practices
|
||||||
|
|
||||||
|
### 1. Version Control
|
||||||
|
|
||||||
|
- Use semantic versioning for releases
|
||||||
|
- Tag stable versions: `v1.0.0`, `v1.1.0`, etc.
|
||||||
|
- Reference specific versions in production: `@v1.0.0`
|
||||||
|
- Use `@main` for development/testing
|
||||||
|
|
||||||
|
### 2. Backwards Compatibility
|
||||||
|
|
||||||
|
- Test with previous versions
|
||||||
|
- Provide migration guides for breaking changes
|
||||||
|
- Deprecate features gracefully
|
||||||
|
- Maintain changelog
|
||||||
|
|
||||||
|
### 3. Security
|
||||||
|
|
||||||
|
- Validate all inputs
|
||||||
|
- Use secrets for sensitive data
|
||||||
|
- Limit permissions to minimum required
|
||||||
|
- Regular security updates
|
||||||
|
|
||||||
|
### 4. Documentation
|
||||||
|
|
||||||
|
- Keep README files updated
|
||||||
|
- Document all input parameters
|
||||||
|
- Provide usage examples
|
||||||
|
- Include troubleshooting guides
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Validation Script Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check dependencies
|
||||||
|
./scripts/validate-templates.sh
|
||||||
|
|
||||||
|
# Manual YAML validation
|
||||||
|
yamllint .gitea/workflows/python/code-quality.yml
|
||||||
|
|
||||||
|
# Manual structure check
|
||||||
|
yq eval '.on | has("workflow_call")' .gitea/workflows/python/code-quality.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Project Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Python projects
|
||||||
|
cd test-projects/python-basic
|
||||||
|
uv sync --all-extras
|
||||||
|
uv run pytest --verbose
|
||||||
|
|
||||||
|
# Web projects
|
||||||
|
cd test-projects/web-basic
|
||||||
|
npm install
|
||||||
|
npm run format:check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow Issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check workflow logs in Gitea
|
||||||
|
# Enable debug logging:
|
||||||
|
env:
|
||||||
|
ACTIONS_STEP_DEBUG: true
|
||||||
|
ACTIONS_RUNNER_DEBUG: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
When adding new templates or making changes:
|
||||||
|
|
||||||
|
1. **Create/update test projects** to validate the changes
|
||||||
|
2. **Run validation script** locally before committing
|
||||||
|
3. **Update documentation** as needed
|
||||||
|
4. **Test with feature branches** before merging
|
||||||
|
5. **Tag releases** for stable versions
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Gitea Actions Documentation](https://docs.gitea.io/en-us/actions/)
|
||||||
|
- [GitHub Actions Syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions)
|
||||||
|
- [Act - Local Testing Tool](https://github.com/nektos/act)
|
||||||
|
- [yamllint Documentation](https://yamllint.readthedocs.io/)
|
||||||
|
- [yq Documentation](https://mikefarah.gitbook.io/yq/)
|
||||||
Executable
+709
@@ -0,0 +1,709 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# CI Template Validation Script
|
||||||
|
# This script validates the syntax and structure of Gitea Actions workflow templates
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
WORKFLOW_DIR=".gitea/workflows"
|
||||||
|
REQUIRED_FIELDS=("name" "on" "jobs")
|
||||||
|
REUSABLE_TRIGGER="workflow_call"
|
||||||
|
|
||||||
|
# Counters
|
||||||
|
TOTAL_FILES=0
|
||||||
|
VALID_FILES=0
|
||||||
|
WARNINGS=0
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
log_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
((WARNINGS++))
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if required tools are installed
|
||||||
|
check_dependencies() {
|
||||||
|
log_info "Checking dependencies..."
|
||||||
|
|
||||||
|
if ! command -v yamllint &> /dev/null; then
|
||||||
|
log_warning "yamllint not found. Install with: pip install yamllint"
|
||||||
|
log_info "Will skip YAML syntax validation"
|
||||||
|
SKIP_YAMLLINT=true
|
||||||
|
else
|
||||||
|
log_success "yamllint found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v yq &> /dev/null; then
|
||||||
|
log_warning "yq not found. Install with: brew install yq (macOS) or apt install yq (Ubuntu)"
|
||||||
|
log_info "Will use basic grep validation instead"
|
||||||
|
SKIP_YQ=true
|
||||||
|
else
|
||||||
|
log_success "yq found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate YAML syntax
|
||||||
|
validate_yaml_syntax() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
if [[ "$SKIP_YAMLLINT" == "true" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if yamllint "$file" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "YAML syntax validation failed for $file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if file is a reusable workflow
|
||||||
|
is_reusable_workflow() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
if [[ "$SKIP_YQ" == "true" ]]; then
|
||||||
|
# Fallback to grep
|
||||||
|
if grep -q "workflow_call" "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Use yq for precise checking
|
||||||
|
if yq eval '.on | has("workflow_call")' "$file" 2>/dev/null | grep -q "true"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate required workflow fields
|
||||||
|
validate_workflow_structure() {
|
||||||
|
local file="$1"
|
||||||
|
local valid=true
|
||||||
|
|
||||||
|
for field in "${REQUIRED_FIELDS[@]}"; do
|
||||||
|
if [[ "$SKIP_YQ" == "true" ]]; then
|
||||||
|
# Fallback to grep
|
||||||
|
if ! grep -q "^${field}:" "$file"; then
|
||||||
|
log_error "Missing required field '$field' in $file"
|
||||||
|
valid=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Use yq for precise checking
|
||||||
|
if ! yq eval "has(\"$field\")" "$file" 2>/dev/null | grep -q "true"; then
|
||||||
|
log_error "Missing required field '$field' in $file"
|
||||||
|
valid=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$valid" == "true" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate workflow inputs (for reusable workflows)
|
||||||
|
validate_workflow_inputs() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
if ! is_reusable_workflow "$file"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$SKIP_YQ" == "true" ]]; then
|
||||||
|
# Basic check with grep
|
||||||
|
if grep -q "inputs:" "$file"; then
|
||||||
|
log_success "Inputs section found in $file"
|
||||||
|
else
|
||||||
|
log_warning "No inputs section found in reusable workflow $file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Detailed validation with yq
|
||||||
|
local inputs_count
|
||||||
|
inputs_count=$(yq eval '.on.workflow_call.inputs | length' "$file" 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [[ "$inputs_count" -gt 0 ]]; then
|
||||||
|
log_success "Found $inputs_count input(s) in $file"
|
||||||
|
|
||||||
|
# Validate each input has required fields
|
||||||
|
local input_names
|
||||||
|
input_names=$(yq eval '.on.workflow_call.inputs | keys | .[]' "$file" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
while IFS= read -r input_name; do
|
||||||
|
if [[ -n "$input_name" ]]; then
|
||||||
|
# Check for description
|
||||||
|
if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"description\")" "$file" 2>/dev/null | grep -q "true"; then
|
||||||
|
log_warning "Input '$input_name' missing description in $file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for type
|
||||||
|
if ! yq eval ".on.workflow_call.inputs.${input_name} | has(\"type\")" "$file" 2>/dev/null | grep -q "true"; then
|
||||||
|
log_warning "Input '$input_name' missing type in $file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$input_names"
|
||||||
|
else
|
||||||
|
log_warning "No inputs found in reusable workflow $file"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate a single workflow file
|
||||||
|
validate_workflow_file() {
|
||||||
|
local file="$1"
|
||||||
|
local filename
|
||||||
|
filename=$(basename "$file")
|
||||||
|
|
||||||
|
log_info "Validating $file..."
|
||||||
|
((TOTAL_FILES++))
|
||||||
|
|
||||||
|
# Check if file exists and is readable
|
||||||
|
if [[ ! -r "$file" ]]; then
|
||||||
|
log_error "Cannot read file: $file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate YAML syntax
|
||||||
|
if ! validate_yaml_syntax "$file"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate workflow structure
|
||||||
|
if ! validate_workflow_structure "$file"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Additional validations for reusable workflows
|
||||||
|
if is_reusable_workflow "$file"; then
|
||||||
|
log_success "$filename is a reusable workflow"
|
||||||
|
validate_workflow_inputs "$file"
|
||||||
|
else
|
||||||
|
log_warning "$filename is not a reusable workflow (missing workflow_call trigger)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for common issues
|
||||||
|
if grep -q "uses: \./\|uses: \.\\\\" "$file"; then
|
||||||
|
log_warning "Found relative path in 'uses' field in $file - this may cause issues"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for hardcoded values that should be inputs
|
||||||
|
if grep -q "python-version: [\"']3\." "$file"; then
|
||||||
|
log_warning "Found hardcoded Python version in $file - consider making it an input"
|
||||||
|
fi
|
||||||
|
|
||||||
|
((VALID_FILES++))
|
||||||
|
log_success "$filename validation completed"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate all workflow files
|
||||||
|
validate_all_workflows() {
|
||||||
|
log_info "Starting workflow validation..."
|
||||||
|
|
||||||
|
if [[ ! -d "$WORKFLOW_DIR" ]]; then
|
||||||
|
log_error "Workflow directory '$WORKFLOW_DIR' not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find all YAML files in workflow directory
|
||||||
|
local workflow_files
|
||||||
|
workflow_files=$(find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ -z "$workflow_files" ]]; then
|
||||||
|
log_error "No workflow files found in '$WORKFLOW_DIR'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate each file
|
||||||
|
local failed_files=0
|
||||||
|
while IFS= read -r file; do
|
||||||
|
if [[ -n "$file" ]]; then
|
||||||
|
if ! validate_workflow_file "$file"; then
|
||||||
|
((failed_files++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done <<< "$workflow_files"
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
echo
|
||||||
|
log_info "=== Validation Summary ==="
|
||||||
|
log_info "Total files: $TOTAL_FILES"
|
||||||
|
log_success "Valid files: $VALID_FILES"
|
||||||
|
|
||||||
|
if [[ $WARNINGS -gt 0 ]]; then
|
||||||
|
log_warning "Warnings: $WARNINGS"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $failed_files -gt 0 ]]; then
|
||||||
|
log_error "Failed files: $failed_files"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $WARNINGS -gt 0 ]]; then
|
||||||
|
log_warning "Validation completed with warnings"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "All workflows validated successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate documentation files
|
||||||
|
validate_documentation() {
|
||||||
|
log_info "Validating documentation..."
|
||||||
|
|
||||||
|
local readme_files=("README.md" "README-python-code-quality.md" "README-python-pytest.md" "README-web-formatting.md")
|
||||||
|
|
||||||
|
for readme in "${readme_files[@]}"; do
|
||||||
|
if [[ -f "$readme" ]]; then
|
||||||
|
log_success "Found $readme"
|
||||||
|
|
||||||
|
# Check for basic sections
|
||||||
|
if ! grep -q "^# " "$readme"; then
|
||||||
|
log_warning "$readme missing main heading"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q "## Usage" "$readme"; then
|
||||||
|
log_warning "$readme missing Usage section"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log_warning "Missing documentation file: $readme"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate workflow file list
|
||||||
|
list_workflows() {
|
||||||
|
log_info "Available workflow templates:"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -d "$WORKFLOW_DIR" ]]; then
|
||||||
|
find "$WORKFLOW_DIR" -name "*.yml" -o -name "*.yaml" | while read -r file; do
|
||||||
|
local relative_path
|
||||||
|
relative_path=$(echo "$file" | sed "s|^$WORKFLOW_DIR/||")
|
||||||
|
|
||||||
|
if is_reusable_workflow "$file"; then
|
||||||
|
echo -e " ${GREEN}✓${NC} $relative_path (reusable)"
|
||||||
|
else
|
||||||
|
echo -e " ${YELLOW}○${NC} $relative_path (not reusable)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_error "Workflow directory not found"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main function
|
||||||
|
main() {
|
||||||
|
echo -e "${BLUE}CI Template Validator${NC}"
|
||||||
|
echo "======================"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
case "${1:-validate}" in
|
||||||
|
"validate")
|
||||||
|
check_dependencies
|
||||||
|
validate_all_workflows
|
||||||
|
validate_documentation
|
||||||
|
;;
|
||||||
|
"list")
|
||||||
|
list_workflows
|
||||||
|
;;
|
||||||
|
"help"|"-h"|"--help")
|
||||||
|
echo "Usage: $0 [command]"
|
||||||
|
echo
|
||||||
|
echo "Commands:"
|
||||||
|
echo " validate Validate all workflow files (default)"
|
||||||
|
echo " list List all workflow templates"
|
||||||
|
echo " help Show this help message"
|
||||||
|
echo
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Validate all workflows"
|
||||||
|
echo " $0 validate # Same as above"
|
||||||
|
echo " $0 list # List all available templates"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown command: $1"
|
||||||
|
echo "Use '$0 help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function with all arguments
|
||||||
|
main "$@"
|
||||||
|
|
||||||
|
echo -e "${BLUE}🔍 CI Template Validation Script${NC}"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Function to print status
|
||||||
|
print_status() {
|
||||||
|
if [ $1 -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}✅ $2${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}❌ $2${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to print warning
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to print info
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if required tools are installed
|
||||||
|
check_dependencies() {
|
||||||
|
echo -e "\n${BLUE}Checking dependencies...${NC}"
|
||||||
|
|
||||||
|
local missing_deps=()
|
||||||
|
|
||||||
|
if ! command -v yamllint &> /dev/null; then
|
||||||
|
missing_deps+=("yamllint")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
missing_deps+=("python3")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#missing_deps[@]} -gt 0 ]; then
|
||||||
|
echo -e "${RED}Missing dependencies: ${missing_deps[*]}${NC}"
|
||||||
|
echo "Install with:"
|
||||||
|
for dep in "${missing_deps[@]}"; do
|
||||||
|
case $dep in
|
||||||
|
yamllint)
|
||||||
|
echo " pip install yamllint"
|
||||||
|
;;
|
||||||
|
python3)
|
||||||
|
echo " Install Python 3.x"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_status 0 "All dependencies available"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate YAML syntax
|
||||||
|
validate_yaml_syntax() {
|
||||||
|
echo -e "\n${BLUE}Validating YAML syntax...${NC}"
|
||||||
|
|
||||||
|
local yaml_files=()
|
||||||
|
while IFS= read -r -d $'\0'; do
|
||||||
|
yaml_files+=("$REPLY")
|
||||||
|
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
|
||||||
|
|
||||||
|
if [ ${#yaml_files[@]} -eq 0 ]; then
|
||||||
|
print_warning "No YAML files found in .gitea/workflows/"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local errors=0
|
||||||
|
for file in "${yaml_files[@]}"; do
|
||||||
|
if yamllint "$file" > /dev/null 2>&1; then
|
||||||
|
print_status 0 "YAML syntax valid: $file"
|
||||||
|
else
|
||||||
|
print_status 1 "YAML syntax error: $file"
|
||||||
|
yamllint "$file"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $errors -eq 0 ]; then
|
||||||
|
print_status 0 "All YAML files have valid syntax"
|
||||||
|
else
|
||||||
|
print_status 1 "$errors YAML files have syntax errors"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate workflow structure
|
||||||
|
validate_workflow_structure() {
|
||||||
|
echo -e "\n${BLUE}Validating workflow structure...${NC}"
|
||||||
|
|
||||||
|
local workflow_files=()
|
||||||
|
while IFS= read -r -d $'\0'; do
|
||||||
|
workflow_files+=("$REPLY")
|
||||||
|
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
|
||||||
|
|
||||||
|
local errors=0
|
||||||
|
for file in "${workflow_files[@]}"; do
|
||||||
|
echo -e "\nChecking $file..."
|
||||||
|
|
||||||
|
# Check for workflow_call trigger
|
||||||
|
if grep -q "workflow_call" "$file"; then
|
||||||
|
print_status 0 "Has workflow_call trigger"
|
||||||
|
else
|
||||||
|
print_status 1 "Missing workflow_call trigger"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for inputs section
|
||||||
|
if grep -q "inputs:" "$file"; then
|
||||||
|
print_status 0 "Has inputs section"
|
||||||
|
else
|
||||||
|
print_warning "No inputs section (may be intentional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for jobs section
|
||||||
|
if grep -q "jobs:" "$file"; then
|
||||||
|
print_status 0 "Has jobs section"
|
||||||
|
else
|
||||||
|
print_status 1 "Missing jobs section"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for runs-on
|
||||||
|
if grep -q "runs-on:" "$file"; then
|
||||||
|
print_status 0 "Has runs-on specified"
|
||||||
|
else
|
||||||
|
print_status 1 "Missing runs-on specification"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for checkout action
|
||||||
|
if grep -q "actions/checkout" "$file"; then
|
||||||
|
print_status 0 "Uses checkout action"
|
||||||
|
else
|
||||||
|
print_warning "No checkout action (may be intentional)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $errors -eq 0 ]; then
|
||||||
|
print_status 0 "All workflows have valid structure"
|
||||||
|
else
|
||||||
|
print_status 1 "$errors workflows have structure issues"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate input parameters
|
||||||
|
validate_input_parameters() {
|
||||||
|
echo -e "\n${BLUE}Validating input parameters...${NC}"
|
||||||
|
|
||||||
|
local workflow_files=()
|
||||||
|
while IFS= read -r -d $'\0'; do
|
||||||
|
workflow_files+=("$REPLY")
|
||||||
|
done < <(find .gitea/workflows -name "*.yml" -print0 2>/dev/null)
|
||||||
|
|
||||||
|
for file in "${workflow_files[@]}"; do
|
||||||
|
echo -e "\nChecking inputs in $file..."
|
||||||
|
|
||||||
|
# Extract input definitions and usage
|
||||||
|
local input_definitions=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ $line =~ ^[[:space:]]*([a-zA-Z0-9_-]+):[[:space:]]*$ ]]; then
|
||||||
|
input_name="${BASH_REMATCH[1]}"
|
||||||
|
if [[ ! $input_name =~ ^(description|required|type|default)$ ]]; then
|
||||||
|
input_definitions+=("$input_name")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(sed -n '/inputs:/,/^[[:space:]]*[a-zA-Z]/p' "$file" | head -n -1)
|
||||||
|
|
||||||
|
# Check if inputs are used in the workflow
|
||||||
|
for input in "${input_definitions[@]}"; do
|
||||||
|
if grep -q "\${{ inputs\.$input }}" "$file"; then
|
||||||
|
print_status 0 "Input '$input' is used in workflow"
|
||||||
|
else
|
||||||
|
print_warning "Input '$input' is defined but not used"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create test project structure
|
||||||
|
create_test_project() {
|
||||||
|
echo -e "\n${BLUE}Creating test project structure...${NC}"
|
||||||
|
|
||||||
|
local test_dir="test-validation-project"
|
||||||
|
|
||||||
|
if [ -d "$test_dir" ]; then
|
||||||
|
print_info "Removing existing test project"
|
||||||
|
rm -rf "$test_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$test_dir/src/test_package"
|
||||||
|
cd "$test_dir"
|
||||||
|
|
||||||
|
# Create pyproject.toml
|
||||||
|
cat > pyproject.toml << 'EOF'
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "test-validation-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Test package for CI template validation"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"pyupgrade>=3.0.0",
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 88
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create source code
|
||||||
|
mkdir -p src/test_package
|
||||||
|
cat > src/test_package/__init__.py << 'EOF'
|
||||||
|
"""Test package for validation."""
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
def hello_world() -> str:
|
||||||
|
"""Return a greeting message."""
|
||||||
|
return "Hello, World!"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat > src/test_package/math_utils.py << 'EOF'
|
||||||
|
"""Math utility functions."""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def add_numbers(numbers: List[float]) -> float:
|
||||||
|
"""Add a list of numbers."""
|
||||||
|
return sum(numbers)
|
||||||
|
|
||||||
|
def multiply_numbers(numbers: List[float]) -> float:
|
||||||
|
"""Multiply a list of numbers."""
|
||||||
|
result = 1.0
|
||||||
|
for num in numbers:
|
||||||
|
result *= num
|
||||||
|
return result
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create tests
|
||||||
|
mkdir -p tests
|
||||||
|
cat > tests/test_math_utils.py << 'EOF'
|
||||||
|
"""Tests for math utilities."""
|
||||||
|
import pytest
|
||||||
|
from test_package.math_utils import add_numbers, multiply_numbers
|
||||||
|
|
||||||
|
def test_add_numbers():
|
||||||
|
assert add_numbers([1, 2, 3]) == 6
|
||||||
|
assert add_numbers([]) == 0
|
||||||
|
assert add_numbers([5.5, 2.5]) == 8.0
|
||||||
|
|
||||||
|
def test_multiply_numbers():
|
||||||
|
assert multiply_numbers([2, 3, 4]) == 24
|
||||||
|
assert multiply_numbers([1]) == 1
|
||||||
|
assert multiply_numbers([2.5, 2]) == 5.0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
print_status 0 "Test project created at $test_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate validation report
|
||||||
|
generate_report() {
|
||||||
|
echo -e "\n${BLUE}Generating validation report...${NC}"
|
||||||
|
|
||||||
|
local report_file="validation-report.md"
|
||||||
|
|
||||||
|
cat > "$report_file" << 'EOF'
|
||||||
|
# CI Template Validation Report
|
||||||
|
|
||||||
|
Generated on: $(date)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
This report contains the validation results for the CI template workflows.
|
||||||
|
|
||||||
|
## Workflow Files
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Add workflow file information
|
||||||
|
find .gitea/workflows -name "*.yml" 2>/dev/null | while read -r file; do
|
||||||
|
echo "### $file" >> "$report_file"
|
||||||
|
echo "" >> "$report_file"
|
||||||
|
echo "- **Size:** $(wc -c < "$file") bytes" >> "$report_file"
|
||||||
|
echo "- **Lines:** $(wc -l < "$file") lines" >> "$report_file"
|
||||||
|
|
||||||
|
if grep -q "workflow_call" "$file"; then
|
||||||
|
echo "- **Type:** Reusable workflow ✅" >> "$report_file"
|
||||||
|
else
|
||||||
|
echo "- **Type:** Standard workflow ⚠️" >> "$report_file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local input_count=$(grep -c "^[[:space:]]*[a-zA-Z0-9_-]*:[[:space:]]*$" "$file" | head -n 1)
|
||||||
|
echo "- **Input parameters:** $input_count" >> "$report_file"
|
||||||
|
echo "" >> "$report_file"
|
||||||
|
done
|
||||||
|
|
||||||
|
print_status 0 "Validation report generated: $report_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
echo -e "${BLUE}Starting CI template validation...${NC}"
|
||||||
|
|
||||||
|
local exit_code=0
|
||||||
|
|
||||||
|
# Run all validation steps
|
||||||
|
check_dependencies || exit_code=1
|
||||||
|
validate_yaml_syntax || exit_code=1
|
||||||
|
validate_workflow_structure || exit_code=1
|
||||||
|
validate_input_parameters || exit_code=1
|
||||||
|
create_test_project || exit_code=1
|
||||||
|
generate_report || exit_code=1
|
||||||
|
|
||||||
|
echo -e "\n${BLUE}Validation Summary${NC}"
|
||||||
|
echo "=================="
|
||||||
|
|
||||||
|
if [ $exit_code -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}🎉 All validations passed!${NC}"
|
||||||
|
echo -e "${GREEN}Your CI templates are ready for use.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}💥 Some validations failed.${NC}"
|
||||||
|
echo -e "${RED}Please review the errors above and fix them.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${BLUE}Next steps:${NC}"
|
||||||
|
echo "1. Review the validation report"
|
||||||
|
echo "2. Test the templates with the created test project"
|
||||||
|
echo "3. Run the automated test workflow: .gitea/workflows/test-templates.yml"
|
||||||
|
|
||||||
|
return $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the main function
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
name: Test CI Templates
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
code-quality:
|
||||||
|
name: Code Quality
|
||||||
|
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
working-directory: "."
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Run Tests
|
||||||
|
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
coverage-package: "test_package"
|
||||||
|
pytest-args: "--verbose"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Test Package
|
||||||
|
|
||||||
|
A simple Python package for testing CI templates.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Basic calculator operations
|
||||||
|
- Utility functions
|
||||||
|
- Comprehensive test suite
|
||||||
|
- Type hints
|
||||||
|
- Code quality tools integration
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
uv sync --all-extras
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
uv run pytest
|
||||||
|
|
||||||
|
# Run code quality checks
|
||||||
|
uv run mypy .
|
||||||
|
uv run ruff check .
|
||||||
|
uv run ruff format --check .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing CI Templates
|
||||||
|
|
||||||
|
This project is used to test the CI templates in the parent repository.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "test-package"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Test package for CI template validation"
|
||||||
|
authors = [
|
||||||
|
{ name = "Test Author", email = "[email protected]" }
|
||||||
|
]
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
dependencies = [
|
||||||
|
"requests>=2.28.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
"mypy>=1.0.0",
|
||||||
|
"ruff>=0.1.0",
|
||||||
|
"pyupgrade>=3.0.0",
|
||||||
|
]
|
||||||
|
test = [
|
||||||
|
"pytest>=7.0.0",
|
||||||
|
"pytest-cov>=4.0.0",
|
||||||
|
"pytest-mock>=3.10.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
|
python_classes = ["Test*"]
|
||||||
|
python_functions = ["test_*"]
|
||||||
|
addopts = [
|
||||||
|
"--strict-markers",
|
||||||
|
"--strict-config",
|
||||||
|
"--verbose",
|
||||||
|
"--cov=test_package",
|
||||||
|
"--cov-report=term-missing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.10"
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py310"
|
||||||
|
line-length = 88
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"I", # isort
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
source = ["test_package"]
|
||||||
|
omit = ["tests/*"]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"def __repr__",
|
||||||
|
"if self.debug:",
|
||||||
|
"if settings.DEBUG",
|
||||||
|
"raise AssertionError",
|
||||||
|
"raise NotImplementedError",
|
||||||
|
"if 0:",
|
||||||
|
"if __name__ == .__main__.:",
|
||||||
|
"class .*\\bProtocol\\):",
|
||||||
|
"@(abc\\.)?abstractmethod",
|
||||||
|
]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"""Test package for CI template validation."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
from .calculator import Calculator
|
||||||
|
from .utils import format_number
|
||||||
|
|
||||||
|
__all__ = ["Calculator", "format_number"]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""Calculator module for basic arithmetic operations."""
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
Number = Union[int, float]
|
||||||
|
|
||||||
|
|
||||||
|
class Calculator:
|
||||||
|
"""A simple calculator class."""
|
||||||
|
|
||||||
|
def add(self, a: Number, b: Number) -> Number:
|
||||||
|
"""Add two numbers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def subtract(self, a: Number, b: Number) -> Number:
|
||||||
|
"""Subtract b from a."""
|
||||||
|
return a - b
|
||||||
|
|
||||||
|
def multiply(self, a: Number, b: Number) -> Number:
|
||||||
|
"""Multiply two numbers."""
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
def divide(self, a: Number, b: Number) -> float:
|
||||||
|
"""Divide a by b."""
|
||||||
|
if b == 0:
|
||||||
|
raise ValueError("Cannot divide by zero")
|
||||||
|
return a / b
|
||||||
|
|
||||||
|
def power(self, base: Number, exponent: Number) -> Number:
|
||||||
|
"""Raise base to the power of exponent."""
|
||||||
|
return base ** exponent
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"""Utility functions for the test package."""
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
|
def format_number(number: Union[int, float], precision: int = 2) -> str:
|
||||||
|
"""Format a number with specified precision."""
|
||||||
|
if isinstance(number, int):
|
||||||
|
return str(number)
|
||||||
|
return f"{number:.{precision}f}"
|
||||||
|
|
||||||
|
|
||||||
|
def is_even(number: int) -> bool:
|
||||||
|
"""Check if a number is even."""
|
||||||
|
return number % 2 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def factorial(n: int) -> int:
|
||||||
|
"""Calculate factorial of a number."""
|
||||||
|
if n < 0:
|
||||||
|
raise ValueError("Factorial is not defined for negative numbers")
|
||||||
|
if n == 0 or n == 1:
|
||||||
|
return 1
|
||||||
|
return n * factorial(n - 1)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"""Tests for the calculator module."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from test_package.calculator import Calculator
|
||||||
|
|
||||||
|
|
||||||
|
class TestCalculator:
|
||||||
|
"""Test cases for Calculator class."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
"""Set up test fixtures."""
|
||||||
|
self.calc = Calculator()
|
||||||
|
|
||||||
|
def test_add(self):
|
||||||
|
"""Test addition operation."""
|
||||||
|
assert self.calc.add(2, 3) == 5
|
||||||
|
assert self.calc.add(-1, 1) == 0
|
||||||
|
assert self.calc.add(0.1, 0.2) == pytest.approx(0.3)
|
||||||
|
|
||||||
|
def test_subtract(self):
|
||||||
|
"""Test subtraction operation."""
|
||||||
|
assert self.calc.subtract(5, 3) == 2
|
||||||
|
assert self.calc.subtract(1, 1) == 0
|
||||||
|
assert self.calc.subtract(-1, -2) == 1
|
||||||
|
|
||||||
|
def test_multiply(self):
|
||||||
|
"""Test multiplication operation."""
|
||||||
|
assert self.calc.multiply(3, 4) == 12
|
||||||
|
assert self.calc.multiply(-2, 3) == -6
|
||||||
|
assert self.calc.multiply(0, 100) == 0
|
||||||
|
|
||||||
|
def test_divide(self):
|
||||||
|
"""Test division operation."""
|
||||||
|
assert self.calc.divide(10, 2) == 5.0
|
||||||
|
assert self.calc.divide(7, 2) == 3.5
|
||||||
|
assert self.calc.divide(-6, 3) == -2.0
|
||||||
|
|
||||||
|
def test_divide_by_zero(self):
|
||||||
|
"""Test division by zero raises error."""
|
||||||
|
with pytest.raises(ValueError, match="Cannot divide by zero"):
|
||||||
|
self.calc.divide(5, 0)
|
||||||
|
|
||||||
|
def test_power(self):
|
||||||
|
"""Test power operation."""
|
||||||
|
assert self.calc.power(2, 3) == 8
|
||||||
|
assert self.calc.power(5, 0) == 1
|
||||||
|
assert self.calc.power(2, -1) == 0.5
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Tests for the utils module."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from test_package.utils import format_number, is_even, factorial
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtils:
|
||||||
|
"""Test cases for utility functions."""
|
||||||
|
|
||||||
|
def test_format_number_integer(self):
|
||||||
|
"""Test formatting integers."""
|
||||||
|
assert format_number(42) == "42"
|
||||||
|
assert format_number(-5) == "-5"
|
||||||
|
|
||||||
|
def test_format_number_float(self):
|
||||||
|
"""Test formatting floats."""
|
||||||
|
assert format_number(3.14159) == "3.14"
|
||||||
|
assert format_number(3.14159, 3) == "3.142"
|
||||||
|
assert format_number(2.0) == "2.00"
|
||||||
|
|
||||||
|
def test_is_even(self):
|
||||||
|
"""Test even number detection."""
|
||||||
|
assert is_even(2) is True
|
||||||
|
assert is_even(4) is True
|
||||||
|
assert is_even(1) is False
|
||||||
|
assert is_even(3) is False
|
||||||
|
assert is_even(0) is True
|
||||||
|
assert is_even(-2) is True
|
||||||
|
assert is_even(-1) is False
|
||||||
|
|
||||||
|
def test_factorial_positive(self):
|
||||||
|
"""Test factorial of positive numbers."""
|
||||||
|
assert factorial(0) == 1
|
||||||
|
assert factorial(1) == 1
|
||||||
|
assert factorial(5) == 120
|
||||||
|
assert factorial(3) == 6
|
||||||
|
|
||||||
|
def test_factorial_negative(self):
|
||||||
|
"""Test factorial of negative numbers raises error."""
|
||||||
|
with pytest.raises(ValueError, match="Factorial is not defined for negative numbers"):
|
||||||
|
factorial(-1)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
name: Test Web Formatting
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
formatting:
|
||||||
|
name: Web Formatting
|
||||||
|
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
prettier-config: ".prettierrc.json"
|
||||||
|
check-yaml: true
|
||||||
|
check-json: true
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: Test Web App
|
||||||
|
description: A simple web application for testing
|
||||||
|
|
||||||
|
api:
|
||||||
|
version: v1
|
||||||
|
endpoints:
|
||||||
|
- name: health
|
||||||
|
path: /health
|
||||||
|
method: GET
|
||||||
|
- name: calculator
|
||||||
|
path: /calculate
|
||||||
|
method: POST
|
||||||
|
|
||||||
|
database:
|
||||||
|
type: sqlite
|
||||||
|
name: test.db
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level: info
|
||||||
|
format: json
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
console.log("Hello, World!");
|
||||||
|
|
||||||
|
const calculator = {
|
||||||
|
add: (a, b) => a + b,
|
||||||
|
subtract: (a, b) => a - b,
|
||||||
|
multiply: (a, b) => a * b,
|
||||||
|
divide: (a, b) => {
|
||||||
|
if (b === 0) {
|
||||||
|
throw new Error("Cannot divide by zero");
|
||||||
|
}
|
||||||
|
return a / b;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = calculator;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "test-web-project",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test web project for CI template validation",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check ."
|
||||||
|
},
|
||||||
|
"keywords": ["test", "ci", "templates"],
|
||||||
|
"author": "Test Author",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user