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
16 changes: 12 additions & 4 deletions Assets/Tests/Editor/OnionAssemblyDependencyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed class OnionAssemblyDependencyTests
private const string FirstPartyToolsAssemblyName = "UnityCLILoop.FirstPartyTools.Editor";
private const string FirstPartyToolsAssemblyNamePrefix = "UnityCLILoop.FirstPartyTools.";
private const string ClearConsoleAssemblyName = "UnityCLILoop.FirstPartyTools.ClearConsole.Editor";
private const string CommonConsoleAssemblyName = "UnityCLILoop.FirstPartyTools.Common.Console.Editor";
private const string CompileAssemblyName = "UnityCLILoop.FirstPartyTools.Compile.Editor";
private const string ControlPlayModeAssemblyName = "UnityCLILoop.FirstPartyTools.ControlPlayMode.Editor";
private const string ExecuteDynamicCodeAssemblyName = "UnityCLILoop.FirstPartyTools.ExecuteDynamicCode.Editor";
Expand Down Expand Up @@ -507,6 +508,7 @@ public void InfrastructureAsmdef_WhenLoaded_DependsOnApplicationRuntimeAndDoesNo
InternalApiBridgeAssemblyName,
ApplicationAssemblyName,
DomainAssemblyName,
CommonConsoleAssemblyName,
PausePointsRuntimeAssemblyName,
ToolContractsAssemblyName
}));
Expand Down Expand Up @@ -881,30 +883,36 @@ public void DomainReloadStateRegistration_WhenLoaded_DoesNotReadSettingsSynchron
public void SetupWizardStartup_WhenLoaded_SchedulesVersionCheckInsteadOfReadingSettingsSynchronously()
{
// Tests that Setup Wizard settings reads run after the synchronous Editor startup hook.
//
// A cold-start session that hits Unity's native "Scripts have compiler errors" dialog
// never flushes EditorApplication.delayCall again for the rest of that process's
// lifetime, so this startup check rides on a self-unsubscribing EditorApplication.update
// tick instead of delayCall.
string setupWizardSource = ReadProductionSource(
"Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs");
string setupWizardStartupFlowSource = ReadProductionSource(
"Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs");

Assert.That(
setupWizardSource,
Does.Contain("EditorApplication.delayCall += startupFlow.TryShowOnVersionChange;"));
Does.Contain("EditorApplication.update += RunStartupCheckOnFirstUpdateTick;"));
Assert.That(
setupWizardStartupFlowSource,
Does.Contain("EditorApplication.delayCall += () => _showWindowOnVersionChange();"));
Assert.That(setupWizardSource, Does.Not.Contain("\n startupFlow.TryShowOnVersionChange();"));
}

[Test]
public void SetupWizardStartup_WhenMigrationAutoScanIsRequested_SchedulesAutoScanThroughDelayCall()
public void SetupWizardStartup_WhenMigrationAutoScanIsRequested_SchedulesAutoScanThroughEditorUpdatePolling()
{
// Tests that startup migration auto-scan runs through a delayed Editor callback.
// Tests that startup migration auto-scan polls via EditorApplication.update instead of
// delayCall, since delayCall stops flushing after the native compiler-errors dialog.
string setupWizardStartupFlowSource = ReadProductionSource(
"Packages/src/Editor/Presentation/Setup/SetupWizardStartupFlow.cs");

Assert.That(
setupWizardStartupFlowSource,
Does.Contain("EditorApplication.delayCall += () => _showThirdPartyMigrationAutoScan();"));
Does.Contain("EditorApplication.update += PollThirdPartyToolMigrationAutoScan;"));
}

[Test]
Expand Down
41 changes: 23 additions & 18 deletions Assets/Tests/Editor/SetupWizardWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public class SetupWizardWindowTests
private string _settingsFileContent;
private IUnityCliLoopEditorSettingsPort _editorSettingsPort;
private UnityCliLoopEditorSettingsRepository _editorSettingsRepository;
private UnityCliLoopSessionFlagsRepository _sessionFlagsRepository;
private UnityCliLoopEditorSessionStateSnapshot _originalSessionState;

