Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: CI

on:
push:
branches: [main, dev]
branches: [main]
pull_request:
workflow_dispatch:

Expand Down
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,29 @@ Telnet / 串口见 [`zh/host/Telnet与串口可行性调研.md`](https://github.
### 留在本仓库的文档

`README.md`、`LICENSE`,以及各插件目录下的 `README.md`(该插件自己的实现说明与偏离记录)。

### 测试:不要把 `await` 写进带集合实参的断言里

本仓库跑在 `net11.0` + `LangVersion preview`(C# 的 first-class span)。在这套语义下,
`byte[]` / 集合表达式实参会**隐式转成 `ReadOnlySpan<T>`** 再传进
`Assert.AreSequenceEqual`、`MemoryExtensions.SequenceEqual`、`StartsWith`、`IndexOf` 之类的重载。

于是这一行是错的:

```csharp
Assert.AreSequenceEqual(content, await File.ReadAllBytesAsync(local)); // ❌
```

实参从左往右求值:`content` 先转成 `ReadOnlySpan<byte>`,然后在第二个实参的 `await` 处挂起。
span 是 ref struct,跨不了挂起点 —— 恢复之后拿到的是**空 span**,断言无条件失败。
编译器**不报错也不告警**,只有真正走异步(await 没同步完成)时才现形,
表现为「单跑绿、一起跑红」「本地绿、CI 红」的假不稳定。

正确写法是先把 `await` 落到局部变量:

```csharp
byte[] downloaded = await File.ReadAllBytesAsync(local); // ✅
Assert.AreSequenceEqual(content, downloaded);
```

同一条规则适用于任何 span 接收者或 span 实参的调用:**调用的实参列表里不许出现 `await`**。
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<!-- Apache-2.0,与本仓库 AGPL-3.0 + 商业双许可相容。随插件目录分发。
测试工程也要引它(环回服务器要按收到的原始请求行重算 SigV4),
集中管版本正是为了这两处永远一致。 -->
<PackageVersion Include="AWSSDK.S3" Version="4.0.102.5" />
<PackageVersion Include="AWSSDK.S3" Version="4.0.103" />
</ItemGroup>
<ItemGroup Label="串口插件">
<!-- MIT,微软自家维护,Windows / Linux / macOS 的 RID 原生包齐全。
Expand All @@ -49,7 +49,7 @@
注意:这个包带 runtimes/<rid>/native/,插件目录里必须**有那一层** ——
没有的话 Linux/macOS 上是运行期 PlatformNotSupportedException,
而在 Windows 开发机上永远测不出来(调研文档 §五.2 记的正是这条)。 -->
<PackageVersion Include="System.IO.Ports" Version="11.0.0-preview.7.26381.103" />
<PackageVersion Include="System.IO.Ports" Version="11.0.0-rc.1.26425.128" />
</ItemGroup>
<ItemGroup Label="测试框架">
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
Expand All @@ -58,4 +58,4 @@
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="NSubstitute" Version="6.2.0" />
</ItemGroup>
</Project>
</Project>
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "11.0.100-preview.7.26381.103",
"version": "11.0.100-rc.1.26425.128",
"rollForward": "latestFeature",
"allowPrerelease": true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,8 @@ public void BinaryValue_MalformedEscape_RefusesToWrite()
await PumpAsync();

Assert.Contains("转义写坏了", vm.StatusMessage);
Assert.AreSequenceEqual(original, await ReadRawAsync("bad:blob"), "拒绝写入时服务端的值必须原封不动。");
byte[] stored = await ReadRawAsync("bad:blob");
Assert.AreSequenceEqual(original, stored, "拒绝写入时服务端的值必须原封不动。");
});
}

Expand Down
12 changes: 8 additions & 4 deletions tests/VelaShell.Plugin.Redis.Tests/RedisStoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ public async Task Favorites_RoundTripPerConnection()
await store.SaveFavoritesAsync("redis.example:6379", ["user:1", "lock:a"]);
await store.SaveFavoritesAsync("10.0.0.2:6379", ["other:1"]);

