Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions Assets/Tests/Editor/CliInstallationDetectorTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics;

using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.Infrastructure;
Expand Down Expand Up @@ -84,5 +86,138 @@ public void SelectPreferredDetection_WhenPackageOwnedCliMissingUsesShellPath()
Assert.That(result.Version, Is.EqualTo("2.1.0"));
Assert.That(result.ExecutablePath, Is.EqualTo("/Users/masamichi/.npm-global/bin/uloop"));
}

[Test]
public void SelectPreferredDetection_WhenShellVersionExistsWithoutPathUsesShellVersion()
{
// Verifies that installed state does not depend on command path availability.
CliInstallationDetection packageOwnedDetection = new(
"3.0.0-beta.3",
"/Users/masamichi/.local/bin/uloop");
CliInstallationDetection shellDetection = new(
"2.1.0",
null);

CliInstallationDetection result = CliInstallationDetector.SelectPreferredDetection(
packageOwnedDetection,
shellDetection);

Assert.That(result.Version, Is.EqualTo("2.1.0"));
Assert.That(result.ExecutablePath, Is.Null);
}

[Test]
public void BuildShellCliDetectionCommand_UsesShortVersionFlag()
{
// Verifies that shell detection asks the command itself for its terminal-visible version.
string command = CliInstallationDetector.BuildShellCliDetectionCommand("uloop");

Assert.That(command, Does.Contain("command -v uloop"));
Assert.That(command, Does.Contain("uloop -v"));
Assert.That(command, Does.Contain("uloop_version_status=$?"));
Assert.That(command, Does.Contain("__ULOOP_VERSION_STATUS_START__"));
Assert.That(command, Does.Not.Contain("uloop --version"));
}

[Test]
public void ParseShellCliInstallationOutput_WhenPathAndVersionExist_ReturnsDetection()
{
// Verifies that shell detection keeps terminal-visible path data as auxiliary UI context.
string output = "banner\n"
+ "__ULOOP_PATH_START__\n"
+ "/Users/masamichi/.npm-global/bin/uloop\n"
+ "__ULOOP_PATH_END__\n"
+ "__ULOOP_VERSION_START__\n"
+ "2.1.1\n"
+ "__ULOOP_VERSION_END__\n"
+ "__ULOOP_VERSION_STATUS_START__\n"
+ "0\n"
+ "__ULOOP_VERSION_STATUS_END__\n";

CliInstallationDetection detection =
CliInstallationDetector.ParseShellCliInstallationOutput(output);

Assert.That(detection.Version, Is.EqualTo("2.1.1"));
Assert.That(detection.ExecutablePath, Is.EqualTo("/Users/masamichi/.npm-global/bin/uloop"));
}

[Test]
public void ParseShellCliInstallationOutput_WhenOnlyVersionExists_ReturnsInstalledDetection()
{
// Verifies that installation state depends on version output, not path availability.
string output = "__ULOOP_PATH_START__\n"
+ "__ULOOP_PATH_END__\n"
+ "__ULOOP_VERSION_START__\n"
+ "2.1.1\n"
+ "__ULOOP_VERSION_END__\n"
+ "__ULOOP_VERSION_STATUS_START__\n"
+ "0\n"
+ "__ULOOP_VERSION_STATUS_END__\n";

CliInstallationDetection detection =
CliInstallationDetector.ParseShellCliInstallationOutput(output);

Assert.That(detection.Version, Is.EqualTo("2.1.1"));
Assert.That(detection.ExecutablePath, Is.Null);
}

[Test]
public void ParseShellCliInstallationOutput_WhenVersionCommandFails_ReturnsPathWithoutVersion()
{
// Verifies that failed shell probes do not treat stdout usage text as a CLI version.
string output = "__ULOOP_PATH_START__\n"
+ "/Users/masamichi/.npm-global/bin/uloop\n"
+ "__ULOOP_PATH_END__\n"
+ "__ULOOP_VERSION_START__\n"
+ "usage: broken uloop\n"
+ "__ULOOP_VERSION_END__\n"
+ "__ULOOP_VERSION_STATUS_START__\n"
+ "1\n"
+ "__ULOOP_VERSION_STATUS_END__\n";

CliInstallationDetection detection =
CliInstallationDetector.ParseShellCliInstallationOutput(output);

Assert.That(detection.Version, Is.Null);
Assert.That(detection.ExecutablePath, Is.EqualTo("/Users/masamichi/.npm-global/bin/uloop"));
}

