Skip to content
Closed

Test #101

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions .github/workflows/CyclomaticComplexity.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
name: Check Complexity PR
name: Python Complexity Check

on:
push:
pull_request:
types: [opened, synchronize, reopened]

jobs:
check-complexity:
complexity-analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout del código
uses: actions/checkout@v4
with:
fetch-depth: 0 # Obligatorio para descargar el historial de Git y poder comparar ramas
fetch-depth: 0 # Necesario para comparar ramas y detectar archivos cambiados

- name: Configurar Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Analizar solo archivos modificados
- name: Ejecutar análisis de complejidad
run: |
RAMA_DESTINO="origin/${{ github.base_ref }}"

ARCHIVOS=$(git diff --name-only --diff-filter=AM $RAMA_DESTINO HEAD | grep '\.py$' || true)
ARCHIVOS=$(git diff --name-only --diff-filter=AM origin/${{ github.base_ref }} HEAD | grep '\.py$' || true)

if [ -z "$ARCHIVOS" ]; then
echo "No hay archivos Python modificados en esta Pull Request."
echo "No se encontraron archivos Python modificados en esta Pull Request."
exit 0
fi
echo "Archivos detectados para analizar:"

echo "Analizando los siguientes archivos:"
echo "$ARCHIVOS"

python CyclomaticMetric.py $ARCHIVOS
python mi_complejidad.py $ARCHIVOS > resultados_complejidad.json

cat resultados_complejidad.json
111 changes: 83 additions & 28 deletions CyclomaticMetric.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import sys, ast
import sys
import ast
import json

class AnalizadorComplejidad(ast.NodeVisitor):
def __init__(self):
Expand Down Expand Up @@ -46,7 +48,6 @@ def visit_GeneratorExp(self, node):
self.generic_visit(node)

def calcular(codigo_fuente: str) -> int:
"""Parsea el código y devuelve su complejidad ciclomática."""
try:
arbol = ast.parse(codigo_fuente)
except SyntaxError:
Expand All @@ -56,37 +57,91 @@ def calcular(codigo_fuente: str) -> int:
visitante.visit(arbol)
return visitante.complejidad

def analizar_proyecto(ruta_directorio: str, limite_complejidad: int = 15):
exito = True
DIRECTORIOS_IGNORADOS = {'venv', 'env', '.venv', 'migrations', '__pycache__', '.git', 'tests'}
ARCHIVOS_IGNORADOS = {'manage.py', 'settings.py', 'wsgi.py', 'asgi.py'}

def es_archivo_valido(ruta):
nombre = os.path.basename(ruta)

if nombre in ARCHIVOS_IGNORADOS or not nombre.endswith('.py'):
return False

partes_ruta = ruta.split(os.sep)
for ignorado in DIRECTORIOS_IGNORADOS:
if ignorado in partes_ruta:
return False

return True

def analizar_archivos(rutas_archivos: list, limite_complejidad: int = 15):
resultados_array = []
archivos_procesados = 0
archivos_fallidos = 0

for raiz, _, archivos in os.walk(ruta_directorio):
if 'migrations' in raiz or 'venv' in raiz or '__pycache__' in raiz or '.git' in raiz:
for ruta in rutas_archivos:
if not os.path.exists(ruta):
continue

if not es_archivo_valido(ruta):
continue

for archivo in archivos:
if '__init__' in archivo:
continue
if archivo.endswith('.py'):
ruta_completa = os.path.join(raiz, archivo)

with open(ruta_completa, 'r', encoding='utf-8') as f:
contenido = f.read()

complejidad = calcular(contenido)

if complejidad > limite_complejidad:
print(f"❌ PELIGRO: {ruta_completa} tiene complejidad {complejidad}")
exito = False
elif complejidad >= limite_complejidad - 5:
print(f"⚠️ AVISO: {ruta_completa} tiene complejidad {complejidad}")
else:
# Añade esto para ver el escaneo completo
print(f"✅ OK: {ruta_completa} (Complejidad: {complejidad})")
if not exito:
with open(ruta, 'r', encoding='utf-8') as f:
contenido = f.read()

