├── .github └── workflows │ ├── build.yml │ ├── gradle-wrapper-validation.yml │ ├── pr.yml │ ├── release.yml │ └── release_info.sh ├── .gitignore ├── AUTHORS ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── redis │ │ └── embedded │ │ ├── AbstractRedisInstance.java │ │ ├── PortProvider.java │ │ ├── Redis.java │ │ ├── RedisExecProvider.java │ │ ├── RedisServer.java │ │ ├── RedisServerBuilder.java │ │ ├── exceptions │ │ ├── EmbeddedRedisException.java │ │ ├── OsDetectionException.java │ │ └── RedisBuildingException.java │ │ ├── ports │ │ ├── EphemeralPortProvider.java │ │ ├── PredefinedPortProvider.java │ │ └── SequencePortProvider.java │ │ └── util │ │ ├── Architecture.java │ │ ├── Files.java │ │ ├── JarUtil.java │ │ ├── JedisUtil.java │ │ ├── OS.java │ │ ├── OSDetector.java │ │ ├── OsArchitecture.java │ │ ├── Preconditions.java │ │ └── Resources.java └── resources │ ├── redis-server-4.0.10 │ ├── redis-server-4.0.10-32 │ └── redis-server-4.0.10.app └── test └── java └── redis └── embedded ├── RedisServerClusterTest.java ├── RedisServerTest.java ├── SpringDataConnectivityTest.java └── ports ├── EphemeralPortProviderTest.java ├── PredefinedPortProviderTest.java └── SequencePortProviderTest.java /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Branch Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx1g -Xms1g 10 | 11 | jobs: 12 | branch-build: 13 | # Only run this on repositories in the 'spinnaker' org, not on forks. 14 | if: startsWith(github.repository, 'spinnaker/') 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: actions/setup-java@v1 19 | with: 20 | java-version: 11 21 | - uses: actions/cache@v1 22 | with: 23 | path: ~/.gradle 24 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 25 | restore-keys: | 26 | ${{ runner.os }}-gradle- 27 | - name: Build 28 | run: ./gradlew build --stacktrace 29 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | validation: 6 | name: "Gradle wrapper validation" 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: gradle/wrapper-validation-action@v1 11 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | on: [ pull_request ] 4 | 5 | env: 6 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - uses: actions/cache@v1 17 | with: 18 | path: ~/.gradle 19 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 20 | restore-keys: | 21 | ${{ runner.os }}-gradle- 22 | - name: Build 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | - "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" 8 | 9 | env: 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx1g -Xms1g 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - run: git fetch --prune --unshallow 18 | - uses: actions/setup-java@v1 19 | with: 20 | java-version: 11 21 | - uses: actions/cache@v1 22 | with: 23 | path: ~/.gradle 24 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 25 | restore-keys: | 26 | ${{ runner.os }}-gradle- 27 | - name: Assemble release info 28 | id: release_info 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | run: | 32 | . .github/workflows/release_info.sh ${{ github.event.repository.full_name }} 33 | echo ::set-output name=CHANGELOG::$(echo -e "${CHANGELOG}") 34 | echo ::set-output name=SKIP_RELEASE::${SKIP_RELEASE} 35 | echo ::set-output name=IS_CANDIDATE::${IS_CANDIDATE} 36 | echo ::set-output name=RELEASE_VERSION::${RELEASE_VERSION} 37 | - name: Release build 38 | env: 39 | ORG_GRADLE_PROJECT_version: ${{ steps.release_info.outputs.RELEASE_VERSION }} 40 | ORG_GRADLE_PROJECT_nexusPublishEnabled: true 41 | ORG_GRADLE_PROJECT_nexusUsername: ${{ secrets.NEXUS_USERNAME }} 42 | ORG_GRADLE_PROJECT_nexusPassword: ${{ secrets.NEXUS_PASSWORD }} 43 | ORG_GRADLE_PROJECT_nexusPgpSigningKey: ${{ secrets.NEXUS_PGP_SIGNING_KEY }} 44 | ORG_GRADLE_PROJECT_nexusPgpSigningPassword: ${{ secrets.NEXUS_PGP_SIGNING_PASSWORD }} 45 | run: | 46 | ./gradlew --info publishToNexus closeAndReleaseNexusStagingRepository 47 | - name: Create release 48 | if: steps.release_info.outputs.SKIP_RELEASE == 'false' 49 | uses: actions/create-release@v1 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | tag_name: ${{ github.ref }} 54 | release_name: ${{ github.event.repository.name }} ${{ github.ref }} 55 | body: | 56 | ${{ steps.release_info.outputs.CHANGELOG }} 57 | draft: false 58 | prerelease: ${{ steps.release_info.outputs.IS_CANDIDATE }} 59 | -------------------------------------------------------------------------------- /.github/workflows/release_info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | # Only look to the latest release to determine the previous tag -- this allows us to skip unsupported tag formats (like `version-1.0.0`) 4 | export PREVIOUS_TAG=`curl --silent "https://api.github.com/repos/$1/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'` 5 | echo "PREVIOUS_TAG=$PREVIOUS_TAG" 6 | export NEW_TAG=${GITHUB_REF/refs\/tags\//} 7 | echo "NEW_TAG=$NEW_TAG" 8 | export CHANGELOG=`git log $NEW_TAG...$PREVIOUS_TAG --oneline` 9 | echo "CHANGELOG=$CHANGELOG" 10 | 11 | #Format the changelog so it's markdown compatible 12 | CHANGELOG="${CHANGELOG//$'%'/%25}" 13 | CHANGELOG="${CHANGELOG//$'\n'/%0A}" 14 | CHANGELOG="${CHANGELOG//$'\r'/%0D}" 15 | 16 | # If the previous release tag is the same as this tag the user likely cut a release (and in the process created a tag), which means we can skip the need to create a release 17 | export SKIP_RELEASE=`[[ "$PREVIOUS_TAG" = "$NEW_TAG" ]] && echo "true" || echo "false"` 18 | 19 | # https://github.com/fsaintjacques/semver-tool/blob/master/src/semver#L5-L14 20 | NAT='0|[1-9][0-9]*' 21 | ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*' 22 | IDENT="$NAT|$ALPHANUM" 23 | FIELD='[0-9A-Za-z-]+' 24 | SEMVER_REGEX="\ 25 | ^[vV]?\ 26 | ($NAT)\\.($NAT)\\.($NAT)\ 27 | (\\-(${IDENT})(\\.(${IDENT}))*)?\ 28 | (\\+${FIELD}(\\.${FIELD})*)?$" 29 | 30 | # Used in downstream steps to determine if the release should be marked as a "prerelease" and if the build should build candidate release artifacts 31 | export IS_CANDIDATE=`[[ $NEW_TAG =~ $SEMVER_REGEX && ! -z ${BASH_REMATCH[4]} ]] && echo "true" || echo "false"` 32 | 33 | # This is the version string we will pass to the build, trim off leading 'v' if present 34 | export RELEASE_VERSION=`[[ $NEW_TAG =~ $SEMVER_REGEX ]] && echo "${NEW_TAG:1}" || echo "${NEW_TAG}"` 35 | echo "RELEASE_VERSION=$RELEASE_VERSION" 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .project 3 | .classpath 4 | .settings 5 | .idea 6 | *.iml 7 | 8 | /target 9 | */target 10 | /build 11 | */build 12 | 13 | .gradle 14 | out 15 | .idea 16 | *.ipr 17 | *.iws 18 | *.iml -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Names should be added to this file with this pattern: 2 | # 3 | # For individuals: 4 | # Name 5 | # 6 | # For organizations: 7 | # Organization 8 | 9 | Krzysztof Styrc 10 | Piotr Turek 11 | anthonyu 12 | Artem Orobets 13 | Sean Simonsen 14 | Rob Winch 15 | Netflix, Inc <*@netflix.com> 16 | 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2017 Netflix 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | embedded-redis 2 | ============== 3 | 4 | [![Build Status](https://travis-ci.org/spinnaker/embedded-redis.png?branch=master)](https://travis-ci.org/spinnaker/embedded-redis) Linux 5 | 6 | Redis embedded server for Java integration testing. 7 | 8 | This was forked from [kstyrc](https://github.com/kstyrc/embedded-redis) to allow us to upgrade redis. 9 | 10 | Maven dependency 11 | ============== 12 | Gradle 13 | ```groovy 14 | dependencies { 15 | compile 'com.netflix.spinnaker.embedded-redis:embedded-redis:0.7' 16 | } 17 | ``` 18 | Maven 19 | ```xml 20 | 21 | com.netflix.spinnaker.embedded-redis 22 | embedded-redis 23 | 0.7 24 | 25 | ``` 26 | 27 | Previous release on Maven Central: 28 | ```xml 29 | 30 | com.github.kstyrc 31 | embedded-redis 32 | 0.6 33 | 34 | ``` 35 | 36 | 37 | Usage 38 | ============== 39 | 40 | Running RedisServer is as simple as: 41 | ```java 42 | RedisServer redisServer = new RedisServer(6379); 43 | redisServer.start(); 44 | // do some work 45 | redisServer.stop(); 46 | ``` 47 | 48 | You can also provide RedisServer with your own executable: 49 | ```java 50 | // 1) given explicit file (os-independence broken!) 51 | RedisServer redisServer = new RedisServer("/path/to/your/redis", 6379); 52 | 53 | // 2) given os-independent matrix 54 | RedisExecProvider customProvider = RedisExecProvider.defaultProvider() 55 | .override(OS.UNIX, "/path/to/unix/redis") 56 | .override(OS.WINDOWS, Architecture.x86, "/path/to/windows/redis") 57 | .override(OS.Windows, Architecture.x86_64, "/path/to/windows/redis") 58 | .override(OS.MAC_OS_X, Architecture.x86, "/path/to/macosx/redis") 59 | .override(OS.MAC_OS_X, Architecture.x86_64, "/path/to/macosx/redis") 60 | 61 | RedisServer redisServer = new RedisServer(customProvider, 6379); 62 | ``` 63 | 64 | You can also use fluent API to create RedisServer: 65 | ```java 66 | RedisServer redisServer = RedisServer.builder() 67 | .redisExecProvider(customRedisProvider) 68 | .port(6379) 69 | .slaveOf("locahost", 6378) 70 | .configFile("/path/to/your/redis.conf") 71 | .build(); 72 | ``` 73 | 74 | Or even create simple redis.conf file from scratch: 75 | ```java 76 | RedisServer redisServer = RedisServer.builder() 77 | .redisExecProvider(customRedisProvider) 78 | .port(6379) 79 | .slaveOf("locahost", 6378) 80 | .setting("daemonize no") 81 | .setting("appendonly no") 82 | .setting("maxheap 128M") 83 | .build(); 84 | ``` 85 | 86 | ## Setting up a cluster 87 | 88 | Our Embedded Redis has support for HA Redis clusters with Sentinels and master-slave replication 89 | 90 | #### Using ephemeral ports 91 | A simple redis integration test with Redis cluster on ephemeral ports, with setup similar to that from production would look like this: 92 | ```java 93 | public class SomeIntegrationTestThatRequiresRedis { 94 | private RedisCluster cluster; 95 | private Set jedisSentinelHosts; 96 | 97 | @Before 98 | public void setup() throws Exception { 99 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave 100 | cluster = RedisCluster.builder().ephemeral().sentinelCount(3).quorumSize(2) 101 | .replicationGroup("master1", 1) 102 | .replicationGroup("master2", 1) 103 | .replicationGroup("master3", 1) 104 | .build(); 105 | cluster.start(); 106 | 107 | //retrieve ports on which sentinels have been started, using a simple Jedis utility class 108 | jedisSentinelHosts = JedisUtil.sentinelHosts(cluster); 109 | } 110 | 111 | @Test 112 | public void test() throws Exception { 113 | // testing code that requires redis running 114 | JedisSentinelPool pool = new JedisSentinelPool("master1", jedisSentinelHosts); 115 | } 116 | 117 | @After 118 | public void tearDown() throws Exception { 119 | cluster.stop(); 120 | } 121 | } 122 | ``` 123 | 124 | #### Retrieving ports 125 | The above example starts Redis cluster on ephemeral ports, which you can later get with ```cluster.ports()```, 126 | which will return a list of all ports of the cluster. You can also get ports of sentinels with ```cluster.sentinelPorts()``` 127 | or servers with ```cluster.serverPorts()```. ```JedisUtil``` class contains utility methods for use with Jedis client. 128 | 129 | #### Using predefined ports 130 | You can also start Redis cluster on predefined ports and even mix both approaches: 131 | ```java 132 | public class SomeIntegrationTestThatRequiresRedis { 133 | private RedisCluster cluster; 134 | 135 | @Before 136 | public void setup() throws Exception { 137 | final List sentinels = Arrays.asList(26739, 26912); 138 | final List group1 = Arrays.asList(6667, 6668); 139 | final List group2 = Arrays.asList(6387, 6379); 140 | //creates a cluster with 3 sentinels, quorum size of 2 and 3 replication groups, each with one master and one slave 141 | cluster = RedisCluster.builder().sentinelPorts(sentinels).quorumSize(2) 142 | .serverPorts(group1).replicationGroup("master1", 1) 143 | .serverPorts(group2).replicationGroup("master2", 1) 144 | .ephemeralServers().replicationGroup("master3", 1) 145 | .build(); 146 | cluster.start(); 147 | } 148 | //(...) 149 | ``` 150 | The above will create and start a cluster with sentinels on ports ```26739, 26912```, first replication group on ```6667, 6668```, 151 | second replication group on ```6387, 6379``` and third replication group on ephemeral ports. 152 | 153 | Redis version 154 | ============== 155 | 156 | When not provided with the desired redis executable, RedisServer runs os-dependent executable enclosed in jar. Currently is uses: 157 | - Redis 3.2.11 in case of Linux/Unix 158 | - Redis 3.2.11 in case of OSX 159 | - Windows support not provided 160 | 161 | However, you should provide RedisServer with redis executable if you need specific version. 162 | 163 | 164 | License 165 | ============== 166 | Licensed under the Apache License, Version 2.0 167 | 168 | 169 | Contributors 170 | ============== 171 | * Krzysztof Styrc ([@kstyrc](http://github.com/kstyrc)) 172 | * Piotr Turek ([@turu](http://github.com/turu)) 173 | * anthonyu ([@anthonyu](http://github.com/anthonyu)) 174 | * Artem Orobets ([@enisher](http://github.com/enisher)) 175 | * Sean Simonsen ([@SeanSimonsen](http://github.com/SeanSimonsen)) 176 | * Rob Winch ([@rwinch](http://github.com/rwinch)) 177 | 178 | 179 | Changelog 180 | ============== 181 | ### 0.7 182 | * Support for Redis 3.2.11 183 | * Removed Windows support 184 | 185 | ### 0.6 186 | * Support JDK 6 + 187 | 188 | ### 0.5 189 | * OS detection fix 190 | * redis binary per OS/arch pair 191 | * Updated to 2.8.19 binary for Windows 192 | 193 | ### 0.4 194 | * Updated for Java 8 195 | * Added Sentinel support 196 | * Ability to create arbitrary clusters on arbitrary (ephemeral) ports 197 | * Updated to latest guava 198 | * Throw an exception if redis has not been started 199 | * Redis errorStream logged to System.out 200 | 201 | ### 0.3 202 | * Fluent API for RedisServer creation 203 | 204 | ### 0.2 205 | * Initial decent release 206 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'io.spinnaker.project' version "$spinnakerGradleVersion" apply false 19 | } 20 | 21 | apply plugin: "io.spinnaker.project" 22 | 23 | description = "Redis embedded server for Java integration testing" 24 | group = "io.spinnaker.embedded-redis" 25 | apply plugin: 'java-library' 26 | 27 | dependencies { 28 | testImplementation "junit:junit:4.12" 29 | 30 | testImplementation 'commons-logging:commons-logging:1.1.1' 31 | testImplementation 'org.mockito:mockito-all:1.8.5' 32 | testImplementation "org.springframework.data:spring-data-redis:1.4.1.RELEASE" 33 | testImplementation "redis.clients:jedis:2.9.0" 34 | } 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | spinnakerGradleVersion=8.14.0 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/embedded-redis/33dd8d6991328137215821d28398f75182e8d0ac/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-6.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | rootProject.name="embedded-redis" 18 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/AbstractRedisInstance.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.io.*; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import redis.embedded.exceptions.EmbeddedRedisException; 10 | 11 | abstract class AbstractRedisInstance implements Redis { 12 | protected List args = Collections.emptyList(); 13 | private volatile boolean active = false; 14 | private Process redisProcess; 15 | private final int port; 16 | 17 | private ExecutorService executor; 18 | 19 | protected AbstractRedisInstance(int port) { 20 | this.port = port; 21 | } 22 | 23 | @Override 24 | public boolean isActive() { 25 | return active; 26 | } 27 | 28 | @Override 29 | public synchronized void start() throws EmbeddedRedisException { 30 | if (active) { 31 | throw new EmbeddedRedisException("This redis server instance is already running..."); 32 | } 33 | try { 34 | redisProcess = createRedisProcessBuilder().start(); 35 | logErrors(); 36 | awaitRedisServerReady(); 37 | active = true; 38 | } catch (IOException e) { 39 | throw new EmbeddedRedisException("Failed to start Redis instance", e); 40 | } 41 | } 42 | 43 | private void logErrors() { 44 | final InputStream errorStream = redisProcess.getErrorStream(); 45 | BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream)); 46 | Runnable printReaderTask = new PrintReaderRunnable(reader); 47 | executor = Executors.newSingleThreadExecutor(); 48 | executor.submit(printReaderTask); 49 | } 50 | 51 | private void awaitRedisServerReady() throws IOException { 52 | BufferedReader reader = 53 | new BufferedReader(new InputStreamReader(redisProcess.getInputStream())); 54 | try { 55 | String outputLine; 56 | do { 57 | outputLine = reader.readLine(); 58 | if (outputLine == null) { 59 | // Something goes wrong. Stream is ended before server was activated. 60 | throw new RuntimeException("Can't start redis server. Check logs for details."); 61 | } 62 | } while (!outputLine.matches(redisReadyPattern())); 63 | } finally { 64 | closeQuietly(reader); 65 | } 66 | } 67 | 68 | protected abstract String redisReadyPattern(); 69 | 70 | private ProcessBuilder createRedisProcessBuilder() { 71 | File executable = new File(args.get(0)); 72 | ProcessBuilder pb = new ProcessBuilder(args); 73 | pb.directory(executable.getParentFile()); 74 | return pb; 75 | } 76 | 77 | @Override 78 | public synchronized void stop() throws EmbeddedRedisException { 79 | if (active) { 80 | if (executor != null && !executor.isShutdown()) { 81 | executor.shutdown(); 82 | } 83 | redisProcess.destroy(); 84 | tryWaitFor(); 85 | active = false; 86 | } 87 | } 88 | 89 | private void tryWaitFor() { 90 | try { 91 | redisProcess.waitFor(); 92 | } catch (InterruptedException e) { 93 | throw new EmbeddedRedisException("Failed to stop redis instance", e); 94 | } 95 | } 96 | 97 | @Override 98 | public List ports() { 99 | return Arrays.asList(port); 100 | } 101 | 102 | private static void closeQuietly(Reader reader) { 103 | if (reader == null) { 104 | return; 105 | } 106 | 107 | try { 108 | reader.close(); 109 | } catch (Throwable ignored) { 110 | // ignore 111 | } 112 | } 113 | 114 | private static class PrintReaderRunnable implements Runnable { 115 | private final BufferedReader reader; 116 | 117 | private PrintReaderRunnable(BufferedReader reader) { 118 | this.reader = reader; 119 | } 120 | 121 | public void run() { 122 | try { 123 | readLines(); 124 | } finally { 125 | closeQuietly(reader); 126 | } 127 | } 128 | 129 | public void readLines() { 130 | try { 131 | String line; 132 | while ((line = reader.readLine()) != null) { 133 | System.out.println(line); 134 | } 135 | } catch (IOException e) { 136 | e.printStackTrace(); 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/PortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | public interface PortProvider { 4 | int next(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/Redis.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.util.List; 4 | import redis.embedded.exceptions.EmbeddedRedisException; 5 | 6 | public interface Redis { 7 | boolean isActive(); 8 | 9 | void start() throws EmbeddedRedisException; 10 | 11 | void stop() throws EmbeddedRedisException; 12 | 13 | List ports(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisExecProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import redis.embedded.util.Architecture; 8 | import redis.embedded.util.JarUtil; 9 | import redis.embedded.util.OS; 10 | import redis.embedded.util.OsArchitecture; 11 | import redis.embedded.util.Preconditions; 12 | 13 | public class RedisExecProvider { 14 | 15 | private final Map executables = new HashMap(); 16 | 17 | public static RedisExecProvider defaultProvider() { 18 | return new RedisExecProvider(); 19 | } 20 | 21 | private RedisExecProvider() { 22 | initExecutables(); 23 | } 24 | 25 | private void initExecutables() { 26 | // TODO: Include windows binaries 27 | // executables.put(OsArchitecture.WINDOWS_x86, "redis-server-4.0.10.exe"); 28 | // executables.put(OsArchitecture.WINDOWS_x86_64, "redis-server-4.0.10.exe"); 29 | 30 | executables.put(OsArchitecture.UNIX_x86, "redis-server-4.0.10-32"); 31 | executables.put(OsArchitecture.UNIX_x86_64, "redis-server-4.0.10"); 32 | 33 | executables.put(OsArchitecture.MAC_OS_X_x86, "redis-server-4.0.10.app"); 34 | executables.put(OsArchitecture.MAC_OS_X_x86_64, "redis-server-4.0.10.app"); 35 | } 36 | 37 | public RedisExecProvider override(OS os, String executable) { 38 | Preconditions.checkNotNull(executable); 39 | for (Architecture arch : Architecture.values()) { 40 | override(os, arch, executable); 41 | } 42 | return this; 43 | } 44 | 45 | public RedisExecProvider override(OS os, Architecture arch, String executable) { 46 | Preconditions.checkNotNull(executable); 47 | executables.put(new OsArchitecture(os, arch), executable); 48 | return this; 49 | } 50 | 51 | public File get() throws IOException { 52 | OsArchitecture osArch = OsArchitecture.detect(); 53 | String executablePath = executables.get(osArch); 54 | return fileExists(executablePath) 55 | ? new File(executablePath) 56 | : JarUtil.extractExecutableFromJar(executablePath); 57 | } 58 | 59 | private boolean fileExists(String executablePath) { 60 | return new File(executablePath).exists(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisServer.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class RedisServer extends AbstractRedisInstance { 10 | private static final String REDIS_READY_PATTERN = ".*# Server initialized.*"; 11 | private static final int DEFAULT_REDIS_PORT = 6379; 12 | 13 | public RedisServer() throws IOException { 14 | this(DEFAULT_REDIS_PORT); 15 | } 16 | 17 | public RedisServer(int port) throws IOException { 18 | super(port); 19 | File executable = RedisExecProvider.defaultProvider().get(); 20 | this.args = Arrays.asList(executable.getAbsolutePath(), "--port", Integer.toString(port)); 21 | } 22 | 23 | public RedisServer(File executable, int port) { 24 | super(port); 25 | this.args = Arrays.asList(executable.getAbsolutePath(), "--port", Integer.toString(port)); 26 | } 27 | 28 | public RedisServer(RedisExecProvider redisExecProvider, int port) throws IOException { 29 | super(port); 30 | this.args = 31 | Arrays.asList(redisExecProvider.get().getAbsolutePath(), "--port", Integer.toString(port)); 32 | } 33 | 34 | RedisServer(List args, int port) { 35 | super(port); 36 | this.args = new ArrayList(args); 37 | } 38 | 39 | public static RedisServerBuilder builder() { 40 | return new RedisServerBuilder(); 41 | } 42 | 43 | @Override 44 | protected String redisReadyPattern() { 45 | return REDIS_READY_PATTERN; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/RedisServerBuilder.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.InetSocketAddress; 6 | import java.nio.charset.Charset; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import redis.embedded.exceptions.RedisBuildingException; 10 | import redis.embedded.util.Files; 11 | 12 | public class RedisServerBuilder { 13 | private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 14 | private static final String CONF_FILENAME = "embedded-redis-server"; 15 | 16 | private File executable; 17 | private RedisExecProvider redisExecProvider = RedisExecProvider.defaultProvider(); 18 | private int port = 6379; 19 | private InetSocketAddress slaveOf; 20 | private String redisConf; 21 | 22 | private StringBuilder redisConfigBuilder; 23 | 24 | public RedisServerBuilder redisExecProvider(RedisExecProvider redisExecProvider) { 25 | this.redisExecProvider = redisExecProvider; 26 | return this; 27 | } 28 | 29 | public RedisServerBuilder port(int port) { 30 | this.port = port; 31 | return this; 32 | } 33 | 34 | public RedisServerBuilder slaveOf(String hostname, int port) { 35 | this.slaveOf = new InetSocketAddress(hostname, port); 36 | return this; 37 | } 38 | 39 | public RedisServerBuilder slaveOf(InetSocketAddress slaveOf) { 40 | this.slaveOf = slaveOf; 41 | return this; 42 | } 43 | 44 | public RedisServerBuilder configFile(String redisConf) { 45 | if (redisConfigBuilder != null) { 46 | throw new RedisBuildingException( 47 | "Redis configuration is already partially build using setting(String) method!"); 48 | } 49 | this.redisConf = redisConf; 50 | return this; 51 | } 52 | 53 | public RedisServerBuilder setting(String configLine) { 54 | if (redisConf != null) { 55 | throw new RedisBuildingException("Redis configuration is already set using redis conf file!"); 56 | } 57 | 58 | if (redisConfigBuilder == null) { 59 | redisConfigBuilder = new StringBuilder(); 60 | } 61 | 62 | redisConfigBuilder.append(configLine); 63 | redisConfigBuilder.append(LINE_SEPARATOR); 64 | return this; 65 | } 66 | 67 | public RedisServer build() { 68 | tryResolveConfAndExec(); 69 | List args = buildCommandArgs(); 70 | return new RedisServer(args, port); 71 | } 72 | 73 | public void reset() { 74 | this.executable = null; 75 | this.redisConfigBuilder = null; 76 | this.slaveOf = null; 77 | this.redisConf = null; 78 | } 79 | 80 | private void tryResolveConfAndExec() { 81 | try { 82 | resolveConfAndExec(); 83 | } catch (IOException e) { 84 | throw new RedisBuildingException("Could not build server instance", e); 85 | } 86 | } 87 | 88 | private void resolveConfAndExec() throws IOException { 89 | if (redisConf == null && redisConfigBuilder != null) { 90 | File redisConfigFile = File.createTempFile(resolveConfigName(), ".conf"); 91 | redisConfigFile.deleteOnExit(); 92 | Files.write(redisConfigBuilder.toString(), redisConfigFile, Charset.forName("UTF-8")); 93 | redisConf = redisConfigFile.getAbsolutePath(); 94 | } 95 | 96 | try { 97 | executable = redisExecProvider.get(); 98 | } catch (Exception e) { 99 | throw new RedisBuildingException("Failed to resolve executable", e); 100 | } 101 | } 102 | 103 | private String resolveConfigName() { 104 | return CONF_FILENAME + "_" + port; 105 | } 106 | 107 | private List buildCommandArgs() { 108 | List args = new ArrayList(); 109 | args.add(executable.getAbsolutePath()); 110 | 111 | if (!(redisConf == null || redisConf.isEmpty())) { 112 | args.add(redisConf); 113 | } 114 | 115 | args.add("--port"); 116 | args.add(Integer.toString(port)); 117 | 118 | if (slaveOf != null) { 119 | args.add("--slaveof"); 120 | args.add(slaveOf.getHostName()); 121 | args.add(Integer.toString(slaveOf.getPort())); 122 | } 123 | 124 | return args; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/EmbeddedRedisException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class EmbeddedRedisException extends RuntimeException { 4 | public EmbeddedRedisException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | 8 | public EmbeddedRedisException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/OsDetectionException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class OsDetectionException extends RuntimeException { 4 | public OsDetectionException(String message) { 5 | super(message); 6 | } 7 | 8 | public OsDetectionException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/exceptions/RedisBuildingException.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.exceptions; 2 | 3 | public class RedisBuildingException extends RuntimeException { 4 | public RedisBuildingException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | 8 | public RedisBuildingException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/EphemeralPortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import redis.embedded.PortProvider; 6 | import redis.embedded.exceptions.RedisBuildingException; 7 | 8 | public class EphemeralPortProvider implements PortProvider { 9 | @Override 10 | public int next() { 11 | try { 12 | final ServerSocket socket = new ServerSocket(0); 13 | socket.setReuseAddress(false); 14 | int port = socket.getLocalPort(); 15 | socket.close(); 16 | return port; 17 | } catch (IOException e) { 18 | // should not ever happen 19 | throw new RedisBuildingException("Could not provide ephemeral port", e); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/PredefinedPortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import redis.embedded.PortProvider; 8 | import redis.embedded.exceptions.RedisBuildingException; 9 | 10 | public class PredefinedPortProvider implements PortProvider { 11 | private final List ports = new LinkedList(); 12 | private final Iterator current; 13 | 14 | public PredefinedPortProvider(Collection ports) { 15 | this.ports.addAll(ports); 16 | this.current = this.ports.iterator(); 17 | } 18 | 19 | @Override 20 | public synchronized int next() { 21 | if (!current.hasNext()) { 22 | throw new RedisBuildingException("Run out of Redis ports!"); 23 | } 24 | return current.next(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/ports/SequencePortProvider.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | import redis.embedded.PortProvider; 5 | 6 | public class SequencePortProvider implements PortProvider { 7 | private AtomicInteger currentPort = new AtomicInteger(26379); 8 | 9 | public SequencePortProvider() {} 10 | 11 | public SequencePortProvider(int currentPort) { 12 | this.currentPort.set(currentPort); 13 | } 14 | 15 | public void setCurrentPort(int port) { 16 | currentPort.set(port); 17 | } 18 | 19 | @Override 20 | public int next() { 21 | return currentPort.getAndIncrement(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/Architecture.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public enum Architecture { 4 | x86, 5 | x86_64 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/Files.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import java.io.*; 4 | import java.net.URL; 5 | import java.nio.charset.Charset; 6 | import java.util.UUID; 7 | 8 | public class Files { 9 | public static void write(String from, File to, Charset charset) throws IOException { 10 | FileOutputStream fos = null; 11 | try { 12 | fos = new FileOutputStream(to); 13 | OutputStreamWriter osw = new OutputStreamWriter(fos, charset); 14 | osw.write(from); 15 | osw.flush(); 16 | osw.close(); 17 | } finally { 18 | if (fos != null) { 19 | try { 20 | fos.close(); 21 | } catch (Throwable ignored) { 22 | // ignore 23 | } 24 | } 25 | } 26 | } 27 | 28 | public static File createTempDir() throws IOException { 29 | File baseDir = new File(System.getProperty("java.io.tmpdir")); 30 | File tmpDir = new File(baseDir, UUID.randomUUID().toString()); 31 | if (tmpDir.mkdir()) { 32 | return tmpDir; 33 | } 34 | throw new IllegalStateException("Failed to create temp directory " + tmpDir.getAbsolutePath()); 35 | } 36 | 37 | public static void copyURLToFile(URL url, File destination) throws IOException { 38 | FileOutputStream dest = null; 39 | InputStream src = null; 40 | byte[] buf = new byte[16 * 1024]; 41 | int bytesRead; 42 | 43 | try { 44 | src = url.openStream(); 45 | dest = new FileOutputStream(destination); 46 | while ((bytesRead = src.read(buf)) != -1) { 47 | dest.write(buf, 0, bytesRead); 48 | } 49 | dest.flush(); 50 | dest.close(); 51 | src.close(); 52 | } finally { 53 | if (dest != null) { 54 | try { 55 | dest.close(); 56 | } catch (Throwable ignored) { 57 | // ignore 58 | } 59 | } 60 | if (src != null) { 61 | try { 62 | src.close(); 63 | } catch (Throwable ignored) { 64 | // ignore 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/JarUtil.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | public class JarUtil { 7 | 8 | public static File extractExecutableFromJar(String executable) throws IOException { 9 | File tmpDir = Files.createTempDir(); 10 | tmpDir.deleteOnExit(); 11 | 12 | File command = new File(tmpDir, executable); 13 | 14 | Files.copyURLToFile(Resources.getResource(executable), command); 15 | command.deleteOnExit(); 16 | command.setExecutable(true); 17 | 18 | return command; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/JedisUtil.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Set; 6 | import redis.embedded.Redis; 7 | 8 | public class JedisUtil { 9 | public static Set jedisHosts(Redis redis) { 10 | final List ports = redis.ports(); 11 | return portsToJedisHosts(ports); 12 | } 13 | 14 | public static Set portsToJedisHosts(List ports) { 15 | Set hosts = new HashSet(); 16 | for (Integer p : ports) { 17 | hosts.add("localhost:" + p); 18 | } 19 | return hosts; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OS.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public enum OS { 4 | WINDOWS, 5 | UNIX, 6 | MAC_OS_X 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OSDetector.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStreamReader; 5 | import redis.embedded.exceptions.OsDetectionException; 6 | 7 | public class OSDetector { 8 | 9 | public static OS getOS() { 10 | String osName = System.getProperty("os.name").toLowerCase(); 11 | 12 | if (osName.contains("win")) { 13 | return OS.WINDOWS; 14 | } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { 15 | return OS.UNIX; 16 | } else if ("Mac OS X".equalsIgnoreCase(osName)) { 17 | return OS.MAC_OS_X; 18 | } else { 19 | throw new OsDetectionException("Unrecognized OS: " + osName); 20 | } 21 | } 22 | 23 | public static Architecture getArchitecture() { 24 | OS os = getOS(); 25 | switch (os) { 26 | case WINDOWS: 27 | return getWindowsArchitecture(); 28 | case UNIX: 29 | return getUnixArchitecture(); 30 | case MAC_OS_X: 31 | return getMacOSXArchitecture(); 32 | default: 33 | throw new OsDetectionException("Unrecognized OS: " + os); 34 | } 35 | } 36 | 37 | private static Architecture getWindowsArchitecture() { 38 | String arch = System.getenv("PROCESSOR_ARCHITECTURE"); 39 | String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); 40 | 41 | if (arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64")) { 42 | return Architecture.x86_64; 43 | } else { 44 | return Architecture.x86; 45 | } 46 | } 47 | 48 | private static Architecture getUnixArchitecture() { 49 | BufferedReader input = null; 50 | try { 51 | String line; 52 | Process proc = Runtime.getRuntime().exec("uname -m"); 53 | input = new BufferedReader(new InputStreamReader(proc.getInputStream())); 54 | while ((line = input.readLine()) != null) { 55 | if (line.length() > 0) { 56 | if (line.contains("64")) { 57 | return Architecture.x86_64; 58 | } 59 | } 60 | } 61 | } catch (Exception e) { 62 | throw new OsDetectionException(e); 63 | } finally { 64 | try { 65 | if (input != null) { 66 | input.close(); 67 | } 68 | } catch (Exception ignored) { 69 | // ignore 70 | } 71 | } 72 | 73 | return Architecture.x86; 74 | } 75 | 76 | private static Architecture getMacOSXArchitecture() { 77 | BufferedReader input = null; 78 | try { 79 | String line; 80 | Process proc = Runtime.getRuntime().exec("sysctl hw"); 81 | input = new BufferedReader(new InputStreamReader(proc.getInputStream())); 82 | while ((line = input.readLine()) != null) { 83 | if (line.length() > 0) { 84 | if ((line.contains("cpu64bit_capable")) && (line.trim().endsWith("1"))) { 85 | return Architecture.x86_64; 86 | } 87 | } 88 | } 89 | } catch (Exception e) { 90 | throw new OsDetectionException(e); 91 | } finally { 92 | try { 93 | if (input != null) { 94 | input.close(); 95 | } 96 | } catch (Exception ignored) { 97 | // ignore 98 | } 99 | } 100 | 101 | return Architecture.x86; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/OsArchitecture.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public class OsArchitecture { 4 | 5 | public static final OsArchitecture WINDOWS_x86 = new OsArchitecture(OS.WINDOWS, Architecture.x86); 6 | public static final OsArchitecture WINDOWS_x86_64 = 7 | new OsArchitecture(OS.WINDOWS, Architecture.x86_64); 8 | 9 | public static final OsArchitecture UNIX_x86 = new OsArchitecture(OS.UNIX, Architecture.x86); 10 | public static final OsArchitecture UNIX_x86_64 = new OsArchitecture(OS.UNIX, Architecture.x86_64); 11 | 12 | public static final OsArchitecture MAC_OS_X_x86 = 13 | new OsArchitecture(OS.MAC_OS_X, Architecture.x86); 14 | public static final OsArchitecture MAC_OS_X_x86_64 = 15 | new OsArchitecture(OS.MAC_OS_X, Architecture.x86_64); 16 | 17 | private final OS os; 18 | private final Architecture arch; 19 | 20 | public static OsArchitecture detect() { 21 | OS os = OSDetector.getOS(); 22 | Architecture arch = OSDetector.getArchitecture(); 23 | return new OsArchitecture(os, arch); 24 | } 25 | 26 | public OsArchitecture(OS os, Architecture arch) { 27 | Preconditions.checkNotNull(os); 28 | Preconditions.checkNotNull(arch); 29 | 30 | this.os = os; 31 | this.arch = arch; 32 | } 33 | 34 | public OS os() { 35 | return os; 36 | } 37 | 38 | public Architecture arch() { 39 | return arch; 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | 47 | OsArchitecture that = (OsArchitecture) o; 48 | 49 | return arch == that.arch && os == that.os; 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = os.hashCode(); 55 | result = 31 * result + arch.hashCode(); 56 | return result; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/Preconditions.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | public class Preconditions { 4 | public static T checkNotNull(T reference) { 5 | if (reference == null) { 6 | throw new NullPointerException(); 7 | } else { 8 | return reference; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/redis/embedded/util/Resources.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.util; 2 | 3 | import java.net.URL; 4 | 5 | public class Resources { 6 | public static URL getResource(String resource) { 7 | ClassLoader cl = Thread.currentThread().getContextClassLoader(); 8 | if (cl == null) { 9 | cl = Resources.class.getClassLoader(); 10 | } 11 | 12 | URL url = cl.getResource(resource); 13 | 14 | if (url == null) { 15 | throw new IllegalArgumentException("resource " + resource + " not found"); 16 | } 17 | 18 | return url; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/redis-server-4.0.10: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/embedded-redis/33dd8d6991328137215821d28398f75182e8d0ac/src/main/resources/redis-server-4.0.10 -------------------------------------------------------------------------------- /src/main/resources/redis-server-4.0.10-32: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/embedded-redis/33dd8d6991328137215821d28398f75182e8d0ac/src/main/resources/redis-server-4.0.10-32 -------------------------------------------------------------------------------- /src/main/resources/redis-server-4.0.10.app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/embedded-redis/33dd8d6991328137215821d28398f75182e8d0ac/src/main/resources/redis-server-4.0.10.app -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisServerClusterTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import redis.clients.jedis.Jedis; 9 | import redis.clients.jedis.JedisPool; 10 | 11 | public class RedisServerClusterTest { 12 | 13 | private RedisServer redisServer1; 14 | private RedisServer redisServer2; 15 | 16 | @Before 17 | public void setUp() throws Exception { 18 | redisServer1 = RedisServer.builder().port(6300).build(); 19 | 20 | redisServer2 = RedisServer.builder().port(6301).slaveOf("localhost", 6300).build(); 21 | 22 | redisServer1.start(); 23 | redisServer2.start(); 24 | } 25 | 26 | @Test 27 | public void testSimpleOperationsAfterRun() throws Exception { 28 | JedisPool pool = null; 29 | Jedis jedis = null; 30 | try { 31 | pool = new JedisPool("localhost", 6300); 32 | jedis = pool.getResource(); 33 | jedis.mset("abc", "1", "def", "2"); 34 | 35 | assertEquals("1", jedis.mget("abc").get(0)); 36 | assertEquals("2", jedis.mget("def").get(0)); 37 | assertEquals(null, jedis.mget("xyz").get(0)); 38 | } finally { 39 | if (jedis != null) pool.returnResource(jedis); 40 | } 41 | } 42 | 43 | @After 44 | public void tearDown() throws Exception { 45 | redisServer1.stop(); 46 | redisServer2.stop(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/RedisServerTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import redis.clients.jedis.Jedis; 7 | import redis.clients.jedis.JedisPool; 8 | import redis.embedded.exceptions.RedisBuildingException; 9 | import redis.embedded.util.Architecture; 10 | import redis.embedded.util.OS; 11 | import redis.embedded.util.Resources; 12 | 13 | public class RedisServerTest { 14 | 15 | private RedisServer redisServer; 16 | 17 | @Test(timeout = 1500L) 18 | public void testSimpleRun() throws Exception { 19 | redisServer = new RedisServer(6379); 20 | redisServer.start(); 21 | Thread.sleep(1000L); 22 | redisServer.stop(); 23 | } 24 | 25 | @Test(expected = RuntimeException.class) 26 | public void shouldNotAllowMultipleRunsWithoutStop() throws Exception { 27 | try { 28 | redisServer = new RedisServer(6379); 29 | redisServer.start(); 30 | redisServer.start(); 31 | } finally { 32 | redisServer.stop(); 33 | } 34 | } 35 | 36 | @Test 37 | public void shouldAllowSubsequentRuns() throws Exception { 38 | redisServer = new RedisServer(6379); 39 | redisServer.start(); 40 | redisServer.stop(); 41 | 42 | redisServer.start(); 43 | redisServer.stop(); 44 | 45 | redisServer.start(); 46 | redisServer.stop(); 47 | } 48 | 49 | @Test 50 | public void testSimpleOperationsAfterRun() throws Exception { 51 | redisServer = new RedisServer(6379); 52 | redisServer.start(); 53 | 54 | JedisPool pool = null; 55 | Jedis jedis = null; 56 | try { 57 | pool = new JedisPool("localhost", 6379); 58 | jedis = pool.getResource(); 59 | jedis.mset("abc", "1", "def", "2"); 60 | 61 | assertEquals("1", jedis.mget("abc").get(0)); 62 | assertEquals("2", jedis.mget("def").get(0)); 63 | assertEquals(null, jedis.mget("xyz").get(0)); 64 | } finally { 65 | if (jedis != null) pool.returnResource(jedis); 66 | redisServer.stop(); 67 | } 68 | } 69 | 70 | @Test 71 | public void shouldIndicateInactiveBeforeStart() throws Exception { 72 | redisServer = new RedisServer(6379); 73 | assertFalse(redisServer.isActive()); 74 | } 75 | 76 | @Test 77 | public void shouldIndicateActiveAfterStart() throws Exception { 78 | redisServer = new RedisServer(6379); 79 | redisServer.start(); 80 | assertTrue(redisServer.isActive()); 81 | redisServer.stop(); 82 | } 83 | 84 | @Test 85 | public void shouldIndicateInactiveAfterStop() throws Exception { 86 | redisServer = new RedisServer(6379); 87 | redisServer.start(); 88 | redisServer.stop(); 89 | assertFalse(redisServer.isActive()); 90 | } 91 | 92 | @Test 93 | public void shouldOverrideDefaultExecutable() throws Exception { 94 | RedisExecProvider customProvider = 95 | RedisExecProvider.defaultProvider() 96 | .override( 97 | OS.UNIX, 98 | Architecture.x86, 99 | Resources.getResource("redis-server-4.0.10-32").getFile()) 100 | .override( 101 | OS.UNIX, 102 | Architecture.x86_64, 103 | Resources.getResource("redis-server-4.0.10").getFile()) 104 | // TODO .override(OS.WINDOWS, Architecture.x86, 105 | // Resources.getResource("redis-server-4.0.10.exe").getFile()) 106 | // TODO .override(OS.WINDOWS, Architecture.x86_64, 107 | // Resources.getResource("redis-server-4.0.10.exe").getFile()) 108 | .override(OS.MAC_OS_X, Resources.getResource("redis-server-4.0.10.app").getFile()); 109 | 110 | redisServer = new RedisServerBuilder().redisExecProvider(customProvider).build(); 111 | } 112 | 113 | @Test(expected = RedisBuildingException.class) 114 | public void shouldFailWhenBadExecutableGiven() throws Exception { 115 | RedisExecProvider buggyProvider = 116 | RedisExecProvider.defaultProvider() 117 | .override(OS.UNIX, "some") 118 | .override(OS.WINDOWS, Architecture.x86, "some") 119 | .override(OS.WINDOWS, Architecture.x86_64, "some") 120 | .override(OS.MAC_OS_X, "some"); 121 | 122 | redisServer = new RedisServerBuilder().redisExecProvider(buggyProvider).build(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/SpringDataConnectivityTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.core.StringRedisTemplate; 11 | import redis.clients.jedis.JedisShardInfo; 12 | 13 | public class SpringDataConnectivityTest { 14 | 15 | private RedisServer redisServer; 16 | private RedisTemplate template; 17 | private JedisConnectionFactory connectionFactory; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | redisServer = new RedisServer(6379); 22 | redisServer.start(); 23 | 24 | JedisShardInfo shardInfo = new JedisShardInfo("localhost", 6379); 25 | connectionFactory = new JedisConnectionFactory(); 26 | connectionFactory.setShardInfo(shardInfo); 27 | 28 | template = new StringRedisTemplate(); 29 | template.setConnectionFactory(connectionFactory); 30 | template.afterPropertiesSet(); 31 | } 32 | 33 | @Test 34 | public void shouldBeAbleToUseSpringData() throws Exception { 35 | // given 36 | template.opsForValue().set("foo", "bar"); 37 | 38 | // when 39 | String result = template.opsForValue().get("foo"); 40 | 41 | // then 42 | assertEquals("bar", result); 43 | } 44 | 45 | @After 46 | public void tearDown() throws Exception { 47 | redisServer.stop(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/EphemeralPortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import org.junit.Test; 8 | 9 | public class EphemeralPortProviderTest { 10 | 11 | @Test 12 | public void nextShouldGiveNextFreeEphemeralPort() throws Exception { 13 | // given 14 | final int portCount = 20; 15 | final EphemeralPortProvider provider = new EphemeralPortProvider(); 16 | 17 | // when 18 | final List ports = new ArrayList(); 19 | for (int i = 0; i < portCount; i++) { 20 | ports.add(provider.next()); 21 | } 22 | 23 | // then 24 | System.out.println(ports); 25 | assertEquals(20, ports.size()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/PredefinedPortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import org.junit.Test; 10 | import redis.embedded.exceptions.RedisBuildingException; 11 | 12 | public class PredefinedPortProviderTest { 13 | 14 | @Test 15 | public void nextShouldGiveNextPortFromAssignedList() throws Exception { 16 | // given 17 | Collection ports = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 18 | final PredefinedPortProvider provider = new PredefinedPortProvider(ports); 19 | 20 | // when 21 | final List returnedPorts = new ArrayList(); 22 | for (int i = 0; i < ports.size(); i++) { 23 | returnedPorts.add(provider.next()); 24 | } 25 | 26 | // then 27 | assertEquals(ports, returnedPorts); 28 | } 29 | 30 | @Test(expected = RedisBuildingException.class) 31 | public void nextShouldThrowExceptionWhenRunOutsOfPorts() throws Exception { 32 | // given 33 | Collection ports = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 34 | final PredefinedPortProvider provider = new PredefinedPortProvider(ports); 35 | 36 | // when 37 | for (int i = 0; i < ports.size(); i++) { 38 | provider.next(); 39 | } 40 | 41 | // then exception should be thrown... 42 | provider.next(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/redis/embedded/ports/SequencePortProviderTest.java: -------------------------------------------------------------------------------- 1 | package redis.embedded.ports; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | public class SequencePortProviderTest { 8 | 9 | @Test 10 | public void nextShouldIncrementPorts() throws Exception { 11 | // given 12 | final int startPort = 10; 13 | final int portCount = 101; 14 | final SequencePortProvider provider = new SequencePortProvider(startPort); 15 | 16 | // when 17 | int max = 0; 18 | for (int i = 0; i < portCount; i++) { 19 | int port = provider.next(); 20 | if (port > max) { 21 | max = port; 22 | } 23 | } 24 | 25 | // then 26 | assertEquals(portCount + startPort - 1, max); 27 | } 28 | } 29 | --------------------------------------------------------------------------------