Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

system lambda jdk 16 compatibility issue #1942

Closed
SylvainJuge opened this issue Jul 29, 2021 · 7 comments · Fixed by #2184
Closed

system lambda jdk 16 compatibility issue #1942

SylvainJuge opened this issue Jul 29, 2021 · 7 comments · Fixed by #2184

Comments

@SylvainJuge
Copy link
Member

SylvainJuge commented Jul 29, 2021

We use system-lambda for testing custom environment variables.
There is a known compatibility issue with JDK16 that makes some of our tests to fail.

There is no reliable work-around for now, the only option would be to modify our test implementation (and possibly use another way to mock dependencies on System class, at least to set environment variables.

Known affected test(s)

  • co.elastic.apm.agent.impl.payload.ContainerInfoTest.testKubernetesDownwardApi

How to reproduce:

  • simply run tests with JDK 16

Work-around (not usable outside of testing)

  • add --add-opens=java.base/java.util=ALL-UNNAMED into JVM arguments

Stack trace:

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module @66133adc

	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:177)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:171)
	at com.github.stefanbirkner.systemlambda.SystemLambda$WithEnvironmentVariables.getFieldValue(SystemLambda.java:1275)
	at com.github.stefanbirkner.systemlambda.SystemLambda$WithEnvironmentVariables.getEditableMapOfVariables(SystemLambda.java:1235)
	at com.github.stefanbirkner.systemlambda.SystemLambda$WithEnvironmentVariables.restoreOriginalVariables(SystemLambda.java:1213)
	at com.github.stefanbirkner.systemlambda.SystemLambda$WithEnvironmentVariables.execute(SystemLambda.java:1128)
	at co.elastic.apm.agent.impl.payload.ContainerInfoTest.testKubernetesDownwardApi(ContainerInfoTest.java:137)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at .....
@felixbarny
Copy link
Member

We could use the static mocking feature of Mockito 3.4.0+ to work around that: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48

This requires the mockito-inline dependency.

Example:

 try (MockedStatic mocked = mockStatic(System.class)) {
    Map<String, String> mockedEnv = new HashMap(System.getenv());
    mockedEnv.put("foo", "bar");
    mocked.when(System::getenv).thenReturn(mockedEnv);
    assertEquals("bar", System.getenv().get("foo"));
 }

@eyalkoren
Copy link
Contributor

org.mockito.exceptions.base.MockitoException: It is not possible to mock static methods of java.lang.System to avoid interfering with class loading what leads to infinite loops

@felixbarny
Copy link
Member

Hmm.. Then two more ideas.

  1. Try to convince the Mockito folks to make the java.lang.System exception more fine grained. For example to only allow mocking getenv or to only disallow the problematic methods.
  2. Just write a custom instrumentation that only applies to tests that can manipulate the return value of System::getenv. ContainerInfoTest would then extend from AbstractInstrumentationTest.

@SylvainJuge
Copy link
Member Author

Alternatively, we could also create a very small abstraction over the calls to System class when retrieving environment variables.
When testing, we can use an alternate implementation that allows setting arbitrary values.

@felixbarny
Copy link
Member

Yes, that's probably the simplest and cleanest approach 🙂

@eyalkoren
Copy link
Contributor

I actually took my chances with the instrumentation approach, mainly because it is much more fun, but also I think it is actually cleaner than changing production code for testing purposes.
I hope it doesn't cause a mess 🤞

@eyalkoren eyalkoren linked a pull request Oct 14, 2021 that will close this issue
3 tasks
@eyalkoren
Copy link
Contributor

Closed by #2195

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants