Compare commits

..
3 Commits
Author SHA1 Message Date
Brian Bjarke Jensen 9abca60246 formatting fix
Test CI Templates / test-python-code-quality (3.10) (push) Failing after 6s
Test CI Templates / test-python-code-quality (3.11) (push) Failing after 5s
Test CI Templates / test-python-code-quality (3.12) (push) Failing after 6s
Test CI Templates / validate-syntax (push) Successful in 50s
Test CI Templates / test-python-code-quality (false, false, basic) (push) Failing after 5s
Test CI Templates / test-python-code-quality (false, true, skip-pyupgrade) (push) Failing after 5s
Test CI Templates / test-python-code-quality (true, false, skip-mypy) (push) Failing after 5s
Test CI Templates / test-python-pytest (false, 3.11) (push) Failing after 5s
Test CI Templates / test-python-pytest (false, 3.12) (push) Failing after 5s
Test CI Templates / test-python-pytest (true, 3.11) (push) Failing after 5s
Test CI Templates / test-python-pytest (true, 3.12) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, false, 18) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, false, 20) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, true, 18) (push) Failing after 5s
Test CI Templates / test-web-formatting (false, true, 20) (push) Failing after 5s
Test CI Templates / test-web-formatting (true, false, 18) (push) Failing after 5s
Test CI Templates / test-web-formatting (true, false, 20) (push) Failing after 5s
Test CI Templates / test-web-formatting (true, true, 18) (push) Failing after 6s
Test CI Templates / test-web-formatting (true, true, 20) (push) Failing after 7s
Test CI Templates / test-monorepo (push) Failing after 6s
Test CI Templates / test-edge-cases (push) Failing after 6s
Test CI Templates / test-results (push) Successful in 3s
2025-09-19 19:39:13 +02:00
Brian Bjarke Jensen b426d43de0 added tests 2025-09-19 19:38:10 +02:00
Brian Bjarke Jensen 0abd398c10 initial commit 2025-09-19 19:20:47 +02:00
24 changed files with 2875 additions and 2 deletions
+74
View File
@@ -0,0 +1,74 @@
name: Python Code Quality Pipeline
on:
workflow_call:
inputs:
python-version:
description: 'Python version to use (e.g., "3.12")'
required: false
type: string
default: "3.12"
working-directory:
description: 'Working directory for the project'
required: false
type: string
default: "."
skip-mypy:
description: 'Skip mypy type checking'
required: false
type: boolean
default: false
skip-pyupgrade:
description: 'Skip pyupgrade check'
required: false
type: boolean
default: false
uv-extras:
description: 'UV extras to install (e.g., "dev,test")'
required: false
type: string
default: "all-extras"
jobs:
code-quality:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Install uv
run: pip install uv
- name: Install dependencies
env:
UV_LINK_MODE: copy
run: |
if [ "${{ inputs.uv-extras }}" = "all-extras" ]; then
uv sync --all-extras
else
uv sync --extra ${{ inputs.uv-extras }}
fi
- name: Type check with mypy
if: ${{ !inputs.skip-mypy }}
run: uv run mypy .
- name: Ruff lint
run: uv run ruff check .
- name: Ruff format
run: uv run ruff format --check .
- name: Pyupgrade check
if: ${{ !inputs.skip-pyupgrade }}
run: uv run pyupgrade --py312-plus $(git ls-files '*.py') && git diff --exit-code
+101
View File
@@ -0,0 +1,101 @@
name: Python Test Pipeline
on:
workflow_call:
inputs:
python-version:
description: 'Python version to use (e.g., "3.12")'
required: false
type: string
default: "3.12"
working-directory:
description: 'Working directory for the project'
required: false
type: string
default: "."
uv-extras:
description: 'UV extras to install (e.g., "dev,test")'
required: false
type: string
default: "all-extras"
pytest-args:
description: 'Additional arguments to pass to pytest'
required: false
type: string
default: ""
coverage-package:
description: 'Package name for coverage reporting (e.g., "my_package")'
required: false
type: string
default: ""
skip-coverage-comment:
description: 'Skip posting coverage comment to PR'
required: false
type: boolean
default: false
api-url:
description: 'Gitea API URL for posting comments'
required: false
type: string
default: ""
secrets:
CI_RUNNER_TOKEN:
description: 'Token for posting PR comments'
required: false
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Install uv
run: pip install uv
- name: Install dependencies
env:
UV_LINK_MODE: copy
run: |
if [ "${{ inputs.uv-extras }}" = "all-extras" ]; then
uv sync --all-extras
else
uv sync --extra ${{ inputs.uv-extras }}
fi
- name: Run pytest
env:
PYTHONPATH: .
run: |
if [ -n "${{ inputs.coverage-package }}" ]; then
uv run pytest --cov=${{ inputs.coverage-package }} --cov-report=term-missing ${{ inputs.pytest-args }} > coverage.txt
else
uv run pytest ${{ inputs.pytest-args }}
fi
- name: Post coverage summary to PR
if: ${{ !inputs.skip-coverage-comment && inputs.coverage-package != '' && github.event_name == 'pull_request' && inputs.api-url != '' }}
env:
API_URL: ${{ inputs.api-url }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
run: |
if [ -f coverage.txt ]; then
COVERAGE=$(cat coverage.txt)
COMMENT_BODY="**Test Coverage Report:**\n\`\`\`\n$COVERAGE\n\`\`\`"
curl -s -X POST "$API_URL/repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/comments" \
-H "Authorization: token $CI_RUNNER_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\": \"$COMMENT_BODY\"}"
fi
+460
View File
@@ -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
+87
View File
@@ -0,0 +1,87 @@
name: Web/General Formatting Pipeline
on:
workflow_call:
inputs:
working-directory:
description: 'Working directory for the project'
required: false
type: string
default: "."
node-version:
description: 'Node.js version to use for prettier (e.g., "20")'
required: false
type: string
default: "20"
skip-prettier:
description: 'Skip prettier formatting check'
required: false
type: boolean
default: false
prettier-config:
description: 'Path to prettier config file (optional)'
required: false
type: string
default: ""
check-yaml:
description: 'Check YAML files with yamllint'
required: false
type: boolean
default: false
check-json:
description: 'Check JSON files for validity'
required: false
type: boolean
default: false
jobs:
formatting:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
if: ${{ !inputs.skip-prettier }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install prettier
if: ${{ !inputs.skip-prettier }}
run: npm install --save-dev --save-exact prettier
- name: Run prettier check
if: ${{ !inputs.skip-prettier }}
run: |
if [ -n "${{ inputs.prettier-config }}" ]; then
npx prettier --check . --config ${{ inputs.prettier-config }}
else
npx prettier --check .
fi
- name: Set up Python for YAML linting
if: ${{ inputs.check-yaml }}
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install yamllint
if: ${{ inputs.check-yaml }}
run: pip install yamllint
- name: Check YAML files
if: ${{ inputs.check-yaml }}
run: yamllint .
- name: Check JSON files
if: ${{ inputs.check-json }}
run: |
find . -name "*.json" -type f | while read -r file; do
echo "Checking $file"
python -m json.tool "$file" > /dev/null
done
+126
View File
@@ -0,0 +1,126 @@
# Python Code Quality Pipeline Template
A reusable Gitea Actions workflow for Python code quality checks including type checking, linting, and formatting.
## Features
- **Type checking** with mypy
- **Linting** with ruff
- **Code formatting** with ruff
- **Python version upgrades** with pyupgrade
- **Flexible configuration** with optional steps and customizable inputs
## Usage
To use this reusable workflow in your repository, create a workflow file (e.g., `.gitea/workflows/python/code-quality.yml`) in your target repository:
### Basic Usage
```yaml
name: Code Quality
on:
push:
branches:
- main
pull_request:
jobs:
code-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
```
### Advanced Usage with Custom Inputs
```yaml
name: Code Quality
on:
push:
branches:
- main
pull_request:
jobs:
code-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
python-version: "3.11"
working-directory: "./backend"
uv-extras: "dev,test"
```
## Available Inputs
| Input | Description | Required | Default | Type |
|-------|-------------|----------|---------|------|
| `python-version` | Python version to use (e.g., "3.12") | No | "3.12" | string |
| `working-directory` | Working directory for the project | No | "." | string |
| `skip-mypy` | Skip mypy type checking | No | false | boolean |
| `skip-pyupgrade` | Skip pyupgrade check | No | false | boolean |
| `uv-extras` | UV extras to install (e.g., "dev,test") | No | "all-extras" | string |
## Requirements
Your Python project should have:
1. A `pyproject.toml` file with uv dependency management
2. Properly configured tools:
- `mypy` for type checking (if not skipped)
- `ruff` for linting and formatting
- `pyupgrade` for Python version upgrades (if not skipped)
3. Optional: prettier configuration for non-Python files (if not skipped)
## Examples
### Skip mypy for a project without type hints
```yaml
jobs:
code-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
skip-mypy: true
```
### Use specific Python version and extras
```yaml
jobs:
code-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
python-version: "3.11"
uv-extras: "dev,lint"
```
### Monorepo with specific working directory
```yaml
jobs:
backend-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
working-directory: "./backend"
ml-service-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
working-directory: "./ml-service"
python-version: "3.11"
```
## Repository Structure
For this template to work properly, ensure your repository has the workflow file in the correct location:
```
.gitea/
workflows/
python/
code-quality.yml # This reusable workflow
```
## Contributing
To update this template, modify the workflow file and ensure all changes are backward compatible or properly versioned using git tags.
+248
View File
@@ -0,0 +1,248 @@
# Python Test Pipeline Template
A reusable Gitea Actions workflow for running Python tests with pytest, including optional coverage reporting and PR comment integration.
## Features
- **Pytest testing** with configurable arguments
- **Coverage reporting** with pytest-cov
- **PR comment integration** for coverage reports
- **UV package management** support
- **Flexible configuration** with optional steps and customizable inputs
- **Monorepo support** with working directory configuration
## Usage
### Basic Usage
Create a workflow file in your repository (e.g., `.github/workflows/test.yml`):
```yaml
name: Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
coverage-package: "my_package"
```
### Advanced Usage with PR Comments
```yaml
name: Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
python-version: "3.11"
coverage-package: "data_store"
pytest-args: "--verbose --tb=short"
api-url: "https://gitea.gt-proj.com/api/v1"
secrets:
CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
```
### Examples
### Skip coverage reporting
```yaml
jobs:
test:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
pytest-args: "--verbose"
```
### Custom pytest configuration
```yaml
jobs:
test:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
coverage-package: "myapp"
pytest-args: "--maxfail=1 --tb=short -x"
uv-extras: "test,dev"
```
### Monorepo with specific working directory
```yaml
jobs:
backend-tests:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
working-directory: "./backend"
coverage-package: "backend_api"
ml-service-tests:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/pytest.yml@main
with:
working-directory: "./ml-service"
coverage-package: "ml_models"
python-version: "3.11"
```
## Available Inputs
| Input | Description | Required | Default | Type |
|-------|-------------|----------|---------|------|
| `python-version` | Python version to use (e.g., "3.12") | No | "3.12" | string |
| `working-directory` | Working directory for the project | No | "." | string |
| `uv-extras` | UV extras to install (e.g., "dev,test") | No | "all-extras" | string |
| `pytest-args` | Additional arguments to pass to pytest | No | "" | string |
| `coverage-package` | Package name for coverage reporting | No | "" | string |
| `skip-coverage-comment` | Skip posting coverage comment to PR | No | false | boolean |
| `api-url` | Gitea API URL for posting comments | No | "" | string |
## Available Secrets
| Secret | Description | Required |
|--------|-------------|----------|
| `CI_RUNNER_TOKEN` | Token for posting PR comments | No* |
*Required only if you want coverage comments on PRs
## Coverage Reporting
### Enabling Coverage
Set the `coverage-package` input to your main package name:
```yaml
with:
coverage-package: "my_package"
```
This will:
1. Run pytest with `--cov=my_package --cov-report=term-missing`
2. Generate a coverage report
3. Save the output to `coverage.txt`
### PR Comments
To enable coverage comments on pull requests:
1. **Set required inputs:**
```yaml
with:
coverage-package: "my_package"
api-url: "https://your-gitea-instance.com/api/v1"
```
2. **Provide the CI token secret:**
```yaml
secrets:
CI_RUNNER_TOKEN: ${{ secrets.CI_RUNNER_TOKEN }}
```
3. **Create the token in Gitea:**
- Go to your Gitea instance → Settings → Applications → Generate New Token
- Grant `repo` permissions
- Add it as a repository secret named `CI_RUNNER_TOKEN`
### Coverage Comment Format
The coverage comment will appear on PRs like this:
```
**Test Coverage Report:**
```
Name Stmts Miss Cover Missing
-------------------------------------------------
my_package/__init__.py 12 2 83% 45-46
my_package/utils.py 25 0 100%
-------------------------------------------------
TOTAL 37 2 95%
```
## Pytest Configuration
The workflow respects your project's pytest configuration:
- **pytest.ini** - Standard pytest configuration
- **pyproject.toml** - Modern Python project configuration
- **setup.cfg** - Legacy configuration
Example `pyproject.toml` configuration:
```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
]
markers = [
"slow: marks tests as slow",
"unit: marks tests as unit tests",
"integration: marks tests as integration tests",
]
```
## Requirements
- Repository must be public or you must have access to the template repository
- Python project with `pyproject.toml` and UV for dependency management
- Test dependencies should be specified in your project's extras (e.g., `test`, `dev`)
- For coverage comments: Gitea API access and `CI_RUNNER_TOKEN` secret
## Dependencies
The workflow expects these to be available in your project's test dependencies:
- **pytest**: Test runner
- **pytest-cov**: Coverage plugin (if using coverage reporting)
Example `pyproject.toml` dependencies:
```toml
[project.optional-dependencies]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-mock>=3.10.0",
]
```
## Troubleshooting
### Coverage not working
1. Ensure `coverage-package` matches your actual package name
2. Check that `pytest-cov` is installed in your test dependencies
3. Verify your package is importable (check `PYTHONPATH`)
### PR comments not appearing
1. Verify `api-url` is correct (should include `/api/v1`)
2. Check `CI_RUNNER_TOKEN` secret exists and has repo permissions
3. Ensure the workflow runs on `pull_request` events
4. Verify coverage is being generated (`coverage-package` is set)
### Tests not found
1. Check your `pytest` configuration in `pyproject.toml` or `pytest.ini`
2. Verify test files follow naming conventions (`test_*.py` or `*_test.py`)
3. Use `pytest-args` to specify custom test discovery options
## Contributing
To update this template, modify the workflow file and ensure all changes are backward compatible or properly versioned using git tags.
+119
View File
@@ -0,0 +1,119 @@
# Web/General Formatting Pipeline Template
This reusable Gitea Actions workflow provides formatting checks for web technologies and general file formats including JavaScript, TypeScript, JSON, YAML, Markdown, and more using Prettier and other tools.
## Features
- **Prettier formatting**: Check formatting for JS, TS, JSON, YAML, Markdown, HTML, CSS, and more
- **YAML validation**: Optional yamllint checking for YAML files
- **JSON validation**: Optional JSON syntax validation
- **Configurable**: Skip specific checks, custom prettier config, and more
## Usage
### Basic Usage
Create a workflow file in your repository (e.g., `.gitea/workflows/formatting.yml`):
```yaml
name: Formatting Check
on:
push:
branches: [main]
pull_request:
jobs:
formatting:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
```
### Advanced Usage with Custom Options
```yaml
name: Formatting Check
on:
push:
branches: [main]
pull_request:
jobs:
formatting:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
with:
working-directory: "./frontend"
node-version: "18"
prettier-config: ".prettierrc.json"
check-yaml: true
check-json: true
```
### Combined with Python Code Quality
For repositories that contain both Python and web technologies:
```yaml
name: Code Quality
on:
push:
branches: [main]
pull_request:
jobs:
python-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
python-version: "3.12"
working-directory: "./backend"
web-formatting:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
with:
working-directory: "./frontend"
check-yaml: true
```
## Input Parameters
| Parameter | Description | Required | Default | Type |
|-----------|-------------|----------|---------|------|
| `working-directory` | Working directory for the project | No | `"."` | string |
| `node-version` | Node.js version to use for prettier | No | `"20"` | string |
| `skip-prettier` | Skip prettier formatting check | No | `false` | boolean |
| `prettier-config` | Path to prettier config file | No | `""` | string |
| `check-yaml` | Check YAML files with yamllint | No | `false` | boolean |
| `check-json` | Check JSON files for validity | No | `false` | boolean |
## Prettier Configuration
You can customize Prettier behavior by:
1. **Using a config file**: Set the `prettier-config` input to point to your config file
2. **Using default prettier discovery**: Prettier will automatically find `.prettierrc`, `.prettierrc.json`, `.prettierrc.js`, etc.
3. **Using package.json**: Add prettier config to your `package.json`
Example `.prettierrc.json`:
```json
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2
}
```
## What Files Are Checked
- **Prettier**: JavaScript, TypeScript, JSON, YAML, Markdown, HTML, CSS, and more
- **yamllint**: All `.yml` and `.yaml` files (when enabled)
- **JSON validation**: All `.json` files (when enabled)
## Requirements
- Repository must be public or you must have access to the template repository
- For YAML checking: Repository should contain YAML files
- For JSON checking: Repository should contain JSON files
+114 -2
View File
@@ -1,3 +1,115 @@
# CI-templates # CI Templates
CI pipeline templates Reusable Gitea Actions workflow templates for consistent code quality across repositories.
## Available Templates
### Python Code Quality (`.gitea/workflows/python/code-quality.yml`)
A comprehensive Python code quality pipeline including type checking, linting, formatting, and modernization.
**Features:**
- Type checking with mypy
- Linting with ruff
- Code formatting with ruff format
- Python syntax modernization with pyupgrade
- UV package management support
📖 [Read the full documentation](README-python-code-quality.md)
### Python Test Pipeline (`.gitea/workflows/python/pytest.yml`)
A comprehensive Python testing pipeline using pytest with coverage reporting and PR comment integration.
**Features:**
- Pytest testing with configurable arguments
- Coverage reporting with pytest-cov
- PR comment integration for coverage reports
- UV package management support
- Monorepo support
📖 [Read the full documentation](README-python-pytest.md)
### Web/General Formatting (`.gitea/workflows/web/formatting.yml`)
A formatting pipeline for web technologies and general file formats using Prettier and other tools.
**Features:**
- Prettier formatting for JS, TS, JSON, YAML, Markdown, HTML, CSS
- Optional YAML validation with yamllint
- Optional JSON syntax validation
- Node.js environment setup
📖 [Read the full documentation](README-web-formatting.md)
## Quick Start
### For Python Projects
```yaml
name: Code Quality
on: [push, pull_request]
jobs:
python-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
```
### For Web/General Projects
```yaml
name: Formatting
on: [push, pull_request]
jobs:
formatting:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
```
### For Mixed Projects
```yaml
name: Code Quality
on: [push, pull_request]
jobs:
python-quality:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/python/code-quality.yml@main
with:
working-directory: "./backend"
web-formatting:
uses: gitea.gt-proj.com/brian/CI-templates/.gitea/workflows/web/formatting.yml@main
with:
working-directory: "./frontend"
```
## Repository Structure
```text
.gitea/workflows/
├── python/
│ ├── code-quality.yml # Python code quality template
│ └── pytest.yml # Python testing template
└── web/
└── formatting.yml # Web/general formatting template
README-python-code-quality.md # Python code quality documentation
README-python-pytest.md # Python testing documentation
README-web-formatting.md # Web formatting documentation
```
## Contributing
When updating templates:
1. Ensure backward compatibility
2. Update documentation
3. Test with sample projects
4. Consider versioning with git tags for breaking changes
+389
View File
@@ -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/)
+709
View File
@@ -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"
+30
View File
@@ -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.
+82
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
+20
View File
@@ -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
+15
View File
@@ -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;
+18
View File
@@ -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"
}
}
+87
View File
@@ -0,0 +1,87 @@
name: Web/General Formatting Pipeline
on:
workflow_call:
inputs:
working-directory:
description: 'Working directory for the project'
required: false
type: string
default: "."
node-version:
description: 'Node.js version to use for prettier (e.g., "20")'
required: false
type: string
default: "20"
skip-prettier:
description: 'Skip prettier formatting check'
required: false
type: boolean
default: false
prettier-config:
description: 'Path to prettier config file (optional)'
required: false
type: string
default: ""
check-yaml:
description: 'Check YAML files with yamllint'
required: false
type: boolean
default: false
check-json:
description: 'Check JSON files for validity'
required: false
type: boolean
default: false
jobs:
formatting:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
if: ${{ !inputs.skip-prettier }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install prettier
if: ${{ !inputs.skip-prettier }}
run: npm install --save-dev --save-exact prettier
- name: Run prettier check
if: ${{ !inputs.skip-prettier }}
run: |
if [ -n "${{ inputs.prettier-config }}" ]; then
npx prettier --check . --config ${{ inputs.prettier-config }}
else
npx prettier --check .
fi
- name: Set up Python for YAML linting
if: ${{ inputs.check-yaml }}
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install yamllint
if: ${{ inputs.check-yaml }}
run: pip install yamllint
- name: Check YAML files
if: ${{ inputs.check-yaml }}
run: yamllint .
- name: Check JSON files
if: ${{ inputs.check-json }}
run: |
find . -name "*.json" -type f | while read -r file; do
echo "Checking $file"
python -m json.tool "$file" > /dev/null
done