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
6 changes: 6 additions & 0 deletions GVFS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Payload", "GVFS\GVFS.P
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Installers", "GVFS\GVFS.Installers\GVFS.Installers.csproj", "{258FEAC0-5E2D-408A-9652-9E9653219F3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.CommandLine.Tests", "GVFS\GVFS.CommandLine.Tests\GVFS.CommandLine.Tests.csproj", "{4D201963-957A-436A-8E43-79A63FB84B94}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Expand Down Expand Up @@ -135,6 +137,10 @@ Global
{258FEAC0-5E2D-408A-9652-9E9653219F3B}.Debug|x64.Build.0 = Debug|Any CPU
{258FEAC0-5E2D-408A-9652-9E9653219F3B}.Release|x64.ActiveCfg = Release|Any CPU
{258FEAC0-5E2D-408A-9652-9E9653219F3B}.Release|x64.Build.0 = Release|Any CPU
{4D201963-957A-436A-8E43-79A63FB84B94}.Debug|x64.ActiveCfg = Debug|Any CPU
{4D201963-957A-436A-8E43-79A63FB84B94}.Debug|x64.Build.0 = Debug|Any CPU
{4D201963-957A-436A-8E43-79A63FB84B94}.Release|x64.ActiveCfg = Release|Any CPU
{4D201963-957A-436A-8E43-79A63FB84B94}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion GVFS/FastFetch/FastFetchVerb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static RootCommand BuildRootCommand()
};
rootCommand.Add(foldersListOption);

Option<bool> allowIndexMetadataOption = new Option<bool>("--Allow-index-metadata-update-from-working-tree") { Description = "When specified, index metadata is updated from disk if not already in the index." };
Option<bool> allowIndexMetadataOption = new Option<bool>("--allow-index-metadata-update-from-working-tree") { Description = "When specified, index metadata is updated from disk if not already in the index." };
rootCommand.Add(allowIndexMetadataOption);

Option<bool> verboseOption = new Option<bool>("--verbose") { Description = "Show all outputs on the console in addition to writing them to a log file" };
Expand Down
3 changes: 3 additions & 0 deletions GVFS/FastFetch/InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("GVFS.CommandLine.Tests")]
7 changes: 6 additions & 1 deletion GVFS/FastFetch/Program.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using System.CommandLine;
using System.Runtime.CompilerServices;
using GVFS.PlatformLoader;

[assembly: InternalsVisibleTo("GVFS.CommandLine.Tests")]

namespace FastFetch
{
public class Program
{
public static void Main(string[] args)
{
GVFSPlatformLoader.Initialize();
RootCommand rootCommand = FastFetchVerb.BuildRootCommand();
RootCommand rootCommand = BuildRootCommand();
rootCommand.Parse(args).Invoke();
}

internal static RootCommand BuildRootCommand() => FastFetchVerb.BuildRootCommand();
}
}
237 changes: 237 additions & 0 deletions GVFS/GVFS.CommandLine.Tests/FastFetchCliTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Linq;
using NUnit.Framework;

namespace GVFS.CommandLine.Tests
{
/// <summary>
/// Tests that FastFetch CLI parsing matches the original CommandLineParser behavior.
/// Verifies short aliases, defaults, and option names are backward-compatible.
/// </summary>
[TestFixture]
public class FastFetchCliTests
{
private RootCommand rootCommand;

[SetUp]
public void SetUp()
{
rootCommand = FastFetch.Program.BuildRootCommand();
}

#region Short Aliases

[Test]
public void CommitOption_HasShortAlias_C()
{
var opt = FindOption("--commit");
Assert.That(opt, Is.Not.Null, "Expected --commit option to exist");
Assert.That(opt.Aliases, Does.Contain("-c"), "Expected -c short alias for --commit");
}

[Test]
public void BranchOption_HasShortAlias_B()
{
var opt = FindOption("--branch");
Assert.That(opt, Is.Not.Null, "Expected --branch option to exist");
Assert.That(opt.Aliases, Does.Contain("-b"), "Expected -b short alias for --branch");
}

[Test]
public void MaxRetriesOption_HasShortAlias_R()
{
var opt = FindOption("--max-retries");
Assert.That(opt, Is.Not.Null, "Expected --max-retries option to exist");
Assert.That(opt.Aliases, Does.Contain("-r"), "Expected -r short alias for --max-retries");
}

[TestCase("-c", "abc123")]
[TestCase("-b", "main")]
[TestCase("-r", "5")]
public void ShortAliases_ParseCorrectly(string alias, string value)
{
var parseResult = rootCommand.Parse(new[] { alias, value });
Assert.That(parseResult.Errors, Is.Empty, $"Parsing '{alias} {value}' should produce no errors");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about cases where it should fail to parse?

}

#endregion

#region Default Values

[Test]
public void ChunkSize_DefaultsTo4000()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());
var opt = FindOption<int>("--chunk-size");
Assert.That(opt, Is.Not.Null);
Assert.That(parseResult.GetValue(opt), Is.EqualTo(4000),
"ChunkSize should default to 4000 when not specified");
}

[Test]
public void MaxRetries_DefaultsTo10()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());
var opt = FindOption<int>("--max-retries");
Assert.That(opt, Is.Not.Null);
Assert.That(parseResult.GetValue(opt), Is.EqualTo(10),
"MaxRetries should default to 10 when not specified");
}

