¿Cómo convencer a un diseñador de juegos para que realice pruebas?

Supongo que para nadie es un secreto que hay muchos especialistas involucrados en el desarrollo de juegos, y no solo programadores. El lanzamiento de un juego es imposible sin artistas, modeladores, artistas de efectos visuales y, por supuesto, diseñadores de juegos. Por cierto sobre este último. Los amamos mucho, pero a menudo rompen recursos. No es que quieran hacerlo, pero debido a la naturaleza del trabajo, necesitan hacer muchas ediciones pequeñas, y la posibilidad de equivocarse es mayor. Y después de todo, muchos errores son errores tipográficos triviales, una línea incompleta o, a la inversa, una línea extra eliminada. Todo esto se puede corregir sin salir de la caja. Pero, ¿cómo hacer eso? Escriba las regulaciones que debe ejecutar% my_folder% / scripts / mega_checker antes de comprometerse. Lo comprobamos, no funciona. El hombre es una criatura compleja y olvidadiza. Y quiero comprobar los recursos.



Pero encontramos una salida: ahora es imposible comprometerse con el repositorio sin pruebas. Al menos de forma imperceptible y con impunidad.







Sistema de prueba



Lo primero que necesitamos es un sistema de prueba. Ya lo hemos descrito aquí. Recuerde que necesita el mismo código para ejecutarse en el servidor Ci, y localmente, para que no haya dificultad en mantenerlo. Es deseable que el proyecto pueda establecer varios parámetros para pruebas comunes, o mejor aún, extenderlo con los suyos. Por supuesto, los dulces no salieron de inmediato.



La etapa uno- puedes correr, pero duele. Todavía está claro qué hacer con el código Python, pero con todo tipo de utilidades como CppCheck, Bloaty, optipng, nuestras muletas internas, bicicletas, no. Para funcionar correctamente, necesitamos archivos ejecutables para todas las plataformas en las que trabajan nuestros colegas (mac, windows y linux). En esta etapa, todos los binarios necesarios estaban en el repositorio y la ruta relativa a la carpeta de binarios se indicó en la configuración del sistema de prueba.



<CppCheck bin_folder=”utils/cppcheck”>...</CppCheck>
      
      





Esto plantea varios problemas:



  • desde el lado del proyecto, necesita almacenar archivos innecesarios en el repositorio, ya que son necesarios en la computadora de cada desarrollador. Naturalmente, el repositorio es más grande debido a esto.
  • cuando surge un problema, es difícil entender qué versión tiene el proyecto, si la estructura requerida está en la carpeta.
  • ¿Dónde obtener los binarios necesarios? ¿Compilar usted mismo, descargar en Internet?


Etapa dos : ponemos las cosas en orden en los servicios públicos. Pero, ¿qué pasa si escribe todas las utilidades necesarias y las recopila en un repositorio? La idea es que en el servidor ya estén montadas las utilidades para todas las plataformas necesarias, que también están versionadas. Ya usamos Nexus Sonatype, así que fuimos al siguiente departamento y acordamos los archivos. El resultado es una estructura: 





Para empezar, necesita un script que sepa la dirección secreta donde están los binarios, pueda descargarlos y también ejecutar, dependiendo de la plataforma, con los parámetros pasados.



Omitir las complejidades de la implementación
def get_tools_info(project_tools_xml, available_tools_xml):
    # Parse available tools at first and feel up dictionary
    root = etree.parse(available_tools_xml).getroot()
    tools = {}

    # Parse xml and find current installed version ...
    return tools

def update_tool(tool_info: ToolInfo):
    if tool_info.current_version == tool_info.needed_version:
        return
    if tool_info.needed_version not in tool_info.versions:
        raise RuntimeError(f'Tool "{tool_info.tool_id}" has no version "{tool_info.needed_version}"')
    if os.path.isdir(tool_info.output_folder):
        shutil.rmtree(tool_info.output_folder)
    g_server_interface.download(tool_id=tool_info.tool_id, version=tool_info.needed_version,
                                output_folder=tool_info.output_folder)

def run_tool(tool_info: ToolInfo, tool_args):
    system_name = platform.system().lower()
    tool_bin = tool_info.exe_infos[system_name].executable
    full_path = os.path.join(tool_info.output_folder, tool_bin)
    command = [full_path] + tool_args
    try:
        print(f'Run tool: "{tool_info.tool_id}" with commands: "{" ".join(tool_args)}"')
        output = subprocess.check_output(command)
        print(output)
    except Exception as e:
        print(f'Fail with: {e}')
        return 1
    return 0

def run(project_tools_xml, available_tools_xml, tool_id, tool_args):
    tools = get_tools_info(project_tools_xml=project_tools_xml, available_tools_xml=available_tools_xml)
    update_tool(tools[tool_id])
    return run_tool(tool_info, tool_args)

      
      