complejidad = calcular(contenido)
archivos_procesados += 1

# Asignar el status_code que solicitaste
if complejidad > limite_complejidad:
status_code = "DANGER"
archivos_fallidos += 1
elif complejidad >= limite_complejidad - 5:
status_code = "WARN"
else:
status_code = "OK"

resultados_array.append({
"file": ruta,
"complexity": complejidad,
"status_code": status_code
})

# Construcción del diccionario final con la estructura requerida
salida_json = {
"analysis_type": "Cyclomatic Complexity",
"threshold": limite_complejidad,
"summary": {
"total_files": archivos_procesados,
"failed_files": archivos_fallidos
},
"results": resultados_array
}

# Imprimir el JSON formateado con indentación
print(json.dumps(salida_json, indent=4))

# Mantener el código de salida del sistema para que falle en el CI si es necesario
if archivos_fallidos > 0:
sys.exit(1)
else:
print("✅ Análisis completado. El código cumple con los estándares.")
sys.exit(0)

if __name__ == "__main__":
analizar_proyecto('./')
archivos_a_analizar = sys.argv[1:]
limite = 15

if not archivos_a_analizar:
# Estructura vacía consistente si no hay archivos
salida_vacia = {
"analysis_type": "Cyclomatic Complexity",
"threshold": limite,
"summary": {
"total_files": 0,
"failed_files": 0
},
"results": []
}
print(json.dumps(salida_vacia, indent=4))
sys.exit(0)

analizar_archivos(archivos_a_analizar, limite)
77 changes: 77 additions & 0 deletions web_app/prueba.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# views_prueba.py

def funcion_simple(request):
"""
Complejidad esperada: 2
Comportamiento: Silencio. Pasa la validación sin imprimir nada.
Desglose: 1 (base) + 1 (if) = 2.
"""
if request.method == 'POST':
return "Guardado"
return "Formulario"


def funcion_mediana(usuario):
"""
Complejidad esperada: 11
Comportamiento: Advertencia (⚠️ AVISO). Está en el rango de (Límite - 5).
Desglose:
1 (base) + 1(if) + 1(if) + 1(elif) + 1(for) + 1(if) + 1(elif) +
1(elif) + 1(and) + 1(except) + 1(if) = 11.
"""
nivel = 0
if usuario.is_authenticated:
if usuario.is_staff:
nivel = 1
elif usuario.is_superuser:
nivel = 2

for permiso in usuario.permisos:
if permiso == 'leer':
pass
elif permiso == 'escribir':
pass
elif permiso == 'borrar' and usuario.is_admin:
pass

try:
procesar(usuario)
except Exception:
if usuario.reintentos > 0:
pass

return nivel


def funcion_compleja(request):
"""
Complejidad esperada: 17
Comportamiento: Fallo (❌ FALLO). Supera el límite de 15. Rompe la Pull Request.
Desglose:
1 (base) + 1(if) + 1(if) + 1(if) + 1(and) + 1(for) + 1(if) + 1(elif) +
1(elif) + 1(or) + 1(elif) + 1(if) + 1(elif) + 1(elif) + 1(for) +
1(if) + 1(if) = 17.
"""
if request.method == 'GET':
if request.GET.get('filtro1'):
if request.GET.get('filtro2') and request.GET.get('filtro3'):
for item in request.items:
if item.valido:
pass
elif item.pendiente:
pass
elif item.rechazado or item.expirado:
pass
elif request.GET.get('orden'):
if request.GET['orden'] == 'asc':
pass
elif request.GET['orden'] == 'desc':
pass
elif request.method == 'POST':
for x in range(10):
if x == 5:
continue
if x == 9:
break

return "Fin"
Loading