[Test]
public void Folders_DefaultsToEmptyString()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());
var opt = FindOption<string>("--folders");
Assert.That(opt, Is.Not.Null);
Assert.That(parseResult.GetValue(opt), Is.EqualTo(""),
"Folders should default to empty string when not specified");
}

[Test]
public void FoldersList_DefaultsToEmptyString()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());
var opt = FindOption<string>("--folders-list");
Assert.That(opt, Is.Not.Null);
Assert.That(parseResult.GetValue(opt), Is.EqualTo(""),
"FoldersList should default to empty string when not specified");
}

[Test]
public void BooleanOptions_DefaultToFalse()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());

var checkout = FindOption<bool>("--checkout");
var forceCheckout = FindOption<bool>("--force-checkout");
var verbose = FindOption<bool>("--verbose");
var allowIndexMetadata = FindOption<bool>("--allow-index-metadata-update-from-working-tree");

Assert.Multiple(() =>
{
Assert.That(parseResult.GetValue(checkout), Is.False, "--checkout should default to false");
Assert.That(parseResult.GetValue(forceCheckout), Is.False, "--force-checkout should default to false");
Assert.That(parseResult.GetValue(verbose), Is.False, "--verbose should default to false");
Assert.That(parseResult.GetValue(allowIndexMetadata), Is.False, "--allow-index-metadata-update-from-working-tree should default to false");
});
}

[Test]
public void IntThreadOptions_DefaultToZero()
{
var parseResult = rootCommand.Parse(System.Array.Empty<string>());

var search = FindOption<int>("--search-thread-count");
var download = FindOption<int>("--download-thread-count");
var index = FindOption<int>("--index-thread-count");
var checkoutThread = FindOption<int>("--checkout-thread-count");

Assert.Multiple(() =>
{
Assert.That(parseResult.GetValue(search), Is.EqualTo(0));
Assert.That(parseResult.GetValue(download), Is.EqualTo(0));
Assert.That(parseResult.GetValue(index), Is.EqualTo(0));
Assert.That(parseResult.GetValue(checkoutThread), Is.EqualTo(0));
});
}

#endregion

#region Explicit Value Parsing

[Test]
public void ChunkSize_ExplicitValue_Overrides()
{
var parseResult = rootCommand.Parse(new[] { "--chunk-size", "8000" });
var opt = FindOption<int>("--chunk-size");
Assert.That(parseResult.GetValue(opt), Is.EqualTo(8000));
}

[Test]
public void MaxRetries_ExplicitValue_Overrides()
{
var parseResult = rootCommand.Parse(new[] { "--max-retries", "3" });
var opt = FindOption<int>("--max-retries");
Assert.That(parseResult.GetValue(opt), Is.EqualTo(3));
}

[Test]
public void CommitAndBranch_ParseWithShortAliases()
{
var parseResult = rootCommand.Parse(new[] { "-c", "abc123", "-b", "feature/test" });
var commitOpt = FindOption<string>("--commit");
var branchOpt = FindOption<string>("--branch");
Assert.Multiple(() =>
{
Assert.That(parseResult.GetValue(commitOpt), Is.EqualTo("abc123"));
Assert.That(parseResult.GetValue(branchOpt), Is.EqualTo("feature/test"));
});
}

[Test]
public void AllStringOptions_ParseCorrectly()
{
var parseResult = rootCommand.Parse(new[]
{
"--commit", "abc123",
"--branch", "main",
"--cache-server-url", "https://cache.example.com",
"--git-path", @"C:\Program Files\Git\bin\git.exe",
"--folders", "src;lib",
"--folders-list", @"C:\folders.txt",
"--parent-activity-id", "12345678-1234-1234-1234-123456789012"
});

Assert.That(parseResult.Errors, Is.Empty, "All string options should parse without errors");
}

[Test]
public void MaxRetries_ShortAlias_R_ParsesCorrectly()
{
var parseResult = rootCommand.Parse(new[] { "-r", "5" });
var opt = FindOption<int>("--max-retries");
Assert.That(parseResult.GetValue(opt), Is.EqualTo(5));
}

#endregion

#region All Expected Options Exist

[Test]
public void AllExpectedOptions_Exist()
{
var expectedOptions = new[]
{
"--commit", "--branch", "--cache-server-url", "--chunk-size",
"--checkout", "--force-checkout", "--search-thread-count",
"--download-thread-count", "--index-thread-count", "--checkout-thread-count",
"--max-retries", "--git-path", "--folders", "--folders-list",
"--allow-index-metadata-update-from-working-tree", "--verbose",
"--parent-activity-id"
};

foreach (var optName in expectedOptions)
{
Assert.That(FindOption(optName), Is.Not.Null, $"Expected option {optName} to exist");
}
}

#endregion

#region Helpers

private Option FindOption(string name)
{
return rootCommand.Options.FirstOrDefault(o => o.Name == name || o.Aliases.Contains(name));
}

private Option<T> FindOption<T>(string name)
{
return rootCommand.Options.FirstOrDefault(o => o.Name == name || o.Aliases.Contains(name)) as Option<T>;
}

#endregion
}
}
21 changes: 21 additions & 0 deletions GVFS/GVFS.CommandLine.Tests/GVFS.CommandLine.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net471</TargetFramework>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NUnitLite" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="System.CommandLine" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FastFetch\FastFetch.csproj" />
<ProjectReference Include="..\GVFS.Mount\GVFS.Mount.csproj" />
<ProjectReference Include="..\GVFS\GVFS.csproj" />
</ItemGroup>

</Project>
Loading
Loading