-
Notifications
You must be signed in to change notification settings - Fork 31
Add ADB device tracking (host:track-devices) for real-time device monitoring #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,189 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Collections.Generic; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Diagnostics; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.IO; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Linq; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Net.Sockets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Text; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Threading; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System.Threading.Tasks; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Xamarin.Android.Tools; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Monitors ADB device connections in real-time via the <c>host:track-devices-l</c> socket protocol. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Pushes device list updates through a callback whenever devices connect, disconnect, or change state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public sealed class AdbDeviceTracker : IDisposable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly int port; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly Action<TraceLevel, string> logger; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly string? adbPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly IDictionary<string, string>? environmentVariables; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IReadOnlyList<AdbDeviceInfo> currentDevices = Array.Empty<AdbDeviceInfo> (); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CancellationTokenSource? trackingCts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool disposed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a new AdbDeviceTracker. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="adbPath">Optional path to the adb executable for starting the server if needed.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="port">ADB daemon port (default 5037).</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="environmentVariables">Optional environment variables for adb processes.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="logger">Optional logger callback.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AdbDeviceTracker (string? adbPath = null, int port = 5037, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IDictionary<string, string>? environmentVariables = null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<TraceLevel, string>? logger = null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (port <= 0 || port > 65535) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ArgumentOutOfRangeException (nameof (port), "Port must be between 1 and 65535."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.adbPath = adbPath; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.port = port; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.environmentVariables = environmentVariables; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.logger = logger ?? RunnerDefaults.NullLogger; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+46
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Current snapshot of tracked devices. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public IReadOnlyList<AdbDeviceInfo> CurrentDevices => currentDevices; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Starts tracking device changes. Calls <paramref name="onDevicesChanged"/> whenever | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// the device list changes. Blocks until cancelled or disposed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Automatically reconnects on connection drops with exponential backoff. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="onDevicesChanged">Callback invoked with the updated device list on each change.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Token to stop tracking.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public async Task StartAsync ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Action<IReadOnlyList<AdbDeviceInfo>> onDevicesChanged, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CancellationToken cancellationToken = default) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (onDevicesChanged == null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ArgumentNullException (nameof (onDevicesChanged)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (disposed) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ObjectDisposedException (nameof (AdbDeviceTracker)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trackingCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var token = trackingCts.Token; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var backoffMs = InitialBackoffMs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (!token.IsCancellationRequested) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await TrackDevicesAsync (onDevicesChanged, token).ConfigureAwait (false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (OperationCanceledException) when (token.IsCancellationRequested) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception ex) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.Invoke (TraceLevel.Warning, $"ADB tracking connection lost: {ex.Message}. Reconnecting in {backoffMs}ms..."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Task.Delay (backoffMs, token).ConfigureAwait (false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (OperationCanceledException) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backoffMs = Math.Min (backoffMs * 2, MaxBackoffMs); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Reset backoff on clean connection | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backoffMs = InitialBackoffMs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+90
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trackingCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); | |
| var token = trackingCts.Token; | |
| var backoffMs = InitialBackoffMs; | |
| while (!token.IsCancellationRequested) { | |
| try { | |
| await TrackDevicesAsync (onDevicesChanged, token).ConfigureAwait (false); | |
| } catch (OperationCanceledException) when (token.IsCancellationRequested) { | |
| break; | |
| } catch (Exception ex) { | |
| logger.Invoke (TraceLevel.Warning, $"ADB tracking connection lost: {ex.Message}. Reconnecting in {backoffMs}ms..."); | |
| try { | |
| await Task.Delay (backoffMs, token).ConfigureAwait (false); | |
| } catch (OperationCanceledException) { | |
| break; | |
| } | |
| backoffMs = Math.Min (backoffMs * 2, MaxBackoffMs); | |
| continue; | |
| } | |
| // Reset backoff on clean connection | |
| backoffMs = InitialBackoffMs; | |
| var linkedCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); | |
| if (Interlocked.CompareExchange (ref trackingCts, linkedCts, null) != null) { | |
| linkedCts.Dispose (); | |
| throw new InvalidOperationException ("Device tracking has already been started."); | |
| } | |
| try { | |
| var token = linkedCts.Token; | |
| var backoffMs = InitialBackoffMs; | |
| while (!token.IsCancellationRequested) { | |
| try { | |
| await TrackDevicesAsync (onDevicesChanged, token).ConfigureAwait (false); | |
| } catch (OperationCanceledException) when (token.IsCancellationRequested) { | |
| break; | |
| } catch (Exception ex) { | |
| logger.Invoke (TraceLevel.Warning, $"ADB tracking connection lost: {ex.Message}. Reconnecting in {backoffMs}ms..."); | |
| try { | |
| await Task.Delay (backoffMs, token).ConfigureAwait (false); | |
| } catch (OperationCanceledException) { | |
| break; | |
| } | |
| backoffMs = Math.Min (backoffMs * 2, MaxBackoffMs); | |
| continue; | |
| } | |
| // Reset backoff on clean connection | |
| backoffMs = InitialBackoffMs; | |
| } | |
| } finally { | |
| Interlocked.CompareExchange (ref trackingCts, null, linkedCts); | |
| linkedCts.Dispose (); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using System.Text; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Xamarin.Android.Tools.Tests; | ||
|
|
||
| [TestFixture] | ||
| public class AdbDeviceTrackerTests | ||
| { | ||
| [Test] | ||
| public void Constructor_InvalidPort_ThrowsArgumentOutOfRangeException () | ||
| { | ||
| Assert.Throws<ArgumentOutOfRangeException> (() => new AdbDeviceTracker (port: 0)); | ||
| Assert.Throws<ArgumentOutOfRangeException> (() => new AdbDeviceTracker (port: -1)); | ||
| Assert.Throws<ArgumentOutOfRangeException> (() => new AdbDeviceTracker (port: 70000)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Constructor_ValidPort_Succeeds () | ||
| { | ||
| using var tracker = new AdbDeviceTracker (port: 5037); | ||
| Assert.IsNotNull (tracker); | ||
| Assert.AreEqual (0, tracker.CurrentDevices.Count); | ||
| } | ||
|
|
||
| [Test] | ||
| public void StartAsync_NullCallback_ThrowsArgumentNullException () | ||
| { | ||
| using var tracker = new AdbDeviceTracker (); | ||
| Assert.ThrowsAsync<ArgumentNullException> (() => tracker.StartAsync (null!)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void StartAsync_AfterDispose_ThrowsObjectDisposedException () | ||
| { | ||
| var tracker = new AdbDeviceTracker (); | ||
| tracker.Dispose (); | ||
| Assert.ThrowsAsync<ObjectDisposedException> (() => tracker.StartAsync (_ => { })); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Dispose_MultipleTimes_DoesNotThrow () | ||
| { | ||
| var tracker = new AdbDeviceTracker (); | ||
| tracker.Dispose (); | ||
| Assert.DoesNotThrow (() => tracker.Dispose ()); | ||
| } | ||
|
|
||
| // --- TryReadLengthPrefixedAsync tests --- | ||
|
|
||
| [Test] | ||
| public async Task TryReadLengthPrefixedAsync_ValidPayload () | ||
| { | ||
| var payload = "emulator-5554\tdevice\n"; | ||
| var hex = payload.Length.ToString ("x4"); | ||
| var data = Encoding.ASCII.GetBytes (hex + payload); | ||
| using var stream = new MemoryStream (data); | ||
|
|
||
| var result = await AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None); | ||
| Assert.AreEqual (payload, result); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task TryReadLengthPrefixedAsync_EmptyPayload () | ||
| { | ||
| var data = Encoding.ASCII.GetBytes ("0000"); | ||
| using var stream = new MemoryStream (data); | ||
|
|
||
| var result = await AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None); | ||
| Assert.AreEqual (string.Empty, result); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task TryReadLengthPrefixedAsync_EndOfStream_ReturnsNull () | ||
| { | ||
| using var stream = new MemoryStream (Array.Empty<byte> ()); | ||
|
|
||
| var result = await AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None); | ||
| Assert.IsNull (result); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task TryReadLengthPrefixedAsync_MultipleDevices () | ||
| { | ||
| var payload = | ||
| "0A041FDD400327\tdevice product:redfin model:Pixel_5 device:redfin transport_id:2\n" + | ||
| "emulator-5554\tdevice product:sdk_gphone64_x86_64 model:sdk_gphone64_x86_64 device:emu64xa transport_id:1\n"; | ||
| var hex = payload.Length.ToString ("x4"); | ||
| var data = Encoding.ASCII.GetBytes (hex + payload); | ||
| using var stream = new MemoryStream (data); | ||
|
|
||
| var result = await AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None); | ||
| Assert.IsNotNull (result); | ||
|
|
||
| var devices = AdbRunner.ParseAdbDevicesOutput (result!.Split ('\n')); | ||
| Assert.AreEqual (2, devices.Count); | ||
| Assert.AreEqual ("0A041FDD400327", devices [0].Serial); | ||
| Assert.AreEqual ("emulator-5554", devices [1].Serial); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TryReadLengthPrefixedAsync_InvalidHex_ThrowsFormatException () | ||
| { | ||
| var data = Encoding.ASCII.GetBytes ("ZZZZ"); | ||
| using var stream = new MemoryStream (data); | ||
|
|
||
| Assert.ThrowsAsync<FormatException> ( | ||
| () => AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None)); | ||
| } | ||
|
|
||
| [Test] | ||
| public void TryReadLengthPrefixedAsync_TruncatedPayload_ThrowsIOException () | ||
| { | ||
| // Header says 100 bytes but only 5 are present | ||
| var data = Encoding.ASCII.GetBytes ("0064hello"); | ||
| using var stream = new MemoryStream (data); | ||
|
|
||
| Assert.ThrowsAsync<IOException> ( | ||
| () => AdbDeviceTracker.TryReadLengthPrefixedAsync (stream, CancellationToken.None)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using System.Linq;is not used in this file and will trigger CS8019 (and can fail Release builds when warnings are treated as errors). Remove the unnecessary using.