diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java index a03490d8f0410..2eecef8304f2c 100644 --- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestsAutoTest.java @@ -16,6 +16,9 @@ package com.android.dumprendertree; +import com.android.dumprendertree.forwarder.AdbUtils; +import com.android.dumprendertree.forwarder.ForwardServer; + import android.app.Instrumentation; import android.content.Intent; import android.os.Bundle; @@ -42,7 +45,7 @@ class MyTestRecorder { private BufferedOutputStream mBufferedOutputFailedStream; private BufferedOutputStream mBufferedOutputNoresultStream; private BufferedOutputStream mBufferedOutputTimedoutStream; - + public void passed(String layout_file) { try { mBufferedOutputPassedStream.write(layout_file.getBytes()); @@ -52,7 +55,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void failed(String layout_file) { try { mBufferedOutputFailedStream.write(layout_file.getBytes()); @@ -62,7 +65,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void noresult(String layout_file) { try { mBufferedOutputNoresultStream.write(layout_file.getBytes()); @@ -72,7 +75,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void timedout(String url) { try { mBufferedOutputTimedoutStream.write(url.getBytes()); @@ -82,14 +85,14 @@ class MyTestRecorder { e.printStackTrace(); } } - + public MyTestRecorder(boolean resume) { try { File resultsPassedFile = new File("/sdcard/layout_tests_passed.txt"); File resultsFailedFile = new File("/sdcard/layout_tests_failed.txt"); File noExpectedResultFile = new File("/sdcard/layout_tests_nontext.txt"); File resultTimedoutFile = new File("/sdcard/layout_tests_timedout.txt"); - + mBufferedOutputPassedStream = new BufferedOutputStream(new FileOutputStream(resultsPassedFile, resume)); mBufferedOutputFailedStream = @@ -102,7 +105,7 @@ class MyTestRecorder { e.printStackTrace(); } } - + public void close() { try { mBufferedOutputPassedStream.close(); @@ -120,7 +123,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2 mTestList; private boolean mRebaselineResults; private String mTestPathPrefix; private boolean mFinished; - + public LayoutTestsAutoTest() { super("com.android.dumprendertree", TestShellActivity.class); + + int addr = -1; + try { + addr = AdbUtils.resolve("android-browser-test.mtv.corp.google.com"); + } catch (IOException ioe) { + Log.e(LOGTAG, "failed to resolve server address.", ioe); + } + if(addr != -1) { + fs8000 = new ForwardServer(8000, addr, 8000); + fs8080 = new ForwardServer(8080, addr, 8080); + fs8443 = new ForwardServer(8443, addr, 8443); + } } // This function writes the result of the layout test to @@ -157,7 +181,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2(); - + // Read settings try { this.mTestPathPrefix = (new File(LAYOUT_TESTS_ROOT + runner.mTestPath)).getCanonicalPath(); - } catch (IOException e) { + } catch (IOException e) { Log.e(LOGTAG, "Cannot find test path prefix: " + e.getMessage()); return; } - + this.mRebaselineResults = runner.mRebaseline; - + int timeout = runner.mTimeoutInMillis; if (timeout <= 0) { timeout = DEFAULT_TIMEOUT_IN_MILLIS; } - + this.mResultRecorder = new MyTestRecorder(resume); - + if (!resume) clearTestStatus(); - + getTestList(); if (resume) resumeTestList(); @@ -377,6 +401,15 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2> 24) & 0xFF) + "." + + ((host >> 16) & 0xFF) + "." + + ((host >> 8) & 0xFF) + "." + + (host & 0xFF); + } + + /** + * + * Resolve DNS name into IP address + * + * @param host DNS name + * @return IP address in integer format + * @throws IOException + */ + public static int resolve(String host) throws IOException { + Socket localSocket = new Socket(ADB_HOST, ADB_PORT); + DataInputStream dis = new DataInputStream(localSocket.getInputStream()); + OutputStream os = localSocket.getOutputStream(); + int count_read = 0; + byte[] buf = new byte[128]; + + if (localSocket == null || dis == null || os == null) + return -1; + String cmd = "dns:" + host; + + if(!sendAdbCmd(dis, os, cmd)) + return -1; + + count_read = dis.readInt(); + localSocket.close(); + return count_read; + } + + /** + * + * Send an ADB command using existing socket connection + * + * the streams provided must be from a socket connected to adbd already + * + * @param is input stream of the socket connection + * @param os output stream of the socket + * @param cmd the adb command to send + * @return if adb gave a success response + * @throws IOException + */ + public static boolean sendAdbCmd(InputStream is, OutputStream os, + String cmd) throws IOException { + byte[] buf = new byte[ADB_RESPONSE_SIZE]; + + cmd = String.format("%04X", cmd.length()) + cmd; + os.write(cmd.getBytes()); + int read = is.read(buf); + if(read != ADB_RESPONSE_SIZE || !ADB_OK.equals(new String(buf))) { + Log.w(LOGTAG, "adb cmd faild."); + return false; + } + return true; + } + + /** + * + * Get a tcp socket connection to specified IP address and port proxied by adb + * + * The proxying is transparent, e.g. if a socket is returned, then it can be written to and + * read from as if it is directly connected to the target + * + * @param remoteAddress IP address of the host to connect to + * @param remotePort port of the host to connect to + * @return a valid Socket instance if successful, null otherwise + */ + public static Socket getForwardedSocket(int remoteAddress, int remotePort) { + try { + Socket socket = new Socket(ADB_HOST, ADB_PORT); + String cmd = "tcp:" + remotePort + ":" + convert(remoteAddress); + if(!sendAdbCmd(socket.getInputStream(), socket.getOutputStream(), cmd)) { + socket.close(); + return null; + } + return socket; + } catch (IOException ioe) { + Log.w(LOGTAG, "error creating adb socket", ioe); + return null; + } + } +} diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java new file mode 100644 index 0000000000000..74e018eada1fc --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/ForwardServer.java @@ -0,0 +1,117 @@ +package com.android.dumprendertree.forwarder; + +import android.util.Log; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashSet; +import java.util.Set; + +/** + * + * A port forwarding server. Listens at specified local port and forward the tcp communications to + * external host/port via adb networking proxy. + * + */ +public class ForwardServer { + + private static final String LOGTAG = "ForwardServer"; + + private int remotePort; + private int remoteAddress; + private int localPort; + private ServerSocket serverSocket; + private boolean started; + + private Set forwarders; + + public ForwardServer(int localPort, int remoteAddress, int remotePort) { + this.localPort = localPort; + this.remoteAddress = remoteAddress; + this.remotePort = remotePort; + started = false; + forwarders = new HashSet(); + } + + public synchronized void start() throws IOException { + if(!started) { + serverSocket = new ServerSocket(localPort); + Thread serverThread = new Thread(new ServerRunner(serverSocket)); + serverThread.setName(LOGTAG); + serverThread.start(); + started = true; + } + } + + public synchronized void stop() { + if(started) { + synchronized (forwarders) { + for(Forwarder forwarder : forwarders) + forwarder.stop(); + forwarders.clear(); + } + try { + serverSocket.close(); + } catch (IOException ioe) { + Log.v(LOGTAG, "exception while closing", ioe); + } finally { + started = false; + } + } + } + + public synchronized boolean isRunning() { + return started; + } + + private class ServerRunner implements Runnable { + + private ServerSocket socket; + + public ServerRunner(ServerSocket socket) { + this.socket = socket; + } + + public void run() { + try { + while (true) { + Socket localSocket = socket.accept(); + Socket remoteSocket = AdbUtils.getForwardedSocket(remoteAddress, remotePort); + if(remoteSocket == null) { + try { + localSocket.close(); + } catch (IOException ioe) { + Log.w(LOGTAG, "error while closing socket", ioe); + } finally { + Log.w(LOGTAG, "failed to start forwarding from " + localSocket); + } + } else { + Forwarder forwarder = new Forwarder(localSocket, remoteSocket, + ForwardServer.this); + forwarder.start(); + } + } + } catch (IOException ioe) { + return; + } + } + } + + public void register(Forwarder forwarder) { + synchronized (forwarders) { + if(!forwarders.contains(forwarder)) { + forwarders.add(forwarder); + } + } + } + + public void unregister(Forwarder recyclable) { + synchronized (forwarders) { + if(forwarders.contains(recyclable)) { + recyclable.stop(); + forwarders.remove(recyclable); + } + } + } +} \ No newline at end of file diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java new file mode 100644 index 0000000000000..e1e04a7e538cb --- /dev/null +++ b/tests/DumpRenderTree/src/com/android/dumprendertree/forwarder/Forwarder.java @@ -0,0 +1,92 @@ +package com.android.dumprendertree.forwarder; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * + * Worker class for {@link ForwardServer}. A Forwarder will be created once the ForwardServer + * accepts an incoming connection, and it will then forward the incoming/outgoing streams to a + * connection already proxied by adb networking (see also {@link AdbUtils}). + * + */ +public class Forwarder { + + private ForwardServer server; + private Socket from, to; + + private static final String LOGTAG = "Forwarder"; + + public Forwarder (Socket from, Socket to, ForwardServer server) { + this.server = server; + this.from = from; + this.to = to; + server.register(this); + } + + public void start() { + Thread outgoing = new Thread(new SocketPipe(from, to)); + Thread incoming = new Thread(new SocketPipe(to, from)); + outgoing.setName(LOGTAG); + incoming.setName(LOGTAG); + outgoing.start(); + incoming.start(); + } + + public void stop() { + shutdown(from); + shutdown(to); + } + + private void shutdown(Socket socket) { + try { + socket.shutdownInput(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#shutdownInput", e); + } + try { + socket.shutdownOutput(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#shutdownOutput", e); + } + try { + socket.close(); + } catch (IOException e) { + Log.v(LOGTAG, "Socket#close", e); + } + } + + private class SocketPipe implements Runnable { + + private Socket in, out; + + public SocketPipe(Socket in, Socket out) { + this.in = in; + this.out = out; + } + + public void run() { + try { + int length; + InputStream is = in.getInputStream(); + OutputStream os = out.getOutputStream(); + byte[] buffer = new byte[4096]; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } catch (IOException ioe) { + } finally { + server.unregister(Forwarder.this); + } + } + + @Override + public String toString() { + return "SocketPipe{" + in + "=>" + out + "}"; + } + } +}