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
179 changes: 6 additions & 173 deletions src/c#/BowlTest/BowlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using GeneralUpdate.Bowl;
using GeneralUpdate.Bowl.Internal;
using GeneralUpdate.Bowl.Strategies;
using GeneralUpdate.Bowl.Strategys;
using BowlTest.Utilities;

/// <summary>
Expand All @@ -16,27 +15,22 @@
/// - HandleCrashAsync: skip restore when conditions not met
/// - HandleCrashAsync: OnCrash callback with valid path -> invoked
/// - HandleCrashAsync: OnCrash callback throws -> swallowed
/// - MapToContext: all fields mapped correctly
/// - CreateParameter: env var not set/empty/whitespace/invalid -> ArgumentNullException
/// - CreateParameter: valid JSON -> correct MonitorParameter
/// </summary>
public class BowlTests
{
private readonly FakeBowlStrategy _strategy;
private readonly FakeCrashReporter _reporter;
private readonly FakeSystemInfoProvider _sysInfo;
private readonly FakeEnvironmentProvider _env;

public BowlTests()
{
_strategy = new FakeBowlStrategy();
_reporter = new FakeCrashReporter();
_sysInfo = new FakeSystemInfoProvider();
_env = new FakeEnvironmentProvider();
}

private Bowl CreateBowl() =>
new Bowl(_strategy, _reporter, _sysInfo, _env);
new Bowl(_strategy, _reporter, _sysInfo);

private static BowlContext CreateValidContext(
string processName = "test.exe",
Expand Down Expand Up @@ -70,34 +64,26 @@ public void Constructor_AllArgsValid_DoesNotThrow()
public void Constructor_StrategyNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() =>
new Bowl(null!, _reporter, _sysInfo, _env));
new Bowl(null!, _reporter, _sysInfo));
Assert.Equal("strategy", ex.ParamName);
}

[Fact]
public void Constructor_CrashReporterNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() =>
new Bowl(_strategy, null!, _sysInfo, _env));
new Bowl(_strategy, null!, _sysInfo));
Assert.Equal("crashReporter", ex.ParamName);
}

[Fact]
public void Constructor_SystemInfoProviderNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() =>
new Bowl(_strategy, _reporter, null!, _env));
new Bowl(_strategy, _reporter, null!));
Assert.Equal("systemInfoProvider", ex.ParamName);
}

[Fact]
public void Constructor_EnvNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws<ArgumentNullException>(() =>
new Bowl(_strategy, _reporter, _sysInfo, null!));
Assert.Equal("env", ex.ParamName);
}

// ---- LaunchAsync: Strategy returns null ----

[Fact]
Expand Down Expand Up @@ -145,7 +131,6 @@ await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
[Fact]
public async Task LaunchAsync_ProcessTimeout_ReturnsFailedResult()
{
// Use a short timeout to trigger timeout
_strategy.PrepareResult = new ProcessStartInfo
{
FileName = "cmd.exe",
Expand Down Expand Up @@ -264,155 +249,6 @@ public async Task LaunchAsync_CrashDetected_HandleCrashInvoked()
}
}

// ---- MapToContext ----

[Fact]
public void MapToContext_ConvertsAllFields_CorrectValues()
{
var param = new MonitorParameter
{
ProcessNameOrId = "myapp.exe",
DumpFileName = "v2_fail.dmp",
FailFileName = "v2_fail.json",
TargetPath = "C:\\app",
FailDirectory = "C:\\app\\fail\\v2",
BackupDirectory = "C:\\app\\v2",
WorkModel = "Normal",
ExtendedField = "2.0.0",
};

var ctx = Bowl.MapToContext(param);

Assert.Equal("myapp.exe", ctx.ProcessNameOrId);
Assert.Equal("v2_fail.dmp", ctx.DumpFileName);
Assert.Equal("v2_fail.json", ctx.FailFileName);
Assert.Equal("C:\\app", ctx.TargetPath);
Assert.Equal("C:\\app\\fail\\v2", ctx.FailDirectory);
Assert.Equal("C:\\app\\v2", ctx.BackupDirectory);
Assert.Equal("Normal", ctx.WorkModel);
Assert.Equal("2.0.0", ctx.ExtendedField);
Assert.Equal(30_000, ctx.TimeoutMs);
Assert.Equal(DumpType.Full, ctx.DumpType);
Assert.True(ctx.AutoRestore);
}

