25 diciembre 2014

Exportando e importando un virtualenv

Una cosa que he aprendido recientemente de los entornos virtualenv es que facilitan la exportación de proyectos.

Cuando le das un proyecto a un amigo o colaborador, dentro de un archivo comprimido, tienes que decirle qué dependencias instalar para ejecutar el proyecto. Afortunadamente virtualenv (bueno, en realidad pip) te da una manera automatizada de hacerlo.

Supón que tenemos una carpeta de proyecto que quieres exportar y supón que has hecho un virtualenv para ese proyecto. Con tu virtualenv activado, ejecuta:

(env)dante@Camelot:~/project-directory$ pip freeze > requirements.txt


Esto creará un  fichero llamado requirements.txt. En ese fichero, pip colocará todos los nombres de los paquetes instalados en el virtualenv, junto con sus versiones. Ese fichero generado (en nuestro caso requirements.txt) debería se incluido en el paquete que exportemos.

Para importar un proyecto exportado de esta manera, el importador debería descomprimir la carpeta de proyecto y crear un virtualenv en esa localización. Con ese virtualenv activado, pip debería ejecutarse de esta manera:

(env)otherguy@host:~/project-directory$ pip install -r requirements.txt

Esta llamada a pip instalará todos los paquetes y versiones incluidos en el fichero requierements.txt. Fácil y eficiente.

16 noviembre 2014

Visual Studio Community

Grandes noticias para los desarrolladores. De acuerdo con los nuevos rumbos iniciados por  Satya Nadella, Microsoft ha anunció el pasado 12 de Noviembre que la nueva versión de Visual Studio iba a ser lanzada con una licencia bastante abierta que va apermitir su uso gratuito por  desarrolladores individuales y grupos, tanto con fines comerciales como no comerciales.

Esta nueva versión se llama Visual Studio Community y saldrá con la cuarta actualización de Visual Studio 2013. A diferencia de la edición Visual Studio Express, la versión Community promete todas las carácterísticas de Visual Studio de manera gratuita, destacando especialmente el soporte multiplataforma y de plugins. Con la edición Express no se podía tener acceso a los más de 5.000 plugins disponibles para Visual Studio. Sin embargo, la versión Community cambia eso y permitirá la instalación de todos los plugins que deseemos. Además, la versión Express se enfocaba a plataformas específicas (Web, PC, etc). La versión Community, siguiendo las nuevas directrices estratégicas de Microsoft hacia la convergencia de dispositivos, unifica todo eso y nos permitirá desarrollar para múltiples plataformas.

Con todas estas características, ¿por qué usar Visual Studio Express?, la verdad es que no lo se . Lo que se dice es que la versión Community supone el fin de la Express, pero Microsoft no lo ha confirmado hasta ahora.

De acuerdo con su licencia, se puede descargar Visual Studio Community de manera gratuita tanto para desarrollo en solitario como en grupo, tanto con fines comerciales como no comerciales, siempre y cuando los grupos estén por debajo de los 5 desarrolladores y no estén trabajando para una empresa (entendiendo como "empresa" a aquella organización con 250 PC o 1 millon de dólares anuales de ingresos). En caso de caer en la categoría de "empresa" sólo se puede utilizar la versión Community con fines educativos y open source.

Con sus herramientas fuertemente integradas (diseñadores, debugger, editores y profilers) y su soporte para múltiples lenguajes como C#, Visual Basic, Visual C++, Javascript, HTML5 y (lo mejor de todo) Python, Visual Studio es una gran opción para desarrollar en el ecositema Windows. La verdad es que estoy bastante contento con PyCharm y su soporte para ejecutarse en Windows y Linux, pero supongo que le daré una oportunidad a Visual Studio la próxima vez que trabaje con .NET y IronPython.

18 octubre 2014

Entornos virtuales para desarrollos reales, jugando con virtualenv

No se en vuestro caso, pero yo suelo desarrollar varios proyectos al mismo tiempo. El problema es que aveces necesitas librerías para un proyecto que entran en conflicto con las de otro. Por eso eso es por lo que virtualenv existe en Python.

Gracias a virtualenv puedes mantener todas las versiones de python, librerías, paquetes y dependencias de un projecto aisladas de las de otro. Virtualenv hace esto creando para tu proyecto una copia de python aislada y separada de las del resto de tus proyectos. Esto es especialmente útil también si tienes que desarrollar e instalar librerías de python en un sistema linux para que el que no tienes permisos de root/sudo con los que instalar cosas a nivel de sistema operativo.

Usar virtualenv como parte habitual de tus herramientas de desarrollo te ahorrará quebraderos de cabeza futuros.

El primer paso a la hora de usar virtualenv es asegurarse de qué versión de python vamos a usar para nuestro proyecto. La herramienta virtualenv original no funciona correctamente en Python 3, por lo que si quieres usarlo te tendrás que ceñir a Python 2.7. Afortunadamente, desde su versión 3.3, Python incluye su propia versión de virtualenv con el nombre de venv. La pega es que la versión de venv que viene con Python 3.3 no soporta el uso de pip dentro del entorno virtual, aunque afortunadamente dicho soporte de añadió en Python 3.4. Vamos a cubrir ambos en este artículo: primero virtualenv y luego venv.

Se puede instalar virtualenv usando el gestor de paquetes nativo del sistema operativo o el gestor de paquetes pip de python. En Ubuntu, usando el gestor de paquetes del sistema sería:
dante@Camelot:~$ sudo aptitude install python-virtualenv

Usando pip bastaría con:
dante@Camelot:~$ pip install virtualenv

Se puede conprobar qué versión se ha instalado con:
dante@Camelot:~$ virtualenv --version

Para usarlo, sólo que hay que ir con la consola al directorio de tu proyecto y ejecutar el comando siguiente:
dante@Camelot:~/project-directory$ virtualenv --no-site-packages env
New python executable in env/bin/python Installing setuptools, pip...done. dante@Camelot:~/project-directory$

Ese comando creará un direcotio llamado venv donde se pondrán los ejecutables necesarios para ejecutar el entorno virtual. La opción --no-site-packages aisla del todo tu entorno de trabajo del resto del sistema al no incluir en él los paquetes y módulos ya instalados en el sistema. De esa manera, se puede tener un entorno aislado, libre de cualquier paquete previamente instalado.

Antes de empezar a trabajar hay que arrancar el entorno virtual ejecutando el script de activación:
dante@Camelot:~/project-directory$ source env/bin/activate (env)dante@Camelot:~/project-directory$

Se puede saber que se está trabajando en un entorno virtual gracias a que aparece el nombre del directorio del entorno, rodeado por paréntesis, a la izquierda de la ruta en la línea de comandos. Mientras permanezcas en el entorno virtual, todos los paquetes que instales con pip serán almacenados en la instancia virtual de python dejando tranquilo al python de tu sistema operativo.

Para salir del entorno virtual sólo hay que teclear deactivate:
(env)dante@Camelot:~/project-directory$ deactivate dante@Camelot:~/project-directory$

Deberías crear un entorno virtual cada vez que comiences un nuevo proyecto.