En el servidor, agregamos un archivo con una descripción de las utilidades. La dirección de este archivo no ha cambiado, por lo que lo primero que hacemos es ir allí y ver qué tenemos en stock. Omitiendo sutilezas, estos son los nombres de los paquetes y la ruta al archivo ejecutable dentro del paquete para cada plataforma.



xml "en el servidor"
<?xml version='1.0' encoding='utf-8'?>
<Tools>
	<CppCheck>
		<windows executable="cppcheck.exe" />
		<darwin executable="cppcheck" />
		<linux executable="cppcheck" />
	</CppCheck>
</Tools>

      
      







Y en el proyecto, agregue un archivo con una descripción de lo que necesita.



proyecto xml

<?xml version='1.0' encoding='utf-8'?>
<Tools>
	<CppCheck version="1.89" />
</Tools>
      
      





, , , . .



python -m utility_runner --available-source D:\Playrix\![habr]\gd_hooks\available_source.xml --project-tools D:\Playrix\![habr]\gd_hooks\project\project_tools.xml --run-tool CppCheck -- --version

      
      





:



  • , ,
  • , , . .




, — .



- ?



- , , ? -, . — , . : git.



-, — bash-, git: pull push, , git-.



, :



  • pre-commit — . , .
  • prepare-commit-msg — , . , rebase.
  • commit-msg — . , . , .


, , , .git/hooks. — . , ( Windows Mac), . , .



, . , .



. , , git-bash Windows. FAQ.



