• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package vogar.monitor;
18 
19 import com.google.gson.JsonObject;
20 import java.io.BufferedInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.net.ConnectException;
25 import java.net.Socket;
26 import java.net.SocketException;
27 import java.nio.charset.Charset;
28 import vogar.Log;
29 import vogar.Outcome;
30 import vogar.Result;
31 import vogar.util.IoUtils;
32 
33 /**
34  * Connects to a target process to monitor its action using XML over raw
35  * sockets.
36  */
37 public final class HostMonitor {
38     private static final Charset UTF8 = Charset.forName("UTF-8");
39 
40     private Log log;
41     private Handler handler;
42     private final String marker = "//00xx";
43 
HostMonitor(Log log, Handler handler)44     public HostMonitor(Log log, Handler handler) {
45         this.log = log;
46         this.handler = handler;
47     }
48 
49     /**
50      * Returns true if the target process completed normally.
51      */
attach(int port)52     public boolean attach(int port) throws IOException {
53         for (int attempt = 0; true; attempt++) {
54             Socket socket = null;
55             try {
56                 socket = new Socket("localhost", port);
57                 InputStream in = new BufferedInputStream(socket.getInputStream());
58                 if (checkStream(in)) {
59                     log.verbose("action monitor connected to " + socket.getRemoteSocketAddress());
60                     return followStream(in);
61                 }
62             } catch (ConnectException ignored) {
63             } catch (SocketException ignored) {
64             } finally {
65                 IoUtils.closeQuietly(socket);
66             }
67 
68             log.verbose("connection " + attempt + " to localhost:"
69                     + port + " failed; retrying in 1s");
70             try {
71                 Thread.sleep(1000);
72             } catch (InterruptedException ignored) {
73             }
74         }
75     }
76 
77     /**
78      * Somewhere between the host and client process, broken socket connections
79      * are being accepted. Before we try to do any work on such a connection,
80      * check it to make sure it's not dead!
81      *
82      * TODO: file a bug (against adb?) for this
83      */
checkStream(InputStream in)84     private boolean checkStream(InputStream in) throws IOException {
85         in.mark(1);
86         if (in.read() == -1) {
87             return false;
88         } else {
89             in.reset();
90             return true;
91         }
92     }
93 
followStream(InputStream in)94     public boolean followStream(InputStream in) throws IOException {
95         return followProcess(new InterleavedReader(marker, new InputStreamReader(in, UTF8)));
96     }
97 
98     /**
99      * Our wire format is a mix of strings and the JSON values like the following:
100      *
101      * {"outcome"="java.util.FormatterMain"}
102      * {"result"="SUCCESS"}
103      * {"outcome"="java.util.FormatterTest#testBar" runner="vogar.target.junit.JUnitRunner"}
104      * {"result"="SUCCESS"}
105      * {"completedNormally"=true}
106      */
followProcess(InterleavedReader reader)107     private boolean followProcess(InterleavedReader reader) throws IOException {
108         String currentOutcome = null;
109         StringBuilder output = new StringBuilder();
110         boolean completedNormally = false;
111 
112         Object o;
113         while ((o = reader.read()) != null) {
114             if (o instanceof String) {
115                 String text = (String) o;
116                 if (currentOutcome != null) {
117                     output.append(text);
118                     handler.output(currentOutcome, text);
119                 } else {
120                     handler.print(text);
121                 }
122             } else if (o instanceof JsonObject) {
123                 JsonObject jsonObject = (JsonObject) o;
124                 if (jsonObject.get("outcome") != null) {
125                     currentOutcome = jsonObject.get("outcome").getAsString();
126                     handler.output(currentOutcome, "");
127                     handler.start(currentOutcome);
128                 } else if (jsonObject.get("result") != null) {
129                     Result currentResult = Result.valueOf(jsonObject.get("result").getAsString());
130                     handler.finish(new Outcome(currentOutcome, currentResult, output.toString()));
131                     output.delete(0, output.length());
132                     currentOutcome = null;
133                 } else if (jsonObject.get("completedNormally") != null) {
134                     completedNormally = jsonObject.get("completedNormally").getAsBoolean();
135                 }
136             } else {
137                 throw new IllegalStateException("Unexpected object: " + o);
138             }
139         }
140 
141         return completedNormally;
142     }
143 
144 
145     /**
146      * Handles updates on the outcomes of a target process.
147      */
148     public interface Handler {
149 
150         /**
151          * Receive notification that an outcome is pending.
152          */
start(String outcomeName)153         void start(String outcomeName);
154 
155         /**
156          * Receive a completed outcome.
157          */
finish(Outcome outcome)158         void finish(Outcome outcome);
159 
160         /**
161          * Receive partial output from an action being executed.
162          */
output(String outcomeName, String output)163         void output(String outcomeName, String output);
164 
165         /**
166          * Receive a string to print immediately
167          */
print(String string)168         void print(String string);
169     }
170 }
171