content: /*******************************************************************************
   * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and 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:
   *    Marc R. Hoffmann - initial API and implementation
   *    
   *******************************************************************************/
  package org.jacoco.core.tools;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.ConnectException;
  import java.net.InetAddress;
  import java.net.ServerSocket;
  import java.net.Socket;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import org.jacoco.core.data.SessionInfo;
  import org.jacoco.core.runtime.IRemoteCommandVisitor;
  import org.jacoco.core.runtime.RemoteControlReader;
  import org.jacoco.core.runtime.RemoteControlWriter;
  import org.junit.After;
  import org.junit.Before;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.rules.ExpectedException;
  
  /**
   * Unit tests for {@link ExecDumpClient}.
   */
  public class ExecDumpClientTest {
  
  	private ExecDumpClient client;
  	private List<String> callbacks;
  
  	private boolean dumpRequested;
  	private boolean resetRequested;
  
  	private ServerSocket server;
  
  	@Rule
  	public ExpectedException exception = ExpectedException.none();
  
  	@Before
  	public void setup() {
  		callbacks = new ArrayList<String>();
  		client = new ExecDumpClient() {
  			@Override
  			protected void onConnecting(InetAddress address, int port) {
  				callbacks.add("onConnecting");
  			}
  
  			@Override
  			protected void onConnectionFailure(IOException exception) {
  				callbacks.add("onConnectionFailure");
  			}
  		};
  	}
  
  	@After
  	public void teardown() throws IOException {
  		if (server != null) {
  			server.close();
  		}
  	}
  
  	@Test
  	public void testNoRetries() throws IOException {
  		try {
  			client.dump((String) null, getFreePort());
  			fail("ConnectException expected");
  		} catch (ConnectException e) {
  			// expected
  		}
  
  		assertEquals(Arrays.asList("onConnecting"), callbacks);
  	}
  
  	@Test
  	public void testWithRetries() throws IOException {
  		client.setRetryCount(3);
  		client.setRetryDelay(0);
  		try {
  			client.dump((String) null, getFreePort());
  			fail("ConnectException expected");
  		} catch (ConnectException e) {
  			// expected
  		}
  
  		assertEquals(Arrays.asList( // Initial attempt
  				"onConnecting",
  				// 1. Retry
  				"onConnectionFailure", "onConnecting",
  				// 2. Retry
  				"onConnectionFailure", "onConnecting",
  				// 3. Retry
  				"onConnectionFailure", "onConnecting")
  
  				, callbacks);
  	}
  
  	@Test
  	public void testDump() throws IOException {
  		int port = createExecServer();
  		ExecFileLoader loader = client.dump((String) null, port);
  		assertTrue(dumpRequested);
  		assertFalse(resetRequested);
  
  		List<SessionInfo> infos = loader.getSessionInfoStore().getInfos();
  		assertEquals(1, infos.size());
  		assertEquals("TestId", infos.get(0).getId());
  	}
  
  	@Test
  	public void testReset() throws IOException {
  		int port = createExecServer();
  		client.setDump(false);
  		client.setReset(true);
  		client.dump((String) null, port);
  		assertFalse(dumpRequested);
  		assertTrue(resetRequested);
  	}
  
  	@Test
  	public void should_throw_IOException_when_server_closes_connection_without_response()
  			throws IOException {
  		exception.expect(IOException.class);
  		exception.expectMessage("Socket closed unexpectedly.");
  
  		int port = createNopServer();
  		client.dump((String) null, port);
  	}
  
  	private int getFreePort() throws IOException {
  		final ServerSocket server = new ServerSocket(0, 0,
  				InetAddress.getByName(null));
  		final int port = server.getLocalPort();
  		server.close();
  		return port;
  	}
  
  	private int createExecServer() throws IOException {
  		server = new ServerSocket(0, 0, InetAddress.getByName(null));
  		new Thread(new Runnable() {
  			public void run() {
  				try {
  					handleConnection(server.accept());
  				} catch (IOException e) {
  					// ignore
  				}
  			}
  		}).start();
  		return server.getLocalPort();
  	}
  
  	private void handleConnection(Socket socket) throws IOException {
  		final RemoteControlWriter writer = new RemoteControlWriter(
  				socket.getOutputStream());
  		final RemoteControlReader reader = new RemoteControlReader(
  				socket.getInputStream());
  		reader.setRemoteCommandVisitor(new IRemoteCommandVisitor() {
  			public void visitDumpCommand(boolean dump, boolean reset)
  					throws IOException {
  				dumpRequested = dump;
  				resetRequested = reset;
  				if (dump) {
  					writer.visitSessionInfo(
  							new SessionInfo("TestId", 100, 200));
  				}
  				writer.sendCmdOk();
  			}
  		});
  		reader.read();
  	}
  
  	private int createNopServer() throws IOException {
  		server = new ServerSocket(0, 0, InetAddress.getByName(null));
  		new Thread(new Runnable() {
  			public void run() {
  				try {
  					Socket socket = server.accept();
  					InputStream in = socket.getInputStream();
  					// Read Header:
  					in.read();
  					in.read();
  					in.read();
  					in.read();
  					in.read();
  					// Read Dump Command:
  					in.read();
  					in.read();
  					in.read();
  					// Then just close connection without any response:
  					socket.close();
  				} catch (IOException e) {
  					// ignore
  				}
  			}
  		}).start();
  		return server.getLocalPort();
  	}
  
  }
  