我正在尝试将大型代码库从maven迁移到bazel,并且我发现某些测试写入target/classes
和target/test-classes
,并且生产代码将其读取为类路径上的资源。这是因为maven surefire / failsafe默认从模块目录运行,并将target/classes
和target/test-classes
添加到类路径中。
对于我来说,迁移这个大型代码库,唯一合理的解决方案是创建目标,目标/类和目标/测试类文件夹,并将最后两个添加到测试的类路径中。
关于如何实现这一点的任何想法?
由于
答案 0 :(得分:1)
另一种方法。与其生成测试套件,不如创建一个自定义javaagent和一个自定义类加载器。使用jvm_flags
进行设置和配置。
javaagent有一个premain方法。这听起来像是一个自然的地方,可以处理发生在常规main方法之前的事情,即使它们与类检测,调试,coverage收集或任何其他javaagents常规用法没有任何关系。
自定义javaagent读取系统属性extra.dirs
并创建在那里指定的目录。
需要Classloader,这样我们才能在运行时修改classpath而不会受到黑客的攻击。很大的优势是该解决方案可在Java 10上运行。
自定义类加载器读取系统属性extra.class.path
并(实际上)在java.class.path
中的内容之前添加了它。
以这种方式处理意味着可以使用标准的bazel规则。
runtime_classgen_dirs = ":".join([
"target/classes",
"target/test-classes",
])
java_test(
...,
jvm_flags = [
# agent
"-javaagent:$(location //tools:test-agent_deploy.jar)",
"-Dextra.dirs=" + runtime_classgen_dirs,
# classloader
"-Djava.system.class.loader=ResourceJavaAgent",
"-Dextra.class.path=" + runtime_classgen_dirs,
],
,,,,
deps = [
# not runtime_deps, cause https://github.com/bazelbuild/bazel/issues/1566
"//tools:test-agent_deploy.jartest-agent_deploy.jar"
],
...,
)
java_binary(
name = "test-agent",
testonly = True,
srcs = ["ResourceJavaAgent.java"],
deploy_manifest_lines = ["Premain-Class: ResourceJavaAgent"],
main_class = "ResourceJavaAgent",
visibility = ["//visibility:public"],
)
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
// https://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
public class ResourceJavaAgent extends URLClassLoader {
private final ClassLoader parent;
public ResourceJavaAgent(ClassLoader parent) throws MalformedURLException {
super(buildClassPath(), null);
this.parent = parent; // I need the parent as backup for SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
System.out.println("initializing url classloader");
}
private static URL[] buildClassPath() throws MalformedURLException {
final String JAVA_CLASS_PATH = "java.class.path";
final String EXTRA_CLASS_PATH = "extra.class.path";
List<String> paths = new LinkedList<>();
paths.addAll(Arrays.asList(System.getProperty(EXTRA_CLASS_PATH, "").split(File.pathSeparator)));
paths.addAll(Arrays.asList(System.getProperty(JAVA_CLASS_PATH, "").split(File.pathSeparator)));
URL[] urls = new URL[paths.size()];
for (int i = 0; i < paths.size(); i++) {
urls[i] = Paths.get(paths.get(i)).toUri().toURL(); // important only for resource url, really: this url must be absolute, to pass getClass().getResource("/users.properties").toURI()) with uri that isOpaque == false.
// System.out.println(urls[i]);
}
// this is for spawnVM functionality in tests
System.setProperty(JAVA_CLASS_PATH, System.getProperty(EXTRA_CLASS_PATH, "") + File.pathSeparator + System.getProperty(JAVA_CLASS_PATH));
return urls;
}
@Override
public Class<?> loadClass(String s) throws ClassNotFoundException {
try {
return super.loadClass(s);
} catch (ClassNotFoundException e) {
return parent.loadClass(s); // we search parent second, not first, as the default URLClassLoader would
}
}
private static void createRequestedDirs() {
for (String path : System.getProperty("extra.dirs", "").split(File.pathSeparator)) {
new File(path).mkdirs();
}
}
private static void createRequestedLinks() {
String linkPaths = System.getProperty("extra.link.path", null);
if (linkPaths == null) {
return;
}
for (String linkPath : linkPaths.split(",")) {
String[] fromTo = linkPath.split(":");
Path from = Paths.get(fromTo[0]);
Path to = Paths.get(fromTo[1]);
try {
Files.createSymbolicLink(from.toAbsolutePath(), to.toAbsolutePath());
} catch (IOException e) {
throw new IllegalArgumentException("Unable to create link " + linkPath, e);
}
}
}
public static void premain(String args, Instrumentation instrumentation) throws Exception {
createRequestedDirs();
createRequestedLinks();
}
}
答案 1 :(得分:0)
如果您可以告诉测试在哪里编写这些文件(如果target/classes
和target/test-classes
是硬编码的),然后将测试运行转换为genrule
,那么您可以指定生成二进制文件data
规则的genrule输出为*_binary
。
答案 2 :(得分:0)
我解决了第一部分,创建了目录。我仍然不知道如何将后两个添加到classpath。
从https://gerrit.googlesource.com/bazlets/+/master/tools/junit.bzl开始,我将其修改为读取
_OUTPUT = """import org.junit.runners.Suite;
import org.junit.runner.RunWith;
import org.junit.BeforeClass;
import java.io.File;
@RunWith(Suite.class)
@Suite.SuiteClasses({%s})
public class %s {
@BeforeClass
public static void setUp() throws Exception {
new File("./target").mkdir();
}
}
"""
_PREFIXES = ("org", "com", "edu")
# ...
我添加了@BeforeClass setUp method
。
我将其作为junit.bzl
存储到项目的third_party
目录中。
然后进入BUILD file
,
load("//third_party:junit.bzl", "junit_tests")
junit_tests(
name = "my_bundled_test",
srcs = glob(["src/test/java/**/*.java"]),
data = glob(["src/test/resources/**"]),
resources = glob(["src/test/resources/**"]),
tags = [
# ...
],
runtime_deps = [
# ...
],
],
deps = [
# ...
],
)
现在,测试本身包含有一个setUp方法,该方法将为我创建一个目录。我以后不会删除它们,这可能是一个不错的主意。
之所以需要目录中的测试资源(而不是bazel默认提供的jar文件中的目录)是因为我的测试将URI传递给了new FileInputStream(new File(uri))
。如果文件位于JAR中,则URI将为file:/path/to/my.jar!/my.file
,而其余的测试无法使用该URI。