/*
* Copyright 2016-present Facebook, Inc.
*
* 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.facebook.buck.shell;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.testutil.integration.TemporaryPaths;
import com.facebook.buck.util.HumanReadableException;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class WorkerProcessProtocolZeroTest {
@Rule public TemporaryPaths temporaryPaths = new TemporaryPaths();
private JsonWriter dummyJsonWriter;
private JsonReader dummyJsonReader;
@Before
public void setUp() throws IOException {
dummyJsonWriter = new JsonWriter(new StringWriter());
dummyJsonReader = new JsonReader(new StringReader(""));
}
@Test
public void testSendHandshake() throws IOException {
StringWriter jsonSentToWorkerProcess = new StringWriter();
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
new JsonWriter(jsonSentToWorkerProcess), dummyJsonReader, newTempFile(), () -> {});
int handshakeID = 123;
protocol.sendHandshake(handshakeID);
String expectedJson =
String.format(
"[{\"id\":%d,\"type\":\"handshake\",\"protocol_version\":\"0\",\"capabilities\":[]}",
handshakeID);
assertThat(jsonSentToWorkerProcess.toString(), Matchers.containsString(expectedJson));
}
@Test
public void testSendCommand() throws IOException {
StringWriter jsonSentToWorkerProcess = new StringWriter();
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
new JsonWriter(jsonSentToWorkerProcess), dummyJsonReader, newTempFile(), () -> {});
int messageID = 123;
Path argsPath = Paths.get("args");
Path stdoutPath = Paths.get("stdout");
Path stderrPath = Paths.get("stderr");
protocol.sendCommand(messageID, WorkerProcessCommand.of(argsPath, stdoutPath, stderrPath));
String expectedJson =
String.format(
"{\"id\":%d,\"type\":\"command\","
+ "\"args_path\":\"%s\",\"stdout_path\":\"%s\",\"stderr_path\":\"%s\"}",
messageID, argsPath.toString(), stdoutPath.toString(), stderrPath.toString());
assertThat(jsonSentToWorkerProcess.toString(), Matchers.containsString(expectedJson));
}
private JsonReader createMockJsonReaderForReceiveHandshake(
int handshakeID, String type, String protocolVersion) throws IOException {
String jsonToBeRead =
String.format(
"[{\"id\":%d,\"type\":\"%s\",\"protocol_version\":\"%s\",\"capabilities\":[]}",
handshakeID, type, protocolVersion);
return new JsonReader(new StringReader(jsonToBeRead));
}
@Test
public void testReceiveHandshake() throws IOException {
int handshakeID = 123;
JsonReader jsonReader = createMockJsonReaderForReceiveHandshake(handshakeID, "handshake", "0");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
protocol.receiveHandshake(handshakeID);
}
@Test
public void testReceiveHandshakeWithMalformedJSON() throws IOException {
String malformedJson = "=^..^= meow";
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
dummyJsonWriter,
new JsonReader(new StringReader(malformedJson)),
newTempFile(),
() -> {});
try {
protocol.receiveHandshake(123);
} catch (HumanReadableException e) {
assertThat(e.getMessage(), Matchers.containsString("Error receiving handshake response"));
}
}
@Test
public void testReceiveHandshakeWithIncorrectID() throws IOException {
int handshakeID = 123;
int differentHandshakeID = 456;
JsonReader jsonReader =
createMockJsonReaderForReceiveHandshake(differentHandshakeID, "handshake", "0");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveHandshake(handshakeID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(),
Matchers.containsString(
String.format(
"Expected handshake response's \"id\" value to be \"%d\"", handshakeID)));
}
}
@Test
public void testReceiveHandshakeWithIncorrectType() throws IOException {
int handshakeID = 123;
JsonReader jsonReader =
createMockJsonReaderForReceiveHandshake(handshakeID, "INCORRECT MESSAGE TYPE", "0");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveHandshake(handshakeID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(),
Matchers.containsString("Expected handshake response's \"type\" to be \"handshake\""));
}
}
@Test
public void testReceiveHandshakeWithIncorrectProtocolVersion() throws IOException {
int handshakeID = 123;
JsonReader jsonReader =
createMockJsonReaderForReceiveHandshake(handshakeID, "handshake", "BAD PROTOCOL VERSION");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveHandshake(handshakeID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(),
Matchers.containsString(
"Expected handshake response's \"protocol_version\" to be \"0\""));
}
}
private JsonReader createMockJsonReaderForReceiveCommandResponse(
int messageID, String type, int exitCode) throws IOException {
String jsonToBeRead =
String.format("{\"id\":%d,\"type\":\"%s\",\"exit_code\":%d}", messageID, type, exitCode);
return new JsonReader(new StringReader(jsonToBeRead));
}
@Test
public void testReceiveCommandResponse() throws IOException {
int messageID = 123;
JsonReader jsonReader = createMockJsonReaderForReceiveCommandResponse(messageID, "result", 0);
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
protocol.receiveCommandResponse(messageID);
}
@Test
public void testReceiveCommandResponseWithMalformedJSON() throws IOException {
String malformedJson = "><(((('> blub";
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
dummyJsonWriter,
new JsonReader(new StringReader(malformedJson)),
newTempFile(),
() -> {});
try {
protocol.receiveCommandResponse(123);
} catch (HumanReadableException e) {
assertThat(e.getMessage(), Matchers.containsString("Error receiving command response"));
}
}
@Test
public void testReceiveCommandResponseWithIncorrectMessageID() throws IOException {
int messageID = 123;
int differentMessageID = 456;
JsonReader jsonReader =
createMockJsonReaderForReceiveCommandResponse(differentMessageID, "result", 0);
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveCommandResponse(messageID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(),
Matchers.containsString(
String.format("Expected response's \"id\" value to be \"%d\"", messageID)));
}
}
@Test
public void testReceiveCommandResponseWithInvalidType() throws IOException {
int messageID = 123;
JsonReader jsonReader =
createMockJsonReaderForReceiveCommandResponse(messageID, "INVALID RESPONSE TYPE", 0);
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveCommandResponse(messageID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(), Matchers.containsString("Expected response's \"type\" to be one of"));
}
}
@Test
public void testClose() throws IOException {
StringWriter jsonSentToWorkerProcess = new StringWriter();
JsonWriter writer = new JsonWriter(jsonSentToWorkerProcess);
// write an opening bracket now, so the writer doesn't throw due to invalid JSON when it goes
// to write the closing bracket
writer.beginArray();
// add an opening bracket and consume it now, so that the reader doesn't throw due to invalid
// JSON when it goes to read the closing bracket
JsonReader reader = new JsonReader(new StringReader("[]"));
reader.beginArray();
AtomicBoolean cleanedUp = new AtomicBoolean(false);
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(writer, reader, newTempFile(), () -> cleanedUp.set(true));
protocol.close();
String expectedJson = "]";
assertThat(jsonSentToWorkerProcess.toString(), Matchers.endsWith(expectedJson));
assertTrue(cleanedUp.get());
}
@Test
public void testProcessIsStillDestroyedEvenIfErrorOccursWhileClosingStreams() throws IOException {
JsonWriter writer = new JsonWriter(new StringWriter());
// write an opening bracket now, so the writer doesn't throw due to invalid JSON when it goes
// to write the closing bracket
writer.beginArray();
JsonReader reader = new JsonReader(new StringReader("invalid JSON"));
AtomicBoolean cleanedUp = new AtomicBoolean(false);
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(writer, reader, newTempFile(), () -> cleanedUp.set(true));
try {
protocol.close();
} catch (IOException e) {
assertThat(e.getMessage(), Matchers.containsString("malformed JSON"));
// assert that process was still destroyed despite the exception
assertTrue(cleanedUp.get());
}
}
private Path newTempFile() throws IOException {
return temporaryPaths.newFile();
}
@Test
public void testReceiveCommand() throws Exception {
Path argsPath = Paths.get("args");
Path stdoutPath = Paths.get("out");
Path stderrPath = Paths.get("err");
int messageId = 123;
JsonReader jsonReader =
createMockJsonReaderForReceiveCommand(
messageId,
"command",
argsPath.toString(),
stdoutPath.toString(),
stderrPath.toString());
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
WorkerProcessCommand command = protocol.receiveCommand(messageId);
assertThat(command.getArgsPath(), Matchers.equalToObject(argsPath));
assertThat(command.getStdOutPath(), Matchers.equalToObject(stdoutPath));
assertThat(command.getStdErrPath(), Matchers.equalToObject(stderrPath));
}
@Test
public void testReceiveCommandWithMalformedJSON() throws IOException {
String malformedJson = "><(((('> blub";
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
dummyJsonWriter,
new JsonReader(new StringReader(malformedJson)),
newTempFile(),
() -> {});
try {
protocol.receiveCommand(123);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(), Matchers.containsString("Error receiving command from external process"));
}
}
@Test
public void testReceiveCommandWithIncorrectMessageID() throws IOException {
int messageID = 123;
int differentMessageID = 456;
JsonReader jsonReader =
createMockJsonReaderForReceiveCommand(
differentMessageID, "command", "/path/to/args", "/path/to/stdout", "/path/to/stderr");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveCommand(messageID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(),
Matchers.containsString(
String.format("Expected command's \"id\" value to be \"%d\"", messageID)));
}
}
@Test
public void testReceiveCommandWithInvalidType() throws IOException {
int messageID = 123;
JsonReader jsonReader =
createMockJsonReaderForReceiveCommand(
messageID,
"INVALID RESPONSE TYPE",
"/path/to/args",
"/path/to/stdout",
"/path/to/stderr");
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(dummyJsonWriter, jsonReader, newTempFile(), () -> {});
try {
protocol.receiveCommand(messageID);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(), Matchers.containsString("Expected command's \"type\" to be \"command\""));
}
}
private JsonReader createMockJsonReaderForReceiveCommand(
int messageID, String type, String argsPath, String stdoutPath, String stderrPath)
throws IOException {
String jsonToBeRead =
String.format(
"{"
+ "\"id\":%d,"
+ "\"type\":\"%s\","
+ "\"args_path\":\"%s\","
+ "\"stdout_path\":\"%s\","
+ "\"stderr_path\":\"%s\""
+ "}",
messageID, type, argsPath, stdoutPath, stderrPath);
return new JsonReader(new StringReader(jsonToBeRead));
}
@Test
public void testSendCommandResponse() throws IOException {
StringWriter jsonSentToWorkerProcess = new StringWriter();
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
new JsonWriter(jsonSentToWorkerProcess), dummyJsonReader, newTempFile(), () -> {});
int messageID = 123;
protocol.sendCommandResponse(messageID, "result", 0);
String expectedJson =
String.format("{\"id\":%d,\"type\":\"result\",\"exit_code\":0}", messageID);
assertThat(jsonSentToWorkerProcess.toString(), Matchers.containsString(expectedJson));
}
@Test
public void testSendCommandResponseWithWrongType() throws IOException {
StringWriter jsonSentToWorkerProcess = new StringWriter();
WorkerProcessProtocol protocol =
new WorkerProcessProtocolZero(
new JsonWriter(jsonSentToWorkerProcess), dummyJsonReader, newTempFile(), () -> {});
try {
protocol.sendCommandResponse(123, "WRONG_TYPE", 1);
} catch (HumanReadableException e) {
assertThat(
e.getMessage(), Matchers.containsString("Expected response's \"type\" to be one of"));
}
}
}