Merge pull request #790 from liangliangyy/dev #561
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |