From ea3bfdc59e67ef202554590bc6cde6385ee1db5f Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Thu, 9 Apr 2026 16:37:21 +0200 Subject: [PATCH 1/5] Fixes task interface validation gap compared to CodeTaskFactory --- .../RoslynCodeTaskFactory_Tests.cs | 83 +++++++++++++++++++ .../RoslynCodeTaskFactory.cs | 8 +- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 713c5613419..2aabfebd30b 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -784,6 +784,49 @@ public override bool Execute() } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ClassDoesNotInheritFromITask(bool forceOutOfProc) + { + const string taskName = "ClassDoesNotInheritFromITask"; + string unformattedMessage = ResourceUtilities.GetResourceString("CodeTaskFactory.NeedsITaskInterface"); + + string projectContent = $$""" + + + + + namespace InlineTask + { + public class {{taskName}} + { + public bool Execute() + { + return true; + } + } + } + + + + + <{{taskName}} /> + + + """; + + using TestEnvironment env = TestEnvironment.Create(); + if (forceOutOfProc) + { + env.SetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC", "1"); + } + + TransientTestProjectWithFiles proj = env.CreateTestProjectWithFiles(projectContent); + MockLogger logger = proj.BuildProjectExpectFailure(); + logger.AssertLogContains(unformattedMessage); + } + [Fact] public void EmbedsGeneratedFromSourceFileInBinlog() { @@ -909,6 +952,46 @@ public void RoslynCodeTaskFactory_UsingAPI(bool forceOutOfProc) } #endif + [Fact] + public void BuildRoslynCodeTaskFactoryTempDirectoryDoesntExist() + { + string text = """ + + + + + + + + Log.LogMessage(MessageImportance.High, Text); + + + + + + + + """; + + var newTempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + using var env = TestEnvironment.Create(); + + Directory.Exists(newTempPath).ShouldBeFalse(); + env.SetEnvironmentVariable("TMP", newTempPath); + env.SetEnvironmentVariable("TMPDIR", newTempPath); + + try + { + MockLogger logger = Helpers.BuildProjectWithNewOMExpectSuccess(text); + logger.AssertLogContains("Hello, World!"); + } + finally + { + FileUtilities.DeleteDirectoryNoThrow(newTempPath, true); + } + } + private void TryLoadTaskBodyAndExpectFailure(string taskBody, string expectedErrorMessage) { if (expectedErrorMessage == null) diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index b8a0932d296..a82b42e5788 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -141,7 +141,13 @@ public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) { // The type of the task has already been determined and the assembly is already loaded after compilation so // just create an instance of the type and return it. - return Activator.CreateInstance(TaskType) as ITask; + ITask taskInstance = Activator.CreateInstance(TaskType) as ITask; + if (taskInstance is null) + { + _log.LogErrorWithCodeFromResources("CodeTaskFactory.NeedsITaskInterface", _taskName); + } + + return taskInstance; } /// From 11a5b957328fb94fc27dc2443c8997fefef866f0 Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Thu, 9 Apr 2026 16:46:58 +0200 Subject: [PATCH 2/5] Removes changes that will be part of another PR --- .../RoslynCodeTaskFactory_Tests.cs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 2aabfebd30b..308fac3c061 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -952,46 +952,6 @@ public void RoslynCodeTaskFactory_UsingAPI(bool forceOutOfProc) } #endif - [Fact] - public void BuildRoslynCodeTaskFactoryTempDirectoryDoesntExist() - { - string text = """ - - - - - - - - Log.LogMessage(MessageImportance.High, Text); - - - - - - - - """; - - var newTempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - - using var env = TestEnvironment.Create(); - - Directory.Exists(newTempPath).ShouldBeFalse(); - env.SetEnvironmentVariable("TMP", newTempPath); - env.SetEnvironmentVariable("TMPDIR", newTempPath); - - try - { - MockLogger logger = Helpers.BuildProjectWithNewOMExpectSuccess(text); - logger.AssertLogContains("Hello, World!"); - } - finally - { - FileUtilities.DeleteDirectoryNoThrow(newTempPath, true); - } - } - private void TryLoadTaskBodyAndExpectFailure(string taskBody, string expectedErrorMessage) { if (expectedErrorMessage == null) From 5cbce88a8a9f355cf0e145d4da04e048242834fa Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Thu, 9 Apr 2026 16:56:10 +0200 Subject: [PATCH 3/5] Leverages test output helper --- src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 308fac3c061..4885e53ec8d 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -18,6 +18,7 @@ using VerifyTests; using VerifyXunit; using Xunit; +using Xunit.Abstractions; using static VerifyXunit.Verifier; @@ -32,8 +33,11 @@ public class RoslynCodeTaskFactory_Tests private readonly VerifySettings _verifySettings; - public RoslynCodeTaskFactory_Tests() + private readonly ITestOutputHelper _testOutput; + + public RoslynCodeTaskFactory_Tests(ITestOutputHelper testOutput) { + _testOutput = testOutput; UseProjectRelativeDirectory("TaskFactorySource"); _verifySettings = new(); @@ -816,7 +820,7 @@ public bool Execute() """; - using TestEnvironment env = TestEnvironment.Create(); + using TestEnvironment env = TestEnvironment.Create(_testOutput); if (forceOutOfProc) { env.SetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC", "1"); From 77ffb91e79250292c8f2b29121751b5d2cfc0c92 Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Thu, 9 Apr 2026 17:02:10 +0200 Subject: [PATCH 4/5] Logs into the correct log stream --- src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index a82b42e5788..a68a34750f4 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -144,7 +144,8 @@ public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) ITask taskInstance = Activator.CreateInstance(TaskType) as ITask; if (taskInstance is null) { - _log.LogErrorWithCodeFromResources("CodeTaskFactory.NeedsITaskInterface", _taskName); + TaskLoggingHelper taskInvocationLog = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName); + taskInvocationLog.LogErrorWithCodeFromResources("CodeTaskFactory.NeedsITaskInterface", _taskName); } return taskInstance; From ee85c78d425be053163ad3c544e10383482de381 Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Fri, 10 Apr 2026 14:08:18 +0200 Subject: [PATCH 5/5] Adds missing logger configuration --- src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index a68a34750f4..a6196fa0032 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -144,7 +144,11 @@ public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) ITask taskInstance = Activator.CreateInstance(TaskType) as ITask; if (taskInstance is null) { - TaskLoggingHelper taskInvocationLog = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName); + TaskLoggingHelper taskInvocationLog = new TaskLoggingHelper(taskFactoryLoggingHost, _taskName) + { + TaskResources = AssemblyResources.PrimaryResources, + HelpKeywordPrefix = "MSBuild." + }; taskInvocationLog.LogErrorWithCodeFromResources("CodeTaskFactory.NeedsITaskInterface", _taskName); }