using System.Diagnostics; using LlamaApp.Llama; using Xunit; namespace LlamaApp.LlamaCpp.Tests; /// /// Unit tests for the crash-safe managed-server check /// (). The rules that matter /// operationally: a live llama process whose PID matches the file IS our /// managed server (adopt it after an app crash — don't kill it); a dead PID, /// garbage content, or a live process that clearly isn't our server (PID /// reuse) means "not managed" and the stale file is cleaned up. /// public sealed class LlamaManagerPidFileTests : IDisposable { private readonly string _root = Path.Combine(Path.GetTempPath(), "llama-pid-tests-" + Guid.NewGuid().ToString("N")); private readonly List _spawned = new(); public LlamaManagerPidFileTests() => Directory.CreateDirectory(_root); public void Dispose() { foreach (var p in _spawned) { try { if (!p.HasExited) p.Kill(entireProcessTree: true); } catch { /* best-effort */ } p.Dispose(); } try { Directory.Delete(_root, recursive: true); } catch { /* best-effort */ } } private string WritePidFile(string content) { var path = Path.Combine(_root, ".llama.pid"); File.WriteAllText(path, content); return path; } private Process Spawn(string fileName, string arguments) { var p = Process.Start(new ProcessStartInfo { FileName = fileName, Arguments = arguments, CreateNoWindow = true, UseShellExecute = false, })!; _spawned.Add(p); return p; } /// Runs a cmd.exe copy named llama.exe so is "llama". private Process SpawnFakeLlama() { var fake = Path.Combine(_root, "llama.exe"); if (!File.Exists(fake)) File.Copy(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"), fake); return Spawn(fake, "/c ping -n 30 127.0.0.1 >nul"); } [Fact] public void ReadLiveManagedPid_MissingFile_ReturnsNull() { Assert.Null(LlamaManager.ReadLiveManagedPid(Path.Combine(_root, ".llama.pid"))); } [Fact] public void ReadLiveManagedPid_GarbageContent_ReturnsNull_AndDeletesFile() { var path = WritePidFile("not-a-pid"); Assert.Null(LlamaManager.ReadLiveManagedPid(path)); Assert.False(File.Exists(path)); } [Fact] public void ReadLiveManagedPid_DeadPid_ReturnsNull_AndDeletesFile() { var proc = Spawn("cmd.exe", "/c exit 0"); proc.WaitForExit(); var path = WritePidFile(proc.Id.ToString()); Assert.Null(LlamaManager.ReadLiveManagedPid(path)); Assert.False(File.Exists(path)); } [Fact] public void ReadLiveManagedPid_LiveNonLlamaProcess_ReturnsNull_AndDeletesFile() { // PID-reuse guard: a live process that isn't llama must not be // considered our managed server. var proc = Spawn("cmd.exe", "/c ping -n 30 127.0.0.1 >nul"); var path = WritePidFile(proc.Id.ToString()); Assert.Null(LlamaManager.ReadLiveManagedPid(path)); Assert.False(File.Exists(path)); } [Fact] public void ReadLiveManagedPid_LiveLlamaProcess_ReturnsPid() { var proc = SpawnFakeLlama(); var path = WritePidFile(proc.Id.ToString()); Assert.Equal(proc.Id, LlamaManager.ReadLiveManagedPid(path)); } [Fact] public void ReadLiveManagedPid_LlamaStartedAfterFileWrite_ReturnsNull_AndDeletesFile() { // PID-reuse guard #2: the process started AFTER the PID file's // timestamp — it can't be the server we launched. var proc = SpawnFakeLlama(); var path = WritePidFile(proc.Id.ToString()); File.SetLastWriteTimeUtc(path, DateTime.UtcNow.AddMinutes(-10)); Assert.Null(LlamaManager.ReadLiveManagedPid(path)); Assert.False(File.Exists(path)); } }