xamarin-macios/tests/dotnet/UnitTests/TemplateTest.cs

371 строка
14 KiB
C#

using System.Text.Json;
#nullable enable
namespace Xamarin.Tests {
public enum TemplateLanguage {
CSharp,
FSharp,
VisualBasic,
}
public static class TemplateLanguageExtensions {
public static string AsString (this TemplateLanguage @this)
{
return @this switch {
TemplateLanguage.CSharp => "csharp",
TemplateLanguage.FSharp => "fsharp",
TemplateLanguage.VisualBasic => "visualbasic",
_ => throw new NotImplementedException ($"'{@this} is not implemented.'")
};
}
public static string AsFileExtension (this TemplateLanguage @this)
{
return @this switch {
TemplateLanguage.CSharp => "csproj",
TemplateLanguage.FSharp => "fsproj",
TemplateLanguage.VisualBasic => "vbproj",
_ => throw new NotImplementedException ($"'{@this} is not implemented.'")
};
}
public static string AsLanguageIdentifier (this TemplateLanguage @this)
{
return @this switch {
TemplateLanguage.CSharp => "C#",
TemplateLanguage.FSharp => "F#",
TemplateLanguage.VisualBasic => "VB",
_ => throw new NotImplementedException ($"'{@this} is not implemented.'")
};
}
}
[TestFixture]
public class TemplateTest : TestBaseClass {
public struct TemplateInfo {
public readonly ApplePlatform Platform;
public readonly TemplateLanguage? Language;
public readonly string Template;
public readonly bool Execute;
public readonly TemplateType TemplateType;
public TemplateInfo (ApplePlatform platform, string template, TemplateLanguage? language = null, bool execute = false)
{
Platform = platform;
Language = language;
Template = template;
Execute = execute;
TemplateType = ParseConfig (platform, language, template);
}
static TemplateType ParseConfig (ApplePlatform platform, TemplateLanguage? language, string template)
{
var languageDir = language?.AsString () ?? "";
var jsonPath = Path.Combine (Configuration.SourceRoot, "dotnet", "Templates", $"Microsoft.{platform.AsString ()}.Templates", template, languageDir, ".template.config", "template.json");
var options = new JsonSerializerOptions {
PropertyNameCaseInsensitive = true,
IncludeFields = true,
AllowTrailingCommas = false, // for max compat
};
try {
var json = JsonSerializer.Deserialize<TemplateConfig> (File.ReadAllText (jsonPath), options);
var type = json?.Tags?.Type;
return Enum.Parse<TemplateType> (type!, true);
} catch (Exception e) {
throw new Exception ($"Failed to parse {jsonPath}", e);
}
}
public override string ToString ()
{
return TemplateWithLanguage;
}
public string TemplateWithLanguage {
get => Language.HasValue ? $"{Template}-{Language.Value.AsString ()}" : Template;
}
}
public enum TemplateType {
Project,
Item,
}
public static TemplateInfo [] Templates = {
/* project templates */
new TemplateInfo (ApplePlatform.iOS, "ios", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.iOS, "ios", TemplateLanguage.FSharp),
new TemplateInfo (ApplePlatform.iOS, "ios", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.iOS, "ios-tabbed"),
new TemplateInfo (ApplePlatform.iOS, "ioslib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.iOS, "ioslib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.iOS, "iosbinding", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvos", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvos", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.TVOS, "tvoslib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.TVOS, "tvoslib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.TVOS, "tvosbinding", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst", TemplateLanguage.CSharp, execute: true),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst", TemplateLanguage.VisualBasic, execute: true),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystlib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystlib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalystbinding", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacOSX, "macos", TemplateLanguage.CSharp, execute: true),
new TemplateInfo (ApplePlatform.MacOSX, "macos", TemplateLanguage.VisualBasic, execute: true),
new TemplateInfo (ApplePlatform.MacOSX, "macoslib", TemplateLanguage.CSharp),
new TemplateInfo (ApplePlatform.MacOSX, "macoslib", TemplateLanguage.VisualBasic),
new TemplateInfo (ApplePlatform.MacOSX, "macosbinding", TemplateLanguage.CSharp),
/* item templates */
new TemplateInfo (ApplePlatform.iOS, "ios-controller"),
new TemplateInfo (ApplePlatform.iOS, "ios-storyboard"),
new TemplateInfo (ApplePlatform.iOS, "ios-view"),
new TemplateInfo (ApplePlatform.iOS, "ios-viewcontroller"),
new TemplateInfo (ApplePlatform.TVOS, "tvos-controller"),
new TemplateInfo (ApplePlatform.TVOS, "tvos-storyboard"),
new TemplateInfo (ApplePlatform.TVOS, "tvos-view"),
new TemplateInfo (ApplePlatform.TVOS, "tvos-viewcontroller"),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst-controller"),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst-storyboard"),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst-view"),
new TemplateInfo (ApplePlatform.MacCatalyst, "maccatalyst-viewcontroller"),
new TemplateInfo (ApplePlatform.MacOSX, "macos-controller"),
new TemplateInfo (ApplePlatform.MacOSX, "macos-storyboard"),
new TemplateInfo (ApplePlatform.MacOSX, "macos-view"),
new TemplateInfo (ApplePlatform.MacOSX, "macos-viewcontroller"),
};
public static TemplateInfo [] GetProjectTemplates ()
{
return Templates.Where (v => v.TemplateType == TemplateType.Project).ToArray ();
}
public class TemplateConfig {
public string? Name;
public string? ShortName;
public TemplateConfigTag? Tags;
}
public class TemplateConfigTag {
public string? Language;
public string? Type;
}
[Test]
public void AreAllTemplatesListed ()
{
var allListedTemplates = Templates.Select (v => v.TemplateWithLanguage).ToArray ();
var allTemplates = new List<string> ();
foreach (var platform in Enum.GetValues<ApplePlatform> ()) {
var dir = Path.Combine (Configuration.SourceRoot, "dotnet", "Templates", $"Microsoft.{platform.AsString ()}.Templates");
if (!Directory.Exists (dir))
continue;
var templateDirectories = Directory.GetDirectories (dir);
// read the template's configuration to figure out if it's a project template, and if not, skip it
foreach (var template in templateDirectories) {
var templateDir = Path.Combine (dir, template);
var hasFoundLangTemplate = false;
foreach (var language in Enum.GetValues<TemplateLanguage> ()) {
var langDir = Path.Combine (templateDir, language.AsString ());
if (!Directory.Exists (langDir))
continue;
var jsonPath = Path.Combine (langDir, ".template.config", "template.json");
Assert.That (jsonPath, Does.Exist, "Config file must exist");
if (!File.Exists (jsonPath))
continue;
allTemplates.Add ($"{Path.GetFileName (template)}-{language.AsString ()}");
hasFoundLangTemplate = true;
}
if (!hasFoundLangTemplate) {
var rootJsonPath = Path.Combine (templateDir, ".template.config", "template.json");
Assert.That (rootJsonPath, Does.Exist, "Config file must exist");
if (!File.Exists (rootJsonPath))
continue;
allTemplates.Add (Path.GetFileName (template));
}
}
}
Assert.That (allListedTemplates, Is.EquivalentTo (allTemplates), "The listed templates here and the templates on disk don't match");
}
[Test]
[TestCaseSource (nameof (GetProjectTemplates))]
public void CreateAndBuildProjectTemplate (TemplateInfo info)
{
Configuration.IgnoreIfIgnoredPlatform (info.Platform);
var language = info.Language ?? TemplateLanguage.CSharp;
var tmpDir = Cache.CreateTemporaryDirectory ();
var outputDir = Path.Combine (tmpDir, info.Template);
DotNet.AssertNew (outputDir, info.Template, language: language.AsLanguageIdentifier ());
var proj = Path.Combine (outputDir, $"{info.Template}.{language.AsFileExtension ()}");
var rv = DotNet.AssertBuild (proj);
var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message);
Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}");
if (info.Execute) {
var platform = info.Platform;
var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform);
Assert.IsTrue (CanExecute (info.Platform, runtimeIdentifiers), "Must be executable to execute!");
// First add some code to exit the template if it launches successfully.
InsertCodeToExitAppAfterLaunch (language, outputDir);
// Build the sample
rv = DotNet.AssertBuild (proj);
// There should still not be any warnings
warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message);
Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}");
var appPath = GetAppPath (proj, platform, runtimeIdentifiers);
var appExecutable = GetNativeExecutable (platform, appPath);
ExecuteWithMagicWordAndAssert (appExecutable);
}
}
[Test]
[TestCase (ApplePlatform.iOS, TemplateLanguage.CSharp)]
[TestCase (ApplePlatform.iOS, TemplateLanguage.FSharp)]
[TestCase (ApplePlatform.TVOS, TemplateLanguage.CSharp)]
[TestCase (ApplePlatform.MacCatalyst, TemplateLanguage.CSharp)]
[TestCase (ApplePlatform.MacOSX, TemplateLanguage.CSharp)]
public void CreateAndBuildItemTemplates (ApplePlatform platform, TemplateLanguage language)
{
Configuration.IgnoreIfIgnoredPlatform (platform);
// We create a new project from the basic project template, and then we add all the
// item templates for the given platforms. This is faster than testing the item templates
// one-by-one.
bool IsMatching (TemplateLanguage lang, TemplateLanguage? value)
{
if (lang == TemplateLanguage.CSharp)
return value is null or TemplateLanguage.CSharp;
return lang == value;
}
var info = Templates.Single (v => string.Equals (v.Template, platform.AsString (), StringComparison.OrdinalIgnoreCase) && IsMatching (language, v.Language));
var itemTemplates = Templates.Where (v => v.TemplateType == TemplateType.Item && v.Platform == platform && IsMatching (language, v.Language));
var tmpDir = Cache.CreateTemporaryDirectory ();
var outputDir = Path.Combine (tmpDir, info.Template);
DotNet.AssertNew (outputDir, info.Template, language: language.AsLanguageIdentifier ());
foreach (var item in itemTemplates)
DotNet.AssertNew (outputDir, item.Template, "item_" + item.Template, language: language.AsLanguageIdentifier ());
var proj = Path.Combine (outputDir, $"{info.Template}.{language.AsFileExtension ()}");
var rv = DotNet.AssertBuild (proj);
var warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message);
Assert.That (warnings, Is.Empty, $"Build warnings:\n\t{string.Join ("\n\t", warnings)}");
if (info.Execute) {
var runtimeIdentifiers = GetDefaultRuntimeIdentifier (platform);
Assert.IsTrue (CanExecute (info.Platform, runtimeIdentifiers), "Must be executable to execute!");
// First add some code to exit the template if it launches successfully.
InsertCodeToExitAppAfterLaunch (language, outputDir);
// Build the sample
rv = DotNet.AssertBuild (proj);
// There should still not be any warnings
warnings = BinLog.GetBuildLogWarnings (rv.BinLogPath).Select (v => v.Message);
Assert.That (warnings, Is.Empty, $"Build warnings (2):\n\t{string.Join ("\n\t", warnings)}");
var appPath = GetAppPath (proj, platform, runtimeIdentifiers);
var appExecutable = GetNativeExecutable (platform, appPath);
ExecuteWithMagicWordAndAssert (appExecutable);
}
}
static void InsertCodeToExitAppAfterLaunch (TemplateLanguage language, string outputDir)
{
switch (language) {
case TemplateLanguage.CSharp:
InsertCSharpCodeToExitAppAfterLaunch (outputDir);
break;
case TemplateLanguage.FSharp:
InsertFSharpCodeToExitAppAfterLaunch (outputDir);
break;
case TemplateLanguage.VisualBasic:
InsertVBCodeToExitAppAfterLaunch (outputDir);
break;
default:
throw new NotImplementedException ($"'Inserting {language} code is not implemented.'");
}
}
static void InsertCSharpCodeToExitAppAfterLaunch (string outputDir)
{
var mainFile = Path.Combine (outputDir, "Main.cs");
var mainContents = File.ReadAllText (mainFile);
var exitSampleWithSuccess = @"NSTimer.CreateScheduledTimer (1, (v) => {
Console.WriteLine (Environment.GetEnvironmentVariable (""MAGIC_WORD""));
Environment.Exit (0);
});
";
var modifiedMainContents =
mainContents.Replace ("// This is the main entry point of the application.",
exitSampleWithSuccess);
Assert.AreNotEqual (modifiedMainContents, mainContents, "Failed to modify the main content");
File.WriteAllText (mainFile, modifiedMainContents);
}
static void InsertFSharpCodeToExitAppAfterLaunch (string outputDir)
{
var mainFile = Path.Combine (outputDir, "Main.fs");
var mainContents = File.ReadAllText (mainFile);
// Attention: The indentation is important in F#, so to avoid having to deal with that, put everything on a single line.
var exitSampleWithSuccess =
@"Foundation.NSTimer.CreateScheduledTimer(1, fun _ -> System.Console.WriteLine(System.Environment.GetEnvironmentVariable(""MAGIC_WORD"")); System.Environment.Exit(0)) |> ignore";
var modifiedMainContents =
mainContents.Replace ("// This is the main entry point of the application.",
exitSampleWithSuccess);
Assert.AreNotEqual (modifiedMainContents, mainContents, "Failed to modify the main content");
File.WriteAllText (mainFile, modifiedMainContents);
}
static void InsertVBCodeToExitAppAfterLaunch (string outputDir)
{
var mainFile = Path.Combine (outputDir, "Main.vb");
var mainContents = File.ReadAllText (mainFile);
var exitSampleWithSuccess = @"Foundation.NSTimer.CreateScheduledTimer (1,
Sub (ByVal v)
Console.WriteLine (Environment.GetEnvironmentVariable (""MAGIC_WORD""))
Environment.Exit (0)
End Sub
)
";
var modifiedMainContents =
mainContents.Replace ("' This is the main entry point of the application.",
exitSampleWithSuccess);
Assert.AreNotEqual (modifiedMainContents, mainContents, "Failed to modify the main content");
File.WriteAllText (mainFile, modifiedMainContents);
}
}
}