Skip to content

Commit 5f3c1bb

Browse files
committed
Add test utility for generating Jar files with compiled classes.
This was requested by a few different people and may be generally useful, so I'd like to contribute this and not block on a different PR for it to get in. Author: Patrick Wendell <pwendell@gmail.com> Closes #326 from pwendell/class-loader-test-utils and squashes the following commits: ff3e88e [Patrick Wendell] Add test utility for generating Jar files with compiled classes.
1 parent 60e18ce commit 5f3c1bb

File tree

1 file changed

+98
-0
lines changed

1 file changed

+98
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark
19+
20+
import java.io.{File, FileInputStream, FileOutputStream}
21+
import java.net.{URI, URL}
22+
import java.util.jar.{JarEntry, JarOutputStream}
23+
24+
import scala.collection.JavaConversions._
25+
26+
import javax.tools.{JavaFileObject, SimpleJavaFileObject, ToolProvider}
27+
import com.google.common.io.Files
28+
29+
object TestUtils {
30+
31+
/**
32+
* Create a jar that defines classes with the given names.
33+
*
34+
* Note: if this is used during class loader tests, class names should be unique
35+
* in order to avoid interference between tests.
36+
*/
37+
def createJarWithClasses(classNames: Seq[String]): URL = {
38+
val tempDir = Files.createTempDir()
39+
val files = for (name <- classNames) yield createCompiledClass(name, tempDir)
40+
val jarFile = new File(tempDir, "testJar-%s.jar".format(System.currentTimeMillis()))
41+
createJar(files, jarFile)
42+
}
43+
44+
/**
45+
* Create a jar file that contains this set of files. All files will be located at the root
46+
* of the jar.
47+
*/
48+
def createJar(files: Seq[File], jarFile: File): URL = {
49+
val jarFileStream = new FileOutputStream(jarFile)
50+
val jarStream = new JarOutputStream(jarFileStream, new java.util.jar.Manifest())
51+
52+
for (file <- files) {
53+
val jarEntry = new JarEntry(file.getName)
54+
jarStream.putNextEntry(jarEntry)
55+
56+
val in = new FileInputStream(file)
57+
val buffer = new Array[Byte](10240)
58+
var nRead = 0
59+
while (nRead <= 0) {
60+
nRead = in.read(buffer, 0, buffer.length)
61+
jarStream.write(buffer, 0, nRead)
62+
}
63+
in.close()
64+
}
65+
jarStream.close()
66+
jarFileStream.close()
67+
68+
jarFile.toURI.toURL
69+
}
70+
71+
// Adapted from the JavaCompiler.java doc examples
72+
private val SOURCE = JavaFileObject.Kind.SOURCE
73+
private def createURI(name: String) = {
74+
URI.create(s"string:///${name.replace(".", "/")}${SOURCE.extension}")
75+
}
76+
77+
private class JavaSourceFromString(val name: String, val code: String)
78+
extends SimpleJavaFileObject(createURI(name), SOURCE) {
79+
override def getCharContent(ignoreEncodingErrors: Boolean) = code
80+
}
81+
82+
/** Creates a compiled class with the given name. Class file will be placed in destDir. */
83+
def createCompiledClass(className: String, destDir: File): File = {
84+
val compiler = ToolProvider.getSystemJavaCompiler
85+
val sourceFile = new JavaSourceFromString(className, s"public class $className {}")
86+
87+
// Calling this outputs a class file in pwd. It's easier to just rename the file than
88+
// build a custom FileManager that controls the output location.
89+
compiler.getTask(null, null, null, null, null, Seq(sourceFile)).call()
90+
91+
val fileName = className + ".class"
92+
val result = new File(fileName)
93+
if (!result.exists()) throw new Exception("Compiled file not found: " + fileName)
94+
val out = new File(destDir, fileName)
95+
result.renameTo(out)
96+
out
97+
}
98+
}

0 commit comments

Comments
 (0)