Skip to content

Commit 8c2f906

Browse files
JusterZhuclaude
andauthored
refactor(Bowl): remove IPC dependency, simplify API, drop macOS (#491)
Breaking changes: - Remove Bowl 4-param internal constructor; only 1 public paramless constructor - Remove Bowl.CreateParameter() and Bowl.MapToContext() (IPC-based entry points) - Remove SetVariable('UpgradeFail') step from crash pipeline (was auto-deleted on read) - BowlContext no longer depends on MonitorParameter - Crash report JSON uses flat fields instead of nested MonitorParameter Removed modules: - Ipc/ (Environments, IpcEncryption) — duplicate of Core's IPC - Configuration/ProcessContract — Bowl-specific, unused without IPC - Internal/EnvironmentProvider (IEnvironmentProvider) — no longer needed - Internal/LinuxSystem — only used by deleted LinuxStrategy - Strategies/MacBowlStrategy — lldb unusable in production (SIP/task_for_pid) - Strategys/ (MonitorParameter, AbstractStrategy, IStrategy, WindowStrategy, LinuxStrategy) — legacy synchronous API, all [Obsolete] Linux improvements: - Check PATH for procdump before attempting sudo install - Auto-detect PID (-p) vs process name (-w) for procdump flag - Write bowl_linux_unsupported.txt in fail directory for unsupported distros API surface cleanup: - TextTraceListener, WindowsOutputDebugListener: public -> internal - StorageHelper: public -> internal Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2f61464 commit 8c2f906

41 files changed

Lines changed: 387 additions & 2504 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/c#/BowlTest/BowlTests.cs

Lines changed: 6 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using GeneralUpdate.Bowl;
33
using GeneralUpdate.Bowl.Internal;
44
using GeneralUpdate.Bowl.Strategies;
5-
using GeneralUpdate.Bowl.Strategys;
65
using BowlTest.Utilities;
76

87
/// <summary>
@@ -16,27 +15,22 @@
1615
/// - HandleCrashAsync: skip restore when conditions not met
1716
/// - HandleCrashAsync: OnCrash callback with valid path -> invoked
1817
/// - HandleCrashAsync: OnCrash callback throws -> swallowed
19-
/// - MapToContext: all fields mapped correctly
20-
/// - CreateParameter: env var not set/empty/whitespace/invalid -> ArgumentNullException
21-
/// - CreateParameter: valid JSON -> correct MonitorParameter
2218
/// </summary>
2319
public class BowlTests
2420
{
2521
private readonly FakeBowlStrategy _strategy;
2622
private readonly FakeCrashReporter _reporter;
2723
private readonly FakeSystemInfoProvider _sysInfo;
28-
private readonly FakeEnvironmentProvider _env;
2924

3025
public BowlTests()
3126
{
3227
_strategy = new FakeBowlStrategy();
3328
_reporter = new FakeCrashReporter();
3429
_sysInfo = new FakeSystemInfoProvider();
35-
_env = new FakeEnvironmentProvider();
3630
}
3731

3832
private Bowl CreateBowl() =>
39-
new Bowl(_strategy, _reporter, _sysInfo, _env);
33+
new Bowl(_strategy, _reporter, _sysInfo);
4034

4135
private static BowlContext CreateValidContext(
4236
string processName = "test.exe",
@@ -70,34 +64,26 @@ public void Constructor_AllArgsValid_DoesNotThrow()
7064
public void Constructor_StrategyNull_ThrowsArgumentNullException()
7165
{
7266
var ex = Assert.Throws<ArgumentNullException>(() =>
73-
new Bowl(null!, _reporter, _sysInfo, _env));
67+
new Bowl(null!, _reporter, _sysInfo));
7468
Assert.Equal("strategy", ex.ParamName);
7569
}
7670

7771
[Fact]
7872
public void Constructor_CrashReporterNull_ThrowsArgumentNullException()
7973
{
8074
var ex = Assert.Throws<ArgumentNullException>(() =>
81-
new Bowl(_strategy, null!, _sysInfo, _env));
75+
new Bowl(_strategy, null!, _sysInfo));
8276
Assert.Equal("crashReporter", ex.ParamName);
8377
}
8478

8579
[Fact]
8680
public void Constructor_SystemInfoProviderNull_ThrowsArgumentNullException()
8781
{
8882
var ex = Assert.Throws<ArgumentNullException>(() =>
89-
new Bowl(_strategy, _reporter, null!, _env));
83+
new Bowl(_strategy, _reporter, null!));
9084
Assert.Equal("systemInfoProvider", ex.ParamName);
9185
}
9286

