forked from puhanfmc3/tentest
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
13 KiB
372 lines
13 KiB
name: Django CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
- dev
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- '**/*.css'
|
|
- '**/*.js'
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
- dev
|
|
paths-ignore:
|
|
- '**/*.md'
|
|
- '**/*.css'
|
|
- '**/*.js'
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
# 标准测试 - Python 3.10
|
|
- python-version: "3.10"
|
|
test-type: "standard"
|
|
database: "mysql"
|
|
elasticsearch: false
|
|
coverage: false
|
|
|
|
# 标准测试 - Python 3.11
|
|
- python-version: "3.11"
|
|
test-type: "standard"
|
|
database: "mysql"
|
|
elasticsearch: false
|
|
coverage: false
|
|
|
|
# 完整测试 - 包含ES和覆盖率
|
|
- python-version: "3.11"
|
|
test-type: "full"
|
|
database: "mysql"
|
|
elasticsearch: true
|
|
coverage: true
|
|
|
|
# Docker构建测试
|
|
- python-version: "3.11"
|
|
test-type: "docker"
|
|
database: "none"
|
|
elasticsearch: false
|
|
coverage: false
|
|
|
|
name: Test (${{ matrix.test-type }}, Python ${{ matrix.python-version }})
|
|
|
|
steps:
|
|
- name: Checkout代码
|
|
uses: actions/checkout@v4
|
|
|
|
- name: 设置测试信息
|
|
id: test-info
|
|
run: |
|
|
echo "test_name=${{ matrix.test-type }}-py${{ matrix.python-version }}" >> $GITHUB_OUTPUT
|
|
if [ "${{ matrix.test-type }}" = "docker" ]; then
|
|
echo "skip_python_setup=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "skip_python_setup=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# MySQL数据库设置 (只有需要数据库的测试才执行)
|
|
- name: 启动MySQL数据库
|
|
if: matrix.database == 'mysql'
|
|
uses: samin/mysql-action@v1.3
|
|
with:
|
|
host port: 3306
|
|
container port: 3306
|
|
character set server: utf8mb4
|
|
collation server: utf8mb4_general_ci
|
|
mysql version: latest
|
|
mysql root password: root
|
|
mysql database: djangoblog
|
|
mysql user: root
|
|
mysql password: root
|
|
|
|
# Elasticsearch设置 (只有完整测试才执行)
|
|
- name: 配置系统参数 (ES)
|
|
if: matrix.elasticsearch == true
|
|
run: |
|
|
sudo swapoff -a
|
|
sudo sysctl -w vm.swappiness=1
|
|
sudo sysctl -w fs.file-max=262144
|
|
sudo sysctl -w vm.max_map_count=262144
|
|
|
|
- name: 启动Elasticsearch
|
|
if: matrix.elasticsearch == true
|
|
uses: miyataka/elasticsearch-github-actions@1
|
|
with:
|
|
stack-version: '7.12.1'
|
|
plugins: 'https://release.infinilabs.com/analysis-ik/stable/elasticsearch-analysis-ik-7.12.1.zip'
|
|
|
|
# Python环境设置 (Docker测试跳过)
|
|
- name: 设置Python ${{ matrix.python-version }}
|
|
if: steps.test-info.outputs.skip_python_setup == 'false'
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: 'pip'
|
|
cache-dependency-path: 'requirements.txt'
|
|
|
|
# 多层缓存策略优化
|
|
- name: 缓存Python依赖
|
|
if: steps.test-info.outputs.skip_python_setup == 'false'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cache/pip
|
|
.pytest_cache
|
|
key: ${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('**/pyproject.toml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-python-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }}-
|
|
${{ runner.os }}-python-${{ matrix.python-version }}-
|
|
${{ runner.os }}-python-
|
|
|
|
# Django缓存优化 (测试数据库等)
|
|
- name: 缓存Django资源
|
|
if: matrix.test-type != 'docker'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
.coverage*
|
|
htmlcov/
|
|
.django_cache/
|
|
key: ${{ runner.os }}-django-${{ matrix.test-type }}-${{ github.sha }}
|
|
restore-keys: |
|
|
${{ runner.os }}-django-${{ matrix.test-type }}-
|
|
${{ runner.os }}-django-
|
|
|
|
- name: 安装Python依赖
|
|
if: steps.test-info.outputs.skip_python_setup == 'false'
|
|
run: |
|
|
echo "📦 安装Python依赖 (Python ${{ matrix.python-version }})"
|
|
python -m pip install --upgrade pip setuptools wheel
|
|
|
|
# 安装基础依赖
|
|
pip install -r requirements.txt
|
|
|
|
# 根据测试类型安装额外依赖
|
|
if [ "${{ matrix.coverage }}" = "true" ]; then
|
|
echo "📊 安装覆盖率工具"
|
|
pip install coverage[toml]
|
|
fi
|
|
|
|
# 验证关键依赖
|
|
echo "🔍 验证关键依赖安装"
|
|
python -c "import django; print(f'Django version: {django.get_version()}')"
|
|
python -c "import MySQLdb; print('MySQL client: OK')" || python -c "import pymysql; print('PyMySQL client: OK')"
|
|
|
|
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
|
python -c "import elasticsearch; print('Elasticsearch client: OK')"
|
|
fi
|
|
|
|
# Django环境准备
|
|
- name: 准备Django环境
|
|
if: matrix.test-type != 'docker'
|
|
env:
|
|
DJANGO_MYSQL_PASSWORD: root
|
|
DJANGO_MYSQL_HOST: 127.0.0.1
|
|
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
|
run: |
|
|
echo "🔧 准备Django测试环境"
|
|
|
|
# 等待数据库就绪
|
|
echo "⏳ 等待MySQL数据库启动..."
|
|
for i in {1..30}; do
|
|
if python -c "import MySQLdb; MySQLdb.connect(host='127.0.0.1', user='root', passwd='root', db='djangoblog')" 2>/dev/null; then
|
|
echo "✅ MySQL数据库连接成功"
|
|
break
|
|
fi
|
|
echo "🔄 等待数据库启动... ($i/30)"
|
|
sleep 2
|
|
done
|
|
|
|
# 等待Elasticsearch就绪 (如果启用)
|
|
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
|
echo "⏳ 等待Elasticsearch启动..."
|
|
for i in {1..30}; do
|
|
if curl -s http://127.0.0.1:9200/_cluster/health | grep -q '"status":"green"\|"status":"yellow"'; then
|
|
echo "✅ Elasticsearch连接成功"
|
|
break
|
|
fi
|
|
echo "🔄 等待Elasticsearch启动... ($i/30)"
|
|
sleep 2
|
|
done
|
|
fi
|
|
|
|
# Django测试执行
|
|
- name: 执行数据库迁移
|
|
if: matrix.test-type != 'docker'
|
|
env:
|
|
DJANGO_MYSQL_PASSWORD: root
|
|
DJANGO_MYSQL_HOST: 127.0.0.1
|
|
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
|
run: |
|
|
echo "🗄️ 执行数据库迁移"
|
|
|
|
# 检查迁移文件
|
|
echo "📋 检查待应用的迁移..."
|
|
python manage.py showmigrations
|
|
|
|
# 检查是否有未创建的迁移
|
|
python manage.py makemigrations --check --verbosity 2
|
|
|
|
# 执行迁移
|
|
python manage.py migrate --verbosity 2
|
|
|
|
echo "✅ 数据库迁移完成"
|
|
|
|
- name: 运行Django测试
|
|
if: matrix.test-type != 'docker'
|
|
env:
|
|
DJANGO_MYSQL_PASSWORD: root
|
|
DJANGO_MYSQL_HOST: 127.0.0.1
|
|
DJANGO_ELASTICSEARCH_HOST: ${{ matrix.elasticsearch && '127.0.0.1:9200' || '' }}
|
|
run: |
|
|
echo "🧪 开始执行 ${{ matrix.test-type }} 测试 (Python ${{ matrix.python-version }})"
|
|
|
|
# 显示Django配置信息
|
|
python manage.py diffsettings | head -20
|
|
|
|
# 运行测试
|
|
if [ "${{ matrix.coverage }}" = "true" ]; then
|
|
echo "📊 运行测试并生成覆盖率报告"
|
|
coverage run --source='.' --omit='*/venv/*,*/migrations/*,*/tests/*,manage.py' manage.py test --verbosity=2
|
|
|
|
echo "📈 生成覆盖率报告"
|
|
coverage xml
|
|
coverage report --show-missing
|
|
coverage html
|
|
|
|
echo "📋 覆盖率统计:"
|
|
coverage report | tail -1
|
|
else
|
|
echo "🧪 运行标准测试"
|
|
python manage.py test --verbosity=2 --failfast
|
|
fi
|
|
|
|
echo "✅ 测试执行完成"
|
|
|
|
# 覆盖率报告上传 (只有完整测试才执行)
|
|
- name: 上传覆盖率到Codecov
|
|
if: matrix.coverage == true && success()
|
|
uses: codecov/codecov-action@v4
|
|
with:
|
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
file: ./coverage.xml
|
|
flags: unittests
|
|
name: codecov-${{ steps.test-info.outputs.test_name }}
|
|
fail_ci_if_error: false
|
|
verbose: true
|
|
|
|
- name: 上传覆盖率到Codecov (备用)
|
|
if: matrix.coverage == true && failure()
|
|
uses: codecov/codecov-action@v4
|
|
with:
|
|
file: ./coverage.xml
|
|
flags: unittests
|
|
name: codecov-${{ steps.test-info.outputs.test_name }}-fallback
|
|
fail_ci_if_error: false
|
|
verbose: true
|
|
|
|
# Docker构建测试
|
|
- name: 设置QEMU
|
|
if: matrix.test-type == 'docker'
|
|
uses: docker/setup-qemu-action@v3
|
|
|
|
- name: 设置Docker Buildx
|
|
if: matrix.test-type == 'docker'
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Docker构建测试
|
|
if: matrix.test-type == 'docker'
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
push: false
|
|
tags: djangoblog/djangoblog:test-${{ github.sha }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
# 收集测试工件 (失败时收集调试信息)
|
|
- name: 收集测试工件
|
|
if: failure() && matrix.test-type != 'docker'
|
|
run: |
|
|
echo "🔍 收集测试失败的调试信息"
|
|
|
|
# 收集Django日志
|
|
if [ -d "logs" ]; then
|
|
echo "📄 Django日志文件:"
|
|
ls -la logs/
|
|
if [ -f "logs/djangoblog.log" ]; then
|
|
echo "🔍 最新日志内容:"
|
|
tail -100 logs/djangoblog.log
|
|
fi
|
|
fi
|
|
|
|
# 显示数据库状态
|
|
echo "🗄️ 数据库连接状态:"
|
|
python -c "
|
|
try:
|
|
from django.db import connection
|
|
cursor = connection.cursor()
|
|
cursor.execute('SELECT VERSION()')
|
|
print(f'MySQL版本: {cursor.fetchone()[0]}')
|
|
cursor.execute('SHOW TABLES')
|
|
tables = cursor.fetchall()
|
|
print(f'数据库表数量: {len(tables)}')
|
|
except Exception as e:
|
|
print(f'数据库连接错误: {e}')
|
|
" || true
|
|
|
|
# Elasticsearch状态 (如果启用)
|
|
if [ "${{ matrix.elasticsearch }}" = "true" ]; then
|
|
echo "🔍 Elasticsearch状态:"
|
|
curl -s http://127.0.0.1:9200/_cluster/health?pretty || true
|
|
fi
|
|
|
|
# 上传测试工件
|
|
- name: 上传覆盖率HTML报告
|
|
if: matrix.coverage == true && always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: coverage-report-${{ steps.test-info.outputs.test_name }}
|
|
path: htmlcov/
|
|
retention-days: 30
|
|
|
|
# 性能统计
|
|
- name: 测试性能统计
|
|
if: always() && matrix.test-type != 'docker'
|
|
run: |
|
|
echo "⚡ 测试性能统计:"
|
|
echo " 开始时间: $(date -d '@${{ job.started_at }}' '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '未知')"
|
|
echo " 当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# 系统资源使用情况
|
|
echo "💻 系统资源:"
|
|
echo " CPU使用: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)%"
|
|
echo " 内存使用: $(free -h | awk '/^Mem:/ {printf "%.1f%%", $3/$2 * 100}')"
|
|
echo " 磁盘使用: $(df -h / | awk 'NR==2{printf "%s", $5}')"
|
|
|
|
# 测试结果汇总
|
|
- name: 测试完成总结
|
|
if: always()
|
|
run: |
|
|
echo "📋 ============ 测试执行总结 ============"
|
|
echo " 🏷️ 测试类型: ${{ matrix.test-type }}"
|
|
echo " 🐍 Python版本: ${{ matrix.python-version }}"
|
|
echo " 🗄️ 数据库: ${{ matrix.database }}"
|
|
echo " 🔍 Elasticsearch: ${{ matrix.elasticsearch }}"
|
|
echo " 📊 覆盖率: ${{ matrix.coverage }}"
|
|
echo " ⚡ 状态: ${{ job.status }}"
|
|
echo " 📅 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "============================================"
|
|
|
|
# 根据测试结果显示不同消息
|
|
if [ "${{ job.status }}" = "success" ]; then
|
|
echo "🎉 测试执行成功!"
|
|
else
|
|
echo "❌ 测试执行失败,请检查上面的日志"
|
|
fi
|