|
19 | 19 |
|
20 | 20 | import java.io.File; |
21 | 21 | import java.lang.reflect.Field; |
22 | | -import java.lang.reflect.Modifier; |
| 22 | +import java.lang.reflect.Method; |
23 | 23 | import java.nio.charset.StandardCharsets; |
24 | 24 | import java.nio.file.Files; |
25 | 25 | import java.security.Permission; |
26 | 26 | import java.util.Collection; |
| 27 | +import java.util.HashMap; |
27 | 28 | import java.util.Map; |
28 | 29 | import java.util.concurrent.TimeUnit; |
29 | 30 | import java.util.stream.Collectors; |
@@ -61,7 +62,8 @@ public class FlinkSubmissionTest { |
61 | 62 | private static final Logger LOG = LoggerFactory.getLogger(FlinkSubmissionTest.class); |
62 | 63 |
|
63 | 64 | @ClassRule public static final TemporaryFolder TEMP_FOLDER = new TemporaryFolder(); |
64 | | - private static final Map<String, String> ENV = System.getenv(); |
| 65 | + // Snapshot at class init; System.getenv() is a live view that we mutate in-place below. |
| 66 | + private static final Map<String, String> ENV = new HashMap<>(System.getenv()); |
65 | 67 | private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); |
66 | 68 |
|
67 | 69 | /** Flink cluster that runs over the lifespan of the tests. */ |
@@ -210,21 +212,52 @@ private static void restoreEnvironment() throws Exception { |
210 | 212 | * We modify the JVM's environment variables here. This is necessary for the end-to-end test |
211 | 213 | * because Flink's CliFrontend requires a Flink configuration file for which the location can only |
212 | 214 | * be set using the {@code ConfigConstants.ENV_FLINK_CONF_DIR} environment variable. |
| 215 | + * |
| 216 | + * <p>On Unix JDKs, {@code ProcessEnvironment.theEnvironment} is a {@code Map<Variable,Value>}. |
| 217 | + * {@code System.getenv(String)} looks up {@code Variable} keys, so inserting plain {@code String} |
| 218 | + * keys (via {@code putAll}) is a no-op for lookups and leaves Flink unable to find {@code |
| 219 | + * FLINK_CONF_DIR}. On Windows the map uses {@code String} keys (plus a case-insensitive map). |
213 | 220 | */ |
| 221 | + @SuppressWarnings("unchecked") |
214 | 222 | private static void modifyEnv(Map<String, String> env) throws Exception { |
215 | | - Class processEnv = Class.forName("java.lang.ProcessEnvironment"); |
216 | | - Field envField = processEnv.getDeclaredField("theUnmodifiableEnvironment"); |
| 223 | + Class<?> processEnvironment = Class.forName("java.lang.ProcessEnvironment"); |
217 | 224 |
|
218 | | - Field modifiersField = Field.class.getDeclaredField("modifiers"); |
219 | | - modifiersField.setAccessible(true); |
220 | | - modifiersField.setInt(envField, envField.getModifiers() & ~Modifier.FINAL); |
221 | | - |
222 | | - envField.setAccessible(true); |
223 | | - envField.set(null, env); |
224 | | - envField.setAccessible(false); |
| 225 | + Field theEnvironmentField = processEnvironment.getDeclaredField("theEnvironment"); |
| 226 | + theEnvironmentField.setAccessible(true); |
| 227 | + Map<Object, Object> envMap = (Map<Object, Object>) theEnvironmentField.get(null); |
| 228 | + envMap.clear(); |
| 229 | + try { |
| 230 | + // Unix: keys/values are ProcessEnvironment$Variable / $Value, not String. |
| 231 | + Class<?> variableClass = Class.forName("java.lang.ProcessEnvironment$Variable"); |
| 232 | + Class<?> valueClass = Class.forName("java.lang.ProcessEnvironment$Value"); |
| 233 | + Method valueOfVariable = variableClass.getDeclaredMethod("valueOf", String.class); |
| 234 | + Method valueOfValue = valueClass.getDeclaredMethod("valueOf", String.class); |
| 235 | + valueOfVariable.setAccessible(true); |
| 236 | + valueOfValue.setAccessible(true); |
| 237 | + for (Map.Entry<String, String> entry : env.entrySet()) { |
| 238 | + envMap.put( |
| 239 | + valueOfVariable.invoke(null, entry.getKey()), |
| 240 | + valueOfValue.invoke(null, entry.getValue())); |
| 241 | + } |
| 242 | + } catch (ClassNotFoundException e) { |
| 243 | + // Windows (and similar): theEnvironment uses String keys. |
| 244 | + envMap.putAll(env); |
| 245 | + } |
225 | 246 |
|
226 | | - modifiersField.setInt(envField, envField.getModifiers() & Modifier.FINAL); |
227 | | - modifiersField.setAccessible(false); |
| 247 | + // Windows: System.getenv(String) reads the case-insensitive map when present. |
| 248 | + try { |
| 249 | + Field theCaseInsensitiveEnvironmentField = |
| 250 | + processEnvironment.getDeclaredField("theCaseInsensitiveEnvironment"); |
| 251 | + theCaseInsensitiveEnvironmentField.setAccessible(true); |
| 252 | + Map<String, String> ciEnvMap = |
| 253 | + (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null); |
| 254 | + if (ciEnvMap != null) { |
| 255 | + ciEnvMap.clear(); |
| 256 | + ciEnvMap.putAll(env); |
| 257 | + } |
| 258 | + } catch (NoSuchFieldException e) { |
| 259 | + // Not present on Unix JDKs. |
| 260 | + } |
228 | 261 | } |
229 | 262 |
|
230 | 263 | /** Prevents the CliFrontend from calling System.exit. */ |
|
0 commit comments