├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main └── groovy │ └── com │ └── sourcemuse │ └── gradle │ └── plugin │ ├── GradleMongoPluginExtension.groovy │ ├── LogDestination.groovy │ └── flapdoodle │ ├── adapters │ ├── CustomFlapdoodleProcessLogger.groovy │ ├── CustomFlapdoodleRuntimeConfig.groovy │ ├── FileOutputStreamProcessor.groovy │ ├── ProcessOutputFactory.groovy │ ├── StorageFactory.groovy │ └── VersionFactory.groovy │ └── gradle │ └── GradleMongoPlugin.groovy └── test └── groovy └── com └── sourcemuse └── gradle └── plugin ├── BuildScriptBuilder.groovy ├── GradleMongoPluginExtensionSpec.groovy ├── MongoPluginConfigSpec.groovy ├── MongoPluginTasksSpec.groovy └── MongoUtils.groovy /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | # IntelliJ IDEA 3 | ################# 4 | .idea/* 5 | *.iml 6 | *.ipr 7 | *.iws 8 | out/* 9 | 10 | ################# 11 | # Gradle 12 | ################# 13 | build/* 14 | .gradle/* 15 | 16 | ################# 17 | # Eclipse 18 | ################# 19 | .classpath 20 | .project 21 | .settings/ 22 | bin/ 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: groovy 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## 1.0.6 - 2018-02-03 5 | ### Fixed 6 | - Fixed bug where trying to stop the Mongo process would sometimes cause an exception. 7 | ## 1.0.5 - 2018-01-18 8 | ### Changed 9 | - `startManagedMongoDb` now leaves pre-existing Mongo instances running after the build completes. 10 | ## 1.0.4 - 2018-01-17 11 | ### Added 12 | - Added Mongo 3.6 support by upgrading to latest version of Flapdoodle. 13 | - Made mongod command-line options configurable. 14 | - Made Mongo access control configurable. 15 | - Made MongoDb server params configurable. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Mashooq Badar, Robert Taylor 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions 13 | and limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Gradle Mongo Plugin ## 2 | 3 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.sourcemuse.gradle.plugin/gradle-mongo-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.sourcemuse.gradle.plugin/gradle-mongo-plugin) 4 | [![Build Status](https://travis-ci.org/sourcemuse/GradleMongoPlugin.svg?branch=master)](https://travis-ci.org/sourcemuse/GradleMongoPlugin) 5 | 6 | The Gradle Mongo Plugin allows you to run a managed instance of Mongo from your gradle build. 7 | 8 | It's available on both [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22gradle-mongo-plugin%22) and the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/com.sourcemuse.mongo). 9 | 10 | ### Usage ### 11 | 12 | Enable the plugin in your gradle build: 13 | 14 | ```groovy 15 | plugins { 16 | id 'com.sourcemuse.mongo' version '2.0.0' 17 | } 18 | ``` 19 | 20 | Hey presto, you can now declare a dependency on a running mongo instance from any of your tasks: 21 | 22 | ```groovy 23 | task integrationTest(type: Test) { 24 | runWithMongoDb = true 25 | } 26 | 27 | ``` 28 | 29 | ### Configuration ### 30 | 31 | Configure your Mongo instances inside a ```mongo``` block: 32 | 33 | ``` 34 | mongo { 35 | port 12345 36 | logging 'console' 37 | ... 38 | } 39 | ``` 40 | 41 | The `mongo` configuration block can be declared at either the project or the task level. Task-level configuration inherits from any project-level configuration provided. 42 | 43 | The following properties are configurable: 44 | 45 | * ```args```: A map of mongod command-line options (value can be empty for arguments without values) (defaults to **[:]**) 46 | * ```artifactStorePath``` The location where Mongo will be downloaded to 47 | * ```auth```: Enables [access control](https://docs.mongodb.com/manual/tutorial/enable-authentication/) (defaults to **false**) 48 | * ```bindIp```: The ip address Mongo binds itself to (defaults to **'127.0.0.1'**) 49 | * ```downloadUrl```: The URL from where Mongo will be downloaded 50 | * ```journalingEnabled```: Toggles journaling (defaults to **false**) 51 | * ```logFilePath```: The desired log file path (defaults to **'embedded-mongo.log'**) 52 | * ```logging```: The type of logging to be produced: **'console'**, **'file'** or **'none'** (defaults to **'file'**) 53 | * ```mongodVerbosity```: The verbosity level of the mongod process. Supported options are as per the [mongod configuration documentation](http://docs.mongodb.org/manual/reference/program/mongod/#cmdoption-verbose) (default level is non-verbose) 54 | * ```mongoVersion```: The version of Mongo to run. Can be **'DEVELOPMENT'** or **'PRODUCTION'** for the latest versions of the respective branch, or take the form **'1.2-LATEST'** or **'1.2.3'** for specific versions 55 | * ```params```: A map of [MongoDB Server Parameters](https://docs.mongodb.com/manual/reference/parameters/) (defaults to **[:]**) 56 | * ```port```: The port Mongo will listen on (defaults to **27017**). For random port assignment, set this value to **'RANDOM'** (the actual port value used will be available during the build through the **project.mongo.port** property) 57 | * ```proxyHost```: The proxy host name for Mongo downloads 58 | * ```proxyPort```: The proxy port for Mongo downloads 59 | * ```storageEngine```: The name of the storage engine to use. Can be **'wiredTiger'** or **'mmapv1'** for MongoDB Community Edition (default is **'wiredTiger'** for Mongo 3.2 and later; otherwise it is **'mmapv1'**). Alternative distributions might support additional engines 60 | * ```storageLocation```: The directory location from where embedded Mongo will run, such as ```/tmp/storage``` (defaults to a java temp directory) 61 | * ```syncDelay```: The interval in seconds between fsync operations where mongod flushes its working memory to disk. See [syncdelay parameter](https://docs.mongodb.com/manual/reference/parameters/#param.syncdelay) for more information 62 | 63 | ### Tasks ### 64 | 65 | For your convenience the plugin also adds the following tasks to your buildscript: 66 | 67 | ``` 68 | $ gradle tasks 69 | ... 70 | Mongo tasks 71 | ----------- 72 | startManagedMongoDb - Starts a local MongoDb instance which will stop when the build process completes 73 | startMongoDb - Starts a local MongoDb instance 74 | stopMongoDb - Stops the local MongoDb instance 75 | ... 76 | ``` 77 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.api.tasks.wrapper.Wrapper.DistributionType 2 | 3 | buildscript { 4 | repositories { 5 | maven { 6 | url 'https://plugins.gradle.org/m2/' 7 | } 8 | } 9 | } 10 | 11 | plugins { 12 | id 'com.gradle.plugin-publish' version '1.0.0' 13 | id 'maven-publish' 14 | id 'groovy' 15 | id 'idea' 16 | id 'eclipse' 17 | id 'signing' 18 | id 'java-gradle-plugin' 19 | } 20 | 21 | group = 'com.sourcemuse.gradle.plugin' 22 | version = '2.0.1-SNAPSHOT' 23 | 24 | sourceCompatibility = 17 25 | targetCompatibility = 17 26 | 27 | repositories { 28 | mavenCentral() 29 | } 30 | 31 | dependencies { 32 | implementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:3.4.6' 33 | implementation 'org.mongodb:mongo-java-driver:3.12.11' 34 | 35 | testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0', { 36 | exclude module: 'groovy-all' 37 | } 38 | testImplementation 'org.littleshoot:littleproxy:1.1.2' 39 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' 40 | } 41 | 42 | task javadocJar(type: Jar, dependsOn: javadoc) { 43 | classifier = 'javadoc' 44 | from 'build/docs/javadoc' 45 | } 46 | 47 | task sourcesJar(type: Jar) { 48 | classifier = 'sources' 49 | from sourceSets.main.allSource 50 | } 51 | 52 | artifacts { 53 | archives jar 54 | archives javadocJar 55 | archives sourcesJar 56 | } 57 | 58 | publishing { 59 | publications { 60 | mavenJava(MavenPublication) { 61 | 62 | pom { 63 | artifactId = 'gradle-mongo-plugin' 64 | name = 'Gradle Mongo Plugin' 65 | description = 'Gradle plugin for managing a local instance of MongoDb' 66 | url = 'https://github.com/sourcemuse/GradleMongoPlugin' 67 | 68 | licenses { 69 | license { 70 | name = 'The Apache License, Version 2.0' 71 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 72 | } 73 | } 74 | developers { 75 | developer { 76 | id = 'roberttaylor426' 77 | name = 'Robert Taylor' 78 | } 79 | developer { 80 | id = 'flashboss' 81 | name = 'Luca Stancapiano' 82 | email = 'luca.stancapiano@vige.it' 83 | } 84 | } 85 | scm { 86 | connection = 'scm:git@github.com:sourcemuse/GradleMongoPlugin.git' 87 | developerConnection = 'scm:git@github.com:sourcemuse/GradleMongoPlugin.git' 88 | url = 'scm:git@github.com:sourcemuse/GradleMongoPlugin.git' 89 | } 90 | } 91 | } 92 | } 93 | repositories { 94 | maven { 95 | name = 'ossSonatype' 96 | credentials(PasswordCredentials) 97 | def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 98 | def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots/' 99 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 100 | } 101 | } 102 | } 103 | 104 | signing { 105 | required { !version.endsWith('SNAPSHOT') } 106 | sign publishing.publications.mavenJava 107 | } 108 | 109 | pluginBundle { 110 | website = 'https://github.com/sourcemuse/GradleMongoPlugin' 111 | vcsUrl = 'https://github.com/sourcemuse/GradleMongoPlugin' 112 | tags = ['mongo', 'mongodb'] 113 | } 114 | 115 | gradlePlugin { 116 | plugins { 117 | mongoPlugin { 118 | id = 'com.sourcemuse.mongo' 119 | displayName = 'Gradle Mongo plugin' 120 | description = 'Gradle plugin for running a managed instance of Mongo.' 121 | implementationClass = 'com.sourcemuse.gradle.plugin.flapdoodle.gradle.GradleMongoPlugin' 122 | } 123 | } 124 | } 125 | 126 | test { 127 | useJUnitPlatform() 128 | testLogging { 129 | events "passed", "skipped", "failed" 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcemuse/GradleMongoPlugin/e1a98d624a69f71954221ccee9d878b0df6b667e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | import com.mongodb.ServerAddress 4 | 5 | import java.util.regex.Pattern 6 | 7 | import static com.sourcemuse.gradle.plugin.LogDestination.FILE 8 | import static java.lang.Integer.parseInt 9 | 10 | class GradleMongoPluginExtension { 11 | 12 | static final EPHEMERAL_TEMPORARY_FOLDER = null 13 | static final Pattern VALID_MONGOD_VERBOSITY_FORMAT = ~/(?i)-?v+|-{0,2}verbose/ 14 | 15 | private int port = ServerAddress.defaultPort() 16 | String bindIp = ServerAddress.defaultHost() 17 | boolean journalingEnabled = false 18 | String logging = FILE as String 19 | String logFilePath = 'embedded-mongo.log' 20 | String mongoVersion = 'PRODUCTION' 21 | String storageEngine = 'wiredTiger' 22 | String storageLocation = EPHEMERAL_TEMPORARY_FOLDER 23 | String mongodVerbosity = '' 24 | String downloadUrl = '' 25 | String proxyHost = '' 26 | int proxyPort = 80 27 | String artifactStorePath = '' 28 | boolean auth = false 29 | Map args = [:] 30 | Map params = [:] 31 | Integer syncDelay = null 32 | 33 | void setDownloadUrl(String url) { 34 | try { 35 | this.downloadUrl = new URL(url).toString() 36 | } catch (MalformedURLException ignored) { 37 | throw new IllegalArgumentException("DownloadURL ${url} is not a valid URL.") 38 | } 39 | } 40 | 41 | void setDownloadURL(String url) { 42 | println "Warning: 'downloadURL' has been deprecated. It has been renamed to 'downloadUrl' " + 43 | "and will no longer be supported in a future release." 44 | setDownloadUrl(url) 45 | } 46 | 47 | int getPort() { 48 | port 49 | } 50 | 51 | void setPort(Object port) { 52 | if (port instanceof String) { 53 | this.port = parsePortAsString(port) 54 | } else { 55 | this.port = port as Integer 56 | } 57 | } 58 | 59 | void setMongodVerbosity(String mongodVerbosity) { 60 | this.mongodVerbosity = parseMongodVerbosity(mongodVerbosity) 61 | } 62 | 63 | private static Serializable parseMongodVerbosity(String mongodVerbosity) { 64 | if (!(mongodVerbosity ==~ VALID_MONGOD_VERBOSITY_FORMAT)) 65 | throw new IllegalArgumentException("MongodVerbosity should be defined as either '-verbose' or '-v(vvvv)'. " + 66 | "Do not configure this property if you don't wish to have verbose output.") 67 | 68 | def lowerCaseValue = mongodVerbosity.toLowerCase() 69 | 70 | if (lowerCaseValue.endsWith('verbose')) return '-v' 71 | if (lowerCaseValue.startsWith('v')) return "-$lowerCaseValue" 72 | return lowerCaseValue 73 | } 74 | 75 | private static int parsePortAsString(String port) { 76 | if (port.toLowerCase() == 'random') { 77 | return randomAvailablePort() 78 | } 79 | 80 | return parseInt(port) 81 | } 82 | 83 | private static int randomAvailablePort() { 84 | try { 85 | ServerSocket server = new ServerSocket() 86 | server.setReuseAddress(true) 87 | server.bind(new InetSocketAddress(0)) 88 | int port = server.getLocalPort() 89 | server.close() 90 | return port 91 | } catch (IOException e) { 92 | throw new IOException('Failed to find random free port', e) 93 | } 94 | } 95 | 96 | GradleMongoPluginExtension overrideWith(GradleMongoPluginExtension pluginExtensionOverride) { 97 | def mergedPluginExtension = new GradleMongoPluginExtension() 98 | applyNonDefaultProperties(this, mergedPluginExtension) 99 | applyNonDefaultProperties(pluginExtensionOverride, mergedPluginExtension) 100 | mergedPluginExtension 101 | } 102 | 103 | private 104 | static void applyNonDefaultProperties(GradleMongoPluginExtension sourcePluginExtension, GradleMongoPluginExtension targetPluginExtension) { 105 | def pluginExtensionWithDefaults = new GradleMongoPluginExtension() 106 | pluginExtensionWithDefaults.properties.findAll { it.key != 'class' }.each { String key, Object value -> 107 | if (sourcePluginExtension[key] != value) { 108 | targetPluginExtension[key] = sourcePluginExtension[key] 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/LogDestination.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | enum LogDestination { 4 | CONSOLE, FILE, NONE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleProcessLogger.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | 3 | import de.flapdoodle.embed.process.distribution.Version 4 | import de.flapdoodle.embed.process.io.progress.ProgressListener 5 | 6 | 7 | class CustomFlapdoodleProcessLogger implements ProgressListener { 8 | private final Version version 9 | 10 | CustomFlapdoodleProcessLogger(Version version) { 11 | this.version = version 12 | } 13 | 14 | @Override 15 | void progress(String label, int percent) { 16 | } 17 | 18 | @Override 19 | void done(String label) { 20 | } 21 | 22 | @Override 23 | void start(String label) { 24 | if (label.contains('Download')) { 25 | println "Downloading Mongo ${version.asInDownloadPath()} distribution..." 26 | } else if (label.contains('Extract')) { 27 | println 'Extracting Mongo binaries...' 28 | } 29 | } 30 | 31 | @Override 32 | void info(String label, String message) { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/CustomFlapdoodleRuntimeConfig.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | import de.flapdoodle.embed.mongo.config.Defaults.DownloadConfigDefaults 3 | import de.flapdoodle.embed.mongo.config.Defaults.RuntimeConfigDefaults 4 | import de.flapdoodle.embed.mongo.packageresolver.Command 5 | import de.flapdoodle.embed.process.config.ImmutableRuntimeConfig 6 | import de.flapdoodle.embed.process.config.store.HttpProxyFactory 7 | import de.flapdoodle.embed.process.config.store.ImmutableDownloadConfig 8 | import de.flapdoodle.embed.process.distribution.Distribution 9 | import de.flapdoodle.embed.process.distribution.Version 10 | import de.flapdoodle.embed.process.io.directories.FixedPath 11 | import de.flapdoodle.embed.process.runtime.CommandLinePostProcessor 12 | import de.flapdoodle.embed.process.store.ArtifactStore 13 | import de.flapdoodle.embed.process.store.Downloader 14 | 15 | class CustomFlapdoodleRuntimeConfig extends RuntimeConfigDefaults { 16 | private final Version version 17 | private final String mongodVerbosity 18 | private final String downloadUrl 19 | private final String proxyHost 20 | private final int proxyPort 21 | private final String artifactStorePath 22 | 23 | CustomFlapdoodleRuntimeConfig(Version version, 24 | String mongodVerbosity, 25 | String downloadUrl, 26 | String proxyHost, 27 | int proxyPort, 28 | String artifactStorePath) { 29 | this.version = version 30 | this.mongodVerbosity = mongodVerbosity 31 | this.downloadUrl = downloadUrl 32 | this.proxyHost = proxyHost 33 | this.proxyPort = proxyPort 34 | this.artifactStorePath = artifactStorePath 35 | } 36 | 37 | ImmutableRuntimeConfig.Builder defaults(Command command) { 38 | ImmutableRuntimeConfig.Builder runtimeConfigBuilder = super.defaults(command) 39 | 40 | ImmutableDownloadConfig.Builder downloadConfigBuilder = new DownloadConfigDefaults().defaultsForCommand(command) 41 | downloadConfigBuilder.progressListener(new CustomFlapdoodleProcessLogger(version)) 42 | 43 | if (downloadUrl) { 44 | downloadConfigBuilder.downloadPath(downloadUrl) 45 | } 46 | 47 | if (proxyHost) { 48 | downloadConfigBuilder.proxyFactory(new HttpProxyFactory(proxyHost, proxyPort)) 49 | } 50 | 51 | if (artifactStorePath) { 52 | downloadConfigBuilder.artifactStorePath(new FixedPath(artifactStorePath)) 53 | } 54 | 55 | runtimeConfigBuilder.commandLinePostProcessor(new CommandLinePostProcessor() { 56 | @Override 57 | List process(Distribution distribution, List args) { 58 | if (mongodVerbosity) args.add(mongodVerbosity) 59 | return args 60 | } 61 | }) 62 | 63 | runtimeConfigBuilder.artifactStore(ArtifactStore.builder() 64 | .downloadConfig(downloadConfigBuilder.build()) 65 | .downloader(Downloader.platformDefault()) 66 | .tempDirFactory(downloadConfigBuilder.artifactStorePath) 67 | .executableNaming(downloadConfigBuilder.fileNaming) 68 | .build()) 69 | 70 | runtimeConfigBuilder 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/FileOutputStreamProcessor.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | 3 | import de.flapdoodle.embed.process.io.StreamProcessor 4 | 5 | class FileOutputStreamProcessor implements StreamProcessor { 6 | File logFile 7 | 8 | FileOutputStreamProcessor(String filePath) { 9 | logFile = new File(filePath) 10 | if (logFile.parentFile) { 11 | logFile.parentFile.mkdirs() 12 | } 13 | logFile.createNewFile() 14 | } 15 | 16 | @Override 17 | void process(String block) { 18 | logFile.text += block 19 | } 20 | 21 | @Override 22 | void onProcessed() { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/ProcessOutputFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | 3 | import static com.sourcemuse.gradle.plugin.LogDestination.CONSOLE 4 | import static com.sourcemuse.gradle.plugin.LogDestination.FILE 5 | import static com.sourcemuse.gradle.plugin.LogDestination.NONE 6 | 7 | import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension 8 | import com.sourcemuse.gradle.plugin.LogDestination 9 | import de.flapdoodle.embed.mongo.packageresolver.Command 10 | import de.flapdoodle.embed.mongo.config.MongodProcessOutputConfig 11 | import de.flapdoodle.embed.process.config.process.ProcessOutput 12 | import de.flapdoodle.embed.process.io.NamedOutputStreamProcessor 13 | import de.flapdoodle.embed.process.io.NullProcessor 14 | import de.flapdoodle.embed.process.io.Processors 15 | import de.flapdoodle.embed.process.io.Slf4jLevel 16 | import groovy.transform.TupleConstructor 17 | import org.gradle.api.GradleScriptException 18 | import org.gradle.api.Project 19 | 20 | @TupleConstructor 21 | class ProcessOutputFactory { 22 | Project project 23 | 24 | ProcessOutput getProcessOutput(GradleMongoPluginExtension pluginExtension) { 25 | def logDestination = pluginExtension.logging.toUpperCase() as LogDestination 26 | 27 | if (logDestination == CONSOLE) { 28 | return MongodProcessOutputConfig.getDefaultInstance(Command.MongoD) 29 | } 30 | 31 | if (logDestination == FILE) { 32 | def logFile = new File(pluginExtension.logFilePath) 33 | def logFilePath = logFile.isAbsolute() ? logFile.absolutePath : 34 | createRelativeFilePathFromBuildDir(logFile) 35 | 36 | def fileOutputStreamProcessor = new FileOutputStreamProcessor(logFilePath) 37 | 38 | return ProcessOutput.builder() 39 | .output(new NamedOutputStreamProcessor('[mongod output]', fileOutputStreamProcessor)) 40 | .error(new NamedOutputStreamProcessor('[mongod error]', fileOutputStreamProcessor)) 41 | .commands(new NamedOutputStreamProcessor('[mongod commands]', fileOutputStreamProcessor)) 42 | .build() 43 | } 44 | 45 | if (logDestination == NONE) { 46 | def nullProcessor = new NullProcessor() 47 | return ProcessOutput.builder() 48 | .output(nullProcessor) 49 | .error(nullProcessor) 50 | .commands(nullProcessor) 51 | .build() 52 | } 53 | 54 | throw new GradleScriptException( 55 | "Unrecognized 'logging' option: ${pluginExtension.logging}. " + 56 | "Choose one of ${LogDestination.values().collect { it.toString().toLowerCase() }.join(', ')}.", 57 | new IllegalArgumentException() 58 | ) 59 | } 60 | 61 | private String createRelativeFilePathFromBuildDir(File logFile) { 62 | project.buildDir.absolutePath + File.separatorChar + logFile.path 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/StorageFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | 3 | import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension 4 | import de.flapdoodle.embed.mongo.config.Storage 5 | 6 | class StorageFactory { 7 | Storage getStorage(GradleMongoPluginExtension extension) { 8 | if(extension.storageLocation){ 9 | new Storage(extension.storageLocation, null, 0) 10 | } else { 11 | new Storage() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/adapters/VersionFactory.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.adapters 2 | 3 | import static de.flapdoodle.embed.mongo.distribution.Version.Main.DEVELOPMENT 4 | import static de.flapdoodle.embed.mongo.distribution.Version.Main.PRODUCTION 5 | 6 | import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension 7 | import de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion 8 | import de.flapdoodle.embed.process.distribution.Version.GenericVersion 9 | import de.flapdoodle.embed.mongo.packageresolver.Feature 10 | import de.flapdoodle.embed.mongo.distribution.Version 11 | import de.flapdoodle.embed.mongo.distribution.Versions 12 | import de.flapdoodle.embed.mongo.distribution.Versions.GenericFeatureAwareVersion 13 | 14 | 15 | class VersionFactory { 16 | 17 | static final String LATEST_VERSION = '-LATEST' 18 | 19 | IFeatureAwareVersion getVersion(GradleMongoPluginExtension pluginExtension) { 20 | def suppliedVersion = pluginExtension.mongoVersion 21 | 22 | if (versionIsDevOrProd(suppliedVersion)) { 23 | return suppliedVersion as Version.Main 24 | } 25 | 26 | return parseVersionNumber(suppliedVersion) 27 | } 28 | 29 | private static boolean versionIsDevOrProd(String suppliedVersion) { 30 | try { 31 | (suppliedVersion as Version.Main) in [DEVELOPMENT, PRODUCTION] 32 | } catch (ignored) {} 33 | } 34 | 35 | private static IFeatureAwareVersion parseVersionNumber(String suppliedVersion) { 36 | String mongoVersion = convertToFlapdoodleVersion(suppliedVersion) 37 | 38 | if (mongoVersion.endsWith(LATEST_VERSION)) { 39 | mongoVersion = mongoVersion.substring(0, mongoVersion.length() - LATEST_VERSION.length()) 40 | 41 | if (versionMatchesMainBranchVersion(mongoVersion)) { 42 | return mongoVersion as Version.Main 43 | } 44 | } else if (versionMatchesSpecificVersion(mongoVersion)) { 45 | return mongoVersion as Version 46 | } else { 47 | // we'll just assume that the version is newer and supports all features 48 | return new GenericFeatureAwareVersion(GenericVersion.of(suppliedVersion)) 49 | } 50 | } 51 | 52 | private static boolean versionMatchesMainBranchVersion(String mongoVersion) { 53 | try { 54 | mongoVersion in Version.Main.values().collect { it.toString() } 55 | } catch (ignored) {} 56 | } 57 | 58 | private static String convertToFlapdoodleVersion(String suppliedVersion) { 59 | def mongoVersion = 'V' + suppliedVersion 60 | mongoVersion = mongoVersion.replace('.', '_') 61 | mongoVersion 62 | } 63 | 64 | private static Version versionMatchesSpecificVersion(String mongoVersion) { 65 | try { 66 | mongoVersion as Version 67 | } catch (ignored) {} 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/groovy/com/sourcemuse/gradle/plugin/flapdoodle/gradle/GradleMongoPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin.flapdoodle.gradle 2 | 3 | import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.CONTINUE_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS 4 | import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.ManageProcessInstruction.STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS 5 | 6 | import java.util.concurrent.atomic.AtomicInteger 7 | 8 | import org.bson.Document 9 | import org.gradle.BuildListener 10 | import org.gradle.BuildResult 11 | import org.gradle.api.Plugin 12 | import org.gradle.api.Project 13 | import org.gradle.api.Task 14 | import org.gradle.api.initialization.Settings 15 | import org.gradle.api.invocation.Gradle 16 | import org.gradle.api.tasks.TaskState 17 | 18 | import com.mongodb.MongoClient 19 | import com.sourcemuse.gradle.plugin.GradleMongoPluginExtension 20 | import com.sourcemuse.gradle.plugin.flapdoodle.adapters.CustomFlapdoodleRuntimeConfig 21 | import com.sourcemuse.gradle.plugin.flapdoodle.adapters.ProcessOutputFactory 22 | import com.sourcemuse.gradle.plugin.flapdoodle.adapters.StorageFactory 23 | import com.sourcemuse.gradle.plugin.flapdoodle.adapters.VersionFactory 24 | 25 | import de.flapdoodle.embed.mongo.MongodProcess 26 | import de.flapdoodle.embed.mongo.MongodStarter 27 | import de.flapdoodle.embed.mongo.config.ImmutableMongoCmdOptions 28 | import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig 29 | import de.flapdoodle.embed.mongo.config.Net 30 | import de.flapdoodle.embed.mongo.packageresolver.Command 31 | import de.flapdoodle.embed.mongo.runtime.Mongod 32 | import de.flapdoodle.embed.process.runtime.Network 33 | 34 | class GradleMongoPlugin implements Plugin { 35 | 36 | static final String PLUGIN_EXTENSION_NAME = 'mongo' 37 | static final String TASK_GROUP_NAME = 'Mongo' 38 | 39 | @Override 40 | void apply(Project project) { 41 | configureTaskProperties(project) 42 | 43 | addStartManagedMongoDbTask(project) 44 | addStartMongoDbTask(project) 45 | addStopMongoDbTask(project) 46 | 47 | extendAllTasksWithMongoOptions(project) 48 | 49 | project.afterEvaluate { 50 | configureTasksRequiringMongoDb(project) 51 | } 52 | } 53 | 54 | private static void configureTaskProperties(Project project) { 55 | project.extensions.create(PLUGIN_EXTENSION_NAME, GradleMongoPluginExtension) 56 | project.getRootProject().extensions.extraProperties.set("mongoPortToProcessMap", new HashMap()) 57 | } 58 | 59 | private static void addStartManagedMongoDbTask(Project project) { 60 | project.task(group: TASK_GROUP_NAME, description: 'Starts a local MongoDb instance which will stop when the build process completes', 'startManagedMongoDb').doFirst { 61 | def mongoStartedByTask = startMongoDb(project, STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) 62 | 63 | if (mongoStartedByTask) { 64 | ensureMongoDbStopsEvenIfGradleDaemonIsRunning(project) 65 | } 66 | } 67 | } 68 | 69 | private static void addStartMongoDbTask(Project project) { 70 | project.task(group: TASK_GROUP_NAME, description: 'Starts a local MongoDb instance', 'startMongoDb').doFirst { 71 | startMongoDb(project, CONTINUE_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) 72 | } 73 | } 74 | 75 | private static void ensureMongoDbStopsEvenIfGradleDaemonIsRunning(Project project) { 76 | boolean stopMongoDbTaskPresent = project.gradle.taskGraph.allTasks.find { it.name == 'stopMongoDb' } 77 | 78 | if (!stopMongoDbTaskPresent) { 79 | def lastTask = project.gradle.taskGraph.allTasks[-1] 80 | 81 | project.gradle.addBuildListener(new BuildListener() { 82 | @Override 83 | void buildFinished(BuildResult buildResult) { 84 | buildResult.gradle.rootProject.tasks.each { 85 | if (it == lastTask) { 86 | stopMongoDb(project) 87 | } 88 | } 89 | } 90 | 91 | @Override 92 | void projectsEvaluated(Gradle gradle) {} 93 | 94 | @Override 95 | void projectsLoaded(Gradle gradle) {} 96 | 97 | @Override 98 | void settingsEvaluated(Settings gradle) {} 99 | }) 100 | } 101 | } 102 | 103 | private static boolean startMongoDb(Project project, ManageProcessInstruction manageProcessInstruction) { 104 | def pluginExtension = project[PLUGIN_EXTENSION_NAME] as GradleMongoPluginExtension 105 | return startMongoDb(pluginExtension, project, manageProcessInstruction) 106 | } 107 | 108 | private 109 | static boolean startMongoDb(GradleMongoPluginExtension pluginExtension, Project project, ManageProcessInstruction manageProcessInstruction) { 110 | if (mongoInstanceAlreadyRunning(pluginExtension.bindIp, pluginExtension.port)) { 111 | println "Mongo instance already running at ${pluginExtension.bindIp}:${pluginExtension.port}. Reusing." 112 | return false 113 | } 114 | 115 | def processOutput = new ProcessOutputFactory(project).getProcessOutput(pluginExtension) 116 | def version = new VersionFactory().getVersion(pluginExtension) 117 | def storage = new StorageFactory().getStorage(pluginExtension) 118 | 119 | def configBuilder = ImmutableMongodConfig.builder() 120 | .cmdOptions(createMongoCommandOptions(pluginExtension)) 121 | .version(version) 122 | .replication(storage) 123 | .net(new Net(pluginExtension.bindIp, pluginExtension.port, Network.localhostIsIPv6())) 124 | 125 | pluginExtension.args.each { k, v -> 126 | if (!v) 127 | configBuilder.putArgs("--${k}", null) 128 | else 129 | configBuilder.putArgs("--${k}", v) 130 | } 131 | 132 | pluginExtension.params.each { k, v -> configBuilder.putParams(k, v) } 133 | 134 | def mongodConfig = configBuilder.build() 135 | 136 | def runtimeConfig = new CustomFlapdoodleRuntimeConfig(version, 137 | pluginExtension.mongodVerbosity, 138 | pluginExtension.downloadUrl, 139 | pluginExtension.proxyHost, 140 | pluginExtension.proxyPort, 141 | pluginExtension.artifactStorePath) 142 | .defaults(Command.MongoD) 143 | .processOutput(processOutput) 144 | .isDaemonProcess(manageProcessInstruction == STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) 145 | .build() 146 | 147 | def runtime = MongodStarter.getInstance(runtimeConfig) 148 | 149 | def mongodExecutable = runtime.prepare(mongodConfig) 150 | println "Starting Mongod ${version.asInDownloadPath()} on port ${pluginExtension.port}..." 151 | def mongoProc = mongodExecutable.start() 152 | println 'Mongod started.' 153 | project.rootProject.mongoPortToProcessMap.put(pluginExtension.port, mongoProc) 154 | return true 155 | } 156 | 157 | private static boolean mongoInstanceAlreadyRunning(String bindIp, int port) { 158 | if (isPortAvailable(bindIp, port)) { 159 | return false 160 | } 161 | 162 | try { 163 | def mongoClient = new MongoClient(bindIp, port) 164 | mongoClient.getDatabase('test').runCommand(new Document(buildinfo: 1)) 165 | } catch (Throwable ignored) { 166 | return false 167 | } 168 | return true 169 | } 170 | 171 | private static boolean isPortAvailable(String host, int port) { 172 | Socket socket = null 173 | try { 174 | socket = new Socket(host, port) 175 | return false 176 | } catch (IOException ignored) { 177 | return true 178 | } finally { 179 | try { 180 | socket.close() 181 | } catch (Throwable ignored) { 182 | } 183 | } 184 | } 185 | 186 | private static ImmutableMongoCmdOptions createMongoCommandOptions(GradleMongoPluginExtension pluginExtension) { 187 | def mongoCommandOptionsBuilder = ImmutableMongoCmdOptions.builder() 188 | .useNoJournal(!pluginExtension.journalingEnabled) 189 | .storageEngine(pluginExtension.storageEngine) 190 | .auth(pluginExtension.auth) 191 | 192 | if (pluginExtension.syncDelay != null){ 193 | mongoCommandOptionsBuilder.syncDelay(pluginExtension.syncDelay) 194 | } else { 195 | mongoCommandOptionsBuilder.useDefaultSyncDelay(true) 196 | } 197 | 198 | mongoCommandOptionsBuilder.build() 199 | } 200 | 201 | private static void addStopMongoDbTask(Project project) { 202 | project.task(group: TASK_GROUP_NAME, description: 'Stops the local MongoDb instance', 'stopMongoDb').doFirst { 203 | stopMongoDb(project) 204 | } 205 | } 206 | 207 | private static void stopMongoDb(Project project) { 208 | def port = project."$PLUGIN_EXTENSION_NAME".port as Integer 209 | def proc = project.rootProject.mongoPortToProcessMap.remove(port) as MongodProcess 210 | stopMongoDb(port, proc) 211 | } 212 | 213 | private static void stopMongoDb(int port, MongodProcess proc) { 214 | println "Shutting-down Mongod on port ${port}." 215 | def force = (proc == null) 216 | 217 | try { 218 | proc?.stop() 219 | } catch (ignored) { 220 | force = true 221 | } 222 | 223 | if (force && !Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port)) { 224 | println "Could not shut down mongo, is access control enabled?" 225 | } 226 | } 227 | 228 | private static void extendAllTasksWithMongoOptions(Project project) { 229 | project.tasks.each { 230 | extend(it) 231 | } 232 | 233 | project.tasks.whenTaskAdded { 234 | extend(it) 235 | } 236 | } 237 | 238 | private static void extend(Task task) { 239 | task.ext.runWithMongoDb = false 240 | task.extensions.add(PLUGIN_EXTENSION_NAME, GradleMongoPluginExtension) 241 | } 242 | 243 | private static void configureTasksRequiringMongoDb(Project project) { 244 | project.tasks.each { 245 | def task = it 246 | if (task.runWithMongoDb) { 247 | def rootProject = project.getRootProject() 248 | def mergedPluginExtension = getTaskSpecificMongoConfiguration(task, project) 249 | def port = mergedPluginExtension.port 250 | 251 | task.doFirst { 252 | synchronized (rootProject) { 253 | ensureMongoTaskTrackingPropertiesAreSet(rootProject) 254 | def mongoDependencyCount = rootProject.mongoTaskDependenciesCountByPort.get(port).getAndIncrement() 255 | def mongoStartedByTask = startMongoDb(mergedPluginExtension, project, STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS) 256 | if (mongoDependencyCount == 0) { 257 | rootProject.mongoInstancesStartedDuringBuild.put(port, mongoStartedByTask) 258 | } 259 | } 260 | } 261 | } 262 | } 263 | 264 | project.gradle.addBuildListener(new BuildListener() { 265 | @Override 266 | void buildFinished(BuildResult buildResult) { 267 | buildResult.gradle.rootProject.tasks.each { 268 | TaskState state = it.state 269 | if (it.runWithMongoDb && state.didWork) { 270 | def rootProject = it.project 271 | def mergedPluginExtension = getTaskSpecificMongoConfiguration(it, project) 272 | def port = mergedPluginExtension.port 273 | synchronized (rootProject) { 274 | def mongoDependencyCount = rootProject.mongoTaskDependenciesCountByPort.get(port).decrementAndGet() 275 | if (mongoDependencyCount == 0 && rootProject.mongoInstancesStartedDuringBuild.get(port)) { 276 | stopMongoDb(port, rootProject.mongoPortToProcessMap.remove(port)) 277 | } 278 | } 279 | } 280 | } 281 | } 282 | 283 | @Override 284 | void projectsEvaluated(Gradle gradle) {} 285 | 286 | @Override 287 | void projectsLoaded(Gradle gradle) {} 288 | 289 | @Override 290 | void settingsEvaluated(Settings gradle) {} 291 | }) 292 | } 293 | 294 | private static void ensureMongoTaskTrackingPropertiesAreSet(Project rootProject) { 295 | if (!rootProject.extensions.extraProperties.has("mongoTaskDependenciesCountByPort")) { 296 | rootProject.extensions.extraProperties.set("mongoTaskDependenciesCountByPort", new HashMap().withDefault { 297 | new AtomicInteger() 298 | }) 299 | rootProject.extensions.extraProperties.set("mongoInstancesStartedDuringBuild", new HashMap<>().withDefault { 300 | false 301 | }) 302 | } 303 | } 304 | 305 | private static GradleMongoPluginExtension getTaskSpecificMongoConfiguration(Task task, Project project) { 306 | def projectPluginExtension = project[PLUGIN_EXTENSION_NAME] as GradleMongoPluginExtension 307 | def taskPluginExtension = task.extensions.findByType(GradleMongoPluginExtension) 308 | projectPluginExtension.overrideWith(taskPluginExtension) 309 | } 310 | } 311 | 312 | enum ManageProcessInstruction { 313 | STOP_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS, CONTINUE_MONGO_PROCESS_WHEN_BUILD_PROCESS_STOPS 314 | } 315 | -------------------------------------------------------------------------------- /src/test/groovy/com/sourcemuse/gradle/plugin/BuildScriptBuilder.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | class BuildScriptBuilder { 4 | 5 | static final DEFAULT_MONGOD_PORT = 27017 6 | static final STOP_MONGO_DB_FOR_TEST = 'stopMongoDbForTest' 7 | static final START_MONGO_DB_FOR_TEST = 'startMongoDbForTest' 8 | static final START_MANAGED_MONGO_DB_FOR_TEST = 'startManagedMongoDbForTest' 9 | static final MONGO_RUNNING_FLAG = 'mongo running!' 10 | static final MONGO_NOT_RUNNING_FLAG = 'mongo not running!' 11 | 12 | Map configProperties = [:] 13 | 14 | static BuildScriptBuilder buildScript() { 15 | new BuildScriptBuilder() 16 | } 17 | 18 | String build() { 19 | def mongoConfigBlock = new MongoPluginConfigBlock() 20 | 21 | configProperties.each { name, value -> 22 | mongoConfigBlock.addPropertyConfig(name, value as String) 23 | } 24 | 25 | """ 26 | |import static com.sourcemuse.gradle.plugin.flapdoodle.gradle.GradleMongoPlugin.mongoInstanceAlreadyRunning 27 | | 28 | |plugins { id 'com.sourcemuse.mongo' } 29 | | 30 | |def performMongoCheck = { 31 | | if (mongoInstanceAlreadyRunning(mongo.bindIp, mongo.port)) 32 | | println "${MONGO_RUNNING_FLAG}" 33 | | else 34 | | println "${MONGO_NOT_RUNNING_FLAG}" 35 | |} 36 | |task ${START_MANAGED_MONGO_DB_FOR_TEST} (dependsOn: startManagedMongoDb) { doLast performMongoCheck } 37 | |task ${START_MONGO_DB_FOR_TEST} (dependsOn: startMongoDb) { doLast performMongoCheck } 38 | |task ${STOP_MONGO_DB_FOR_TEST} (dependsOn: [startMongoDb, stopMongoDb]) 39 | | 40 | |${mongoConfigBlock} 41 | """.stripMargin() 42 | } 43 | 44 | BuildScriptBuilder withPort(int port) { 45 | configProperties.port = port 46 | this 47 | } 48 | 49 | BuildScriptBuilder withLogging(String logging) { 50 | configProperties.logging = asStringProperty(logging) 51 | this 52 | } 53 | 54 | BuildScriptBuilder withFilePath(String filePath) { 55 | configProperties.logFilePath = asStringProperty(filePath.replace('\\', '\\\\')) 56 | this 57 | } 58 | 59 | BuildScriptBuilder withMongoVersion(String version) { 60 | configProperties.mongoVersion = asStringProperty(version) 61 | this 62 | } 63 | 64 | BuildScriptBuilder withStorageEngine(String storageEngine) { 65 | configProperties.storageEngine = asStringProperty(storageEngine) 66 | this 67 | } 68 | 69 | BuildScriptBuilder withStorageLocation(String storage) { 70 | configProperties.storageLocation = asStringProperty(storage.replace('\\', '\\\\')) 71 | this 72 | } 73 | 74 | BuildScriptBuilder withJournalingEnabled() { 75 | configProperties.journalingEnabled = true 76 | this 77 | } 78 | 79 | BuildScriptBuilder withVerboseLogging() { 80 | configProperties.mongodVerbosity = asStringProperty('-v') 81 | this 82 | } 83 | 84 | Integer getConfiguredPort() { 85 | configProperties.port as Integer 86 | } 87 | 88 | BuildScriptBuilder withDownloadURL(String downloadURL) { 89 | configProperties.downloadURL = asStringProperty(downloadURL) 90 | this 91 | } 92 | 93 | BuildScriptBuilder withProxy(String host, int port) { 94 | configProperties.proxyHost = asStringProperty(host) 95 | configProperties.proxyPort = port 96 | this 97 | } 98 | 99 | BuildScriptBuilder withArtifactStorePath(String artifactStorePath) { 100 | configProperties.artifactStorePath = asStringProperty(artifactStorePath.replace('\\', '\\\\')) 101 | this 102 | } 103 | 104 | BuildScriptBuilder withAuth() { 105 | configProperties.auth = true 106 | this 107 | } 108 | 109 | BuildScriptBuilder withParams(Map params) { 110 | configProperties.params = params.inspect() 111 | this 112 | } 113 | 114 | BuildScriptBuilder withArgs(Map args) { 115 | configProperties.args = args.inspect() 116 | this 117 | } 118 | 119 | private String asStringProperty(String value) { 120 | "'$value'" 121 | } 122 | 123 | static class MongoPluginConfigBlock { 124 | String config = '' 125 | 126 | void addPropertyConfig(String propertyName, String value) { 127 | if (!config) { 128 | config += 'mongo {\n' 129 | } 130 | 131 | config += "\t$propertyName = $value\n" 132 | } 133 | 134 | String toString() { 135 | if (config) { 136 | return config + '}' 137 | } else { 138 | return config 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/groovy/com/sourcemuse/gradle/plugin/GradleMongoPluginExtensionSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class GradleMongoPluginExtensionSpec extends Specification { 7 | def pluginExtension = new GradleMongoPluginExtension() 8 | 9 | def 'port can be supplied as a number'() { 10 | given: 11 | pluginExtension.port = 12345 12 | 13 | when: 14 | def port = pluginExtension.port 15 | 16 | then: 17 | port == 12345 18 | } 19 | 20 | def 'port can be supplied as a String'() { 21 | given: 22 | pluginExtension.port = '12345' 23 | 24 | when: 25 | def port = pluginExtension.port 26 | 27 | then: 28 | port == 12345 29 | } 30 | 31 | @Unroll 32 | def 'port is randomized when supplied as #variant "#randomLabel"'() { 33 | given: 34 | pluginExtension.port = randomLabel 35 | 36 | when: 37 | def port = pluginExtension.port 38 | 39 | then: 40 | port >= 0 && port <= 65535 41 | 42 | where: 43 | variant | randomLabel 44 | 'lowercase' | 'random' 45 | 'uppercase' | 'RANDOM' 46 | 'mixed-case' | 'rAnDoM' 47 | } 48 | 49 | def 'repeated port checks are idempotent when a random port is picked'() { 50 | given: 51 | this.pluginExtension.port = 'random' 52 | 53 | when: 54 | def port = this.pluginExtension.port 55 | 56 | then: 57 | port == this.pluginExtension.port 58 | } 59 | 60 | @Unroll 61 | def 'mongod verbosity supplied #variant "#verbosityLabel" translates to -v'() { 62 | given: 63 | this.pluginExtension.mongodVerbosity = verbosityLabel 64 | 65 | when: 66 | def verbosity = this.pluginExtension.mongodVerbosity 67 | 68 | then: 69 | verbosity == '-v' 70 | 71 | where: 72 | variant | verbosityLabel 73 | 'lowercase' | 'verbose' 74 | 'uppercase' | 'VERBOSE' 75 | 'mixed-case' | 'VerBose' 76 | 'prefixed with hyphen' | '-verbose' 77 | 'prefixed with double hyphen' | '--verbose' 78 | } 79 | 80 | def 'mongod verbosity is automatically prefixed with a hyphen'() { 81 | given: 82 | this.pluginExtension.mongodVerbosity = 'v' 83 | 84 | when: 85 | def verbosity = this.pluginExtension.mongodVerbosity 86 | 87 | then: 88 | verbosity == '-v' 89 | } 90 | 91 | def "mongod verbosity can be supplied with multiple v's"() { 92 | given: 93 | this.pluginExtension.mongodVerbosity = 'vvvvv' 94 | 95 | when: 96 | def verbosity = this.pluginExtension.mongodVerbosity 97 | 98 | then: 99 | verbosity == '-vvvvv' 100 | } 101 | 102 | @Unroll 103 | def "mongod verbosity uppercase V's are turned into lowercase v's"() { 104 | given: 105 | this.pluginExtension.mongodVerbosity = suppliedVerbosity 106 | 107 | when: 108 | def verbosity = this.pluginExtension.mongodVerbosity 109 | 110 | then: 111 | verbosity == '-vvvvv' 112 | 113 | where: 114 | suppliedVerbosity << ['VVVVV', '-VVVVV'] 115 | } 116 | 117 | def 'mongod verbosity containing any other characters is rejected'() { 118 | when: 119 | this.pluginExtension.mongodVerbosity = 'very verbose!' 120 | 121 | then: 122 | def throwable = thrown(IllegalArgumentException) 123 | throwable.getMessage() == "MongodVerbosity should be defined as either '-verbose' or '-v(vvvv)'. " + 124 | "Do not configure this property if you don't wish to have verbose output." 125 | } 126 | 127 | def 'mongod download url throws exception for invalid url'() { 128 | given: 129 | String invalidURL = 'thisisnotavalidurl' 130 | 131 | when: 132 | this.pluginExtension.downloadUrl = invalidURL 133 | 134 | then: 135 | def throwable = thrown(IllegalArgumentException) 136 | throwable.message == "DownloadURL ${invalidURL} is not a valid URL." 137 | } 138 | 139 | def 'mongod download url can be set for valid url'() { 140 | given: 141 | String validURL = 'http://google.com' 142 | 143 | when: 144 | this.pluginExtension.downloadUrl = validURL 145 | 146 | then: 147 | notThrown(IllegalArgumentException) 148 | } 149 | 150 | def 'config can be overridden'() { 151 | given: 152 | def overridingPluginExtension = new GradleMongoPluginExtension() 153 | this.pluginExtension.bindIp = "1.2.3.4" 154 | this.pluginExtension.port = 12345 155 | overridingPluginExtension.bindIp = "7.8.9.0" 156 | overridingPluginExtension.downloadUrl = "http://abc.com" 157 | overridingPluginExtension.proxyPort = 443 158 | overridingPluginExtension.proxyHost = 'yourproxy' 159 | overridingPluginExtension.artifactStorePath = '/tmp' 160 | 161 | when: 162 | def mergedPluginExtension = this.pluginExtension.overrideWith(overridingPluginExtension) 163 | 164 | then: 165 | mergedPluginExtension.bindIp == "7.8.9.0" 166 | mergedPluginExtension.port == 12345 167 | mergedPluginExtension.downloadUrl == "http://abc.com" 168 | mergedPluginExtension.proxyPort == 443 169 | mergedPluginExtension.proxyHost == 'yourproxy' 170 | mergedPluginExtension.artifactStorePath == '/tmp' 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginConfigSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.* 4 | import static com.sourcemuse.gradle.plugin.MongoUtils.* 5 | 6 | import org.bson.Document 7 | import org.gradle.testkit.runner.BuildResult 8 | import org.gradle.testkit.runner.GradleRunner 9 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer 10 | 11 | import com.mongodb.MongoCredential 12 | 13 | import de.flapdoodle.embed.mongo.distribution.Version 14 | import spock.lang.Issue 15 | import spock.lang.Specification 16 | import spock.lang.TempDir 17 | 18 | class MongoPluginConfigSpec extends Specification { 19 | 20 | def static final VERBOSE_LOGGING_SAMPLE = 'ismaster' 21 | 22 | @TempDir 23 | File tmp 24 | 25 | def buildScript = new BuildScriptBuilder() 26 | 27 | def 'port is configurable'() { 28 | given: 29 | generate(buildScript.withPort(12345)) 30 | def args = START_MONGO_DB_FOR_TEST 31 | 32 | when: 33 | runGradle(args) 34 | def mongoRunningOnPort = mongoInstanceRunning(12345) 35 | 36 | then: 37 | mongoRunningOnPort 38 | } 39 | 40 | def 'logging can route to the console'() { 41 | given: 42 | generate(buildScript.withLogging('console')) 43 | def args = START_MONGO_DB_FOR_TEST 44 | 45 | when: 46 | def executionResult = runGradle(args) 47 | 48 | then: 49 | executionResult.getOutput().contains('[mongod output]') 50 | } 51 | 52 | def 'logging can be switched off'() { 53 | given: 54 | generate(buildScript.withLogging('none')) 55 | def args = START_MONGO_DB_FOR_TEST 56 | 57 | when: 58 | def executionResult = runGradle(args) 59 | 60 | then: 61 | !executionResult.getOutput().contains('[mongod output]') 62 | } 63 | 64 | def 'logging can be routed to a file'() { 65 | given: 66 | def tempFile = tmp.createTempFile("start", "end") 67 | generate(buildScript.withLogging('file').withFilePath(tempFile.absolutePath)) 68 | def args = START_MONGO_DB_FOR_TEST 69 | 70 | when: 71 | def executionResult = runGradle(args) 72 | 73 | then: 74 | !executionResult.getOutput().contains('[mongod output]') 75 | tempFile.text.contains('[mongod output]') 76 | } 77 | 78 | def 'general version is configurable'() { 79 | given: 80 | generate(buildScript.withMongoVersion('PRODUCTION')) 81 | def args = START_MONGO_DB_FOR_TEST 82 | 83 | when: 84 | runGradle(args) 85 | def mongoVersion = getMongoVersionRunning(DEFAULT_MONGOD_PORT) 86 | 87 | then: 88 | Version.Main.PRODUCTION.asInDownloadPath().equalsIgnoreCase(mongoVersion) 89 | } 90 | 91 | def 'specific version is configurable'() { 92 | given: 93 | generate(buildScript.withMongoVersion('3.4.5')) 94 | def args = START_MONGO_DB_FOR_TEST 95 | 96 | when: 97 | runGradle(args) 98 | def mongoVersion = getMongoVersionRunning(DEFAULT_MONGOD_PORT) 99 | 100 | then: 101 | Version.V3_4_5.asInDownloadPath().equalsIgnoreCase(mongoVersion) 102 | } 103 | 104 | def 'latest branch version is configurable'() { 105 | given: 106 | generate(buildScript.withMongoVersion('5.0-LATEST')) 107 | def args = START_MONGO_DB_FOR_TEST 108 | 109 | when: 110 | runGradle(args) 111 | def mongoVersion = getMongoVersionRunning(DEFAULT_MONGOD_PORT) 112 | 113 | then: 114 | Version.Main.V5_0.asInDownloadPath().equalsIgnoreCase(mongoVersion) 115 | } 116 | 117 | @Issue('https://github.com/sourcemuse/GradleMongoPlugin/issues/15') 118 | def 'unrecognized version is configurable'() { 119 | given: 120 | def version = '3.2.0' 121 | generate(buildScript.withMongoVersion(version)) 122 | def args = START_MONGO_DB_FOR_TEST 123 | 124 | when: 125 | runGradle(args) 126 | def mongoVersion = getMongoVersionRunning(DEFAULT_MONGOD_PORT) 127 | 128 | then: 129 | mongoVersion == version 130 | } 131 | 132 | def 'storage engine can be set to WiredTiger'() { 133 | given: 134 | generate(buildScript.withMongoVersion(Version.Main.V3_2.asInDownloadPath()).withStorageEngine('wiredTiger')) 135 | def args = START_MONGO_DB_FOR_TEST 136 | 137 | when: 138 | runGradle(args) 139 | 140 | then: 141 | mongoServerStatus().storageEngine.name == 'wiredTiger' 142 | noExceptionThrown() 143 | } 144 | 145 | def 'storage engine can be set to MMAPv1'() { 146 | given: 147 | generate(buildScript.withMongoVersion(Version.Main.V3_2.asInDownloadPath()).withStorageEngine('mmapv1')) 148 | def args = START_MONGO_DB_FOR_TEST 149 | 150 | when: 151 | runGradle(args) 152 | 153 | then: 154 | mongoServerStatus().storageEngine.name == 'mmapv1' 155 | noExceptionThrown() 156 | } 157 | 158 | def 'the default storage engine is WiredTiger for versions before 3.2'() { 159 | given: 160 | generate(buildScript.withMongoVersion(Version.Main.V3_2.asInDownloadPath())) 161 | def args = START_MONGO_DB_FOR_TEST 162 | 163 | when: 164 | runGradle(args) 165 | 166 | then: 167 | mongoServerStatus().storageEngine.name == 'wiredTiger' 168 | noExceptionThrown() 169 | } 170 | 171 | def 'the default storage engine is wiredTiger for versions after 5.0'() { 172 | given: 173 | generate(buildScript.withMongoVersion(Version.Main.V5_0.asInDownloadPath())) 174 | def args = START_MONGO_DB_FOR_TEST 175 | 176 | when: 177 | runGradle(args) 178 | 179 | then: 180 | mongoServerStatus().storageEngine.name == 'wiredTiger' 181 | noExceptionThrown() 182 | } 183 | 184 | def 'replication storage location is configurable'() { 185 | given: 186 | def storageDir = tmp.createTempDir() 187 | generate(buildScript.withStorageLocation(storageDir.toString())) 188 | def args = START_MONGO_DB_FOR_TEST 189 | 190 | when: 191 | runGradle(args) 192 | 193 | then: 194 | storageDir.listFiles().size() > 0 195 | } 196 | 197 | def 'journaling can be enabled'() { 198 | given: 199 | // From 2.6 onwards, journaled writes onto a non-journaled mongo db throw exceptions 200 | generate(buildScript.withJournalingEnabled().withMongoVersion('2.6.1')) 201 | def args = START_MONGO_DB_FOR_TEST 202 | 203 | when: 204 | runGradle(args) 205 | makeJournaledWrite() 206 | 207 | then: 208 | noExceptionThrown() 209 | } 210 | 211 | def 'logging can be made verbose'() { 212 | given: 213 | generate(buildScript.withVerboseLogging().withLogging('console')) 214 | def args = START_MONGO_DB_FOR_TEST 215 | 216 | when: 217 | def executionResult = runGradle(args) 218 | 219 | then: 220 | executionResult.getOutput().contains(VERBOSE_LOGGING_SAMPLE) 221 | println executionResult.getOutput() 222 | } 223 | 224 | def 'by default logging is not verbose'() { 225 | given: 226 | generate(buildScript.withLogging('console')) 227 | def args = START_MONGO_DB_FOR_TEST 228 | 229 | when: 230 | def executionResult = runGradle(args) 231 | 232 | then: 233 | !executionResult.getOutput().contains(VERBOSE_LOGGING_SAMPLE) 234 | } 235 | 236 | def 'a URL that does not resolve to a mongo binary will fail'() { 237 | given: 238 | generate(buildScript.withDownloadURL('http://www.google.com').withMongoVersion('1.6.5')) 239 | def args = START_MONGO_DB_FOR_TEST 240 | 241 | when: 242 | GradleRunner.create() 243 | .withPluginClasspath() 244 | .withProjectDir(tmp) 245 | .withArguments(args) 246 | .buildAndFail() 247 | 248 | then: 249 | noExceptionThrown() 250 | } 251 | 252 | def 'will fail with non-routeable proxy'() { 253 | given: 254 | int proxyPort = 9091 255 | String proxyHost = 'invalidHost' 256 | String path = tmp.createTempDir().toString() 257 | generate(buildScript.withProxy(proxyHost, proxyPort).withArtifactStorePath(path)) 258 | def args = START_MONGO_DB_FOR_TEST 259 | 260 | when: 261 | GradleRunner.create() 262 | .withPluginClasspath() 263 | .withProjectDir(tmp) 264 | .withArguments(args) 265 | .buildAndFail() 266 | 267 | then: 268 | noExceptionThrown() 269 | } 270 | 271 | def 'can use proxy to download and a custom location'() { 272 | given: 273 | int proxyPort = 9091 274 | DefaultHttpProxyServer.bootstrap().withPort(proxyPort).start() 275 | String path = tmp.toString() 276 | generate(buildScript.withProxy('localhost', proxyPort).withArtifactStorePath(path)) 277 | def args = START_MONGO_DB_FOR_TEST 278 | 279 | when: 280 | runGradle(args) 281 | 282 | then: 283 | mongoInstanceRunning() 284 | noExceptionThrown() 285 | } 286 | 287 | def 'can start/stop with authentication enabled'() { 288 | given: 289 | generate(buildScript.withAuth()) 290 | def args = START_MANAGED_MONGO_DB_FOR_TEST 291 | 292 | when: 293 | def result = runGradle(args) 294 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_RUNNING_FLAG) 295 | def mongoRunningAfterBuild = mongoInstanceRunning() 296 | 297 | then: 298 | mongoRunningDuringBuild 299 | !mongoRunningAfterBuild 300 | } 301 | 302 | def 'unauthenticated commands are rejected'() { 303 | given: 304 | generate(buildScript.withAuth().withMongoVersion("3.4.0")) 305 | def cred = MongoCredential.createCredential('admin', 'admin', 'qwert123'.toCharArray()) 306 | 307 | when: 308 | runGradle(START_MONGO_DB_FOR_TEST) 309 | def cmd = new Document('createUser', 'admin') 310 | cmd.put('pwd', 'qwert123') 311 | cmd.put('roles', ['root']) 312 | def unauthSuccess = runMongoCommand(null, cmd) 313 | 314 | then: 315 | mongoInstanceRunning() 316 | unauthSuccess 317 | 318 | when: 319 | def authSuccess = runMongoCommand(cred, new Document('dbStats', 1)) 320 | unauthSuccess = runMongoCommand(null, new Document('dbStats', 1)) 321 | 322 | then: 323 | authSuccess 324 | !unauthSuccess 325 | 326 | when: 327 | // Mongod.sendShutdown will not work when authentication is enabled, so perform a special cleanup 328 | runMongoCommand(cred, new Document('shutdown', 1)) 329 | 330 | then: 331 | noExceptionThrown() 332 | !mongoInstanceRunning() 333 | } 334 | 335 | def 'parameters can be set'() { 336 | given: 337 | generate(buildScript.withParams([cursorTimeoutMillis: '300000'])) 338 | def args = START_MONGO_DB_FOR_TEST 339 | 340 | when: 341 | runGradle(args) 342 | 343 | then: 344 | noExceptionThrown() 345 | } 346 | 347 | def 'custom command line arguments can be set'() { 348 | given: 349 | generate(buildScript.withArgs([slowms: '10', maxConns: '1000'])) 350 | def args = START_MONGO_DB_FOR_TEST 351 | 352 | when: 353 | runGradle(args) 354 | 355 | then: 356 | noExceptionThrown() 357 | } 358 | 359 | def cleanup() { 360 | ensureMongoIsStopped(buildScript.configuredPort ?: DEFAULT_MONGOD_PORT) 361 | } 362 | 363 | BuildResult runGradle(String args) { 364 | return GradleRunner.create() 365 | .withPluginClasspath() 366 | .withProjectDir(tmp) 367 | .withArguments(args) 368 | .build() 369 | } 370 | 371 | void generate(BuildScriptBuilder buildScriptBuilder) { 372 | def buildScriptContent = buildScriptBuilder.build() 373 | def newFile = new File(tmp.absolutePath + '/build.gradle') 374 | newFile.createNewFile() 375 | newFile << buildScriptContent 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/test/groovy/com/sourcemuse/gradle/plugin/MongoPluginTasksSpec.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.* 4 | import static com.sourcemuse.gradle.plugin.MongoUtils.ensureMongoIsStopped 5 | import static com.sourcemuse.gradle.plugin.MongoUtils.mongoInstanceRunning 6 | 7 | import org.gradle.testkit.runner.BuildResult 8 | import org.gradle.testkit.runner.GradleRunner 9 | 10 | import spock.lang.Specification 11 | import spock.lang.TempDir 12 | 13 | class MongoPluginTasksSpec extends Specification { 14 | 15 | static final String MONGO_STARTED_MESSAGE = 'Mongod started' 16 | static final String STOPPING_MONGO_MESSAGE = 'Stopping Mongod' 17 | 18 | @TempDir 19 | File tmp 20 | 21 | def 'individual tasks can declare a dependency on a running mongo instance'() { 22 | given: 23 | buildScript(""" 24 | plugins { id 'com.sourcemuse.mongo' } 25 | 26 | task A { 27 | runWithMongoDb = true 28 | } 29 | """) 30 | def args = 'A' 31 | 32 | when: 33 | BuildResult result = runGradle(args) 34 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_STARTED_MESSAGE) 35 | 36 | then: 37 | mongoRunningDuringBuild 38 | } 39 | 40 | def 'individual tasks can override their mongo configuration'() { 41 | given: 42 | buildScript(""" 43 | plugins { id 'com.sourcemuse.mongo' } 44 | 45 | mongo { 46 | port = 23456 47 | } 48 | 49 | task A { 50 | runWithMongoDb = true 51 | mongo { 52 | port = 12345 53 | } 54 | } 55 | """) 56 | def args = 'A' 57 | 58 | when: 59 | BuildResult result = runGradle(args) 60 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_STARTED_MESSAGE) 61 | def mongoRunningOnConfiguredPort = result.getOutput().contains('12345') 62 | 63 | then: 64 | mongoRunningDuringBuild 65 | mongoRunningOnConfiguredPort 66 | } 67 | 68 | def 'mongo instance is stopped after task completes'() { 69 | given: 70 | buildScript(""" 71 | plugins { id 'com.sourcemuse.mongo' } 72 | 73 | task A { 74 | runWithMongoDb = true 75 | } 76 | 77 | task B (dependsOn: A) { 78 | doFirst { 79 | println 'Running task B.' 80 | } 81 | } 82 | """) 83 | def args = 'B' 84 | 85 | when: 86 | BuildResult result = runGradle(args) 87 | 88 | then: 89 | mongoStoppedWhenTaskBExecutes(result) 90 | } 91 | 92 | private static boolean mongoStoppedWhenTaskBExecutes(BuildResult result) { 93 | result.getOutput().indexOf(STOPPING_MONGO_MESSAGE) < result.getOutput().indexOf('Running task B') 94 | } 95 | 96 | def 'mongo does not start when task is skipped during configuration phase'() { 97 | given: 98 | buildScript(""" 99 | plugins { id 'com.sourcemuse.mongo' } 100 | 101 | task A { 102 | runWithMongoDb = true 103 | enabled = false 104 | } 105 | """) 106 | def args = 'A' 107 | 108 | when: 109 | BuildResult result = runGradle(args) 110 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_STARTED_MESSAGE) 111 | 112 | then: 113 | !mongoRunningDuringBuild 114 | } 115 | 116 | def 'mongo does not start when task is skipped during execution phase'() { 117 | given: 118 | buildScript(""" 119 | plugins { id 'com.sourcemuse.mongo' } 120 | 121 | task A { 122 | runWithMongoDb = true 123 | onlyIf { false } 124 | } 125 | """) 126 | def args = 'A' 127 | 128 | when: 129 | BuildResult result = runGradle(args) 130 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_STARTED_MESSAGE) 131 | 132 | then: 133 | !mongoRunningDuringBuild 134 | } 135 | 136 | def 'a new mongo instance is not launched if an existing instance is already bound to the same port'() { 137 | given: 138 | buildScript(""" 139 | plugins { id 'com.sourcemuse.mongo' } 140 | 141 | task B { 142 | runWithMongoDb = true 143 | } 144 | 145 | task A(dependsOn: B) { 146 | runWithMongoDb = true 147 | } 148 | """) 149 | def args = 'A' 150 | 151 | when: 152 | BuildResult result = runGradle(args) 153 | def ioExceptionDuringBuild = result.getOutput().contains("IOException") 154 | 155 | then: 156 | !ioExceptionDuringBuild 157 | 158 | cleanup: 159 | ensureMongoIsStopped() 160 | } 161 | 162 | def 'when an existing mongo instance is reused by a task, mongo is not shutdown when the task completes'() { 163 | given: 164 | buildScript(""" 165 | plugins { id 'com.sourcemuse.mongo' } 166 | 167 | task A { 168 | runWithMongoDb = true 169 | } 170 | 171 | A.dependsOn startMongoDb 172 | """) 173 | def args = 'A' 174 | 175 | when: 176 | runGradle(args) 177 | def mongoRunningAfterBuild = mongoInstanceRunning() 178 | 179 | then: 180 | mongoRunningAfterBuild 181 | 182 | cleanup: 183 | ensureMongoIsStopped() 184 | } 185 | 186 | def 'multiple mongo instances can be started if bound to separate ports'() { 187 | given: 188 | buildScript(""" 189 | plugins { id 'com.sourcemuse.mongo' } 190 | 191 | task B { 192 | runWithMongoDb = true 193 | mongo { 194 | port = 27018 195 | } 196 | } 197 | 198 | task A(dependsOn: B) { 199 | runWithMongoDb = true 200 | mongo { 201 | port = 27017 202 | } 203 | } 204 | """) 205 | def args = 'A' 206 | 207 | when: 208 | BuildResult result = runGradle(args) 209 | def ioExceptionDuringBuild = result.getOutput().contains("IOException") 210 | 211 | then: 212 | !ioExceptionDuringBuild 213 | 214 | cleanup: 215 | ensureMongoIsStopped(27018) 216 | assert !mongoInstanceRunning(27017) 217 | assert !mongoInstanceRunning(27018) 218 | } 219 | 220 | def 'startManagedMongoDb starts a mongo instance, and then stops once the build has completed'() { 221 | given: 222 | generate(buildScript()) 223 | def args = START_MANAGED_MONGO_DB_FOR_TEST 224 | 225 | when: 226 | BuildResult result = runGradle(args) 227 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_RUNNING_FLAG) 228 | def mongoRunningAfterBuild = mongoInstanceRunning() 229 | 230 | then: 231 | mongoRunningDuringBuild 232 | !mongoRunningAfterBuild 233 | } 234 | 235 | def 'startMongoDb starts a mongo instance that continues running after the build has completed'() { 236 | given: 237 | generate(buildScript()) 238 | def args = START_MONGO_DB_FOR_TEST 239 | 240 | when: 241 | BuildResult result = runGradle(args) 242 | def mongoRunningDuringBuild = result.getOutput().contains(MONGO_RUNNING_FLAG) 243 | def mongoRunningAfterBuild = mongoInstanceRunning() 244 | 245 | then: 246 | mongoRunningDuringBuild 247 | mongoRunningAfterBuild 248 | 249 | cleanup: 250 | ensureMongoIsStopped() 251 | } 252 | 253 | def 'if startManagedMongoDb reuses a mongo instance, then it does not stop that instance when the build completes'() { 254 | given: 255 | generate(buildScript()) 256 | 257 | when: 258 | runGradle("startMongoDb") 259 | 260 | and: 261 | runGradle("startManagedMongoDb") 262 | 263 | then: 264 | mongoInstanceRunning() 265 | 266 | cleanup: 267 | ensureMongoIsStopped() 268 | } 269 | 270 | def 'stopMongoDb stops the mongo instance'() { 271 | given: 272 | generate(buildScript()) 273 | def args = STOP_MONGO_DB_FOR_TEST 274 | 275 | when: 276 | runGradle(args) 277 | def mongoRunningAfterBuild = mongoInstanceRunning() 278 | 279 | then: 280 | !mongoRunningAfterBuild 281 | } 282 | 283 | def cleanup() { 284 | assert !mongoInstanceRunning() 285 | } 286 | 287 | BuildResult runGradle(String args) { 288 | return GradleRunner.create() 289 | .withPluginClasspath() 290 | .withProjectDir(tmp) 291 | .withArguments(args) 292 | .build() 293 | } 294 | 295 | void generate(BuildScriptBuilder buildScriptBuilder) { 296 | buildScript(buildScriptBuilder.build()) 297 | } 298 | 299 | void buildScript(String buildScriptContent) { 300 | def newFile = new File(tmp.absolutePath + '/build.gradle') 301 | newFile.createNewFile() 302 | newFile << buildScriptContent 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/test/groovy/com/sourcemuse/gradle/plugin/MongoUtils.groovy: -------------------------------------------------------------------------------- 1 | package com.sourcemuse.gradle.plugin 2 | 3 | import com.mongodb.* 4 | import com.mongodb.client.MongoDatabase 5 | import de.flapdoodle.embed.mongo.runtime.Mongod 6 | import org.bson.Document 7 | 8 | import static com.sourcemuse.gradle.plugin.BuildScriptBuilder.DEFAULT_MONGOD_PORT 9 | 10 | class MongoUtils { 11 | 12 | private static final String LOOPBACK_ADDRESS = '127.0.0.1' 13 | private static final String DATABASE_NAME = 'test' 14 | 15 | static void ensureMongoIsStopped(int port = DEFAULT_MONGOD_PORT) { 16 | Mongod.sendShutdown(InetAddress.getLoopbackAddress(), port) 17 | } 18 | 19 | static MongoDatabase mongoDatabase(int port) { 20 | def mongoClient = new MongoClient(LOOPBACK_ADDRESS, port) 21 | mongoClient.getDatabase(DATABASE_NAME) 22 | } 23 | 24 | static Document mongoServerStatus(int port = DEFAULT_MONGOD_PORT) { 25 | mongoDatabase(port).runCommand(new Document('serverStatus', 1)) 26 | } 27 | 28 | static boolean mongoInstanceRunning(int port = DEFAULT_MONGOD_PORT) { 29 | if (isPortAvailable(LOOPBACK_ADDRESS, port)) { 30 | return false 31 | } 32 | try { 33 | getMongoVersionRunning(port) 34 | } catch (Throwable ignored) { 35 | return false 36 | } 37 | return true 38 | } 39 | 40 | private static boolean isPortAvailable(String host, int port) { 41 | Socket socket = null 42 | try { 43 | socket = new Socket(host, port) 44 | return false 45 | } catch (IOException ignored) { 46 | return true 47 | } finally { 48 | try { 49 | socket.close() 50 | } catch (Throwable ignored) { 51 | } 52 | } 53 | } 54 | 55 | static String getMongoVersionRunning(int port) { 56 | try { 57 | def mongoClient = new MongoClient(LOOPBACK_ADDRESS, port) 58 | def result = mongoClient.getDatabase(DATABASE_NAME).runCommand(new Document('buildInfo', 1)) 59 | return result.version 60 | } catch (Exception e) { 61 | return 'none' 62 | } 63 | } 64 | 65 | static boolean makeJournaledWrite() { 66 | try { 67 | def options = MongoClientOptions.builder().writeConcern(WriteConcern.JOURNALED).build() 68 | def mongoClient = new MongoClient("${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}", options) 69 | writeSampleObjectToDb(mongoClient) 70 | return true 71 | } catch (Exception e) { 72 | return false 73 | } 74 | } 75 | 76 | private static void writeSampleObjectToDb(MongoClient mongoClient) { 77 | def db = mongoClient.getDatabase(DATABASE_NAME) 78 | db.createCollection('test-collection') 79 | def document = new Document('key', 'val') 80 | db.getCollection('test-collection').insertOne(document) 81 | } 82 | 83 | static boolean runMongoCommand(MongoCredential credential, Document cmd) { 84 | ServerAddress addr = new ServerAddress("${LOOPBACK_ADDRESS}:${DEFAULT_MONGOD_PORT}") 85 | 86 | def mongoClient = credential ? 87 | new MongoClient(addr, credential, MongoClientOptions.builder().build()) : 88 | new MongoClient(addr, MongoClientOptions.builder().build()) 89 | 90 | try { 91 | mongoClient.getDatabase('admin').runCommand(cmd) 92 | } 93 | catch (MongoException e) { 94 | return false 95 | } 96 | finally { 97 | mongoClient.close() 98 | } 99 | return true 100 | } 101 | } --------------------------------------------------------------------------------