|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public class SGen_Tests
{
private readonly ITestOutputHelper _output;
public SGen_Tests(ITestOutputHelper output)
{
_output = output;
}
#if RUNTIME_TYPE_NETCORE
[Fact]
public void TaskFailsOnCore()
{
using TestEnvironment testenv = TestEnvironment.Create(_output);
MockLogger logger = ObjectModelHelpers.BuildProjectExpectFailure(@$"
<Project>
<Target Name=""MyTarget"">
<SGen
BuildAssemblyName=""Foo""
BuildAssemblyPath=""Foo""
ShouldGenerateSerializer=""true""
UseProxyTypes=""true""
UseKeep=""true""
References=""Foo""
KeyContainer=""Foo""
KeyFile=""Foo""
DelaySign=""true""
SerializationAssembly=""Foo""
SdkToolsPath=""Foo""
Platform=""Foo""
Types=""Foo""
/>
</Target>
</Project>");
logger.ErrorCount.ShouldBe(1);
logger.Errors.First().Code.ShouldBe("MSB3474");
}
#else
internal sealed class SGenExtension : SGen
{
internal string CommandLine()
{
return base.GenerateCommandLineCommands();
}
}
[Fact]
public void KeyFileQuotedOnCommandLineIfNecessary()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
// This should result in a nested, quoted parameter on
// the command line, which ultimately looks like this:
//
// /compiler:"/keyfile:\"c:\Some Folder\MyKeyFile.snk\""
//
sgen.KeyFile = "c:\\Some Folder\\MyKeyFile.snk";
string commandLine = sgen.CommandLine();
Assert.True(commandLine.IndexOf("/compiler:\"/keyfile:\\\"" + sgen.KeyFile + "\\\"\"", StringComparison.OrdinalIgnoreCase) >= 0);
}
[Fact]
public void TestKeepFlagTrue()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
sgen.UseKeep = true;
string commandLine = sgen.CommandLine();
Assert.True(commandLine.IndexOf("/keep", StringComparison.OrdinalIgnoreCase) >= 0);
}
[Fact]
public void TestKeepFlagFalse()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
sgen.UseKeep = false;
string commandLine = sgen.CommandLine();
Assert.True(commandLine.IndexOf("/keep", StringComparison.OrdinalIgnoreCase) < 0);
}
[Fact]
public void TestInputChecks1()
{
MockEngine engine = new MockEngine();
SGenExtension sgen = new SGenExtension();
sgen.BuildEngine = engine;
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" + Path.GetInvalidPathChars()[0];
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
// This should result in a quoted parameter...
sgen.KeyFile = "c:\\Some Folder\\MyKeyFile.snk";
string commandLine = sgen.CommandLine();
Assert.Equal(1, engine.Errors);
}
[Fact]
public void TestInputChecks2()
{
MockEngine engine = new MockEngine();
SGenExtension sgen = new SGenExtension();
sgen.BuildEngine = engine;
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll" + Path.GetInvalidPathChars()[0];
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
// This should result in a quoted parameter...
sgen.KeyFile = "c:\\Some Folder\\MyKeyFile.snk";
string commandLine = sgen.CommandLine();
Assert.Equal(1, engine.Errors);
}
[Fact]
public void TestInputChecks3()
{
Assert.Throws<ArgumentNullException>(() =>
{
MockEngine engine = new MockEngine();
SGenExtension sgen = new SGenExtension();
sgen.BuildEngine = engine;
sgen.BuildAssemblyName = null;
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
// This should result in a quoted parameter...
sgen.KeyFile = "c:\\Some Folder\\MyKeyFile.snk";
string commandLine = sgen.CommandLine();
});
}
[Fact]
public void TestInputChecks4()
{
Assert.Throws<ArgumentNullException>(() =>
{
MockEngine engine = new MockEngine();
SGenExtension sgen = new SGenExtension();
sgen.BuildEngine = engine;
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = null;
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
// This should result in a quoted parameter...
sgen.KeyFile = "c:\\Some Folder\\MyKeyFile.snk";
string commandLine = sgen.CommandLine();
});
}
[Fact]
public void TestInputPlatform()
{
SGenExtension sgen = new SGenExtension();
sgen.Platform = "x86";
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = NativeMethodsShared.IsUnixLike
? "/SomeFolder/MyAsm.dll"
: "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
string commandLine = sgen.CommandLine();
string targetCommandLine = "/assembly:\"" + sgen.BuildAssemblyPath + Path.DirectorySeparatorChar
+ "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" /compiler:/platform:x86";
Assert.Equal(targetCommandLine, commandLine);
}
[Fact]
public void TestInputTypes()
{
SGenExtension sgen = new SGenExtension();
sgen.Types = new string[] { "System.String", "System.Boolean" };
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = NativeMethodsShared.IsUnixLike
? "/SomeFolder/MyAsm.dll"
: "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
string commandLine = sgen.CommandLine();
string targetCommandLine = "/assembly:\"" + sgen.BuildAssemblyPath + Path.DirectorySeparatorChar
+ "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" /type:System.String /type:System.Boolean";
Assert.Equal(targetCommandLine, commandLine);
}
[Fact]
public void TestInputEmptyTypesAndPlatform()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = NativeMethodsShared.IsUnixLike ? "/SomeFolder/MyAsm.dll" : "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
string commandLine = sgen.CommandLine();
string targetCommandLine = "/assembly:\"" + sgen.BuildAssemblyPath + Path.DirectorySeparatorChar
+ "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"";
Assert.Equal(targetCommandLine, commandLine);
}
[Fact]
public void TestNullReferences()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
sgen.UseKeep = false;
sgen.References = null;
string commandLine = sgen.CommandLine();
Assert.True(commandLine.IndexOf("/reference:", StringComparison.OrdinalIgnoreCase) < 0);
}
[Fact]
public void TestEmptyReferences()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
sgen.UseKeep = false;
sgen.References = Array.Empty<string>();
string commandLine = sgen.CommandLine();
Assert.True(commandLine.IndexOf("/reference:", StringComparison.OrdinalIgnoreCase) < 0);
}
[Fact]
public void TestReferencesCommandLine()
{
SGenExtension sgen = new SGenExtension();
sgen.BuildAssemblyName = "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
sgen.BuildAssemblyPath = "C:\\SomeFolder\\MyAsm.dll";
sgen.ShouldGenerateSerializer = true;
sgen.UseProxyTypes = false;
sgen.UseKeep = false;
sgen.References = new string[] { "C:\\SomeFolder\\reference1.dll", "C:\\SomeFolder\\reference2.dll" };
string commandLine = sgen.CommandLine();
string targetCommandLine = "/assembly:\"" + sgen.BuildAssemblyPath + Path.DirectorySeparatorChar
+ "MyAsm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\" /reference:\"C:\\SomeFolder\\reference1.dll,C:\\SomeFolder\\reference2.dll\"";
Assert.Equal(targetCommandLine, commandLine);
}
/// <summary>
/// Verifies that GenerateFullPathToTool resolves SdkToolsPath relative to the
/// TaskEnvironment's project directory and returns an absolute path, validating
/// the TaskEnvironment.GetAbsolutePath() integration.
/// </summary>
[Fact]
public void GenerateFullPathToTool_ResolvesRelativeSdkToolsPathViaTaskEnvironment()
{
using TestEnvironment env = TestEnvironment.Create(_output);
string projectDir = env.CreateFolder().Path;
string sdkSubDir = "sdk";
string sdkFullDir = Path.Combine(projectDir, sdkSubDir);
Directory.CreateDirectory(sdkFullDir);
string fakeTool = Path.Combine(sdkFullDir, "sgen.exe");
File.WriteAllBytes(fakeTool, []);
using var driver = new MultiThreadedTaskEnvironmentDriver(projectDir);
var taskEnv = new TaskEnvironment(driver);
TestableSGen t = new TestableSGen();
t.TaskEnvironment = taskEnv;
t.BuildEngine = new MockEngine(_output);
t.SdkToolsPath = sdkSubDir; // relative path — must resolve via TaskEnvironment
string result = t.CallGenerateFullPathToTool();
result.ShouldNotBeNull("GenerateFullPathToTool should find the tool via SdkToolsPath");
result.ShouldBe(fakeTool);
}
/// <summary>
/// Verifies that in GetProcessStartInfo
/// working directory comes from the TaskEnvironment.
/// </summary>
[Fact]
public void GetProcessStartInfo_UsesTaskEnvironmentWorkingDirectory()
{
using TestEnvironment env = TestEnvironment.Create(_output);
string expectedWorkingDir = env.DefaultTestDirectory.Path;
using var driver = new MultiThreadedTaskEnvironmentDriver(expectedWorkingDir);
var taskEnv = new TaskEnvironment(driver);
TestableSGen t = new TestableSGen();
t.TaskEnvironment = taskEnv;
t.BuildEngine = new MockEngine(_output);
ProcessStartInfo startInfo = t.CallGetProcessStartInfo(Path.Combine(expectedWorkingDir, "sgen.exe"), "/nologo", null);
startInfo.WorkingDirectory.ShouldBe(expectedWorkingDir);
}
/// <summary>
/// Verifies that BuildAssemblyPath uses TaskEnvironment.GetAbsolutePath
/// instead of Path.GetFullPath, by providing a relative path that resolves
/// correctly against the TaskEnvironment's project directory.
/// </summary>
[Fact]
public void BuildAssemblyPath_UsesTaskEnvironmentGetAbsolutePath()
{
using TestEnvironment env = TestEnvironment.Create(_output);
string projectDir = env.CreateFolder().Path;
string subDir = "output";
Directory.CreateDirectory(Path.Combine(projectDir, subDir));
using var driver = new MultiThreadedTaskEnvironmentDriver(projectDir);
var taskEnv = new TaskEnvironment(driver);
TestableSGen t = new TestableSGen();
t.TaskEnvironment = taskEnv;
t.BuildEngine = new MockEngine(_output);
t.BuildAssemblyPath = subDir; // relative path
string result = t.BuildAssemblyPath;
result.ShouldBe(Path.Combine(projectDir, subDir));
}
/// <summary>
/// Subclass that exposes protected methods for testing without reflection.
/// </summary>
private sealed class TestableSGen : SGen
{
public string CallGenerateFullPathToTool() => GenerateFullPathToTool();
public ProcessStartInfo CallGetProcessStartInfo(string pathToTool, string commandLineCommands, string responseFileSwitch)
=> GetProcessStartInfo(pathToTool, commandLineCommands, responseFileSwitch);
}
#endif
}
}
|