martes, 2 de agosto de 2016

Git: mover una carpeta a otro repositorio manteniendo el histórico


http://lulachronicles.blogspot.nl/2016/06/git-how-to-move-folder-to-new-empty.html


Esto es cómo lo hice yo, no se si es la mejor forma pero funciona 100%

Usé los comandos descriptos en este artículo https://help.github.com/articles/remove-sensitive-data/



  1. Dos repositorios: REPO_OLD con la carpeta que quiero mover, REPO_NEW un repositorio vacío a donde quiero mover la carpeta
     
  2. Copiar todo (incluida la carpeta .git) desde el repositorio REPO_OLD al repositorio REPO_NEW (otra forma es clonar REPO_OLD en una nueva carpeta REPO_NEW)
     
  3. IMPORTANTE: borrar origin en el repositorio REPO_NEW: git remote rm origin
     
  4. Borrar en el repositorio  REPO_NEW todas las carpetas que NO se quieren mover. Por ej. si la carpeta se llama FOLDER entonces

    git filter-branch --force --index-filter ' git rm --cached --ignore-unmatch -r  FOLDER/ ' --prune-empty --tag-name-filter cat -- --all

    hacerlo con todas las carpetas, una por una. Al final en REPO_NEW debería quedar solamente la carpeta que queremos mover. Si algunas carpetas todavía existen es porque tenían archivos que no se habían agregado a git, se pueden borrar a mano sin problemas.
     
  5. Ahora hay que setear el origin en REPO_NEW para que apunte al nuevo repositorio.
     
    Por ej. git remote add origin git@bitbucket.org:someuser/REPO_NEW.git 
     
  6. Pushear los cambios, forzando que sobreescriba en el origin: git push origin --force --all
     
  7. Ok, la carpeta está copiada, pero todavía existe en el otro repositorio. Se puede borrar usando el mismo comando del paso 4.
     
  8. Listo


Kotlin incluye codigo muerto en el bytecode



http://lulachronicles.blogspot.nl/2016/07/kotlin-includes-dead-code-in-generated.html

Hay dos presentaciones sobre Kotlin que disfrute mucho, ambas describen lo mejor de este nuevo lenguaje.






Pero hay una cosa no descripta en esas presentaciones, un detalle sobre cómo Kotlin genera el bytecode:

Las 'inlined functions' de Kotlin no remueven el codigo muerto y lo incluyen en el bytecode.



No es un gran problema ya que Proguard o una herramienta similar remueve el codigo muerto al optimizar las clases, pero en los proyectos Android a veces es necesario deshabilitar las optimizaciones de Proguard debido a la complejidad del proyecto, las librerias usadas, etc.

Un ejemplo mostrando el código y el bytecode de Java y Kotlin


Veamos un ejemplo. Queremos mostrar mensajes de debug, pero no queremos incluir en el bytecode de la release build este código de debug.:
  • En java podemos meter los logs dentro de una condición, chequeando una constante booleana. En este caso el compilador ignora todo lo que está dentro de la condición cuando el valor de la constante es falso.
  • En Kotlin podemos usar inlined functions, una gran ventaja de este lenguaje, para no chequear una cosntante cada vez que queremos mostrar un log. Un ejemplo de una inlined function en Kotlin

    inline fun debug(func: () -> String) {
        if (BuildConfig.DEBUG) {
            println(func())
        }
    }


Por ejemplo, mostramos un mensaje de debug dentro de un método llamado "doSomething"

Java
void doSomething() {
    if (BuildConfig.DEBUG) {
        System.out.println("Este es un mensaje de debug");
    }
}

Kotlin
fun doSomething() {
    debug { "Este es un mensaje de debug" }
}

+1 para Kotlin, menos código, limpio y simple.

En Java podríamos crear un método static y poner la condición dentro del método, así tendríamos un código limpio como el de Kotlin, pero el compilador de Java no removería todos los llamados a la función y todos los strings de debug se incluirían en la build de release. Incluso optimizando con Proguard los llamados al método se remueven, pero no los parámetros, en realidad depende de la cantidad de pasos en la optimización dentro de proguard.properties.


Veamos ahora el bytecode generado por Java y Kotlin.


Para analizar el bytecode uso el plugin de IntelliJ/Eclipse creado por los autores de la librería ASM, el OW2 Consortium. Para Kotlin podría usar el plugin que viene con IntelliJ pero no tiene una opción para ignorar los números de linea, los labels y la info de stack.


Bytecode generado cuando la constante DEBUG es true

Java
  void doSomething() {
    getstatic 'BuildConfig.DEBUG','Z'
    ifeq l0
    getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
    ldc "Este es un mensaje de debug"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   l0
    return
  }


