Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2d29b325 authored by Android (Google) Code Review's avatar Android (Google) Code Review
Browse files

Merge change 8396

* changes:
  checkin for port forwarding through adb, gets access to external network via USB, this also adds in related plumbing for running the http hosted tests, but will not enable those tests yet.
parents 10e85ba9 f4bf552b
Loading
Loading
Loading
Loading
+109 −38
Original line number Diff line number Diff line
@@ -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;
@@ -139,6 +142,15 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh
    static final String LAYOUT_RESULTS_CRASHED_RESULT_FILE = "results/layout_tests_crashed.txt";
    static final String LAYOUT_TESTS_RUNNER = "run_layout_tests.py";

    static final String HTTP_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/";
    static final String HTTPS_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/ssl/";
    static final String HTTP_LOCAL_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/local/";
    static final String HTTP_MEDIA_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/media/";
    static final String HTTP_WML_TESTS_PREFIX = "/sdcard/android/layout_tests/http/tests/wml/";


    private ForwardServer fs8000, fs8080, fs8443;

    private MyTestRecorder mResultRecorder;
    private Vector<String> mTestList;
    private boolean mRebaselineResults;
@@ -147,6 +159,18 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh

    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
@@ -311,7 +335,7 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setClass(activity, TestShellActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.putExtra(TestShellActivity.TEST_URL, "file://" + test);
        intent.putExtra(TestShellActivity.TEST_URL, getTestUrl(test));
        intent.putExtra(TestShellActivity.RESULT_FILE, resultFile);
        intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
        activity.startActivity(intent);
@@ -377,6 +401,15 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh
        TestShellActivity activity = (TestShellActivity) getActivity();

        // Run tests.
        int addr = -1;
        try{
            addr = AdbUtils.resolve("android-browser-test.mtv.corp.google.com");
        } catch (IOException ioe) {
            Log.w(LOGTAG, "error while resolving test host name", ioe);
        }
        if(addr == -1) {
            Log.w(LOGTAG, "failed to resolve test host. http tests will fail.");
        }
        for (int i = 0; i < mTestList.size(); i++) {
            String s = mTestList.elementAt(i);
            FsUtils.updateTestStatus(TEST_STATUS_FILE, s);
@@ -385,10 +418,48 @@ public class LayoutTestsAutoTest extends ActivityInstrumentationTestCase2<TestSh
        }

        FsUtils.updateTestStatus(TEST_STATUS_FILE, "#DONE");
        if(fs8000 != null)
            fs8000.stop();
        if(fs8080 != null)
            fs8080.stop();
        if(fs8443 != null)
            fs8443.stop();

        activity.finish();
    }

    private void startForwardServerIfNeeded() {
        try {
            if(fs8000 != null)
                fs8000.start();
            if(fs8080 != null)
                fs8080.start();
            if(fs8443 != null)
                fs8443.start();
        } catch (IOException ioe) {
            Log.w(LOGTAG, "failed to start forwarder. http tests will fail.", ioe);
        }
    }

    private String getTestUrl(String path) {
        String url = null;
        if (!path.startsWith(HTTP_TESTS_PREFIX)) {
            url = "file://" + path;
        } else {
            startForwardServerIfNeeded();
            if (path.startsWith(HTTPS_TESTS_PREFIX)) {
                // still cut the URL after "http/tests/"
                url = "https://127.0.0.1:8443/" + path.substring(HTTP_TESTS_PREFIX.length());
            } else if (!path.startsWith(HTTP_LOCAL_TESTS_PREFIX)
                    && !path.startsWith(HTTP_MEDIA_TESTS_PREFIX)
                    && !path.startsWith(HTTP_WML_TESTS_PREFIX)) {
                url = "http://127.0.0.1:8000/" + path.substring(HTTP_TESTS_PREFIX.length());
            } else {
                url = "file://" + path;
            }
        }
        return url;
    }

    private String getTestPath() {
        LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
+9 −0
Original line number Diff line number Diff line
@@ -530,6 +530,12 @@ public class TestShellActivity extends Activity implements LayoutTestController
            return true;
        }

        @Override
        public boolean onJsTimeout() {
            Log.v(LOGTAG, "JavaScript timeout");
            return false;
        }

        @Override
        public void onExceededDatabaseQuota(String url_str,
                String databaseIdentifier, long currentQuota,
@@ -614,6 +620,9 @@ public class TestShellActivity extends Activity implements LayoutTestController
        }

        WebSettings settings = webview.getSettings();
        settings.setAppCacheEnabled(true);
        settings.setAppCachePath(getApplicationContext().getCacheDir().getPath());
        settings.setAppCacheMaxSize(Long.MAX_VALUE);
        settings.setJavaScriptEnabled(true);
        settings.setJavaScriptCanOpenWindowsAutomatically(true);
        settings.setSupportMultipleWindows(true);
+112 −0
Original line number Diff line number Diff line
package com.android.dumprendertree.forwarder;

import android.util.Log;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class AdbUtils {

    private static final String ADB_OK = "OKAY";
    private static final int ADB_PORT = 5037;
    private static final String ADB_HOST = "127.0.0.1";
    private static final int ADB_RESPONSE_SIZE = 4;

    private static final String LOGTAG = "AdbUtils";

    /**
     *
     * Convert integer format IP into xxx.xxx.xxx.xxx format
     *
     * @param host IP address in integer format
     * @return human readable format
     */
    public static String convert(int host) {
        return ((host >> 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;
        }
    }
}
+117 −0
Original line number Diff line number Diff line
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<Forwarder> forwarders;

    public ForwardServer(int localPort, int remoteAddress, int remotePort) {
        this.localPort = localPort;
        this.remoteAddress = remoteAddress;
        this.remotePort = remotePort;
        started = false;
        forwarders = new HashSet<Forwarder>();
    }

    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
+92 −0
Original line number Diff line number Diff line
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  + "}";
        }
    }
}