diff --git a/.gitignore b/.gitignore index 7e31aff..2183054 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ build/ .gradle/ local.properties *.iml -core/out \ No newline at end of file +core/out +repo +.project +.settings \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 4f6d8ac..8884489 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,28 @@ -language: java +language: android +sudo: required +env: + global: + - ADB_INSTALL_TIMEOUT=8 # 8 minutes (2 minutes by default) + +jdk: + - oraclejdk8 + +android: + components: + - tools + - platform-tools + - build-tools-28.0.0-rc02 + - build-tools-27.0.3 + - android-27 + - android-28 + - extra-android-support + - extra-google-m2repository + - extra-android-m2repository script: -- ./gradlew clean ktlint test + - ./gradlew ktlint test install + - ./gradlew install + - cd KotlinConsumer + - ./gradlew test + - cd ../AndroidConsumer + - ./gradlew test \ No newline at end of file diff --git a/AndroidConsumer/.gitignore b/AndroidConsumer/.gitignore new file mode 100644 index 0000000..5edb4ee --- /dev/null +++ b/AndroidConsumer/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/AndroidConsumer/app/.gitignore b/AndroidConsumer/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/AndroidConsumer/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/AndroidConsumer/app/__snapshot__/com.karumi.androidconsumer.ExampleUnitTest_addition_isWrong.snap b/AndroidConsumer/app/__snapshot__/com.karumi.androidconsumer.ExampleUnitTest_addition_isWrong.snap new file mode 100644 index 0000000..da2d398 --- /dev/null +++ b/AndroidConsumer/app/__snapshot__/com.karumi.androidconsumer.ExampleUnitTest_addition_isWrong.snap @@ -0,0 +1 @@ +14 \ No newline at end of file diff --git a/AndroidConsumer/app/build.gradle b/AndroidConsumer/app/build.gradle new file mode 100644 index 0000000..0951c4e --- /dev/null +++ b/AndroidConsumer/app/build.gradle @@ -0,0 +1,46 @@ +buildscript { + repositories { + google() + jcenter() + maven { url uri('../../repo') } + } + dependencies { + classpath 'com.karumi.kotlinsnapshot:plugin:0.2.0' + } +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'com.karumi.kotlin-snapshot' + +repositories { + maven { url uri('../../repo') } +} + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.karumi.androidconsumer" + minSdkVersion 19 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:28.0.0-rc02' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/AndroidConsumer/app/proguard-rules.pro b/AndroidConsumer/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/AndroidConsumer/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/AndroidConsumer/app/src/main/AndroidManifest.xml b/AndroidConsumer/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6bc01f7 --- /dev/null +++ b/AndroidConsumer/app/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + diff --git a/AndroidConsumer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/AndroidConsumer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..56d3e0f --- /dev/null +++ b/AndroidConsumer/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/AndroidConsumer/app/src/main/res/drawable/ic_launcher_background.xml b/AndroidConsumer/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..f56e377 --- /dev/null +++ b/AndroidConsumer/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6d5e5d0 --- /dev/null +++ b/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6d5e5d0 --- /dev/null +++ b/AndroidConsumer/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher.png b/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a2f5908 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..1b52399 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher.png b/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ff10afd Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..115a4c7 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..dcd3cd8 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..459ca60 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..8ca12fe Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8e19b41 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b824ebd Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..4c19a13 Binary files /dev/null and b/AndroidConsumer/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/AndroidConsumer/app/src/main/res/values/colors.xml b/AndroidConsumer/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..5a077b3 --- /dev/null +++ b/AndroidConsumer/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/AndroidConsumer/app/src/main/res/values/strings.xml b/AndroidConsumer/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..c23cf9a --- /dev/null +++ b/AndroidConsumer/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + AndroidConsumer + diff --git a/AndroidConsumer/app/src/main/res/values/styles.xml b/AndroidConsumer/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..e494029 --- /dev/null +++ b/AndroidConsumer/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/AndroidConsumer/app/src/test/java/com/karumi/androidconsumer/ExampleUnitTest.kt b/AndroidConsumer/app/src/test/java/com/karumi/androidconsumer/ExampleUnitTest.kt new file mode 100644 index 0000000..a01fdff --- /dev/null +++ b/AndroidConsumer/app/src/test/java/com/karumi/androidconsumer/ExampleUnitTest.kt @@ -0,0 +1,11 @@ +package com.karumi.androidconsumer + +import com.karumi.kotlinsnapshot.matchWithSnapshot +import org.junit.Test + +class ExampleUnitTest { + @Test + fun addition_isWrong() { + 14.matchWithSnapshot() + } +} diff --git a/AndroidConsumer/build.gradle b/AndroidConsumer/build.gradle new file mode 100644 index 0000000..d1f55e6 --- /dev/null +++ b/AndroidConsumer/build.gradle @@ -0,0 +1,25 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.2.10' + repositories { + google() + jcenter() + maven { url uri('../repo') } + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/AndroidConsumer/gradle.properties b/AndroidConsumer/gradle.properties new file mode 100644 index 0000000..743d692 --- /dev/null +++ b/AndroidConsumer/gradle.properties @@ -0,0 +1,13 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/AndroidConsumer/gradle/wrapper/gradle-wrapper.jar b/AndroidConsumer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7a3265e Binary files /dev/null and b/AndroidConsumer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/AndroidConsumer/gradle/wrapper/gradle-wrapper.properties b/AndroidConsumer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..113b11a --- /dev/null +++ b/AndroidConsumer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Aug 31 12:23:57 CEST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip diff --git a/AndroidConsumer/gradlew b/AndroidConsumer/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/AndroidConsumer/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/AndroidConsumer/gradlew.bat b/AndroidConsumer/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/AndroidConsumer/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/AndroidConsumer/settings.gradle b/AndroidConsumer/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/AndroidConsumer/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/KotlinConsumer/__snapshot__/com.karumi.kotlinsnapshot.kotlinconsumer.AnyTest_anyTest.snap b/KotlinConsumer/__snapshot__/com.karumi.kotlinsnapshot.kotlinconsumer.AnyTest_anyTest.snap new file mode 100644 index 0000000..da2d398 --- /dev/null +++ b/KotlinConsumer/__snapshot__/com.karumi.kotlinsnapshot.kotlinconsumer.AnyTest_anyTest.snap @@ -0,0 +1 @@ +14 \ No newline at end of file diff --git a/KotlinConsumer/build.gradle b/KotlinConsumer/build.gradle new file mode 100644 index 0000000..10687ac --- /dev/null +++ b/KotlinConsumer/build.gradle @@ -0,0 +1,37 @@ +buildscript { + ext.kotlin_version = '1.2.10' + repositories { + mavenCentral() + maven { url uri("../repo") } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.karumi.kotlinsnapshot:plugin:0.2.0' + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.2.60' +} + +group 'com.karumi.kotlinsnapshot' +version '0.2.0' + +apply plugin: 'com.karumi.kotlin-snapshot' + +repositories { + mavenCentral() + maven { url uri("../repo") } +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testCompile "junit:junit:4.11" +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/KotlinConsumer/gradle/wrapper/gradle-wrapper.jar b/KotlinConsumer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..01b8bf6 Binary files /dev/null and b/KotlinConsumer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/KotlinConsumer/gradle/wrapper/gradle-wrapper.properties b/KotlinConsumer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..933b647 --- /dev/null +++ b/KotlinConsumer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-bin.zip diff --git a/KotlinConsumer/gradlew b/KotlinConsumer/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/KotlinConsumer/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/KotlinConsumer/gradlew.bat b/KotlinConsumer/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/KotlinConsumer/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/KotlinConsumer/out/test/classes/META-INF/kotlinconsumer_main.kotlin_module b/KotlinConsumer/out/test/classes/META-INF/kotlinconsumer_main.kotlin_module new file mode 100644 index 0000000..e0dc0c3 Binary files /dev/null and b/KotlinConsumer/out/test/classes/META-INF/kotlinconsumer_main.kotlin_module differ diff --git a/KotlinConsumer/out/test/classes/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.class b/KotlinConsumer/out/test/classes/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.class new file mode 100644 index 0000000..81c7e4c Binary files /dev/null and b/KotlinConsumer/out/test/classes/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.class differ diff --git a/KotlinConsumer/settings.gradle b/KotlinConsumer/settings.gradle new file mode 100644 index 0000000..4f32e3c --- /dev/null +++ b/KotlinConsumer/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'kotlinconsumer' + diff --git a/KotlinConsumer/src/test/kotlin/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.kt b/KotlinConsumer/src/test/kotlin/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.kt new file mode 100644 index 0000000..904fcb6 --- /dev/null +++ b/KotlinConsumer/src/test/kotlin/com/karumi/kotlinsnapshot/kotlinconsumer/AnyTest.kt @@ -0,0 +1,12 @@ +package com.karumi.kotlinsnapshot.kotlinconsumer + +import com.karumi.kotlinsnapshot.matchWithSnapshot +import org.junit.Test + +class AnyTest { + + @Test + fun anyTest() { + 14.matchWithSnapshot() + } +} diff --git a/README.md b/README.md index 0d99e52..d5760cb 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,19 @@ Snapshot testing is an assertion strategy based on the comparision of the instan ## Getting started -Add the dependency to your ```build.gradle``` file: +Add our Gradle Plugin to your ```build.gradle``` file: ``` gradle - testImplementation 'com.karumi:kotlinsnapshot:0.1.0' + buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.karumi.kotlinsnapshot:plugin:0.2.0' + } +} + +apply plugin: 'com.karumi.kotlin-snapshot' ``` Invoke the extension function named ``matchWithSnapshot`` from any instance. The name of the snapshot is not mandatory, if you don't specify it as the first ``matchWithSnapshot`` param the library will infer it from the test execution context. Example: @@ -54,62 +63,18 @@ On subsequent runs, the value will be compared with the snapshot stored in the f ## Updating Snapshots -In order to update and purge snapshots from the command line, you should add a new system property called "updateSnapshots" to your `build.gradle` file: - -``` gradle - -test { - // update snapshots via command line - systemProperty "updateSnapshots", System.getProperty("u") -} -``` - -On Android projects you might need to do this instead: +In order to update snapshots from the command line, you just need to execute one command: -```build.gradle - testOptions { - unitTests.all { - // update snapshots via command line - systemProperty "updateSnapshots", System.getProperty("u") - } - } ``` - -Then, when you want to update the snapshot for a specific test run: -``` bash -./gradlew test -Du=1 --info --tests *MyTest.someTestINeedToUpdate +./gradlew updateSnapshots ``` -Please note that in this example I used "u" but, it can be any other flag you like, depending on how you configure your gradle file. - -On android projects the `test` task might not support the `--tests` flag to filter tests, you might need to run something like `testDebug` instead. It is not necessary to filter tests when updating, but you should do it to avoid inconsistencies. - ## Purging Snapshots -As you rename snapshots, old unused snapshots may remain in your project. You can delete all existing snapshots and rebuild the ones that are actually used using the "purgeSnapshots" system property. The setup is identical to the one used for updating snapshots. - -``` gradle - -test { - // purge snapshots via command line - systemProperty "purgeSnapshots", System.getProperty("p") -} -``` - -Again, android projects may need to handle this differently: +As you rename snapshots, old unused snapshots may remain in your project. You can delete all existing snapshots and rebuild the ones that are actually used using the "purgeSnapshots" gradle task -```build.gradle - testOptions { - unitTests.all { - // update snapshots via command line - systemProperty "purgeSnapshots", System.getProperty("p") - } - } ``` - -Then, when you want to delete the snapshots: -``` bash -./gradlew test -Dp=1 --info +./gradlew purgeSnapshots ``` ## Contributing diff --git a/core/.classpath b/core/.classpath new file mode 100644 index 0000000..f1fe04f --- /dev/null +++ b/core/.classpath @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..107422a --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,4 @@ +.classpath +.project +.settings +bin \ No newline at end of file diff --git a/core/.project b/core/.project new file mode 100644 index 0000000..170595d --- /dev/null +++ b/core/.project @@ -0,0 +1,23 @@ + + + core + Project core created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/core/.settings/org.eclipse.buildship.core.prefs b/core/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..b1886ad --- /dev/null +++ b/core/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/core/bin/com/karumi/kotlinsnapshot/KotlinSnapshot.kt b/core/bin/com/karumi/kotlinsnapshot/KotlinSnapshot.kt new file mode 100644 index 0000000..b8ee52c --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/KotlinSnapshot.kt @@ -0,0 +1,18 @@ +package com.karumi.kotlinsnapshot + +import com.karumi.kotlinsnapshot.core.Camera + +class KotlinSnapshot(snapshotsFolder: String = "") { + + private val camera = Camera(snapshotsFolder) + + fun matchWithSnapshot(value: Any, snapshotName: String? = null) { + camera.matchWithSnapshot(snapshotName, value) + } +} + +private val camera = Camera() + +fun Any.matchWithSnapshot(snapshotName: String? = null) { + camera.matchWithSnapshot(snapshotName, this) +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/Camera.kt b/core/bin/com/karumi/kotlinsnapshot/core/Camera.kt new file mode 100644 index 0000000..ee36e51 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/Camera.kt @@ -0,0 +1,105 @@ +package com.karumi.kotlinsnapshot.core + +import com.karumi.kotlinsnapshot.exceptions.TestNameNotFoundException +import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch +import java.io.File +import java.nio.file.Paths + +class Camera(relativePath: String = "") { + private val snapshotDir: File + private val dmp = DiffMatchPatch() + + init { + snapshotDir = createSnapshotDir(relativePath) + purgeSnapshotsIfNeeded(snapshotDir) + } + + fun matchWithSnapshot(value: Any) { + matchWithSnapshot(null, value) + } + + fun matchWithSnapshot(snapshotName: String? = null, value: Any) { + val snapshotFileName = if (snapshotName != null) + "$snapshotName.snap" + else + "${extractTestCaseName()}.snap" + val snapshotFile = File(snapshotDir, snapshotFileName) + if (snapshotFile.exists()) + matchValueWithExistingSnapshot(snapshotFile, value) + else + writeSnapshot(false, snapshotFile, value) + } + + private val shouldUpdateSnapshots: Boolean by lazy { + System.getProperty("updateSnapshots") == "1" + } + + private fun differsFromSnapshot(diffs: List): Boolean = + diffs.find { diff -> diff.operation != DiffMatchPatch.Operation.EQUAL } != null + + private fun matchValueWithExistingSnapshot(snapshotFile: File, value: Any) { + val snapshotContents = snapshotFile.readText() + val valueString = value.toString() + val diffs = dmp.diffMain(snapshotContents, valueString) + val hasChanged = differsFromSnapshot(diffs) + if (hasChanged && shouldUpdateSnapshots) + writeSnapshot(true, snapshotFile, value) + else if (hasChanged) { + val msg = DiffPrinter.toReadableConsoleMessage(snapshotFile.name, diffs) + throw SnapshotException(diffs, msg) + } + } + + private fun writeSnapshot(update: Boolean, snapshotFile: File, value: Any) { + snapshotFile.writeText(value.toString()) + val fileName = "\"${snapshotFile.name}\"" + val msg = if (update) "${Term.green("1 snapshot, $fileName,")} updated." + else "${Term.green("1 snapshot, $fileName,")} written." + println(msg) + } + + private companion object { + private val purgedDirectories = HashSet() + fun createSnapshotDir(relativePath: String): File { + val dir = System.getProperty("user.dir") + val snapshotDirPath = Paths.get(dir, relativePath, "__snapshot__").toString() + val snapshotDir = File(snapshotDirPath) + snapshotDir.mkdirs() + return snapshotDir + } + + fun purgeSnapshotsIfNeeded(snapshotDir: File) { + val pathToPurge = snapshotDir.absolutePath + val shouldPurge = System.getProperty("purgeSnapshots") == "1" && + !purgedDirectories.contains(pathToPurge) + + if (shouldPurge) { + snapshotDir.deleteAllContainedFiles() + purgedDirectories.add(pathToPurge) + } + } + } + + private fun extractTestCaseName(): String { + val stackTrace = Thread.currentThread().stackTrace + val testCaseTrace = stackTrace.toList().firstOrNull { trace -> + val completeClassName = trace.className.toLowerCase() + val packageName = completeClassName + .substring(0, completeClassName.lastIndexOf(".")) + val isAJUnitClass = packageName + .contains("junit") + val isAGradleClass = packageName.contains("org.gradle.api.internal.tasks.testing") + val isATestClass = completeClassName.contains("test") + val isASpecClass = completeClassName.contains("spec") + (isATestClass || isASpecClass) && !isAJUnitClass && !isAGradleClass + } + if (testCaseTrace != null) { + return "${testCaseTrace.className}_${testCaseTrace.methodName}" + } else { + throw TestNameNotFoundException("Kotlin Snapshot library couldn't find the name " + + "of the test. Review if the test case file or the spec file contains the word " + + "test or spec or specify a snapshot name manually, this is a requirement needed " + + "to use Kotlin Snapshot") + } + } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/CameraTest.kt b/core/bin/com/karumi/kotlinsnapshot/core/CameraTest.kt new file mode 100644 index 0000000..b3750ca --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/CameraTest.kt @@ -0,0 +1,39 @@ +package com.karumi.kotlinsnapshot.core + +import com.karumi.kotlinsnapshot.matchWithSnapshot +import org.junit.Test + +class CameraTest { + + data class User(val id: Int, val name: String) + + @Test + fun should_take_snapshots_of_data_classes() { + val u1 = User(1, "gabriel") + u1.matchWithSnapshot("should take snapshot of a data class") + } + + @Test + fun should_take_snapshots_of_collections() { + val list = listOf( + User(1, "gabriel"), + User(2, "andres"), + User(3, "miguel")) + list.matchWithSnapshot("should take snapshot of a list") + } + + @Test + fun should_take_snapshots_of_maps() { + val map = mapOf( + Pair(1, User(1, "gabriel")), + Pair(2, User(2, "andres")), + Pair(3, User(3, "miguel"))) + map.matchWithSnapshot("should take snapshot of a map") + } + + @Test + fun should_take_snapshots_of_json_strings() { + val json = """{"name":"gabriel","id":5}""" + json.matchWithSnapshot("should take snapshot of a json string") + } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinter.kt b/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinter.kt new file mode 100644 index 0000000..38beb7d --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinter.kt @@ -0,0 +1,28 @@ +package com.karumi.kotlinsnapshot.core + +import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch +import java.util.LinkedList + +internal object DiffPrinter { + private val dmp = DiffMatchPatch() + + private fun printDiff(diff: DiffMatchPatch.Diff): String = + when (diff.operation) { + DiffMatchPatch.Operation.EQUAL, null -> " ${diff.text}\n" + DiffMatchPatch.Operation.DELETE -> Term.green("- ${diff.text}\n") + DiffMatchPatch.Operation.INSERT -> Term.red("+ ${diff.text}\n") + } + + fun toReadableConsoleMessage( + snapshotName: String, + diffs: LinkedList + ): String { + val sb = StringBuilder("${Term.red("Received value")} does not match ${ + Term.green("stored snapshot: \"$snapshotName\"")}\n\n${ + Term.green("-Snapshot")}\n${Term.red("+Received")}\n\n") + + dmp.diffCleanupSemantic(diffs) + diffs.forEach { diff -> sb.append(printDiff(diff)) } + return sb.toString() + } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinterTest.kt b/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinterTest.kt new file mode 100644 index 0000000..9e8deb6 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/DiffPrinterTest.kt @@ -0,0 +1,21 @@ +package com.karumi.kotlinsnapshot.core + +import com.karumi.kotlinsnapshot.matchWithSnapshot +import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch +import org.junit.Test + +class DiffPrinterTest { + val dmp = DiffMatchPatch() + + @Test + fun should_print_diff_to_a_readable_console_message() { + val firstJson = """{"name":"gabriel","id":5}""" + val secondJson = """{"name":"andres","id":5}""" + + val snapshotName = "should print diff to a readable console message" + val diffs = dmp.diffMain(firstJson, secondJson) + val msg = DiffPrinter.toReadableConsoleMessage(snapshotName, diffs) + + msg.matchWithSnapshot(snapshotName) + } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/FileUtils.kt b/core/bin/com/karumi/kotlinsnapshot/core/FileUtils.kt new file mode 100644 index 0000000..866dd5b --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/FileUtils.kt @@ -0,0 +1,7 @@ +package com.karumi.kotlinsnapshot.core + +import java.io.File + +internal fun File.deleteAllContainedFiles() { + this.listFiles().forEach { it.delete() } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/InferenceNameTest.kt b/core/bin/com/karumi/kotlinsnapshot/core/InferenceNameTest.kt new file mode 100644 index 0000000..2ad2aa6 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/InferenceNameTest.kt @@ -0,0 +1,42 @@ +package com.karumi.kotlinsnapshot.core + +import com.karumi.kotlinsnapshot.exceptions.TestNameNotFoundException +import com.karumi.kotlinsnapshot.matchWithSnapshot +import org.junit.Test + +class InferenceNameTest { + + @Test + fun the_snap_test_name_will_be_inferred_in_test_cases_named_with_test_if_it_is_not_specified( + ) { + val pedro = Developer("Pedro", 3) + pedro.matchWithSnapshot() + } + + @Test + fun `the snap test name will be inferred even if uses spaces`() { + val toni = Developer("Toni", 1) + toni.matchWithSnapshot() + } +} + +class InferenceNameSpec { + + @Test + fun the_snap_test_name_will_be_inferred_in_test_cases_named_with_spec_if_it_is_not_specified( + ) { + val sergio = Developer("Sergio", 2) + sergio.matchWithSnapshot() + } +} + +class InvalidClassName { + + @Test(expected = TestNameNotFoundException::class) + fun if_the_test_name_can_not_be_found_and_exception_will_be_thrown() { + val fran = Developer("Fran", 1) + fran.matchWithSnapshot() + } +} + +data class Developer(val name: String, val yearsInTheCompany: Int) diff --git a/core/bin/com/karumi/kotlinsnapshot/core/RelativePathTest.kt b/core/bin/com/karumi/kotlinsnapshot/core/RelativePathTest.kt new file mode 100644 index 0000000..9720c29 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/RelativePathTest.kt @@ -0,0 +1,15 @@ +package com.karumi.kotlinsnapshot.core + +import com.karumi.kotlinsnapshot.KotlinSnapshot +import org.junit.Test + +class RelativePathTest { + private val kotlinSnapshot = + KotlinSnapshot(snapshotsFolder = "src/test/kotlin/com/karumi/kotlinsnapshot/core") + + @Test + fun should_take_snapshots_and_store_them_in_the_provided_relative_path() { + val json = """{"name":"gabriel","id":5}""" + kotlinSnapshot.matchWithSnapshot(json, "should take snapshot of a json string") + } +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/SnapshotException.kt b/core/bin/com/karumi/kotlinsnapshot/core/SnapshotException.kt new file mode 100644 index 0000000..e736e04 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/SnapshotException.kt @@ -0,0 +1,5 @@ +package com.karumi.kotlinsnapshot.core + +import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch + +class SnapshotException(val diffs: List, msg: String) : Exception(msg) diff --git a/core/bin/com/karumi/kotlinsnapshot/core/Term.kt b/core/bin/com/karumi/kotlinsnapshot/core/Term.kt new file mode 100644 index 0000000..f3d763a --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/Term.kt @@ -0,0 +1,10 @@ +package com.karumi.kotlinsnapshot.core + +internal object Term { + private val RESET = "\u001B[0m" + private val GREEN = "\u001B[32m" + private val RED = "\u001B[31m" + + fun green(s: String): String = "$GREEN$s$RESET" + fun red(s: String): String = "$RED$s$RESET" +} diff --git a/core/bin/com/karumi/kotlinsnapshot/core/__snapshot__/should take snapshot of a json string.snap b/core/bin/com/karumi/kotlinsnapshot/core/__snapshot__/should take snapshot of a json string.snap new file mode 100644 index 0000000..6e9c223 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/core/__snapshot__/should take snapshot of a json string.snap @@ -0,0 +1 @@ +{"name":"gabriel","id":5} \ No newline at end of file diff --git a/core/bin/com/karumi/kotlinsnapshot/exceptions/TestNameNotFoundException.kt b/core/bin/com/karumi/kotlinsnapshot/exceptions/TestNameNotFoundException.kt new file mode 100644 index 0000000..9050ff9 --- /dev/null +++ b/core/bin/com/karumi/kotlinsnapshot/exceptions/TestNameNotFoundException.kt @@ -0,0 +1,3 @@ +package com.karumi.kotlinsnapshot.exceptions + +class TestNameNotFoundException(message: String) : RuntimeException(message) diff --git a/core/build.gradle b/core/build.gradle index 74a971c..dfab830 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,5 +1,5 @@ group 'com.karumi.kotlinsnapshot' -version '0.0.1' +version '0.2.0' buildscript { ext.kotlin_version = '1.2.10' @@ -35,6 +35,16 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' } +apply plugin: 'maven' +apply plugin: 'signing' + +install { + repositories { + mavenDeployer { + repository(url: uri('../repo')) + } + } +} compileKotlin { kotlinOptions.jvmTarget = "1.8" diff --git a/core/gradle.properties b/core/gradle.properties new file mode 100644 index 0000000..c716f0e --- /dev/null +++ b/core/gradle.properties @@ -0,0 +1,18 @@ +POM_NAME=core +POM_ARTIFACT_ID=core +POM_PACKAGING=jar + +VERSION_NAME=0.2.0 +VERSION_CODE=000200 +GROUP=com.karumi.kotlinsnapshot + +POM_DESCRIPTION=Snapshot Testing framework for Kotlin +POM_URL=https://github.com/Karumi/KotlinSnapshot +POM_SCM_URL=https://github.com/Karumi/KotlinSnapshot +POM_SCM_CONNECTION=scm:git@github.com:karumi/KotlinSnapshot.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:karumi/KotlinSnapshot.git +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=karumi +POM_DEVELOPER_NAME=Kaurmi \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 9352ac3..c716f0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ -POM_NAME=KotlinSnapshot -POM_ARTIFACT_ID=kotlinsnapshot +POM_NAME=core +POM_ARTIFACT_ID=core POM_PACKAGING=jar -VERSION_NAME=0.1.0 -VERSION_CODE=000100 -GROUP=com.karumi +VERSION_NAME=0.2.0 +VERSION_CODE=000200 +GROUP=com.karumi.kotlinsnapshot POM_DESCRIPTION=Snapshot Testing framework for Kotlin POM_URL=https://github.com/Karumi/KotlinSnapshot diff --git a/plugin/.gitignore b/plugin/.gitignore new file mode 100644 index 0000000..1ce2164 --- /dev/null +++ b/plugin/.gitignore @@ -0,0 +1 @@ +gradle/wrapper \ No newline at end of file diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..d137438 --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,140 @@ +group 'com.karumi.kotlinsnapshot' +version '0.2.0' + +buildscript { + ext.kotlin_version = '1.2.10' + + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin' +apply plugin: 'groovy' + +repositories { + mavenCentral() +} + +dependencies { + compile gradleApi() + compile localGroovy() + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" +} + +compileGroovy.dependsOn = compileGroovy.taskDependencies.values - 'compileJava' +compileKotlin.dependsOn compileGroovy +compileKotlin.classpath += files(compileGroovy.destinationDir) +classes.dependsOn compileKotlin +compileKotlin { + kotlinOptions.jvmTarget = "1.8" + classpath = classpath.plus(files(compileGroovy.destinationDir)) +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +apply plugin: 'maven' +apply plugin: 'signing' + +install { + repositories { + mavenDeployer { + repository(url: uri('../repo')) + } + } +} + +def isReleaseBuild() { + return VERSION_NAME.contains("SNAPSHOT") == false +} + +def getRepositoryUsername() { + return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" +} + +def getRepositoryPassword() { + return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" +} + +signing { + required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives +} + +task javadocJar(type: Jar) { + classifier = 'javadoc' + from javadoc +} + +task sourcesJar(type: Jar) { + classifier = 'sources' + from sourceSets.main.allSource +} + +artifacts { + archives javadocJar, sourcesJar +} + +uploadArchives { + repositories { + mavenDeployer { + // POM signature + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + // Target repository + repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) + } + + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + pom.project { + name POM_NAME + description POM_DESCRIPTION + packaging POM_PACKAGING + url POM_URL + + scm { + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION + url POM_SCM_URL + } + + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST + } + } + + developers { + developer { + id = POM_DEVELOPER_ID + name = POM_DEVELOPER_NAME + } + } + } + } + } +} + +sourceSets { + test { + output.resourcesDir = "build/classes/test/resources" + groovy { + srcDirs = ['test/groovy'] + } + } + main { + groovy { + srcDirs = ['src/main/groovy'] + } + } +} + diff --git a/plugin/gradle.properties b/plugin/gradle.properties new file mode 100644 index 0000000..4e3c77e --- /dev/null +++ b/plugin/gradle.properties @@ -0,0 +1,18 @@ +POM_NAME=plugin +POM_ARTIFACT_ID=plugin +POM_PACKAGING=jar + +VERSION_NAME=0.2.0 +VERSION_CODE=000200 +GROUP=com.karumi.kotlinsnapshot + +POM_DESCRIPTION=Snapshot Testing framework for Kotlin +POM_URL=https://github.com/Karumi/KotlinSnapshot +POM_SCM_URL=https://github.com/Karumi/KotlinSnapshot +POM_SCM_CONNECTION=scm:git@github.com:karumi/KotlinSnapshot.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:karumi/KotlinSnapshot.git +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=karumi +POM_DEVELOPER_NAME=Kaurmi \ No newline at end of file diff --git a/plugin/src/main/groovy/com/karumi/kotlinsnapshot/plugin/GroovyHack.groovy b/plugin/src/main/groovy/com/karumi/kotlinsnapshot/plugin/GroovyHack.groovy new file mode 100644 index 0000000..c7715d8 --- /dev/null +++ b/plugin/src/main/groovy/com/karumi/kotlinsnapshot/plugin/GroovyHack.groovy @@ -0,0 +1,35 @@ +package com.karumi.kotlinsnapshot.plugin + +import org.gradle.api.Project + +class GroovyHack { + + static void configureProjectForSnapshotUpdate(Project project) { + def androidExtension = project.extensions.findByName("android") + if (androidExtension != null) { + androidExtension.testOptions.unitTests.all { + systemProperty "updateSnapshots", "1" + } + } + + project.test { + if (["systemProperty"] != null) { + systemProperty "updateSnapshots", "1" + } + } + } + + static void configureProjectForSnapshotPurge(Project project) { + def androidExtension = project.extensions.findByName("android") + if (androidExtension != null) { + androidExtension.testOptions.unitTests.all { + systemProperty "purgeSnapshots", "1" + } + } + project.test { + if (["systemProperty"] != null) { + systemProperty "purgeSnapshots", "1" + } + } + } +} diff --git a/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/KotlinSnapshotPlugin.kt b/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/KotlinSnapshotPlugin.kt new file mode 100644 index 0000000..710b41a --- /dev/null +++ b/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/KotlinSnapshotPlugin.kt @@ -0,0 +1,42 @@ +package com.karumi.kotlinsnapshot.plugin + +import com.karumi.kotlinsnapshot.plugin.task.KotlinSnapshotTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.DependencyResolutionListener +import org.gradle.api.artifacts.ResolvableDependencies + +open class KotlinSnapshotPlugin : Plugin { + override fun apply(target: Project?) { + target ?: return + + addKotlinSnapshotDependency(target) + target.afterEvaluate { addTasks(target) } + } + + private fun addTasks(project: Project) { + val updateSnapshotsTask = project.tasks.create( + KotlinSnapshotTask.UpdateSnapshots.name, + KotlinSnapshotTask.UpdateSnapshots::class.java) + val purgeSnapshotsTask = project.tasks.create( + KotlinSnapshotTask.PurgeSnapshots.name, + KotlinSnapshotTask.PurgeSnapshots::class.java) + + val testTask = project.tasks.findByName("test") + updateSnapshotsTask.finalizedBy(testTask) + purgeSnapshotsTask.finalizedBy(testTask) + } + + private fun addKotlinSnapshotDependency(project: Project) { + project.gradle.addListener(object : DependencyResolutionListener { + override fun beforeResolve(dependencies: ResolvableDependencies?) { + val dependency = project.dependencies + .create("com.karumi.kotlinsnapshot:core:0.2.0") + project.dependencies.add("testImplementation", dependency) + project.gradle.removeListener(this) + } + + override fun afterResolve(dependencies: ResolvableDependencies?) {} + }) + } +} diff --git a/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/task/Tasks.kt b/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/task/Tasks.kt new file mode 100644 index 0000000..85c8c3e --- /dev/null +++ b/plugin/src/main/kotlin/com/karumi/kotlinsnapshot/plugin/task/Tasks.kt @@ -0,0 +1,42 @@ +package com.karumi.kotlinsnapshot.plugin.task + +import com.karumi.kotlinsnapshot.plugin.GroovyHack +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction + +sealed class KotlinSnapshotTask : DefaultTask() { + + init { + group = "KotlinSnapshot" + } + + open class UpdateSnapshots : KotlinSnapshotTask() { + companion object { + const val name = "updateSnapshots" + } + + init { + description = "Run all tests updating snapshots when differences are found." + } + + @TaskAction + fun updateSnapshots() { + GroovyHack.configureProjectForSnapshotUpdate(project) + } + } + + open class PurgeSnapshots : KotlinSnapshotTask() { + companion object { + const val name = "purgeSnapshots" + } + + init { + description = "Delete all snapshots and update them all" + } + + @TaskAction + fun purgeSnapshots() { + GroovyHack.configureProjectForSnapshotPurge(project) + } + } +} diff --git a/plugin/src/main/resources/META-INF/gradle-plugins/com.karumi.kotlin-snapshot.properties b/plugin/src/main/resources/META-INF/gradle-plugins/com.karumi.kotlin-snapshot.properties new file mode 100644 index 0000000..2843c30 --- /dev/null +++ b/plugin/src/main/resources/META-INF/gradle-plugins/com.karumi.kotlin-snapshot.properties @@ -0,0 +1 @@ +implementation-class=com.karumi.kotlinsnapshot.plugin.KotlinSnapshotPlugin \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index dd6f9b4..fc6c0a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include 'core' +include ':core', ':plugin'