93-
[Fact]
94-
public void Constructor_EnvNull_ThrowsArgumentNullException()
95-
{
96-
var ex = Assert.Throws<ArgumentNullException>(() =>
97-
new Bowl(_strategy, _reporter, _sysInfo, null!));
98-
Assert.Equal("env", ex.ParamName);
99-
}
100-
10187
// ---- LaunchAsync: Strategy returns null ----
10288

10389
[Fact]
@@ -145,7 +131,6 @@ await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
145131
[Fact]
146132
public async Task LaunchAsync_ProcessTimeout_ReturnsFailedResult()
147133
{
148-
// Use a short timeout to trigger timeout
149134
_strategy.PrepareResult = new ProcessStartInfo
150135
{
151136
FileName = "cmd.exe",
@@ -264,155 +249,6 @@ public async Task LaunchAsync_CrashDetected_HandleCrashInvoked()
264249
}
265250
}
266251

267-
// ---- MapToContext ----
268-
269-
[Fact]
270-
public void MapToContext_ConvertsAllFields_CorrectValues()
271-
{
272-
var param = new MonitorParameter
273-
{
274-
ProcessNameOrId = "myapp.exe",
275-
DumpFileName = "v2_fail.dmp",
276-
FailFileName = "v2_fail.json",
277-
TargetPath = "C:\\app",
278-
FailDirectory = "C:\\app\\fail\\v2",
279-
BackupDirectory = "C:\\app\\v2",
280-
WorkModel = "Normal",
281-
ExtendedField = "2.0.0",
282-
};
283-
284-
var ctx = Bowl.MapToContext(param);
285-
286-
Assert.Equal("myapp.exe", ctx.ProcessNameOrId);
287-
Assert.Equal("v2_fail.dmp", ctx.DumpFileName);
288-
Assert.Equal("v2_fail.json", ctx.FailFileName);
289-
Assert.Equal("C:\\app", ctx.TargetPath);
290-
Assert.Equal("C:\\app\\fail\\v2", ctx.FailDirectory);
291-
Assert.Equal("C:\\app\\v2", ctx.BackupDirectory);
292-
Assert.Equal("Normal", ctx.WorkModel);
293-
Assert.Equal("2.0.0", ctx.ExtendedField);
294-
Assert.Equal(30_000, ctx.TimeoutMs);
295-
Assert.Equal(DumpType.Full, ctx.DumpType);
296-
Assert.True(ctx.AutoRestore);
297-
}
298-
299-
[Fact]
300-
public void MapToContext_TimeoutMsFixedAt30000()
301-
{
302-
var param = new MonitorParameter { ProcessNameOrId = "test" };
303-
var ctx = Bowl.MapToContext(param);
304-
Assert.Equal(30_000, ctx.TimeoutMs);
305-
}
306-
307-
[Fact]
308-
public void MapToContext_DumpTypeFixedAtFull()
309-
{
310-
var param = new MonitorParameter { ProcessNameOrId = "test" };
311-
var ctx = Bowl.MapToContext(param);
312-
Assert.Equal(DumpType.Full, ctx.DumpType);
313-
}
314-
315-
[Fact]
316-
public void MapToContext_AutoRestoreFixedAtTrue()
317-
{
318-
var param = new MonitorParameter { ProcessNameOrId = "test" };
319-
var ctx = Bowl.MapToContext(param);
320-
Assert.True(ctx.AutoRestore);
321-
}
322-
323-
// ---- CreateParameter: Env var not set ----
324-
325-
[Fact]
326-
public void CreateParameter_EnvVarNotSet_ThrowsArgumentNullException()
327-
{
328-
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
329-
try
330-
{
331-
Environment.SetEnvironmentVariable("ProcessInfo", null, EnvironmentVariableTarget.Process);
332-
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
333-
Assert.Contains("ProcessInfo", ex.Message);
334-
}
335-
finally
336-
{
337-
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
338-
}
339-
}
340-
341-
[Fact]
342-
public void CreateParameter_EnvVarEmpty_ThrowsArgumentNullException()
343-
{
344-
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
345-
try
346-
{
347-
Environment.SetEnvironmentVariable("ProcessInfo", string.Empty, EnvironmentVariableTarget.Process);
348-
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
349-
Assert.Contains("ProcessInfo", ex.Message);
350-
}
351-
finally
352-
{
353-
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
354-
}
355-
}
356-
357-
[Fact]
358-
public void CreateParameter_EnvVarWhitespace_ThrowsArgumentNullException()
359-
{
360-
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
361-
try
362-
{
363-
Environment.SetEnvironmentVariable("ProcessInfo", " ", EnvironmentVariableTarget.Process);
364-
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
365-
Assert.Contains("ProcessInfo", ex.Message);
366-
}
367-
finally
368-
{
369-
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
370-
}
371-
}
372-
373-
[Fact]
374-
public void CreateParameter_InvalidJson_ThrowsArgumentNullException()
375-
{
376-
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
377-
try
378-
{
379-
Environment.SetEnvironmentVariable("ProcessInfo", "not valid json", EnvironmentVariableTarget.Process);
380-
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
381-
Assert.Contains("ProcessInfo", ex.Message);
382-
}
383-
finally
384-
{
385-
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
386-
}
387-
}
388-
389-
[Fact]
390-
public void CreateParameter_ValidJson_ReturnsCorrectMonitorParameter()
391-
{
392-
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
393-
var tempDir = Path.Combine(Path.GetTempPath(), $"BowlTest_App_{Guid.NewGuid():N}");
394-
try
395-
{
396-
Environment.SetEnvironmentVariable("ProcessInfo",
397-
$"{{\"AppName\":\"myapp.exe\",\"LastVersion\":\"2.5.0\",\"InstallPath\":\"{tempDir.Replace("\\", "\\\\")}\"}}",
398-
EnvironmentVariableTarget.Process);
399-
400-
var param = Bowl.CreateParameter();
401-
402-
Assert.Equal("myapp.exe", param.ProcessNameOrId);
403-
Assert.Equal("2.5.0_fail.dmp", param.DumpFileName);
404-
Assert.Equal("2.5.0_fail.json", param.FailFileName);
405-
Assert.Equal(tempDir, param.TargetPath);
406-
Assert.Equal("2.5.0", param.ExtendedField);
407-
Assert.Equal(Path.Combine(tempDir, "fail", "2.5.0"), param.FailDirectory);
408-
Assert.Equal(Path.Combine(tempDir, "2.5.0"), param.BackupDirectory);
409-
}
410-
finally
411-
{
412-
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
413-
}
414-
}
415-
416252
// ---- HandleCrashAsync: All steps success ----
417253

