From 9917849b11cc03da1c38e93160e4092d9b8a6150 Mon Sep 17 00:00:00 2001 From: Shyju Krishnankutty Date: Wed, 23 Aug 2023 11:51:15 -0700 Subject: [PATCH] BugFix-FunctionMetadataProvider emits incorrect binding name property value for async functions (#1851) * Fixing 1817 - FunctionMetadataProvider emits incorrect binding name property value for async functions * Add release notes --- ...unctionMetadataProviderGenerator.Parser.cs | 4 +- sdk/release_notes.md | 3 +- .../IntegratedTriggersAndBindingsTests.cs | 123 +++++++++++++++++- 3 files changed, 126 insertions(+), 4 deletions(-) diff --git a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs index 2e6cf4ecc..b6e47266f 100644 --- a/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs +++ b/sdk/Sdk.Generators/FunctionMetadataProviderGenerator/FunctionMetadataProviderGenerator.Parser.cs @@ -432,10 +432,10 @@ private bool TryGetReturnTypeBindings(MethodDeclarationSyntax method, SemanticMo } if (!SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.VoidType) && - !SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.TaskType)) + !SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskType)) { // If there is a Task return type, inspect T, the inner type. - if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol, _knownTypes.TaskOfTType)) + if (SymbolEqualityComparer.Default.Equals(returnTypeSymbol.OriginalDefinition, _knownTypes.TaskOfTType)) { GenericNameSyntax genericSyntax = (GenericNameSyntax)returnTypeSyntax; var innerTypeSyntax = genericSyntax.TypeArgumentList.Arguments.First(); // Generic task should only have one type argument diff --git a/sdk/release_notes.md b/sdk/release_notes.md index a87243e5e..6098a0f71 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -14,4 +14,5 @@ ### Microsoft.Azure.Functions.Worker.Sdk.Generators -- Updated source generated versions of FunctionExecutor to use `global::` namespace prefix to avoid ambiguity between namespaces.(#1847) \ No newline at end of file +- Updated source generated versions of FunctionExecutor to use `global::` namespace prefix to avoid ambiguity between namespaces.(#1847) +- Fixing the check to determine the function return type for async functions.(#1817) \ No newline at end of file diff --git a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs index 513302dd6..0171928c8 100644 --- a/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs +++ b/test/Sdk.Generator.Tests/FunctionMetadataProviderGeneratorTests/IntegratedTriggersAndBindingsTests.cs @@ -486,7 +486,7 @@ public Task> GetFunctionMetadataAsync(string d var metadataList = new List(); var Function0RawBindings = new List(); Function0RawBindings.Add(@"{""name"":""myReq"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Admin"",""methods"":[""get"",""Post""],""route"":""/api2""}"); - Function0RawBindings.Add(@"{""name"":""Result"",""type"":""http"",""direction"":""Out""}"); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); var Function0 = new DefaultFunctionMetadata { @@ -526,6 +526,127 @@ await TestHelpers.RunTestAsync( expectedGeneratedFileName, expectedOutput); } + + [Fact] + public async Task GenerateMultipleFunctionsMetadataTest() + { + string inputCode = """ + using System; + using System.Threading.Tasks; + using System.Collections.Generic; + using Microsoft.Azure.Functions.Worker; + using Microsoft.Azure.Functions.Worker.Http; + + namespace FunctionApp + { + public class HttpTriggerSimple + { + [Function(nameof(Run))] + public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req, FunctionContext executionContext) + { + throw new NotImplementedException(); + } + [Function(nameof(RunAsync))] + public Task RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) + { + throw new NotImplementedException(); + } + [Function(nameof(RunAsync2))] + public async Task RunAsync2([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req) + { + throw new NotImplementedException(); + } + } + } + """; + + string expectedGeneratedFileName = $"GeneratedFunctionMetadataProvider.g.cs"; + string expectedOutput = """ + // + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Text.Json; + using System.Threading.Tasks; + using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + + namespace Microsoft.Azure.Functions.Worker + { + public class GeneratedFunctionMetadataProvider : IFunctionMetadataProvider + { + public Task> GetFunctionMetadataAsync(string directory) + { + var metadataList = new List(); + var Function0RawBindings = new List(); + Function0RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function0RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function0 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "Run", + EntryPoint = "FunctionApp.HttpTriggerSimple.Run", + RawBindings = Function0RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function0); + var Function1RawBindings = new List(); + Function1RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function1RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function1 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "RunAsync", + EntryPoint = "FunctionApp.HttpTriggerSimple.RunAsync", + RawBindings = Function1RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function1); + var Function2RawBindings = new List(); + Function2RawBindings.Add(@"{""name"":""req"",""type"":""HttpTrigger"",""direction"":""In"",""authLevel"":""Anonymous"",""methods"":[""get"",""post""]}"); + Function2RawBindings.Add(@"{""name"":""$return"",""type"":""http"",""direction"":""Out""}"); + + var Function2 = new DefaultFunctionMetadata + { + Language = "dotnet-isolated", + Name = "RunAsync2", + EntryPoint = "FunctionApp.HttpTriggerSimple.RunAsync2", + RawBindings = Function2RawBindings, + ScriptFile = "TestProject.dll" + }; + metadataList.Add(Function2); + + return Task.FromResult(metadataList.ToImmutableArray()); + } + } + + public static class WorkerHostBuilderFunctionMetadataProviderExtension + { + /// + /// Adds the GeneratedFunctionMetadataProvider to the service collection. + /// During initialization, the worker will return generated function metadata instead of relying on the Azure Functions host for function indexing. + /// + public static IHostBuilder ConfigureGeneratedFunctionMetadataProvider(this IHostBuilder builder) + { + builder.ConfigureServices(s => + { + s.AddSingleton(); + }); + return builder; + } + } + } + """; + + await TestHelpers.RunTestAsync( + _referencedExtensionAssemblies, + inputCode, + expectedGeneratedFileName, + expectedOutput); + } } } }