forked from apache/geode
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GEODE-10321: Acceptance test for Geode access to JDK internals (apach…
…e#7772) * GEODE-10321: Acceptance test for Geode access to JDK internals Add acceptance tests to demonstrate two ways to give Geode access to encapsulated fields of a JDK 17 class: - Tell Gfsh to open the class's package when starting a Geode member. - Tell Gfsh to use `open-all-jdk-packages-linux-openjdk-17` as an argument file when starting a Geode member. Add another test to demonstrate the kind of error that happens when Geode cannot access an encapsulated field of a JDK 17 class. * Clean up Remove unnecessary dependency on JUnit 5 rule migration support. Start server without default server. Other cleanup.
- Loading branch information
1 parent
425bdb8
commit b20a6b0
Showing
3 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
geode-assembly/src/acceptanceTest/java/org/apache/geode/jdk/JdkEncapsulationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* 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.jdk; | ||
|
||
import static org.apache.geode.internal.AvailablePortHelper.getRandomAvailableTCPPort; | ||
import static org.apache.geode.test.util.JarUtils.createJarWithClasses; | ||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | ||
import static org.assertj.core.api.Assumptions.assumeThat; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
|
||
import org.junit.Before; | ||
import org.junit.BeforeClass; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
|
||
import org.apache.geode.test.junit.rules.FolderRule; | ||
import org.apache.geode.test.junit.rules.gfsh.GfshRule; | ||
import org.apache.geode.test.junit.rules.gfsh.GfshScript; | ||
import org.apache.geode.test.version.JavaVersions; | ||
|
||
/** | ||
* Test several ways to make normally inaccessible JDK packages accessible on JDK 17. | ||
*/ | ||
public class JdkEncapsulationTest { | ||
@Rule(order = 0) | ||
public final FolderRule folderRule = new FolderRule(); | ||
|
||
@Rule(order = 1) | ||
public final GfshRule gfshRule = new GfshRule(folderRule::getFolder); | ||
|
||
private String startServer; | ||
private GfshScript traverseEncapsulatedJdkObject; | ||
|
||
@BeforeClass | ||
public static void validOnlyOnJdk17AndLater() { | ||
assumeThat(JavaVersions.current().specificationVersion()) | ||
.isGreaterThanOrEqualTo(17); | ||
} | ||
|
||
@Before | ||
public void startLocatorWithObjectTraverserFunction() throws IOException { | ||
Path jarPath = folderRule.getFolder().toPath().resolve("traverse-encapsulated-jdk-object.jar"); | ||
createJarWithClasses(jarPath, TraverseEncapsulatedJdkObject.class); | ||
|
||
int locatorPort = getRandomAvailableTCPPort(); | ||
String locators = "localhost[" + locatorPort + "]"; | ||
|
||
startServer = "start server --name=server --disable-default-server --locators=" + locators; | ||
traverseEncapsulatedJdkObject = GfshScript | ||
.of("connect --locator=" + locators) | ||
.and("execute function --id=" + TraverseEncapsulatedJdkObject.ID); | ||
|
||
GfshScript | ||
.of("start locator --port=" + locatorPort) | ||
.and("deploy --jar=" + jarPath) | ||
.execute(gfshRule); | ||
} | ||
|
||
// If this test fails, it means the object we're trying to traverse has no inaccessible fields, | ||
// and so is not useful for the other tests. If it fails, update TraverseInaccessibleJdkObject | ||
// to use a type that actually has inaccessible fields. | ||
@Test | ||
public void cannotMakeEncapsulatedFieldsAccessibleByDefault() { | ||
gfshRule.execute(startServer); // No JDK options | ||
|
||
String traversalResult = traverseEncapsulatedJdkObject | ||
.expectExitCode(1) // Because we did not open any JDK packages. | ||
.execute(gfshRule) | ||
.getOutputText(); | ||
|
||
assertThat(traversalResult) | ||
.as("result of traversing %s", TraverseEncapsulatedJdkObject.OBJECT.getClass()) | ||
.contains("Exception: java.lang.reflect.InaccessibleObjectException"); | ||
} | ||
|
||
@Test | ||
public void canMakeEncapsulatedFieldsAccessibleInExplicitlyOpenedPackages() { | ||
String objectPackage = TraverseEncapsulatedJdkObject.OBJECT.getClass().getPackage().getName(); | ||
String objectModule = TraverseEncapsulatedJdkObject.MODULE; | ||
|
||
String openThePackageOfTheEncapsulatedJdkObject = | ||
String.format(" --J=--add-opens=%s/%s=ALL-UNNAMED", objectModule, objectPackage); | ||
|
||
gfshRule.execute(startServer + openThePackageOfTheEncapsulatedJdkObject); | ||
|
||
traverseEncapsulatedJdkObject | ||
.expectExitCode(0) // Because we opened the encapsulated object's package. | ||
.execute(gfshRule); | ||
} | ||
|
||
@Test | ||
public void canMakeEncapsulatedFieldsAccessibleInPackagesOpenedByArgumentFile() { | ||
// A few of the packages opened by this argument file are specific to Linux JDKs. Running this | ||
// test on other operating systems might emit some warnings, but they are harmless. | ||
String argumentFileName = "open-all-jdk-packages-linux-openjdk-17"; | ||
Path argumentFilePath = Paths.get(System.getenv("GEODE_HOME"), "config", argumentFileName) | ||
.toAbsolutePath().normalize(); | ||
|
||
String useArgumentFile = " --J=@" + argumentFilePath; | ||
|
||
gfshRule.execute(startServer + useArgumentFile); | ||
|
||
traverseEncapsulatedJdkObject | ||
.expectExitCode(0) // Because the argument file opens all JDK packages. | ||
.execute(gfshRule); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...-assembly/src/acceptanceTest/java/org/apache/geode/jdk/TraverseEncapsulatedJdkObject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* 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.jdk; | ||
|
||
import java.math.BigDecimal; | ||
|
||
import org.apache.geode.cache.execute.Function; | ||
import org.apache.geode.cache.execute.FunctionContext; | ||
import org.apache.geode.internal.size.ObjectTraverser; | ||
import org.apache.geode.internal.size.ObjectTraverser.Visitor; | ||
|
||
public class TraverseEncapsulatedJdkObject implements Function<Void> { | ||
private static final Visitor TRAVERSE_ENTIRE_OBJECT_GRAPH = (parent, object) -> true; | ||
private final ObjectTraverser traverser = new ObjectTraverser(); | ||
|
||
// OBJECT must have a JDK type with inaccessible fields, defined in a package that Gfsh does | ||
// not open by default. | ||
static final BigDecimal OBJECT = BigDecimal.ONE; | ||
// MODULE must be the module that defines OBJECT's type. | ||
static final String MODULE = "java.base"; | ||
static final String ID = "traverse-big-decimal"; | ||
|
||
@Override | ||
public String getId() { | ||
return ID; | ||
} | ||
|
||
@Override | ||
public void execute(FunctionContext<Void> context) { | ||
try { | ||
traverser.breadthFirstSearch(OBJECT, TRAVERSE_ENTIRE_OBJECT_GRAPH, false); | ||
} catch (IllegalAccessException e) { | ||
context.getResultSender().sendException(e); | ||
return; | ||
} | ||
context.getResultSender().lastResult("OK"); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
geode-junit/src/main/java/org/apache/geode/test/util/JarUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* 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.util; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.io.UncheckedIOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Arrays; | ||
import java.util.jar.JarEntry; | ||
import java.util.jar.JarOutputStream; | ||
|
||
import org.apache.commons.io.IOUtils; | ||
|
||
public class JarUtils { | ||
public static Path createJarWithClasses(Path jarPath, Class<?>... classes) throws IOException { | ||
Path jarFile = Files.createFile(jarPath); | ||
try (OutputStream outputStream = Files.newOutputStream(jarFile); | ||
JarOutputStream jarOutputStream = new JarOutputStream(outputStream)) { | ||
Arrays.stream(classes) | ||
.forEach(clazz -> writeClassTo(clazz, jarOutputStream)); | ||
} | ||
return jarFile; | ||
} | ||
|
||
private static void writeClassTo(Class<?> clazz, JarOutputStream jarOutputStream) { | ||
String className = clazz.getName(); | ||
String classAsPath = className.replace('.', '/') + ".class"; | ||
try (InputStream classInputStream = clazz.getClassLoader().getResourceAsStream(classAsPath)) { | ||
if (classInputStream == null) { | ||
throw new IllegalArgumentException("Cannot read class definition for " + className); | ||
} | ||
JarEntry classEntry = new JarEntry(classAsPath); | ||
classEntry.setTime(System.currentTimeMillis()); | ||
byte[] classBytes = IOUtils.toByteArray(classInputStream); | ||
jarOutputStream.putNextEntry(classEntry); | ||
jarOutputStream.write(classBytes); | ||
jarOutputStream.closeEntry(); | ||
} catch (IOException e) { | ||
throw new UncheckedIOException(e); | ||
} | ||
} | ||
} |