[Fact]
public void MapToContext_TimeoutMsFixedAt30000()
{
var param = new MonitorParameter { ProcessNameOrId = "test" };
var ctx = Bowl.MapToContext(param);
Assert.Equal(30_000, ctx.TimeoutMs);
}

[Fact]
public void MapToContext_DumpTypeFixedAtFull()
{
var param = new MonitorParameter { ProcessNameOrId = "test" };
var ctx = Bowl.MapToContext(param);
Assert.Equal(DumpType.Full, ctx.DumpType);
}

[Fact]
public void MapToContext_AutoRestoreFixedAtTrue()
{
var param = new MonitorParameter { ProcessNameOrId = "test" };
var ctx = Bowl.MapToContext(param);
Assert.True(ctx.AutoRestore);
}

// ---- CreateParameter: Env var not set ----

[Fact]
public void CreateParameter_EnvVarNotSet_ThrowsArgumentNullException()
{
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
try
{
Environment.SetEnvironmentVariable("ProcessInfo", null, EnvironmentVariableTarget.Process);
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
Assert.Contains("ProcessInfo", ex.Message);
}
finally
{
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
}
}

[Fact]
public void CreateParameter_EnvVarEmpty_ThrowsArgumentNullException()
{
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
try
{
Environment.SetEnvironmentVariable("ProcessInfo", string.Empty, EnvironmentVariableTarget.Process);
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
Assert.Contains("ProcessInfo", ex.Message);
}
finally
{
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
}
}

[Fact]
public void CreateParameter_EnvVarWhitespace_ThrowsArgumentNullException()
{
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
try
{
Environment.SetEnvironmentVariable("ProcessInfo", " ", EnvironmentVariableTarget.Process);
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
Assert.Contains("ProcessInfo", ex.Message);
}
finally
{
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
}
}

[Fact]
public void CreateParameter_InvalidJson_ThrowsArgumentNullException()
{
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
try
{
Environment.SetEnvironmentVariable("ProcessInfo", "not valid json", EnvironmentVariableTarget.Process);
var ex = Assert.Throws<ArgumentNullException>(() => Bowl.CreateParameter());
Assert.Contains("ProcessInfo", ex.Message);
}
finally
{
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
}
}

[Fact]
public void CreateParameter_ValidJson_ReturnsCorrectMonitorParameter()
{
var oldValue = Environment.GetEnvironmentVariable("ProcessInfo", EnvironmentVariableTarget.Process);
var tempDir = Path.Combine(Path.GetTempPath(), $"BowlTest_App_{Guid.NewGuid():N}");
try
{
Environment.SetEnvironmentVariable("ProcessInfo",
$"{{\"AppName\":\"myapp.exe\",\"LastVersion\":\"2.5.0\",\"InstallPath\":\"{tempDir.Replace("\\", "\\\\")}\"}}",
EnvironmentVariableTarget.Process);

var param = Bowl.CreateParameter();

Assert.Equal("myapp.exe", param.ProcessNameOrId);
Assert.Equal("2.5.0_fail.dmp", param.DumpFileName);
Assert.Equal("2.5.0_fail.json", param.FailFileName);
Assert.Equal(tempDir, param.TargetPath);
Assert.Equal("2.5.0", param.ExtendedField);
Assert.Equal(Path.Combine(tempDir, "fail", "2.5.0"), param.FailDirectory);
Assert.Equal(Path.Combine(tempDir, "2.5.0"), param.BackupDirectory);
}
finally
{
Environment.SetEnvironmentVariable("ProcessInfo", oldValue, EnvironmentVariableTarget.Process);
}
}

