diff --git a/release_notes.md b/release_notes.md index 181d29c162..a5472bdfb7 100644 --- a/release_notes.md +++ b/release_notes.md @@ -1,8 +1,9 @@ -### Release notes - - -- Adding activity sources for Durable and WebJobs (Kafka and RabbitMQ) (#11137) -- Add JitTrace Files for v4.1041 -- Fix startup deadlock on transient exceptions (#11142) +### Release notes + + +- Adding activity sources for Durable and WebJobs (Kafka and RabbitMQ) (#11137) +- Add JitTrace Files for v4.1041 +- Fix startup deadlock on transient exceptions (#11142) +- Add warning log for end of support bundle version, any bundle version < 4 (#11075) diff --git a/src/WebJobs.Script.SiteExtension/New-PrivateSiteExtension.ps1 b/src/WebJobs.Script.SiteExtension/New-PrivateSiteExtension.ps1 index 7bb851eeac..ededc82d2a 100644 --- a/src/WebJobs.Script.SiteExtension/New-PrivateSiteExtension.ps1 +++ b/src/WebJobs.Script.SiteExtension/New-PrivateSiteExtension.ps1 @@ -42,7 +42,7 @@ param ( $normalizeBitness = @{ 'x64' = '64bit' '64bit' = '64bit' - 'x86' = '64bit' + 'x86' = '32bit' '32bit' = '32bit' } diff --git a/src/WebJobs.Script/Diagnostics/Extensions/LoggerExtension.cs b/src/WebJobs.Script/Diagnostics/Extensions/LoggerExtension.cs index 95afe4f588..a8ba41ca7f 100644 --- a/src/WebJobs.Script/Diagnostics/Extensions/LoggerExtension.cs +++ b/src/WebJobs.Script/Diagnostics/Extensions/LoggerExtension.cs @@ -206,6 +206,16 @@ internal static class LoggerExtension private static readonly Action _publishingMetrics = LoggerMessage.Define(LogLevel.Debug, new EventId(338, nameof(PublishingMetrics)), "{metrics}"); + private static readonly Action _outdatedExtensionBundleFuture = + LoggerMessage.Define(LogLevel.Warning, + new EventId(342, nameof(OutdatedExtensionBundle)), + "Your current bundle version {currentVersion} will reach end of support on Aug 4, 2026. Upgrade to [{suggestedMinVersion}.*, {suggestedMaxVersion}.0.0). For more information, see https://aka.ms/functions-outdated-bundles"); + + private static readonly Action _outdatedExtensionBundlePast = + LoggerMessage.Define(LogLevel.Warning, + new EventId(342, nameof(OutdatedExtensionBundle)), + "Your current bundle version {currentVersion} has reached end of support on Aug 4, 2026. Upgrade to [{suggestedMinVersion}.*, {suggestedMaxVersion}.0.0). For more information, see https://aka.ms/functions-outdated-bundles"); + public static void PublishingMetrics(this ILogger logger, string metrics) { _publishingMetrics(logger, metrics, null); @@ -407,5 +417,25 @@ public static void IncorrectAzureFunctionsFolderPath(this ILogger logger, string { _incorrectAzureFunctionsFolderPath(logger, path, EnvironmentSettingNames.FunctionWorkerRuntime, null); } + + public static void OutdatedExtensionBundle(this ILogger logger, string currentVersion, int suggestedMinVersion, int suggestedMaxVersion) + { + if (!logger.IsEnabled(LogLevel.Warning)) + { + return; + } + + var currentTime = DateTime.UtcNow; + var deprecationDate = new DateTime(2026, 8, 5, 0, 0, 0, DateTimeKind.Utc); + + if (currentTime >= deprecationDate) + { + _outdatedExtensionBundlePast(logger, currentVersion, suggestedMinVersion, suggestedMaxVersion, null); + } + else + { + _outdatedExtensionBundleFuture(logger, currentVersion, suggestedMinVersion, suggestedMaxVersion, null); + } + } } } \ No newline at end of file diff --git a/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs b/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs index 3c3c1dd597..9e3a7aec9d 100644 --- a/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs +++ b/src/WebJobs.Script/ExtensionBundle/ExtensionBundleManager.cs @@ -10,8 +10,10 @@ using System.Threading.Tasks; using Microsoft.Azure.WebJobs.Script.Config; using Microsoft.Azure.WebJobs.Script.Configuration; +using Microsoft.Azure.WebJobs.Script.Diagnostics; using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions; using Microsoft.Azure.WebJobs.Script.Models; +using Microsoft.Azure.WebJobs.Script.Properties; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using NuGet.Versioning; @@ -375,5 +377,33 @@ public async Task GetExtensionBundleBinPathAsync() // if no bin directory is present something is wrong return FileUtility.DirectoryExists(binPath) ? binPath : null; } + + public string GetOutdatedBundleVersion() + { + // If the extension bundle version is not set or if the extension bundle is not the default one, + // return empty string + if (string.IsNullOrEmpty(_extensionBundleVersion) || + !string.Equals(_options?.Id, ScriptConstants.DefaultExtensionBundleId, StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + // Extract the major version number from the version string + int dotIndex = _extensionBundleVersion.IndexOf('.'); + if (dotIndex <= 0 || !int.TryParse(_extensionBundleVersion.AsSpan(0, dotIndex), out var majorVersion) || majorVersion == 0) + { + return string.Empty; + } + + int latestMajorVersion = ScriptConstants.ExtensionBundleV4MajorVersion; + + // Return the version if it's outdated + if (majorVersion < latestMajorVersion) + { + return _extensionBundleVersion; + } + + return string.Empty; + } } } \ No newline at end of file diff --git a/src/WebJobs.Script/ExtensionBundle/IExtensionBundleManager.cs b/src/WebJobs.Script/ExtensionBundle/IExtensionBundleManager.cs index 9a63b25a07..a404a92557 100644 --- a/src/WebJobs.Script/ExtensionBundle/IExtensionBundleManager.cs +++ b/src/WebJobs.Script/ExtensionBundle/IExtensionBundleManager.cs @@ -20,5 +20,7 @@ public interface IExtensionBundleManager bool IsLegacyExtensionBundle(); Task GetExtensionBundleDetails(); + + string GetOutdatedBundleVersion(); } } \ No newline at end of file diff --git a/src/WebJobs.Script/Host/ExtensionBundleManagerValidator.cs b/src/WebJobs.Script/Host/ExtensionBundleManagerValidator.cs new file mode 100644 index 0000000000..9a888ce002 --- /dev/null +++ b/src/WebJobs.Script/Host/ExtensionBundleManagerValidator.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions; +using Microsoft.Azure.WebJobs.Script.ExtensionBundle; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Script.Host +{ + internal sealed class ExtensionBundleManagerValidator : IFunctionAppValidator + { + private readonly IExtensionBundleManager _extensionBundleManager; + + public ExtensionBundleManagerValidator(IExtensionBundleManager extensionBundleManager) + { + _extensionBundleManager = extensionBundleManager; + } + + public void Validate(ScriptJobHostOptions options, IEnvironment environment, ILogger logger) + { + if (!logger.IsEnabled(LogLevel.Warning)) + { + return; + } + + string outdatedBundleVersion = _extensionBundleManager.GetOutdatedBundleVersion(); + if (!string.IsNullOrEmpty(outdatedBundleVersion)) + { + int latestMajorVersion = ScriptConstants.ExtensionBundleV4MajorVersion; + logger.OutdatedExtensionBundle(outdatedBundleVersion, latestMajorVersion, latestMajorVersion + 1); + } + } + } +} diff --git a/src/WebJobs.Script/Host/FunctionAppValidationService.cs b/src/WebJobs.Script/Host/FunctionAppValidationService.cs index 582da52dac..cb2c234714 100644 --- a/src/WebJobs.Script/Host/FunctionAppValidationService.cs +++ b/src/WebJobs.Script/Host/FunctionAppValidationService.cs @@ -3,11 +3,8 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -22,15 +19,18 @@ internal sealed class FunctionAppValidationService : BackgroundService private readonly IEnvironment _environment; private readonly ILogger _logger; private readonly IOptions _scriptOptions; + private readonly IEnumerable _validators; public FunctionAppValidationService( ILogger logger, IOptions scriptOptions, - IEnvironment environment) + IEnvironment environment, + IEnumerable validators) { _scriptOptions = scriptOptions ?? throw new ArgumentNullException(nameof(scriptOptions)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _environment = environment ?? throw new ArgumentNullException(nameof(environment)); + _validators = validators ?? throw new ArgumentNullException(nameof(validators)); } protected override async Task ExecuteAsync(CancellationToken cancellationToken) @@ -40,39 +40,21 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) // Adding a delay to ensure that this validation does not impact the cold start performance Utility.ExecuteAfterColdStartDelay(_environment, Validate, cancellationToken); } - await Task.CompletedTask; } private void Validate() { - try + foreach (var validator in _validators) { - string azureFunctionsDirPath = Path.Combine(_scriptOptions.Value.RootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName); - - if (_scriptOptions.Value.RootScriptPath is not null && - !_scriptOptions.Value.IsDefaultHostConfig && - Utility.IsDotnetIsolatedApp(environment: _environment) && - !Directory.Exists(azureFunctionsDirPath)) + try { - // Search for the .azurefunctions directory within nested directories to verify scenarios where it isn't located at the root. This situation occurs when a function app has been improperly zipped. - IEnumerable azureFunctionsDirectories = Directory.GetDirectories(_scriptOptions.Value.RootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName, SearchOption.AllDirectories) - .Where(dir => !dir.Equals(azureFunctionsDirPath, StringComparison.OrdinalIgnoreCase)); - - if (azureFunctionsDirectories.Any()) - { - string azureFunctionsDirectoriesPath = string.Join(", ", azureFunctionsDirectories).Replace(_scriptOptions.Value.RootScriptPath, string.Empty); - _logger.IncorrectAzureFunctionsFolderPath(azureFunctionsDirectoriesPath); - } - else - { - _logger.MissingAzureFunctionsFolder(); - } + validator.Validate(_scriptOptions.Value, _environment, _logger); + } + catch (Exception ex) + { + _logger.LogTrace(ex, "Validator {ValidatorType} failed", validator.GetType().Name); } - } - catch (Exception ex) - { - _logger.LogTrace("Unable to validate deployed function app payload", ex); } } } diff --git a/src/WebJobs.Script/Host/IFunctionAppValidator.cs b/src/WebJobs.Script/Host/IFunctionAppValidator.cs new file mode 100644 index 0000000000..55cb2ef8a1 --- /dev/null +++ b/src/WebJobs.Script/Host/IFunctionAppValidator.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Script.Host +{ + /// + /// A validator interface for function app payload validation. + /// + internal interface IFunctionAppValidator + { + void Validate(ScriptJobHostOptions options, IEnvironment environment, ILogger logger); + } +} diff --git a/src/WebJobs.Script/Host/MissingAzureFunctionsFolderValidator.cs b/src/WebJobs.Script/Host/MissingAzureFunctionsFolderValidator.cs new file mode 100644 index 0000000000..22cde17319 --- /dev/null +++ b/src/WebJobs.Script/Host/MissingAzureFunctionsFolderValidator.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Azure.WebJobs.Script.Diagnostics.Extensions; +using Microsoft.Extensions.Logging; + +namespace Microsoft.Azure.WebJobs.Script.Host +{ + internal sealed class MissingAzureFunctionsFolderValidator : IFunctionAppValidator + { + public void Validate(ScriptJobHostOptions options, IEnvironment environment, ILogger logger) + { + string azureFunctionsDirPath = Path.Combine(options.RootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName); + + if (options.RootScriptPath is not null && + !options.IsDefaultHostConfig && + Utility.IsDotnetIsolatedApp(environment: environment) && + !Directory.Exists(azureFunctionsDirPath)) + { + IEnumerable azureFunctionsDirectories = Directory.GetDirectories(options.RootScriptPath, ScriptConstants.AzureFunctionsSystemDirectoryName, SearchOption.AllDirectories) + .Where(dir => !dir.Equals(azureFunctionsDirPath, StringComparison.OrdinalIgnoreCase)); + + if (azureFunctionsDirectories.Any()) + { + string azureFunctionsDirectoriesPath = string.Join(", ", azureFunctionsDirectories).Replace(options.RootScriptPath, string.Empty); + logger.IncorrectAzureFunctionsFolderPath(azureFunctionsDirectoriesPath); + } + else + { + logger.MissingAzureFunctionsFolder(); + } + } + } + } +} diff --git a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs index 87855ac655..52f05c2432 100644 --- a/src/WebJobs.Script/ScriptHostBuilderExtensions.cs +++ b/src/WebJobs.Script/ScriptHostBuilderExtensions.cs @@ -170,10 +170,12 @@ public static IHostBuilder AddScriptHostCore(this IHostBuilder builder, ScriptAp builder.ConfigureServices((context, services) => { - if (!SystemEnvironment.Instance.IsPlaceholderModeEnabled()) - { - services.AddHostedService(); - } + // Register all validators here + services.AddSingleton(); + services.AddSingleton(); + + // Add more validators as needed + services.AddHostedService(); services.AddSingleton(); services.AddSingleton(s => diff --git a/test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs b/test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs index a40e998d81..317c33d4c7 100644 --- a/test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs +++ b/test/WebJobs.Script.Tests.Integration/TestFunctionHost.cs @@ -531,6 +531,7 @@ private class TestExtensionBundleManager : IExtensionBundleManager public bool IsLegacyExtensionBundle() => true; + public string GetOutdatedBundleVersion() { return string.Empty; /* no-op for test */ } } private class PostConfigureServices diff --git a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/EndToEndTestFixture.cs b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/EndToEndTestFixture.cs index c927318d89..779af0a694 100644 --- a/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/EndToEndTestFixture.cs +++ b/test/WebJobs.Script.Tests.Integration/WebHostEndToEnd/EndToEndTestFixture.cs @@ -359,6 +359,12 @@ public async Task AddMasterKey(HttpRequestMessage request) private class TestExtensionBundleManager : IExtensionBundleManager { + public string GetOutdatedBundleVersion() + { + // no operation in this test fixture + return string.Empty; + } + public Task GetExtensionBundleBinPathAsync() => Task.FromResult(null); public Task GetExtensionBundleDetails() => Task.FromResult(null); diff --git a/test/WebJobs.Script.Tests/Description/FunctionAppValidationServiceTests.cs b/test/WebJobs.Script.Tests/Description/FunctionAppValidationServiceTests.cs index cd1848e234..5eb641c4a6 100644 --- a/test/WebJobs.Script.Tests/Description/FunctionAppValidationServiceTests.cs +++ b/test/WebJobs.Script.Tests/Description/FunctionAppValidationServiceTests.cs @@ -7,7 +7,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.Azure.WebJobs.Script.Config; +using Microsoft.Azure.WebJobs.Script.Configuration; using Microsoft.Azure.WebJobs.Script.Description; +using Microsoft.Azure.WebJobs.Script.ExtensionBundle; using Microsoft.Azure.WebJobs.Script.Host; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -23,6 +26,7 @@ public class FunctionAppValidationServiceTests private readonly Mock> _scriptOptionsMock; private readonly ScriptJobHostOptions _scriptJobHostOptions; private readonly TestLoggerProvider _testLoggerProvider; + private readonly ILoggerFactory _loggerFactory; public FunctionAppValidationServiceTests() { @@ -36,9 +40,9 @@ public FunctionAppValidationServiceTests() _scriptOptionsMock.Setup(o => o.Value).Returns(_scriptJobHostOptions); _testLoggerProvider = new TestLoggerProvider(); - var factory = new LoggerFactory(); - factory.AddProvider(_testLoggerProvider); - _testLogger = factory.CreateLogger(); + _loggerFactory = new LoggerFactory(); + _loggerFactory.AddProvider(_testLoggerProvider); + _testLogger = _loggerFactory.CreateLogger(); } [Fact] @@ -46,17 +50,20 @@ public async Task StartAsync_NotDotnetIsolatedApp_DoesNotLogError() { _testLoggerProvider.ClearAllLogMessages(); + var mockValidator = new Mock(); + // No-op validator var service = new FunctionAppValidationService( _testLogger, _scriptOptionsMock.Object, - new TestEnvironment()); + new TestEnvironment(), + [mockValidator.Object]); // Act await service.StartAsync(CancellationToken.None); //Assert var traces = _testLoggerProvider.GetAllLogMessages(); - var traceMessage = traces.FirstOrDefault(val => val.EventId.Name.Equals("MissingAzureFunctionsFolder")); + var traceMessage = traces.FirstOrDefault(val => string.Equals(val.EventId.Name, "MissingAzureFunctionsFolder")); Assert.Null(traceMessage); } @@ -69,17 +76,19 @@ public async Task StartAsync_PlaceholderMode_DoesNotLogError() var environment = new TestEnvironment(); environment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsitePlaceholderMode, "1"); + var mockValidator = new Mock(); var service = new FunctionAppValidationService( _testLogger, _scriptOptionsMock.Object, - environment); + environment, + [mockValidator.Object]); // Act await service.StartAsync(CancellationToken.None); //Assert var traces = _testLoggerProvider.GetAllLogMessages(); - var traceMessage = traces.FirstOrDefault(val => val.EventId.Name.Equals("MissingAzureFunctionsFolder")); + var traceMessage = traces.FirstOrDefault(val => string.Equals(val.EventId.Name, "MissingAzureFunctionsFolder")); Assert.Null(traceMessage); } @@ -102,17 +111,19 @@ public async Task StartAsync_NewAppWithNoPayload_DoesNotLogError() scriptOptionsMock.Setup(o => o.Value).Returns(scriptJobHostOptions); + var mockValidator = new Mock(); var service = new FunctionAppValidationService( _testLogger, scriptOptionsMock.Object, - environment); + environment, + [mockValidator.Object]); // Act await service.StartAsync(CancellationToken.None); //Assert var traces = _testLoggerProvider.GetAllLogMessages(); - var traceMessage = traces.FirstOrDefault(val => val.EventId.Name.Equals("MissingAzureFunctionsFolder")); + var traceMessage = traces.FirstOrDefault(val => string.Equals(val.EventId.Name, "MissingAzureFunctionsFolder")); Assert.Null(traceMessage); } @@ -133,10 +144,13 @@ public async Task StartAsync_MissingAzureFunctionsFolder_LogsWarning() var environment = new TestEnvironment(); environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, "dotnet-isolated"); + // Use the real validator for folder check + var folderValidator = new MissingAzureFunctionsFolderValidator(); var service = new FunctionAppValidationService( _testLogger, _scriptOptionsMock.Object, - environment); + environment, + [folderValidator]); // Act await service.StartAsync(CancellationToken.None); @@ -147,5 +161,53 @@ await TestHelpers.Await(() => return completed > 0; }); } + + [Theory] + [InlineData("Microsoft.Azure.Functions.ExtensionBundle", "3.36.0", true)] + [InlineData("Microsoft.Azure.Functions.ExtensionBundle", "2.25.0", true)] + [InlineData("Microsoft.Azure.Functions.ExtensionBundle", "4.22.0", false)] + [InlineData("Microsoft.Azure.Functions.ExtensionBundle.Preview", "4.29.0", false)] + [InlineData("Microsoft.Azure.Functions.ExtensionBundle.Preview", "3.2.0", false)] + [InlineData(null, null, false)] + [InlineData("", "", false)] + public void GetOutdatedBundleWarningMessage_LogsWarning(string bundleId, string bundleVersion, bool shouldLogEvent) + { + // Arrange + _testLoggerProvider.ClearAllLogMessages(); + + var options = new ExtensionBundleOptions { Id = bundleId }; + var env = new TestEnvironment(); + var config = new FunctionsHostingConfigOptions(); + var manager = new ExtensionBundleManager(options, env, _loggerFactory, config); + + // Set the private _extensionBundleVersion field using reflection + typeof(ExtensionBundleManager) + .GetField("_extensionBundleVersion", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .SetValue(manager, bundleVersion); + + var validator = new ExtensionBundleManagerValidator(manager); + var service = new FunctionAppValidationService( + _testLogger, + _scriptOptionsMock.Object, + env, + [validator]); + + // Act + validator.Validate(_scriptJobHostOptions, env, _testLogger); + + // Assert + var logMessages = _testLoggerProvider.GetAllLogMessages(); + + // Check for both possible resource strings (future and past deprecation) + bool hasFutureWarning = logMessages.Any(m => m.FormattedMessage.Contains(bundleVersion) + && m.FormattedMessage.Contains("will reach end of support on Aug 4, 2026.") + && m.Level == LogLevel.Warning); + bool hasPastWarning = logMessages.Any(m => m.FormattedMessage.Contains(bundleVersion) + && m.FormattedMessage.Contains("has reached end of support on Aug 4, 2026.") + && m.Level == LogLevel.Warning); + bool hasOutdatedBundleLog = hasFutureWarning || hasPastWarning; + + Assert.Equal(shouldLogEvent, hasOutdatedBundleLog); + } } } \ No newline at end of file diff --git a/test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleContentProviderTests.cs b/test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleContentProviderTests.cs index 2d11c01efd..d896d0ad37 100644 --- a/test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleContentProviderTests.cs +++ b/test/WebJobs.Script.Tests/ExtensionBundle/ExtensionBundleContentProviderTests.cs @@ -157,6 +157,12 @@ public Task GetExtensionBundleBinPathAsync() public bool IsExtensionBundleConfigured() => _isExtensionBundleConfigured; public bool IsLegacyExtensionBundle() => _isLegacyExtensionBundle; + + public string GetOutdatedBundleVersion() + { + // No-op for test stub. This can be extended for test verifications if needed. + return string.Empty; + } } } } diff --git a/test/WebJobs.Script.Tests/ExtensionManagerTests.cs b/test/WebJobs.Script.Tests/ExtensionManagerTests.cs index 891ca6ffdc..634af36e18 100644 --- a/test/WebJobs.Script.Tests/ExtensionManagerTests.cs +++ b/test/WebJobs.Script.Tests/ExtensionManagerTests.cs @@ -207,6 +207,12 @@ public Task GetExtensionBundleBinPathAsync() public bool IsExtensionBundleConfigured() => _isExtensionBundleConfigured; public bool IsLegacyExtensionBundle() => _isLegacyExtensionBundle; + + public string GetOutdatedBundleVersion() + { + // No-op for test stub. This can be extended for test verifications if needed. + return string.Empty; + } } } } diff --git a/test/WebJobs.Script.Tests/ScriptStartupTypeDiscovererTests.cs b/test/WebJobs.Script.Tests/ScriptStartupTypeDiscovererTests.cs index 0372156aa6..a924e0b995 100644 --- a/test/WebJobs.Script.Tests/ScriptStartupTypeDiscovererTests.cs +++ b/test/WebJobs.Script.Tests/ScriptStartupTypeDiscovererTests.cs @@ -780,7 +780,7 @@ public async Task GetExtensionsStartupTypes_WorkerRuntimeNotSetForNodeApp_LoadsE //Assert var traces = testLoggerProvider.GetAllLogMessages(); - var traceMessage = traces.FirstOrDefault(val => val.EventId.Name.Equals("ScriptStartNotLoadingExtensionBundle")); + var traceMessage = traces.FirstOrDefault(val => string.Equals(val.EventId.Name, "ScriptStartNotLoadingExtensionBundle")); bool loadingExtensionBundle = traceMessage == null; Assert.True(loadingExtensionBundle);