Usar venv en Python 3.4 no es tan diferente. Sin embargo, hay que tener en cuenta que Ubuntu 14.04 viene con una versión estropeada de venv. Si intentases crear un entorno virtual con venv en Ubuntu 14.04, nos saldría un error como este:
dante@Camelot:~/project-directory2$ pyvenv-3.4 env Error: Command '['/home/dante/project-directory2/env/bin/python3.4', '-Im', 'ensurepip', '--upgrade', '--default-pip']' returned non-zero exit status 1 dante@Camelot:~/project-directory2$

La única manera que que he encontrado de solucionar este problema es actualizar Ubuntu a la versión 14.10:
dante@Camelot:~/project-directory2$ sudo do-release-upgrade -d

El sistema tardará unas cuantas horas en actualizarse a la versión 14.10. En ese punto puede ser que haya que instalar venv a través del gestor de paquetes:
dante@Camelot:~/project-directory2$ cat /etc/issue Ubuntu 14.10 \n \l dante@Camelot:~/project-directory2$ pyvenv-3.4 env El programa «pyvenv-3.4» no está instalado. Puede instalarlo escribiendo: sudo apt-get install python3.4-venv dante@Camelot:~/project-directory2$ sudo aptitude install python3.4-venv Se instalarán los siguiente paquetes NUEVOS: python3.4-venv 0 paquetes actualizados, 1 nuevos instalados, 0 para eliminar y 0 sin actualizar. Necesito descargar 1.438 kB de archivos. Después de desempaquetar se usarán 1.603 kB. Des: 1 http://es.archive.ubuntu.com/ubuntu/ utopic/universe python3.4-venv amd64 3.4.2-1 [1.438 kB] Descargados 1.438 kB en 0seg. (1.717 kB/s) Seleccionando el paquete python3.4-venv previamente no seleccionado. (Leyendo la base de datos ... 237114 ficheros o directorios instalados actualmente.) Preparing to unpack .../python3.4-venv_3.4.2-1_amd64.deb ... Unpacking python3.4-venv (3.4.2-1) ... Processing triggers for man-db (2.7.0.2-2) ... Configurando python3.4-venv (3.4.2-1) ... dante@Camelot:~/project-directory2$ pyvenv-3.4 env

De esa manera venv se ejecutaría sin errores:
dante@Camelot:~/project-directory2$ source env/bin/activate (env)dante@Camelot:~/project-directory2$

