Skip to content

Commit c8b33cb

Browse files
authored
Merge pull request localstack#22 from atlassian/feat/junit-test
Add JUnit integration + example test class
2 parents 12e9f1b + 4a094ce commit c8b33cb

File tree

14 files changed

+452
-64
lines changed

14 files changed

+452
-64
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ addons:
1111
apt:
1212
packages:
1313
- oracle-java8-installer
14+
- oracle-java8-set-default
15+
16+
env:
17+
global:
18+
- JAVA_HOME=/usr/lib/jvm/java-8-oracle
1419

1520
install:
1621
- travis_wait 20 make install

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ compile: ## Compile Java code (KCL library utils)
2828
echo "Compiling"
2929
$(VENV_RUN); python -c 'from localstack.utils.kinesis import kclipy_helper; print kclipy_helper.get_kcl_classpath()'
3030
javac -cp $(shell $(VENV_RUN); python -c 'from localstack.utils.kinesis import kclipy_helper; print kclipy_helper.get_kcl_classpath()') localstack/utils/kinesis/java/com/atlassian/*.java
31+
(test ! -e ext/java || cd ext/java && mvn -DskipTests package)
3132
# TODO enable once we want to support Java-based Lambdas
3233
# (cd localstack/mock && mvn package)
3334

@@ -39,7 +40,7 @@ coveralls: ## Publish coveralls metrics
3940
($(VENV_RUN); coveralls)
4041

4142
infra: ## Manually start the local infrastructure for testing
42-
($(VENV_RUN); localstack/mock/infra.py)
43+
$(VENV_RUN); exec localstack/mock/infra.py
4344

4445
web: ## Start web application (dashboard)
4546
($(VENV_RUN); bin/localstack web --port=8081)

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,17 @@ missing functionality on top of them:
6868
* `npm` (node.js package manager)
6969
* `java`/`javac` (Java runtime environment and compiler)
7070

71-
## Installation
71+
## Installing
7272

73-
To install the tool and all its dependencies, run the following command:
73+
The easiest way to install *LocalStack* is via `pip`:
74+
75+
```
76+
pip install localstack
77+
```
78+
79+
## Developing
80+
81+
If you pull the repo in order to extend/modify LocalStack, run this command to install all dependencies:
7482

7583
```
7684
make install
@@ -131,6 +139,27 @@ def my_app_test():
131139

132140
See the example test file `tests/test_integration.py` for more details.
133141

142+
## Integration with Java/JUnit
143+
144+
In order to use *LocalStack* with Java, the project ships with a simple JUnit runner. Take a look
145+
at the example JUnit test in `ext/java`. When you run the test, all dependencies are automatically
146+
downloaded and installed to a temporary directory in your system.
147+
148+
```
149+
@RunWith(LocalstackTestRunner.class)
150+
public class MyCloudAppTest {
151+
152+
@Test
153+
public void testLocalS3API() {
154+
AmazonS3 s3 = new AmazonS3Client(...);
155+
s3.setEndpoint(LocalstackTestRunner.getEndpointS3());
156+
List<Bucket> buckets = s3.listBuckets();
157+
...
158+
}
159+
160+
}
161+
```
162+
134163
## Web Dashboard
135164

136165
The projects also comes with a simple Web dashboard that allows to view the
@@ -143,6 +172,8 @@ make web
143172

144173
## Change Log
145174

175+
* v0.3.0: Add simple integration for JUnit; improve process signal handling
176+
* v0.2.11: Refactored the AWS assume role function
146177
* v0.2.10: Added AWS assume role functionality.
147178
* v0.2.9: Kinesis error response formatting
148179
* v0.2.7: Throw Kinesis errors randomly

ext/java/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target/

ext/java/pom.xml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.atlassian</groupId>
6+
<artifactId>localstack-utils</artifactId>
7+
<packaging>jar</packaging>
8+
<version>1.0-SNAPSHOT</version>
9+
<name>localstack-utils</name>
10+
11+
<dependencies>
12+
<dependency>
13+
<groupId>com.amazonaws</groupId>
14+
<artifactId>aws-lambda-java-core</artifactId>
15+
<version>1.1.0</version>
16+
</dependency>
17+
<dependency>
18+
<groupId>com.amazonaws</groupId>
19+
<artifactId>aws-lambda-java-events</artifactId>
20+
<version>1.3.0</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>junit</groupId>
24+
<artifactId>junit</artifactId>
25+
<version>4.12</version>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.amazonaws</groupId>
29+
<artifactId>aws-java-sdk</artifactId>
30+
<version>1.11.86</version>
31+
<scope>test</scope>
32+
</dependency>
33+
</dependencies>
34+
35+
<build>
36+
<plugins>
37+
<plugin>
38+
<groupId>org.apache.maven.plugins</groupId>
39+
<artifactId>maven-shade-plugin</artifactId>
40+
<version>2.3</version>
41+
<configuration>
42+
<createDependencyReducedPom>false</createDependencyReducedPom>
43+
</configuration>
44+
<executions>
45+
<execution>
46+
<phase>package</phase>
47+
<goals>
48+
<goal>shade</goal>
49+
</goals>
50+
</execution>
51+
</executions>
52+
</plugin>
53+
</plugins>
54+
</build>
55+
</project>

localstack/mock/src/main/java/com/atlassian/LambdaContext.java renamed to ext/java/src/main/java/com/atlassian/localstack/LambdaContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.atlassian;
1+
package com.atlassian.localstack;
22

33
import java.util.logging.Level;
44
import java.util.logging.Logger;

localstack/mock/src/main/java/com/atlassian/LambdaExecutor.java renamed to ext/java/src/main/java/com/atlassian/localstack/LambdaExecutor.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.atlassian;
1+
package com.atlassian.localstack;
22

33
import java.io.BufferedReader;
44
import java.io.BufferedWriter;
@@ -21,6 +21,11 @@
2121
import com.amazonaws.services.lambda.runtime.events.KinesisEvent.Record;
2222
import com.fasterxml.jackson.databind.ObjectMapper;
2323

24+
/**
25+
* TODO: Support for AWS Lambda functions written in Java is work in progress.
26+
*
27+
* @author Waldemar Hummer
28+
*/
2429
public class LambdaExecutor {
2530

2631
@SuppressWarnings("unchecked")
@@ -32,8 +37,8 @@ public static void main(String[] args) throws Exception {
3237
if(test) {
3338
final String testFile = "/tmp/test.event.kinesis.json";
3439
String content = "{\"records\": ["
35-
+ "{\"kinesis\": " +
36-
+ "{}" +
40+
+ "{\"kinesis\": "
41+
+ "{}"
3742
+ "}"
3843
+ "]}";
3944
writeFile(testFile, content);
@@ -53,6 +58,7 @@ public void run() {
5358
KinesisEvent event = new KinesisEvent();
5459
ObjectMapper reader = new ObjectMapper();
5560
String fileContent = readFile(args[1]);
61+
@SuppressWarnings("deprecation")
5662
Map<String,Object> map = reader.reader(Map.class).readValue(fileContent);
5763
List<Map<String,Object>> records = (List<Map<String, Object>>) get(map, "Records");
5864
event.setRecords(new LinkedList<KinesisEvent.KinesisEventRecord>());
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.atlassian.localstack;
2+
3+
import java.io.BufferedReader;
4+
import java.io.File;
5+
import java.io.FileInputStream;
6+
import java.io.IOException;
7+
import java.io.InputStreamReader;
8+
import java.util.Map;
9+
import java.util.concurrent.atomic.AtomicReference;
10+
import java.util.logging.Logger;
11+
import java.util.regex.Pattern;
12+
13+
import org.junit.runner.notification.RunNotifier;
14+
import org.junit.runners.BlockJUnit4ClassRunner;
15+
import org.junit.runners.model.InitializationError;
16+
17+
import com.amazonaws.util.IOUtils;
18+
19+
/**
20+
* Simple JUnit test runner that automatically downloads, installs, starts,
21+
* and stops the LocalStack local cloud infrastructure components.
22+
*
23+
* Should work cross-OS, however has been only tested under Unix (Linux/MacOS).
24+
*
25+
* @author Waldemar Hummer
26+
*/
27+
public class LocalstackTestRunner extends BlockJUnit4ClassRunner {
28+
29+
private static final AtomicReference<Process> INFRA_STARTED = new AtomicReference<Process>();
30+
private static String CONFIG_FILE_CONTENT = "";
31+
32+
private static final String INFRA_READY_MARKER = "Ready.";
33+
private static final String TMP_INSTALL_DIR = System.getProperty("java.io.tmpdir") +
34+
File.separator + "localstack_install_dir";
35+
private static final String ADDITIONAL_PATH = "/usr/local/bin/";
36+
private static final String LOCALHOST = "localhost";
37+
private static final String LOCALSTACK_REPO_URL = "https://github.com/atlassian/localstack";
38+
39+
private static final Logger LOG = Logger.getLogger(LocalstackTestRunner.class.getName());
40+
41+
public LocalstackTestRunner(Class<?> klass) throws InitializationError {
42+
super(klass);
43+
}
44+
45+
/* SERVICE ENDPOINTS */
46+
47+
public static String getEndpointS3() {
48+
ensureInstallation();
49+
return getEndpoint("s3");
50+
}
51+
52+
public static String getEndpointKinesis() {
53+
ensureInstallation();
54+
return getEndpoint("kinesis");
55+
}
56+
57+
public static String getEndpointLambda() {
58+
ensureInstallation();
59+
return getEndpoint("lambda");
60+
}
61+
62+
public static String getEndpointDynamoDB() {
63+
ensureInstallation();
64+
return getEndpoint("dynamodb");
65+
}
66+
67+
public static String getEndpointDynamoDBStreams() {
68+
ensureInstallation();
69+
return getEndpoint("dynamodbstreams");
70+
}
71+
72+
public static String getEndpointAPIGateway() {
73+
ensureInstallation();
74+
return getEndpoint("apigateway");
75+
}
76+
77+
public static String getEndpointElasticsearch() {
78+
ensureInstallation();
79+
return getEndpoint("elasticsearch");
80+
}
81+
82+
public static String getEndpointFirehose() {
83+
ensureInstallation();
84+
return getEndpoint("firehose");
85+
}
86+
87+
public static String getEndpointSNS() {
88+
ensureInstallation();
89+
return getEndpoint("sns");
90+
}
91+
92+
public static String getEndpointSQS() {
93+
ensureInstallation();
94+
return getEndpoint("sns");
95+
}
96+
97+
/* UTILITY METHODS */
98+
99+
@Override
100+
public void run(RunNotifier notifier) {
101+
setupInfrastructure();
102+
super.run(notifier);
103+
}
104+
105+
private static void ensureInstallation() {
106+
File dir = new File(TMP_INSTALL_DIR);
107+
if(!dir.exists()) {
108+
LOG.info("Installing LocalStack to temporary directory (this might take a while): " + TMP_INSTALL_DIR);
109+
exec("git clone " + LOCALSTACK_REPO_URL + " " + TMP_INSTALL_DIR);
110+
exec("cd " + TMP_INSTALL_DIR + "; make install");
111+
}
112+
}
113+
114+
private static void killProcess(Process p) {
115+
p.destroy();
116+
p.destroyForcibly();
117+
}
118+
119+
private static String getEndpoint(String service) {
120+
ensureInstallation();
121+
String regex = ".*DEFAULT_PORT_" + service.toUpperCase() + "\\s*=\\s*([0-9]+).*";
122+
String port = Pattern.compile(regex, Pattern.DOTALL | Pattern.MULTILINE).matcher(CONFIG_FILE_CONTENT).replaceAll("$1");
123+
return "http://" + LOCALHOST + ":" + port + "/";
124+
}
125+
126+
private static Process exec(String cmd) {
127+
return exec(cmd, true);
128+
}
129+
130+
private static Process exec(String cmd, boolean wait) {
131+
try {
132+
Map<String, String> env = System.getenv();
133+
final Process p = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd},
134+
new String[]{"PATH=" + ADDITIONAL_PATH + ":" + env.get("PATH")});
135+
if (wait) {
136+
int code = p.waitFor();
137+
if(code != 0) {
138+
String stderr = IOUtils.toString(p.getErrorStream());
139+
String stdout = IOUtils.toString(p.getInputStream());
140+
throw new IllegalStateException("Failed to run command '" + cmd + "', return code " + code +
141+
".\nSTDOUT: " + stdout + "\nSTDERR: " + stderr);
142+
}
143+
} else {
144+
/* make sure we destroy the process on JVM shutdown */
145+
Runtime.getRuntime().addShutdownHook(new Thread() {
146+
public void run() {
147+
killProcess(p);
148+
}
149+
});
150+
}
151+
return p;
152+
} catch (Exception e) {
153+
throw new RuntimeException(e);
154+
}
155+
}
156+
157+
private void setupInfrastructure() {
158+
synchronized (INFRA_STARTED) {
159+
ensureInstallation();
160+
if(INFRA_STARTED.get() != null) return;
161+
String cmd = "cd " + TMP_INSTALL_DIR + "; exec make infra";
162+
Process proc;
163+
try {
164+
proc = exec(cmd, false);
165+
BufferedReader r1 = new BufferedReader(new InputStreamReader(proc.getInputStream()));
166+
String line;
167+
LOG.info("Waiting for infrastructure to be spun up");
168+
while((line = r1.readLine()) != null) {
169+
if(INFRA_READY_MARKER.equals(line)) {
170+
break;
171+
}
172+
}
173+
/* read contents of LocalStack config file */
174+
String configFile = TMP_INSTALL_DIR + File.separator + "localstack" + File.separator + "constants.py";
175+
CONFIG_FILE_CONTENT = IOUtils.toString(new FileInputStream(configFile));
176+
INFRA_STARTED.set(proc);
177+
} catch (IOException e) {
178+
throw new RuntimeException(e);
179+
}
180+
}
181+
}
182+
183+
public static void teardownInfrastructure() {
184+
Process proc = INFRA_STARTED.get();
185+
if(proc == null) {
186+
return;
187+
}
188+
killProcess(proc);
189+
}
190+
}

0 commit comments

Comments
 (0)