diff --git a/appengine-plugins-core/pom.xml b/appengine-plugins-core/pom.xml index 96bdae8bd..c8310aa4f 100644 --- a/appengine-plugins-core/pom.xml +++ b/appengine-plugins-core/pom.xml @@ -59,7 +59,7 @@ UTF-8 - com.google.cloud.tools.appengine.operations.DevServerTest,com.google.cloud.tools.appengine.operations.cloudsdk.serialization.AppEngineDeployResult,com.google.cloud.tools.io.FilePermissionsTest,com.google.cloud.tools.managedcloudsdk.install.InstallerFactoryTest,com.google.cloud.tools.appengine.AppEngineDescriptorTest,com.google.cloud.tools.appengine.operations.cloudsdk.serialization.CloudSdkVersionTest,com.google.cloud.tools.appengine.operations.GenRepoInfoFileTest,com.google.cloud.tools.appengine.operations.DeploymentTest,com.google.cloud.tools.appengine.operations.AuthTest,com.google.cloud.tools.appengine.configuration.RunConfigurationTest + com.google.cloud.tools.appengine.operations.DevServerJava8Test,com.google.cloud.tools.appengine.operations.DevServerJava9OrAboveTest,com.google.cloud.tools.appengine.operations.cloudsdk.serialization.AppEngineDeployResult,com.google.cloud.tools.io.FilePermissionsTest,com.google.cloud.tools.managedcloudsdk.install.InstallerFactoryTest,com.google.cloud.tools.appengine.AppEngineDescriptorTest,com.google.cloud.tools.appengine.operations.cloudsdk.serialization.CloudSdkVersionTest,com.google.cloud.tools.appengine.operations.GenRepoInfoFileTest,com.google.cloud.tools.appengine.operations.DeploymentTest,com.google.cloud.tools.appengine.operations.AuthTest,com.google.cloud.tools.appengine.configuration.RunConfigurationTest diff --git a/appengine-plugins-core/src/main/java/com/google/cloud/tools/appengine/operations/DevServer.java b/appengine-plugins-core/src/main/java/com/google/cloud/tools/appengine/operations/DevServer.java index 0531432ff..393fb9f3d 100644 --- a/appengine-plugins-core/src/main/java/com/google/cloud/tools/appengine/operations/DevServer.java +++ b/appengine-plugins-core/src/main/java/com/google/cloud/tools/appengine/operations/DevServer.java @@ -16,6 +16,8 @@ package com.google.cloud.tools.appengine.operations; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; + import com.google.cloud.tools.appengine.AppEngineDescriptor; import com.google.cloud.tools.appengine.AppEngineException; import com.google.cloud.tools.appengine.configuration.RunConfiguration; @@ -80,6 +82,16 @@ public void run(RunConfiguration config) throws AppEngineException { jvmArguments.addAll(config.getJvmFlags()); } + if (!JAVA_SPECIFICATION_VERSION.value().equals("1.8")) { + // Due to JPMS restrictions, Java11 or later need more flags: + jvmArguments.add("--add-opens"); + jvmArguments.add("java.base/java.net=ALL-UNNAMED"); + jvmArguments.add("--add-opens"); + jvmArguments.add("java.base/sun.net.www.protocol.http=ALL-UNNAMED"); + jvmArguments.add("--add-opens"); + jvmArguments.add("java.base/sun.net.www.protocol.https=ALL-UNNAMED"); + } + arguments.addAll(DevAppServerArgs.get("default_gcs_bucket", config.getDefaultGcsBucketName())); arguments.addAll(DevAppServerArgs.get("application", config.getProjectId())); diff --git a/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerTest.java b/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava8Test.java similarity index 97% rename from appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerTest.java rename to appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava8Test.java index f0f8f5929..dc6124fe7 100644 --- a/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerTest.java +++ b/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava8Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 Google LLC. + * Copyright 2016-2022 Google LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.google.cloud.tools.appengine.operations; +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; @@ -39,6 +41,7 @@ import java.util.logging.LogRecord; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -49,7 +52,7 @@ /** Unit tests for {@link DevServer}. */ @RunWith(MockitoJUnitRunner.class) -public class DevServerTest { +public class DevServerJava8Test { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); private Path fakeJavaSdkHome; @@ -74,6 +77,12 @@ public class DevServerTest { private final Map expectedJava8Environment = ImmutableMap.of("GAE_ENV", "localdev", "GAE_RUNTIME", "java8"); + @BeforeClass + public static void disableIfJavaVersionAbove8() { + assumeTrue( + "DevServerTestJava8 requires Java 8", JAVA_SPECIFICATION_VERSION.value().equals("1.8")); + } + @Before public void setUp() throws IOException { devServer = Mockito.spy(new DevServer(sdk, devAppServerRunner)); diff --git a/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava9OrAboveTest.java b/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava9OrAboveTest.java new file mode 100644 index 000000000..9079343c2 --- /dev/null +++ b/appengine-plugins-core/src/test/java/com/google/cloud/tools/appengine/operations/DevServerJava9OrAboveTest.java @@ -0,0 +1,587 @@ +/* + * Copyright 2016-2022 Google LLC. + * + * Licensed 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 com.google.cloud.tools.appengine.operations; + +import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION; +import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.google.cloud.tools.appengine.AppEngineException; +import com.google.cloud.tools.appengine.configuration.RunConfiguration; +import com.google.cloud.tools.appengine.configuration.StopConfiguration; +import com.google.cloud.tools.appengine.operations.cloudsdk.process.ProcessHandlerException; +import com.google.cloud.tools.test.utils.LogStoringHandler; +import com.google.cloud.tools.test.utils.SpyVerifier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** Unit tests for {@link DevServer}. */ +@RunWith(MockitoJUnitRunner.class) +public class DevServerJava9OrAboveTest { + + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private Path fakeJavaSdkHome; + + private LogStoringHandler testHandler; + @Mock private CloudSdk sdk; + @Mock private DevAppServerRunner devAppServerRunner; + + private DevServer devServer; + + private final Path java8Service = Paths.get("src/test/resources/projects/EmptyStandard8Project"); + private final Path java7Service = Paths.get("src/test/resources/projects/EmptyStandard7Project"); + + private final Path java8Service1EnvVars = + Paths.get("src/test/resources/projects/Standard8Project1EnvironmentVariables"); + private final Path java8Service2EnvVars = + Paths.get("src/test/resources/projects/Standard8Project2EnvironmentVariables"); + + // Environment variables included in running the dev server for Java 7/8 runtimes. + private final Map expectedJava7Environment = + ImmutableMap.of("GAE_ENV", "localdev", "GAE_RUNTIME", "java7"); + private final Map expectedJava8Environment = + ImmutableMap.of("GAE_ENV", "localdev", "GAE_RUNTIME", "java8"); + + @BeforeClass + public static void disableIfJavaVersion8() { + // CI does not run with anything below 8, so equals is safe. + assumeTrue( + "DevServerTestJava8 requires Java 9 or above", + !JAVA_SPECIFICATION_VERSION.value().equals("1.8")); + } + + @Before + public void setUp() throws IOException { + devServer = Mockito.spy(new DevServer(sdk, devAppServerRunner)); + fakeJavaSdkHome = temporaryFolder.newFolder("java-sdk").toPath(); + + Mockito.when(sdk.getAppEngineSdkForJavaPath()).thenReturn(fakeJavaSdkHome); + + testHandler = LogStoringHandler.getForLogger(DevServer.class.getName()); + } + + @Test + public void testStop_allFlags() { + StopConfiguration configuration = + StopConfiguration.builder().host("alt-local-host").port(7777).build(); + try { + devServer.stop(configuration); + Assert.fail(); + } catch (AppEngineException ex) { + Assert.assertEquals( + "Error connecting to http://alt-local-host:7777/_ah/admin/quit", ex.getMessage()); + } + } + + @Test + public void testStop_defaultAdminHost() { + StopConfiguration configuration = StopConfiguration.builder().port(7777).build(); + try { + devServer.stop(configuration); + Assert.fail(); + } catch (AppEngineException ex) { + Assert.assertEquals( + "Error connecting to http://localhost:7777/_ah/admin/quit", ex.getMessage()); + } + } + + @Test + public void testNullSdk() { + try { + new DevServer(null, devAppServerRunner); + Assert.fail("Allowed null SDK"); + } catch (NullPointerException expected) { + } + + try { + new DevServer(sdk, null); + Assert.fail("Allowed null runner"); + } catch (NullPointerException expected) { + } + } + + @Test + public void testPrepareCommand_allFlags() throws Exception { + + RunConfiguration configuration = + Mockito.spy( + RunConfiguration.builder(ImmutableList.of(java8Service)) + .host("host") + .port(8090) + .jvmFlags(ImmutableList.of("-Dflag1", "-Dflag2")) + .defaultGcsBucketName("buckets") + .environment(null) + .automaticRestart(true) + .projectId("my-project") + .environment(ImmutableMap.of("ENV_NAME", "ENV_VAL")) + .additionalArguments(Arrays.asList("--ARG1", "--ARG2")) + .build()); + + SpyVerifier.newVerifier(configuration).verifyAllValuesNotNull(); + + List expectedFlags = + ImmutableList.of( + "--address=host", + "--port=8090", + "--default_gcs_bucket=buckets", + "--application=my-project", + "--allow_remote_shutdown", + "--disable_update_check", + "--ARG1", + "--ARG2", + "--no_java_agent", + java8Service.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "-Dappengine.fullscan.seconds=1", + "-Dflag1", + "-Dflag2", + "--add-opens", + "java.base/java.net=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", + "-D--enable_all_permissions=true"); + + // Not us immutable map, it enforces order + Map expectedEnvironment = + ImmutableMap.builder() + .putAll(expectedJava8Environment) + .put("ENV_NAME", "ENV_VAL") + .build(); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedEnvironment, + java8Service /* workingDirectory */); + + SpyVerifier.newVerifier(configuration) + .verifyDeclaredGetters( + ImmutableMap.of("getServices", 7, "getJavaHomeDir", 2, "getJvmFlags", 2)); + } + + @Test + public void testPrepareCommand_booleanFlags() + throws AppEngineException, ProcessHandlerException, IOException { + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service)).build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java8Service.toString()); + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + devServer.run(configuration); + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedJava8Environment, + java8Service /* workingDirectory */); + } + + @Test + public void testPrepareCommand_noFlags() + throws AppEngineException, ProcessHandlerException, IOException { + + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service)).build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java8Service.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedJava8Environment, + java8Service /* workingDirectory */); + } + + @Test + public void testPrepareCommand_noFlagsJava7() + throws AppEngineException, ProcessHandlerException, IOException { + + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java7Service)).build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", "--disable_update_check", java7Service.toString()); + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", + "java.base/java.net=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-javaagent:" + + fakeJavaSdkHome.resolve("agent/appengine-agent.jar").toAbsolutePath().toString()); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedJava7Environment, + java7Service /* workingDirectory */); + } + + @Test + public void testPrepareCommand_noFlagsMultiModule() + throws AppEngineException, ProcessHandlerException, IOException { + + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java7Service, java8Service)).build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java7Service.toString(), + java8Service.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run(expectedJvmArgs, expectedFlags, expectedJava8Environment, null /* workingDirectory */); + } + + @Test + public void testPrepareCommand_appEngineWebXmlEnvironmentVariables() + throws AppEngineException, ProcessHandlerException, IOException { + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service1EnvVars)).build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java8Service1EnvVars.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + + Map expectedConfigurationEnvironment = + ImmutableMap.of("key1", "val1", "key2", "val2"); + Map expectedEnvironment = + ImmutableMap.builder() + .putAll(expectedConfigurationEnvironment) + .putAll(expectedJava8Environment) + .build(); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedEnvironment, + java8Service1EnvVars /* workingDirectory */); + } + + @Test + public void testPrepareCommand_multipleServicesDuplicateAppEngineWebXmlEnvironmentVariables() + throws AppEngineException, ProcessHandlerException, IOException { + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service1EnvVars, java8Service2EnvVars)) + .build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java8Service1EnvVars.toString(), + java8Service2EnvVars.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + + Map expectedConfigurationEnvironment = + ImmutableMap.of("key1", "val1", "keya", "vala", "key2", "duplicated-key", "keyc", "valc"); + Map expectedEnvironment = + ImmutableMap.builder() + .putAll(expectedConfigurationEnvironment) + .putAll(expectedJava8Environment) + .build(); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run(expectedJvmArgs, expectedFlags, expectedEnvironment, null /* workingDirectory */); + } + + @Test + public void testPrepareCommand_clientSuppliedEnvironmentVariables() + throws AppEngineException, ProcessHandlerException, IOException { + Map clientEnvironmentVariables = + ImmutableMap.of("mykey1", "myval1", "mykey2", "myval2"); + + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java7Service)) + .environment(clientEnvironmentVariables) + .build(); + + Map expectedEnvironment = + ImmutableMap.builder() + .putAll(expectedJava7Environment) + .putAll(clientEnvironmentVariables) + .build(); + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", "--disable_update_check", java7Service.toString()); + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", + "java.base/java.net=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", + "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-javaagent:" + + fakeJavaSdkHome.resolve("agent/appengine-agent.jar").toAbsolutePath().toString()); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedEnvironment, + java7Service /* workingDirectory */); + } + + @Test + public void testPrepareCommand_clientSuppliedAndAppEngineWebXmlEnvironmentVariables() + throws AppEngineException, ProcessHandlerException, IOException { + Map clientEnvironmentVariables = + ImmutableMap.of("mykey1", "myval1", "mykey2", "myval2"); + + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service1EnvVars)) + .environment(clientEnvironmentVariables) + .build(); + + List expectedFlags = + ImmutableList.of( + "--allow_remote_shutdown", + "--disable_update_check", + "--no_java_agent", + java8Service1EnvVars.toString()); + + List expectedJvmArgs = + ImmutableList.of( + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.http=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.www.protocol.https=ALL-UNNAMED", + "-Duse_jetty9_runtime=true", "-D--enable_all_permissions=true"); + + Map appEngineEnvironment = ImmutableMap.of("key1", "val1", "key2", "val2"); + Map expectedEnvironment = + ImmutableMap.builder() + .putAll(appEngineEnvironment) + .putAll(expectedJava8Environment) + .putAll(clientEnvironmentVariables) + .build(); + + devServer.run(configuration); + + verify(devAppServerRunner, times(1)) + .run( + expectedJvmArgs, + expectedFlags, + expectedEnvironment, + java8Service1EnvVars /* workingDirectory */); + } + + @Test + public void testCheckAndWarnIgnored_withSetValue() { + devServer.checkAndWarnIgnored(new Object(), "testName"); + + Assert.assertEquals(1, testHandler.getLogs().size()); + + LogRecord logRecord = testHandler.getLogs().get(0); + Assert.assertEquals( + "testName only applies to Dev Appserver v2 and will be ignored by Dev Appserver v1", + logRecord.getMessage()); + Assert.assertEquals(Level.WARNING, logRecord.getLevel()); + } + + @Test + public void testCheckAndWarnIgnored_withUnsetValue() { + devServer.checkAndWarnIgnored(null, "testName"); + + Assert.assertEquals(0, testHandler.getLogs().size()); + } + + @Test + public void testDetermineJavaRuntime_noWarningsJava7() throws AppEngineException { + Assert.assertTrue(devServer.isSandboxEnforced(ImmutableList.of(java7Service))); + Assert.assertEquals(0, testHandler.getLogs().size()); + } + + @Test + public void testDetermineJavaRuntime_noWarningsJava7Multiple() throws AppEngineException { + Assert.assertTrue(devServer.isSandboxEnforced(ImmutableList.of(java7Service, java7Service))); + Assert.assertEquals(0, testHandler.getLogs().size()); + } + + @Test + public void testDetermineJavaRuntime_noWarningsJava8() throws AppEngineException { + Assert.assertFalse(devServer.isSandboxEnforced(ImmutableList.of(java8Service))); + Assert.assertEquals(0, testHandler.getLogs().size()); + } + + @Test + public void testDetermineJavaRuntime_noWarningsJava8Multiple() throws AppEngineException { + Assert.assertFalse(devServer.isSandboxEnforced(ImmutableList.of(java8Service, java8Service))); + Assert.assertEquals(0, testHandler.getLogs().size()); + } + + @Test + public void testDetermineJavaRuntime_mixedModeWarning() throws AppEngineException { + + Assert.assertFalse(devServer.isSandboxEnforced(ImmutableList.of(java8Service, java7Service))); + Assert.assertEquals(1, testHandler.getLogs().size()); + + LogRecord logRecord = testHandler.getLogs().get(0); + Assert.assertEquals( + "Mixed runtimes detected, will not enforce sandbox restrictions.", logRecord.getMessage()); + Assert.assertEquals(Level.WARNING, logRecord.getLevel()); + } + + @Test + public void testWorkingDirectory_fallbackIfOneProject() + throws ProcessHandlerException, AppEngineException, IOException { + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service)).build(); + + devServer.run(configuration); + + verify(devAppServerRunner).run(any(), any(), any(), eq(java8Service) /* workingDirectory */); + } + + @Test + public void testWorkingDirectory_noFallbackIfManyProjects() + throws ProcessHandlerException, AppEngineException, IOException { + RunConfiguration configuration = + RunConfiguration.builder(ImmutableList.of(java8Service, java8Service)).build(); + + devServer.run(configuration); + + verify(devAppServerRunner).run(any(), any(), any(), eq(null) /* workingDirectory */); + } + + @Test + public void testGetLocalAppEngineEnvironmentVariables_java7() { + Map environment = DevServer.getLocalAppEngineEnvironmentVariables("java7"); + Assert.assertEquals(expectedJava7Environment, environment); + } + + @Test + public void testGetLocalAppEngineEnvironmentVariables_java8() { + Map environment = DevServer.getLocalAppEngineEnvironmentVariables("java8"); + Assert.assertEquals(expectedJava8Environment, environment); + } + + @Test + public void testGetLocalAppEngineEnvironmentVariables_other() { + Map environment = + DevServer.getLocalAppEngineEnvironmentVariables("some_other_runtime"); + Map expectedEnvironment = + ImmutableMap.of("GAE_ENV", "localdev", "GAE_RUNTIME", "some_other_runtime"); + Assert.assertEquals(expectedEnvironment, environment); + } + + @Test + public void testGetGaeRuntimeJava_isJava8() { + Assert.assertEquals("java8", DevServer.getGaeRuntimeJava(true)); + } + + @Test + public void testGetGaeRuntimeJava_isNotJava8() { + Assert.assertEquals("java7", DevServer.getGaeRuntimeJava(false)); + } +}