Open-source RPC tests.

--
MOS_MIGRATED_REVID=108127872
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 888f231..370614e 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -599,6 +599,33 @@
     ],
 )
 
+java_test(
+    name = "server_test",
+    srcs = glob([
+        "server/*.java",
+    ]),
+    args = ["com.google.devtools.build.lib.AllTests"],
+    tags = ["server"],
+    deps = [
+        ":foundations_testutil",
+        ":test_runner",
+        ":testutil",
+        "//src/main/java/com/google/devtools/build/lib:bazel-core",
+        "//src/main/java/com/google/devtools/build/lib:collect",
+        "//src/main/java/com/google/devtools/build/lib:io",
+        "//src/main/java/com/google/devtools/build/lib:server",
+        "//src/main/java/com/google/devtools/build/lib:unix",
+        "//src/main/java/com/google/devtools/build/lib:util",
+        "//src/main/java/com/google/devtools/build/lib:vfs",
+        "//third_party:guava",
+        "//third_party:guava-testlib",
+        "//third_party:jsr305",
+        "//third_party:junit4",
+        "//third_party:mockito",
+        "//third_party:truth",
+    ],
+)
+
 java_binary(
     name = "skylarkshell",
     srcs = ["syntax/SkylarkShell.java"],
@@ -888,6 +915,7 @@
     "analysis",
     "foundations",
     "shell",
+    "server",
     "skyframe",
     "ideinfo",
 ]
