Skip to content

Commit 8007ea8

Browse files
committed
Initial Commit
0 parents  commit 8007ea8

File tree

6 files changed

+929
-0
lines changed

6 files changed

+929
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
bin/
2+
browserstack-api/target
3+
*.class
4+
.DS_Store
5+
*.swp

browserstack-api/pom.xml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<groupId>com.browserstack.local</groupId>
5+
<artifactId>browserstack-api</artifactId>
6+
<packaging>jar</packaging>
7+
<version>1.0-SNAPSHOT</version>
8+
<name>browserstack-api</name>
9+
<url>http://maven.apache.org</url>
10+
<properties>
11+
<powermock.version>1.6.4</powermock.version>
12+
</properties>
13+
<dependencies>
14+
<dependency>
15+
<groupId>junit</groupId>
16+
<artifactId>junit</artifactId>
17+
<version>4.12</version>
18+
<scope>test</scope>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.mockito</groupId>
22+
<artifactId>mockito-core</artifactId>
23+
<version>1.10.19</version>
24+
<scope>test</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.powermock</groupId>
28+
<artifactId>powermock-module-junit4</artifactId>
29+
<version>${powermock.version}</version>
30+
<scope>test</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.powermock</groupId>
34+
<artifactId>powermock-api-mockito</artifactId>
35+
<version>${powermock.version}</version>
36+
<scope>test</scope>
37+
</dependency>
38+
</dependencies>
39+
</project>
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package com.browserstack.local;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.io.BufferedReader;
6+
import java.io.InputStreamReader;
7+
import java.util.Map;
8+
import java.util.HashMap;
9+
import java.util.regex.Matcher;
10+
import java.util.regex.Pattern;
11+
import java.util.logging.Level;
12+
import java.util.logging.Logger;
13+
14+
import com.browserstack.local.BrowserStackTunnel.TunnelState;
15+
import com.browserstack.local.BrowserStackTunnel.BrowserStackLocalListener;
16+
17+
public class BrowserStackLocal {
18+
private static Logger logger;
19+
private static final int MAX_CONNECT_WAIT = 30000; // 30s x 2 = 60s
20+
private static final int MAX_CONNECT_ATTEMPTS = 2;
21+
22+
private final File binaryFile;
23+
private final String argumentString;
24+
25+
private Process process;
26+
private Thread processThread;
27+
28+
private StringBuffer output;
29+
private String lastError;
30+
private TunnelState tunnelState;
31+
32+
private BrowserStackLocalListener listener;
33+
34+
private static final Object monitor = new Object();
35+
private static final Map<Pattern, TunnelState> stateMatchers = new HashMap<Pattern, TunnelState>();
36+
37+
static {
38+
stateMatchers.put(Pattern.compile("Press Ctrl-C to exit.*", Pattern.MULTILINE), TunnelState.CONNECTED);
39+
stateMatchers.put(Pattern.compile("\\s*\\*\\*\\* Error:\\s+(.*)$", Pattern.MULTILINE), TunnelState.ERROR);
40+
}
41+
42+
protected BrowserStackLocal(File binaryFile, String argumentString) {
43+
if (binaryFile == null || argumentString == null || !binaryFile.exists()) {
44+
throw new IllegalArgumentException("Invalid arguments");
45+
}
46+
47+
this.binaryFile = binaryFile;
48+
this.argumentString = argumentString;
49+
this.output = new StringBuffer();
50+
this.tunnelState = TunnelState.IDLE;
51+
this.logger = BrowserStackTunnel.logger;
52+
53+
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
54+
@Override
55+
public void run() {
56+
BrowserStackLocal.this.kill();
57+
}
58+
}));
59+
}
60+
61+
protected void run() {
62+
if (process != null) {
63+
kill();
64+
}
65+
66+
processThread = new Thread(new Runnable() {
67+
@Override
68+
public void run() {
69+
notifyTunnelStateChanged(TunnelState.CONNECTING);
70+
71+
try {
72+
logger.fine("Arguments -- " + argumentString);
73+
process = new ProcessBuilder((binaryFile.getAbsolutePath() + " " + argumentString).split(" ")).start();
74+
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
75+
76+
String line;
77+
while ((line = br.readLine()) != null) {
78+
output.append(line).append("\n");
79+
80+
if (processOutput(output.toString())) {
81+
logger.fine(output.toString());
82+
output.setLength(0);
83+
}
84+
}
85+
} catch (IOException e) {
86+
if (listener != null) {
87+
listener.onError(e.getMessage());
88+
}
89+
} finally {
90+
logger.fine(output.toString());
91+
output.setLength(0);
92+
notifyTunnelStateChanged(TunnelState.DISCONNECTED);
93+
}
94+
}
95+
});
96+
97+
processThread.start();
98+
}
99+
100+
protected void run(BrowserStackLocalListener listener) {
101+
setListener(listener);
102+
run();
103+
}
104+
105+
protected void runSync(BrowserStackLocalListener listener) {
106+
setListener(listener);
107+
108+
if (process != null) {
109+
kill();
110+
}
111+
112+
notifyTunnelStateChanged(TunnelState.CONNECTING);
113+
run();
114+
115+
int connAttempts = 0;
116+
boolean connFailed = false;
117+
118+
synchronized (monitor) {
119+
while (tunnelState == TunnelState.CONNECTING) {
120+
logger.info("Waiting: " + connAttempts);
121+
try {
122+
monitor.wait(MAX_CONNECT_WAIT);
123+
} catch (InterruptedException e) {
124+
logger.info("Exc: " + e.getMessage() + " " + isConnected());
125+
}
126+
127+
if (MAX_CONNECT_ATTEMPTS > 0 && ++connAttempts >= MAX_CONNECT_ATTEMPTS) {
128+
connFailed = true;
129+
break;
130+
}
131+
}
132+
}
133+
134+
if (connFailed) {
135+
killWithError("Failed to connect to BrowserStack");
136+
}
137+
}
138+
139+
protected void kill() {
140+
if (process != null) {
141+
process.destroy();
142+
process = null;
143+
}
144+
145+
if (processThread != null && processThread.isAlive()) {
146+
processThread.interrupt();
147+
processThread = null;
148+
}
149+
150+
logger.fine(output.toString());
151+
output.setLength(0);
152+
tunnelState = TunnelState.DISCONNECTED;
153+
}
154+
155+
protected void setListener(BrowserStackLocalListener listener) {
156+
this.listener = listener;
157+
}
158+
159+
protected boolean isConnected() {
160+
return (tunnelState == TunnelState.CONNECTED);
161+
}
162+
163+
protected String getLastError() {
164+
return lastError;
165+
}
166+
167+
protected TunnelState getTunnelState() {
168+
return tunnelState;
169+
}
170+
171+
private void killWithError(String message) {
172+
setError(message);
173+
notifyTunnelStateChanged(TunnelState.ERROR);
174+
kill();
175+
}
176+
177+
private boolean processOutput(final String output) {
178+
if (output != null && !output.trim().isEmpty()) {
179+
String error;
180+
181+
for (Map.Entry<Pattern, TunnelState> entry : stateMatchers.entrySet()) {
182+
Matcher m = entry.getKey().matcher(output);
183+
184+
if (m.find()) {
185+
if (entry.getValue() == TunnelState.ERROR) {
186+
error = (m.groupCount() > 0) ? m.group(1) : output;
187+
} else {
188+
error = null;
189+
}
190+
191+
setError(error);
192+
notifyTunnelStateChanged(entry.getValue());
193+
return true;
194+
}
195+
}
196+
}
197+
198+
return false;
199+
}
200+
201+
private void notifyTunnelStateChanged(TunnelState state) {
202+
if (tunnelState != state) {
203+
if (listener != null) {
204+
listener.onTunnelStateChange(state);
205+
}
206+
207+
synchronized (monitor) {
208+
monitor.notifyAll();
209+
}
210+
}
211+
212+
tunnelState = state;
213+
}
214+
215+
private void setError(String message) {
216+
lastError = message;
217+
218+
if (listener != null) {
219+
listener.lastError = lastError;
220+
}
221+
}
222+
}

0 commit comments

Comments
 (0)