Skip to content

Commit 0c5e193

Browse files
committed
improve test execution speed on macOS
1 parent b81248c commit 0c5e193

20 files changed

Lines changed: 484 additions & 481 deletions

‎.github/workflows/test-packages.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ jobs:
6565
extra_options_common: '/p:TestReleasedPackage=true'
6666
verbose: ${{ inputs.verbose == 'true' }}
6767
dotnet_sdk_version: ${{ inputs.dotnet_sdk_version == '' && '10.0.100' || inputs.dotnet_sdk_version }}
68-
timeout_minutes_test_job: 25 # Smdn.TPSmartHomeDevices.Tapo on macos-13 may exceed the 20 minutes limit.
69-
timeout_hang: "[ {'OS':'windows','Timeout':'6min'}, {'OS':'macos','Timeout':'10min'}, {'OS':'','Timeout':'1min'} ]"
68+
timeout_minutes_test_job: 10
69+
timeout_hang: "[ {'OS':'windows','Timeout':'6min'}, {'OS':'macos','Timeout':'6min'}, {'OS':'','Timeout':'1min'} ]"
7070
timeout_vstest_connection: "[ {'OS':'windows','Timeout':360}, {'OS':'macos','Timeout':180}, {'OS':'','Timeout':60} ]"
7171
dotnet_test_options_verbosity_level: ${{ inputs.dotnet_test_options_verbosity_level }}
7272
dotnet_test_options_framework: ${{ inputs.dotnet_test_options_framework }}

‎.github/workflows/test.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ jobs:
7373
os: ${{ inputs.os == '' && 'ubuntu-24.04, ubuntu-22.04, windows-latest, macos-latest' || inputs.os }}
7474
verbose: ${{ inputs.verbose == 'true' }}
7575
dotnet_sdk_version: ${{ inputs.dotnet_sdk_version == '' && '10.0.100' || inputs.dotnet_sdk_version }}
76-
timeout_minutes_test_job: 25 # Smdn.TPSmartHomeDevices.Tapo on macos-13 may exceed the 20 minutes limit.
77-
timeout_hang: "[ {'OS':'windows','Timeout':'6min'}, {'OS':'macos','Timeout':'10min'}, {'OS':'','Timeout':'1min'} ]"
76+
timeout_minutes_test_job: 10
77+
timeout_hang: "[ {'OS':'windows','Timeout':'6min'}, {'OS':'macos','Timeout':'6min'}, {'OS':'','Timeout':'1min'} ]"
7878
timeout_vstest_connection: "[ {'OS':'windows','Timeout':360}, {'OS':'macos','Timeout':180}, {'OS':'','Timeout':60} ]"
7979
extra_options_common: ${{ inputs.extra_options_common }}
8080
dotnet_test_options_verbosity_level: ${{ inputs.dotnet_test_options_verbosity_level }}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-FileCopyrightText: 2025 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
using System;
4+
using System.Net;
5+
using System.Security.Cryptography;
6+
using System.Text.Json;
7+
using System.Threading.Tasks;
8+
9+
using NUnit.Framework;
10+
11+
namespace Smdn.TPSmartHomeDevices.Tapo.Protocol;
12+
13+
[SetUpFixture]
14+
internal class CommonPseudoTapoDevice {
15+
public static PseudoTapoDevice Instance {
16+
get => field ?? throw new InvalidCastException("CommonPseudoTapoDevice is not set up yet.");
17+
private set;
18+
}
19+
20+
public static PseudoTapoDevice Configure(
21+
Action<HttpListenerContext>? funcProcessRequest = null,
22+
Func<PseudoTapoDevice.SessionBase, string?>? funcGenerateToken = null,
23+
Func<PseudoTapoDevice.SessionBase, RSA, HandshakeResponse>? funcGenerateHandshakeResponse = null,
24+
Func<PseudoTapoDevice.SessionBase, string?>? funcGenerateCookieValue = null,
25+
Func<PseudoTapoDevice.SessionBase, JsonElement, LoginDeviceResponse>? funcGenerateLoginDeviceResponse = null,
26+
Func<PseudoTapoDevice.SessionBase, string, JsonElement, (int, ITapoPassThroughResponse?)>? funcGeneratePassThroughResponse = null,
27+
Action<IPEndPoint, Memory<byte>, Memory<byte>>? funcGenerateKlapAuthHash = null,
28+
Func<PseudoTapoDevice.SessionBase, (HttpStatusCode, string)>? funcGenerateKlapHandshake2Response = null,
29+
Func<PseudoTapoDevice.SessionBase, JsonDocument, int, object?>? funcGenerateKlapRequestResponse = null
30+
)
31+
{
32+
Instance.ClearSessions();
33+
Instance.FuncProcessRequest = funcProcessRequest;
34+
Instance.FuncGenerateToken = funcGenerateToken;
35+
Instance.FuncGenerateHandshakeResponse = funcGenerateHandshakeResponse;
36+
Instance.FuncGenerateCookieValue = funcGenerateCookieValue;
37+
Instance.FuncGenerateLoginDeviceResponse = funcGenerateLoginDeviceResponse;
38+
Instance.FuncGeneratePassThroughResponse = funcGeneratePassThroughResponse;
39+
Instance.FuncGenerateKlapAuthHash = funcGenerateKlapAuthHash;
40+
Instance.FuncGenerateKlapHandshake2Response = funcGenerateKlapHandshake2Response;
41+
Instance.FuncGenerateKlapRequestResponse = funcGenerateKlapRequestResponse;
42+
43+
return Instance;
44+
}
45+
46+
[OneTimeSetUp]
47+
public void SetUp()
48+
{
49+
Instance = new PseudoTapoDevice();
50+
51+
Instance.Start();
52+
}
53+
54+
[OneTimeTearDown]
55+
public async Task TearDown()
56+
{
57+
await Instance.DisposeAsync().ConfigureAwait(false);
58+
59+
Instance = null!;
60+
}
61+
}