Kotlin (el bytecode no es óptimo como el de java, ver el subrayado
  public final static void doSomething() {
    nop
    getstatic 'BuildConfig.DEBUG','Z'
    ifeq l0
    ldc "Este es un mensaje de debug"
    astore 0
    nop
    getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
    aload 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   l0
    return
  }

+1 para Java, menos código que ejecutar.

La diferencia no es grande pero Kotlin incluye algunos nops inútiles el la forma en que carga el string "Este es un mensaje de debug" es redundante, carga el string, lo guarda en el stack (astore 0), y lo carga de nuevo (aload 0).



Bytecode generado cuando la constante DEBUG es false

Java
  void doSomething() {
    return
  }


Kotlin (dead code is underlined)
  public final static void doSomething() {
    nop
    iconst_0
    ifeq l0
    ldc "Este es un mensaje de debug"
    astore 0
    nop
    getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
    aload 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   l0
    return
  }

+1 para Java, el compilador ignora el código dentro de la condición, pero el inline de Kotlin no hace ningún chequeo, simplemente copia y pega el código dentro del método, sin tener en cuenta el valor de la constante.


Cómo evitarlo usando gradle flavors


Lo que reomiendo para Kotlin es, en vez de chequear el valor de una constante, usar gradle flavors, el método para la build de debug muestra el mensaje sin chequear nada, el método de release no hace nada.

Debug flavorRelease flavor
inline fun debug(func: () -> String) {
    println(func())
}
inline fun debug(func: () -> String) {
}


Así el bytecode generado en la build de release es casi nulo .


Kotlin Bytecode: Debug flavor
  public final static void doSomething() {
    nop
    ldc "Este es un mensaje de debug"
    astore 0
    nop
    getstatic 'java/lang/System.out','Ljava/io/PrintStream;'
    aload 0
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
    return
  }

Kotlin Bytecode: Release flavor
  public final static void doSomething() {    
    nop
    return
  }



Kotlin agrega un nop dentro del bytecode de release, pero es mejor que el código muerto incluido antes.


Estamos resolviendo un gran problema con esto? Para la mayoria de los proyectos no, pero estos pequeños detalles pueden ser relevantes en proyectos que necesitan alta performance.








lunes, 1 de agosto de 2016

Cómo ejecutar feature tests de Cucumber en android, pasos para Android Studio 2.1






Estos pasos no son para tests de UI, si quieres correr tests de integracion con Cucumber te recomiendo usar Calabash.


  1. Crear un nuevo proyecto en Android Studio.
     
  2. Abrir build.gradle y agregar las dependencias:

    androidTestCompile 'junit:junit:4.12'
    androidTestCompile group: 'info.cukes', name: 'cucumber-java', version: '1.2.2'
    androidTestCompile group: 'info.cukes', name: 'cucumber-core', version: '1.2.2'
    androidTestCompile group: 'info.cukes', name: 'cucumber-html', version: '0.2.3'
    androidTestCompile group: 'info.cukes', name: 'cucumber-jvm-deps', version: '1.0.3'
    androidTestCompile group: 'info.cukes', name: 'gherkin', version: '2.12.2'
    androidTestCompile 'info.cukes:cucumber-android:1.2.0@jar'
    androidTestCompile group: 'info.cukes', name: 'cucumber-junit', version: '1.2.2'

    deben ser exactamente estos números de version. Las últimas versiones de Cucumber están compiladas con java 8, lo que no es soportado por android (por ahora)

     
  3. Necesitamos configurar en gradle que los tests sean ejecutados por Cucumber. Para eso hay que agregar en build.gradle el instrumentation runner, dentro de defaultConfig

    defaultConfig {
        applicationId "my.application.package"    minSdkVersion 14
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"    
        testInstrumentationRunner "cucumber.api.android.CucumberInstrumentation"
    }
     
  4. Crea una nueva carpeta dentro de assets para los features de Cucumber en src/androidTest/assets
     
  5. Gradle no sabe nada de esta nueva carpeta con los features. Hay que configurar esta carpeta para que Cucumber pueda leer los features y ejecutarlos. Por ej. debajo de defaultConfig

    defaultConfig {
        ...
    }
    sourceSets {
        androidTest {
            assets.srcDirs = ['src/androidTest/assets']
        }
    }
     
  6. Crear una nueva carpeta llamada "features" dentro de la carpeta de assets. Todos los archivos de features van a guardarse aquí (se puede usar subfolders). Acá un ejemplo de un feature que debe ir en un archivo en esta carpeta:

    Feature: Calculator
    
      Scenario Outline: a simple sum
        Given I have numbers 2 and 3
        When I sum
        Then the result is 5
     
  7. Este paso es muy importante. Crea tus tests en el subpaquete "test" de tu paquete principal, por ejemplo my.application.package.test

    Ejemplo de un test para el feature anterior:

    @CucumberOptions(features = "features")
    public class CalculatorFeatureTest {
    
        private int mNum1;
        private int mNum2;
        private int mResult;
    
        @Given("^I have numbers (-*\\d+) and (-*\\d+)$")
        public void i_have_numbers_and(int n1, int n2) throws Throwable {
            mNum1 = n1;
            mNum2 = n2;
        }
    
        @When("^I sum$")
        public void i_sum() throws Throwable {
            mResult = mNum1 + mNum2;
        }
    
        @Then("^the result is (-*\\d+)$")
        public void the_result_is(int result) throws Throwable {
            assertEquals(mResult, result);
        }
    
    }

    La annotation @CucumberOptions especifica la carpeta adentro de assets, en este caso "features"
     
  8. En Android Studio, click derecho en el paquete con los tests, y elegir "Ejecutar tests en ..."
     
  9. Listo
Estos pasos están basados en los pasos descriptos en http://mdzyuba.blogspot.nl/2015/01/cucumber-jvm-and-android-studio.html