// ---- HandleCrashAsync: All steps success ----

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

Assert.True(result.DumpCaptured);
// For Normal mode, UpgradeFail should NOT be set
if (workModel != "Upgrade")
{
Assert.False(_env.SetVariableCalled);
}
// Restore should NOT happen for Normal mode or Upgrade with AutoRestore=false
Assert.False(result.Restored);
}
finally
{
Expand Down
51 changes: 18 additions & 33 deletions src/c#/BowlTest/Internal/CrashJsonContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using GeneralUpdate.Bowl.Internal;
using GeneralUpdate.Bowl.Strategys;

/// <summary>
/// 分支覆盖点:
/// CrashJsonContext 类:
/// - 类型标注为 [JsonSerializable(typeof(Crash))]
/// - 公开 Crash 属性(JsonTypeInfo&lt;Crash&gt;)
/// - 序列化 Crash 对象(含完整数据)
/// - 序列化 Crash 对象(空字段)
/// </summary>
public class CrashJsonContextTests
{
[Fact]
public void Default_Crash属性不为null()
public void Default_CrashPropertyNotNull()
{
var ctx = CrashJsonContext.Default;
Assert.NotNull(ctx.Crash);
}

[Fact]
public void Default_Crash属性类型为JsonTypeInfo()
public void Default_CrashPropertyIsJsonTypeInfo()
{
var ctx = CrashJsonContext.Default;
Assert.IsAssignableFrom<JsonTypeInfo<Crash>>(ctx.Crash);
}

[Fact]
public void 序列化完整Crash对象_成功生成JSON()
public void SerializeFullCrash_GeneratesValidJson()
{
var crash = new Crash
{
Parameter = new MonitorParameter
{
ProcessNameOrId = "test.exe",
DumpFileName = "v1_fail.dmp",
FailFileName = "v1_fail.json",
TargetPath = "/app",
FailDirectory = "/app/fail/v1",
BackupDirectory = "/app/v1",
WorkModel = "Upgrade",
ExtendedField = "1.0.0",
},
ProcessNameOrId = "test.exe",
DumpFileName = "v1_fail.dmp",
FailFileName = "v1_fail.json",
TargetPath = "/app",
FailDirectory = "/app/fail/v1",
BackupDirectory = "/app/v1",
WorkModel = "Upgrade",
ExtendedField = "1.0.0",
ProcdumpOutPutLines = new List<string> { "line1", "line2" },
};

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

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

[Fact]
public void 序列化空列表ProcdumpOutPutLines_生成JSON包含空数组()
public void SerializeEmptyProcdumpOutPutLines_JsonContainsEmptyArray()
{
var crash = new Crash
{
Expand All @@ -75,23 +63,20 @@ public void 序列化空列表ProcdumpOutPutLines_生成JSON包含空数组()
}

[Fact]
public void 反序列化_成功还原Crash对象()
public void Deserialize_RestoresCrashCorrectly()
{
var original = new Crash
{
Parameter = new MonitorParameter
{
ProcessNameOrId = "myapp",
WorkModel = "Normal",
},
ProcessNameOrId = "myapp",
WorkModel = "Normal",
ProcdumpOutPutLines = new List<string> { "output1" },
};
var json = JsonSerializer.Serialize(original, CrashJsonContext.Default.Crash);
var deserialized = JsonSerializer.Deserialize(json, CrashJsonContext.Default.Crash);

Assert.NotNull(deserialized);
Assert.Equal("myapp", deserialized!.Parameter!.ProcessNameOrId);
Assert.Equal("Normal", deserialized.Parameter!.WorkModel);
Assert.Equal("myapp", deserialized!.ProcessNameOrId);
Assert.Equal("Normal", deserialized.WorkModel);
Assert.Single(deserialized.ProcdumpOutPutLines!);
Assert.Equal("output1", deserialized.ProcdumpOutPutLines![0]);
}
Expand Down
Loading
Loading