/******************************************************************************* * Copyright (c) 2012, 2015 Wind River Systems, Inc. and others. 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.tcf.te.tests.tcf; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.tcf.core.TransientPeer; import org.eclipse.tcf.protocol.IPeer; import org.eclipse.tcf.protocol.JSON; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.te.core.interfaces.IConnectable; import org.eclipse.tcf.te.runtime.callback.Callback; import org.eclipse.tcf.te.runtime.concurrent.util.ExecutorsUtil; import org.eclipse.tcf.te.runtime.model.factory.Factory; import org.eclipse.tcf.te.runtime.utils.Host; import org.eclipse.tcf.te.runtime.utils.net.IPAddressUtil; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerModel; import org.eclipse.tcf.te.tcf.locator.interfaces.nodes.IPeerNode; import org.eclipse.tcf.te.tcf.locator.interfaces.services.IPeerModelLookupService; import org.eclipse.tcf.te.tcf.locator.interfaces.services.IPeerModelUpdateService; import org.eclipse.tcf.te.tcf.locator.model.ModelManager; import org.eclipse.tcf.te.tests.CoreTestCase; /** * TCF test case implementation. * <p> * Launches a TCF agent at local host and make it available for a test case. */ public class TcfTestCase extends CoreTestCase { // The agent launcher instance private AgentLauncher launcher; // The peer instance protected IPeer peer; // The peer model instance protected IPeerNode peerNode; // The test agent location private IPath agentLocation; /* (non-Javadoc) * @see org.eclipse.tcf.te.tests.CoreTestCase#setUp() */ @Override protected void setUp() throws Exception { super.setUp(); launchAgent(); } /* (non-Javadoc) * @see org.eclipse.tcf.te.tests.CoreTestCase#tearDown() */ @Override protected void tearDown() throws Exception { if (peerNode != null && peerNode.isConnectStateChangeActionAllowed(IConnectable.ACTION_DISCONNECT)) { Callback cb = new Callback(); peerNode.changeConnectState(IConnectable.ACTION_DISCONNECT, cb, null); ExecutorsUtil.waitAndExecute(60000, cb.getDoneConditionTester(null)); } if (launcher != null) launcher.dispose(); if (peerNode != null) { Runnable runnable = new Runnable() { @Override public void run() { ModelManager.getPeerModel().getService(IPeerModelUpdateService.class).remove(peerNode); } }; Protocol.invokeAndWait(runnable); } peer = null; peerNode = null; super.tearDown(); } /** * Launches a TCF agent at local host. */ protected void launchAgent() { // Get the agent location IPath path = getAgentFile(); Throwable error = null; String message = null; // If the agent is not marked executable on Linux, we have to change that if (Host.isLinuxHost() && !path.toFile().canExecute()) { try { Runtime.getRuntime().exec(new String[] { "chmod", "u+x", path.toString() }); //$NON-NLS-1$ //$NON-NLS-2$ } catch (IOException e) { error = e; message = e.getLocalizedMessage(); } } assertNull("Failed to make the agent executable for the current user.", message); //$NON-NLS-1$ error = null; message = null; assertTrue("Agent should be executable but is not.", path.toFile().canExecute()); //$NON-NLS-1$ // Create the agent launcher launcher = new AgentLauncher(path); try { launcher.launch(); } catch (Throwable e) { error = e; message = e.getLocalizedMessage(); } assertNull("Failed to launch agent: " + message, error); //$NON-NLS-1$ error = null; message = null; assertNotNull("Process handle not associated with launcher.", launcher.getProcess()); //$NON-NLS-1$ assertNotNull("Process output reader not associated with launcher.", launcher.getOutputReader()); //$NON-NLS-1$ Process process = launcher.getProcess(); int exitCode = -1; try { exitCode = process.exitValue(); } catch (IllegalThreadStateException e) { error = e; message = e.getLocalizedMessage(); } assertNotNull("Agent process died with exit code " + exitCode, error); //$NON-NLS-1$ error = null; message = null; // The agent is started with "-S" to write out the peer attributes in JSON format. String output = null; int counter = 10; while (counter > 0 && output == null) { // Try to read in the output output = launcher.getOutputReader().getOutput(); if ("".equals(output)) { //$NON-NLS-1$ output = null; waitAndDispatch(200); } counter--; } assertNotNull("Failed to read output from agent.", output); //$NON-NLS-1$ // Find the "Server-Properties: ..." string within the output int start = output.indexOf("Server-Properties:"); //$NON-NLS-1$ if (start != -1 && start > 0) { output = output.substring(start); } // Strip away "Server-Properties:" output = output.replace("Server-Properties:", " "); //$NON-NLS-1$ //$NON-NLS-2$ output = output.trim(); // Expectation is that the agent is printing the server properties as single line. // If we have still a newline in the string, ignore everything after it if (output.indexOf('\n') != -1) { output = output.substring(0, output.indexOf('\n')); output = output.trim(); } // Read into an object Object object = parseOne(output); @SuppressWarnings("unchecked") final Map<String, String> attrs = new HashMap<String, String>((Map<String, String>)object); // Lookup the corresponding peer object final IPeerModel model = ModelManager.getPeerModel(); assertNotNull("Failed to access locator model instance.", model); //$NON-NLS-1$ // The expected peer id is "<transport>:<canonical IP>:<port>" String transport = attrs.get(IPeer.ATTR_TRANSPORT_NAME); assertNotNull("Unexpected return value 'null'.", transport); //$NON-NLS-1$ String port = attrs.get(IPeer.ATTR_IP_PORT); assertNotNull("Unexpected return value 'null'.", port); //$NON-NLS-1$ String ip = IPAddressUtil.getInstance().getIPv4LoopbackAddress(); assertNotNull("Unexpected return value 'null'.", ip); //$NON-NLS-1$ final String id = transport + ":" + ip + ":" + port; //$NON-NLS-1$ //$NON-NLS-2$ final AtomicReference<IPeerNode> node = new AtomicReference<IPeerNode>(); Runnable runnable = new Runnable() { @Override public void run() { node.set(model.getService(IPeerModelLookupService.class).lkupPeerModelById(id)); // If the peer model is not found by id, try the agent id as fallback. if (node.get() == null) { String agentID = attrs.get(IPeer.ATTR_AGENT_ID); assertNotNull("Unexpected return value 'null'.", agentID); //$NON-NLS-1$ IPeerNode[] candidates = model.getService(IPeerModelLookupService.class).lkupPeerModelByAgentId(agentID); if (candidates != null && candidates.length > 0) node.set(candidates[0]); } } }; assertFalse("Test is running in TCF dispatch thread.", Protocol.isDispatchThread()); //$NON-NLS-1$ Protocol.invokeAndWait(runnable); // If the peer model is still not found, we create a transient peer if (node.get() == null) { attrs.put(IPeer.ATTR_ID, id); attrs.put(IPeer.ATTR_IP_HOST, ip); attrs.put("SkipValueAdds", "true"); //$NON-NLS-1$ //$NON-NLS-2$ peer = new TransientPeer(attrs); peerNode = Factory.getInstance().newInstance(IPeerNode.class, new Object[] { model, peer }); runnable = new Runnable() { @Override public void run() { model.getService(IPeerModelUpdateService.class).add(peerNode); } }; Protocol.invokeAndWait(runnable); } else { peerNode = node.get(); peer = peerNode.getPeer(); } assertNotNull("Failed to determine the peer to use for the tests.", peer); //$NON-NLS-1$ } protected IPath getAgentFile() { IPath path = getAgentLocation(); assertNotNull("Cannot determine TCF agent location.", path); //$NON-NLS-1$ // Add the agent executable name path = path.append("agent"); //$NON-NLS-1$ if (Host.isWindowsHost()) path = path.addFileExtension("exe"); //$NON-NLS-1$ assertTrue("Invalid agent location: " + path.toString(), path.toFile().isFile()); //$NON-NLS-1$ return path; } /** * Returns the agent location. * * @return The agent location or <code>null</code> if not found. */ protected IPath getAgentLocation() { if (agentLocation == null) { String agentPath = System.getProperty("tcf.agent.path"); //$NON-NLS-1$ if (agentPath != null && !"".equals(agentPath.trim())) { //$NON-NLS-1$ agentLocation = new Path(agentPath); } else { agentLocation = getDataLocation("agent", true, true); //$NON-NLS-1$ } } return agentLocation; } /** * Parses a object from the given encoded string. * * @param encoded The encoded string. Must not be <code>null</code>. * @return The object */ private static Object parseOne(final String encoded) { assertNotNull(encoded); final AtomicReference<Object> object = new AtomicReference<Object>(); final AtomicReference<String> message = new AtomicReference<String>(); final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); Runnable runnable = new Runnable() { @Override public void run() { try { object.set(JSON.parseOne(encoded.getBytes("UTF-8"))); //$NON-NLS-1$ } catch (IOException e) { error.set(e); message.set(e.getLocalizedMessage()); } } }; assertFalse("Test is running in TCF dispatch thread.", Protocol.isDispatchThread()); //$NON-NLS-1$ Protocol.invokeAndWait(runnable); assertNull("Failed to parse server properties: " + message.get(), error.get()); //$NON-NLS-1$ assertTrue("Server properties object is not of expected type Map.", object.get() instanceof Map); //$NON-NLS-1$ return object.get(); } /** * Returns the location of the helloWorld binary. * * @return The location of the helloWorld binary or <code>null/code>. */ protected IPath getHelloWorldLocation() { IPath path = getDataLocation("helloWorld", true, true); //$NON-NLS-1$ if (path != null) { path = path.append("helloWorld"); //$NON-NLS-1$ if (Host.isWindowsHost()) { path = path.addFileExtension("exe"); //$NON-NLS-1$ } } return path; } /** * Copy a file from source to destination. * * @param source The source file. Must not be <code>null</code>. * @param dest The destination file. Must not be <code>null</code>. * * @throws IOException In case the copy fails. */ protected void copyFile(File source, File dest) throws IOException { assertNotNull(source); assertNotNull(dest); FileChannel inputChannel = null; FileChannel outputChannel = null; try { inputChannel = new FileInputStream(source).getChannel(); outputChannel = new FileOutputStream(dest).getChannel(); outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); } finally { if (inputChannel != null) inputChannel.close(); if (outputChannel != null) outputChannel.close(); } } }