You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ud...@apache.org on 2017/08/11 16:47:01 UTC

[02/10] geode git commit: GEODE-3313: Test utility supports building jar files with multiple classes

GEODE-3313: Test utility supports building jar files with multiple classes


Project: http://git-wip-us.apache.org/repos/asf/geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/06b839c2
Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/06b839c2
Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/06b839c2

Branch: refs/heads/feature/GEODE-3416
Commit: 06b839c2b458c47eb26f42de65ca82947b8988c2
Parents: 1efbf58
Author: Jared Stewart <js...@pivotal.io>
Authored: Tue Aug 8 10:11:07 2017 -0700
Committer: Jared Stewart <js...@pivotal.io>
Committed: Thu Aug 10 09:27:39 2017 -0700

----------------------------------------------------------------------
 geode-junit/build.gradle                        |   6 +-
 .../geode/test/compiler/ClassNameExtractor.java |  33 +++++
 .../geode/test/compiler/CompiledSourceCode.java |  35 +++++
 .../apache/geode/test/compiler/JarBuilder.java  | 122 ++++++++++++++++++
 .../geode/test/compiler/JavaCompiler.java       | 123 ++++++++++++++++++
 .../test/compiler/UncompiledSourceCode.java     |  71 +++++++++++
 .../test/compiler/ClassNameExtractorTest.java   |  54 ++++++++
 .../geode/test/compiler/JarBuilderTest.java     | 127 +++++++++++++++++++
 .../geode/test/compiler/JavaCompilerTest.java   |  79 ++++++++++++
 .../test/compiler/UncompiledSourceCodeTest.java |  43 +++++++
 .../geode/test/compiler/AbstractClass.java      |  18 +++
 .../geode/test/compiler/ConcreteClass.java      |  19 +++
 12 files changed, 729 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/build.gradle
