using System; using System.IO; using System.IO.Pipes; using Xunit; using ytLive.Helpers; using ytLive.Services.Audio; namespace ytLive.Tests; /// /// 2026-11-06 crash: two LlamaCasty instances on one machine, the second one died on the Go Live click with /// IOException: All pipe instances are busy. The audio pipe server allows exactly ONE instance and the /// name was a shared constant, so this pins both halves of the fix — the collision is real, and each dev /// instance now gets its own name. /// public sealed class AudioPipeInstanceTests : IDisposable { private readonly string? _original = Environment.GetEnvironmentVariable(InstanceProfile.InstanceVariable); public void Dispose() => Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, _original); [Fact] public void ASecondServerOnTheSameName_CannotStart_WhichIsWhatKilledTheSecondInstance() { var name = $"ytllive_test_{Guid.NewGuid():N}"; using var first = new NamedPipeServerStream(name, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); var error = Record.Exception(() => new NamedPipeServerStream(name, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)); Assert.NotNull(error); Assert.IsType(error); } [Fact] public void ADevInstanceServesItsOwnPipeName() { Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, null); #if DEBUG // No claimed id (launched from the IDE) must still never collide with the Release build's pipe. var unnamed = InstanceProfile.AudioPipeName("ytllive_audio"); Assert.StartsWith("ytllive_audio_pid", unnamed); Assert.Equal(unnamed, InstanceProfile.AudioPipeName("ytllive_audio")); #else Assert.Equal("ytllive_audio", InstanceProfile.AudioPipeName("ytllive_audio")); #endif Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, "2"); Assert.Equal("ytllive_audio_2", InstanceProfile.AudioPipeName("ytllive_audio")); Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, "qa"); Assert.Equal("ytllive_audio_qa", InstanceProfile.AudioPipeName("ytllive_audio")); Assert.NotEqual(InstanceProfile.AudioPipeName("ytllive_audio"), WithInstance("3", () => InstanceProfile.AudioPipeName("ytllive_audio"))); } private static string WithInstance(string id, Func read) { var original = Environment.GetEnvironmentVariable(InstanceProfile.InstanceVariable); try { Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, id); return read(); } finally { Environment.SetEnvironmentVariable(InstanceProfile.InstanceVariable, original); } } [Fact] public void TheMixerRefusesAStartedSecondTime_ButAWriterOnATakenNameThrows() { // The mixer's own guard is idempotent (StartLive twice is a no-op); the exception came from the // pipe itself, which the VM now catches and reports instead of letting it end the process. var name = $"ytllive_test_{Guid.NewGuid():N}"; var writer = new NamedPipeAudioWriter(); writer.Start(name); try { var second = Record.Exception(() => new NamedPipeAudioWriter().Start(name)); Assert.IsType(second); } finally { writer.Stop(); } } }