Una vez en tu entorno virtual puedes instalar todos lo paquetes que necesites para tu desarrollo usando pip, y sin ensuciar las librerías de tu sistema operativo. Las distribuciones modernas de Linix hacen un uso intensivo de aplicaciones de python. Por eso es una buena práctica mantener limpias las librerías de python del sistema operativo, con sólo lo que realmente necesite tu linux, y cacharrear con las librerías específicas de tus desarrollos a través de sus respectivos entornos virtuales. Por ejemplo, podrías usar un entorno virtual si necesitases desarrollar en Python 3 en un sistema operativo cuyo intérprete de python por defecto fuese de la versión 2.7:
dante@Camelot:~/project-directory2$ source env/bin/activate (env) dante@Camelot:~/project-directory2$ python Python 3.4.2 (default, Oct 8 2014, 13:08:17) [GCC 4.9.1] on linux Type "help", "copyright", "credits" or "license" for more information. >>> exit() (env) dante@Camelot:~/project-directory2$ deactivate dante@Camelot:~/project-directory2$ python Python 2.7.8 (default, Oct 8 2014, 06:57:53) [GCC 4.9.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>

No te cortes con los entornos virtuales. Puedes usarlos en entornos de producción, de hecho son lo que se recomienda en realidad para situaciones de producción, en las que no quieres que una actualización de las librerías del sistema operativo rompa las dependencias de una aplicación desarrollada y en funcionamiento.

23 febrero 2014

Violent Python

Python es ampliamente usado en muchos campos, incluyendo las matemáticas, la física, la ingeniería, el scripting, la programación web y, por supuesto, la seguridad. Su capacidad para ejercer de unión entre tantas herramientas y lenguajes de programación hace de python la opción perfecta para el pentesting.

"Violent Python" araña la superficie del uso de python en el mundo de la programación de herramientas de seguridad. Es un libro correcto, en realidad un recetario correcto. Correcto porque aunque los programas de ejemplo son cortos y sencillos, muestran a python en acción en muchos campos de la seguridad: geolocalización, ofuscación, desarrollo de exploits, análisis de red, creación de tráfico de red, web scrapping y un largo etcétera.

El problema es que el libro es sólo correcto porque los programas de ejemplo nos son muy pythonicos. Aunque el código es simple y claro, python ofrece maneras más elegantes de hacer esas cosas. Además, los programas de ejemplo carecen de ambición y no van más allá de meras curiosidades. En mi opinión, los ejemplos podrían haber sido más espectaculares y se podrían haber cubierto más campos de la de la seguridad.

No lamento haber comprado "Violent Python", pero puede ser que este un poco decepcionado porque el libro está más dirigido a gente que se encuentra en un punto más inicial que yo en el viaje hacia la ingeniería de seguridad. Para esa gente este libro es una aproximación directa y divertida al desarrollo de herramientas de seguridad.

21 febrero 2014

Probando tu código con unittest

Cuando programas aplicaciones pequeñas, el ciclo de desarrollos suele ser "escribir código->probar manualmente->escribir código->probar manualmente". El problema con este método es que conforme el proyecto crece en complejidad hay que emplear más y más tiempo en probarlo para asegurar que los últimos cambios no han tenido efectos colaterales en parte alguna de la aplicación. Es habitual olvidarse de probar cosas o pensar que siguen bien después de los últimos cambios, sólo para encontrarse con que una parte de la aplicación que probaste al principio y que funcionaba ahora no lo hace por culpa de un cambio que hiciste hace varios ciclos sin que te dieses cuenta del problema.

En realidad, las pruebas manuales suelen ser ineficientes y es fácil cometer errores con ellas, por eso cuando el proyecto se vuelva complejo, deberíamos automatizar nuestras pruebas. Una de las librerías más usadas para automatizar pruebas es unittest, presente en python desde su versión 2.1. Esta librería te permite preparar pequeños scripts para probar el comportamiento de los componentes de nuestro programa.

Si quieres usar unitest para probar tu código yo seguiría la metodología TDD. Esta metodología te test cases), que son scripts para probar una sección concreta de tu código. Estos test cases son muy útiles para forzarnos a definir los interfaces y el comportamiento deseados de las funciones a desarrollar. Una vez definidos las pruebas, y sólo entonces, se puede escribir el código manteniendo en mente que el objetivo es superar las pruebas. Cuando el código está finalizado es cuando se le somete a las pruebas, si las supera se puede acometer el siguiente ciclo de desarrollo (definir pruebas, escribir código, ejecutar código), si el código fracasa en las pruebas se resuelve el fallo y se vuelve a intentar hasta que supere el test.
hace escribir primero tus paquetes de prueba (en adelante

Se que a primera vista este método parece innecesariamente complejo. Lo que un desarrollador quiere es escribir código para su aplicación no emplear tiempo con el código de las pruebas. Esa es la razón por la que muchos desarrolladores odian esta técnica. Pero tras darle una oportunidad lo habitual es acabar adorando TDD por la confianza que aporta sobre el código. Una vez que tu test está definido lo único que necesitas es ejecutarlos tras un cambio de código para asegurar que el cambio no haya introducido ningún error en un remoto lugar de tu código. Además si trabajas en un proyecto con colaboradores, las pruebas son una manera estupenda de asegurar que una aporte de código realmente funciona.

Podemos probar lo que queramos de nuestra aplicación: sus módulos, sus funciones y clase, su GUI, etc. Por ejemplo, si estuviéramos probando una aplicación web podríamos combinar unittest con Selenium para simular un navegador usando la web, mientras que si estuviéramos probando un GUI basado en QT deberíamos usar QTest.

Cuando trabajemos con unittest deberíamos tener claro que nuestro instrumento principal serán los test cases. Un test case debería enfocarse a probar un único escenario. In python un test case es una clase que hereda de unittest.TestCase. Los test case tienen esta estructura general:

    import unittest

    class TestPartOfCode(unittest.TestCase):

 def setUp(self):
     <test initialization>

 def test_something(self):
     <code to test something>
     self.assert... # All the asserts you need to be sure correctness condition is found.
     
 def test_something_else(self):
     <code to test something>
     self.assert... # All the asserts you need to be sure correctness condition is found.

 def tearDown(self):
     <test shutdown>


Puedes hacer que que un test case se ejecute por si mismo añadiendo al final:

if __name__ == '__main__':
    unittest.main()

Si no se hace la anterior no queda otra más que llamar al test case externamente.

Cuando se ejecuta unittest, este busca las subclasses de unittest.Testcase y ejecuta todos los métodos de esas subclasses cuyo nombre empiece por "test_". Hay métodos especiales como setUp() y tearDown(): setUp() se ejecuta antes de cada prueba para preparar su contexto, mientras que tearDown() se ejecuta después para desmontar dicho contexto.

Normalmente no se tiene un único test case sino varios para probar cada característica de tu programa. Hay muchas aproximaciones, en aplicaciones con GUI se puede tener un test case por ventana en los que sus métodos chequearían cada control de esa ventana. Un truco válido sería agrupar en un test case todas las pruebas que compartan la misma lógica de setUp() y tearDown().

Por eso lo normal es tener muchos test cases, por lo que es más eficiente cargarlo externamiento y ejecutarlos de manera automatizada. Creo que es una buena práctica mantener los test en una carpeta diferente del código, por ejemplo en una carpeta "tests" que se encuentre dentro de la de proyecto. Suelo incluir un fichero vacío "__init__.py" dentro de ese directorio para convertirlo en un paquete de python. Supongamos que ese es nuestro caso, para cargar y ejecutar los test cases necesitamos un script para descubrirlos (suelo llamarlo "run_tests.py"):

    import unittest

    def run_functional_tests(pattern=None):
       print("Running tests...")
       if pattern is None:
           tests = unittest.defaultTestLoader.discover("tests")
       else:
           pattern_with_globs = "%s" % (pattern,)
           tests = unittest.defaultTestLoader.discover("tests", pattern=pattern_with_globs)
       runner = unittest.TextTestRunner()
       runner.run(tests)

    if __name__ == "__main__":
       if len(sys.argv) == 1:
       run_functional_tests()
    else:
       run_functional_tests(pattern=sys.argv[1])

Este script se sitúa habitualmente en la carpeta raíz del proyecto, al mismo nivel que la carpeta de tests. Si se le llama sin argumentos se limita a entrar en el directorio de tests y cargar todos los tests cases que descubra en fichero python cuyo nombre comience por la cadena "tests". Si se ejecuta el script con un argumento, lo usa como una especie de filtro, para cargar sólo aquellos test cases localizados en fichero de python cuyo nombre comiencen con la palabra pasada como argumento. De esta manera se puede ejecutar sólo un subconjunto de los tests cases.

Con unittest se pueden probar aplicaciones web y de consola, e incluso las de GUI. Las últimas son más difíciles de probar porque acceder a los widgets de GUI depende de cada implementación de los mismos y de las herramientas que facilite cada implementación para ello. Por ejemplo, los creadores de QT ofrecen el módulo QTests para ser usado con Unittest. Este módulo permite simular pulsaciones tanto de teclado como de ratón.

Por eso podríamos utilizar tanto una aplicación web como de consola para explicar cómo usar unittest, pero como los tutoriales de cómo usar QTest con pyQT son bastante escasos voy a contribuir haciendo uno aquí., esa es la razón por la que en este artículo voy a desarrollar tests cases para probar una aplicación GUI de pyQT. Como base del ejemplo vamos a usar el código fuente de pyQTMake. Lo mejor es que te descargues todo el código fuente  usando Mercurial tal y como expliqué en uno de mis artículos anteriores. Para clonar el código fuente y situarlo en la versión que vamos a usar hay que teclear lo siguiente en la consola de Ubuntu:
dante@Camelot:~$ hg clone https://borjalopezm@bitbucket.org/borjalopezm/pyqtmake/ example requesting all changes adding changesets adding manifests adding file changes added 10 changesets with 120 changes to 74 files updating to branch default 67 files updated, 0 files merged, 0 files removed, 0 files unresolved dante@Camelot:~/Desarrollos$ cd example dante@Camelot:~/Desarrollos/example$ hg update 9 0 files updated, 0 files merged, 0 files removed, 0 files unresolved dante@Camelot:~/Desarrollos/example$

Ok, ahora que ya tenemos el código fuente vamos a analizar el código del fichero pyqtmake.py. Fíjate en la función "connections":

def connections(MainWin):
    ## TODO: This signals are connected using old way. I must change it to new way
    MainWin.connect(MainWin.ui.action_About,  SIGNAL("triggered()"),  MainWin.onAboutAction)
    MainWin.connect(MainWin.ui.actionLanguajes,  SIGNAL("triggered()"),  MainWin.onLanguagesAction)
    MainWin.connect(MainWin.ui.actionOpen,  SIGNAL("triggered()"),  MainWin.onOpenAction)
    MainWin.connect(MainWin.ui.actionPaths_to_compilers,  SIGNAL("triggered()"),  MainWin.onPathsToCompilersAction)
    MainWin.connect(MainWin.ui.actionPyQTmake_Help,  SIGNAL("triggered()"),  MainWin.onHelpAction)
    MainWin.connect(MainWin.ui.actionQuit,  SIGNAL("triggered()"),  MainWin.close)
    MainWin.connect(MainWin.ui.actionSave,  SIGNAL("triggered()"),  MainWin.onSaveAction)
    MainWin.connect(MainWin.ui.actionSave_as,  SIGNAL("triggered()"),  MainWin.onSaveAsAction)
    return MainWin

Parece que este segmento de código se puede mejorar para que use el nuevo estilo de conexión de señales de pyQT. La cuestión es que no queremos que se rompa nada por nuestras modificaciones por lo que vamos a desarrollar test cases para asegurar que nuestro nuevo código se comporta como el antiguo.

Estas conexiones permiten a MainWin reaccionar a los clicks de ratón en determinados widgets con la correspondiente apertura de ventanas. Nuestro test debería comprobar que estas ventanas se siguen abriendo correctamente tras  los cambios en nuestro código.

El código completo para estos tests se encuentra en el fichero test_main_window.py dentro de la carpeta tests del código fuente de pyQTMake.

Para probar la aplicación nuestro test debe arrancarla primero. Unittest tiene dos métodos para preparar el contexto de nuestras pruebas: setUp() y setUpClass(). El primer método, setUp() se ejecuta antes de cada prueba, mientras que setUpClass() se ejecuta uno única vez cuando se crea el test case al completo.

En este test case en concreto vamos a usar setUp() para crear la aplicación cada vez que queramos probar uno de sus componentes:

    def setUp(self):
        # Initialization
        self.app, self.configuration = run_tests.init_application()
        # Main Window creation.
        self.MainWin = MainWindow()
        # SLOTS
        self.MainWin = pyqtmake.connections(self.MainWin)
        #EXECUTION
        self.MainWin.show()
        QTest.qWaitForWindowShown(self.MainWin)
        # self.app.exec_() # Don't call exec or your qtest commands won't reach
                           # widgets.

QTest.qWaitForWindowShow() para la ejecución hasta que la ventana a la que se espera esté realmente activa. Si no se usa podría ocurrir que nuestro programa avanzase y comenzase a llamar a elementos de una ventana que no está aún activa.

Nuestro primer test va a ser realmente simple:

    def test_on_about_action(self):
        """Push "About" menu option to check if correct window opened."""
        QTest.keyClick(self.MainWin, "h", Qt.AltModifier)
        QTest.keyClick(self.MainWin.ui.menu_Help, 'a', Qt.AltModifier)
        QTest.qWaitForWindowShown(self.MainWin.About_Window)
        self.assertIsInstance(self.MainWin.About_Window, AboutWindow)

QTest.KeyClick() manda la pulsación de una tecla al widget que hayamos señalado. Puede usarse con modificadores de teclado, en este caso Qt.AltModifiers significa que estamos simulando la que se presionando una tecla al mismo tiempo que la tecla Alt. ¿Por qué usamos teclas en este caso? ¿No podemos utilizar QTest para simular una pulsación de ratón?, sí se puede, el problema es que QTest.mouseClick() sólo puede interactuar con widgets mientras que los items de un menú de QT son en realidad actions, por lo que la única manera de llamarlos que he encontrado es usar los atajos de teclado que tengan configurado esos elementos de menú.

El punto clave en una prueba con unittest son las llamadas de tipo "assert...". Esta familia de funciones comprueban que se cumple una condición específica, si es así la prueba se declara exitosa y si no fallida. Hay un tercer estado de salida para una prueba: errónea, pero esto sólo significa que nuestro test no funcionó como esperábamos, fallando en algún punto.

En nuestro ejemplo, self.assertInstance() comprueba, como sugiere su nombre, que el atributo About_Window es en realidad una instancia de AboutWindow. Si examinamos el slot que estamos probando, MainWin.onAboutAction(), esto ocurre solamente cuando se abre correctamente una ventana, que es precisamente lo que estamos probando.

Unittest ofrece una larga lista de variantes de assert:



Sin embargo, hay que fijarse que sólo un pequeño subconjunto de ellos están incluidos en versiones antiguas de Python.

Si lo que queremos es probar que el código lanza excepciones como se espera que haga podemos usar:


En este punto, si ejecutamos "run_test.py" el test será exitoso. El TDD dice que debes desarrollar pruebas que fallen en un principio, pero aquí no estamos desarrollando código desde cero sino modificando código que ya funciona, por lo que no está mal que el test sea exitoso para asegurarnos de que es correcto.

Para empezar a modificar el código e incluir el "nuevo estilo" de connections deberíamos comentar todas las conexiones que queremos cambiar. Para simplificar nuestro ejemplo vamos a modificar sólo la primera conexión:

def connections(MainWin):
    ## TODO: This signals are connected using old way. I must change it to new way
    #MainWin.connect(MainWin.ui.action_About,  SIGNAL("triggered()"),  MainWin.onAboutAction)
    MainWin.connect(MainWin.ui.actionLanguajes,  SIGNAL("triggered()"),  MainWin.onLanguagesAction)
    MainWin.connect(MainWin.ui.actionOpen,  SIGNAL("triggered()"),  MainWin.onOpenAction)
    MainWin.connect(MainWin.ui.actionPaths_to_compilers,  SIGNAL("triggered()"),  MainWin.onPathsToCompilersAction)
    MainWin.connect(MainWin.ui.actionPyQTmake_Help,  SIGNAL("triggered()"),  MainWin.onHelpAction)
    MainWin.connect(MainWin.ui.actionQuit,  SIGNAL("triggered()"),  MainWin.close)
    MainWin.connect(MainWin.ui.actionSave,  SIGNAL("triggered()"),  MainWin.onSaveAction)
    MainWin.connect(MainWin.ui.actionSave_as,  SIGNAL("triggered()"),  MainWin.onSaveAsAction)
    return MainWin


Aquí es donde ejecutar "run_tests.py" falla, por lo que no situamos en el punto correcto de TDD. Partiendo de ahí tenemos que desarrollar código que haga que nuestra prueba sea exitosa de nuevo.

def connections(MainWin):
    ## TODO: This signals are connected using old way. I must change it to new way
    #MainWin.connect(MainWin.ui.action_About,  SIGNAL("triggered()"),  MainWin.onAboutAction)
    MainWin.ui.action_About.triggered.connect(MainWin.onAboutAction)
    MainWin.connect(MainWin.ui.actionLanguajes,  SIGNAL("triggered()"),  MainWin.onLanguagesAction)
    MainWin.connect(MainWin.ui.actionOpen,  SIGNAL("triggered()"),  MainWin.onOpenAction)
    MainWin.connect(MainWin.ui.actionPaths_to_compilers,  SIGNAL("triggered()"),  MainWin.onPathsToCompilersAction)
    MainWin.connect(MainWin.ui.actionPyQTmake_Help,  SIGNAL("triggered()"),  MainWin.onHelpAction)
    MainWin.connect(MainWin.ui.actionQuit,  SIGNAL("triggered()"),  MainWin.close)
    MainWin.connect(MainWin.ui.actionSave,  SIGNAL("triggered()"),  MainWin.onSaveAction)
    MainWin.connect(MainWin.ui.actionSave_as,  SIGNAL("triggered()"),  MainWin.onSaveAsAction)
    return MainWin

Con esta modificación nuestro test volverá a ser exitoso de nuevo, lo que es señal de que nuestro código funciona. Puedes verificarlo manualmente si lo deseas.

Una vez que has finalizado tus tests lo normal es que quieras cerrar las ventanas de prueba, para ello el método tearDown() de tu test case debería ser:

def tearDown(self):
    #EXIT
    if hasattr(self.MainWin, "About_Window"):
        self.MainWin.About_Window.close()
    self.MainWin.close()
    self.app.exit()

Para probar más aspectos de tu código sólo tienes añadir más métodos de tests en tus subclases de unittest.TestCase.

Con todo esto ya estás preparado para equiparte con un buen conjunto de tests para guiarte a través de tu desarrollo.


25 enero 2014

Mantén a salvo tu código (tutorial de Mercurial)

En uno de mis artículos anteriores escribía acerca de algunas de las opciones disponibles para guardar versiones del código conforme este evolucionaba. Analizamos las principales opciones utilizadas por los desarrolladores freelance: Git y Mercurial, y los principales proveedores en la nube para ellos: GitHub y Bitbucket. Mi conclusión fue que, dado que desarrollo principalmente en Python, mi elección lógica era Mercurial y Bitbucket, mayoritaria dentro de la comunidad Python. En este artículo vamos a aprender los principales comandos para usar Mercurial y mantener un repositorio en Bitbucket.

Mercurial cuenta con instaladores para Windows, Linux y MacOS. Además, se pueden utilizar herramientas gráficas para gestionarlo (como TortoiseHg) o limitarse a controlarlo desde la consola. En este tutorial vamos a centrarnos en la versión para linux (concretamente la de Ubuntu) gestionada mediante comandos de consola. Usar la consola tiene la ventaja de que es más claro e inmediato explicar los conceptos.

Para instalar Mercurial basta con teclear:

$ sudo aptitude install mercurial

Una vez instalado, se puede ejecutar como un usuario normal sin privilegios, pero antes se debe hacer una configuración mínima: en la raíz del directorio home del usuario se debe crear un fichero llamado ".hgrc" (ojo al punto inicial. Este fichero sirve para fijar las variables globales utilizadas por Mercurial. La configuración mínima necesaria para Mercurial es el usuario y la dirección de correo que se quiera usar para marcar cada actualización del repositorio. En mi caso, el fichero tiene el siguiente contenido:
$ cat .hgrc [ui] username = dante [extensions] graphlog= $

Con  cambiar el contenido por el nombre y el correo de cada una ya está, esa es toda la configuración que necesita Mercurial para funcionar. La parte del Graphlog nos permitirá mostrar información muy útil cuando expliquemos las ramas (branches), algo más tarde en este artículo.

Ahora vayamos a la carpeta donde tenemos el código fuente que queremos controlar y le diremos a Mercurial que cree allí el repositorio. Supongamos que el fichero de código fuente se llama sólo "source" y que su contenido es:
source$ ls source$

Para crear ahí un repositorio de Mercurial basta con hacer:
source$ ls source$ hg init source$ ls source$

Espera, ¿no ha cambiado nada?, ¿esto es normal?. En realidad sí porque Mercurial esconde su directorio de trabajo para protegerlo de borrados accidentales:
source$ ls -la total 12 drwxrwxr-x 3 dante dante 4096 ene 17 22:11 . drwxrwxr-x 6 dante dante 4096 ene 17 22:09 .. drwxrwxr-x 3 dante dante 4096 ene 17 22:11 .hg source$

Ahí está, el directorio ".hg" que usa Mercurial. Dentro de él, Mercurial guardará las versiones de nuestro ficheros de código. Mientras la carpeta ".hg" esté a salvo también lo estará nuestro código.

Con "hg status" podemos ver qué pasa en nuestro repositorio. Si lo tecleamos con un repositorio recien creado en una carpeta que aún no tenga ficheros, "hg status" devolverá una respuesta vacía:
source$ hg status source$

Sin embargo, si creamos dos ficheros:
source$ touch code_1.txt source$ touch code_2.txt source$ ls code_1.txt  code_2.txt source$ hg status ? code_1.txt ? code_2.txt source$

Esas dos interrogaciones de la salida de "hg status" nos dicen que Mercurial ha detectado dos ficheros en la carpeta que todavía no están incluidos en el repositorio. Para añadirlos debemos hacer:
source$ hg add code_1.txt source$ hg add code_2.txt source$ hg status A code_1.txt A code_2.txt source$
Ahora las interrogaciones han cambiado a "A" lo que significa que esos ficheros acaban de añadirse al repositorio. Esta vez hemos añadido los ficheros uno a uno, pero podríamos haberlo hecho de una vez haciendo solamente "hg add .". También podríamos usar comodines en este comando. Además podemos crear listas de exclusión si creásemos el fichero ".hgignore" dentro de la carpeta del fichero fuente. De esta manera podemos ajustar en detalle qué ficheros se incluirán en el repositorio de Mercurial y cuales no. Por ejemplo, lo normal es tener en el repositorio ficheros textuales de código fuente, no ficheros compilados (de ese código fuente) o bases de datos de prueba que puedan ser regeneradas fácilmente. Lo mejor es guardar en el repositorio sólo los ficheros que realmente se necesiten con el fin de mantener el tamaño del repositorio lo más pequeño posible. Hay que tener en cuenta que si alojamos nuestro repositorio en Bitbucket (u otro alojamiento de código fuente), seguramente tendremos un límite de tamaño máximo para nuestro repositorio en la nube si queremos seguir como usuarios gratuitos.

Los cambios en el repositorio no serán válidos hasta que los validemos con "hg commit":
source$ hg commit -m "Two initial files just created empty." source$ hg status source$

El parámetro "-m" en el "hg commit" nos permite comentar la versión de manera que podamos saber de un vistazo qué cambios contiene. Una vez que el cambio es validado desaparece de "hg status", esa es la razón por la que en nuestro anterior ejemplo vuelve a salir vacío de nuevo. Si modificamos uno de los ficheros:
source$ hg status source$ echo "Hello" >> code_1.txt source$ hg status M code_1.txt source$

La "M" en la salida de hg status significa que Mercurial ha detectado que un fichero incluido en el repositorio ha cambiado con respecto a la versión que había registrada. Para incluir dicha modificación en el repositorio tenemos que validar el cambio:
source$ hg commit -m "Code_2 modified." source$ hg status source$

¡Pero un momento! ¡espera! ¡hemos cometido un error!, el mensaje es incorrecto porque el fichero modificado es code_1 no code_2. Mercurial nos permite corregir la última validación con el parámetro "--amend":
source$ hg log changeset:   1:4161fbd0c054 tag:         tip user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_2 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg commit --amend -m "Code_1 modified." saved backup bundle to /home/dante/Desarrollos/source/.hg/strip-backup/4161fbd0c054-amend-backup.hg source$ hg log changeset:   1:17759dec5135 tag:         tip user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$

"hg log" muestra el histórico de validaciones. A través de ese histórico podemos ver que el mensaje de la última actualización ha sido corregido gracias al parámetro "--amend". Sin embargo, con ese parámetro sólo se puede arreglar la última validación. Cambiar validaciones más antiguas se considera peligroso y no hay una manera fácil de hacerlo (aunque se puede, pero es bastante delicado).

¿Qué pasa si uno se da cuenta de que ya no necesita uno de los ficheros del proyecto y quiere retirarlo del repositorio para que Mercurial no le haga seguimiento?. Una opción sería borrar el fichero de la carpeta del código fuente...
source$ ls code_1.txt  code_2.txt source$ rm code_2.txt source$ ls code_1.txt source$ hg status ! code_2.txt source$

... pero se puede ver que Mercurial alerta (el mensaje con la exclamación "!") de que no puede encontrar un fichero al que le hace seguimiento por estar en el repositorio. Para decirle a Mercurial que deje de hacerle ese seguimiento a un fichero en particular:
source$ hg status ! code_2.txt source$ hg remove code_2.txt source$ hg status R code_2.txt source$ hg commit -m "Code_2 removed." source$ hg status source$

Con "hg remove" se puede marcar un fichero para ser retirado del repositorio, esa es la razón por la que "hg log" muestra una "R" que significa que el fichero va a ser borrado del repositorio en la próxima validación.

Vale, hemos borrado un fichero del repositorio pero entonces nos damos cuenta de que en realidad lo necesitábamos y que borrarlo fue un error. Tenemos dos opciones. El primero es devolver el repositorio a la última versión en la que el fichero borrado estaba presente, copiarlo a un fichero temporal, devolver al repositorio al últimos estado actualizado y copiar el fichero de la carpeta temporal a la del código:
source$ hg log changeset:   2:88ac7cad647e tag:         tip user:        dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg update 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$ cp code_2.txt /tmp/code_2.txt source$ hg update 2 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt source$ cp /tmp/code_2.txt code_2.txt source$ hg status ? code_2.txt source$ hg add code_2.txt source$ hg status A code_2.txt source$

Como se puede ver, con el comando "hg update" podemos hacer que nuestra carpeta de código fuente viaje en el tiempo al estado que tenía en una versión concreta. Sólo hay que tener en cuenta que el número que usa "hg update" es el primero que figura en el id de revisión mostrado por "hg log". Por ejemplo, si quisiéramos volver a este estado:

changeset:   1:17759dec5135
user:        dante
date:        Fri Jan 17 23:09:00 2014 +0100
summary:     Code_1 modified.

deberíamos usar "hg update 1" debido a que la versión dice "changeset 1:...", ¿se entiende?.

El problema de este enfoque es que es poco elegante y resulta fácil cometer un error. Un enfoque más directo sería localizar el último estado en el que está presente el fichero a recuperar y traerlo de vuelta con "hg revert":
source$ ls code_1.txt source$ hg status source$ hg log -l 1 code_2.txt changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg revert -r 0 code_2.txt source$ hg log changeset:   2:88ac7cad647e tag:         tip user:        dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ hg status A code_2.txt source$ source$ hg commit -m "Code_2 recovered." source$ hg log changeset:   3:9214d0557080 tag:         tip user:        dante date:        Sat Jan 18 01:07:24 2014 +0100 summary:     Code_2 recovered. changeset:   2:88ac7cad647e user:       dante date:        Sat Jan 18 00:39:50 2014 +0100 summary:     Code_2 removed. changeset:   1:17759dec5135 user:        dante date:        Fri Jan 17 23:09:00 2014 +0100 summary:     Code_1 modified. changeset:   0:bf50392b0bf2 user:        dante date:        Fri Jan 17 22:43:34 2014 +0100 summary:     Two initial files just created empty. source$ ls code_1.txt  code_2.txt source$

Lo principal es que "hg log -l 1 code_2.txt" muestra que la última versión en el que el fichero fue modificado. Con esa versión podemos hacer que Mercurial rescate el fichero deseado desde alí. ("hg revert -r 0 code_2.txt). No hay que olvidar realizar una validación al finalizar el rescate.

Ahora elevemos las apuestas. A veces uno quiere intentar desarrollar nuevas funcionalidades para nuestro programa pero no quiere enredar con los ficheros estables. Ahí es donde entran en juego las ramas. Al crear una rama podemos desarrollar sobre una copia aparte de nuestra rama principal (denominada "default"). Una vez que estemos seguros de que la rama está ista para entrar en producción podemos fundir (merge) la rama con la principal, incluyendo los cambios en los ficheros estables de la rama principal.

Supongamos que queremos desarrollar dos funcionalidades, podemos crear dos ramas: "feature1" y " feature2":
source$ hg branches default 0:03e7ab9fb0c6 source$ hg branch feature1 marked working directory as branch feature1 (branches are permanent and global, did you want a bookmark?) source$ hg branches default 0:03e7ab9fb0c6 source$ hg status source$ hg commit -m "Feature1 branch created." source$ hg branches feature1 1:6c061eff633f default 0:03e7ab9fb0c6 (inactive) source$

"hg branches" muestra las ramas del repositorio pero estas no se crean en realidad hasta que se validan con un "hg commit", tras hacer un "hg branch". Esa es la razón por la que el primer "hg branches" del ejemplo anterior sólo muestra la rama principal.
source$ touch code_feature1.txt source$ ls code_1.txt  code_2.txt  code_feature1.txt source$ hg status ? code_feature1.txt source$ hg add code_feature1.txt source$ hg commit -m "code_feature1.txt created"

Para cambiar de una rama a otra hay que usar "hg update":
source$ hg update default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$

Cuando cambiamos de una rama a otra los ficheros se crean o borran del directorio para crear el esquema de la versión de la rama.
source$ hg branch feature2 marked working directory as branch feature2 (branches are permanent and global, did you want a bookmark?) source$ hg commit -m "Feature2 branch created" source$ touch code_feature2.txt source$ hg add code_feature2.txt source$ hg commit -m "code_feature2.txt created" source$ ls code_1.txt  code_2.txt  code_feature2.txt source$ hg branches feature2                       7:42123cefb28c feature1                       5:09f18d24ae0e default                        3:9214d0557080 (inactive) source$ hg update default 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt source$

Por supuesto podemos seguir trabajando en la rama principal:

source$ ls code_1.txt  code_2.txt source$ touch code_3.txt source$ ls code_1.txt  code_2.txt  code_3.txt source$ hg add code_3.txt source$ hg commit -m "code_3.txt created" source$

Cuando uno trabaja simultáneamente con varias ramas es natural sentirse un poco perdido. Para saber en que rama estamos en cada momento se puede teclear "hg branch" sin nada detrás. Para conseguir una representación gráfica de los cambios validados a las distintas ramas se puede usar "hg log -G":

source$ hg log -G @  changeset:   8:09e718575633 |  tag:         tip |  parent:      3:9214d0557080 |  user:        dante |  date:        Sat Jan 18 20:53:06 2014 +0100 |  summary:     code_3.txt created | | o  changeset:   7:42123cefb28c | |  branch:      feature2 | |  user:        dante | |  date:        Sat Jan 18 20:40:56 2014 +0100 | |  summary:     code_feature2.txt created | | | o  changeset:   6:52f1c855ba6b |/   branch:      feature2 |    parent:      3:9214d0557080 |    user:        dante |    date:        Sat Jan 18 20:39:05 2014 +0100 |    summary:     Feature2 branch created | | o  changeset:   5:09f18d24ae0e | |  branch:      feature1 | |  user:        dante | |  date:        Sat Jan 18 20:22:35 2014 +0100 | |  summary:     code_feature1.txt created | | | o  changeset:   4:2632a2e93070 |/   branch:      feature1 |    user:        dante |    date:        Sat Jan 18 20:20:28 2014 +0100 |    summary:     Feature1 branch created | o  changeset:   3:9214d0557080 |  user:        dante |  date:        Sat Jan 18 01:07:24 2014 +0100 |  summary:     Code_2 recovered. | o  changeset:   2:88ac7cad647e |  user:        dante |  date:        Sat Jan 18 00:39:50 2014 +0100 |  summary:     Code_2 removed. | o  changeset:   1:17759dec5135 |  user:        dante |  date:        Fri Jan 17 23:09:00 2014 +0100 |  summary:     Code_1 modified. | o  changeset:   0:bf50392b0bf2    user:        dante    date:        Fri Jan 17 22:43:34 2014 +0100    summary:     Two initial files just created empty. source$

Para usar el parámetro "-G" con "hg log" hay que incluir las siguientes líneas en el fichero ".hgrc" que mencionábamos al comienzo del artículo:

[extensions]
graphlog=

Una vez que llegamos a la conclusión de que nuestra rama está lo suficientemente madura como para agregar sus cambios a la rama principal, podemos usar "hg merge":
 source$ hg update feature1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_feature1.txt source$ cat code_1.txt Hello source$ echo "World" >> code_1.txt source$ cat code_1.txt Hello World source$ hg status M code_1.txt source$ hg commit -m "code_1.txt modified with world" source$ hg update default 2 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_3.txt source$ cat code_1.txt Hello source$ hg merge feature1 2 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) source$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source$ cat code_1.txt Hello World source$


Es importante tener en cuenta que antes de hacer un merge hay que ponerse en la rama donde se quieren insertar los cambios. Una vez allí se llama a "hg merge" con el nombre de rama desde donde se quieren importar los cambios. Por supuesto, los cambios no se incorporan de manera efectiva al repositorio hasta que son validados:
source$ hg status M code_1.txt M code_feature1.txt source$ hg commit -m "Feature1 merged to default branch" source$

Fijémonos en cómo el log graph ha cambiado para mostrar la unión entre ramas:
source$ hg log -G @    changeset:   10:677a88f54dd3 |\   tag:         tip | |  parent:      8:1b93d501259a | |  parent:      9:8b55fb7eec71 | |  user:        dante | |  date:        Sun Jan 19 00:07:54 2014 +0100 | |  summary:     Feature1 merged to default branch | | | o  changeset:   9:8b55fb7eec71 | |  branch:      feature1 | |  parent:      5:197964afe12f | |  user:        dante | |  date:        Sat Jan 18 23:57:03 2014 +0100 | |  summary:     code_1.txt modified with world | | o |  changeset:   8:1b93d501259a | |  parent:      3:132c0505c7b2 | |  user:        dante | |  date:        Sat Jan 18 23:56:24 2014 +0100 | |  summary:     code_3.txt created | | | | o  changeset:   7:86391749b3c3 | | |  branch:      feature2 | | |  user:        dante | | |  date:        Sat Jan 18 23:55:04 2014 +0100 | | |  summary:     code_feature2.txt created | | | +---o  changeset:   6:30decd2ffa21 | |    branch:      feature2 | |    parent:      3:132c0505c7b2 | |    user:        dante | |    date:        Sat Jan 18 23:54:38 2014 +0100 | |    summary:     Feature2 branch created | | | o  changeset:   5:197964afe12f | |  branch:      feature1 | |  user:        dante | |  date:        Sat Jan 18 23:53:43 2014 +0100 | |  summary:     code_feature1.txt created | | | o  changeset:   4:4bbf5ca2e0b6 |/   branch:      feature1 |    user:        dante |    date:        Sat Jan 18 23:52:26 2014 +0100 |    summary:     Feature1 branch created | o  changeset:   3:132c0505c7b2 |  user:        dante |  date:        Sat Jan 18 23:52:02 2014 +0100 |  summary:     Code_2 recovered. | o  changeset:   2:05e0a410c49d |  user:        dante |  date:        Sat Jan 18 23:51:24 2014 +0100 |  summary:     Code_2 removed. | o  changeset:   1:552e1b95fffe |  user:        dante |  date:        Sat Jan 18 23:49:35 2014 +0100 |  summary:     Code_1 modified. | o  changeset:   0:a22ab902f1a7    user:        dante    date:        Sat Jan 18 23:48:55 2014 +0100    summary:     Two initial files just created empty. source$

Cuando se ha acabado el trabajo en una rama y no se planea hacer ninguna mejora más en dicha rama, se puede hacer para cerrar una rama de manera que ya no aparezca en la lista de "hg branches":
source$ hg branches default                       10:677a88f54dd3 feature2                       7:86391749b3c3 feature1                       9:8b55fb7eec71 (inactive) source$ hg update feature1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved source$ hg commit --close-branch -m "Feature1 included in default. No further work planned here" source$ hg branches default                       10:677a88f54dd3 feature2                       7:86391749b3c3 source$

La ramas cerradas se pueden abrir de nuevo entrando en ellas con un "hg update" y validando el cambio con "hg commit".

Hasta ahora hemos aprendido lo básico para trabajar con Mercurial en una carpeta de código fuente local. Normalmente es difícil borrar accidentalmente un directorio oculto como ".hg", pero siempre podemos perder nuestro disco duro por un fallo hardware (o uno puede meter la pata haciendo un "rm -rf" mientras escribía este artículo). En ese caso perderíamos el repositorio. Además cuando trabajemos con un equipo necesitaremos un repositorio central en el que mezclar los avances de cualquier miembro en la rama principal. Bitbucket es la respuesta para ambas necesidades. Por eso vamos a ver cómo podemos conservar un backup de nuestro repositorio en la nube de Bitbucket.

Una vez que nos hayamos registrados en Bitbucket podemos crear un nuevo repositorio:


Se puede configurar el repositorio tanto como público como privado, podemos hacer que pueda ser usado tanto con Git como con Mercurialo incluso incluir una Wiki en el repositorio de la página web. Si nuestro equipo es de menos de cinco personas, Bitbucket nos ofrecerá sus servicios de manera gratuita.

Cuando se crea un repositorio podemos subir a él nuestra copia del código fuente con el comando "hg push":

source$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 12 changesets with 7 changes to 5 files (+1 heads) source$

Con el repositorio subido a Bitbucket, todos los miembros pueden conseguir una copia local del proyecto con "hg clone":
source2$ ls source2$ hg clone https://dante@bitbucket.org/dante/sourcecode . http authorization required realm: Bitbucket.org HTTP user: borjalopezm password: requesting all changes adding changesets adding manifests adding file changes added 12 changesets with 7 changes to 5 files (+1 heads) updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved source2$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source2$ hg update 0 files updated, 0 files merged, 0 files removed, 0 files unresolved source2$

Hay que fijarse en el punto (".") que hay justo después de la url del "hg clone", si no lo usamos los ficheros se descargarán en una carpeta denominada "sourcecode" dentro de "source2". Suele ser una buena idea hacer un "hg update" para asegurarse de que estamos trabajando en la versión más actualizada del proyecto.

Tras eso, el miembro del equipo ya podrá trabajar en su repositorio local. Para subir los avances a Bitbucket habría que hacer un "hg push" como hicimos antes para subir los ficheros por primera vez a Bitbucket:
source2$ ls code_1.txt  code_2.txt  code_3.txt  code_feature1.txt source2$ touch code_4.txt source2$ hg add code_4.txt source2$ hg commit -m "Code_4.txt added" source2$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user:dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files source2$

Tras el clone inicial, los otros miembros del equipo pueden conseguir actualizaciones (como code_4.txt) con un "hg pull":

source$ hg pull https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: pulling from https://dante@bitbucket.org/dante/sourcecode searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) source$ hg update default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved source$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source$

¿Pero qué pasa si dos miembros hacen la modificación sobre el mismo fichero?. Supongamos que un miembro hace:
source$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source$ cat code_1.txt Hello World source$ echo "Hello WWW" > code_1.txt source$ hg commit -m "One line hello WWW" source$ cat code_1.txt Hello WWW source$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 1 changesets with 1 changes to 1 files source$

Y justo un poco después otro miembro hace en su propio repositorio:
source2$ ls code_1.txt  code_2.txt  code_3.txt  code_4.txt  code_feature1.txt source2$ cat code_1.txt Hello World source2$ echo "Wide Web" >> code_1.txt source2$ cat code_1.txt Hello World Wide Web source2$ hg commit -m "Code_1 added Wide Web" source2$ hg push https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes abort: push creates new remote head e716387febe4! (you should pull and merge or use push -f to force) source2$

Lo que ha pasado es que Bitbucket ha detectado que el segundo push contenía una versión conflictiva del fichero code_1.txt. Cuando tenemos dos versiones de un fichero en la misma rama y nivel de versión estamos ante lo que la terminología del control de versiones denomina "dos cabezas" ("two heads"). Por defecto, Bitbucket no permite que tengamos dos cabeza y recomienda que nos bajemos las últimas actualizaciones con "hg pull" y mezclarlas con nuestra versión local con un "hg  merge":
source2$ hg heads changeset:   13:e716387febe4 tag:         tip user:        dante date:        Mon Jan 20 21:46:00 2014 +0100 summary:     Code_1 added Wide Web changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$ hg branch default source2$

En este punto se puede ver que tenemos una cabeza por cada rama. Esta es una situación normal, pero si actualizamos:
source2$ hg pull https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: pulling from https://dante@bitbucket.org/dante/sourcecode searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) source2$

Fijémonos en el último mensaje que nos alerta de que la última actualización ha creado múltiples cabezas. De hecho, si ejecutamos "hg heads":
source2$ hg heads changeset:   14:c3a688edd25a tag:         tip parent:      12:53443797a7da user:        dante date:        Mon Jan 20 21:46:25 2014 +0100 summary:     One line hello WWW changeset:   13:e716387febe4 user:        dante date:        Mon Jan 20 21:46:00 2014 +0100 summary:     Code_1 added Wide Web changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$

Podemos ver que tenemos dos cabezas en la rama principal. Por eso es el momento de mezclarlas con un "hg merge":
source2$ hg merge merging code_1.txt 3 archivos que editar 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) source2$ cat code_1.txt Hello WWW World Wide Web source2$ hg commit -m "Code_1 merged with repository" source2$ hg heads changeset:   15:fed327662238 tag:         tip parent:      13:e716387febe4 parent:      14:c3a688edd25a user:        dante date:        Mon Jan 20 22:06:39 2014 +0100 summary:     Code_1 merged with repository changeset:   7:86391749b3c3 branch:      feature2 user:        dante date:        Sat Jan 18 23:55:04 2014 +0100 summary:     code_feature2.txt created source2$ hg https://dante@bitbucket.org/dante/sourcecode pushing to https://dante@bitbucket.org/dante/sourcecode http authorization required realm: Bitbucket.org HTTP user: dante password: searching for changes remote: adding changesets remote: adding manifests remote: adding file changes remote: added 2 changesets with 2 changes to 1 files source2$

En caso de conflictos como este, "hg merge" abre un editor con paneles (que no muestro aquí) con el que comparar las versiones del mismo fichero que entran en conflicto entre si y modificarlas de manera que la copia local sea compatible con la de Bitbucket. Por lo general prefiero usar Mercurial desde la consola en vez de utilizar las múltiples aplicaciones gráficas existentes (como TortoiseHG), pero tengo que admitir que el editor de consola que utiliza Mercurial es un poco árido al estar basado en Vim (soy de los que prefiere Nano frente a Vim).

Una vez mezclado y validado, podemos ver que el número total de cabezas se ha reducido de nuevo a dos (una por rama) por lo que esta vez un push a Bitbucket funcionará como la seda.

Con todas estas herramientas, un equipo de desarrolladores puede trabajar simultaneamente sin pisarse los unos a los otros. Pero Bitbucket ofrece formas para que podemos contribuir con un projecto incluso si no formamos parte de su equipo de desarrollo y no tenemos acceso de escritura a su repositorio. Nos referimos a lo que se denomina forking.



Cuando hacemos fork del repositorio de otro usuario lo que ocurre en segundo plano es que el repositorio se clona en nuestra cuenta de Bitbucket. De esa manera tendremos la oportunidad de escribir y probar modificaciones contra nuestro propio repositorio. Una vez que nuestro código está preparado, podemos solicitar un "pull request" al autor original. Si él lo acepta, se realizará la mezcla entre los dos repositorios y los cambios se incorporarán al repositorio original.



OK, con esto finalizamos el artículo. Ahora estamos en condiciones de dominar las bases del control de versiones con Mercurial y Bitbucket. Siento la extensión del artículo pero quería cubrir todos los temas habituales que se puede encontrar habitualmente un proyecto independiente. Mercurial y Bitbucket tienen otras muchas opciones y refinamientos pero normalmente sólo nos los encontraremos en proyectos más complejos.

Por último, no quiero acabar este artículo sin mencionar que la mayor parte de los conceptos de este artículo son similares a los que usan Git y Github. Recomiendo visitar este tutorial introductorio a Git en el que se puede ver lo similar que es a Merccurial.

15 enero 2014

GNS3 busca apoyo para desarrollar su próxima versión

GNS3, el entorno de virtualización de laboratorios open source desarrollado en Python, acerca del cual escribí en uno de mis artículos anteriores, está buscando apoyo financiero para desarrollar su próxima versión. Su campaña en Crowdhoster ha sido un gran éxito. Con un objetivo inicial de 35.000$, han alcanzado hasta ahora 280.000$.

Las nuevas funcionalidades son muy interesantes, pero sobre todo se encuentra la inclusión de las capacidades de conmutación que nos permitirán librarnos de los trucos con los Cisco 3540 y sus tarjetas NM-16ESW para simular entornos conmutados. Otras funcionalidades nuevas son los laboratorios de seguridad, el procesado bajo demanda en la nube, etc. Hay que resaltar las opciones para empresas. Aquellas en el negocio de las redes de datos se pueden beneficiar enormemente de las posibilidades para la formación de GNS3.