Skip to content

Commit 44c9bda

Browse files
authored
Merge pull request #57 from browserstack/LOC-6563-arm64-proxy-parity
3.1.0: arm64 + proxy passthrough for binary download (LOC-6563)
2 parents 3012a29 + d968e36 commit 44c9bda

8 files changed

Lines changed: 149 additions & 30 deletions

File tree

BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackLocal Unit Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<RootNamespace>BrowserStackLocal_Unit_Tests</RootNamespace>
44
<AssemblyName>BrowserStackLocal Unit Tests</AssemblyName>
5-
<TargetFrameworks>net7.0</TargetFrameworks>
5+
<TargetFrameworks>net6.0</TargetFrameworks>
66
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
77
<Title>BrowserStackLocal Unit Tests</Title>
88
<Product>BrowserStackLocal Unit Tests</Product>
@@ -18,10 +18,10 @@
1818
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
1919
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
2020
</ItemGroup>
21-
<ItemGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
21+
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
2222
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
2323
<Reference Include="BrowserStackLocal">
24-
<HintPath>..\BrowserStackLocal\bin\$(Configuration)\net7.0\BrowserStackLocal.dll</HintPath>
24+
<HintPath>..\BrowserStackLocal\bin\$(Configuration)\net6.0\BrowserStackLocal.dll</HintPath>
2525
</Reference>
2626
</ItemGroup>
2727
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

BrowserStackLocal/BrowserStackLocal Unit Tests/BrowserStackTunnelTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class BrowserStackTunnelTests
1717
static readonly string homepath = os.Platform.ToString() == "Unix" ?
1818
Environment.GetFolderPath(Environment.SpecialFolder.Personal) :
1919
Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
20-
static readonly string binaryName = os.Platform.ToString() == "Unix" ? "BrowserStackLocal-darwin-x64" : "BrowserStackLocal.exe";
20+
static readonly string binaryName = BrowserStackTunnel.GetBinaryName();
2121
private TunnelClass tunnel;
2222
[TestMethod]
2323
public void TestInitialState()
@@ -103,12 +103,36 @@ public void TestBinaryArgumentsAreEmptyOnNull()
103103
}
104104

105105