418254
[Fact]
@@ -566,11 +402,8 @@ public async Task HandleCrashAsync_SkipRestoreConditions(string workModel, bool
566402
var result = await bowl.LaunchAsync(ctx);
567403

568404
Assert.True(result.DumpCaptured);
569-
// For Normal mode, UpgradeFail should NOT be set
570-
if (workModel != "Upgrade")
571-
{
572-
Assert.False(_env.SetVariableCalled);
573-
}
405+
// Restore should NOT happen for Normal mode or Upgrade with AutoRestore=false
406+
Assert.False(result.Restored);
574407
}
575408
finally
576409
{

src/c#/BowlTest/Internal/CrashJsonContextTests.cs

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,36 @@
11
using System.Text.Json;
22
using System.Text.Json.Serialization.Metadata;
33
using GeneralUpdate.Bowl.Internal;
4-
using GeneralUpdate.Bowl.Strategys;
54

6-
/// <summary>
7-
/// 分支覆盖点:
8-
/// CrashJsonContext 类:
9-
/// - 类型标注为 [JsonSerializable(typeof(Crash))]
10-
/// - 公开 Crash 属性(JsonTypeInfo&lt;Crash&gt;)
11-
/// - 序列化 Crash 对象(含完整数据)
12-
/// - 序列化 Crash 对象(空字段)
13-
/// </summary>
145
public class CrashJsonContextTests
156
{
167
[Fact]
17-
public void Default_Crash属性不为null()
8+
public void Default_CrashPropertyNotNull()
189
{
1910
var ctx = CrashJsonContext.Default;
2011
Assert.NotNull(ctx.Crash);
2112
}
2213

2314
[Fact]
24-
public void Default_Crash属性类型为JsonTypeInfo()
15+
public void Default_CrashPropertyIsJsonTypeInfo()
2516
{
2617
var ctx = CrashJsonContext.Default;
2718
Assert.IsAssignableFrom<JsonTypeInfo<Crash>>(ctx.Crash);
2819
}
2920

3021
[Fact]
31-
public void 序列化完整Crash对象_成功生成JSON()
22+
public void SerializeFullCrash_GeneratesValidJson()
3223
{
3324
var crash = new Crash
3425
{
35-
Parameter = new MonitorParameter
36-
{
37-
ProcessNameOrId = "test.exe",
38-
DumpFileName = "v1_fail.dmp",
39-
FailFileName = "v1_fail.json",
40-
TargetPath = "/app",
41-
FailDirectory = "/app/fail/v1",
42-
BackupDirectory = "/app/v1",
43-
WorkModel = "Upgrade",
44-
ExtendedField = "1.0.0",
45-
},
26+
ProcessNameOrId = "test.exe",
27+
DumpFileName = "v1_fail.dmp",
28+
FailFileName = "v1_fail.json",
29+
TargetPath = "/app",
30+
FailDirectory = "/app/fail/v1",
31+
BackupDirectory = "/app/v1",
32+
WorkModel = "Upgrade",
33+
ExtendedField = "1.0.0",
4634
ProcdumpOutPutLines = new List<string> { "line1", "line2" },
4735
};
4836

@@ -55,7 +43,7 @@ public void 序列化完整Crash对象_成功生成JSON()
5543
}
5644

5745
[Fact]
58-
public void 序列化空字段Crash_生成有效JSON()
46+
public void SerializeEmptyCrash_GeneratesValidJson()
5947
{
6048
var crash = new Crash();
6149
var json = JsonSerializer.Serialize(crash, CrashJsonContext.Default.Crash);
@@ -64,7 +52,7 @@ public void 序列化空字段Crash_生成有效JSON()
6452
}
6553

6654
[Fact]
67-
public void 序列化空列表ProcdumpOutPutLines_生成JSON包含空数组()
55+
public void SerializeEmptyProcdumpOutPutLines_JsonContainsEmptyArray()
6856
{
6957
var crash = new Crash
7058
{
@@ -75,23 +63,20 @@ public void 序列化空列表ProcdumpOutPutLines_生成JSON包含空数组()
7563
}
7664

7765
[Fact]
78-
public void 反序列化_成功还原Crash对象()
66+
public void Deserialize_RestoresCrashCorrectly()
7967
{
8068
var original = new Crash
8169
{
82-
Parameter = new MonitorParameter
83-
{
84-
ProcessNameOrId = "myapp",
85-
WorkModel = "Normal",
86-
},
70+
ProcessNameOrId = "myapp",
71+
WorkModel = "Normal",
8772
ProcdumpOutPutLines = new List<string> { "output1" },
8873
};
8974
var json = JsonSerializer.Serialize(original, CrashJsonContext.Default.Crash);
9075
var deserialized = JsonSerializer.Deserialize(json, CrashJsonContext.Default.Crash);
9176

9277
Assert.NotNull(deserialized);
93-
Assert.Equal("myapp", deserialized!.Parameter!.ProcessNameOrId);
94-
Assert.Equal("Normal", deserialized.Parameter!.WorkModel);
78+
Assert.Equal("myapp", deserialized!.ProcessNameOrId);
79+
Assert.Equal("Normal", deserialized.WorkModel);
9580
Assert.Single(deserialized.ProcdumpOutPutLines!);
9681
Assert.Equal("output1", deserialized.ProcdumpOutPutLines![0]);
9782
}

0 commit comments

Comments
 (0)