----------------------------------------------------------------------
diff --git a/geode-junit/build.gradle b/geode-junit/build.gradle
index e47095f..7c533ad 100755
--- a/geode-junit/build.gradle
+++ b/geode-junit/build.gradle
@@ -19,7 +19,11 @@ dependencies {
   compile 'com.jayway.jsonpath:json-path:' + project.'json-path.version'
   testCompile 'commons-lang:commons-lang:' + project.'commons-lang.version'
   testCompile 'com.google.guava:guava:' + project.'guava.version'
-  testCompile 'org.assertj:assertj-core:' + project.'assertj-core.version'
+  compile 'org.assertj:assertj-core:' + project.'assertj-core.version'
+  compile 'commons-io:commons-io:' + project.'commons-io.version'
+  compile 'commons-lang:commons-lang:' + project.'commons-lang.version'
+  compile 'com.google.guava:guava:' + project.'guava.version'
+
 
   compile('junit:junit:' + project.'junit.version') {
     exclude module: 'hamcrest-core'

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
new file mode 100644
index 0000000..c48224c
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/ClassNameExtractor.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ClassNameExtractor {
+  private static final Pattern EXTRACT_CLASS_NAME_REGEX =
+      Pattern.compile("(?:public|private|protected)* *(?:abstract)* *(?:class|interface) +(\\w+)");
+
+  public String extractFromSourceCode(String sourceCode) {
+    Matcher m = EXTRACT_CLASS_NAME_REGEX.matcher(sourceCode);
+    if (m.find()) {
+      return m.group(1);
+    } else {
+      throw new IllegalArgumentException(
+          String.format("Unable to parse class or interface name from: \n'%s'", sourceCode));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
new file mode 100644
index 0000000..c1bfa3d
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/CompiledSourceCode.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+public class CompiledSourceCode {
+
+  /**
+   * Fully qualified classname in a format suitable for Class.forName
+   */
+  public String className;
+
+  public byte[] compiledBytecode;
+
+  public CompiledSourceCode(String className, byte[] aBytes) {
+    this.className = className.replace('/', '.');
+    this.compiledBytecode = aBytes;
+  }
+
+  @Override
+  public String toString() {
+    return className;
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
new file mode 100644
index 0000000..beea476
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/JarBuilder.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+import org.assertj.core.api.Assertions;
+
+
+/**
+ * This class accepts java source code in the format of .java source files or strings containing the
+ * contents of .java source files, and compiles the given source code into a jar file specified by
+ * the user.
+ *
+ * <p>
+ * Example of usage:
+ *
+ * <pre>
+ *  &#064;Rule
+ *  public TemporaryFolder temporaryFolder= new TemporaryFolder();
+ *
+ *  &#064;Test
+ *  public void buildJarUsingStrings() {
+ *  File tempDir = temporaryFolder.getRoot()
+ *  JarBuilder jarBuilder = new JarBuilder(tempDir);
+ *  File outputJar = new File("output.jar");
+ *
+ *  String classInFooBarPackage = &quot;package foo.bar; public class ClassA {int n = 10;}&quot;;
+ *  String classInDefaultPackage = &quot;public class ClassB {}&quot;;
+ *  jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+ *     }
+ *
+ *  &#064;Test
+ *  public void buildJarUsingFiles() {
+ *  File tempDir = temporaryFolder.getRoot()
+ *  JarBuilder jarBuilder = new JarBuilder(tempDir);
+ *  File outputJar = new File("output.jar");
+ *
+ *  File sourceFileOne = new File("ClassA.java");
+ *  File sourceFileTwo = new File("ClassB.java");
+ *  jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+ *     }
+ *
+ *  &#064;Test
+ *  public void buildJarUsingClassNames() {
+ *  File tempDir = temporaryFolder.getRoot()
+ *  JarBuilder jarBuilder = new JarBuilder(tempDir);
+ *  File outputJar = new File("output.jar");
+ *
+ *  String classInFooBarPackage = "foo.bar.ClassInFooBarPackage";
+ *  String classInDefaultPackage = "ClassInDefaultPackage";
+ *  jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+ *     }
+ * </pre>
+ **/
+public class JarBuilder {
+  private final JavaCompiler javaCompiler = new JavaCompiler();
+
+  public void buildJarFromClassNames(File outputJarFile, String... classNames) throws IOException {
+    UncompiledSourceCode[] uncompiledSourceCodes = Arrays.stream(classNames)
+        .map(UncompiledSourceCode::fromClassName).toArray(UncompiledSourceCode[]::new);
+
+    List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(uncompiledSourceCodes);
+
+    buildJar(outputJarFile, compiledSourceCodes);
+  }
+
+  public void buildJar(File outputJarFile, String... sourceFileContents) throws IOException {
+    List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(sourceFileContents);
+
+    buildJar(outputJarFile, compiledSourceCodes);
+  }
+
+  public void buildJar(File outputJarFile, File... sourceFiles) throws IOException {
+    List<CompiledSourceCode> compiledSourceCodes = javaCompiler.compile(sourceFiles);
+
+    buildJar(outputJarFile, compiledSourceCodes);
+  }
+
+  private void buildJar(File outputJarFile, List<CompiledSourceCode> compiledSourceCodes)
+      throws IOException {
+    assertThat(outputJarFile).doesNotExist();
+
+    try (FileOutputStream outputStream = new FileOutputStream(outputJarFile)) {
+      JarOutputStream jarOutputStream = new JarOutputStream(outputStream);
+      for (CompiledSourceCode compiledSource : compiledSourceCodes) {
+
+        String formattedName = compiledSource.className.replace(".", "/");
+        if (!formattedName.endsWith(".class")) {
+          formattedName = formattedName.concat(".class");
+        }
+
+        JarEntry entry = new JarEntry(formattedName);
+        entry.setTime(System.currentTimeMillis());
+        jarOutputStream.putNextEntry(entry);
+        jarOutputStream.write(compiledSource.compiledBytecode);
+        jarOutputStream.closeEntry();
+      }
+      jarOutputStream.close();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
new file mode 100644
index 0000000..8449605
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/JavaCompiler.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import static java.util.stream.Collectors.toList;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import javax.tools.ToolProvider;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import org.apache.commons.io.FileUtils;
+
+
+public class JavaCompiler {
+  private File tempDir;
+
+  public JavaCompiler() {
+    this.tempDir = Files.createTempDir();
+    tempDir.deleteOnExit();
+  }
+
+  public List<CompiledSourceCode> compile(File... sourceFiles) throws IOException {
+    String[] sourceFileContents =
+        Arrays.stream(sourceFiles).map(this::readFileToString).toArray(String[]::new);
+
+    return compile(sourceFileContents);
+  }
+
+  public List<CompiledSourceCode> compile(String... sourceFileContents) throws IOException {
+    UncompiledSourceCode[] uncompiledSourceCodes = Arrays.stream(sourceFileContents)
+        .map(UncompiledSourceCode::fromSourceCode).toArray(UncompiledSourceCode[]::new);
+
+    return compile(uncompiledSourceCodes);
+  }
+
+  public List<CompiledSourceCode> compile(UncompiledSourceCode... uncompiledSources)
+      throws IOException {
+    File temporarySourcesDirectory = createSubdirectory(tempDir, "sources");
+    File temporaryClassesDirectory = createSubdirectory(tempDir, "classes");
+
+    List<String> options = Stream.of("-d", temporaryClassesDirectory.getAbsolutePath(),
+        "-classpath", System.getProperty("java.class.path")).collect(toList());
+
+    try {
+      for (UncompiledSourceCode sourceCode : uncompiledSources) {
+        File sourceFile = new File(temporarySourcesDirectory, sourceCode.simpleClassName + ".java");
+        FileUtils.writeStringToFile(sourceFile, sourceCode.sourceCode, Charsets.UTF_8);
+        options.add(sourceFile.getAbsolutePath());
+      }
+
+      int exitCode = ToolProvider.getSystemJavaCompiler().run(System.in, System.out, System.err,
+          options.toArray(new String[] {}));
+
+      if (exitCode != 0) {
+        throw new RuntimeException(
+            "Unable to compile the given source code. See System.err for details.");
+      }
+
+      List<CompiledSourceCode> compiledSourceCodes = new ArrayList<>();
+      addCompiledClasses(compiledSourceCodes, "", temporaryClassesDirectory);
+      return compiledSourceCodes;
+    } finally {
+      FileUtils.deleteDirectory(temporaryClassesDirectory);
+    }
+  }
+
+  private static void addCompiledClasses(List<CompiledSourceCode> ret, String pkgName, File dir)
+      throws IOException {
+    for (File file : dir.listFiles()) {
+      String filename = file.getName();
+
+      if (file.isDirectory()) {
+        String qname = pkgName + filename + ".";
+        addCompiledClasses(ret, qname, file);
+      } else if (filename.endsWith(".class")) {
+        String qname = pkgName + filename.substring(0, filename.length() - 6);
+        ret.add(new CompiledSourceCode(qname, FileUtils.readFileToByteArray(file)));
+      } else {
+        System.err.println("Unexpected file : " + file.getAbsolutePath());
+      }
+    }
+  }
+
+  private File createSubdirectory(File parent, String directoryName) {
+    File subdirectory = parent.toPath().resolve(directoryName).toFile();
+    if (!subdirectory.exists()) {
+      subdirectory.mkdirs();
+    }
+
+    if (!subdirectory.exists() || !subdirectory.isDirectory()) {
+      throw new IllegalArgumentException("Invalid directory" + subdirectory.getAbsolutePath());
+    }
+
+    return subdirectory;
+  }
+
+  private String readFileToString(File file) {
+    try {
+      return FileUtils.readFileToString(file, Charsets.UTF_8);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java b/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
new file mode 100644
index 0000000..4161f5e
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/compiler/UncompiledSourceCode.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.SystemUtils;
+
+public class UncompiledSourceCode {
+  public String simpleClassName;
+  public String sourceCode;
+
+  private UncompiledSourceCode(String simpleClassName, String sourceCode) {
+    this.simpleClassName = simpleClassName;
+    this.sourceCode = sourceCode;
+  }
+
+  public static UncompiledSourceCode fromSourceCode(String sourceCode) {
+    String simpleClassName = new ClassNameExtractor().extractFromSourceCode(sourceCode);
+    return new UncompiledSourceCode(simpleClassName, sourceCode);
+  }
+
+  public static UncompiledSourceCode fromClassName(String fullyQualifiedClassName) {
+    ClassNameWithPackage classNameWithPackage = ClassNameWithPackage.of(fullyQualifiedClassName);
+    boolean isPackageSpecified = StringUtils.isNotBlank(classNameWithPackage.packageName);
+
+    StringBuilder sourceCode = new StringBuilder();
+    if (isPackageSpecified) {
+      sourceCode.append(String.format("package %s;", classNameWithPackage.packageName));
+      sourceCode.append(SystemUtils.LINE_SEPARATOR);
+    }
+
+    sourceCode.append(String.format("public class %s {}", classNameWithPackage.simpleClassName));
+
+    return new UncompiledSourceCode(classNameWithPackage.simpleClassName, sourceCode.toString());
+  }
+
+  private static class ClassNameWithPackage {
+    String packageName;
+    String simpleClassName;
+
+    static ClassNameWithPackage of(String fqClassName) {
+      int indexOfLastDot = fqClassName.lastIndexOf(".");
+
+      if (indexOfLastDot == -1) {
+        return new ClassNameWithPackage("", fqClassName);
+      } else {
+        String specifiedPackage = fqClassName.substring(0, indexOfLastDot);
+        String simpleClassName = fqClassName.substring(indexOfLastDot + 1);
+
+        return new ClassNameWithPackage(specifiedPackage, simpleClassName);
+      }
+    }
+
+    private ClassNameWithPackage(String packageName, String simpleClassName) {
+      this.packageName = packageName;
+      this.simpleClassName = simpleClassName;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
new file mode 100644
index 0000000..db060f8
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/ClassNameExtractorTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class ClassNameExtractorTest {
+  private static final String SPACE = " ";
+  private static final String CLASS_NAME_TO_FIND = "MyClassNameToFind";
+
+  @Test
+  public void extractsClassNames() throws Exception {
+    SoftAssertions softAssertions = new SoftAssertions();
+    ClassNameExtractor classNameExtractor = new ClassNameExtractor();
+
+    Set<List<String>> permutationsToTest = Sets.cartesianProduct(
+        ImmutableSet.of("public ", "private ", "protected ", ""), ImmutableSet.of("abstract ", ""),
+        ImmutableSet.of("static ", ""), ImmutableSet.of("class ", "interface "),
+        ImmutableSet.of("extends Foo ", ""), ImmutableSet.of("implements Bar ", ""));
+
+    for (List<String> permutation : permutationsToTest) {
+      String firstLineOfSource =
+          permutation.get(0) + permutation.get(1) + permutation.get(2) + permutation.get(3)
+              + CLASS_NAME_TO_FIND + SPACE + permutation.get(4) + permutation.get(5) + " {";
+
+      String className = classNameExtractor.extractFromSourceCode(firstLineOfSource);
+      softAssertions.assertThat(className).isEqualTo(CLASS_NAME_TO_FIND);
+    }
+
+    softAssertions.assertAll();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
new file mode 100644
index 0000000..58adc02
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/JarBuilderTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class JarBuilderTest {
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private JarBuilder jarBuilder;
+  private File outputJar;
+
+  @Before
+  public void setup() {
+    jarBuilder = new JarBuilder();
+    outputJar = new File(temporaryFolder.getRoot(), "output.jar");
+  }
+
+  @Test
+  public void jarWithSingleClass() throws Exception {
+    File classContents = loadTestResource("AbstractClass.java");
+    jarBuilder.buildJar(outputJar, classContents);
+
+    Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+    assertThat(jarEntryNames)
+        .containsExactlyInAnyOrder("org/apache/geode/test/compiler/AbstractClass.class");
+  }
+
+  @Test
+  public void jarWithTwoDependentClasses() throws Exception {
+    File sourceFileOne = loadTestResource("AbstractClass.java");
+    File sourceFileTwo = loadTestResource("ConcreteClass.java");
+
+    jarBuilder.buildJar(outputJar, sourceFileOne, sourceFileTwo);
+
+    Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+
+    assertThat(jarEntryNames).containsExactlyInAnyOrder(
+        "org/apache/geode/test/compiler/AbstractClass.class",
+        "org/apache/geode/test/compiler/ConcreteClass.class");
+  }
+
+  @Test
+  public void jarWithClassInDefaultPackage() throws Exception {
+    String classInFooBarPackage = "package foo.bar; public class ClassInFooBarPackage {}";
+    String classInDefaultPackage = "public class ClassInDefaultPackage {}";
+    jarBuilder.buildJar(outputJar, classInFooBarPackage, classInDefaultPackage);
+
+    Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+    assertThat(jarEntryNames).containsExactlyInAnyOrder("ClassInDefaultPackage.class",
+        "foo/bar/ClassInFooBarPackage.class");
+  }
+
+
+  @Test
+  public void jarFromOnlyClassNames() throws Exception {
+    String defaultPackageClassName = "DefaultClass";
+    String otherPackageClassName = "foo.bar.OtherClass";
+    jarBuilder.buildJarFromClassNames(outputJar, defaultPackageClassName, otherPackageClassName);
+
+    Set<String> jarEntryNames = jarEntryNamesFromFile(outputJar);
+    assertThat(jarEntryNames).containsExactlyInAnyOrder("DefaultClass.class",
+        "foo/bar/OtherClass.class");
+  }
+
+  @Test
+  public void canLoadClassesFromJar() throws Exception {
+    String defaultPackageClassName = "DefaultClass";
+    String otherPackageClassName = "foo.bar.OtherClass";
+    jarBuilder.buildJarFromClassNames(outputJar, defaultPackageClassName, otherPackageClassName);
+
+    URLClassLoader jarClassLoader = new URLClassLoader(new URL[] {outputJar.toURL()});
+
+    jarClassLoader.loadClass("DefaultClass");
+    jarClassLoader.loadClass("foo.bar.OtherClass");
+  }
+
+  private Set<String> jarEntryNamesFromFile(File jarFile) throws Exception {
+    assertThat(jarFile).exists();
+
+    Enumeration<JarEntry> jarEntries = new JarFile(jarFile).entries();
+    return Collections.list(jarEntries).stream().map(JarEntry::getName).collect(toSet());
+  }
+
+  private File loadTestResource(String fileName) throws URISyntaxException {
+    URL resourceFileURL = this.getClass().getResource(fileName);
+    assertThat(resourceFileURL).isNotNull();
+
+    URI resourceUri = resourceFileURL.toURI();
+    return new File(resourceUri);
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
new file mode 100644
index 0000000..425169d
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/JavaCompilerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.assertj.core.api.Assertions;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class JavaCompilerTest {
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Test
+  public void compileSingleClass() throws Exception {
+    File implementsFunctionSourceFile = getFileFromTestResources("AbstractClass.java");
+    String classContents = FileUtils.readFileToString(implementsFunctionSourceFile, "UTF-8");
+
+    List<CompiledSourceCode> compiledSourceCodes = new JavaCompiler().compile(classContents);
+
+    assertThat(compiledSourceCodes).hasSize(1);
+  }
+
+  @Test
+  public void compileTwoDependentClasses() throws Exception {
+    File sourceFileOne = getFileFromTestResources("AbstractClass.java");
+    File sourceFileTwo = getFileFromTestResources("ConcreteClass.java");
+
+    List<CompiledSourceCode> compiledSourceCodes =
+        new JavaCompiler().compile(sourceFileOne, sourceFileTwo);
+
+    assertThat(compiledSourceCodes).hasSize(2);
+  }
+
+  @Test
+  public void invalidSourceThrowsException() throws Exception {
+    JavaCompiler javaCompiler = new JavaCompiler();
+    String sourceCode = "public class foo {this is not valid java source code}";
+    assertThatThrownBy(() -> javaCompiler.compile(sourceCode)).isInstanceOf(Exception.class);
+  }
+
+  private File getFileFromTestResources(String fileName) throws URISyntaxException {
+    URL resourceFileURL = this.getClass().getResource(fileName);
+    assertThat(resourceFileURL).isNotNull();
+
+    URI resourceUri = resourceFileURL.toURI();
+    File file = new File(resourceUri);
+
+    assertThat(file).exists();
+    return file;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java b/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
new file mode 100644
index 0000000..3db8a72
--- /dev/null
+++ b/geode-junit/src/test/java/org/apache/geode/test/compiler/UncompiledSourceCodeTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+import static org.assertj.core.api.Assertions.*;
+
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class UncompiledSourceCodeTest {
+  @Test
+  public void fromClassNameWithNoPackage() throws Exception {
+    UncompiledSourceCode uncompiledSourceCode = UncompiledSourceCode.fromClassName("NoPackage");
+    assertThat(uncompiledSourceCode.simpleClassName).isEqualTo("NoPackage");
+    assertThat(uncompiledSourceCode.sourceCode).isEqualTo("public class NoPackage {}");
+  }
+
+  @Test
+  public void fromClassNameWithPackage() throws Exception {
+    UncompiledSourceCode uncompiledSourceCode =
+        UncompiledSourceCode.fromClassName("foo.bar.ClassName");
+    assertThat(uncompiledSourceCode.simpleClassName).isEqualTo("ClassName");
+    assertThat(uncompiledSourceCode.sourceCode)
+        .isEqualTo("package foo.bar;" + SystemUtils.LINE_SEPARATOR + "public class ClassName {}");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java b/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
new file mode 100644
index 0000000..24ed0c6
--- /dev/null
+++ b/geode-junit/src/test/resources/org/apache/geode/test/compiler/AbstractClass.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+public abstract class AbstractClass {
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/06b839c2/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java b/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
new file mode 100644
index 0000000..bf98672
--- /dev/null
+++ b/geode-junit/src/test/resources/org/apache/geode/test/compiler/ConcreteClass.java
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.test.compiler;
+
+
+public class ConcreteClass extends AbstractClass {
+}