From 5521768c8f5d6532938f0151d2884eda710b1681 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 10:55:12 -0800 Subject: [PATCH 1/7] Use dotnet Muxer from .NET SDK for dotnet discovery --- src/Cli/func/Helpers/DotnetMuxer.cs | 83 ++++++++++++++++++++++++ src/Cli/func/Helpers/FileNameSuffixes.cs | 78 ++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/Cli/func/Helpers/DotnetMuxer.cs create mode 100644 src/Cli/func/Helpers/FileNameSuffixes.cs diff --git a/src/Cli/func/Helpers/DotnetMuxer.cs b/src/Cli/func/Helpers/DotnetMuxer.cs new file mode 100644 index 000000000..7b292cb26 --- /dev/null +++ b/src/Cli/func/Helpers/DotnetMuxer.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// Copied from: https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs +using System.Runtime.InteropServices; + +namespace Azure.Functions.Cli.Helpers; + +public class DotnetMuxer +{ + public static readonly string MuxerName = "dotnet"; + + private readonly string _muxerPath; + + public static readonly string ExeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + + public DotnetMuxer() + { + // Most scenarios are running dotnet.dll as the app + // Root directory with muxer should be two above app base: /sdk/ + string rootPath = Path.GetDirectoryName(Path.GetDirectoryName(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar))); + if (rootPath is not null) + { + string muxerPathMaybe = Path.Combine(rootPath, $"{MuxerName}{FileNameSuffixes.CurrentPlatform.Exe}"); + if (File.Exists(muxerPathMaybe)) + { + _muxerPath = muxerPathMaybe; + } + } + + if (_muxerPath is null) + { + // Best-effort search for muxer. + // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable + string processPath = Environment.ProcessPath; + + // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost + if (processPath is not null && !Path.GetFileNameWithoutExtension(processPath).Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + { + // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable + processPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + if (processPath is null) + { + // fallback to DOTNET_ROOT which typically holds some dotnet executable + var root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (root is not null) + { + processPath = Path.Combine(root, $"dotnet{ExeSuffix}"); + } + } + } + + _muxerPath = processPath; + } + } + + internal string SharedFxVersion + { + get + { + var depsFile = new FileInfo(GetDataFromAppDomain("FX_DEPS_FILE") ?? string.Empty); + return depsFile.Directory?.Name ?? string.Empty; + } + } + + public string MuxerPath + { + get + { + if (_muxerPath == null) + { + throw new InvalidOperationException("Unable to locate dotnet multiplexer"); + } + + return _muxerPath; + } + } + + public static string GetDataFromAppDomain(string propertyName) + { + return AppContext.GetData(propertyName) as string; + } +} diff --git a/src/Cli/func/Helpers/FileNameSuffixes.cs b/src/Cli/func/Helpers/FileNameSuffixes.cs new file mode 100644 index 000000000..26f5dd2d6 --- /dev/null +++ b/src/Cli/func/Helpers/FileNameSuffixes.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +// Copied from: https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/FileNameSuffixes.cs +using System.Runtime.InteropServices; + +namespace Azure.Functions.Cli.Helpers; + +public static class FileNameSuffixes +{ + public const string DepsJson = ".deps.json"; + public const string RuntimeConfigJson = ".runtimeconfig.json"; + public const string RuntimeConfigDevJson = ".runtimeconfig.dev.json"; + + public static PlatformFileNameSuffixes CurrentPlatform + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Windows; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OSX; + } + else + { + // assume everything else is Unix to avoid modifying this file + // every time a new platform is introduced in runtime. + return Unix; + } + } + } + + public static PlatformFileNameSuffixes DotNet { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dll", + Exe = ".exe", + ProgramDatabase = ".pdb", + StaticLib = ".lib" + }; + + public static PlatformFileNameSuffixes Windows { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dll", + Exe = ".exe", + ProgramDatabase = ".pdb", + StaticLib = ".lib" + }; + + public static PlatformFileNameSuffixes OSX { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".dylib", + Exe = string.Empty, + ProgramDatabase = ".pdb", + StaticLib = ".a" + }; + + public static PlatformFileNameSuffixes Unix { get; } = new PlatformFileNameSuffixes + { + DynamicLib = ".so", + Exe = string.Empty, + ProgramDatabase = ".pdb", + StaticLib = ".a" + }; + + public struct PlatformFileNameSuffixes + { + public string DynamicLib { get; internal set; } + + public string Exe { get; internal set; } + + public string ProgramDatabase { get; internal set; } + + public string StaticLib { get; internal set; } + } +} From 02d0c037e44c20bcda5a70963b760a5be12f1aa8 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 10:55:39 -0800 Subject: [PATCH 2/7] Replace CommandChecker.CommandExists with DotnetMuxer --- src/Cli/func/Helpers/DotnetHelpers.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Cli/func/Helpers/DotnetHelpers.cs b/src/Cli/func/Helpers/DotnetHelpers.cs index 70b789a87..33ff7bd22 100644 --- a/src/Cli/func/Helpers/DotnetHelpers.cs +++ b/src/Cli/func/Helpers/DotnetHelpers.cs @@ -28,9 +28,15 @@ private static Task RunDotnetNewAsync(string args) public static void EnsureDotnet() { - if (!CommandChecker.CommandExists("dotnet")) + try + { + _ = new DotnetMuxer().MuxerPath; + } + catch (InvalidOperationException ex) { - throw new CliException("dotnet sdk is required for dotnet based functions. Please install https://microsoft.com/net"); + throw new CliException( + "Unable to locate the .NET SDK. Install the .NET SDK from https://aka.ms/dotnet-download or configure PATH | DOTNET_ROOT | DOTNET_HOST_PATH so that 'dotnet' is discoverable.", + ex); } } From cb096678d5c6d77c598abf3d01c6d666622d5778 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:06:25 -0800 Subject: [PATCH 3/7] Add tests --- .../HelperTests/DotnetHelpersTests.cs | 8 ++ .../HelperTests/DotnetMuxerTests.cs | 94 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs diff --git a/test/Cli/Func.UnitTests/HelperTests/DotnetHelpersTests.cs b/test/Cli/Func.UnitTests/HelperTests/DotnetHelpersTests.cs index 625e26c79..ae869a46b 100644 --- a/test/Cli/Func.UnitTests/HelperTests/DotnetHelpersTests.cs +++ b/test/Cli/Func.UnitTests/HelperTests/DotnetHelpersTests.cs @@ -8,6 +8,14 @@ namespace Azure.Functions.Cli.UnitTests.HelperTests { public class DotnetHelpersTests { + [Fact] + public void EnsureDotnet_DoesNotThrow_WhenDotnetExists() + { + // dotnet is always installed in the test environment + var exception = Record.Exception(() => DotnetHelpers.EnsureDotnet()); + Assert.Null(exception); + } + [Theory] [InlineData("BlobTrigger", "blob")] [InlineData("HttpTrigger", "http")] diff --git a/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs b/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs new file mode 100644 index 000000000..14f9af4fb --- /dev/null +++ b/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Azure.Functions.Cli.Helpers; +using Xunit; + +namespace Azure.Functions.Cli.UnitTests.HelperTests +{ + public class DotnetMuxerTests + { + [Fact] + public void DotnetMuxer_FindsMuxerPath_WhenDotnetExists() + { + // Arrange & Act + var muxer = new DotnetMuxer(); + + // Assert + Assert.NotNull(muxer.MuxerPath); + Assert.False(string.IsNullOrWhiteSpace(muxer.MuxerPath)); + } + + [Fact] + public void MuxerPath_ThrowsInvalidOperationException_WhenNotFound() + { + // This test is tricky because DotnetMuxer's constructor tries to find dotnet + // In a normal test environment, dotnet will be found + // We can only test the property behavior + var muxer = new DotnetMuxer(); + + // If we got here, dotnet was found (which is expected in test environment) + Assert.NotNull(muxer.MuxerPath); + } + + [Fact] + public void MuxerName_IsCorrect() + { + // Assert + Assert.Equal("dotnet", DotnetMuxer.MuxerName); + } + + [Fact] + public void ExeSuffix_IsCorrectForPlatform() + { + // Act & Assert + if (OperatingSystem.IsWindows()) + { + Assert.Equal(".exe", DotnetMuxer.ExeSuffix); + } + else + { + Assert.Equal(string.Empty, DotnetMuxer.ExeSuffix); + } + } + + [Fact] + public void GetDataFromAppDomain_ReturnsNull_ForNonExistentKey() + { + // Act + var result = DotnetMuxer.GetDataFromAppDomain("NON_EXISTENT_KEY_12345"); + + // Assert + Assert.Null(result); + } + + [Fact] + public void MuxerPath_ContainsDotnetExecutable() + { + // Arrange + var muxer = new DotnetMuxer(); + + // Act + var path = muxer.MuxerPath; + + // Assert + Assert.Contains("dotnet", path, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void MuxerPath_PointsToExecutableFile() + { + // Arrange + var muxer = new DotnetMuxer(); + + // Act + var path = muxer.MuxerPath; + + // Assert + // The path should exist as a file + Assert.True( + File.Exists(path) || File.Exists(path + ".exe"), + $"Expected muxer path '{path}' to exist"); + } + } +} From 077cd9a0fb3836f6b010875bf9df3ea9b1f3d261 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 11:13:27 -0800 Subject: [PATCH 4/7] Update release notes --- release_notes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/release_notes.md b/release_notes.md index 3abff2747..a4dbab232 100644 --- a/release_notes.md +++ b/release_notes.md @@ -7,3 +7,8 @@ #### Changes +- Add updated Durable .NET templates (#4692) +- Adding the MCP Tool Trigger Templates for the Node/Typescript (#4651) +- Set `AzureWebJobsStorage` to use the storage emulator by default on all platforms (#4685) +- Set `FUNCTIONS_WORKER_RUNTIME` to custom if the `EnableMcpCustomHandlerPreview` feature flag is set (#4703) +- Enhanced dotnet installation discovery by adopting the same `Muxer` logic used by the .NET SDK itself (#4732) \ No newline at end of file From ad27fb5d2f52265cc7201087a0fb9916f68d905b Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 13:29:08 -0800 Subject: [PATCH 5/7] Remove dupe comment --- src/Cli/func/Helpers/DotnetMuxer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cli/func/Helpers/DotnetMuxer.cs b/src/Cli/func/Helpers/DotnetMuxer.cs index 7b292cb26..2efb319b6 100644 --- a/src/Cli/func/Helpers/DotnetMuxer.cs +++ b/src/Cli/func/Helpers/DotnetMuxer.cs @@ -31,7 +31,6 @@ public DotnetMuxer() if (_muxerPath is null) { // Best-effort search for muxer. - // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable string processPath = Environment.ProcessPath; // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost From 48de81bf212fe3fbe27d072c33774b6b1ef553ac Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 13:51:32 -0800 Subject: [PATCH 6/7] Refactor to static and simplify --- src/Cli/func/Helpers/DotnetHelpers.cs | 2 +- src/Cli/func/Helpers/DotnetMuxer.cs | 83 +++++++---------- src/Cli/func/Helpers/FileNameSuffixes.cs | 78 ---------------- .../HelperTests/DotnetMuxerTests.cs | 90 ++++++++----------- 4 files changed, 68 insertions(+), 185 deletions(-) delete mode 100644 src/Cli/func/Helpers/FileNameSuffixes.cs diff --git a/src/Cli/func/Helpers/DotnetHelpers.cs b/src/Cli/func/Helpers/DotnetHelpers.cs index 33ff7bd22..49d14fab6 100644 --- a/src/Cli/func/Helpers/DotnetHelpers.cs +++ b/src/Cli/func/Helpers/DotnetHelpers.cs @@ -30,7 +30,7 @@ public static void EnsureDotnet() { try { - _ = new DotnetMuxer().MuxerPath; + _ = DotnetMuxer.GetMuxerPath(); } catch (InvalidOperationException ex) { diff --git a/src/Cli/func/Helpers/DotnetMuxer.cs b/src/Cli/func/Helpers/DotnetMuxer.cs index 2efb319b6..59dd80c16 100644 --- a/src/Cli/func/Helpers/DotnetMuxer.cs +++ b/src/Cli/func/Helpers/DotnetMuxer.cs @@ -1,82 +1,61 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -// Copied from: https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs +// Adapted from: https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/Muxer.cs using System.Runtime.InteropServices; namespace Azure.Functions.Cli.Helpers; -public class DotnetMuxer +internal static class DotnetMuxer { - public static readonly string MuxerName = "dotnet"; - - private readonly string _muxerPath; - - public static readonly string ExeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; - - public DotnetMuxer() + private static readonly string _muxerName = "dotnet"; + private static readonly string _exeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty; + + /// + /// Locates the dotnet muxer (dotnet executable). + /// + /// The full path to the dotnet executable. + /// Thrown when the dotnet executable cannot be located. + public static string GetMuxerPath() { + string muxerPath; + // Most scenarios are running dotnet.dll as the app // Root directory with muxer should be two above app base: /sdk/ string rootPath = Path.GetDirectoryName(Path.GetDirectoryName(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar))); if (rootPath is not null) { - string muxerPathMaybe = Path.Combine(rootPath, $"{MuxerName}{FileNameSuffixes.CurrentPlatform.Exe}"); - if (File.Exists(muxerPathMaybe)) + muxerPath = Path.Combine(rootPath, $"{_muxerName}{_exeSuffix}"); + if (File.Exists(muxerPath)) { - _muxerPath = muxerPathMaybe; + return muxerPath; } } - if (_muxerPath is null) - { - // Best-effort search for muxer. - string processPath = Environment.ProcessPath; + // Best-effort search for muxer. + muxerPath = Environment.ProcessPath; - // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost - if (processPath is not null && !Path.GetFileNameWithoutExtension(processPath).Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + // The current process should be dotnet in most normal scenarios except when dotnet.dll is loaded in a custom host like the testhost + if (muxerPath is not null && !Path.GetFileNameWithoutExtension(muxerPath).Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + { + // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable + muxerPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + if (muxerPath is null) { - // SDK sets DOTNET_HOST_PATH as absolute path to current dotnet executable - processPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); - if (processPath is null) + // fallback to DOTNET_ROOT which typically holds some dotnet executable + string root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); + if (root is not null) { - // fallback to DOTNET_ROOT which typically holds some dotnet executable - var root = Environment.GetEnvironmentVariable("DOTNET_ROOT"); - if (root is not null) - { - processPath = Path.Combine(root, $"dotnet{ExeSuffix}"); - } + muxerPath = Path.Combine(root, $"dotnet{_exeSuffix}"); } } - - _muxerPath = processPath; } - } - internal string SharedFxVersion - { - get + if (muxerPath is null) { - var depsFile = new FileInfo(GetDataFromAppDomain("FX_DEPS_FILE") ?? string.Empty); - return depsFile.Directory?.Name ?? string.Empty; + throw new InvalidOperationException("Unable to locate dotnet multiplexer"); } - } - public string MuxerPath - { - get - { - if (_muxerPath == null) - { - throw new InvalidOperationException("Unable to locate dotnet multiplexer"); - } - - return _muxerPath; - } - } - - public static string GetDataFromAppDomain(string propertyName) - { - return AppContext.GetData(propertyName) as string; + return muxerPath; } } diff --git a/src/Cli/func/Helpers/FileNameSuffixes.cs b/src/Cli/func/Helpers/FileNameSuffixes.cs deleted file mode 100644 index 26f5dd2d6..000000000 --- a/src/Cli/func/Helpers/FileNameSuffixes.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -// Copied from: https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/FileNameSuffixes.cs -using System.Runtime.InteropServices; - -namespace Azure.Functions.Cli.Helpers; - -public static class FileNameSuffixes -{ - public const string DepsJson = ".deps.json"; - public const string RuntimeConfigJson = ".runtimeconfig.json"; - public const string RuntimeConfigDevJson = ".runtimeconfig.dev.json"; - - public static PlatformFileNameSuffixes CurrentPlatform - { - get - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return Windows; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return OSX; - } - else - { - // assume everything else is Unix to avoid modifying this file - // every time a new platform is introduced in runtime. - return Unix; - } - } - } - - public static PlatformFileNameSuffixes DotNet { get; } = new PlatformFileNameSuffixes - { - DynamicLib = ".dll", - Exe = ".exe", - ProgramDatabase = ".pdb", - StaticLib = ".lib" - }; - - public static PlatformFileNameSuffixes Windows { get; } = new PlatformFileNameSuffixes - { - DynamicLib = ".dll", - Exe = ".exe", - ProgramDatabase = ".pdb", - StaticLib = ".lib" - }; - - public static PlatformFileNameSuffixes OSX { get; } = new PlatformFileNameSuffixes - { - DynamicLib = ".dylib", - Exe = string.Empty, - ProgramDatabase = ".pdb", - StaticLib = ".a" - }; - - public static PlatformFileNameSuffixes Unix { get; } = new PlatformFileNameSuffixes - { - DynamicLib = ".so", - Exe = string.Empty, - ProgramDatabase = ".pdb", - StaticLib = ".a" - }; - - public struct PlatformFileNameSuffixes - { - public string DynamicLib { get; internal set; } - - public string Exe { get; internal set; } - - public string ProgramDatabase { get; internal set; } - - public string StaticLib { get; internal set; } - } -} diff --git a/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs b/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs index 14f9af4fb..4bbc9c048 100644 --- a/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs +++ b/test/Cli/Func.UnitTests/HelperTests/DotnetMuxerTests.cs @@ -9,86 +9,68 @@ namespace Azure.Functions.Cli.UnitTests.HelperTests public class DotnetMuxerTests { [Fact] - public void DotnetMuxer_FindsMuxerPath_WhenDotnetExists() + public void GetMuxerPath_ReturnsMuxerPath_WhenDotnetExists() { - // Arrange & Act - var muxer = new DotnetMuxer(); + // Act + var path = DotnetMuxer.GetMuxerPath(); // Assert - Assert.NotNull(muxer.MuxerPath); - Assert.False(string.IsNullOrWhiteSpace(muxer.MuxerPath)); + Assert.NotNull(path); + Assert.False(string.IsNullOrWhiteSpace(path)); } [Fact] - public void MuxerPath_ThrowsInvalidOperationException_WhenNotFound() + public void GetMuxerPath_ContainsDotnetExecutable() { - // This test is tricky because DotnetMuxer's constructor tries to find dotnet - // In a normal test environment, dotnet will be found - // We can only test the property behavior - var muxer = new DotnetMuxer(); - - // If we got here, dotnet was found (which is expected in test environment) - Assert.NotNull(muxer.MuxerPath); - } + // Act + var path = DotnetMuxer.GetMuxerPath(); - [Fact] - public void MuxerName_IsCorrect() - { // Assert - Assert.Equal("dotnet", DotnetMuxer.MuxerName); - } - - [Fact] - public void ExeSuffix_IsCorrectForPlatform() - { - // Act & Assert - if (OperatingSystem.IsWindows()) - { - Assert.Equal(".exe", DotnetMuxer.ExeSuffix); - } - else - { - Assert.Equal(string.Empty, DotnetMuxer.ExeSuffix); - } + Assert.Contains("dotnet", path, StringComparison.OrdinalIgnoreCase); } [Fact] - public void GetDataFromAppDomain_ReturnsNull_ForNonExistentKey() + public void GetMuxerPath_PointsToExecutableFile() { // Act - var result = DotnetMuxer.GetDataFromAppDomain("NON_EXISTENT_KEY_12345"); + var path = DotnetMuxer.GetMuxerPath(); // Assert - Assert.Null(result); + // The path should exist as a file + Assert.True( + File.Exists(path) || File.Exists(path + ".exe"), + $"Expected muxer path '{path}' to exist"); } [Fact] - public void MuxerPath_ContainsDotnetExecutable() + public void GetMuxerPath_PointsToFunctionalDotnetExecutable() { - // Arrange - var muxer = new DotnetMuxer(); - // Act - var path = muxer.MuxerPath; + var path = DotnetMuxer.GetMuxerPath(); - // Assert - Assert.Contains("dotnet", path, StringComparison.OrdinalIgnoreCase); - } + // Assert - invoke dotnet --version to verify it's functional + var startInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = path, + Arguments = "--version", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; - [Fact] - public void MuxerPath_PointsToExecutableFile() - { - // Arrange - var muxer = new DotnetMuxer(); + using var process = System.Diagnostics.Process.Start(startInfo); + Assert.NotNull(process); - // Act - var path = muxer.MuxerPath; + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); // Assert - // The path should exist as a file - Assert.True( - File.Exists(path) || File.Exists(path + ".exe"), - $"Expected muxer path '{path}' to exist"); + Assert.Equal(0, process.ExitCode); + Assert.False(string.IsNullOrWhiteSpace(output), "Expected dotnet --version to produce output"); + + // Verify output looks like a version number (e.g., "8.0.100") + Assert.Matches(@"^\d+\.\d+\.\d+", output.Trim()); } } } From 405f4aea94d467c9c8ff569af3d392b490da8373 Mon Sep 17 00:00:00 2001 From: Lilian Kasem Date: Mon, 17 Nov 2025 14:13:39 -0800 Subject: [PATCH 7/7] Update release notes --- release_notes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/release_notes.md b/release_notes.md index a4dbab232..3e75f68a0 100644 --- a/release_notes.md +++ b/release_notes.md @@ -7,8 +7,4 @@ #### Changes -- Add updated Durable .NET templates (#4692) -- Adding the MCP Tool Trigger Templates for the Node/Typescript (#4651) -- Set `AzureWebJobsStorage` to use the storage emulator by default on all platforms (#4685) -- Set `FUNCTIONS_WORKER_RUNTIME` to custom if the `EnableMcpCustomHandlerPreview` feature flag is set (#4703) - Enhanced dotnet installation discovery by adopting the same `Muxer` logic used by the .NET SDK itself (#4732) \ No newline at end of file