/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package dtool.genie; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import static melnorme.utilbox.misc.StringUtil.UTF8; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.StringReader; import java.io.Writer; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.misc.Location; import org.junit.After; import org.junit.Ignore; import org.junit.Test; import dtool.engine.DToolServer; import dtool.engine.operations.FindDefinitionOperation_Test; import dtool.engine.operations.FindDefinitionResult; import dtool.genie.GenieServer.GenieCommandException; import dtool.genie.cmdline.FindDefinitionRequest; import dtool.genie.cmdline.FindDefinitionRequest.FindDefinitionResultParser; import dtool.genie.cmdline.ShutdownServerRequest; import dtool.util.JsonReaderExt; import dtool.util.JsonWriterExt; public class GenieServerTest extends JsonWriterTestUtils { public static void shutdownSocketOutput(Socket socket) { try { socket.shutdownOutput(); } catch (IOException e) { throw assertFail(); } } public static void writeStreamMessage(Writer serverOutput, String clientRequest) { try { serverOutput.write(clientRequest); serverOutput.flush(); } catch (IOException e) { assertFail(); } } protected static void logClientMessage(String message) { System.out.println(message); System.out.flush(); } /* ----------------- ----------------- */ protected TestsGenieServer genieServer; protected void prepareGenieServer() { try { if(genieServer != null) { genieServer.serverSocket.close(); } GenieServer.getSentinelFile().delete(); genieServer = new TestsGenieServer(0); } catch (Exception e) { throw melnorme.utilbox.core.ExceptionAdapter.unchecked(e); } new Thread() { { setName("TestsGenieServer"); } @Override public void run() { genieServer.runServer(); } }.start(); } public static class TestsGenieServer extends GenieServer { public static final DToolServer DTOOL_SERVER = new DToolServer(); protected final ArrayList<Throwable> exceptions = new ArrayList<>(); public TestsGenieServer(int portNumber) throws CommonException { super(DTOOL_SERVER, portNumber); } @Override public void logError(String message, Throwable throwable) { super.logError(message, throwable); exceptions.add(throwable); } protected void shutdownAndAwait() throws InterruptedException { shutdown(); awaitTerminationOfActiveConnectionHandlers(); terminationLatch.await(); } protected void awaitTerminationOfActiveConnectionHandlers() { connectionsSemaphore.acquireUninterruptibly(MAX_CONNECTIONS); logClientMessage("All active connections terminated."); connectionsSemaphore.release(MAX_CONNECTIONS); } } protected Socket socket; protected OutputStreamWriter serverInput; protected BufferedReader serverOutput; @Test public void testBasic() throws Exception { testBasic$(); } public void testBasic$() throws Exception { prepareGenieServer(); cleanup(); // Test cleanup methods testGenieServer$(); } @After public void cleanup() throws InterruptedException { cleanClientConnection(); if(genieServer != null) { genieServer.shutdownAndAwait(); assertTrue(genieServer.serverSocket.isClosed()); assertTrue(genieServer.exceptions.isEmpty()); } genieServer = null; } protected void prepareServerConnection() { try { cleanClientConnection(); int serverPortNumber = genieServer.getServerPortNumber(); logClientMessage("---->> Preparing new client connection. " + " Active server connections: " + genieServer.getActiveConnections()); socket = ShutdownServerRequest.createLocalConnection(serverPortNumber); serverInput = new OutputStreamWriter(socket.getOutputStream(), UTF8); serverOutput = new BufferedReader(new InputStreamReader(socket.getInputStream(), UTF8)); } catch (IOException e) { assertFail(); } } protected void cleanClientConnection(){ if(socket != null) { try { serverInput.flush(); socket.close(); socket = null; } catch (IOException e) { assertFail(); } } } protected HashMap<String, Object> sendCommandAndReadResponse(Map<String, Object> jsObject) { sendCommand(serverInput, jsWriteObject(jsObject)); return readResponseObject(); } protected HashMap<String, Object> readResponseObject() { try { return readObject(serverOutput); } catch (IOException e) { throw assertFail(); } } protected void sendCommand(Writer connectionOut, String jsDocument) { logClientMessage(">> Sending command message:\n" + jsDocument); try { connectionOut.write(jsDocument); connectionOut.flush(); } catch (IOException e) { assertFail(); } } protected HashMap<String, Object> response; public void testGenieServer$() { prepareGenieServer(); prepareServerConnection(); response = sendCommandAndReadResponse(jsObject(entry("about", null))); assertAreEqual(response.get("engine"), GenieServer.ENGINE_NAME); assertAreEqual(response.get("version"), GenieServer.ENGINE_VERSION); assertAreEqual(response.get("protocol_version"), "0.1"); assertTrue(genieServer.exceptions.isEmpty()); // Test invalid commands testError(jsObject(entry("invalid_command", null)), "unknown command"); // Test invalid message json testMessageInvalidJson("{ }", "expected property name"); testMessageInvalidJson("{ ] }", "expected ':'"); // Test disconnect during message writeStreamMessage(serverInput, "{ \"about\" : "); shutdownSocketOutput(socket); awaitConnectionHandlerTermination(); checkServerExceptions("End of input"); prepareServerConnection(); // Test shutdown server String responseString = new ShutdownServerRequest().performAndGetResponse(); HashMap<String, Object> responseObject = readJsonObject(responseString); assertTrue(responseObject.containsKey("error") == false); shutdownSocketOutput(socket); genieServer.awaitTerminationOfActiveConnectionHandlers(); assertTrue(genieServer.serverSocket.isClosed()); assertTrue(!GenieServer.getSentinelFile().exists()); } public void testError(Map<String, Object> clientRequest, String expectedContains) { assertTrue(socket.isClosed() == false); HashMap<String, Object> response = sendCommandAndReadResponse(clientRequest); String errorMsg = assertCast(response.get("error"), String.class); assertStringContains(errorMsg.toLowerCase(), expectedContains); assertTrue(genieServer.exceptions.size() == 1); assertStringContains(genieServer.exceptions.get(0).getMessage().toLowerCase(), expectedContains); genieServer.exceptions.clear(); } public void testMessageInvalidJson(String clientRequest, String expectedErrorContains) { assertTrue(socket.isClosed() == false); writeStreamMessage(serverInput, clientRequest); awaitConnectionHandlerTermination(); checkServerExceptions(expectedErrorContains); } protected void awaitConnectionHandlerTermination() { try { while(serverOutput.read() != -1) { } socket.close(); } catch (IOException e) { } genieServer.awaitTerminationOfActiveConnectionHandlers(); } protected void checkServerExceptions(String expectedErrorContains) { assertTrue(genieServer.exceptions.size() == 1); Throwable exception = genieServer.exceptions.get(0); assertStringContains(exception.getMessage().toLowerCase(), expectedErrorContains.toLowerCase()); genieServer.exceptions.clear(); prepareServerConnection(); } @Test public void testSemanticOps() throws Exception { testSemanticOps$(); } public void testSemanticOps$() throws Exception { prepareGenieServer(); new FindDefinitionOperation_GenieTest().testALL(); prepareServerConnection(); new FindDefinitionOperation_ReuseConnectionTest().testALL(); } @Ignore public class FindDefinitionOperation_GenieTest extends FindDefinitionOperation_Test { @Override protected FindDefinitionResult doOperation(Location filePath, int offset) throws GenieCommandException { try { String response = new FindDefinitionRequest().setArguments(testsDubPath2(), filePath.path, offset). performAndGetResponse(); return new FindDefinitionResultParser().read(new JsonReaderExt(new StringReader(response))); } catch (IOException e) { throw assertFail(); } } } @Ignore public class FindDefinitionOperation_ReuseConnectionTest extends FindDefinitionOperation_GenieTest { @Override protected FindDefinitionResult doOperation(Location filePath, int offset) throws GenieCommandException { try { new FindDefinitionRequest().setArguments(testsDubPath2(), filePath.path, offset). writeRequest(new JsonWriterExt(serverInput)); return new FindDefinitionResultParser().read(new JsonReaderExt(serverOutput)); } catch (IOException e) { throw assertFail(); } } } }