[SetUp]
Expand All @@ -43,7 +42,6 @@ public void SetUp()
DeleteIfExists(SettingsFilePath);
_editorSettingsPort =
UnityCliLoopEditorSettingsTestFactory.CreatePortWithRepository(out _editorSettingsRepository);
_sessionFlagsRepository = UnityCliLoopEditorSessionStateTestFactory.CreateSessionFlagsRepository();
_originalSessionState = UnityCliLoopEditorSessionStateTestFactory.CaptureSnapshot();
UnityCliLoopEditorSessionStateTestFactory.ClearAll();
SetupWizardWindow.InitializeEditorServices(
Expand Down Expand Up @@ -183,22 +181,29 @@ public void ShouldAutoScanThirdPartyToolMigration_ReturnsExpectedValue(
Assert.That(shouldAutoScan, Is.EqualTo(expected));
}

[Test]
public void MaybeMarkThirdPartyToolMigrationAutoScan_WhenEnabled_SetsSessionFlag()
{
// Verifies that the V2-to-V3 upgrade signal is stored only in the current Editor session.
SetupWizardStartupFlow.MaybeMarkThirdPartyToolMigrationAutoScan(_sessionFlagsRepository, true);

Assert.That(_sessionFlagsRepository.GetShouldAutoScanThirdPartyToolMigration(), Is.True);
}

[Test]
public void MaybeMarkThirdPartyToolMigrationAutoScan_WhenDisabled_KeepsSessionFlagFalse()
{
// Verifies that non-upgrade version checks do not request migration scans.
SetupWizardStartupFlow.MaybeMarkThirdPartyToolMigrationAutoScan(_sessionFlagsRepository, false);

Assert.That(_sessionFlagsRepository.GetShouldAutoScanThirdPartyToolMigration(), Is.False);
[TestCase(true, false, 0d, 10d, MigrationAutoScanPollAction.ContinueWaiting)]
[TestCase(true, true, 0d, 10d, MigrationAutoScanPollAction.ContinueWaiting)]
[TestCase(false, false, 0d, 10d, MigrationAutoScanPollAction.Terminate)]
[TestCase(false, true, 0d, 10d, MigrationAutoScanPollAction.RunDetection)]
[TestCase(false, true, 5d, 10d, MigrationAutoScanPollAction.RunDetection)]
[TestCase(false, true, 10d, 10d, MigrationAutoScanPollAction.FallBackToFullScan)]
[TestCase(false, true, 15d, 10d, MigrationAutoScanPollAction.FallBackToFullScan)]
public void DecideMigrationAutoScanPollAction_ReturnsExpectedAction(
bool isCompiling,
bool scriptCompilationFailed,
double elapsedSeconds,
double timeoutSeconds,
MigrationAutoScanPollAction expected)
{
// Verifies the pure poll decision function used to replace the unreliable
// delayCall-based migration auto-scan trigger with an EditorApplication.update poll.
MigrationAutoScanPollAction action = SetupWizardStartupFlow.DecideMigrationAutoScanPollAction(
isCompiling,
scriptCompilationFailed,
elapsedSeconds,
timeoutSeconds);

Assert.That(action, Is.EqualTo(expected));
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Collections.Generic;

using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.Infrastructure;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Test fixture that verifies compile-error-log entries are matched against V2 legacy API tokens
/// and reduced to a deduplicated set of migration target file paths.
/// </summary>
public sealed class ThirdPartyToolMigrationCompileErrorLogMatcherTests
{
[Test]
public void Match_WhenNoEntryContainsLegacyToken_ReturnsEmptyResult()
{
// Verifies that unrelated V3 compile errors produce no matched entries and no target file paths.
List<CompileErrorLogEntry> entries = new List<CompileErrorLogEntry>
{
new CompileErrorLogEntry(
"Assets/Editor/MyTool.cs(1,1): error CS0103: The name 'undefinedSymbol' does not exist in the current context",
"Assets/Editor/MyTool.cs",
1)
};

ThirdPartyToolMigrationCompileErrorLogMatchResult result =
ThirdPartyToolMigrationCompileErrorLogMatcher.Match(entries);

Assert.That(result.MatchedEntries, Is.Empty);
Assert.That(result.TargetFilePaths, Is.Empty);
}

[Test]
public void Match_WhenEntryContainsLegacyToken_ReturnsMatchedEntryAndTargetFilePath()
{
// Verifies that an entry whose message contains a legacy API token is included in both
// the matched-entries list and the target file path list.
List<CompileErrorLogEntry> entries = new List<CompileErrorLogEntry>
{
new CompileErrorLogEntry(
"Assets/Editor/MyTool.cs(3,25): error CS0234: The type or namespace name 'uLoopMCP' " +
"does not exist in the namespace 'io.github.hatayama' (are you missing an assembly reference?)",
"Assets/Editor/MyTool.cs",
3)
};

ThirdPartyToolMigrationCompileErrorLogMatchResult result =
ThirdPartyToolMigrationCompileErrorLogMatcher.Match(entries);

Assert.That(result.MatchedEntries.Count, Is.EqualTo(1));
Assert.That(result.TargetFilePaths, Is.EqualTo(new[] { "Assets/Editor/MyTool.cs" }));
}

[Test]
public void Match_WhenMultipleEntriesShareTheSameFilePath_DeduplicatesTargetFilePaths()
{
// Verifies that multiple legacy-token-matching errors in the same file collapse to one target path.
List<CompileErrorLogEntry> entries = new List<CompileErrorLogEntry>
{
new CompileErrorLogEntry(
"Assets/Editor/MyTool.cs(3,25): error CS0234: The type or namespace name 'uLoopMCP' " +
"does not exist in the namespace 'io.github.hatayama' (are you missing an assembly reference?)",
"Assets/Editor/MyTool.cs",
3),
new CompileErrorLogEntry(
"Assets/Editor/MyTool.cs(9,14): error CS0246: The type or namespace name 'AbstractUnityTool' " +
"could not be found (are you missing a using directive or an assembly reference?)",
"Assets/Editor/MyTool.cs",
9)
};

ThirdPartyToolMigrationCompileErrorLogMatchResult result =
ThirdPartyToolMigrationCompileErrorLogMatcher.Match(entries);

Assert.That(result.MatchedEntries.Count, Is.EqualTo(2));
Assert.That(result.TargetFilePaths, Is.EqualTo(new[] { "Assets/Editor/MyTool.cs" }));
}

[Test]
public void Match_WhenEntriesMixMatchingAndNonMatching_OnlyIncludesMatchingFilePaths()
{
// Verifies that a non-matching entry does not contribute its file path even when it is
// interleaved with matching entries from other files.
List<CompileErrorLogEntry> entries = new List<CompileErrorLogEntry>
{
new CompileErrorLogEntry(
"Assets/Editor/Unrelated.cs(1,1): error CS0103: The name 'undefinedSymbol' does not exist in the current context",
"Assets/Editor/Unrelated.cs",
1),
new CompileErrorLogEntry(
"Assets/Editor/LegacyTool.cs(2,6): error CS0246: The type or namespace name 'McpTool' " +
"could not be found (are you missing a using directive or an assembly reference?)",
"Assets/Editor/LegacyTool.cs",
2)
};

ThirdPartyToolMigrationCompileErrorLogMatchResult result =
ThirdPartyToolMigrationCompileErrorLogMatcher.Match(entries);

Assert.That(result.MatchedEntries.Count, Is.EqualTo(1));
Assert.That(result.TargetFilePaths, Is.EqualTo(new[] { "Assets/Editor/LegacyTool.cs" }));
}

[Test]
public void Match_WhenEntriesIsNull_Throws()
{
// Verifies fail-fast behavior when the entry collection itself is missing.
Assert.Throws<System.ArgumentNullException>(() =>
ThirdPartyToolMigrationCompileErrorLogMatcher.Match(null));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 144 additions & 0 deletions Assets/Tests/Editor/ThirdPartyToolMigrationConsoleErrorParserTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System.Collections.Generic;

using NUnit.Framework;

using io.github.hatayama.UnityCliLoop.Infrastructure;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Verifies that raw Unity Console error text is parsed back into structured
/// CompileErrorLogEntry values, including Windows-style paths (backslashes, drive letters,
/// spaces) that must parse correctly regardless of the host OS running the test.
/// </summary>
public sealed class ThirdPartyToolMigrationConsoleErrorParserTests
{
private const string ProjectRoot = "/Users/dev/Project";

[Test]
public void Parse_WithProjectRelativeUnixPath_ReturnsEntryWithAbsolutePath()
{
// Verifies a standard csc/Roslyn diagnostic line with a project-relative Unix-style path
// is parsed into an absolute file path, line number, and code-prefixed message.
List<string> rawMessages = new List<string>
{
"Assets/Editor/Foo.cs(3,25): error CS0246: The type or namespace name 'Bar' could not be found"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(1));
Assert.That(entries[0].FilePath, Is.EqualTo("/Users/dev/Project/Assets/Editor/Foo.cs"));
Assert.That(entries[0].LineNumber, Is.EqualTo(3));
Assert.That(entries[0].Message, Does.Contain("Bar"));
}

[Test]
public void Parse_WithBackslashSeparatedRelativePath_NormalizesToForwardSlashAbsolutePath()
{
// Verifies Windows-style backslash separators in a project-relative path are normalized
// to forward slashes and combined with the project root, without relying on the host OS's
// own path-separator handling.
List<string> rawMessages = new List<string>
{
@"Assets\Editor\Foo.cs(3,25): error CS0246: The type or namespace name 'Bar' could not be found"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(1));
Assert.That(entries[0].FilePath, Is.EqualTo("/Users/dev/Project/Assets/Editor/Foo.cs"));
}

[Test]
public void Parse_WithWindowsDriveLetterAbsolutePath_KeepsPathAbsoluteWithoutProjectRoot()
{
// Verifies a Windows drive-letter absolute path is recognized as already-rooted (via an
// explicit drive-letter check, not Path.IsPathRooted, which is host-OS dependent) and is
// not incorrectly combined with the project root.
List<string> rawMessages = new List<string>
{
@"C:\Users\dev\Project\Assets\Editor\Foo.cs(10,1): error CS0246: The type or namespace name 'Bar' could not be found"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(1));
Assert.That(entries[0].FilePath, Is.EqualTo("C:/Users/dev/Project/Assets/Editor/Foo.cs"));
}

[Test]
public void Parse_WithSpacesInPath_ParsesFullPathIncludingSpaces()
{
// Verifies a path containing spaces (e.g. a "My Tools" folder) is parsed in full, not
// truncated at the space.
List<string> rawMessages = new List<string>
{
"Assets/My Tools/Foo.cs(5,10): error CS0246: The type or namespace name 'Bar' could not be found"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(1));
Assert.That(entries[0].FilePath, Is.EqualTo("/Users/dev/Project/Assets/My Tools/Foo.cs"));
}

[Test]
public void Parse_WithWarningSeverity_IsSkipped()
{
// Verifies warning-severity diagnostic lines are not treated as compile errors.
List<string> rawMessages = new List<string>
{
"Assets/Editor/Foo.cs(3,25): warning CS0219: The variable 'x' is never used"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(0));
}

[Test]
public void Parse_WithNonDiagnosticFormatLine_IsSkippedWithoutThrowing()
{
// Verifies plain (non-compiler-diagnostic) console messages are silently ignored.
List<string> rawMessages = new List<string>
{
"This is a regular Debug.LogError message with no file/line prefix"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(0));
}

[Test]
public void Parse_WithMultiLineMessage_UsesOnlyFirstLine()
{
// Verifies only the first physical line of a multi-line console message is parsed for the
// diagnostic file/line prefix.
List<string> rawMessages = new List<string>
{
"Assets/Editor/Foo.cs(3,25): error CS0246: The type or namespace name 'Bar' could not be found\nSome trailing detail line"
};

List<CompileErrorLogEntry> entries = ThirdPartyToolMigrationConsoleErrorParser.Parse(
rawMessages,
ProjectRoot);

Assert.That(entries.Count, Is.EqualTo(1));
Assert.That(entries[0].FilePath, Is.EqualTo("/Users/dev/Project/Assets/Editor/Foo.cs"));
}
}
}
Loading
Loading