Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of the test command and API #5263

Merged
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
60 changes: 60 additions & 0 deletions Microsoft.TemplateEngine.sln
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{B794BF86-4185-4DCE-AC86-C27D5D966B9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TemplateEngine.Authoring.TemplateVerifier", "src\Microsoft.TemplateEngine.Authoring.TemplateVerifier\Microsoft.TemplateEngine.Authoring.TemplateVerifier.csproj", "{12764D81-61A7-437A-90B6-9F245E43F457}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TemplateEngine.Authoring.TemplateVerifier.IntegrationTests", "test\Microsoft.TemplateEngine.Authoring.TemplateVerifier.IntegrationTests\Microsoft.TemplateEngine.Authoring.TemplateVerifier.IntegrationTests.csproj", "{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TemplateEngine.Authoring.TemplateVerifier.UnitTests", "test\Microsoft.TemplateEngine.Authoring.TemplateVerifier.UnitTests\Microsoft.TemplateEngine.Authoring.TemplateVerifier.UnitTests.csproj", "{D478568D-CA20-4331-9019-F585B564425E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.TemplateEngine.Authoring.CLI.UnitTests", "test\Microsoft.TemplateEngine.Authoring.CLI.UnitTests\Microsoft.TemplateEngine.Authoring.CLI.UnitTests.csproj", "{E8B9226E-879F-495A-BDAD-2607844D048C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -388,6 +396,54 @@ Global
{2FFDBB61-8AE8-468B-87D3-0D907D7C2FFE}.Release|x64.Build.0 = Release|Any CPU
{2FFDBB61-8AE8-468B-87D3-0D907D7C2FFE}.Release|x86.ActiveCfg = Release|Any CPU
{2FFDBB61-8AE8-468B-87D3-0D907D7C2FFE}.Release|x86.Build.0 = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|x64.ActiveCfg = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|x64.Build.0 = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|x86.ActiveCfg = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Debug|x86.Build.0 = Debug|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|Any CPU.Build.0 = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|x64.ActiveCfg = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|x64.Build.0 = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|x86.ActiveCfg = Release|Any CPU
{12764D81-61A7-437A-90B6-9F245E43F457}.Release|x86.Build.0 = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|x64.ActiveCfg = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|x64.Build.0 = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|x86.ActiveCfg = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Debug|x86.Build.0 = Debug|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|Any CPU.Build.0 = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|x64.ActiveCfg = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|x64.Build.0 = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|x86.ActiveCfg = Release|Any CPU
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9}.Release|x86.Build.0 = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|x64.ActiveCfg = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|x64.Build.0 = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|x86.ActiveCfg = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Debug|x86.Build.0 = Debug|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|Any CPU.Build.0 = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|x64.ActiveCfg = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|x64.Build.0 = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|x86.ActiveCfg = Release|Any CPU
{D478568D-CA20-4331-9019-F585B564425E}.Release|x86.Build.0 = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|x64.ActiveCfg = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|x64.Build.0 = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|x86.ActiveCfg = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Debug|x86.Build.0 = Debug|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|Any CPU.Build.0 = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|x64.ActiveCfg = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|x64.Build.0 = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|x86.ActiveCfg = Release|Any CPU
{E8B9226E-879F-495A-BDAD-2607844D048C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -419,6 +475,10 @@ Global
{B0330A2C-3F10-4C46-97DF-13D187564F70} = {8B498D0C-F488-4B38-8A7D-B20BF9DB6F60}
{BD758B10-A47F-4159-B9A1-997723AF7349} = {B794BF86-4185-4DCE-AC86-C27D5D966B9B}
{2FFDBB61-8AE8-468B-87D3-0D907D7C2FFE} = {8B498D0C-F488-4B38-8A7D-B20BF9DB6F60}
{12764D81-61A7-437A-90B6-9F245E43F457} = {B794BF86-4185-4DCE-AC86-C27D5D966B9B}
{B1DDA327-F55E-466A-AF3E-7F039B9B51A9} = {8B498D0C-F488-4B38-8A7D-B20BF9DB6F60}
{D478568D-CA20-4331-9019-F585B564425E} = {8B498D0C-F488-4B38-8A7D-B20BF9DB6F60}
{E8B9226E-879F-495A-BDAD-2607844D048C} = {8B498D0C-F488-4B38-8A7D-B20BF9DB6F60}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6EA1A508-6033-4538-BF98-7F71B4E297AD}
Expand Down
16 changes: 16 additions & 0 deletions eng/Signing.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,20 @@
<PropertyGroup>
<UseDotNetCertificate>true</UseDotNetCertificate>
</PropertyGroup>

<!--
These are third party libraries that we use in Arcade. We need to sign them even if they
are already signed. However, they must be signed with a 3rd party certificate.
-->
<ItemGroup>
<FileSignInfo Include="Argon.dll" CertificateName="3PartySHA2" />
Copy link
Member

Choose a reason for hiding this comment

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

just curious: why this was not needed before?

Copy link
Contributor

Choose a reason for hiding this comment

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

<FileSignInfo Include="DiffEngine.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="DiffPlex.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Verify.DiffPlex.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Verify.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Verify.Xunit.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="EmptyFiles.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="FluentAssertions.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="SimpleInfoName.dll" CertificateName="3PartySHA2" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion eng/dependabot/Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Update="xunit.abstractions" Version="2.0.3" />
<PackageReference Update="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Update="Verify.XUnit" Version="17.2.1" />
<PackageReference Update="Verify.XUnit" Version="18.0.0-beta.18" />
<PackageReference Update="Verify.DiffPlex" Version="1.3.0" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.CommandLine;
using System.CommandLine.Binding;
using System.CommandLine.Parsing;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.TemplateEngine.Authoring.TemplateVerifier;

namespace Microsoft.TemplateEngine.Authoring.CLI.Commands.Verify
{
internal class VerifyCommand : ExecutableCommand<VerifyCommandArgs>
{
private const string CommandName = "verify";

private readonly Argument<string> _templateNameArgument = new("template-short-name")
{
Description = LocalizableStrings.command_verify_help_templateName_description,
// 0 for case where only path is specified
Arity = new ArgumentArity(1, 1)
};

private readonly Option<string> _remainingArguments = new Option<string>("--template-args")
{
Description = "Template specific arguments - all joined into single enquoted string. Any needed quotations of actual arguments has to be escaped.",
Arity = new ArgumentArity(0, 1)
};

private readonly Option<string> _templatePathOption = new(new[] { "-p", "--template-path" })
{
Description = LocalizableStrings.command_verify_help_templatePath_description,
};

private readonly Option<string> _newCommandPathOption = new("--new-command-assembly")
{
Description = LocalizableStrings.command_verify_help_newCommandPath_description,
//TODO: do we have better way of distinguishing options that might rarely be needed?
// if not - we should probably add a link to more detailed help in the command description (mentioning that online help has additional options)
IsHidden = true
};

private readonly Option<string> _templateOutputPathOption = new(new[] { "-o", "--output" })
{
Description = LocalizableStrings.command_verify_help_outputPath_description,
};

private readonly Option<string> _expectationsDirectoryOption = new(new[] { "-d", "--expectations-directory" })
{
Description = LocalizableStrings.command_verify_help_expectationsDirPath_description,
};

private readonly Option<bool> _disableDiffToolOption = new("--disable-diff-tool")
{
Description = LocalizableStrings.command_verify_help_disableDiffTool_description,
};

private readonly Option<bool> _disableDefaultExcludePatternsOption = new("--disable-default-exclude-patterns")
{
Description = LocalizableStrings.command_verify_help_disableDefaultExcludes_description,
};

private readonly Option<IEnumerable<string>> _excludePatternOption = new("--exclude-pattern")
{
Description = LocalizableStrings.command_verify_help_customExcludes_description,
Arity = new ArgumentArity(0, 999)
};

private readonly Option<bool> _verifyCommandOutputOption = new("--verify-std")
{
Description = LocalizableStrings.command_verify_help_verifyOutputs_description,
};

private readonly Option<bool> _isCommandExpectedToFailOption = new("--fail-expected")
{
Description = LocalizableStrings.command_verify_help_expectFailure_description,
};

private readonly Option<IEnumerable<UniqueForOption>> _uniqueForOption = new("--unique-for")
{
Description = LocalizableStrings.command_verify_help_uniqueFor_description,
Arity = new ArgumentArity(0, 999),
AllowMultipleArgumentsPerToken = true,
};

public VerifyCommand(ILoggerFactory loggerFactory)
: base(CommandName, LocalizableStrings.command_verify_help_description, loggerFactory)
{
AddArgument(_templateNameArgument);
AddOption(_remainingArguments);
AddOption(_templatePathOption);
AddOption(_newCommandPathOption);
AddOption(_templateOutputPathOption);
AddOption(_expectationsDirectoryOption);
AddOption(_disableDiffToolOption);
AddOption(_disableDefaultExcludePatternsOption);
AddOption(_excludePatternOption);
AddOption(_verifyCommandOutputOption);
AddOption(_isCommandExpectedToFailOption);
FromAmongCaseInsensitive(
_uniqueForOption,
System.Enum.GetNames(typeof(UniqueForOption))
.Where(v => !v.Equals(UniqueForOption.None.ToString(), StringComparison.OrdinalIgnoreCase))
.ToArray());
JanKrivanek marked this conversation as resolved.
Show resolved Hide resolved
AddOption(_uniqueForOption);
}

internal static VerifyCommandArgs ExtractArguments(VerifyCommand verifyCommand, ParseResult parseResult)
{
return new VerifyCommandArgs(
templateName: parseResult.GetValueForArgument(verifyCommand._templateNameArgument),
templateSpecificArgs: parseResult.GetValueForOption(verifyCommand._remainingArguments),
templatePath: parseResult.GetValueForOption(verifyCommand._templatePathOption),
dotnetNewCommandAssemblyPath: parseResult.GetValueForOption(verifyCommand._newCommandPathOption),
expectationsDirectory: parseResult.GetValueForOption(verifyCommand._expectationsDirectoryOption),
outputDirectory: parseResult.GetValueForOption(verifyCommand._templateOutputPathOption),
disableDiffTool: parseResult.GetValueForOption(verifyCommand._disableDiffToolOption),
disableDefaultVerificationExcludePatterns: parseResult.GetValueForOption(verifyCommand._disableDefaultExcludePatternsOption),
verificationExcludePatterns: parseResult.GetValueForOption(verifyCommand._excludePatternOption),
verifyCommandOutput: parseResult.GetValueForOption(verifyCommand._verifyCommandOutputOption),
isCommandExpectedToFail: parseResult.GetValueForOption(verifyCommand._isCommandExpectedToFailOption),
uniqueForOptions: parseResult.GetValueForOption(verifyCommand._uniqueForOption));
}

protected override async Task<int> ExecuteAsync(VerifyCommandArgs args, CancellationToken cancellationToken = default)
{
Logger.LogInformation("Running the verification of {templateName}.", args.TemplateName);

try
{
VerificationEngine engine = new VerificationEngine(LoggerFactory ?? NullLoggerFactory.Instance);
TemplateVerifierOptions options = new(templateName: args.TemplateName)
{
TemplatePath = args.TemplatePath,
TemplateSpecificArgs = args.TemplateSpecificArgs,
DisableDiffTool = args.DisableDiffTool,
DisableDefaultVerificationExcludePatterns = args.DisableDefaultVerificationExcludePatterns,
VerificationExcludePatterns = args.VerificationExcludePatterns,
DotnetNewCommandAssemblyPath = args.DotnetNewCommandAssemblyPath,
ExpectationsDirectory = args.ExpectationsDirectory,
OutputDirectory = args.OutputDirectory,
VerifyCommandOutput = args.VerifyCommandOutput,
IsCommandExpectedToFail = args.IsCommandExpectedToFail,
UniqueFor = args.UniqueFor,
};
await engine.Execute(
options,
cancellationToken,
// We explicitly pass a path - so that the engine then process it and gets the current executing dir
// and treats it as a code base of caller of API (as in case of CLI usage we do not want to store
// expectation files in CLI sources dir)
Path.Combine(Environment.CurrentDirectory, "_")
).ConfigureAwait(false);
return 0;
}
catch (Exception e)
{
Logger.LogError(LocalizableStrings.command_verify_error_failed);
Logger.LogError(e.Message);
TemplateVerificationException? ex = e as TemplateVerificationException;
return (int)(ex?.TemplateVerificationErrorCode ?? TemplateVerificationErrorCode.InternalError);
}
}

protected override BinderBase<VerifyCommandArgs> GetModelBinder() => new VerifyModelBinder(this);

/// <summary>
/// Case insensitive version for <see cref="System.CommandLine.OptionExtensions.FromAmong{TOption}(TOption, string[])"/>.
/// </summary>
private static void FromAmongCaseInsensitive(Option option, string[]? allowedValues = null, string? allowedHiddenValue = null)
{
allowedValues ??= Array.Empty<string>();
option.AddValidator(optionResult => ValidateAllowedValues(optionResult, allowedValues, allowedHiddenValue));
option.AddCompletions(allowedValues);
}

private static void ValidateAllowedValues(OptionResult optionResult, string[] allowedValues, string? allowedHiddenValue = null)
{
var invalidArguments = optionResult.Tokens.Where(token => !allowedValues.Append(allowedHiddenValue).Contains(token.Value, StringComparer.OrdinalIgnoreCase)).ToList();
if (invalidArguments.Any())
{
optionResult.ErrorMessage = string.Format(
LocalizableStrings.command_verify_error_unrecognizedArguments,
string.Join(", ", invalidArguments.Select(arg => $"'{arg.Value}'")),
string.Join(", ", allowedValues.Select(allowedValue => $"'{allowedValue}'")));
}
}

private class VerifyModelBinder : BinderBase<VerifyCommandArgs>
{
private readonly VerifyCommand _verifyCommand;

internal VerifyModelBinder(VerifyCommand verifyCommand)
{
_verifyCommand = verifyCommand;
}

protected override VerifyCommandArgs GetBoundValue(BindingContext bindingContext)
{
return VerifyCommand.ExtractArguments(_verifyCommand, bindingContext.ParseResult);
}
}
}
}
Loading