diff --git a/src/test/java/com/google/devtools/build/lib/server/RPCServerTest.java b/src/test/java/com/google/devtools/build/lib/server/RPCServerTest.java
new file mode 100644
index 0000000..9f6ce72
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/server/RPCServerTest.java
@@ -0,0 +1,126 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.testutil.Suite;
+import com.google.devtools.build.lib.testutil.TestSpec;
+import com.google.devtools.build.lib.util.JavaClock;
+import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.util.io.RecordingOutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.util.FsApparatus;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Run a real RPC server on localhost, and talk to it using the testing
+ * client.
+ */
+@TestSpec(size = Suite.MEDIUM_TESTS)
+public class RPCServerTest extends TestCase {
+
+  private static final long MAX_IDLE_MILLIS = 10000;
+  private static final long HEALTH_CHECK_MILLIS = 1000 * 3;
+  private static final String COMMAND_STDOUT = "Heelllloo....";
+  private static final String COMMAND_STDERR = "...world!";
+
+  private RPCServer server;
+  private FsApparatus scratch = FsApparatus.newNative();
+  private RecordingOutErr outErr = new RecordingOutErr();
+  private Path serverDir;
+  private Path workspaceDir;
+  private RPCTestingClient client;
+  private Thread serverThread = new Thread(){
+    @Override
+    public void run() {
+      server.serve();
+    }
+  };
+
+  private static final ServerCommand helloWorldCommand = new ServerCommand() {
+    @Override
+    public int exec(List<String> args, OutErr outErr, long firstContactTime) throws Exception {
+      outErr.printOut(COMMAND_STDOUT);
+      outErr.printErr(COMMAND_STDERR);
+      return 42;
+    }
+    @Override
+    public boolean shutdown() {
+      return false;
+    }
+  };
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    // Do not use `createUnixTempDir()` here since the file name that results is longer
+    // than 108 characters, so cannot be used as local socket address.
+    File file = File.createTempFile("scratch", ".tmp", new File("/tmp"));
+    file.delete();
+    file.mkdir();
+    serverDir = this.scratch.dir(file.getAbsolutePath());
+
+    workspaceDir = this.scratch.createUnixTempDir();
+    workspaceDir.createDirectory();
+    client = new RPCTestingClient(
+        outErr, serverDir.getRelative("server.socket"));
+    RPCService service = new RPCService(helloWorldCommand);
+    server = new RPCServer(new JavaClock(), service, MAX_IDLE_MILLIS, HEALTH_CHECK_MILLIS,
+        serverDir, workspaceDir);
+    serverThread.start();
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    serverThread.interrupt();
+    serverThread.join();
+
+    FileSystemUtils.deleteTree(serverDir);
+    super.tearDown();
+  }
+
+  protected void testRequest(String request, int ret, String out, String err,
+                             String control) throws Exception {
+    assertEquals(new ServerResponse(control, ret), client.sendRequest(request));
+    assertEquals(out, outErr.outAsLatin1());
+    assertThat(outErr.errAsLatin1()).contains(err);
+  }
+
+  public void testUnknownCommand() throws Exception {
+    testRequest("unknown", 2, "", "SERVER ERROR: Unknown command: unknown\n", "");
+  }
+
+  public void testEmptyBlazeCommand() throws Exception {
+    testRequest("unknown", 2, "", "SERVER ERROR: Unknown command: unknown\n", "");
+  }
+
+  public void testWorkspaceDies() throws Exception {
+    assertTrue(serverThread.isAlive());
+    testRequest("blaze", 42, COMMAND_STDOUT, COMMAND_STDERR, "");
+    Thread.sleep(HEALTH_CHECK_MILLIS * 2);
+    assertTrue(serverThread.isAlive());
+
+    assertTrue(workspaceDir.delete());
+    Thread.sleep(HEALTH_CHECK_MILLIS * 2);
+    assertFalse(serverThread.isAlive());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/server/RPCServiceTest.java b/src/test/java/com/google/devtools/build/lib/server/RPCServiceTest.java
new file mode 100644
index 0000000..42e2683
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/server/RPCServiceTest.java
@@ -0,0 +1,115 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.server.RPCService.UnknownCommandException;
+import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.util.io.RecordingOutErr;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Just makes sure the RPC service understands commands.
+ */
+public class RPCServiceTest extends TestCase {
+
+  private ServerCommand helloWorldCommand = new ServerCommand() {
+    @Override
+    public int exec(List<String> args, OutErr outErr, long firstContactTime) throws Exception {
+      outErr.printOut("Heelllloo....");
+      outErr.printErr("...world!");
+      return 42;
+    }
+    @Override
+    public boolean shutdown() {
+      return false;
+    }
+  };
+
+  private RPCService service =
+      new RPCService(helloWorldCommand);
+
+  public void testUnknownCommandException() {
+    try {
+      service.executeRequest(Arrays.asList("unknown"), new RecordingOutErr(), 0);
+      fail();
+    } catch (UnknownCommandException e) {
+      // success
+    } catch (Exception e){
+      fail();
+    }
+  }
+
+  public void testCommandGetsExecuted() throws Exception {
+    RecordingOutErr outErr = new RecordingOutErr();
+    int exitStatus = service.executeRequest(Arrays.asList("blaze"), outErr, 0);
+
+    assertEquals(42, exitStatus);
+    assertEquals("Heelllloo....", outErr.outAsLatin1());
+    assertEquals("...world!", outErr.errAsLatin1());
+  }
+
+  public void testDelimitation() throws Exception {
+    final List<String> savedArgs = new ArrayList<>();
+
+    RPCService service =
+        new RPCService(new ServerCommand() {
+            @Override
+            public int exec(List<String> args, OutErr outErr, long firstContactTime)
+                throws Exception {
+              savedArgs.addAll(args);
+              return 0;
+            }
+            @Override
+            public boolean shutdown() {
+              return false;
+            }
+          });
+
+    List<String> args = Arrays.asList("blaze", "", " \n", "", "", "", "foo");
+    service.executeRequest(args, new RecordingOutErr(), 0);
+    assertEquals(args.subList(1, args.size()),
+                 savedArgs);
+  }
+
+  public void testShutdownState() throws Exception {
+    assertFalse(service.isShutdown());
+    service.shutdown();
+    assertTrue(service.isShutdown());
+    service.shutdown();
+    assertTrue(service.isShutdown());
+  }
+
+  public void testCommandFailsAfterShutdown() throws Exception {
+    RecordingOutErr outErr = new RecordingOutErr();
+    service.shutdown();
+    try {
+      service.executeRequest(Arrays.asList("blaze"), outErr, 0);
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Received request after shutdown.");
+      /* Make sure it does not execute the command! */
+      assertThat(outErr.outAsLatin1()).isEmpty();
+      assertThat(outErr.errAsLatin1()).isEmpty();
+    }
+  }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/server/RPCTestingClient.java b/src/test/java/com/google/devtools/build/lib/server/RPCTestingClient.java
new file mode 100644
index 0000000..e24e0bb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/server/RPCTestingClient.java
@@ -0,0 +1,78 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.server;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.unix.LocalClientSocket;
+import com.google.devtools.build.lib.unix.LocalSocketAddress;
+import com.google.devtools.build.lib.util.io.RecordingOutErr;
+import com.google.devtools.build.lib.util.io.StreamDemultiplexer;
+import com.google.devtools.build.lib.vfs.Path;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+/**
+ * A client to test RPCServer.
+ */
+public class RPCTestingClient {
+
+  private final RecordingOutErr outErr;
+  private final Path socketFile;
+
+  /**
+   * Create a client to RPCServer. {@code socketFile} must be a file
+   * on disk; this will not work with the in-memory file system.
+   */
+  public RPCTestingClient(RecordingOutErr outErr, Path socketFile) {
+    this.socketFile = socketFile;
+    this.outErr = outErr;
+  }
+
+  public ServerResponse sendRequest(String command, String... params)
+      throws Exception {
+    String request = command;
+    for (String param : params) {
+      request += "\0" + param;
+    }
+    return sendRequest(request);
+  }
+
+  public ServerResponse sendRequest(String request) throws Exception {
+    LocalClientSocket connection = new LocalClientSocket();
+    connection.connect(new LocalSocketAddress(socketFile.getPathFile()));
+    try {
+      OutputStream out = connection.getOutputStream();
+      out.write(request.getBytes(UTF_8));
+      out.flush();
+      connection.shutdownOutput();
+
+      OutputStream stdout = outErr.getOutputStream();
+      OutputStream stderr = outErr.getErrorStream();
+      ByteArrayOutputStream control = new ByteArrayOutputStream();
+      StreamDemultiplexer demux = new StreamDemultiplexer((byte) '1',
+          stdout, stderr, control);
+      ByteStreams.copy(connection.getInputStream(), demux);
+      demux.flush();
+
+      return ServerResponse.parseFrom(control);
+    } finally {
+      connection.close();
+    }
+  }
+
+}