diff --git a/src/c#/BowlTest/BowlTests.cs b/src/c#/BowlTest/BowlTests.cs
index 2128c677..09b2d138 100644
--- a/src/c#/BowlTest/BowlTests.cs
+++ b/src/c#/BowlTest/BowlTests.cs
@@ -2,7 +2,6 @@
using GeneralUpdate.Bowl;
using GeneralUpdate.Bowl.Internal;
using GeneralUpdate.Bowl.Strategies;
-using GeneralUpdate.Bowl.Strategys;
using BowlTest.Utilities;
///
@@ -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
///
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",
@@ -70,7 +64,7 @@ public void Constructor_AllArgsValid_DoesNotThrow()
public void Constructor_StrategyNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws(() =>
- new Bowl(null!, _reporter, _sysInfo, _env));
+ new Bowl(null!, _reporter, _sysInfo));
Assert.Equal("strategy", ex.ParamName);
}
@@ -78,7 +72,7 @@ public void Constructor_StrategyNull_ThrowsArgumentNullException()
public void Constructor_CrashReporterNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws(() =>
- new Bowl(_strategy, null!, _sysInfo, _env));
+ new Bowl(_strategy, null!, _sysInfo));
Assert.Equal("crashReporter", ex.ParamName);
}
@@ -86,18 +80,10 @@ public void Constructor_CrashReporterNull_ThrowsArgumentNullException()
public void Constructor_SystemInfoProviderNull_ThrowsArgumentNullException()
{
var ex = Assert.Throws(() =>
- 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(() =>
- new Bowl(_strategy, _reporter, _sysInfo, null!));
- Assert.Equal("env", ex.ParamName);
- }
-
// ---- LaunchAsync: Strategy returns null ----
[Fact]
@@ -145,7 +131,6 @@ await Assert.ThrowsAnyAsync(() =>
[Fact]
public async Task LaunchAsync_ProcessTimeout_ReturnsFailedResult()
{
- // Use a short timeout to trigger timeout
_strategy.PrepareResult = new ProcessStartInfo
{
FileName = "cmd.exe",
@@ -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(() => 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(() => 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(() => 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(() => 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]
@@ -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
{
diff --git a/src/c#/BowlTest/Internal/CrashJsonContextTests.cs b/src/c#/BowlTest/Internal/CrashJsonContextTests.cs
index d503c335..b97c60db 100644
--- a/src/c#/BowlTest/Internal/CrashJsonContextTests.cs
+++ b/src/c#/BowlTest/Internal/CrashJsonContextTests.cs
@@ -1,48 +1,36 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using GeneralUpdate.Bowl.Internal;
-using GeneralUpdate.Bowl.Strategys;
-///
-/// 分支覆盖点:
-/// CrashJsonContext 类:
-/// - 类型标注为 [JsonSerializable(typeof(Crash))]
-/// - 公开 Crash 属性(JsonTypeInfo<Crash>)
-/// - 序列化 Crash 对象(含完整数据)
-/// - 序列化 Crash 对象(空字段)
-///
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>(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 { "line1", "line2" },
};
@@ -55,7 +43,7 @@ public class CrashJsonContextTests
}
[Fact]
- public void 序列化空字段Crash_生成有效JSON()
+ public void SerializeEmptyCrash_GeneratesValidJson()
{
var crash = new Crash();
var json = JsonSerializer.Serialize(crash, CrashJsonContext.Default.Crash);
@@ -64,7 +52,7 @@ public class CrashJsonContextTests
}
[Fact]
- public void 序列化空列表ProcdumpOutPutLines_生成JSON包含空数组()
+ public void SerializeEmptyProcdumpOutPutLines_JsonContainsEmptyArray()
{
var crash = new Crash
{
@@ -75,23 +63,20 @@ public class CrashJsonContextTests
}
[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 { "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]);
}
diff --git a/src/c#/BowlTest/Internal/CrashTests.cs b/src/c#/BowlTest/Internal/CrashTests.cs
index df749920..664572b9 100644
--- a/src/c#/BowlTest/Internal/CrashTests.cs
+++ b/src/c#/BowlTest/Internal/CrashTests.cs
@@ -1,75 +1,69 @@
using GeneralUpdate.Bowl.Internal;
-using GeneralUpdate.Bowl.Strategys;
-///
-/// 分支覆盖点:
-/// Crash 类:
-/// - 默认构造:Parameter 和 ProcdumpOutPutLines 为 null
-/// - 设置 Parameter 为非 null MonitorParameter
-/// - 设置 ProcdumpOutPutLines 为 List<string>(空列表)
-/// - 设置 ProcdumpOutPutLines 为 List<string>(含数据)
-/// - ProcdumpOutPutLines 为 null(未捕获输出)
-///
public class CrashTests
{
[Fact]
- public void 默认构造_Parameter为null()
+ public void DefaultConstructor_FieldsAreNullOrDefault()
{
var crash = new Crash();
- Assert.Null(crash.Parameter);
+ Assert.Null(crash.ProcessNameOrId);
+ Assert.Null(crash.TargetPath);
+ Assert.NotNull(crash.ProcdumpOutPutLines); // initialized to empty list
+ Assert.Empty(crash.ProcdumpOutPutLines);
}
[Fact]
- public void 默认构造_ProcdumpOutPutLines为null()
+ public void SetProcessNameOrId_ReadsCorrectly()
{
- var crash = new Crash();
- Assert.Null(crash.ProcdumpOutPutLines);
+ var crash = new Crash { ProcessNameOrId = "test.exe" };
+ Assert.Equal("test.exe", crash.ProcessNameOrId);
}
[Fact]
- public void 设置Parameter_读取正确()
+ public void SetTargetPath_ReadsCorrectly()
{
- var param = new MonitorParameter
- {
- ProcessNameOrId = "test.exe",
- DumpFileName = "fail.dmp",
- };
- var crash = new Crash { Parameter = param };
- Assert.NotNull(crash.Parameter);
- Assert.Equal("test.exe", crash.Parameter.ProcessNameOrId);
- Assert.Equal("fail.dmp", crash.Parameter.DumpFileName);
+ var crash = new Crash { TargetPath = @"C:\app" };
+ Assert.Equal(@"C:\app", crash.TargetPath);
}
[Fact]
- public void 设置ProcdumpOutPutLines_空列表_读取正确()
+ public void SetWorkModel_ReadsCorrectly()
{
- var crash = new Crash { ProcdumpOutPutLines = new List() };
- Assert.NotNull(crash.ProcdumpOutPutLines);
- Assert.Empty(crash.ProcdumpOutPutLines);
+ var crash = new Crash { WorkModel = "Upgrade" };
+ Assert.Equal("Upgrade", crash.WorkModel);
}
[Fact]
- public void 设置ProcdumpOutPutLines_含数据_读取正确()
+ public void SetExtendedField_ReadsCorrectly()
{
- var lines = new List { "[10:00:00] ProcDump started.", "[10:00:01] Process exited." };
- var crash = new Crash { ProcdumpOutPutLines = lines };
- Assert.NotNull(crash.ProcdumpOutPutLines);
- Assert.Equal(2, crash.ProcdumpOutPutLines.Count);
- Assert.Contains("[10:00:00] ProcDump started.", crash.ProcdumpOutPutLines);
- Assert.Contains("[10:00:01] Process exited.", crash.ProcdumpOutPutLines);
+ var crash = new Crash { ExtendedField = "2.0.0" };
+ Assert.Equal("2.0.0", crash.ExtendedField);
}
[Fact]
- public void 设置Parameter和ProcdumpOutPutLines_同时存在()
+ public void SetAllFields_ReadsCorrectly()
{
- var param = new MonitorParameter { ProcessNameOrId = "myapp" };
- var lines = new List { "line1", "line2" };
var crash = new Crash
{
- Parameter = param,
- ProcdumpOutPutLines = lines,
+ TargetPath = @"C:\app",
+ FailDirectory = @"C:\app\fail\1.0",
+ BackupDirectory = @"C:\app\1.0",
+ ProcessNameOrId = "myapp.exe",
+ DumpFileName = "crash.dmp",
+ FailFileName = "crash.json",
+ WorkModel = "Upgrade",
+ ExtendedField = "1.0.0",
+ ProcdumpOutPutLines = new List { "line1", "line2" },
};
- Assert.NotNull(crash.Parameter);
- Assert.NotNull(crash.ProcdumpOutPutLines);
+
+ Assert.Equal(@"C:\app", crash.TargetPath);
+ Assert.Equal(@"C:\app\fail\1.0", crash.FailDirectory);
+ Assert.Equal(@"C:\app\1.0", crash.BackupDirectory);
+ Assert.Equal("myapp.exe", crash.ProcessNameOrId);
+ Assert.Equal("crash.dmp", crash.DumpFileName);
+ Assert.Equal("crash.json", crash.FailFileName);
+ Assert.Equal("Upgrade", crash.WorkModel);
+ Assert.Equal("1.0.0", crash.ExtendedField);
+ Assert.Equal(2, crash.ProcdumpOutPutLines.Count);
}
}
diff --git a/src/c#/BowlTest/Internal/EnvironmentProviderTests.cs b/src/c#/BowlTest/Internal/EnvironmentProviderTests.cs
deleted file mode 100644
index 32e92771..00000000
--- a/src/c#/BowlTest/Internal/EnvironmentProviderTests.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using GeneralUpdate.Bowl.Internal;
-
-///
-/// 分支覆盖点:
-/// EnvironmentProvider:
-/// - GetVariable(name):调用 Environments.GetEnvironmentVariable(name)
-/// - SetVariable(name, value):调用 Environments.SetEnvironmentVariable(name, value)
-/// - GetVariable 返回 null(变量不存在)
-/// - GetVariable 返回空字符串(变量存在但为空)
-/// - SetVariable 后 GetVariable 能正确读取
-/// - SetVariable 覆盖现有值
-///
-public class EnvironmentProviderTests
-{
- private const string TestVariableName = "BOWL_TEST_ENV_VAR";
-
- [Fact]
- public void GetVariable_变量不存在_返回null()
- {
- var provider = new EnvironmentProvider();
- // Ensure variable is not set
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
-
- var value = provider.GetVariable(TestVariableName);
- Assert.Null(value);
- }
-
- [Fact]
- public void SetVariable_设置值后GetVariable正确返回()
- {
- var provider = new EnvironmentProvider();
- try
- {
- provider.SetVariable(TestVariableName, "test_value_123");
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal("test_value_123", value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-
- [Fact]
- public void SetVariable_覆盖现有值_返回新值()
- {
- var provider = new EnvironmentProvider();
- try
- {
- provider.SetVariable(TestVariableName, "old_value");
- provider.SetVariable(TestVariableName, "new_value");
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal("new_value", value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-
- [Fact]
- public void SetVariable_空字符串值_GetVariable返回空字符串()
- {
- var provider = new EnvironmentProvider();
- try
- {
- provider.SetVariable(TestVariableName, string.Empty);
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal(string.Empty, value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-
- [Fact]
- public void SetVariable_长字符串值_正确返回()
- {
- var provider = new EnvironmentProvider();
- var longValue = new string('x', 1000);
- try
- {
- provider.SetVariable(TestVariableName, longValue);
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal(longValue, value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-
- [Fact]
- public void SetVariable_含特殊字符_正确返回()
- {
- var provider = new EnvironmentProvider();
- var specialValue = "value with spaces and 中文 and !@#$%";
- try
- {
- provider.SetVariable(TestVariableName, specialValue);
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal(specialValue, value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-
- [Fact]
- public void SetVariable_版本号字符串_正确返回()
- {
- var provider = new EnvironmentProvider();
- try
- {
- provider.SetVariable(TestVariableName, "10.0.0-preview.1");
- var value = provider.GetVariable(TestVariableName);
- Assert.Equal("10.0.0-preview.1", value);
- }
- finally
- {
- Environment.SetEnvironmentVariable(TestVariableName, null, EnvironmentVariableTarget.Process);
- }
- }
-}
diff --git a/src/c#/BowlTest/Internal/LinuxSystemTests.cs b/src/c#/BowlTest/Internal/LinuxSystemTests.cs
deleted file mode 100644
index 3a0f5d74..00000000
--- a/src/c#/BowlTest/Internal/LinuxSystemTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using GeneralUpdate.Bowl.Internal;
-
-///
-/// 分支覆盖点:
-/// LinuxSystem 类:
-/// - 构造函数:name 和 version 为正常值
-/// - 构造函数:name 为 null
-/// - 构造函数:version 为 null
-/// - 构造函数:name 和 version 为空字符串
-/// - Name 属性 get/set
-/// - Version 属性 get/set
-/// - Linux 发行版常见名称(ubuntu、rhel 等)
-///
-public class LinuxSystemTests
-{
- [Fact]
- public void 构造函数_正常参数_属性正确赋值()
- {
- var system = new LinuxSystem("ubuntu", "22.04");
- Assert.Equal("ubuntu", system.Name);
- Assert.Equal("22.04", system.Version);
- }
-
- [Fact]
- public void 构造函数_name为null_允许null()
- {
- var system = new LinuxSystem(null!, "1.0");
- Assert.Null(system.Name);
- Assert.Equal("1.0", system.Version);
- }
-
- [Fact]
- public void 构造函数_version为null_允许null()
- {
- var system = new LinuxSystem("debian", null!);
- Assert.Equal("debian", system.Name);
- Assert.Null(system.Version);
- }
-
- [Fact]
- public void 构造函数_name和version为空字符串()
- {
- var system = new LinuxSystem(string.Empty, string.Empty);
- Assert.Equal(string.Empty, system.Name);
- Assert.Equal(string.Empty, system.Version);
- }
-
- [Fact]
- public void 属性可写_set后正确返回()
- {
- var system = new LinuxSystem("initial", "0.0");
- system.Name = "fedora";
- system.Version = "40";
- Assert.Equal("fedora", system.Name);
- Assert.Equal("40", system.Version);
- }
-
- [Theory]
- [InlineData("ubuntu", "24.04")]
- [InlineData("rhel", "8.10")]
- [InlineData("centos", "9")]
- [InlineData("clearos", "7")]
- [InlineData("fedora", "39")]
- [InlineData("debian", "12")]
- public void 常见发行版_构造函数正常(string name, string version)
- {
- var system = new LinuxSystem(name, version);
- Assert.Equal(name, system.Name);
- Assert.Equal(version, system.Version);
- }
-
- [Fact]
- public void 两个相同属性的实例_不相等()
- {
- var sys1 = new LinuxSystem("ubuntu", "22.04");
- var sys2 = new LinuxSystem("ubuntu", "22.04");
- // Reference types, not record — should not be equal
- Assert.NotEqual(sys1, sys2);
- }
-}
diff --git a/src/c#/BowlTest/Strategies/LinuxBowlStrategyTests.cs b/src/c#/BowlTest/Strategies/LinuxBowlStrategyTests.cs
index d306e2ca..90d92bde 100644
--- a/src/c#/BowlTest/Strategies/LinuxBowlStrategyTests.cs
+++ b/src/c#/BowlTest/Strategies/LinuxBowlStrategyTests.cs
@@ -1,25 +1,6 @@
using GeneralUpdate.Bowl;
using GeneralUpdate.Bowl.Strategies;
-///
-/// 分支覆盖点:
-/// LinuxBowlStrategy.Prepare():
-/// - 首次调用:尝试安装 procdump → 安装成功 → 返回 ProcessStartInfo
-/// - 首次调用:尝试安装 procdump → 安装失败 → 返回 null
-/// - 再次调用:procdump 已安装 → 直接使用缓存,不重新安装
-/// - 再次调用:procdump 上次安装失败 → 返回 null(不重试)
-/// - 安装成功时:FileName="procdump", Arguments 包含 -p 和进程名
-/// - 安装成功时:RedirectStandardOutput/Error=true, UseShellExecute=false
-/// - FailDirectory 存在时先删除再创建
-/// TryInstallProcdump() 分支:
-/// - 检测到支持的发行版(ubuntu/debian/rhel/centos/fedora/clearos)→ 找到包名
-/// - 检测到不支持的发行版 → 返回 false
-/// - /etc/os-release 不存在 → 返回空字符串 → 不匹配任何包 → 返回 false
-/// - install.sh 不存在 → 返回 false
-/// - 包文件不存在 → 返回 false
-/// PostProcessAsync():
-/// - 始终返回 Task.CompletedTask
-///
public class LinuxBowlStrategyTests
{
private BowlContext CreateContext(
@@ -41,15 +22,16 @@ private BowlContext CreateContext(
};
}
+ // ---- Prepare: return type ----
+
[Fact]
- public void Prepare_返回值为null或有效ProcessStartInfo()
+ public void Prepare_ReturnsNullOrValidProcessStartInfo()
{
var strategy = new LinuxBowlStrategy();
var ctx = CreateContext();
var startInfo = strategy.Prepare(ctx);
- // Either null (procdump unavailable) or valid ProcessStartInfo
if (startInfo != null)
{
Assert.Equal("procdump", startInfo.FileName);
@@ -57,13 +39,45 @@ private BowlContext CreateContext(
Assert.True(startInfo.RedirectStandardError);
Assert.False(startInfo.UseShellExecute);
Assert.True(startInfo.CreateNoWindow);
+ }
+ }
+
+ // ---- Prepare: PID vs process name flag selection ----
+
+ [Fact]
+ public void Prepare_ProcessName_UsesWFlag()
+ {
+ var strategy = new LinuxBowlStrategy();
+ var ctx = CreateContext(processName: "dotnet");
+
+ var startInfo = strategy.Prepare(ctx);
+
+ if (startInfo != null)
+ {
+ Assert.Contains("-w", startInfo.Arguments);
+ Assert.Contains("dotnet", startInfo.Arguments);
+ }
+ }
+
+ [Fact]
+ public void Prepare_Pid_UsesPFlag()
+ {
+ var strategy = new LinuxBowlStrategy();
+ var ctx = CreateContext(processName: "12345");
+
+ var startInfo = strategy.Prepare(ctx);
+
+ if (startInfo != null)
+ {
Assert.Contains("-p", startInfo.Arguments);
- Assert.Contains(ctx.ProcessNameOrId, startInfo.Arguments);
+ Assert.Contains("12345", startInfo.Arguments);
}
}
+ // ---- Prepare: consistency ----
+
[Fact]
- public void Prepare_多次调用_结果一致()
+ public void Prepare_MultipleCalls_ConsistentResult()
{
var strategy = new LinuxBowlStrategy();
var ctx = CreateContext();
@@ -71,34 +85,81 @@ private BowlContext CreateContext(
var result1 = strategy.Prepare(ctx);
var result2 = strategy.Prepare(ctx);
- // Both calls should return same type (both null or both not null)
Assert.Equal(result1 == null, result2 == null);
}
[Fact]
- public void Prepare_不同上下文_分别处理()
+ public void Prepare_DifferentContexts_EachUsesOwnProcessName()
{
var strategy = new LinuxBowlStrategy();
var ctx1 = CreateContext(processName: "app1");
var ctx2 = CreateContext(processName: "app2");
var si1 = strategy.Prepare(ctx1);
-
if (si1 != null)
- {
Assert.Contains("app1", si1.Arguments);
- }
var si2 = strategy.Prepare(ctx2);
-
if (si2 != null)
- {
Assert.Contains("app2", si2.Arguments);
+ }
+
+ // ---- Prepare: unsupported hint file written ----
+
+ [Fact]
+ public void Prepare_UnsupportedDistro_WritesHintFile()
+ {
+ // When procdump is not in PATH and we're not on a supported distro,
+ // the strategy should write bowl_linux_unsupported.txt in the fail directory.
+ // This is hard to test in isolation without mocking, but on Windows
+ // the /etc/os-release path doesn't exist, so Prepare should return null
+ // and the hint file should be written.
+ var tempDir = Path.Combine(Path.GetTempPath(), $"BowlLinuxTest_{Guid.NewGuid():N}");
+ Directory.CreateDirectory(tempDir);
+ try
+ {
+ var ctx = CreateContext(processName: "test");
+ ctx = new BowlContext
+ {
+ ProcessNameOrId = "test",
+ DumpFileName = "crash.dmp",
+ FailFileName = "crash.json",
+ TargetPath = "/opt/app",
+ FailDirectory = tempDir,
+ BackupDirectory = "/tmp/backup",
+ WorkModel = "Upgrade",
+ ExtendedField = "1.0.0",
+ TimeoutMs = 30_000,
+ DumpType = DumpType.Full,
+ };
+
+ var strategy = new LinuxBowlStrategy();
+ var startInfo = strategy.Prepare(ctx);
+
+ // On non-Linux (or Linux without procdump in PATH), should return null
+ if (startInfo == null)
+ {
+ // A hint file should have been written explaining why
+ var hintPath = Path.Combine(tempDir, "bowl_linux_unsupported.txt");
+ var hintExists = File.Exists(hintPath);
+
+ // If it doesn't exist, procdump might already be installed
+ if (!hintExists)
+ {
+ // That's also fine — means procdump is available
+ }
+ }
+ }
+ finally
+ {
+ try { Directory.Delete(tempDir, recursive: true); } catch { }
}
}
+ // ---- PostProcessAsync ----
+
[Fact]
- public async Task PostProcessAsync_始终返回CompletedTask()
+ public async Task PostProcessAsync_AlwaysReturnsCompletedTask()
{
var strategy = new LinuxBowlStrategy();
var ctx = CreateContext();
diff --git a/src/c#/BowlTest/Strategies/MacBowlStrategyTests.cs b/src/c#/BowlTest/Strategies/MacBowlStrategyTests.cs
deleted file mode 100644
index 032327f0..00000000
--- a/src/c#/BowlTest/Strategies/MacBowlStrategyTests.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using GeneralUpdate.Bowl;
-using GeneralUpdate.Bowl.Strategies;
-
-///
-/// 分支覆盖点:
-/// MacBowlStrategy.Prepare():
-/// - lldb 不可用 → 返回 null(平台不支持场景)
-/// - lldb 可用 → 返回 ProcessStartInfo(仅 macOS)
-/// - lldb 可用时:RedirectStandardOutput/Error 为 true
-/// - lldb 可用时:UseShellExecute=false, CreateNoWindow=true
-/// - lldb 可用时:FileName="/usr/bin/lldb"
-/// - lldb 可用时:Arguments 包含 --batch、process attach、process save-core、quit
-/// - FailDirectory 存在时先删除再创建
-/// PostProcessAsync():
-/// - 始终返回 Task.CompletedTask
-///
-public class MacBowlStrategyTests
-{
- private BowlContext CreateContext()
- {
- return new BowlContext
- {
- ProcessNameOrId = "test_app",
- DumpFileName = "crash.dmp",
- FailFileName = "crash.json",
- TargetPath = "/Applications/TestApp",
- FailDirectory = "/tmp/fail/test",
- BackupDirectory = "/tmp/backup/test",
- WorkModel = "Normal",
- ExtendedField = "1.0.0",
- TimeoutMs = 30_000,
- DumpType = DumpType.Full,
- };
- }
-
- [Fact]
- public void Prepare_在非macOS平台_返回null()
- {
- var strategy = new MacBowlStrategy();
- var ctx = CreateContext();
-
- var startInfo = strategy.Prepare(ctx);
-
- // On Windows/Linux, lldb is typically not available at /usr/bin/lldb
- // So this should return null on non-macOS
- if (!OperatingSystem.IsMacOS())
- {
- Assert.Null(startInfo);
- }
- }
-
- [Fact]
- public void Prepare_返回值要么为null要么为有效ProcessStartInfo()
- {
- var strategy = new MacBowlStrategy();
- var ctx = CreateContext();
-
- var startInfo = strategy.Prepare(ctx);
-
- if (startInfo != null)
- {
- // On macOS: validate ProcessStartInfo configuration
- Assert.Equal("/usr/bin/lldb", startInfo.FileName);
- Assert.True(startInfo.RedirectStandardOutput);
- Assert.True(startInfo.RedirectStandardError);
- Assert.False(startInfo.UseShellExecute);
- Assert.True(startInfo.CreateNoWindow);
- Assert.Contains("--batch", startInfo.Arguments);
- Assert.Contains("process attach", startInfo.Arguments);
- Assert.Contains("process save-core", startInfo.Arguments);
- Assert.Contains("quit", startInfo.Arguments);
- Assert.Contains(ctx.ProcessNameOrId, startInfo.Arguments);
- }
- }
-
- [Fact]
- public async Task PostProcessAsync_始终返回CompletedTask()
- {
- var strategy = new MacBowlStrategy();
- var ctx = CreateContext();
- var exitResult = new ProcessExitResult { ExitCode = 0, OutputLines = new List() };
-
- var task = strategy.PostProcessAsync(ctx, exitResult, CancellationToken.None);
-
- Assert.Equal(Task.CompletedTask, task);
- await task;
- }
-}
diff --git a/src/c#/BowlTest/Strategies/StrategyFactoryTests.cs b/src/c#/BowlTest/Strategies/StrategyFactoryTests.cs
index 5a740c65..028c301e 100644
--- a/src/c#/BowlTest/Strategies/StrategyFactoryTests.cs
+++ b/src/c#/BowlTest/Strategies/StrategyFactoryTests.cs
@@ -2,22 +2,18 @@
using GeneralUpdate.Bowl.Strategies;
///
-/// 分支覆盖点:
-/// StrategyFactory.Create():
-/// - Windows 平台 → 返回 WindowsBowlStrategy
-/// - Linux 平台 → 返回 LinuxBowlStrategy
-/// - macOS 平台 → 返回 MacBowlStrategy
-/// - 其他平台 → 抛出 PlatformNotSupportedException
+/// Branch coverage points for StrategyFactory.Create():
+/// - Windows platform → returns WindowsBowlStrategy
+/// - Linux platform → returns LinuxBowlStrategy
+/// - Unsupported platform → throws PlatformNotSupportedException
///
public class StrategyFactoryTests
{
[Fact]
- public void Create_返回非null_IBowlStrategy()
+ public void Create_ReturnsNonNull_IBowlStrategy()
{
- // On any supported platform (Win/Lin/Mac), Create should not return null
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var strategy = StrategyFactory.Create();
Assert.NotNull(strategy);
@@ -26,7 +22,7 @@ public class StrategyFactoryTests
}
[Fact]
- public void Create_在Windows上返回WindowsBowlStrategy()
+ public void Create_OnWindows_ReturnsWindowsBowlStrategy()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@@ -36,7 +32,7 @@ public class StrategyFactoryTests
}
[Fact]
- public void Create_在Linux上返回LinuxBowlStrategy()
+ public void Create_OnLinux_ReturnsLinuxBowlStrategy()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
@@ -46,21 +42,10 @@ public class StrategyFactoryTests
}
[Fact]
- public void Create_在macOS上返回MacBowlStrategy()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- var strategy = StrategyFactory.Create();
- Assert.IsType(strategy);
- }
- }
-
- [Fact]
- public void Create_支持平台_不抛出异常()
+ public void Create_SupportedPlatform_DoesNotThrow()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var exception = Record.Exception(() => StrategyFactory.Create());
Assert.Null(exception);
@@ -68,11 +53,10 @@ public class StrategyFactoryTests
}
[Fact]
- public void Create_多次调用_返回不同实例()
+ public void Create_MultipleCalls_ReturnDifferentInstances()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var s1 = StrategyFactory.Create();
var s2 = StrategyFactory.Create();
diff --git a/src/c#/BowlTest/Strategies/WindowsBowlStrategyTests.cs b/src/c#/BowlTest/Strategies/WindowsBowlStrategyTests.cs
index c2d3b193..f655183d 100644
--- a/src/c#/BowlTest/Strategies/WindowsBowlStrategyTests.cs
+++ b/src/c#/BowlTest/Strategies/WindowsBowlStrategyTests.cs
@@ -173,7 +173,7 @@ private string NormalizePath(string path)
var startInfo = strategy.Prepare(ctx);
Assert.NotNull(startInfo);
- Assert.Contains(" -e ", startInfo!.Arguments);
+ Assert.StartsWith("-e ", startInfo!.Arguments);
}
[Fact]
diff --git a/src/c#/BowlTest/Strategys/AbstractStrategyTests.cs b/src/c#/BowlTest/Strategys/AbstractStrategyTests.cs
deleted file mode 100644
index fa054956..00000000
--- a/src/c#/BowlTest/Strategys/AbstractStrategyTests.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using GeneralUpdate.Bowl.Strategys;
-
-///
-/// 分支覆盖点:
-/// AbstractStrategy:
-/// - SetParameter(MonitorParameter):设置 _parameter
-/// - Launch():调用 Startup(),Startup 执行完整启动流程
-/// - Startup() 内部:
-/// - FailDirectory 已存在 → 删除目录
-/// - FailDirectory 不存在 → 仅创建
-/// - Process.Start() → 进程启动
-/// - OutputDataReceived / ErrorDataReceived → 添加非 null 非空行
-/// - WaitForExit(10000) → 等待最多 10 秒
-/// - OutputHandler():
-/// - Data 为 null → 不添加
-/// - Data 为空字符串 → 不添加
-/// - Data 有效 → 添加
-///
-public class AbstractStrategyTests : IDisposable
-{
- private class TestableAbstractStrategy : AbstractStrategy
- {
- public new MonitorParameter _parameter => base._parameter;
- public new List OutputList => base.OutputList;
- public new void TestSetParameter(MonitorParameter p) => base.SetParameter(p);
- public new void TestLaunch() => base.Launch();
- }
-
- private readonly string _tempDir;
-
- public AbstractStrategyTests()
- {
- _tempDir = Path.Combine(Path.GetTempPath(), $"BowlTest_Abstract_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_tempDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_tempDir))
- Directory.Delete(_tempDir, recursive: true);
- }
-
- [Fact]
- public void SetParameter_设置参数_parameter字段更新()
- {
- var strategy = new TestableAbstractStrategy();
- var param = new MonitorParameter { ProcessNameOrId = "test.exe" };
-
- strategy.TestSetParameter(param);
-
- Assert.NotNull(strategy._parameter);
- Assert.Equal("test.exe", strategy._parameter.ProcessNameOrId);
- }
-
- [Fact]
- public void SetParameter_覆盖参数_字段更新为新参数()
- {
- var strategy = new TestableAbstractStrategy();
- var param1 = new MonitorParameter { ProcessNameOrId = "app1.exe" };
- var param2 = new MonitorParameter { ProcessNameOrId = "app2.exe" };
-
- strategy.TestSetParameter(param1);
- strategy.TestSetParameter(param2);
-
- Assert.Equal("app2.exe", strategy._parameter.ProcessNameOrId);
- }
-
- [Fact]
- public void SetParameter_设置null_允许null()
- {
- var strategy = new TestableAbstractStrategy();
- strategy.TestSetParameter(null!);
- Assert.Null(strategy._parameter);
- }
-
- [Fact]
- public void 初始化时OutputList为空()
- {
- var strategy = new TestableAbstractStrategy();
- Assert.NotNull(strategy.OutputList);
- Assert.Empty(strategy.OutputList);
- }
-
- [Fact]
- public void TestLaunch_SuccessWhenFailDirectoryCreated()
- {
- var strategy = new TestableAbstractStrategy();
- var failDir = Path.Combine(_tempDir, "fail");
- // Use a simple cmd.exe that exits quickly
- var param = new MonitorParameter
- {
- ProcessNameOrId = "test",
- InnerApp = "cmd.exe",
- InnerArguments = "/c exit 0",
- FailDirectory = failDir,
- };
- strategy.TestSetParameter(param);
-
- strategy.TestLaunch();
-
- // FailDirectory should exist after Launch
- Assert.True(Directory.Exists(failDir));
- }
-}
diff --git a/src/c#/BowlTest/Strategys/LinuxStrategyTests.cs b/src/c#/BowlTest/Strategys/LinuxStrategyTests.cs
deleted file mode 100644
index d2603524..00000000
--- a/src/c#/BowlTest/Strategys/LinuxStrategyTests.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-using GeneralUpdate.Bowl.Internal;
-using GeneralUpdate.Bowl.Strategys;
-
-///
-/// 分支覆盖点:
-/// LinuxStrategy (Obsolete):
-/// - Launch():调用 Install() 然后 base.Launch()
-/// - Launch():Install 抛出异常 → catch 后重新抛出
-/// - Install():
-/// - GetPacketName() 返回有效包名 → 执行 install.sh
-/// - GetPacketName() 返回空 → 提前返回(不安装)
-/// - install.sh 执行成功(ExitCode=0)→ 记录成功日志
-/// - install.sh 执行失败(ExitCode!=0)→ 记录错误日志
-/// - install.sh 执行抛出异常 → catch 记录错误
-/// - GetPacketName():
-/// - 发行版在 _rocdumpAmd64 列表中 → 返回 .deb 包名
-/// - 发行版在 procdump_el8_x86_64 列表中 → 返回 .el8.rpm 包名
-/// - 发行版在 procdump_cm2_x86_64 列表中 → 返回 .cm2.rpm 包名
-/// - 发行版不在任何列表中 → 返回空字符串
-/// - GetSystem():
-/// - /etc/os-release 存在 → 读取 ID 和 VERSION_ID
-/// - /etc/os-release 不存在 → 抛出 FileNotFoundException
-/// - /etc/os-release 中 ID= 存在但无双引号
-/// - /etc/os-release 中 VERSION_ID= 存在但无双引号
-///
-public class LinuxStrategyTests : IDisposable
-{
- private readonly string _tempDir;
-
- public LinuxStrategyTests()
- {
- _tempDir = Path.Combine(Path.GetTempPath(), $"BowlTest_Linux_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_tempDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_tempDir))
- Directory.Delete(_tempDir, recursive: true);
- }
-
- [Fact]
- public void 构造实例_不抛出异常()
- {
- var ex = Record.Exception(() => new LinuxStrategy());
- Assert.Null(ex);
- }
-
- [Fact]
- public void SetParameter_设置有效参数_不抛出异常()
- {
- var strategy = new LinuxStrategy();
- var param = new MonitorParameter
- {
- ProcessNameOrId = "dotnet",
- InnerApp = "dotnet",
- InnerArguments = "--version",
- FailDirectory = Path.Combine(_tempDir, "linux_fail"),
- DumpFileName = "crash.dmp",
- FailFileName = "crash.json",
- TargetPath = _tempDir,
- BackupDirectory = Path.Combine(_tempDir, "backup"),
- WorkModel = "Upgrade",
- ExtendedField = "1.0",
- };
-
- var ex = Record.Exception(() => strategy.SetParameter(param));
- Assert.Null(ex);
- // Legacy strategy doesn't throw on set
- }
-
- [Fact]
- public void Launch_OnNonLinuxSystem_GracefullyHandlesMissingInstallScript()
- {
- // This test verifies the strategy can be created and called without crashing
- var strategy = new LinuxStrategy();
- var param = new MonitorParameter
- {
- ProcessNameOrId = "dotnet",
- InnerApp = "dotnet",
- InnerArguments = "--version",
- FailDirectory = Path.Combine(_tempDir, "linux_fail"),
- TargetPath = _tempDir,
- BackupDirectory = Path.Combine(_tempDir, "backup"),
- };
- strategy.SetParameter(param);
-
- // Launch will try to install procdump, which may fail on non-Linux
- // The test verifies it doesn't crash unexpectedly
- try
- {
- strategy.Launch();
- }
- catch (FileNotFoundException)
- {
- // Expected on non-Linux when /etc/os-release not found
- }
- catch (Exception)
- {
- // Other exceptions from missing tools are also acceptable
- }
- }
-
- // GetSystem and GetPacketName are private; tested indirectly via Launch
- // The branching logic is:
- // - Ubuntu/Debian → .deb
- // - RHEL/CentOS/Fedora → .el8.rpm
- // - ClearOS → .cm2.rpm
- // - Unknown → empty string (skip install)
-}
diff --git a/src/c#/BowlTest/Strategys/MonitorParameterTests.cs b/src/c#/BowlTest/Strategys/MonitorParameterTests.cs
deleted file mode 100644
index c38596ce..00000000
--- a/src/c#/BowlTest/Strategys/MonitorParameterTests.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using GeneralUpdate.Bowl.Strategys;
-
-///
-/// 分支覆盖点:
-/// MonitorParameter 类(已标记 Obsolete):
-/// - 默认构造:所有属性为 null/default
-/// - WorkModel 默认值为 "Upgrade"
-/// - 设置所有公开属性
-/// - 设置内部属性(InnerArguments, InnerApp)
-/// - TargetPath 为路径字符串
-/// - ProcessNameOrId 为进程名
-/// - ExtendedField 为版本号
-///
-public class MonitorParameterTests
-{
- [Fact]
- public void 默认构造_WorkModel默认值为Upgrade()
- {
- var param = new MonitorParameter();
- Assert.Equal("Upgrade", param.WorkModel);
- }
-
- [Fact]
- public void 默认构造_其他属性为null()
- {
- var param = new MonitorParameter();
- Assert.Null(param.TargetPath);
- Assert.Null(param.FailDirectory);
- Assert.Null(param.BackupDirectory);
- Assert.Null(param.ProcessNameOrId);
- Assert.Null(param.DumpFileName);
- Assert.Null(param.FailFileName);
- Assert.Null(param.ExtendedField);
- }
-
- [Fact]
- public void 设置所有公开属性_正确返回()
- {
- var param = new MonitorParameter
- {
- TargetPath = "C:\\app",
- FailDirectory = "C:\\app\\fail",
- BackupDirectory = "C:\\app\\backup",
- ProcessNameOrId = "test.exe",
- DumpFileName = "v1_fail.dmp",
- FailFileName = "v1_fail.json",
- WorkModel = "Normal",
- ExtendedField = "1.0.0",
- };
-
- Assert.Equal("C:\\app", param.TargetPath);
- Assert.Equal("C:\\app\\fail", param.FailDirectory);
- Assert.Equal("C:\\app\\backup", param.BackupDirectory);
- Assert.Equal("test.exe", param.ProcessNameOrId);
- Assert.Equal("v1_fail.dmp", param.DumpFileName);
- Assert.Equal("v1_fail.json", param.FailFileName);
- Assert.Equal("Normal", param.WorkModel);
- Assert.Equal("1.0.0", param.ExtendedField);
- }
-
- [Fact]
- public void WorkModel为Upgrade_保留Upgrade()
- {
- var param = new MonitorParameter { WorkModel = "Upgrade" };
- Assert.Equal("Upgrade", param.WorkModel);
- }
-
- [Fact]
- public void WorkModel为null_允许null()
- {
- var param = new MonitorParameter { WorkModel = null! };
- Assert.Null(param.WorkModel);
- }
-
- [Fact]
- public void WorkModel为空字符串_允许空字符串()
- {
- var param = new MonitorParameter { WorkModel = string.Empty };
- Assert.Equal(string.Empty, param.WorkModel);
- }
-
- [Fact]
- public void ExtendedField为版本号_正确返回()
- {
- var param = new MonitorParameter { ExtendedField = "10.0.0-preview.1" };
- Assert.Equal("10.0.0-preview.1", param.ExtendedField);
- }
-}
diff --git a/src/c#/BowlTest/Strategys/WindowStrategyTests.cs b/src/c#/BowlTest/Strategys/WindowStrategyTests.cs
deleted file mode 100644
index 588c91ff..00000000
--- a/src/c#/BowlTest/Strategys/WindowStrategyTests.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using GeneralUpdate.Bowl.Strategys;
-
-///
-/// 分支覆盖点:
-/// WindowStrategy (Obsolete):
-/// - Launch():
-/// - 初始化 Actions 管道:CreateCrash, Export, Restore, SetEnvironment
-/// - 根据 OS 架构选择 procdump 可执行文件:
-/// - X86 → "procdump.exe"
-/// - X64 → "procdump64.exe"
-/// - 其他 → "procdump64a.exe"
-/// - InnerArguments 格式:-e -ma {ProcessNameOrId} {dmpFullName}
-/// - 调用 base.Launch()(启动进程)
-/// - ExecuteFinalTreatment():
-/// - dump 文件存在 → 执行所有 actions
-/// - dump 文件不存在 → 跳过 actions
-/// - GetAppName():
-/// - X86 → "procdump.exe"
-/// - X64 → "procdump64.exe"
-/// - 其他 → "procdump64a.exe"
-/// - CreateCrash():序列化 Crash 到 JSON
-/// - Export():
-/// - export.bat 存在 → 启动
-/// - export.bat 不存在 → 抛出 FileNotFoundException
-/// - Restore():
-/// - WorkModel="Upgrade" → 执行恢复
-/// - WorkModel!="Upgrade" → 跳过
-/// - SetEnvironment():
-/// - WorkModel="Upgrade" → 设置环境变量
-/// - WorkModel!="Upgrade" → 跳过
-///
-public class WindowStrategyTests : IDisposable
-{
- private readonly string _tempDir;
-
- public WindowStrategyTests()
- {
- _tempDir = Path.Combine(Path.GetTempPath(), $"BowlTest_Win_{Guid.NewGuid():N}");
- Directory.CreateDirectory(_tempDir);
- }
-
- public void Dispose()
- {
- if (Directory.Exists(_tempDir))
- Directory.Delete(_tempDir, recursive: true);
- }
-
- [Fact]
- public void 构造实例_不抛出异常()
- {
- var ex = Record.Exception(() => new WindowStrategy());
- Assert.Null(ex);
- }
-
- [Fact]
- public void SetParameter_设置有效参数_不抛出异常()
- {
- var strategy = new WindowStrategy();
- var param = new MonitorParameter
- {
- ProcessNameOrId = "test.exe",
- DumpFileName = "crash.dmp",
- FailFileName = "crash.json",
- TargetPath = _tempDir,
- FailDirectory = Path.Combine(_tempDir, "fail"),
- BackupDirectory = Path.Combine(_tempDir, "backup"),
- WorkModel = "Upgrade",
- ExtendedField = "1.0.0",
- };
-
- var ex = Record.Exception(() => strategy.SetParameter(param));
- Assert.Null(ex);
- }
-
- [Fact]
- public void Launch_使用简单命令_不抛出未处理异常()
- {
- var strategy = new WindowStrategy();
- var failDir = Path.Combine(_tempDir, "win_fail");
- var param = new MonitorParameter
- {
- ProcessNameOrId = "test_process",
- InnerApp = "cmd.exe",
- InnerArguments = "/c exit 0",
- DumpFileName = "crash.dmp",
- FailFileName = "crash.json",
- TargetPath = _tempDir,
- FailDirectory = failDir,
- BackupDirectory = Path.Combine(_tempDir, "backup"),
- WorkModel = "Normal",
- ExtendedField = "1.0.0",
- };
- strategy.SetParameter(param);
-
- // WindowStrategy.Launch will try to run procdump which may not exist
- // The test verifies graceful handling
- try
- {
- strategy.Launch();
- }
- catch (Exception)
- {
- // Acceptable on test machines without procdump
- }
- }
-}
diff --git a/src/c#/BowlTest/Utilities/TestFakes.cs b/src/c#/BowlTest/Utilities/TestFakes.cs
index 0441877c..9a3ad302 100644
--- a/src/c#/BowlTest/Utilities/TestFakes.cs
+++ b/src/c#/BowlTest/Utilities/TestFakes.cs
@@ -61,29 +61,3 @@ public Task ExportAsync(string outputDirectory, CancellationToken ct)
return Task.CompletedTask;
}
}
-
-internal class FakeEnvironmentProvider : IEnvironmentProvider
-{
- private readonly Dictionary _variables = new();
- public bool SetVariableCalled { get; private set; }
- public string? LastSetName { get; private set; }
- public string? LastSetValue { get; private set; }
- public Exception? SetVariableException { get; set; }
-
- public string? GetVariable(string name)
- => _variables.TryGetValue(name, out var val) ? val : null;
-
- public void SetVariable(string name, string value)
- {
- SetVariableCalled = true;
- LastSetName = name;
- LastSetValue = value;
- if (SetVariableException != null) throw SetVariableException;
- _variables[name] = value;
- }
-
- public void PreSetVariable(string name, string? value)
- {
- _variables[name] = value;
- }
-}
diff --git a/src/c#/GeneralUpdate.Bowl/Bowl.cs b/src/c#/GeneralUpdate.Bowl/Bowl.cs
index 7e601498..ad511f27 100644
--- a/src/c#/GeneralUpdate.Bowl/Bowl.cs
+++ b/src/c#/GeneralUpdate.Bowl/Bowl.cs
@@ -1,14 +1,10 @@
using System;
using System.IO;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Bowl.Internal;
using GeneralUpdate.Bowl.Strategies;
-using GeneralUpdate.Bowl.Strategys;
using GeneralUpdate.Bowl.FileSystem;
-using GeneralUpdate.Bowl.Configuration;
-using GeneralUpdate.Bowl.Ipc;
namespace GeneralUpdate.Bowl;
@@ -23,7 +19,6 @@ public sealed class Bowl
private readonly IBowlStrategy _strategy;
private readonly ICrashReporter _crashReporter;
private readonly ISystemInfoProvider _systemInfoProvider;
- private readonly IEnvironmentProvider _env;
// ---- Constructors ----
@@ -34,8 +29,7 @@ public Bowl()
: this(
StrategyFactory.Create(),
new CrashReporter(),
- SystemInfoProviderFactory.Create(),
- new EnvironmentProvider())
+ SystemInfoProviderFactory.Create())
{ }
///
@@ -44,13 +38,11 @@ public Bowl()
internal Bowl(
IBowlStrategy strategy,
ICrashReporter crashReporter,
- ISystemInfoProvider systemInfoProvider,
- IEnvironmentProvider env)
+ ISystemInfoProvider systemInfoProvider)
{
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
_crashReporter = crashReporter ?? throw new ArgumentNullException(nameof(crashReporter));
_systemInfoProvider = systemInfoProvider ?? throw new ArgumentNullException(nameof(systemInfoProvider));
- _env = env ?? throw new ArgumentNullException(nameof(env));
}
// ---- Public Async API ----
@@ -175,25 +167,7 @@ private async Task HandleCrashAsync(
}
}
- // 4. Mark failed version to prevent re-upgrading to it
- if (context.WorkModel == "Upgrade")
- {
- try
- {
- _env.SetVariable("UpgradeFail", context.ExtendedField);
- GeneralTracer.Warn($"Bowl.HandleCrashAsync: UpgradeFail set to '{context.ExtendedField}'.");
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- GeneralTracer.Error("Bowl.HandleCrashAsync: failed to set UpgradeFail env var.", ex);
- }
- }
-
- // 5. Platform-specific post-processing
+ // 4. Platform-specific post-processing
try
{
await _strategy.PostProcessAsync(context, exitResult, ct);
@@ -207,30 +181,15 @@ private async Task HandleCrashAsync(
GeneralTracer.Error("Bowl.HandleCrashAsync: post-process failed.", ex);
}
- // 6. Invoke crash callback
+ // 5. Invoke crash callback
if (context.OnCrash != null)
{
try
{
- // Only invoke callback if we have something to report
- if (crashReportPath == null)
- {
- GeneralTracer.Warn("Bowl.HandleCrashAsync: skipping OnCrash callback — no crash report generated.");
- return new BowlResult
- {
- Success = false,
- ExitCode = exitResult.ExitCode,
- DumpCaptured = true,
- DumpFilePath = dumpPath,
- CrashReportPath = null,
- Restored = restored,
- };
- }
-
var crashInfo = new CrashInfo
{
DumpFilePath = dumpPath,
- CrashReportPath = crashReportPath!,
+ CrashReportPath = crashReportPath ?? string.Empty,
Version = context.ExtendedField,
ExitCode = exitResult.ExitCode,
};
@@ -257,68 +216,6 @@ private async Task HandleCrashAsync(
};
}
- // ---- Backward Compatibility ----
-
- ///
- /// Converts legacy to the new .
- ///
- public static BowlContext MapToContext(MonitorParameter p)
- {
- return new BowlContext
- {
- ProcessNameOrId = p.ProcessNameOrId,
- DumpFileName = p.DumpFileName,
- FailFileName = p.FailFileName,
- TargetPath = p.TargetPath,
- FailDirectory = p.FailDirectory,
- BackupDirectory = p.BackupDirectory,
- WorkModel = p.WorkModel,
- ExtendedField = p.ExtendedField,
- TimeoutMs = 30_000,
- DumpType = DumpType.Full,
- AutoRestore = true,
- };
- }
-
- ///
- /// Reads ProcessInfo environment variable and builds a legacy .
- /// Shared with the legacy static API for backward compatibility.
- ///
- internal static MonitorParameter CreateParameter()
- {
- GeneralTracer.Info("Bowl.CreateParameter: reading ProcessInfo from environment variable.");
-
- var json = Environments.GetEnvironmentVariable("ProcessInfo");
- if (string.IsNullOrWhiteSpace(json))
- {
- GeneralTracer.Fatal("Bowl.CreateParameter: ProcessInfo environment variable is not set.");
- throw new ArgumentNullException(
- "ProcessInfo environment variable not set.");
- }
-
- var processInfo = JsonSerializer.Deserialize(json);
- if (processInfo == null)
- {
- GeneralTracer.Fatal("Bowl.CreateParameter: failed to deserialize ProcessInfo JSON.");
- throw new ArgumentNullException(
- "ProcessInfo JSON deserialization failed.");
- }
-
- GeneralTracer.Info(
- $"Bowl.CreateParameter: AppName={processInfo.AppName}, Version={processInfo.LastVersion}");
-
- return new MonitorParameter
- {
- ProcessNameOrId = processInfo.AppName,
- DumpFileName = $"{processInfo.LastVersion}_fail.dmp",
- FailFileName = $"{processInfo.LastVersion}_fail.json",
- TargetPath = processInfo.InstallPath,
- FailDirectory = Path.Combine(processInfo.InstallPath, "fail", processInfo.LastVersion),
- BackupDirectory = Path.Combine(processInfo.InstallPath, processInfo.LastVersion),
- ExtendedField = processInfo.LastVersion,
- };
- }
-
// ---- Helpers ----
private static string? FindDumpFile(BowlContext context)
diff --git a/src/c#/GeneralUpdate.Bowl/BowlContext.cs b/src/c#/GeneralUpdate.Bowl/BowlContext.cs
index 41377b71..0eae0ede 100644
--- a/src/c#/GeneralUpdate.Bowl/BowlContext.cs
+++ b/src/c#/GeneralUpdate.Bowl/BowlContext.cs
@@ -6,7 +6,6 @@ namespace GeneralUpdate.Bowl;
///
/// Immutable execution context for Bowl surveillance.
-/// Replaces the mutable .
///
public readonly record struct BowlContext
{
diff --git a/src/c#/GeneralUpdate.Bowl/Configuration/ProcessContract.cs b/src/c#/GeneralUpdate.Bowl/Configuration/ProcessContract.cs
deleted file mode 100644
index ec83805f..00000000
--- a/src/c#/GeneralUpdate.Bowl/Configuration/ProcessContract.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace GeneralUpdate.Bowl.Configuration;
-
-///
-/// Minimal ProcessContract for Bowl — only the fields needed for crash monitoring and rollback.
-///
-public class ProcessContract
-{
- [JsonPropertyName("AppName")]
- public string AppName { get; set; } = string.Empty;
-
- [JsonPropertyName("InstallPath")]
- public string InstallPath { get; set; } = string.Empty;
-
- [JsonPropertyName("LastVersion")]
- public string LastVersion { get; set; } = string.Empty;
-}
diff --git a/src/c#/GeneralUpdate.Bowl/FileSystem/StorageHelper.cs b/src/c#/GeneralUpdate.Bowl/FileSystem/StorageHelper.cs
index 3e028d86..3a270fca 100644
--- a/src/c#/GeneralUpdate.Bowl/FileSystem/StorageHelper.cs
+++ b/src/c#/GeneralUpdate.Bowl/FileSystem/StorageHelper.cs
@@ -7,7 +7,7 @@ namespace GeneralUpdate.Bowl.FileSystem;
///
/// Minimal file system utilities for Bowl (backup restore, directory cleanup, JSON serialization).
///
-public static class StorageHelper
+internal static class StorageHelper
{
public static void Restore(string backupPath, string sourcePath)
{
diff --git a/src/c#/GeneralUpdate.Bowl/Internal/Crash.cs b/src/c#/GeneralUpdate.Bowl/Internal/Crash.cs
index 2b783c11..2d036521 100644
--- a/src/c#/GeneralUpdate.Bowl/Internal/Crash.cs
+++ b/src/c#/GeneralUpdate.Bowl/Internal/Crash.cs
@@ -1,11 +1,37 @@
-using System.Collections.Generic;
-using GeneralUpdate.Bowl.Strategys;
+using System.Collections.Generic;
namespace GeneralUpdate.Bowl.Internal;
+///
+/// Crash report data transfer object.
+/// Serialized to JSON as the fail report when a crash is detected.
+///
internal class Crash
{
- public MonitorParameter Parameter { get; set; }
-
- public List ProcdumpOutPutLines { get; set; }
-}
\ No newline at end of file
+ /// Application install root path.
+ public string? TargetPath { get; set; }
+
+ /// Directory for failure artifacts.
+ public string? FailDirectory { get; set; }
+
+ /// Backup directory path.
+ public string? BackupDirectory { get; set; }
+
+ /// The name or PID of the monitored process.
+ public string? ProcessNameOrId { get; set; }
+
+ /// Dump file name.
+ public string? DumpFileName { get; set; }
+
+ /// Crash report file name.
+ public string? FailFileName { get; set; }
+
+ /// Work mode: "Upgrade" or "Normal".
+ public string? WorkModel { get; set; }
+
+ /// Extended field, typically the version number.
+ public string? ExtendedField { get; set; }
+
+ /// Captured stdout/stderr lines from the procdump child process.
+ public List ProcdumpOutPutLines { get; set; } = new();
+}
diff --git a/src/c#/GeneralUpdate.Bowl/Internal/CrashReporter.cs b/src/c#/GeneralUpdate.Bowl/Internal/CrashReporter.cs
index ce258c8a..f64c2735 100644
--- a/src/c#/GeneralUpdate.Bowl/Internal/CrashReporter.cs
+++ b/src/c#/GeneralUpdate.Bowl/Internal/CrashReporter.cs
@@ -4,13 +4,11 @@
using System.Threading;
using System.Threading.Tasks;
using GeneralUpdate.Bowl.FileSystem;
-using GeneralUpdate.Bowl;
namespace GeneralUpdate.Bowl.Internal;
///
/// Default crash reporter that serializes a record to JSON.
-/// Replaces the inline CreateCrash() method in the old WindowStrategy.
///
internal sealed class CrashReporter : ICrashReporter
{
@@ -23,17 +21,14 @@ public Task GenerateReportAsync(
var crash = new Crash
{
- Parameter = new Strategys.MonitorParameter
- {
- TargetPath = context.TargetPath,
- FailDirectory = context.FailDirectory,
- BackupDirectory = context.BackupDirectory,
- ProcessNameOrId = context.ProcessNameOrId,
- DumpFileName = context.DumpFileName,
- FailFileName = context.FailFileName,
- WorkModel = context.WorkModel,
- ExtendedField = context.ExtendedField,
- },
+ TargetPath = context.TargetPath,
+ FailDirectory = context.FailDirectory,
+ BackupDirectory = context.BackupDirectory,
+ ProcessNameOrId = context.ProcessNameOrId,
+ DumpFileName = context.DumpFileName,
+ FailFileName = context.FailFileName,
+ WorkModel = context.WorkModel,
+ ExtendedField = context.ExtendedField,
ProcdumpOutPutLines = new List(outputLines),
};
diff --git a/src/c#/GeneralUpdate.Bowl/Internal/EnvironmentProvider.cs b/src/c#/GeneralUpdate.Bowl/Internal/EnvironmentProvider.cs
deleted file mode 100644
index 2b4ff58b..00000000
--- a/src/c#/GeneralUpdate.Bowl/Internal/EnvironmentProvider.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using GeneralUpdate.Bowl.Configuration;
-using GeneralUpdate.Bowl.Ipc;
-
-namespace GeneralUpdate.Bowl.Internal;
-
-///
-/// Environment variable accessor. Abstracts away static environment APIs for testability.
-///
-internal interface IEnvironmentProvider
-{
- /// Gets an environment variable value. Returns null if not set.
- string? GetVariable(string name);
-
- /// Sets an environment variable.
- void SetVariable(string name, string value);
-}
-
-///
-/// Default environment provider backed by .
-///
-internal sealed class EnvironmentProvider : IEnvironmentProvider
-{
- public string? GetVariable(string name)
- => Environments.GetEnvironmentVariable(name);
-
- public void SetVariable(string name, string value)
- => Environments.SetEnvironmentVariable(name, value);
-}
diff --git a/src/c#/GeneralUpdate.Bowl/Internal/LinuxSystem.cs b/src/c#/GeneralUpdate.Bowl/Internal/LinuxSystem.cs
deleted file mode 100644
index 66903efb..00000000
--- a/src/c#/GeneralUpdate.Bowl/Internal/LinuxSystem.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace GeneralUpdate.Bowl.Internal;
-
-internal class LinuxSystem
-{
- internal string Name { get; set; }
-
- internal string Version { get; set; }
-
- internal LinuxSystem(string name, string version)
- {
- Name = name;
- Version = version;
- }
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Internal/SystemInfoProviderFactory.cs b/src/c#/GeneralUpdate.Bowl/Internal/SystemInfoProviderFactory.cs
index 37926833..8fa5a77f 100644
--- a/src/c#/GeneralUpdate.Bowl/Internal/SystemInfoProviderFactory.cs
+++ b/src/c#/GeneralUpdate.Bowl/Internal/SystemInfoProviderFactory.cs
@@ -12,7 +12,7 @@ public static ISystemInfoProvider Create()
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return new WindowsSystemInfoProvider();
- // Linux and macOS: no-op for now (could export journalctl / syslog in future)
+ // Linux: no-op for now (could export journalctl / syslog in future)
return new NoOpSystemInfoProvider();
}
diff --git a/src/c#/GeneralUpdate.Bowl/Ipc/Environments.cs b/src/c#/GeneralUpdate.Bowl/Ipc/Environments.cs
deleted file mode 100644
index 0f1a82b6..00000000
--- a/src/c#/GeneralUpdate.Bowl/Ipc/Environments.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.IO;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace GeneralUpdate.Bowl.Ipc;
-
-///
-/// Secure IPC environment variable provider.
-/// AES-encrypted temp files in a dedicated subdirectory, auto-deleted after read.
-///
-public static class Environments
-{
- private static readonly byte[] _aesKey = SHA256.Create()
- .ComputeHash(Encoding.UTF8.GetBytes("GeneralUpdate.IPC.EnvironmentProvider.v1"));
- private static readonly byte[] _aesIV = new byte[16] { 0x47, 0x55, 0x50, 0x44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-
- private static string IpcDir
- {
- get
- {
- var dir = Path.Combine(Path.GetTempPath(), "GeneralUpdate", "ipc");
- Directory.CreateDirectory(dir);
- return dir;
- }
- }
-
- public static void SetEnvironmentVariable(string key, string value)
- {
- var filePath = Path.Combine(IpcDir, $"{key}.enc");
- var plainBytes = Encoding.UTF8.GetBytes(value);
- IpcEncryption.EncryptToFile(plainBytes, filePath, _aesKey, _aesIV);
- }
-
- public static string GetEnvironmentVariable(string key)
- {
- var filePath = Path.Combine(Path.GetTempPath(), "GeneralUpdate", "ipc", $"{key}.enc");
- var plainBytes = IpcEncryption.DecryptFromFile(filePath, _aesKey, _aesIV);
- return plainBytes != null ? Encoding.UTF8.GetString(plainBytes) : string.Empty;
- }
-}
diff --git a/src/c#/GeneralUpdate.Bowl/Ipc/IpcEncryption.cs b/src/c#/GeneralUpdate.Bowl/Ipc/IpcEncryption.cs
deleted file mode 100644
index f5509c67..00000000
--- a/src/c#/GeneralUpdate.Bowl/Ipc/IpcEncryption.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.IO;
-using System.Security.Cryptography;
-
-namespace GeneralUpdate.Bowl.Ipc;
-
-///
-/// Shared AES encryption utilities for IPC.
-///
-public static class IpcEncryption
-{
- public static void EncryptToFile(byte[] plainBytes, string filePath, byte[] key, byte[] iv)
- {
- using var aes = Aes.Create();
- aes.Key = key;
- aes.IV = iv;
- using var encryptor = aes.CreateEncryptor();
- var cipher = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
- File.WriteAllBytes(filePath, cipher);
- }
-
- public static byte[]? DecryptFromFile(string filePath, byte[] key, byte[] iv)
- {
- if (!File.Exists(filePath)) return null;
-
- try
- {
- var cipher = File.ReadAllBytes(filePath);
- using var aes = Aes.Create();
- aes.Key = key;
- aes.IV = iv;
- using var decryptor = aes.CreateDecryptor();
- return decryptor.TransformFinalBlock(cipher, 0, cipher.Length);
- }
- finally
- {
- try { File.Delete(filePath); } catch { /* best-effort cleanup */ }
- }
- }
-}
diff --git a/src/c#/GeneralUpdate.Bowl/Strategies/LinuxBowlStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategies/LinuxBowlStrategy.cs
index e7b55322..811d356a 100644
--- a/src/c#/GeneralUpdate.Bowl/Strategies/LinuxBowlStrategy.cs
+++ b/src/c#/GeneralUpdate.Bowl/Strategies/LinuxBowlStrategy.cs
@@ -4,15 +4,12 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using GeneralUpdate.Bowl.Internal;
using GeneralUpdate.Bowl;
namespace GeneralUpdate.Bowl.Strategies;
///
/// Linux crash surveillance strategy using procdump.
-/// Fixes the dead-code issue in the legacy LinuxStrategy — the strategy factory
-/// now correctly creates this strategy on Linux platforms.
///
internal sealed class LinuxBowlStrategy : IBowlStrategy
{
@@ -27,39 +24,37 @@ internal sealed class LinuxBowlStrategy : IBowlStrategy
["clearos"] = "procdump-3.3.0-0.cm2.x86_64.rpm",
};
- private bool _procdumpInstalled;
+ private bool _probed;
+ private bool _procdumpAvailable;
+ private string? _lastFailReason;
public ProcessStartInfo? Prepare(in BowlContext context)
{
- // Lazy install procdump if not already done
- if (!_procdumpInstalled)
+ if (!_probed)
{
- var installed = TryInstallProcdump();
- if (!installed)
- {
- GeneralTracer.Warn(
- "LinuxBowlStrategy.Prepare: procdump installation failed on this system.");
- _procdumpInstalled = false;
- // Don't throw; return null signals "tool unavailable" to Bowl (graceful degradation)
- }
- else
- {
- _procdumpInstalled = true;
- }
+ _procdumpAvailable = ProbeProcdump(context.FailDirectory);
+ _probed = true;
}
- if (!_procdumpInstalled)
+ if (!_procdumpAvailable)
return null;
var dumpFullPath = Path.Combine(context.FailDirectory, context.DumpFileName);
EnsureDirectory(context.FailDirectory);
- GeneralTracer.Info($"LinuxBowlStrategy.Prepare: target={context.ProcessNameOrId}, dump={dumpFullPath}");
+ // Detect whether the target is a PID (all digits) or a process name.
+ // Linux procdump uses -p for PID and -w for process name.
+ var isPid = long.TryParse(context.ProcessNameOrId, out _);
+ var flag = isPid ? "-p" : "-w";
+
+ GeneralTracer.Info(
+ $"LinuxBowlStrategy.Prepare: target='{context.ProcessNameOrId}' ({(isPid ? "PID" : "name")}), " +
+ $"flag={flag}, dump={dumpFullPath}");
return new ProcessStartInfo
{
FileName = "procdump",
- Arguments = $"-p {context.ProcessNameOrId} -o \"{dumpFullPath}\"",
+ Arguments = $"{flag} {context.ProcessNameOrId} -o \"{dumpFullPath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
@@ -70,19 +65,46 @@ internal sealed class LinuxBowlStrategy : IBowlStrategy
public Task PostProcessAsync(in BowlContext context,
ProcessExitResult exitResult, CancellationToken ct)
{
- // No additional Linux-specific post-processing at this time.
return Task.CompletedTask;
}
- private static bool TryInstallProcdump()
+ // ---- Probe: check if procdump is available, install if not ----
+
+ ///
+ /// Returns true if procdump can be used.
+ /// Writes a diagnostic file when the environment is unsupported.
+ ///
+ private bool ProbeProcdump(string failDirectory)
{
+ // 1. Already in PATH?
+ if (IsProcdumpInPath())
+ {
+ GeneralTracer.Info("LinuxBowlStrategy: procdump found in PATH, skipping install.");
+ return true;
+ }
+
+ // 2. Detect distro
var distro = DetectDistro();
+ if (string.IsNullOrEmpty(distro))
+ {
+ _lastFailReason = "Cannot detect Linux distribution: /etc/os-release not found.";
+ WriteUnsupportedHint(failDirectory, _lastFailReason);
+ GeneralTracer.Warn($"LinuxBowlStrategy: {_lastFailReason}");
+ return false;
+ }
+
+ // 3. Check if we have a matching package
if (!DistroPackageMap.TryGetValue(distro, out var package))
{
- GeneralTracer.Warn($"LinuxBowlStrategy: unsupported distro '{distro}', cannot install procdump.");
+ _lastFailReason =
+ $"Unsupported Linux distribution: '{distro}'. " +
+ $"Supported distributions: {string.Join(", ", DistroPackageMap.Keys)}.";
+ WriteUnsupportedHint(failDirectory, _lastFailReason);
+ GeneralTracer.Warn($"LinuxBowlStrategy: {_lastFailReason}");
return false;
}
+ // 4. Locate bundled package and install script
var appDir = Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, "Applications", "Linux");
var scriptPath = Path.Combine(appDir, "install.sh");
@@ -90,18 +112,94 @@ private static bool TryInstallProcdump()
if (!File.Exists(scriptPath))
{
- GeneralTracer.Error($"LinuxBowlStrategy: install.sh not found at {scriptPath}.");
+ _lastFailReason = $"install.sh not found at {scriptPath}.";
+ WriteUnsupportedHint(failDirectory, _lastFailReason);
+ GeneralTracer.Error($"LinuxBowlStrategy: {_lastFailReason}");
return false;
}
if (!File.Exists(packagePath))
{
- GeneralTracer.Error($"LinuxBowlStrategy: package not found at {packagePath}.");
+ _lastFailReason = $"procdump package not found at {packagePath}.";
+ WriteUnsupportedHint(failDirectory, _lastFailReason);
+ GeneralTracer.Error($"LinuxBowlStrategy: {_lastFailReason}");
return false;
}
- GeneralTracer.Info($"LinuxBowlStrategy: installing {package} via install.sh.");
- return RunInstallScript(scriptPath, packagePath);
+ // 5. Run install script
+ GeneralTracer.Info($"LinuxBowlStrategy: installing {package} via install.sh for distro '{distro}'.");
+ var installed = RunInstallScript(scriptPath, packagePath);
+ if (!installed)
+ {
+ _lastFailReason =
+ $"Failed to install procdump package '{package}' for distribution '{distro}'. " +
+ "Check that sudo is available without an interactive password prompt.";
+ WriteUnsupportedHint(failDirectory, _lastFailReason);
+ GeneralTracer.Error($"LinuxBowlStrategy: {_lastFailReason}");
+ return false;
+ }
+
+ GeneralTracer.Info("LinuxBowlStrategy: procdump installed successfully.");
+ return true;
+ }
+
+ // ---- Helpers ----
+
+ private static bool IsProcdumpInPath()
+ {
+ try
+ {
+ using var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = "/usr/bin/which",
+ Arguments = "procdump",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ },
+ };
+ process.Start();
+ var exited = process.WaitForExit(5000);
+ if (!exited)
+ {
+ process.Kill();
+ return false;
+ }
+ return process.ExitCode == 0;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static void WriteUnsupportedHint(string failDirectory, string reason)
+ {
+ try
+ {
+ Directory.CreateDirectory(failDirectory);
+ var hintPath = Path.Combine(failDirectory, "bowl_linux_unsupported.txt");
+ var content =
+ $"Bowl Linux Strategy — Unsupported Environment\n" +
+ $"================================================\n" +
+ $"Reason: {reason}\n" +
+ $"Timestamp: {DateTime.UtcNow:O}\n" +
+ $"\n" +
+ $"Supported distributions: {string.Join(", ", DistroPackageMap.Keys)}\n" +
+ $"\n" +
+ $"To use Bowl on this system, install procdump manually and ensure it is\n" +
+ $"available in PATH. Bowl will skip the automatic install if procdump\n" +
+ $"is already present.\n";
+ File.WriteAllText(hintPath, content);
+ GeneralTracer.Info($"LinuxBowlStrategy: unsupported hint written to {hintPath}.");
+ }
+ catch (Exception ex)
+ {
+ GeneralTracer.Error("LinuxBowlStrategy: failed to write unsupported hint.", ex);
+ }
}
private static bool RunInstallScript(string script, string package)
diff --git a/src/c#/GeneralUpdate.Bowl/Strategies/MacBowlStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategies/MacBowlStrategy.cs
deleted file mode 100644
index ac3f7b28..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategies/MacBowlStrategy.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using GeneralUpdate.Bowl;
-
-namespace GeneralUpdate.Bowl.Strategies;
-
-///
-/// macOS crash surveillance strategy.
-/// Uses the built-in lldb debugger for crash capture (procdump does not support macOS).
-///
-///
-/// Note: lldb requires the process being debugged to allow debugging
-/// (SIP / task_for_pid restrictions). For production macOS crash capture, consider
-/// using Crashpad (https://chromium.googlesource.com/crashpad/crashpad/) instead.
-/// This implementation provides a basic stub that can be extended.
-///
-internal sealed class MacBowlStrategy : IBowlStrategy
-{
- public ProcessStartInfo? Prepare(in BowlContext context)
- {
- if (!IsLldbAvailable())
- {
- GeneralTracer.Warn(
- "MacBowlStrategy.Prepare: lldb not available. macOS crash monitoring is unavailable.");
- return null;
- }
-
- var dumpFullPath = Path.Combine(context.FailDirectory, context.DumpFileName);
- EnsureDirectory(context.FailDirectory);
-
- // lldb batch mode: attach to process by name, save core dump, quit.
- // Use separate -o arguments per command to avoid nested quoting issues.
- GeneralTracer.Info($"MacBowlStrategy.Prepare: target={context.ProcessNameOrId}");
-
- return new ProcessStartInfo
- {
- FileName = "/usr/bin/lldb",
- Arguments = string.Concat(
- "--batch",
- $" -o \"process attach --name {context.ProcessNameOrId} --waitfor\"",
- $" -o \"process save-core {dumpFullPath}\"",
- " -o quit"),
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- };
- }
-
- public Task PostProcessAsync(in BowlContext context,
- ProcessExitResult exitResult, CancellationToken ct)
- {
- return Task.CompletedTask;
- }
-
- private static bool IsLldbAvailable()
- {
- try
- {
- using var process = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = "/usr/bin/which",
- Arguments = "lldb",
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- },
- };
- process.Start();
- var exited = process.WaitForExit(3000);
- if (!exited)
- {
- process.Kill();
- return false;
- }
- return process.ExitCode == 0;
- }
- catch
- {
- return false;
- }
- }
-
- private static void EnsureDirectory(string path)
- {
- if (Directory.Exists(path))
- Directory.Delete(path, recursive: true);
- Directory.CreateDirectory(path);
- }
-}
diff --git a/src/c#/GeneralUpdate.Bowl/Strategies/StrategyFactory.cs b/src/c#/GeneralUpdate.Bowl/Strategies/StrategyFactory.cs
index 0e863c3c..a38ef0f6 100644
--- a/src/c#/GeneralUpdate.Bowl/Strategies/StrategyFactory.cs
+++ b/src/c#/GeneralUpdate.Bowl/Strategies/StrategyFactory.cs
@@ -21,9 +21,6 @@ public static IBowlStrategy Create()
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return new LinuxBowlStrategy();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- return new MacBowlStrategy();
-
throw new PlatformNotSupportedException(
$"Bowl does not support the current platform: {RuntimeInformation.OSDescription}");
}
diff --git a/src/c#/GeneralUpdate.Bowl/Strategys/AbstractStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategys/AbstractStrategy.cs
deleted file mode 100644
index 696e3560..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategys/AbstractStrategy.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using GeneralUpdate.Bowl.FileSystem;
-using GeneralUpdate.Bowl;
-
-namespace GeneralUpdate.Bowl.Strategys;
-
-[System.Obsolete("Use Strategies.IBowlStrategy instead. Will be removed in v10.")]
-internal abstract class AbstractStrategy : IStrategy
-{
- protected MonitorParameter _parameter;
- protected List OutputList = new ();
-
- public void SetParameter(MonitorParameter parameter) => _parameter = parameter;
-
- public virtual void Launch()
- {
- GeneralTracer.Info($"AbstractStrategy.Launch: starting inner application. App={_parameter.InnerApp}, Args={_parameter.InnerArguments}");
- Startup(_parameter.InnerApp, _parameter.InnerArguments);
- GeneralTracer.Info("AbstractStrategy.Launch: inner application process finished.");
- }
-
- private void Startup(string appName, string arguments)
- {
- GeneralTracer.Info($"AbstractStrategy.Startup: preparing process. FileName={appName}");
- if (Directory.Exists(_parameter.FailDirectory))
- {
- GeneralTracer.Info($"AbstractStrategy.Startup: removing existing fail directory: {_parameter.FailDirectory}");
- StorageHelper.DeleteDirectory(_parameter.FailDirectory);
- }
- Directory.CreateDirectory(_parameter.FailDirectory);
- GeneralTracer.Info($"AbstractStrategy.Startup: fail directory created: {_parameter.FailDirectory}");
-
- var startInfo = new ProcessStartInfo
- {
- FileName = appName,
- Arguments = arguments,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true
- };
-
- var process = new Process { StartInfo = startInfo };
- process.OutputDataReceived += OutputHandler;
- process.ErrorDataReceived += OutputHandler;
- process.Start();
- GeneralTracer.Info($"AbstractStrategy.Startup: process started. PID={process.Id}");
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- process.WaitForExit(1000 * 10);
- GeneralTracer.Info($"AbstractStrategy.Startup: process exited. ExitCode={process.ExitCode}");
- }
-
- private void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
- {
- var data = outLine.Data;
- if (!string.IsNullOrEmpty(data))
- {
- GeneralTracer.Debug($"AbstractStrategy.OutputHandler: {data}");
- OutputList.Add(data);
- }
- }
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Strategys/IStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategys/IStrategy.cs
deleted file mode 100644
index 694f9c81..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategys/IStrategy.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using GeneralUpdate.Bowl.Internal;
-
-namespace GeneralUpdate.Bowl.Strategys;
-
-[System.Obsolete("Use Strategies.IBowlStrategy instead. Will be removed in v10.")]
-internal interface IStrategy
-{
- void Launch();
-
- void SetParameter(MonitorParameter parameter);
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Strategys/LinuxStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategys/LinuxStrategy.cs
deleted file mode 100644
index 5244276c..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategys/LinuxStrategy.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using GeneralUpdate.Bowl.Internal;
-using GeneralUpdate.Bowl;
-
-namespace GeneralUpdate.Bowl.Strategys;
-
-[System.Obsolete("Use Strategies.LinuxBowlStrategy instead. Will be removed in v10.")]
-internal class LinuxStrategy : AbstractStrategy
-{
- /*procdump-3.3.0-0.cm2.x86_64.rpm:
- Compatible Systems: This RPM package may be suitable for certain CentOS or RHEL-based derivatives, specifically the CM2 version. CM2 typically refers to ClearOS 7.x or similar community-maintained versions.
-
- procdump-3.3.0-0.el8.x86_64.rpm:
- Compatible Systems: This RPM package is suitable for Red Hat Enterprise Linux 8 (RHEL 8), CentOS 8, and other RHEL 8-based distributions.
-
- procdump_3.3.0_amd64.deb:
- Compatible Systems: This DEB package is suitable for Debian and its derivatives, such as Ubuntu, for 64-bit systems (amd64 architecture).*/
-
- private IReadOnlyList _rocdumpAmd64 = new List { "Ubuntu", "Debian" };
- private IReadOnlyList procdump_el8_x86_64 = new List { "Red Hat", "CentOS", "Fedora" };
- private IReadOnlyList procdump_cm2_x86_64 = new List { "ClearOS" };
-
- public override void Launch()
- {
- GeneralTracer.Info("LinuxStrategy.Launch: starting Linux surveillance launch.");
- try
- {
- Install();
- GeneralTracer.Info("LinuxStrategy.Launch: procdump installation completed, invoking base launch.");
- base.Launch();
- GeneralTracer.Info("LinuxStrategy.Launch: launch lifecycle completed.");
- }
- catch (Exception ex)
- {
- GeneralTracer.Error("LinuxStrategy.Launch: exception occurred during Linux surveillance launch.", ex);
- throw;
- }
- }
-
- private void Install()
- {
- GeneralTracer.Info("LinuxStrategy.Install: determining procdump package and running install script.");
- string scriptPath = "./install.sh";
- string packageFile = GetPacketName();
-
- if (string.IsNullOrEmpty(packageFile))
- {
- GeneralTracer.Warn("LinuxStrategy.Install: no matching procdump package found for the current Linux distribution.");
- return;
- }
-
- GeneralTracer.Info($"LinuxStrategy.Install: executing install.sh with package={packageFile}.");
- ProcessStartInfo processStartInfo = new ProcessStartInfo()
- {
- FileName = "/bin/bash",
- Arguments = $"{scriptPath} {packageFile}",
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true
- };
-
- try
- {
- using Process process = Process.Start(processStartInfo);
- string output = process.StandardOutput.ReadToEnd();
- string error = process.StandardError.ReadToEnd();
- process.WaitForExit();
-
- if (!string.IsNullOrEmpty(output))
- GeneralTracer.Info($"LinuxStrategy.Install output: {output}");
-
- if (!string.IsNullOrEmpty(error))
- GeneralTracer.Warn($"LinuxStrategy.Install error output: {error}");
-
- if (process.ExitCode != 0)
- {
- GeneralTracer.Error($"LinuxStrategy.Install: install script exited with code {process.ExitCode}.");
- }
- else
- {
- GeneralTracer.Info("LinuxStrategy.Install: procdump installation succeeded.");
- }
- }
- catch (Exception e)
- {
- GeneralTracer.Error("LinuxStrategy.Install: exception occurred while running install script.", e);
- }
- }
-
- private string GetPacketName()
- {
- GeneralTracer.Info("LinuxStrategy.GetPacketName: detecting Linux distribution.");
- var packageFileName = string.Empty;
- var system = GetSystem();
- GeneralTracer.Info($"LinuxStrategy.GetPacketName: detected distribution={system.Name}, version={system.Version}.");
- if (_rocdumpAmd64.Contains(system.Name))
- {
- packageFileName = $"procdump_3.3.0_amd64.deb";
- }
- else if (procdump_el8_x86_64.Contains(system.Name))
- {
- packageFileName = $"procdump-3.3.0-0.el8.x86_64.rpm";
- }
- else if (procdump_cm2_x86_64.Contains(system.Name))
- {
- packageFileName = $"procdump-3.3.0-0.cm2.x86_64.rpm";
- }
-
- if (string.IsNullOrEmpty(packageFileName))
- GeneralTracer.Warn($"LinuxStrategy.GetPacketName: no matching package for distribution={system.Name}.");
- else
- GeneralTracer.Info($"LinuxStrategy.GetPacketName: resolved package={packageFileName}.");
-
- return packageFileName;
- }
-
- private LinuxSystem GetSystem()
- {
- GeneralTracer.Info("LinuxStrategy.GetSystem: reading /etc/os-release.");
- string osReleaseFile = "/etc/os-release";
- if (File.Exists(osReleaseFile))
- {
- var lines = File.ReadAllLines(osReleaseFile);
- string distro = string.Empty;
- string version = string.Empty;
-
- foreach (var line in lines)
- {
- if (line.StartsWith("ID="))
- {
- distro = line.Substring(3).Trim('\"');
- }
- else if (line.StartsWith("VERSION_ID="))
- {
- version = line.Substring(11).Trim('\"');
- }
- }
-
- GeneralTracer.Info($"LinuxStrategy.GetSystem: distro={distro}, version={version}.");
- return new LinuxSystem(distro, version);
- }
-
- GeneralTracer.Fatal("LinuxStrategy.GetSystem: /etc/os-release not found, cannot determine Linux distribution.");
- throw new FileNotFoundException("Cannot determine the Linux distribution. The /etc/os-release file does not exist.");
- }
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Strategys/MonitorParameter.cs b/src/c#/GeneralUpdate.Bowl/Strategys/MonitorParameter.cs
deleted file mode 100644
index b90e7675..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategys/MonitorParameter.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-namespace GeneralUpdate.Bowl.Strategys;
-
-[System.Obsolete("Use BowlContext instead. Will be removed in v10.")]
-public class MonitorParameter
-{
- public MonitorParameter() { }
-
- public string TargetPath { get; set; }
-
- public string FailDirectory { get; set; }
-
- public string BackupDirectory { get; set; }
-
- public string ProcessNameOrId { get; set; }
-
- public string DumpFileName { get; set; }
-
- public string FailFileName { get; set; }
-
- internal string InnerArguments { get; set; }
-
- internal string InnerApp { get; set; }
-
- ///
- /// Upgrade: upgrade mode. This mode is primarily used in conjunction with GeneralUpdate for internal use. Please do not modify it arbitrarily when the default mode is activated.
- /// Normal: Normal mode,This mode can be used independently to monitor a single program. If the program crashes, it will export the crash information.
- ///
- public string WorkModel { get; set; } = "Upgrade";
-
- public string ExtendedField { get; set; }
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Strategys/WindowStrategy.cs b/src/c#/GeneralUpdate.Bowl/Strategys/WindowStrategy.cs
deleted file mode 100644
index f57a3da4..00000000
--- a/src/c#/GeneralUpdate.Bowl/Strategys/WindowStrategy.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
-using GeneralUpdate.Bowl.Internal;
-using GeneralUpdate.Bowl.FileSystem;
-using GeneralUpdate.Bowl.Configuration;
-using GeneralUpdate.Bowl.Ipc;
-using GeneralUpdate.Bowl;
-
-namespace GeneralUpdate.Bowl.Strategys;
-
-[System.Obsolete("Use Strategies.WindowsBowlStrategy instead. Will be removed in v10.")]
-internal class WindowStrategy : AbstractStrategy
-{
- private const string WorkModel = "Upgrade";
- private string? _applicationsDirectory;
- private List _actions = new();
-
- public override void Launch()
- {
- GeneralTracer.Info("WindowStrategy.Launch: initializing actions pipeline.");
- InitializeActions();
- _applicationsDirectory = Path.Combine(_parameter.TargetPath, "Applications", "Windows");
- _parameter.InnerApp = Path.Combine(_applicationsDirectory, GetAppName());
- var dmpFullName = Path.Combine(_parameter.FailDirectory, _parameter.DumpFileName);
- _parameter.InnerArguments = $"-e -ma {_parameter.ProcessNameOrId} {dmpFullName}";
- GeneralTracer.Info($"WindowStrategy.Launch: launching inner app={_parameter.InnerApp}, dumpFile={dmpFullName}.");
- //This method is used to launch scripts in applications.
- base.Launch();
- GeneralTracer.Info("WindowStrategy.Launch: base launch completed, executing final treatment.");
- ExecuteFinalTreatment();
- GeneralTracer.Info("WindowStrategy.Launch: launch lifecycle completed.");
- }
-
- private string GetAppName()
- {
- var name = RuntimeInformation.OSArchitecture switch
- {
- Architecture.X86 => "procdump.exe",
- Architecture.X64 => "procdump64.exe",
- _ => "procdump64a.exe"
- };
- GeneralTracer.Info($"WindowStrategy.GetAppName: resolved procdump executable={name} for arch={RuntimeInformation.OSArchitecture}.");
- return name;
- }
-
- private void ExecuteFinalTreatment()
- {
- var dumpFile = Path.Combine(_parameter.FailDirectory, _parameter.DumpFileName);
- GeneralTracer.Info($"WindowStrategy.ExecuteFinalTreatment: checking for dump file at {dumpFile}.");
- if (File.Exists(dumpFile))
- {
- GeneralTracer.Info($"WindowStrategy.ExecuteFinalTreatment: dump file found, executing {_actions.Count} remediation action(s).");
- foreach (var action in _actions)
- {
- action.Invoke();
- }
- GeneralTracer.Info("WindowStrategy.ExecuteFinalTreatment: all remediation actions completed.");
- }
- else
- {
- GeneralTracer.Info("WindowStrategy.ExecuteFinalTreatment: no dump file found, monitored process exited normally.");
- }
- }
-
- private void InitializeActions()
- {
- _actions.Add(CreateCrash);
- _actions.Add(Export);
- _actions.Add(Restore);
- _actions.Add(SetEnvironment);
- GeneralTracer.Debug("WindowStrategy.InitializeActions: registered actions: CreateCrash, Export, Restore, SetEnvironment.");
- }
-
- ///
- /// Export the crash output information from procdump.exe and the monitoring parameters of Bowl.
- ///
- private void CreateCrash()
- {
- GeneralTracer.Info("WindowStrategy.CreateCrash: serializing crash report.");
- var crash = new Crash
- {
- Parameter = _parameter,
- ProcdumpOutPutLines = OutputList
- };
- var failJsonPath = Path.Combine(_parameter.FailDirectory, _parameter.FailFileName);
- StorageHelper.CreateJson(failJsonPath, crash);
- GeneralTracer.Info($"WindowStrategy.CreateCrash: crash report written to {failJsonPath}.");
- }
-
- ///
- /// Export operating system information, system logs, and system driver information.
- ///
- private void Export()
- {
- GeneralTracer.Info("WindowStrategy.Export: exporting OS and system diagnostic information.");
- var batPath = Path.Combine(_applicationsDirectory, "export.bat");
- if (!File.Exists(batPath))
- {
- GeneralTracer.Error($"WindowStrategy.Export: export.bat not found at {batPath}.");
- throw new FileNotFoundException("export.bat not found!");
- }
-
- Process.Start(batPath, _parameter.FailDirectory);
- GeneralTracer.Info($"WindowStrategy.Export: export.bat started targeting {_parameter.FailDirectory}.");
- }
-
- ///
- /// Within the GeneralUpdate upgrade system, restore the specified backup version files to the current working directory.
- ///
- private void Restore()
- {
- GeneralTracer.Info($"WindowStrategy.Restore: checking work model. CurrentModel={_parameter.WorkModel}, ExpectedModel={WorkModel}.");
- if (string.Equals(_parameter.WorkModel, WorkModel))
- {
- GeneralTracer.Info($"WindowStrategy.Restore: restoring backup from {_parameter.BackupDirectory} to {_parameter.TargetPath}.");
- StorageHelper.Restore(_parameter.BackupDirectory, _parameter.TargetPath);
- GeneralTracer.Info("WindowStrategy.Restore: restore completed successfully.");
- }
- else
- {
- GeneralTracer.Info("WindowStrategy.Restore: restore skipped, work model is not Upgrade.");
- }
- }
-
- ///
- /// Write the failed update version number to the local environment variable.
- ///
- private void SetEnvironment()
- {
- if (!string.Equals(_parameter.WorkModel, WorkModel))
- {
- GeneralTracer.Info("WindowStrategy.SetEnvironment: skipped, work model is not Upgrade.");
- return;
- }
-
- /*
- * The `UpgradeFail` environment variable is used to mark an exception version number during updates.
- * If the latest version number obtained via an HTTP request is less than or equal to the exception version number, the update is skipped.
- * Once this version number is set, it will not be removed, and updates will not proceed until a version greater than the exception version number is obtained through the HTTP request.
- */
- Environments.SetEnvironmentVariable("UpgradeFail", _parameter.ExtendedField);
- GeneralTracer.Warn($"WindowStrategy.SetEnvironment: UpgradeFail environment variable set to version={_parameter.ExtendedField}.");
- }
-}
\ No newline at end of file
diff --git a/src/c#/GeneralUpdate.Bowl/Tracer/TextTraceListener.cs b/src/c#/GeneralUpdate.Bowl/Tracer/TextTraceListener.cs
index bd513e72..ddf4a46e 100644
--- a/src/c#/GeneralUpdate.Bowl/Tracer/TextTraceListener.cs
+++ b/src/c#/GeneralUpdate.Bowl/Tracer/TextTraceListener.cs
@@ -4,7 +4,9 @@
using System.Collections.Concurrent;
using System.Threading;
-public class TextTraceListener : TraceListener, IDisposable
+namespace GeneralUpdate.Bowl;
+
+internal class TextTraceListener : TraceListener, IDisposable
{
private readonly string _filePath;
private readonly BlockingCollection _messageQueue;
diff --git a/src/c#/GeneralUpdate.Bowl/Tracer/WindowsOutputDebugListener.cs b/src/c#/GeneralUpdate.Bowl/Tracer/WindowsOutputDebugListener.cs
index 0dc55624..fe4c397b 100644
--- a/src/c#/GeneralUpdate.Bowl/Tracer/WindowsOutputDebugListener.cs
+++ b/src/c#/GeneralUpdate.Bowl/Tracer/WindowsOutputDebugListener.cs
@@ -4,7 +4,7 @@
namespace GeneralUpdate.Bowl;
-public class WindowsOutputDebugListener : TraceListener
+internal class WindowsOutputDebugListener : TraceListener
{
///
/// Does not affect .NET AOT compilation and runtime on the Windows platform, provided that the following conditions are met:
diff --git a/tests/BowlTest/Integration/BowlCrashPipelineTests.cs b/tests/BowlTest/Integration/BowlCrashPipelineTests.cs
index bd5d169b..4520be91 100644
--- a/tests/BowlTest/Integration/BowlCrashPipelineTests.cs
+++ b/tests/BowlTest/Integration/BowlCrashPipelineTests.cs
@@ -38,7 +38,7 @@ public void Dispose()
}
///
- /// Simulates a full crash ?dump ?report ?callback pipeline.
+ /// Simulates a full crash �?dump �?report �?callback pipeline.
/// Produces real files that can be inspected on disk.
///
[Fact]
@@ -206,7 +206,7 @@ public async Task SimulatedCrash_NormalMode_DoesNotRestore()
var bowl = CreateBowl(mockStrategy);
var result = await bowl.LaunchAsync(context);
- _output.WriteLine($"Normal mode ?Restored: {result.Restored}, DumpCaptured: {result.DumpCaptured}");
+ _output.WriteLine($"Normal mode �?Restored: {result.Restored}, DumpCaptured: {result.DumpCaptured}");
Assert.True(result.DumpCaptured);
Assert.False(result.Restored, "Normal mode should NOT restore backup");
@@ -242,7 +242,7 @@ public async Task NoCrash_NoDumpFile_ReturnsSuccess()
var bowl = CreateBowl(mockStrategy);
var result = await bowl.LaunchAsync(context);
- _output.WriteLine($"Healthy ?Success: {result.Success}, DumpCaptured: {result.DumpCaptured}");
+ _output.WriteLine($"Healthy �?Success: {result.Success}, DumpCaptured: {result.DumpCaptured}");
Assert.False(result.DumpCaptured, "No dump should be captured for healthy process");
Assert.Equal(0, result.ExitCode);
@@ -253,7 +253,7 @@ public async Task NoCrash_NoDumpFile_ReturnsSuccess()
private static Bowl CreateBowl(IBowlStrategy strategy)
{
return new Bowl(strategy, new CrashReporter(),
- new NoOpInfoProvider(), new MockEnvironmentProvider());
+ new NoOpInfoProvider());
}
///
@@ -325,15 +325,5 @@ public Task ExportAsync(string outputDirectory, CancellationToken ct)
=> Task.CompletedTask;
}
- private sealed class MockEnvironmentProvider : IEnvironmentProvider
- {
- private readonly Dictionary _vars = new();
-
- public string? GetVariable(string name)
- => _vars.TryGetValue(name, out var v) ? v : null;
-
- public void SetVariable(string name, string value)
- => _vars[name] = value;
- }
}
}
diff --git a/tests/BowlTest/Integration/BowlIntegrationTests.cs b/tests/BowlTest/Integration/BowlIntegrationTests.cs
index 4f0d1af8..caccd8e8 100644
--- a/tests/BowlTest/Integration/BowlIntegrationTests.cs
+++ b/tests/BowlTest/Integration/BowlIntegrationTests.cs
@@ -1,9 +1,6 @@
using System;
using System.IO;
-using System.Runtime.InteropServices;
-using System.Text.Json;
using GeneralUpdate.Bowl;
-using GeneralUpdate.Bowl.Strategys;
namespace BowlTest.Integration
{
@@ -37,118 +34,95 @@ public void Dispose()
}
///
- /// Tests Normal mode vs Upgrade mode behavior difference.
+ /// Tests Normal mode vs Upgrade mode behavior difference via BowlContext.
///
[Fact]
public void WorkModel_DifferentiatesBetweenNormalAndUpgradeMode()
{
- // Arrange
- var normalParameter = new MonitorParameter
- {
- WorkModel = "Normal"
- };
-
- var upgradeParameter = new MonitorParameter
- {
- WorkModel = "Upgrade"
- };
+ var normalCtx = new BowlContext { WorkModel = "Normal" };
+ var upgradeCtx = new BowlContext { WorkModel = "Upgrade" };
- // Assert
- Assert.Equal("Normal", normalParameter.WorkModel);
- Assert.Equal("Upgrade", upgradeParameter.WorkModel);
- Assert.NotEqual(normalParameter.WorkModel, upgradeParameter.WorkModel);
+ Assert.Equal("Normal", normalCtx.WorkModel);
+ Assert.Equal("Upgrade", upgradeCtx.WorkModel);
+ Assert.NotEqual(normalCtx.WorkModel, upgradeCtx.WorkModel);
}
///
- /// Tests that parameter paths are correctly constructed from ProcessContract.
+ /// Tests that fail and backup directory paths are constructed correctly from install path and version.
///
[Fact]
- public void ParameterConstruction_FromProcessContract_CreatesCorrectPaths()
+ public void BowlContext_PathConstruction_CreatesCorrectPaths()
{
- // Arrange
var installPath = "/path/to/install";
var version = "3.2.1";
- // Expected paths based on CreateParameter logic in Bowl.cs
var expectedFailDir = Path.Combine(installPath, "fail", version);
var expectedBackupDir = Path.Combine(installPath, version);
var expectedDumpFile = $"{version}_fail.dmp";
var expectedFailFile = $"{version}_fail.json";
- // Act - Create parameter manually with same logic
- var parameter = new MonitorParameter
+ var ctx = new BowlContext
{
TargetPath = installPath,
FailDirectory = expectedFailDir,
BackupDirectory = expectedBackupDir,
DumpFileName = expectedDumpFile,
FailFileName = expectedFailFile,
- ExtendedField = version
+ ExtendedField = version,
};
- // Assert
- Assert.Equal(expectedFailDir, parameter.FailDirectory);
- Assert.Equal(expectedBackupDir, parameter.BackupDirectory);
- Assert.Equal(expectedDumpFile, parameter.DumpFileName);
- Assert.Equal(expectedFailFile, parameter.FailFileName);
- Assert.Equal(version, parameter.ExtendedField);
- Assert.Contains(version, parameter.FailDirectory);
- Assert.Contains(version, parameter.BackupDirectory);
+ Assert.Equal(expectedFailDir, ctx.FailDirectory);
+ Assert.Equal(expectedBackupDir, ctx.BackupDirectory);
+ Assert.Equal(expectedDumpFile, ctx.DumpFileName);
+ Assert.Equal(expectedFailFile, ctx.FailFileName);
+ Assert.Equal(version, ctx.ExtendedField);
+ Assert.Contains(version, ctx.FailDirectory);
+ Assert.Contains(version, ctx.BackupDirectory);
}
///
- /// Tests that extended field can store version information.
+ /// Tests that ExtendedField can store version information.
///
[Fact]
- public void ExtendedField_StoresVersionEntryrmation()
+ public void ExtendedField_StoresVersionInformation()
{
- // Arrange
var versions = new[] { "1.0.0", "2.1.3", "10.5.2-beta" };
foreach (var version in versions)
{
- // Act
- var parameter = new MonitorParameter
- {
- ExtendedField = version
- };
-
- // Assert
- Assert.Equal(version, parameter.ExtendedField);
+ var ctx = new BowlContext { ExtendedField = version };
+ Assert.Equal(version, ctx.ExtendedField);
}
}
///
- /// Tests that ProcessContract JSON with all required fields parses correctly.
+ /// Tests Normalize applies expected defaults.
///
[Fact]
- public void ProcessContractJson_WithAllFields_ParsesCorrectly()
+ public void Normalize_AppliesDefaults_WorkModelTimeoutAndDumpType()
{
- // Arrange
- var json = @"{
- ""AppName"": ""MyApp.exe"",
- ""InstallPath"": ""/path/to/app"",
- ""LastVersion"": ""1.2.3""
- }";
-
- // Act
- var processInfo = JsonSerializer.Deserialize(json);
+ var ctx = new BowlContext { ProcessNameOrId = "test.exe" };
+ var normalized = ctx.Normalize();
- // Assert
- Assert.NotNull(processInfo);
- Assert.Equal("MyApp.exe", processInfo.AppName);
- Assert.Equal("/path/to/app", processInfo.InstallPath);
- Assert.Equal("1.2.3", processInfo.LastVersion);
+ Assert.Equal("Upgrade", normalized.WorkModel);
+ Assert.Equal(30_000, normalized.TimeoutMs);
+ Assert.Equal(DumpType.Full, normalized.DumpType);
}
///
- /// Helper class for ProcessContract JSON deserialization testing.
+ /// Tests that explicit WorkModel "Normal" is preserved after Normalize.
///
- private class ProcessContractDto
+ [Fact]
+ public void Normalize_PreservesExplicitNormalWorkModel()
{
- public string? AppName { get; set; }
- public string? InstallPath { get; set; }
- public string? LastVersion { get; set; }
+ var ctx = new BowlContext
+ {
+ ProcessNameOrId = "test.exe",
+ WorkModel = "Normal",
+ };
+ var normalized = ctx.Normalize();
+
+ Assert.Equal("Normal", normalized.WorkModel);
}
}
}
diff --git a/tests/BowlTest/Strategies/BowlAsyncTests.cs b/tests/BowlTest/Strategies/BowlAsyncTests.cs
index fd42fa70..849bc7c7 100644
--- a/tests/BowlTest/Strategies/BowlAsyncTests.cs
+++ b/tests/BowlTest/Strategies/BowlAsyncTests.cs
@@ -20,7 +20,7 @@ public void Bowl_Constructor_DoesNotThrow()
}
///
- /// LaunchAsync with non-existent procdump path ?either throws Win32Exception
+ /// LaunchAsync with non-existent procdump path �?either throws Win32Exception
/// (file not found) or returns a result with no dump captured.
///
[Fact]
@@ -53,37 +53,5 @@ public async Task LaunchAsync_WithInvalidAppPath_Throws()
}
}
- ///
- /// MapToContext correctly translates old MonitorParameter to new BowlContext.
- ///
- [Fact]
- public void MapToContext_TranslatesAllFields()
- {
- var old = new GeneralUpdate.Bowl.Strategys.MonitorParameter
- {
- ProcessNameOrId = "app.exe",
- DumpFileName = "crash.dmp",
- FailFileName = "crash.json",
- TargetPath = "/install",
- FailDirectory = "/install/fail/1.0",
- BackupDirectory = "/install/1.0",
- WorkModel = "Normal",
- ExtendedField = "2.0.0",
- };
-
- var ctx = Bowl.MapToContext(old);
-
- Assert.Equal("app.exe", ctx.ProcessNameOrId);
- Assert.Equal("crash.dmp", ctx.DumpFileName);
- Assert.Equal("crash.json", ctx.FailFileName);
- Assert.Equal("/install", ctx.TargetPath);
- Assert.Equal("/install/fail/1.0", ctx.FailDirectory);
- Assert.Equal("/install/1.0", 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);
- }
}
}
diff --git a/tests/BowlTest/Strategys/AbstractStrategyTests.cs b/tests/BowlTest/Strategys/AbstractStrategyTests.cs
deleted file mode 100644
index 44781715..00000000
--- a/tests/BowlTest/Strategys/AbstractStrategyTests.cs
+++ /dev/null
@@ -1,149 +0,0 @@
-using System;
-using System.IO;
-using System.Runtime.InteropServices;
-using GeneralUpdate.Bowl.Strategys;
-
-namespace BowlTest.Strategys
-{
- ///
- /// Contains test cases for the AbstractStrategy class behavior.
- /// Tests process launching, output handling, and directory management.
- /// Note: AbstractStrategy is internal, so we test through WindowStrategy.
- ///
- public class AbstractStrategyTests
- {
- ///
- /// Tests that ProcessNameOrId can accept process name.
- ///
- [Fact]
- public void ProcessNameOrId_AcceptsProcessName()
- {
- // Arrange & Act
- var parameter = new MonitorParameter
- {
- ProcessNameOrId = "myapp.exe"
- };
-
- // Assert
- Assert.Equal("myapp.exe", parameter.ProcessNameOrId);
- }
-
- ///
- /// Tests that ProcessNameOrId can accept process ID.
- ///
- [Fact]
- public void ProcessNameOrId_AcceptsProcessId()
- {
- // Arrange & Act
- var parameter = new MonitorParameter
- {
- ProcessNameOrId = "12345"
- };
-
- // Assert
- Assert.Equal("12345", parameter.ProcessNameOrId);
- }
-
- ///
- /// Tests that InnerArguments are constructed correctly for procdump.
- /// We can verify the expected format through the parameter values.
- ///
- [Fact]
- public void InnerArguments_ShouldContainProcdumpParameters()
- {
- // The InnerArguments should be in format: "-e -ma {ProcessNameOrId} {dumpFullPath}"
- // This is set by WindowStrategy before launching
-
- // Arrange
- var processName = "test.exe";
- var dumpFileName = "crash.dmp";
- var failDirectory = "/path/to/fail";
- var expectedDumpPath = Path.Combine(failDirectory, dumpFileName);
-
- // The format should be: -e -ma test.exe /path/to/fail/crash.dmp
- var expectedFormat = $"-e -ma {processName} {expectedDumpPath}";
-
- // Assert - Verify the expected format structure
- Assert.Contains("-e", expectedFormat);
- Assert.Contains("-ma", expectedFormat);
- Assert.Contains(processName, expectedFormat);
- Assert.Contains(dumpFileName, expectedFormat);
- }
-
- ///
- /// Tests that Applications directory path is constructed correctly for Windows.
- ///
- [Fact]
- public void ApplicationsDirectory_ConstructedCorrectly_ForWindows()
- {
- // Arrange
- var targetPath = "/path/to/target";
- var expectedAppDir = Path.Combine(targetPath, "Applications", "Windows");
-
- // Assert
- Assert.Equal($"{targetPath}{Path.DirectorySeparatorChar}Applications{Path.DirectorySeparatorChar}Windows", expectedAppDir);
- }
-
- ///
- /// Tests that InnerApp path is constructed correctly with architecture-specific procdump.
- ///
- [Fact]
- public void InnerApp_PathConstructedCorrectly_WithArchitecture()
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return;
- }
-
- // Arrange
- var targetPath = "/path/to/target";
- var applicationsDir = Path.Combine(targetPath, "Applications", "Windows");
-
- var currentArch = RuntimeInformation.OSArchitecture;
- var expectedExe = currentArch switch
- {
- Architecture.X86 => "procdump.exe",
- Architecture.X64 => "procdump64.exe",
- _ => "procdump64a.exe"
- };
-
- var expectedPath = Path.Combine(applicationsDir, expectedExe);
-
- // Assert
- Assert.Contains("Applications", expectedPath);
- Assert.Contains("Windows", expectedPath);
- Assert.Contains(".exe", expectedPath);
- Assert.EndsWith(expectedExe, expectedPath);
- }
-
- ///
- /// Tests that strategy parameters are properly initialized.
- ///
- [Fact]
- public void Strategy_ParametersInitialized_Correctly()
- {
- // Arrange
- var parameter = new MonitorParameter
- {
- TargetPath = "/target",
- FailDirectory = "/fail",
- BackupDirectory = "/backup",
- ProcessNameOrId = "app.exe",
- DumpFileName = "dump.dmp",
- FailFileName = "fail.json",
- WorkModel = "Upgrade",
- ExtendedField = "1.0.0"
- };
-
- // Assert - All parameters should be set
- Assert.NotNull(parameter.TargetPath);
- Assert.NotNull(parameter.FailDirectory);
- Assert.NotNull(parameter.BackupDirectory);
- Assert.NotNull(parameter.ProcessNameOrId);
- Assert.NotNull(parameter.DumpFileName);
- Assert.NotNull(parameter.FailFileName);
- Assert.NotNull(parameter.WorkModel);
- Assert.NotNull(parameter.ExtendedField);
- }
- }
-}
diff --git a/tests/BowlTest/Strategys/MonitorParameterTests.cs b/tests/BowlTest/Strategys/MonitorParameterTests.cs
deleted file mode 100644
index 8a86dc02..00000000
--- a/tests/BowlTest/Strategys/MonitorParameterTests.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-using GeneralUpdate.Bowl.Strategys;
-
-namespace BowlTest.Strategys
-{
- ///
- /// Contains test cases for the MonitorParameter class.
- /// Tests parameter initialization and property assignments.
- ///
- public class MonitorParameterTests
- {
- ///
- /// Tests that constructor creates a new instance with default values.
- ///
- [Fact]
- public void Constructor_CreatesInstance_WithDefaultValues()
- {
- // Act
- var parameter = new MonitorParameter();
-
- // Assert
- Assert.NotNull(parameter);
- Assert.Equal("Upgrade", parameter.WorkModel);
- }
-
- ///
- /// Tests that WorkModel property has default value of "Upgrade".
- ///
- [Fact]
- public void WorkModel_HasDefaultValue_Upgrade()
- {
- // Arrange & Act
- var parameter = new MonitorParameter();
-
- // Assert
- Assert.Equal("Upgrade", parameter.WorkModel);
- }
-
- ///
- /// Tests that TargetPath property can be set and retrieved.
- ///
- [Fact]
- public void TargetPath_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedPath = "/path/to/target";
-
- // Act
- parameter.TargetPath = expectedPath;
-
- // Assert
- Assert.Equal(expectedPath, parameter.TargetPath);
- }
-
- ///
- /// Tests that FailDirectory property can be set and retrieved.
- ///
- [Fact]
- public void FailDirectory_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedPath = "/path/to/fail";
-
- // Act
- parameter.FailDirectory = expectedPath;
-
- // Assert
- Assert.Equal(expectedPath, parameter.FailDirectory);
- }
-
- ///
- /// Tests that BackupDirectory property can be set and retrieved.
- ///
- [Fact]
- public void BackupDirectory_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedPath = "/path/to/backup";
-
- // Act
- parameter.BackupDirectory = expectedPath;
-
- // Assert
- Assert.Equal(expectedPath, parameter.BackupDirectory);
- }
-
- ///
- /// Tests that ProcessNameOrId property can be set and retrieved.
- ///
- [Fact]
- public void ProcessNameOrId_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedValue = "myapp.exe";
-
- // Act
- parameter.ProcessNameOrId = expectedValue;
-
- // Assert
- Assert.Equal(expectedValue, parameter.ProcessNameOrId);
- }
-
- ///
- /// Tests that DumpFileName property can be set and retrieved.
- ///
- [Fact]
- public void DumpFileName_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedValue = "crash.dmp";
-
- // Act
- parameter.DumpFileName = expectedValue;
-
- // Assert
- Assert.Equal(expectedValue, parameter.DumpFileName);
- }
-
- ///
- /// Tests that FailFileName property can be set and retrieved.
- ///
- [Fact]
- public void FailFileName_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedValue = "fail.json";
-
- // Act
- parameter.FailFileName = expectedValue;
-
- // Assert
- Assert.Equal(expectedValue, parameter.FailFileName);
- }
-
- ///
- /// Tests that WorkModel property can be changed from default value.
- ///
- [Fact]
- public void WorkModel_CanBeChanged()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedValue = "Normal";
-
- // Act
- parameter.WorkModel = expectedValue;
-
- // Assert
- Assert.Equal(expectedValue, parameter.WorkModel);
- }
-
- ///
- /// Tests that ExtendedField property can be set and retrieved.
- ///
- [Fact]
- public void ExtendedField_CanBeSetAndRetrieved()
- {
- // Arrange
- var parameter = new MonitorParameter();
- var expectedValue = "1.0.0";
-
- // Act
- parameter.ExtendedField = expectedValue;
-
- // Assert
- Assert.Equal(expectedValue, parameter.ExtendedField);
- }
-
- ///
- /// Tests that all properties can be set together.
- ///
- [Fact]
- public void AllProperties_CanBeSetTogether()
- {
- // Arrange & Act
- var parameter = new MonitorParameter
- {
- TargetPath = "/target",
- FailDirectory = "/fail",
- BackupDirectory = "/backup",
- ProcessNameOrId = "app.exe",
- DumpFileName = "dump.dmp",
- FailFileName = "fail.json",
- WorkModel = "Normal",
- ExtendedField = "2.0.0"
- };
-
- // Assert
- Assert.Equal("/target", parameter.TargetPath);
- Assert.Equal("/fail", parameter.FailDirectory);
- Assert.Equal("/backup", parameter.BackupDirectory);
- Assert.Equal("app.exe", parameter.ProcessNameOrId);
- Assert.Equal("dump.dmp", parameter.DumpFileName);
- Assert.Equal("fail.json", parameter.FailFileName);
- Assert.Equal("Normal", parameter.WorkModel);
- Assert.Equal("2.0.0", parameter.ExtendedField);
- }
- }
-}
diff --git a/tests/BowlTest/Strategys/WindowStrategyTests.cs b/tests/BowlTest/Strategys/WindowStrategyTests.cs
deleted file mode 100644
index 14156d71..00000000
--- a/tests/BowlTest/Strategys/WindowStrategyTests.cs
+++ /dev/null
@@ -1,196 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using GeneralUpdate.Bowl.Strategys;
-
-namespace BowlTest.Strategys
-{
- ///
- /// Contains test cases for the WindowStrategy class.
- /// Tests strategy initialization, execution flow, and platform-specific behavior.
- /// Note: WindowStrategy is internal, so we test through public API and reflection.
- ///
- public class WindowStrategyTests
- {
- ///
- /// Tests that GetAppName returns correct procdump executable for X86 architecture.
- ///
- [Fact]
- public void GetAppName_ForX86Architecture_ReturnsProcdumpExe()
- {
- // Only run this test on Windows
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return;
- }
-
- // This test validates the architecture-based selection logic
- // For X86: procdump.exe
- // For X64: procdump64.exe
- // For others: procdump64a.exe (ARM64)
-
- var currentArch = RuntimeInformation.OSArchitecture;
- string expectedExe = currentArch switch
- {
- Architecture.X86 => "procdump.exe",
- Architecture.X64 => "procdump64.exe",
- _ => "procdump64a.exe"
- };
-
- // We can't test GetAppName directly as it's private, but we can verify
- // the logic through the behavior of Launch method
- Assert.NotNull(expectedExe);
- Assert.EndsWith(".exe", expectedExe);
- }
-
- ///
- /// Tests that SetParameter sets the parameter correctly.
- ///
- [Fact]
- public void SetParameter_SetsParameterCorrectly()
- {
- // Only run this test on Windows
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return;
- }
-
- // Arrange
- var tempPath = Path.GetTempPath();
- var testPath = Path.Combine(tempPath, $"BowlTest_{Guid.NewGuid()}");
- Directory.CreateDirectory(testPath);
-
- try
- {
- var parameter = new MonitorParameter
- {
- TargetPath = testPath,
- FailDirectory = Path.Combine(testPath, "fail"),
- BackupDirectory = Path.Combine(testPath, "backup"),
- ProcessNameOrId = "test.exe",
- DumpFileName = "test.dmp",
- FailFileName = "test.json",
- WorkModel = "Normal"
- };
-
- // Get WindowStrategy type using reflection
- var bowlAssembly = typeof(MonitorParameter).Assembly;
- var strategyType = bowlAssembly.GetType("GeneralUpdate.Bowl.Strategys.WindowStrategy");
-
- if (strategyType != null)
- {
- // Act
- var strategy = Activator.CreateInstance(strategyType);
- var setParameterMethod = strategyType.GetMethod("SetParameter");
-
- // Assert - Should not throw
- var exception = Record.Exception(() => setParameterMethod?.Invoke(strategy, new[] { parameter }));
- Assert.Null(exception);
- }
- }
- finally
- {
- // Cleanup
- if (Directory.Exists(testPath))
- {
- Directory.Delete(testPath, true);
- }
- }
- }
-
- ///
- /// Tests that WorkModel property correctly defaults to "Upgrade".
- ///
- [Fact]
- public void MonitorParameter_WorkModel_DefaultsToUpgrade()
- {
- // Arrange & Act
- var parameter = new MonitorParameter();
-
- // Assert
- Assert.Equal("Upgrade", parameter.WorkModel);
- }
-
- ///
- /// Tests that MonitorParameter can be configured for Normal mode.
- ///
- [Fact]
- public void MonitorParameter_CanBeConfiguredForNormalMode()
- {
- // Arrange & Act
- var parameter = new MonitorParameter
- {
- WorkModel = "Normal"
- };
-
- // Assert
- Assert.Equal("Normal", parameter.WorkModel);
- }
-
- ///
- /// Tests that dump file name is constructed correctly with version.
- ///
- [Fact]
- public void DumpFileName_ConstructedCorrectly_WithVersion()
- {
- // Arrange
- var version = "1.2.3";
- var expectedDumpFileName = $"{version}_fail.dmp";
- var expectedFailFileName = $"{version}_fail.json";
-
- var parameter = new MonitorParameter
- {
- DumpFileName = expectedDumpFileName,
- FailFileName = expectedFailFileName
- };
-
- // Assert
- Assert.Equal(expectedDumpFileName, parameter.DumpFileName);
- Assert.Equal(expectedFailFileName, parameter.FailFileName);
- }
-
- ///
- /// Tests that backup directory path is constructed correctly.
- ///
- [Fact]
- public void BackupDirectory_ConstructedCorrectly_WithVersionPath()
- {
- // Arrange
- var installPath = "/path/to/install";
- var version = "1.2.3";
- var expectedBackupDir = Path.Combine(installPath, version);
-
- var parameter = new MonitorParameter
- {
- BackupDirectory = expectedBackupDir
- };
-
- // Assert
- Assert.Equal(expectedBackupDir, parameter.BackupDirectory);
- Assert.Contains(version, parameter.BackupDirectory);
- }
-
- ///
- /// Tests that fail directory path is constructed correctly with version.
- ///
- [Fact]
- public void FailDirectory_ConstructedCorrectly_WithVersionPath()
- {
- // Arrange
- var installPath = "/path/to/install";
- var version = "1.2.3";
- var expectedFailDir = Path.Combine(installPath, "fail", version);
-
- var parameter = new MonitorParameter
- {
- FailDirectory = expectedFailDir
- };
-
- // Assert
- Assert.Equal(expectedFailDir, parameter.FailDirectory);
- Assert.Contains("fail", parameter.FailDirectory);
- Assert.Contains(version, parameter.FailDirectory);
- }
- }
-}