Assert.AreSequenceEqual(["user:1", "lock:a"], [.. (await store.LoadFavoritesAsync("redis.example:6379"))]);
Assert.AreSequenceEqual(["other:1"], [.. (await store.LoadFavoritesAsync("10.0.0.2:6379"))]);
List<string> favorites = [.. await store.LoadFavoritesAsync("redis.example:6379")];
Assert.AreSequenceEqual(["user:1", "lock:a"], favorites);
List<string> otherFavorites = [.. await store.LoadFavoritesAsync("10.0.0.2:6379")];
Assert.AreSequenceEqual(["other:1"], otherFavorites);
}

[TestMethod]
Expand Down Expand Up @@ -59,8 +61,10 @@ public async Task History_IsScopedPerConnection()
await store.AppendHistoryAsync("a:6379", "PING");
await store.AppendHistoryAsync("b:6379", "INFO");

Assert.AreSequenceEqual(["PING"], [.. (await store.LoadHistoryAsync("a:6379"))]);
Assert.AreSequenceEqual(["INFO"], [.. (await store.LoadHistoryAsync("b:6379"))]);
List<string> first = [.. await store.LoadHistoryAsync("a:6379")];
Assert.AreSequenceEqual(["PING"], first);
List<string> second = [.. await store.LoadHistoryAsync("b:6379")];
Assert.AreSequenceEqual(["INFO"], second);
}

[TestMethod]
Expand Down
15 changes: 11 additions & 4 deletions tests/VelaShell.Plugin.S3.Tests/S3FileServiceIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,11 @@ public async Task Download_WritesExactContent()
await _service.DownloadFileAsync(_session, "/test-bucket/data/blob.bin", local,
new SynchronousProgress<RemoteTransferProgress>(progress.Add));

Assert.AreSequenceEqual(content, await File.ReadAllBytesAsync(local));
// 读回的字节先落到局部变量:把 await 写进 Assert.AreSequenceEqual 的实参里,
// 第一个实参会先转成 ReadOnlySpan<byte>,再在 await 处挂起 —— 恢复后那个 span 是空的,
// 断言于是无条件失败。这是 C# preview「first-class span」下的编译器坑,详见 AGENTS.md。
byte[] downloaded = await File.ReadAllBytesAsync(local);
Assert.AreSequenceEqual(content, downloaded);
Assert.AreEqual(progress[^1].TotalBytes, progress[^1].TransferredBytes, "最后一次上报必须是满进度。");
AssertAllRequestsSigned();
}
Expand Down Expand Up @@ -337,7 +341,8 @@ public async Task Download_HeadDenied_StillDownloadsViaGet()
await _service.DownloadFileAsync(_session, "/test-bucket/public/asset.png", local,
new SynchronousProgress<RemoteTransferProgress>(progress.Add));

Assert.AreSequenceEqual(content, await File.ReadAllBytesAsync(local));
byte[] downloaded = await File.ReadAllBytesAsync(local);
Assert.AreSequenceEqual(content, downloaded);
// 总长度只能来自 GET 响应,但进度依然要收在满格上。
Assert.AreEqual(content.Length, progress[^1].TotalBytes);
Assert.AreEqual(progress[^1].TotalBytes, progress[^1].TransferredBytes, "最后一次上报必须是满进度。");
Expand Down Expand Up @@ -365,7 +370,8 @@ public async Task Download_DirectReadDenied_FallsBackToPresignedUrl()
await _service.DownloadFileAsync(_session, "/test-bucket/locked/asset.bin", local,
new SynchronousProgress<RemoteTransferProgress>(progress.Add));

Assert.AreSequenceEqual(content, await File.ReadAllBytesAsync(local));
byte[] downloaded = await File.ReadAllBytesAsync(local);
Assert.AreSequenceEqual(content, downloaded);
Assert.AreEqual(content.Length, progress[^1].TotalBytes);
Assert.AreEqual(progress[^1].TotalBytes, progress[^1].TransferredBytes, "最后一次上报必须是满进度。");
// 预签名那次也必须是签对的:服务器重算签名,对不上会计入 SignatureFailures。
Expand Down Expand Up @@ -430,7 +436,8 @@ public async Task Download_ResumesWithRangeRequest()

await _service.DownloadFileAsync(_session, "/test-bucket/resume.bin", local, resumeOffset: 1000);

Assert.AreSequenceEqual(content, await File.ReadAllBytesAsync(local));
byte[] downloaded = await File.ReadAllBytesAsync(local);
Assert.AreSequenceEqual(content, downloaded);
AssertAllRequestsSigned();
}
finally
Expand Down