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
194 changes: 194 additions & 0 deletions src/Tasks.UnitTests/GetAssemblyIdentity_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// 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.IO;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;

#nullable disable
Comment thread
jankratochvilcz marked this conversation as resolved.

namespace Microsoft.Build.UnitTests
{
public sealed class GetAssemblyIdentity_Tests
{
private readonly ITestOutputHelper _output;

public GetAssemblyIdentity_Tests(ITestOutputHelper output)
{
_output = output;
}

private GetAssemblyIdentity CreateTaskUnderTest(MockEngine engine = null)
{
return new GetAssemblyIdentity
{
BuildEngine = engine ?? new MockEngine(_output),
};
}

[Fact]
public void AbsolutePathToExistingAssembly_ProducesCorrectIdentity()
{
// Use this test assembly as the input — it's guaranteed to exist.
// Use the compile-time assembly name as the baseline rather than
// AssemblyName.GetAssemblyName(path), to avoid asserting against the same
// reflection API the production code uses internally.
string assemblyPath = typeof(GetAssemblyIdentity_Tests).Assembly.Location;
AssemblyName expectedName = typeof(GetAssemblyIdentity_Tests).Assembly.GetName();

GetAssemblyIdentity task = CreateTaskUnderTest();
task.AssemblyFiles = [new TaskItem(assemblyPath)];

task.Execute().ShouldBeTrue();

task.Assemblies.Length.ShouldBe(1);
task.Assemblies[0].ItemSpec.ShouldBe(expectedName.FullName);
task.Assemblies[0].GetMetadata("Name").ShouldBe(expectedName.Name);
task.Assemblies[0].GetMetadata("Version").ShouldBe(expectedName.Version.ToString());
}

[Fact]
public void NonExistentFile_LogsErrorAndReturnsFalse()
{
var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
task.AssemblyFiles = [new TaskItem("does-not-exist.dll")];

task.Execute().ShouldBeFalse();

AssertCouldNotGetAssemblyName(engine);
engine.Log.ShouldContain("does-not-exist.dll");
}

[Fact]
public void NonAssemblyFile_LogsErrorAndReturnsFalse()
{
using TestEnvironment env = TestEnvironment.Create(_output);
TransientTestFile textFile = env.CreateFile("notanassembly.txt", "This is not an assembly.");

var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
task.AssemblyFiles = [new TaskItem(textFile.Path)];

task.Execute().ShouldBeFalse();

AssertCouldNotGetAssemblyName(engine);
}

[Fact]
public void MixedBatch_GoodAndBadItems()
{
using TestEnvironment env = TestEnvironment.Create(_output);
TransientTestFile textFile = env.CreateFile("bad.txt", "not an assembly");

string goodAssemblyPath = typeof(GetAssemblyIdentity_Tests).Assembly.Location;
AssemblyName expectedName = typeof(GetAssemblyIdentity_Tests).Assembly.GetName();

var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
task.AssemblyFiles =
[
new TaskItem(goodAssemblyPath),
new TaskItem(textFile.Path),
];

// Execute returns false because one item failed.
task.Execute().ShouldBeFalse();

// The good item should still appear in the output.
task.Assemblies.Length.ShouldBe(1);
task.Assemblies[0].ItemSpec.ShouldBe(expectedName.FullName);

// The bad item should have produced an error.
engine.Errors.ShouldBe(1);
}

[Fact]
public void EmptyItemSpec_LogsErrorAndReturnsFalse()
{
var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
task.AssemblyFiles = [new TaskItem("")];

task.Execute().ShouldBeFalse();

AssertCouldNotGetAssemblyName(engine);
}

[Fact]
public void NullItemSpec_LogsErrorAndReturnsFalse()
{
var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
Assert.Throws<ArgumentNullException>(() => task.AssemblyFiles = [new TaskItem((ITaskItem)null)]);
}

[Fact]
public void RelativePath_ResolvesAgainstProjectDirectory()
{
using TestEnvironment env = TestEnvironment.Create(_output);
TransientTestFolder projectDir = env.CreateFolder();

string sourceAssembly = typeof(GetAssemblyIdentity_Tests).Assembly.Location;
AssemblyName expectedName = typeof(GetAssemblyIdentity_Tests).Assembly.GetName();

string relativeFileName = "TestAssembly.dll";
File.Copy(sourceAssembly, Path.Combine(projectDir.Path, relativeFileName));

var engine = new MockEngine(_output);
GetAssemblyIdentity task = CreateTaskUnderTest(engine);
task.TaskEnvironment = TaskEnvironment.CreateWithProjectDirectoryAndEnvironment(projectDir.Path);
Comment thread
jankratochvilcz marked this conversation as resolved.
task.AssemblyFiles = [new TaskItem(relativeFileName)];

task.Execute().ShouldBeTrue();

task.Assemblies.Length.ShouldBe(1);
task.Assemblies[0].ItemSpec.ShouldBe(expectedName.FullName);
}
Comment thread
jankratochvilcz marked this conversation as resolved.

[Fact]
public void OutputItem_DoesNotContainAbsolutizedPaths()
{
string assemblyPath = typeof(GetAssemblyIdentity_Tests).Assembly.Location;
AssemblyName expectedName = typeof(GetAssemblyIdentity_Tests).Assembly.GetName();

GetAssemblyIdentity task = CreateTaskUnderTest();
task.AssemblyFiles = [new TaskItem(assemblyPath)];

task.Execute().ShouldBeTrue();

task.Assemblies.Length.ShouldBe(1);

// ItemSpec should be the assembly identity string, not a path.
task.Assemblies[0].ItemSpec.ShouldBe(expectedName.FullName);
Comment thread
jankratochvilcz marked this conversation as resolved.
}

[Fact]
public void OutputItem_CopiesInputMetadataVerbatim()
{
string assemblyPath = typeof(GetAssemblyIdentity_Tests).Assembly.Location;

var inputItem = new TaskItem(assemblyPath);
inputItem.SetMetadata("CustomMeta", "CustomValue");

GetAssemblyIdentity task = CreateTaskUnderTest();
task.AssemblyFiles = [inputItem];

task.Execute().ShouldBeTrue();

task.Assemblies.ShouldHaveSingleItem()
.GetMetadata("CustomMeta").ShouldBe("CustomValue");
}

private void AssertCouldNotGetAssemblyName(MockEngine engine)
{
engine.Errors.ShouldBe(1);
engine.Log.ShouldContain("MSB3441");
}
}
}
13 changes: 11 additions & 2 deletions src/Tasks/GetAssemblyIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ namespace Microsoft.Build.Tasks
/// Input: Assembly Include="foo.exe"
/// Output: Identity Include="Foo, Version=1.0.0.0", Name="Foo, Version="1.0.0.0"
/// </comment>
public class GetAssemblyIdentity : TaskExtension
[MSBuildMultiThreadableTask]
public class GetAssemblyIdentity : TaskExtension, IMultiThreadableTask
{
/// <inheritdoc />
public TaskEnvironment TaskEnvironment { get; set; } = TaskEnvironment.Fallback;

private ITaskItem[] _assemblyFiles;

[Required]
Expand Down Expand Up @@ -65,10 +69,14 @@ public override bool Execute()
var list = new List<ITaskItem>();
foreach (ITaskItem item in AssemblyFiles)
{
string assemblyPath = !string.IsNullOrEmpty(item.ItemSpec)
? TaskEnvironment.GetAbsolutePath(item.ItemSpec)
: item.ItemSpec;

AssemblyName an;
try
Comment thread
OvesN marked this conversation as resolved.
{
an = AssemblyName.GetAssemblyName(item.ItemSpec);
an = AssemblyName.GetAssemblyName(assemblyPath);
}
catch (BadImageFormatException e)
{
Expand Down Expand Up @@ -100,6 +108,7 @@ public override bool Execute()
item.CopyMetadataTo(newItem);
list.Add(newItem);
}

Assemblies = list.ToArray();
return !Log.HasLoggedErrors;
}
Expand Down