[Test]
public void KillProcessIfRunning_WhenProcessAlreadyExited_DoesNotThrow()
{
// Verifies that process cleanup tolerates the race where the child exits before Kill.
ProcessStartInfo startInfo = BuildImmediateExitProcessStartInfo();

using Process process = Process.Start(startInfo);
process.WaitForExit();

Assert.DoesNotThrow(() => CliInstallationDetector.KillProcessIfRunning(process));
}

private static ProcessStartInfo BuildImmediateExitProcessStartInfo()
{
if (UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor)
{
return new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c exit 0",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
}

return new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = "-c \"exit 0\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
}
}
}
47 changes: 47 additions & 0 deletions Assets/Tests/Editor/NodeEnvironmentResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,52 @@ public void ExtractAbsolutePathLine_BannerThenAliasTextThenPath_ReturnsPath()

Assert.AreEqual("/usr/local/bin/node", result);
}

[Test]
public void ExtractDirectoryServiceUserShell_WhenUserShellLineExists_ReturnsShellPath()
{
// Verifies that macOS directory service output can recover the user's login shell.
string output = "UserShell: /bin/zsh\n";

string result = NodeEnvironmentResolver.ExtractDirectoryServiceUserShell(output);

Assert.AreEqual("/bin/zsh", result);
}

[Test]
public void SelectUserShell_WhenEnvironmentShellIsMissing_UsesDirectoryServiceShell()
{
// Verifies that GUI-launched Unity can still resolve terminal PATH through the user's login shell.
string result = NodeEnvironmentResolver.SelectUserShell(
null,
"/bin/zsh",
path => path == "/bin/zsh");

Assert.AreEqual("/bin/zsh", result);
}

[Test]
public void SelectUserShell_WhenEnvironmentShellExists_PrefersEnvironmentShell()
{
// Verifies that an inherited SHELL remains authoritative when it points to a real shell.
string result = NodeEnvironmentResolver.SelectUserShell(
"/bin/bash",
"/bin/zsh",
path => path == "/bin/bash" || path == "/bin/zsh");

Assert.AreEqual("/bin/bash", result);
}

[Test]
public void SelectUserShell_WhenNoCandidateExists_ReturnsPosixFallbackShell()
{
// Verifies that shell resolution keeps a deterministic fallback when no user shell is available.
string result = NodeEnvironmentResolver.SelectUserShell(
null,
null,
path => false);

Assert.AreEqual("/bin/sh", result);
}
}
}
42 changes: 42 additions & 0 deletions Assets/Tests/Editor/SetupWizardWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,48 @@ public void CanManageSkills_WhenCliIsInstalled_ReturnsTrue()
Assert.That(canManageSkills, Is.True);
}

[Test]
public void ShouldShowSkillsTargetRow_WhenFirstInstallAndCliMissing_ReturnsTrue()
{
// Verifies that first-time setup can choose a skill target before CLI installation.
bool shouldShow = SetupWizardWindow.ShouldShowSkillsTargetRowForSetupWizard(
shouldUseFirstInstallSkillsUi: true);

Assert.That(shouldShow, Is.True);
}

[Test]
public void ShouldShowSkillsTargetRow_WhenNotFirstInstall_ReturnsFalse()
{
// Verifies that returning setup keeps the compact target row hidden.
bool shouldShow = SetupWizardWindow.ShouldShowSkillsTargetRowForSetupWizard(
shouldUseFirstInstallSkillsUi: false);

Assert.That(shouldShow, Is.False);
}

[Test]
public void ShouldShowSkillsTargetList_WhenCliMissing_ReturnsFalse()
{
// Verifies that multi-target status rows stay hidden until the CLI can inspect skill state.
bool shouldShow = SetupWizardWindow.ShouldShowSkillsTargetListForSetupWizard(
canManageSkills: false,
shouldUseFirstInstallSkillsUi: false);

Assert.That(shouldShow, Is.False);
}

[Test]
public void ShouldShowSkillsTargetList_WhenCliInstalledAndNotFirstInstall_ReturnsTrue()
{
// Verifies that returning users keep the multi-target skill status view.
bool shouldShow = SetupWizardWindow.ShouldShowSkillsTargetListForSetupWizard(
canManageSkills: true,
shouldUseFirstInstallSkillsUi: false);

Assert.That(shouldShow, Is.True);
}

[TestCase(false, false, false, false, null, "3.0.0", "Install CLI")]
[TestCase(true, false, false, false, "3.0.0", "3.0.0", "Installed")]
[TestCase(true, false, false, true, "2.9.0", "3.0.0", "Update CLI (v2.9.0 \u2192 v3.0.0)")]
Expand Down
Loading