: , , dns . , curl [ .





. , . , FAQ. , .git/hooks . , :



git rev-parse
git rev-parse --git-path hooks

      
      





, , :



.git/hooks
      
      



Worktree
%repo_abs%/.git/hooks
      
      



submodule
%repo_abs%/.git/modules/hooks
      
      





— . .git/hooks, . . , .git/hooks , .



,   , - . , -. — . , — . :



  1.   pre-commit , . pre-commit-tmp
  2. commit-msg pre-commit pre-commit-tmp


, : , . , .





<spoiler title=« :> : 32- ; , 64-; pip install , . - 32- — .


Pero aún así, ¿cómo lanzar?



Primero, hicimos una instrucción de varias páginas sobre qué croissants son más sabrosos , qué Python instalar. ¿Pero recordamos sobre los diseñadores de juegos y los huevos revueltos? Siempre se ha quemado: Python del bitness incorrecto o 2.7 en lugar de 3.7. Y todo esto también se multiplica por dos plataformas donde trabajan los usuarios: windows y mac. (Los usuarios de Linux con nosotros son gurús y configuran todo ellos mismos, tocando silenciosamente con el sonido de una pandereta, o pasaron el problema)



. Resolvimos el problema de manera radical: recopilamos python de la versión y el bitness requeridos. Y a la pregunta "cómo lo ponemos y dónde guardarlo" respondieron: ¡Nexus! El único problema: todavía no tenemos Python para ejecutar el script de Python que creamos para ejecutar las utilidades del Nexus.



¡Y ahí es donde entra bash! No da tanto miedo, e incluso es bueno cuando te acostumbras a él. Y funciona en todas partes: en Unix todo ya está bien, y en Windows se instala junto con git-bash (este es nuestro único requisito para el sistema local). El algoritmo de instalación es muy sencillo:



  1. Descargue el archivo de Python compilado para la plataforma requerida. La forma más sencilla de hacer esto es a través de curl: está en casi todas partes (incluso en Windows ).



    Descarga python
    mkdir -p "$PYTHON_PRIMARY_DIR"
    	curl "$PYTHON_NEXUS_URL" --output "$PYTHON_PRIMARY_DIR/ci_python.zip" --insecure || exit 1
          
          





  2. Descomprímalo, cree un entorno virtual que se vincule al binario descargado. No repitas nuestros errores: no olvides clavar la versión virtualenv.



    echo "Unzip python..."
    unzip "$PYTHON_PRIMARY_DIR/ci_python.zip" -d "$PYTHON_PRIMARY_DIR" > "unzip.log"
    	rm -f "$PYTHON_PRIMARY_DIR/ci_python.zip"
    
    	echo "Create virtual environment..."
    "$PYTHON_EXECUTABLE" -m pip install virtualenv==16.7.9 --disable-pip-version-check --no-warn-script-location
          
          



  3. Si necesita bibliotecas de lib / *, debe copiarlas usted mismo. virtualenv no piensa en eso.
  4. Instale todos los paquetes necesarios. Aquí acordamos con los proyectos que tendrán un archivo ci / required.txt, que contendrá todas las dependencias en formato pip .


Instalación de dependencias
OUT_FILE="$VENV_DIR/pip_log.txt"
"$PYTHON_VENV_EXECUTABLE" -m pip install -r "$REQUIRED_FILE" >> "$OUT_FILE" 2>&1
result=$?
if [[ "$result" != "0" ]]; then
	var2=$(grep ERROR "$OUT_FILE")
	echo "$(tput setaf 3)" "$var2" "$(tput sgr 0)"
	echo -e "\e[1;31m" "Error while installing requirements. More details in: $OUT_FILE" "\e[0m"
	result=$ERR_PIP
fi
exit $result

      
      





Ejemplo de Required.txt
pywin32==225;sys_platform == "win32"
cryptography==3.0.0
google-api-python-client==1.7.11

      
      





Cuando abordan un problema, generalmente adjuntan una captura de pantalla de la consola donde se mostraron los errores. Para facilitar nuestro trabajo, no solo almacenamos el resultado de la última ejecución de instalación de pip , sino que también agregamos colores a la vida, mostrando errores de color desde el registro directamente a la consola. ¡Viva grep!



Cómo se ve




A primera vista, puede parecer que no necesitamos un entorno virtual. Después de todo, ya hemos descargado un binario separado en un directorio separado. Incluso si hay varias carpetas donde se implementa nuestro sistema, los binarios siguen siendo diferentes. ¡Pero! Virtualenv tiene un script de activación que hace que se pueda llamar a Python como si estuviera en el entorno global. Esto aísla la ejecución de scripts y facilita su lanzamiento.



Imagínese: necesita ejecutar un archivo por lotes desde el cual se ejecuta un script de Python, desde el cual se ejecuta otro script de Python. Este no es un ejemplo ficticio; así es como se ejecutan los eventos posteriores a la compilación al compilar una aplicación. Sin virtualenv, tendría que calcular las rutas necesarias en todas partes sobre la marcha, pero con activesolo usamos Python en todas partes . Más precisamente, vpython : hemos agregado nuestro propio contenedor para facilitar la ejecución tanto desde la consola como desde los scripts. En el shell, comprobamos si ya estamos en el entorno activado o no, si estamos corriendo en TeamCity (donde está nuestro entorno virtual), y al mismo tiempo preparamos el entorno.



vpython.cmd
set CUR_DIR=%~dp0
set "REPO_DIR=%CUR_DIR%\."

rem VIRTUAL_ENV is the variable from activate.bat and is set automatically
rem TEAMCITY - if we are running from agent we need no virtualenv activation
if "%VIRTUAL_ENV%"=="" IF "%TEAMCITY%"=="" (
	set RETURN=if_state
	goto prepare
	:if_state
	if %ERRORLEVEL% neq 0 (
		echo [31m Error while prepare environment. Run ci\PrepareAll.cmd via command line [0m
		exit /b 1
	)
	call "%REPO_DIR%\.venv\Scripts\activate.bat"
	rem special variable to check if venv activated from this script
	set VENV_FROM_CURRENT=true
)

rem Run simple python and forward args to it
python %*

SET result=%errorlevel%

if "%VENV_FROM_CURRENT%"=="true" (
	call "%REPO_DIR%\.venv\Scripts\deactivate.bat"
	set CI_VENV_RUN=
	set VENV_FROM_CURRENT=
)

:eof
exit /b %result%

:prepare
setlocal
set RUN_FROM_SCRIPT=true
call "%REPO_DIR%\ci\PrepareEnvironment.cmd" > NUL
endlocal
goto %RETURN%

      
      







Tanakan, o no te olvides de poner pruebas



Hemos resuelto el problema del olvido para ejecutar pruebas, pero incluso un script puede pasarse por alto. Por eso, hicieron una pastilla para el olvido. Tiene dos partes.



Cuando nuestro sistema se inicia, modifica el comentario de confirmación y lo marca como "aprobado". Como etiqueta, decidimos no filosofar y agregar [+] o [-] al final del comentario al compromiso.



Se está ejecutando un script en el servidor que analiza los mensajes y, si no encuentra el codiciado conjunto de caracteres, crea una tarea para el autor. Esta es la solución más simple y elegante. Los caracteres no imprimibles no son obvios. Para ejecutar enlaces de servidor, necesita un plan de tarifas diferente en GitHub, y nadie comprará premium para una función. Repasar el historial de confirmaciones, buscar un símbolo y establecer una tarea es obvio y no tan caro.



Sí, puedes poner un símbolo con tus propios bolígrafos, pero ¿estás seguro de que no romperás el ensamblaje en el servidor? Y si lo rompes ... sí, el calvo de Homescapes ya te está siguiendo.



¿Cuál es el resultado final?



Es bastante difícil rastrear la cantidad de errores que han encontrado los ganchos, no llegan al servidor. Solo hay una opinión subjetiva de que hay muchos más conjuntos verdes. Sin embargo, también hay un lado negativo: el compromiso comenzó a tomar bastante tiempo. En algunos casos, puede llevar hasta 10 minutos, pero esa es una historia aparte sobre la optimización.



All Articles