‎tests/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/PseudoTapoDevice.cs‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ private static string CreateEndPointHttpUrl(IPEndPoint endPoint)
130130
? $"http://[{endPoint.Address}]:{endPoint.Port}/"
131131
: $"http://{endPoint.Address}:{endPoint.Port}/";
132132

133-
public object? State { get; }
133+
public object? State { get; set; }
134134
public IPEndPoint? EndPoint { get; private set; }
135135
public Uri? EndPointUri => EndPoint is null ? null : new Uri(CreateEndPointHttpUrl(EndPoint));
136136
private HttpListener? listener;
@@ -198,6 +198,15 @@ public void Dispose()
198198
taskProcessListener = null;
199199
}
200200

201+
public void ClearSessions()
202+
{
203+
unauthorizedSessions.Clear();
204+
authorizedSessions.Clear();
205+
206+
unauthorizedKlapSessions.Clear();
207+
authorizedKlapSessions.Clear();
208+
}
209+
201210
public EndPoint Start(
202211
int? exceptPort = 0
203212
)
@@ -247,6 +256,9 @@ public EndPoint Start(
247256
public IDeviceEndPoint GetEndPoint()
248257
=> new StaticDeviceEndPoint(EndPoint ?? throw new InvalidOperationException("not started"));
249258

259+
public EndPoint GetListenerEndPoint()
260+
=> EndPoint ?? throw new InvalidOperationException("not started");
261+
250262
private async Task ProcessListenerAsync()
251263
{
252264
for (; ; ) {
@@ -392,6 +404,7 @@ private async Task ProcessRequestAsync(HttpListenerContext context)
392404
// validate request method
393405
if (!string.Equals("POST", request.HttpMethod, StringComparison.Ordinal)) {
394406
response.KeepAlive = false;
407+
response.Headers.Add("Connection", "close");
395408

396409
await WriteBadRequestPlainTextContentAsync(
397410
response,
@@ -429,6 +442,7 @@ await WriteBadRequestPlainTextContentAsync(
429442
}
430443

431444
response.KeepAlive = false;
445+
response.Headers.Add("Connection", "close");
432446

433447
await WriteBadRequestPlainTextContentAsync(
434448
response,
@@ -437,7 +451,9 @@ await WriteBadRequestPlainTextContentAsync(
437451
}
438452
finally {
439453
try {
454+
context?.Response?.OutputStream?.Flush();
440455
context?.Response?.OutputStream?.Close();
456+
context?.Response?.Close();
441457
}
442458
catch (InvalidOperationException) when (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
443459
// swallow

‎tests/Smdn.TPSmartHomeDevices.Tapo/Smdn.TPSmartHomeDevices.Tapo.Protocol/TapoClient.Authentication.KLAP.cs‎

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ partial class TapoClientTests {
1515
[Test]
1616
public async Task AuthenticateAsync_KLAP()
1717
{
18-
await using var device = new PseudoTapoDevice() {
19-
FuncGenerateKlapAuthHash = (_, _, authHash) => defaultKlapCredentialProvider!.GetKlapCredential(null).WriteLocalAuthHash(authHash.Span),
20-
};
21-
var endPoint = device.Start();
18+
var device = CommonPseudoTapoDevice.Configure(
19+
funcGenerateKlapAuthHash: (_, _, authHash) => defaultKlapCredentialProvider!.GetKlapCredential(null).WriteLocalAuthHash(authHash.Span)
20+
);
2221

2322
using var client = new TapoClient(
24-
endPoint: endPoint
23+
endPoint: device.GetListenerEndPoint()
2524
);
2625

2726
Assert.DoesNotThrowAsync(
@@ -44,13 +43,12 @@ public async Task AuthenticateAsync_KLAP()
4443
[Test]
4544
public async Task AuthenticateAsync_KLAP_Handshake1_AuthHashMismatch()
4645
{
47-
await using var device = new PseudoTapoDevice() {
48-
FuncGenerateKlapAuthHash = (_, _, authHash) => authHash.Span.Clear()
49-
};
50-
var endPoint = device.Start();
46+
var device = CommonPseudoTapoDevice.Configure(
47+
funcGenerateKlapAuthHash: (_, _, authHash) => authHash.Span.Clear()
48+
);
5149

5250
using var client = new TapoClient(
53-
endPoint: endPoint
51+
endPoint: device.GetListenerEndPoint()
5452
);
5553

5654
var ex = Assert.ThrowsAsync<TapoAuthenticationException>(
@@ -72,14 +70,13 @@ public async Task AuthenticateAsync_KLAP_Handshake2_HttpNonSuccessStatusCode()
7270
{
7371
const HttpStatusCode Handshake2NonSuccessStatusCode = HttpStatusCode.Unauthorized;
7472

75-
await using var device = new PseudoTapoDevice() {
76-
FuncGenerateKlapAuthHash = (_, _, authHash) => defaultKlapCredentialProvider!.GetKlapCredential(null).WriteLocalAuthHash(authHash.Span),
77-
FuncGenerateKlapHandshake2Response = _ => (Handshake2NonSuccessStatusCode, Handshake2NonSuccessStatusCode.ToString()),
78-
};
79-
var endPoint = device.Start();
73+
var device = CommonPseudoTapoDevice.Configure(
74+
funcGenerateKlapAuthHash: (_, _, authHash) => defaultKlapCredentialProvider!.GetKlapCredential(null).WriteLocalAuthHash(authHash.Span),
75+
funcGenerateKlapHandshake2Response: _ => (Handshake2NonSuccessStatusCode, Handshake2NonSuccessStatusCode.ToString())
76+
);
8077

8178
using var client = new TapoClient(
82-
endPoint: endPoint
79+
endPoint: device.GetListenerEndPoint()
8380
);
8481

8582
var ex = Assert.ThrowsAsync<TapoAuthenticationException>(
@@ -113,13 +110,12 @@ public async Task AuthenticateAsync_KLAP_IdentifyCredentialForIdentity(
113110
Type? typeOfExpectedException
114111
)
115112
{
116-
await using var device = new PseudoTapoDevice() {
117-
FuncGenerateKlapAuthHash = (_, _, authHash) => credentialForKlapAuthHash.WriteLocalAuthHash(authHash.Span)
118-
};
119-
var endPoint = device.Start();
113+
var device = CommonPseudoTapoDevice.Configure(
114+
funcGenerateKlapAuthHash: (_, _, authHash) => credentialForKlapAuthHash.WriteLocalAuthHash(authHash.Span)
115+
);
120116

121117
using var client = new TapoClient(
122-
endPoint: endPoint
118+
endPoint: device.GetListenerEndPoint()
123119
);
124120

125121
await Assert.ThatAsync(

0 commit comments

Comments
 (0)