106+
[TestMethod]
107+
public void TestGetBinaryNameReturnsKnownPlatformBinary()
108+
{
109+
string result = BrowserStackTunnel.GetBinaryName();
110+
string[] knownBinaries = new[] {
111+
"BrowserStackLocal.exe",
112+
"BrowserStackLocal-darwin-x64",
113+
"BrowserStackLocal-linux-x64",
114+
"BrowserStackLocal-linux-ia32",
115+
"BrowserStackLocal-linux-arm64",
116+
"BrowserStackLocal-alpine"
117+
};
118+
Assert.Contains(result, knownBinaries);
119+
}
120+
121+
[TestMethod]
122+
public void TestSetProxyAcceptsHostAndPort()
123+
{
124+
tunnel = new TunnelClass();
125+
Assert.DoesNotThrow(() => tunnel.SetProxy("proxy.example.com", 8080));
126+
Assert.DoesNotThrow(() => tunnel.SetProxy(null, 0));
127+
}
128+
106129
public void testFallbackException()
107130
{
108131
tunnel.fallbackPaths();
109132
}
110133
public class TunnelClass : BrowserStackTunnel
111134
{
135+
public TunnelClass() : base("test-user-agent") {}
112136
public StringBuilder getOutputBuilder()
113137
{
114138
return output;

BrowserStackLocal/BrowserStackLocal Unit Tests/LocalTests.cs

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void TestThrowsWithNoAccessKey()
3737
options.Add(new KeyValuePair<string, string>("key", ""));
3838
local = new LocalClass();
3939

40-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
40+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
4141
tunnelMock.Setup(mock => mock.Run("", "", logAbsolute, "start"));
4242
local.setTunnel(tunnelMock.Object);
4343

@@ -53,7 +53,7 @@ public void TestWorksWithAccessKeyInOptions()
5353
options = new List<KeyValuePair<string, string>>();
5454
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
5555
local = new LocalClass();
56-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
56+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
5757
local.setTunnel(tunnelMock.Object);
5858
Assert.DoesNotThrow(new TestDelegate(startWithOptions),
5959
"BROWSERSTACK_ACCESS_KEY cannot be empty. Specify one by adding key to options or adding to the environment variable BROWSERSTACK_ACCESS_KEY.");
@@ -68,7 +68,7 @@ public void TestWorksWithAccessKeyNotInOptions()
6868
Environment.SetEnvironmentVariable("BROWSERSTACK_ACCESS_KEY", "envDummyKey");
6969
options = new List<KeyValuePair<string, string>>();
7070
local = new LocalClass();
71-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
71+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
7272
tunnelMock.Setup(mock => mock.Run("envDummyKey", "", logAbsolute, "start"));
7373
local.setTunnel(tunnelMock.Object);
7474
Assert.DoesNotThrow(new TestDelegate(startWithOptions),
@@ -86,7 +86,7 @@ public void TestWorksForFolderTesting()
8686
options.Add(new KeyValuePair<string, string>("f", "dummyFolderPath"));
8787

8888
local = new LocalClass();
89-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
89+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
9090
tunnelMock.Setup(mock => mock.Run("dummyKey", "dummyFolderPath", logAbsolute, "start"));
9191
local.setTunnel(tunnelMock.Object);
9292
local.start(options);
@@ -103,11 +103,11 @@ public void TestWorksForBinaryPath()
103103
options.Add(new KeyValuePair<string, string>("binarypath", "dummyPath"));
104104

105105
local = new LocalClass();
106-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
106+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
107107
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
108108
local.setTunnel(tunnelMock.Object);
109109
local.start(options);
110-
tunnelMock.Verify(mock => mock.addBinaryPath("dummyPath", ""), Times.Once);
110+
tunnelMock.Verify(mock => mock.addBinaryPath("dummyPath", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
111111
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
112112
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
113113
local.stop();
@@ -125,11 +125,11 @@ public void TestWorksWithBooleanOptions()
125125
options.Add(new KeyValuePair<string, string>("onlyAutomate", "true"));
126126

127127
local = new LocalClass();
128-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
128+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
129129
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
130130
local.setTunnel(tunnelMock.Object);
131131
local.start(options);
132-
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
132+
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
133133
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-vvv.*-force.*-forcelocal.*-forceproxy.*-onlyAutomate.*")), Times.Once());
134134
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
135135
local.stop();
@@ -148,11 +148,11 @@ public void TestWorksWithValueOptions()
148148
options.Add(new KeyValuePair<string, string>("proxyPass", "dummyPass"));
149149

150150
local = new LocalClass();
151-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
151+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
152152
tunnelMock.Setup(mock =>mock.Run("dummyKey", "", logAbsolute, "start"));
153153
local.setTunnel(tunnelMock.Object);
154154
local.start(options);
155-
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
155+
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
156156
tunnelMock.Verify(mock => mock.addBinaryArguments(
157157
It.IsRegex("-localIdentifier.*dummyIdentifier.*dummyHost.*-proxyHost.*dummyHost.*-proxyPort.*dummyPort.*-proxyUser.*dummyUser.*-proxyPass.*dummyPass.*")
158158
), Times.Once());
@@ -171,11 +171,11 @@ public void TestWorksWithCustomOptions()
171171
options.Add(new KeyValuePair<string, string>("customKey2", "customValue2"));
172172

173173
local = new LocalClass();
174-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
174+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
175175
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
176176
local.setTunnel(tunnelMock.Object);
177177
local.start(options);
178-
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
178+
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
179179
tunnelMock.Verify(mock => mock.addBinaryArguments(
180180
It.IsRegex("-customBoolKey1.*-customBoolKey2.*-customKey1.*customValue1.*-customKey2.*customValue2.*")
181181
), Times.Once());
@@ -191,7 +191,7 @@ public void TestCallsFallbackOnFailure()
191191

192192
local = new LocalClass();
193193
int count = 0;
194-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
194+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
195195
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start")).Callback(() =>
196196
{
197197
count++;
@@ -200,7 +200,7 @@ public void TestCallsFallbackOnFailure()
200200
});
201201
local.setTunnel(tunnelMock.Object);
202202
local.start(options);
203-
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
203+
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
204204
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
205205
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Exactly(2));
206206
tunnelMock.Verify(mock => mock.fallbackPaths(), Times.Once());
@@ -214,16 +214,65 @@ public void TestKillsTunnel()
214214
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
215215

216216
local = new LocalClass();
217-
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>();
217+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
218218
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
219219
local.setTunnel(tunnelMock.Object);
220220
local.start(options);
221221
local.stop();
222-
tunnelMock.Verify(mock => mock.addBinaryPath("", ""), Times.Once);
222+
tunnelMock.Verify(mock => mock.addBinaryPath("", "", It.IsAny<bool>(), It.IsAny<Exception>()), Times.Once);
223223
tunnelMock.Verify(mock => mock.addBinaryArguments(It.IsRegex("-logFile \"" + logAbsolute + "\" .*")), Times.Once());
224224
tunnelMock.Verify(mock => mock.Run("dummyKey", "", logAbsolute, "start"), Times.Once());
225225
}
226226

227+
[TestMethod]
228+
public void TestSetProxyCalledWithProxyOptions()
229+
{
230+
options = new List<KeyValuePair<string, string>>();
231+
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
232+
options.Add(new KeyValuePair<string, string>("proxyHost", "proxy.example.com"));
233+
options.Add(new KeyValuePair<string, string>("proxyPort", "8080"));
234+
235+
local = new LocalClass();
236+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
237+
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
238+
local.setTunnel(tunnelMock.Object);
239+
local.start(options);
240+
tunnelMock.Verify(mock => mock.SetProxy("proxy.example.com", 8080), Times.Once);
241+
local.stop();
242+
}
243+
244+
[TestMethod]
245+
public void TestSetProxyCalledWithDefaultsWhenAbsent()
246+
{
247+
options = new List<KeyValuePair<string, string>>();
248+
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
249+
250+
local = new LocalClass();
251+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
252+
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
253+
local.setTunnel(tunnelMock.Object);
254+
local.start(options);
255+
tunnelMock.Verify(mock => mock.SetProxy(null, 0), Times.Once);
256+
local.stop();
257+
}
258+
259+
[TestMethod]
260+
public void TestSetProxyIgnoresInvalidPort()
261+
{
262+
options = new List<KeyValuePair<string, string>>();
263+
options.Add(new KeyValuePair<string, string>("key", "dummyKey"));
264+
options.Add(new KeyValuePair<string, string>("proxyHost", "proxy.example.com"));
265+
options.Add(new KeyValuePair<string, string>("proxyPort", "not-a-number"));
266+
267+
local = new LocalClass();
268+
Mock<BrowserStackTunnel> tunnelMock = new Mock<BrowserStackTunnel>("test-user-agent");
269+
tunnelMock.Setup(mock => mock.Run("dummyKey", "", logAbsolute, "start"));
270+
local.setTunnel(tunnelMock.Object);
271+
local.start(options);
272+
tunnelMock.Verify(mock => mock.SetProxy("proxy.example.com", 0), Times.Once);
273+
local.stop();
274+
}
275+
227276
public void startWithOptions()
228277
{
229278
local.start(options);

BrowserStackLocal/BrowserStackLocal/BrowserStackLocal.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
<PropertyGroup>
33
<RootNamespace>BrowserStack</RootNamespace>
44
<AssemblyName>BrowserStackLocal</AssemblyName>
5-
<TargetFramework>net7.0</TargetFramework>
5+
<TargetFramework>net6.0</TargetFramework>
66
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
77
<Title>BrowserStackLocal</Title>
88
<Product>BrowserStackLocal</Product>
99
<Description>C# Bindings for BrowserStack Local</Description>
10-
<Version>3.0.0</Version>
11-
<AssemblyVersion>3.0.0</AssemblyVersion>
12-
<FileVersion>3.0.0</FileVersion>
10+
<Version>3.1.0</Version>
11+
<AssemblyVersion>3.1.0</AssemblyVersion>
12+
<FileVersion>3.1.0</FileVersion>
1313
<Authors>BrowserStack</Authors>
1414
<Company>BrowserStack</Company>
1515
<Copyright>Copyright © 2016</Copyright>

BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.Net.Http;
1414
using System.Threading.Tasks;
15+
using System.Runtime.InteropServices;
1516
using Newtonsoft.Json;
1617

1718
namespace BrowserStack
@@ -28,6 +29,12 @@ public class BrowserStackTunnel : IDisposable
2829
private bool isFallbackEnabled = false;
2930
private Exception downloadFailureException = null;
3031

32+
// Set via SetProxy(...) before fetchSourceUrl / downloadBinary; otherwise the
33+
// binary download bypasses the user's proxy even when -proxyHost is passed
34+
// through to the running binary via argumentString.
35+
private string proxyHost = null;
36+
private int proxyPort = 0;
37+
3138
static readonly string homepath = !IsWindows() ?
3239
Environment.GetFolderPath(Environment.SpecialFolder.Personal) :
3340
Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
@@ -77,13 +84,14 @@ static bool IsAlpine()
7784
return false;
7885
}
7986

80-
static string GetBinaryName()
87+
public static string GetBinaryName()
8188
{
8289
if (IsWindows()) return "BrowserStackLocal.exe";
8390
if (IsDarwin(uname)) return "BrowserStackLocal-darwin-x64";
8491

8592
if (IsLinux(uname))
8693
{
94+
if (IsArm64()) return "BrowserStackLocal-linux-arm64";
8795
if (Util.Is64BitOS())
8896
{
8997
return IsAlpine() ? "BrowserStackLocal-alpine" : "BrowserStackLocal-linux-x64";
@@ -94,6 +102,17 @@ static string GetBinaryName()
94102
return "BrowserStackLocal.exe";
95103
}
96104

105+
static bool IsArm64()
106+
{
107+
return RuntimeInformation.OSArchitecture == Architecture.Arm64;
108+
}
109+
110+
public virtual void SetProxy(string host, int port)
111+
{
112+
proxyHost = host;
113+
proxyPort = port;
114+
}
115+
97116
public virtual void addBinaryPath(string binaryAbsolute, string accessKey, bool fallbackEnabled = false, Exception failureException = null)
98117
{
99118
if (basePathsIndex == -1)
@@ -169,7 +188,14 @@ private string fetchSourceUrl(string accessKey)
169188
{
170189
var url = "https://local.browserstack.com/binary/api/v1/endpoint";
171190

172-
using (var client = new HttpClient())
191+
HttpClientHandler handler = new HttpClientHandler();
192+
if (!string.IsNullOrEmpty(proxyHost) && proxyPort > 0)
193+
{
194+
handler.Proxy = new WebProxy(proxyHost, proxyPort);
195+
handler.UseProxy = true;
196+
}
197+
198+
using (var client = new HttpClient(handler))
173199
{
174200
var data = new Dictionary<string, object>
175201
{
@@ -216,6 +242,11 @@ public void downloadBinary()
216242

217243
using (var client = new WebClient())
218244
{
245+
client.Headers.Add(HttpRequestHeader.UserAgent, userAgent);
246+
if (!string.IsNullOrEmpty(proxyHost) && proxyPort > 0)
247+
{
248+
client.Proxy = new WebProxy(proxyHost, proxyPort);
249+
}
219250
client.DownloadFile(sourceUrl + "/" + binaryName, this.binaryAbsolute);
220251
}
221252

BrowserStackLocal/BrowserStackLocal/Local.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public class Local
1717
private string userAgent = "browserstack-local-csharp";
1818
private bool isFallbackEnabled = false;
1919
private Exception downloadFailureException = null;
20+
private string proxyHost = null;
21+
private int proxyPort = 0;
2022

2123
protected BrowserStackTunnel tunnel = null;
2224
private static KeyValuePair<string, string> emptyStringPair = new KeyValuePair<string, string>();
@@ -75,6 +77,18 @@ private void addArgs(string key, string value)
7577
}
7678
else
7779
{
80+
if (key.Equals("proxyHost"))
81+
{
82+
proxyHost = value;
83+
}
84+
else if (key.Equals("proxyPort"))
85+
{
86+
if (!int.TryParse(value, out proxyPort))
87+
{
88+
Console.Error.WriteLine($"Invalid proxyPort '{value}'; ignoring proxy for binary download");
89+
}
90+
}
91+
7892
result = valueCommands.Find(pair => pair.Key == key);
7993
if (!result.Equals(emptyStringPair))
8094
{
@@ -226,6 +240,7 @@ public void start(List<KeyValuePair<string, string>> options)
226240
argumentString += "-logFile \"" + customLogPath + "\" ";
227241
argumentString += "--source \"c-sharp:" + bindingVersion + "\" ";
228242
tunnel.addBinaryArguments(argumentString);
243+
tunnel.SetProxy(proxyHost, proxyPort);
229244

230245
DownloadVerifyAndRunBinary();
231246
}

0 commit comments

Comments
 (0)