diff --git a/FunctionsSdkE2ETests/.gitignore b/FunctionsSdkE2ETests/.gitignore new file mode 100644 index 0000000..439bbb5 --- /dev/null +++ b/FunctionsSdkE2ETests/.gitignore @@ -0,0 +1,13 @@ +bin +obj +csx +.vs +.DS_Store +.vscode + +*.user +*.suo +*.cscfg +*.Cache + +pub_razor \ No newline at end of file diff --git a/FunctionsSdkE2ETests/DirectRef.sln b/FunctionsSdkE2ETests/DirectRef.sln new file mode 100644 index 0000000..9525774 --- /dev/null +++ b/FunctionsSdkE2ETests/DirectRef.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectRefEMG", "DirectRef\DirectRefEMG\DirectRefEMG.csproj", "{D76D2626-4BD6-495C-A69D-61DBDF330E5C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedStartup", "SharedStartup\SharedStartup.csproj", "{196A4BB6-8FF2-44C9-BF31-D3517140FD3A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D76D2626-4BD6-495C-A69D-61DBDF330E5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D76D2626-4BD6-495C-A69D-61DBDF330E5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D76D2626-4BD6-495C-A69D-61DBDF330E5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D76D2626-4BD6-495C-A69D-61DBDF330E5C}.Release|Any CPU.Build.0 = Release|Any CPU + {196A4BB6-8FF2-44C9-BF31-D3517140FD3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {196A4BB6-8FF2-44C9-BF31-D3517140FD3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {196A4BB6-8FF2-44C9-BF31-D3517140FD3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {196A4BB6-8FF2-44C9-BF31-D3517140FD3A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {124DF235-0912-41B0-97D7-55D136A20206} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefEMG.csproj b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefEMG.csproj new file mode 100644 index 0000000..ca901fc --- /dev/null +++ b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefEMG.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp3.1 + v3 + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + \ No newline at end of file diff --git a/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefStartup.cs b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefStartup.cs new file mode 100644 index 0000000..2d3fa91 --- /dev/null +++ b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/DirectRefStartup.cs @@ -0,0 +1,14 @@ +using DirectRefEMG; +using Microsoft.Azure.Functions.Extensions.DependencyInjection; + +[assembly: FunctionsStartup(typeof(DirectRefStartup))] + +namespace DirectRefEMG +{ + public class DirectRefStartup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + } + } +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/Function1.cs b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/Function1.cs new file mode 100644 index 0000000..e908523 --- /dev/null +++ b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/Function1.cs @@ -0,0 +1,35 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace DirectRefEMG +{ + public static class Function1 + { + [FunctionName("Function1")] + public static async Task Run( + [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, + ILogger log) + { + log.LogInformation("C# HTTP trigger function processed a request."); + + string name = req.Query["name"]; + + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + dynamic data = JsonConvert.DeserializeObject(requestBody); + name = name ?? data?.name; + + string responseMessage = string.IsNullOrEmpty(name) + ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response." + : $"Hello, {name}. This HTTP triggered function executed successfully."; + + return new OkObjectResult(responseMessage); + } + } +} diff --git a/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/host.json b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/host.json new file mode 100644 index 0000000..b9f92c0 --- /dev/null +++ b/FunctionsSdkE2ETests/DirectRef/DirectRefEMG/host.json @@ -0,0 +1,3 @@ +{ + "version": "2.0" +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/FrameworkVersions.sln b/FunctionsSdkE2ETests/FrameworkVersions.sln new file mode 100644 index 0000000..ad31e96 --- /dev/null +++ b/FunctionsSdkE2ETests/FrameworkVersions.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedStartup", "SharedStartup\SharedStartup.csproj", "{E03C8FCA-EF16-4901-B8C2-97E3ED3722A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore20", "FrameworkVersions\NetCore20\NetCore20.csproj", "{E079DE00-E96B-47DE-AA40-07454B1AFF71}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore21", "FrameworkVersions\NetCore21\NetCore21.csproj", "{D5EDC07B-5AA4-4426-AEA3-E9B39D263B68}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCore22", "FrameworkVersions\NetCore22\NetCore22.csproj", "{112D1C20-2603-40B3-9EC5-2F15F873DAB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandard20", "FrameworkVersions\NetStandard20\NetStandard20.csproj", "{C9E23629-4E83-4AAA-97D8-7301BC0FB34C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E03C8FCA-EF16-4901-B8C2-97E3ED3722A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E03C8FCA-EF16-4901-B8C2-97E3ED3722A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E03C8FCA-EF16-4901-B8C2-97E3ED3722A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E03C8FCA-EF16-4901-B8C2-97E3ED3722A2}.Release|Any CPU.Build.0 = Release|Any CPU + {E079DE00-E96B-47DE-AA40-07454B1AFF71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E079DE00-E96B-47DE-AA40-07454B1AFF71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E079DE00-E96B-47DE-AA40-07454B1AFF71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E079DE00-E96B-47DE-AA40-07454B1AFF71}.Release|Any CPU.Build.0 = Release|Any CPU + {D5EDC07B-5AA4-4426-AEA3-E9B39D263B68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5EDC07B-5AA4-4426-AEA3-E9B39D263B68}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5EDC07B-5AA4-4426-AEA3-E9B39D263B68}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5EDC07B-5AA4-4426-AEA3-E9B39D263B68}.Release|Any CPU.Build.0 = Release|Any CPU + {112D1C20-2603-40B3-9EC5-2F15F873DAB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {112D1C20-2603-40B3-9EC5-2F15F873DAB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {112D1C20-2603-40B3-9EC5-2F15F873DAB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {112D1C20-2603-40B3-9EC5-2F15F873DAB4}.Release|Any CPU.Build.0 = Release|Any CPU + {C9E23629-4E83-4AAA-97D8-7301BC0FB34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9E23629-4E83-4AAA-97D8-7301BC0FB34C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9E23629-4E83-4AAA-97D8-7301BC0FB34C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9E23629-4E83-4AAA-97D8-7301BC0FB34C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BF258C1A-DCD2-4BAA-8455-7B391E0F0AD1} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/FunctionsSdkE2ETests.sln b/FunctionsSdkE2ETests/FunctionsSdkE2ETests.sln new file mode 100644 index 0000000..07a4104 --- /dev/null +++ b/FunctionsSdkE2ETests/FunctionsSdkE2ETests.sln @@ -0,0 +1,69 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionsSdkE2ETests", "FunctionsSdkE2ETests\FunctionsSdkE2ETests.csproj", "{A980D42F-48BA-4052-BAB2-0032A6B7516F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{41E8547B-A497-4C51-B648-80DC7FE9A681}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "V3", "V3\V3\V3.csproj", "{1E490BED-97C2-4FE4-9C85-6837FB5C36A0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DirectRefEMG", "DirectRef\DirectRefEMG\DirectRefEMG.csproj", "{1A5C26E1-09F5-451D-A137-0E65CA38F85C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E3EBA50C-BF05-48FB-8D68-555079B759D0}" + ProjectSection(SolutionItems) = preProject + SdkVersion.props = SdkVersion.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoSdkRef", "NoSdkRef\NoSdkRef.csproj", "{BD963FE0-0F9E-4480-AA2D-A1F9C8089327}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefNotFound", "Razor\RefNotFound\RefNotFound.csproj", "{41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedStartup", "SharedStartup\SharedStartup.csproj", "{36AFB021-DBBD-447D-A6FA-78FB3023D191}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A980D42F-48BA-4052-BAB2-0032A6B7516F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A980D42F-48BA-4052-BAB2-0032A6B7516F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A980D42F-48BA-4052-BAB2-0032A6B7516F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A980D42F-48BA-4052-BAB2-0032A6B7516F}.Release|Any CPU.Build.0 = Release|Any CPU + {1E490BED-97C2-4FE4-9C85-6837FB5C36A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E490BED-97C2-4FE4-9C85-6837FB5C36A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E490BED-97C2-4FE4-9C85-6837FB5C36A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E490BED-97C2-4FE4-9C85-6837FB5C36A0}.Release|Any CPU.Build.0 = Release|Any CPU + {1A5C26E1-09F5-451D-A137-0E65CA38F85C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A5C26E1-09F5-451D-A137-0E65CA38F85C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A5C26E1-09F5-451D-A137-0E65CA38F85C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A5C26E1-09F5-451D-A137-0E65CA38F85C}.Release|Any CPU.Build.0 = Release|Any CPU + {BD963FE0-0F9E-4480-AA2D-A1F9C8089327}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD963FE0-0F9E-4480-AA2D-A1F9C8089327}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD963FE0-0F9E-4480-AA2D-A1F9C8089327}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD963FE0-0F9E-4480-AA2D-A1F9C8089327}.Release|Any CPU.Build.0 = Release|Any CPU + {41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3}.Release|Any CPU.Build.0 = Release|Any CPU + {36AFB021-DBBD-447D-A6FA-78FB3023D191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36AFB021-DBBD-447D-A6FA-78FB3023D191}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36AFB021-DBBD-447D-A6FA-78FB3023D191}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36AFB021-DBBD-447D-A6FA-78FB3023D191}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1E490BED-97C2-4FE4-9C85-6837FB5C36A0} = {41E8547B-A497-4C51-B648-80DC7FE9A681} + {1A5C26E1-09F5-451D-A137-0E65CA38F85C} = {41E8547B-A497-4C51-B648-80DC7FE9A681} + {BD963FE0-0F9E-4480-AA2D-A1F9C8089327} = {41E8547B-A497-4C51-B648-80DC7FE9A681} + {41E58322-B3F0-4B2D-8B7A-0626BCEBEDA3} = {41E8547B-A497-4C51-B648-80DC7FE9A681} + {36AFB021-DBBD-447D-A6FA-78FB3023D191} = {41E8547B-A497-4C51-B648-80DC7FE9A681} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E0021489-706A-4A64-98FA-2D1DFE28695E} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/FunctionsSdkE2ETests/E2ETests.cs b/FunctionsSdkE2ETests/FunctionsSdkE2ETests/E2ETests.cs new file mode 100644 index 0000000..7040e94 --- /dev/null +++ b/FunctionsSdkE2ETests/FunctionsSdkE2ETests/E2ETests.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace FunctionsSdkE2ETests +{ + public class E2ETests + { + private const string _expectedExtensionsJson = "{\"extensions\":[{ \"name\": \"Startup\", \"typeName\":\"SharedStartup.Startup, SharedStartup, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\"}]}"; + private const string _expectedBinFolder = @"\bin"; + + private void CleanBinFolders(string rootDir) + { + foreach (string binDir in Directory.EnumerateDirectories(rootDir, "bin", new EnumerationOptions { RecurseSubdirectories = true })) + { + Directory.Delete(binDir, true); + } + } + + [Fact] + public void Build_DirectRef() + { + string solutionName = "DirectRef"; + + string solutionFile = solutionName + ".sln"; + string workingDir = FindContainingDirectory(solutionFile); + + RunDotNet("restore", workingDir, solutionFile); + RunDotNet("clean", workingDir, solutionFile); + RunDotNet("build", workingDir, solutionFile); + + ValidateExtensionsJsonRecursive(Path.Combine(workingDir, solutionName), 1, expectedFolder: _expectedBinFolder, + ValidateDirectRefStartupExtension, + ValidateSharedStartupExtension); + } + + [Fact] + public void Publish_DirectRef() + { + string publishDir = Path.Combine(Path.GetTempPath(), "FunctionsSdkTests", "pub_directRef"); + if (Directory.Exists(publishDir)) + { + Directory.Delete(publishDir, true); + } + + string solutionName = "DirectRef"; + string solutionFile = solutionName + ".sln"; + string workingDir = FindContainingDirectory(solutionFile); + + RunDotNet("restore", workingDir, solutionFile); + RunDotNet("clean", workingDir, solutionFile); + RunDotNet("publish", workingDir, solutionFile, $"-o {publishDir} /bl"); + + ValidateExtensionsJsonRecursive(publishDir, 1, expectedFolder: _expectedBinFolder, + ValidateDirectRefStartupExtension, + ValidateSharedStartupExtension); + } + + [Fact] + public void Build_NoSdkRef() + { + string solutionName = "NoSdkRef"; + + string solutionFile = solutionName + ".sln"; + string workingDir = FindContainingDirectory(solutionFile); + + string projectDir = Path.Combine(workingDir, solutionName); + + CleanBinFolders(projectDir); + + RunDotNet("restore", workingDir, solutionFile); + RunDotNet("clean", workingDir, solutionFile); + RunDotNet("build", workingDir, solutionFile); + + ValidateExtensionsJsonRecursive(projectDir, 1, expectedFolder: @"Debug\netcoreapp2.1", + t => + { + return t["name"].ToString() == "AzureStorage" + && t["typeName"].ToString() == "Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage, Version=3.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; + }); + } + + [Fact] + public void Build_V3() + { + RunBasicValidation("V3"); + } + + private void RunBasicValidation(string solutionName) + { + string solutionFile = solutionName + ".sln"; + string workingDir = FindContainingDirectory(solutionFile); + + string projectDir = Path.Combine(workingDir, solutionName); + + CleanBinFolders(projectDir); + + RunDotNet("restore", workingDir, solutionFile); + RunDotNet("clean", workingDir, solutionFile); + RunDotNet("build", workingDir, solutionFile); + + ValidateExtensionsJsonRecursive(projectDir, 1, expectedFolder: _expectedBinFolder, + t => + { + return t["name"].ToString() == "AzureStorage" + && t["typeName"].ToString() == "Microsoft.Azure.WebJobs.Extensions.Storage.AzureStorageWebJobsStartup, Microsoft.Azure.WebJobs.Extensions.Storage, Version=3.0.10.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"; + }, + t => + { + return t["name"].ToString() == "Startup" + && t["typeName"].ToString() == $"{solutionName}.Startup, {solutionName}, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; + }); + + // The tests include one auto-created and one manually-created function.json file. The build should generate both into the output folder. + string projectFolder = Path.GetDirectoryName(projectDir); + string projectBinDir = Path.Combine(projectDir, solutionName, "bin"); + + IEnumerable functionJsonFilePaths = Directory.EnumerateFiles(Path.Combine(projectBinDir), "function.json", new EnumerationOptions { RecurseSubdirectories = true }); + // Comment out until 3.0.10 and 1.0.38 are released + // Assert.Collection(functionJsonFilePaths, + // p => Assert.EndsWith("\\Function1\\function.json", p), + // p => Assert.EndsWith("\\Function2\\function.json", p)); + } + + private void RunTest(string solutionName, int expectedExtensionsJsonCount = 1) + { + string solutionFile = solutionName + ".sln"; + string workingDir = FindContainingDirectory(solutionFile); + + RunDotNet("restore", workingDir, solutionFile); + RunDotNet("clean", workingDir, solutionFile); + RunDotNet("build", workingDir, solutionFile); + + ValidateExtensionsJsonRecursive(Path.Combine(workingDir, solutionName), expectedExtensionsJsonCount); + } + + private bool ValidateSharedStartupExtension(JToken extensionToken) + { + return extensionToken["name"].ToString() == "Startup" && extensionToken["typeName"].ToString() == "SharedStartup.Startup, SharedStartup, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; + } + + private bool ValidateDirectRefStartupExtension(JToken extensionToken) + { + return extensionToken["name"].ToString() == "DirectRefStartup" && extensionToken["typeName"].ToString() == "DirectRefEMG.DirectRefStartup, DirectRefEMG, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; + } + + private void ValidateExtensionsJsonRecursive(string startingDir, int expectedCount) + { + ValidateExtensionsJsonRecursive(startingDir, expectedCount, expectedFolder: _expectedBinFolder, ValidateSharedStartupExtension); + } + + private void ValidateExtensionsJsonRecursive(string startingDir, int expectedCount, string expectedFolder, params Func[] extensionValidators) + { + // Check all extensions.json + IEnumerable extensionsFiles = Directory.EnumerateFiles(Path.Combine(startingDir), "extensions.json", new EnumerationOptions { RecurseSubdirectories = true }); + + Assert.Equal(expectedCount, extensionsFiles.Count()); + + foreach (string file in extensionsFiles) + { + Assert.True(Path.GetDirectoryName(file).EndsWith(expectedFolder, StringComparison.OrdinalIgnoreCase), $"'{file}' is not in the '{expectedFolder}' folder"); + + JObject actualJson = JObject.Parse(File.ReadAllText(file)); + + JToken[] extensionsArray = actualJson["extensions"] + .AsEnumerable() + .OrderBy(p => p["name"]) + .ToArray(); + + string createErrorMessage() + { + return $"File: {file} | Actual: {actualJson}"; + } + + Assert.True(extensionValidators.Length == extensionsArray.Length, $"Incorrect number of validators ({extensionValidators.Length}) | {createErrorMessage()}"); + + for (int i = 0; i < extensionValidators.Length; i++) + { + JToken extension = extensionsArray[i]; + Assert.True(extensionValidators[i](extension), $"Failed validator for {extension} | {createErrorMessage()}"); + } + } + } + + private void RunDotNet(string command, string workingDir, string solutionFile, string additionalArgs = null) + { + ProcessStartInfo startInfo = new ProcessStartInfo + { + WorkingDirectory = workingDir, + FileName = "dotnet", + Arguments = $"{command} {solutionFile} {additionalArgs} -nodeReuse:False", + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + using (Process process = Process.Start(startInfo)) + { + StringBuilder stdOut = new StringBuilder(); ; + StringBuilder stdErr = new StringBuilder(); + + process.OutputDataReceived += (s, e) => + { + if (e.Data != null) + { + stdOut.AppendLine(e.Data); + } + }; + + process.ErrorDataReceived += (s, e) => + { + if (e.Data != null) + { + stdErr.AppendLine(e.Data); + } + }; + + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + + process.WaitForExit(); + + Assert.True(process.ExitCode == 0, $"StdOut: {stdOut} | StdErr: {stdErr}"); + Assert.Empty(stdErr.ToString()); + //Assert.DoesNotContain(": warning ", stdOut.ToString()); + Assert.DoesNotContain(": error ", stdOut.ToString()); + } + } + + private string FindContainingDirectory(string fileToFind) + { + string currentDir = Directory.GetCurrentDirectory(); + string dir = null; + + while (currentDir != null && dir == null) + { + if (Directory.EnumerateFiles(currentDir, fileToFind).SingleOrDefault() != null) + { + dir = currentDir; + } + else + { + currentDir = Directory.GetParent(currentDir)?.FullName; + } + } + + return dir; + } + } +} diff --git a/FunctionsSdkE2ETests/FunctionsSdkE2ETests/FunctionsSdkE2ETests.csproj b/FunctionsSdkE2ETests/FunctionsSdkE2ETests/FunctionsSdkE2ETests.csproj new file mode 100644 index 0000000..d04c984 --- /dev/null +++ b/FunctionsSdkE2ETests/FunctionsSdkE2ETests/FunctionsSdkE2ETests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/FunctionsSdkE2ETests/NoSdkRef.sln b/FunctionsSdkE2ETests/NoSdkRef.sln new file mode 100644 index 0000000..0559409 --- /dev/null +++ b/FunctionsSdkE2ETests/NoSdkRef.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NoSdkRef", "NoSdkRef\NoSdkRef.csproj", "{24D545FC-FCBC-4DE7-B94B-821D77C378A6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24D545FC-FCBC-4DE7-B94B-821D77C378A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24D545FC-FCBC-4DE7-B94B-821D77C378A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24D545FC-FCBC-4DE7-B94B-821D77C378A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24D545FC-FCBC-4DE7-B94B-821D77C378A6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {48B5EC69-8C12-406B-943B-C41C5DA3B47F} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/NoSdkRef/NoSdkRef.csproj b/FunctionsSdkE2ETests/NoSdkRef/NoSdkRef.csproj new file mode 100644 index 0000000..d37f0c0 --- /dev/null +++ b/FunctionsSdkE2ETests/NoSdkRef/NoSdkRef.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.1 + v2 + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/FunctionsSdkE2ETests/NoSdkRef/host.json b/FunctionsSdkE2ETests/NoSdkRef/host.json new file mode 100644 index 0000000..b9f92c0 --- /dev/null +++ b/FunctionsSdkE2ETests/NoSdkRef/host.json @@ -0,0 +1,3 @@ +{ + "version": "2.0" +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/Razor.sln b/FunctionsSdkE2ETests/Razor.sln new file mode 100644 index 0000000..e60c51a --- /dev/null +++ b/FunctionsSdkE2ETests/Razor.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.352 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedStartup", "SharedStartup\SharedStartup.csproj", "{3BF97B5B-5D0B-49CE-B183-03C0D4E31086}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RefNotFound", "Razor\RefNotFound\RefNotFound.csproj", "{F5122E92-EA3C-4ED5-8D69-541134443E69}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mvc", "Razor\Mvc\Mvc.csproj", "{4D35EF45-9122-4BB9-B2F3-FEF3DF0C6EF6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3BF97B5B-5D0B-49CE-B183-03C0D4E31086}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BF97B5B-5D0B-49CE-B183-03C0D4E31086}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BF97B5B-5D0B-49CE-B183-03C0D4E31086}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BF97B5B-5D0B-49CE-B183-03C0D4E31086}.Release|Any CPU.Build.0 = Release|Any CPU + {F5122E92-EA3C-4ED5-8D69-541134443E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5122E92-EA3C-4ED5-8D69-541134443E69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5122E92-EA3C-4ED5-8D69-541134443E69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5122E92-EA3C-4ED5-8D69-541134443E69}.Release|Any CPU.Build.0 = Release|Any CPU + {4D35EF45-9122-4BB9-B2F3-FEF3DF0C6EF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D35EF45-9122-4BB9-B2F3-FEF3DF0C6EF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D35EF45-9122-4BB9-B2F3-FEF3DF0C6EF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D35EF45-9122-4BB9-B2F3-FEF3DF0C6EF6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A9D02ED7-1086-4736-AD7A-9F6DA681C515} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/Razor/RefNotFound/RefNotFound.csproj b/FunctionsSdkE2ETests/Razor/RefNotFound/RefNotFound.csproj new file mode 100644 index 0000000..d97efc7 --- /dev/null +++ b/FunctionsSdkE2ETests/Razor/RefNotFound/RefNotFound.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + v3 + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + \ No newline at end of file diff --git a/FunctionsSdkE2ETests/Razor/RefNotFound/host.json b/FunctionsSdkE2ETests/Razor/RefNotFound/host.json new file mode 100644 index 0000000..b9f92c0 --- /dev/null +++ b/FunctionsSdkE2ETests/Razor/RefNotFound/host.json @@ -0,0 +1,3 @@ +{ + "version": "2.0" +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/Razor/RefNotFound/local.settings.json b/FunctionsSdkE2ETests/Razor/RefNotFound/local.settings.json new file mode 100644 index 0000000..4fce9ff --- /dev/null +++ b/FunctionsSdkE2ETests/Razor/RefNotFound/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/SdkVersion.props b/FunctionsSdkE2ETests/SdkVersion.props new file mode 100644 index 0000000..eadb82d --- /dev/null +++ b/FunctionsSdkE2ETests/SdkVersion.props @@ -0,0 +1,13 @@ + + + + + $(FunctionsSdkVersion) + + + 3.0.8 + + + 1.2.0 + + diff --git a/FunctionsSdkE2ETests/SharedStartup/SharedStartup.csproj b/FunctionsSdkE2ETests/SharedStartup/SharedStartup.csproj new file mode 100644 index 0000000..d1385db --- /dev/null +++ b/FunctionsSdkE2ETests/SharedStartup/SharedStartup.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/FunctionsSdkE2ETests/SharedStartup/Startup.cs b/FunctionsSdkE2ETests/SharedStartup/Startup.cs new file mode 100644 index 0000000..3ea9707 --- /dev/null +++ b/FunctionsSdkE2ETests/SharedStartup/Startup.cs @@ -0,0 +1,14 @@ +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using SharedStartup; + +[assembly: FunctionsStartup(typeof(Startup))] + +namespace SharedStartup +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + } + } +} diff --git a/FunctionsSdkE2ETests/V3.sln b/FunctionsSdkE2ETests/V3.sln new file mode 100644 index 0000000..f799db6 --- /dev/null +++ b/FunctionsSdkE2ETests/V3.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29905.134 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "V3", "V3\V3\V3.csproj", "{86349AE8-9D48-4489-B99F-022784D5FC03}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {86349AE8-9D48-4489-B99F-022784D5FC03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86349AE8-9D48-4489-B99F-022784D5FC03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86349AE8-9D48-4489-B99F-022784D5FC03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86349AE8-9D48-4489-B99F-022784D5FC03}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FD0C4C61-C5EC-4257-A36B-2E95CA156BEF} + EndGlobalSection +EndGlobal diff --git a/FunctionsSdkE2ETests/V3/V3/Function1.cs b/FunctionsSdkE2ETests/V3/V3/Function1.cs new file mode 100644 index 0000000..46ff20a --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/Function1.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Host; +using Microsoft.Extensions.Logging; + +namespace V3 +{ + public static class Function1 + { + [FunctionName("Function1")] + public static void Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem, ILogger log) + { + log.LogInformation($"C# Queue trigger function processed: {myQueueItem}"); + } + } +} diff --git a/FunctionsSdkE2ETests/V3/V3/Function2/function.json b/FunctionsSdkE2ETests/V3/V3/Function2/function.json new file mode 100644 index 0000000..6254f9f --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/Function2/function.json @@ -0,0 +1,13 @@ +{ + "bindings": [ + { + "type": "queueTrigger", + "connection": "AzureWebJobsStorage", + "queueName": "myqueue-items", + "name": "myQueueItem" + } + ], + "disabled": false, + "scriptFile": "../bin/V3.dll", + "entryPoint": "V3.Function1.Run" +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/V3/V3/Startup.cs b/FunctionsSdkE2ETests/V3/V3/Startup.cs new file mode 100644 index 0000000..9c2a384 --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/Startup.cs @@ -0,0 +1,14 @@ +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using V3; + +[assembly: FunctionsStartup(typeof(Startup))] + +namespace V3 +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + } + } +} diff --git a/FunctionsSdkE2ETests/V3/V3/V3.csproj b/FunctionsSdkE2ETests/V3/V3/V3.csproj new file mode 100644 index 0000000..bd9a674 --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/V3.csproj @@ -0,0 +1,25 @@ + + + + + netcoreapp3.1 + v3 + + + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + Never + + + \ No newline at end of file diff --git a/FunctionsSdkE2ETests/V3/V3/host.json b/FunctionsSdkE2ETests/V3/V3/host.json new file mode 100644 index 0000000..bb3b8da --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingExcludedTypes": "Request", + "samplingSettings": { + "isEnabled": true + } + } + } +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/V3/V3/local.settings.json b/FunctionsSdkE2ETests/V3/V3/local.settings.json new file mode 100644 index 0000000..4fce9ff --- /dev/null +++ b/FunctionsSdkE2ETests/V3/V3/local.settings.json @@ -0,0 +1,7 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + } +} \ No newline at end of file diff --git a/FunctionsSdkE2ETests/nuget.config b/FunctionsSdkE2ETests/nuget.config new file mode 100644 index 0000000..39688e3 --- /dev/null +++ b/FunctionsSdkE2ETests/nuget.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..b7113b3 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,60 @@ +name: $(Build.SourceBranchName)_$(Build.Reason)_$(devops_buildNumber) + +trigger: +- v3.x +- main + +pool: + vmImage: 'windows-2019' + +variables: + devops_buildNumber: $[counter(format(''), 289)] + +steps: +- task: UseDotNet@2 + displayName: 'Install dotnet v3.x' + inputs: + packageType: 'sdk' + version: '3.1.x' + performMultiLevelLookup: true +- task: CmdLine@2 + inputs: + script: | + powershell Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile '%TEMP%\dotnet-install.ps1' + powershell %TEMP%\dotnet-install.ps1 -Architecture x64 -Version '3.1.301' -InstallDir '%ProgramFiles%\dotnet' + .paket\paket.exe install + packages\FAKE\tools\fake .\build.fsx + env: + FILES_ACCOUNT_KEY: $(FILES_ACCOUNT_KEY) + FILES_ACCOUNT_NAME: $(FILES_ACCOUNT_NAME) + BUILD_VERSION: 1.1.$(devops_buildNumber) + displayName: 'Build' +- task: PowerShell@2 + displayName: 'Set dotnet path' + inputs: + targetType: 'inline' + script: | + $infoContent = dotnet --info + $sdkBasePath = $infoContent | + Where-Object {$_ -match 'Base Path:'} | + ForEach-Object { + ($_ -replace '\s+Base Path:','').trim() + } + Write-Host "dotnet SDK path: $sdkBasePath" + $dotnetPath = (Get-Item $sdkBasePath).Parent.Parent.FullName + Write-Host "dotnet path: $dotnetPath" + Write-Host "##vso[task.setvariable variable=DotNetPath]$dotnetPath" +- task: DotNetCoreCLI@2 + displayName: 'Run End to End tests' + inputs: + command: 'test' + projects: '.\FunctionsSdkE2ETests\FunctionsSdkE2ETests.sln' + arguments: '-v n' +- pwsh: | + Move-Item -Path '$(Build.Repository.LocalPath)\artifacts\Microsoft.NET.Sdk.Functions.*' -Destination '$(Build.ArtifactStagingDirectory)' + displayName: 'Move artifacts' +- task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'drop' + publishLocation: 'Container' \ No newline at end of file diff --git a/build.fsx b/build.fsx index 14ac537..e8f7c62 100644 --- a/build.fsx +++ b/build.fsx @@ -31,13 +31,14 @@ let connectionString = let buildTaskOutputPath = "src\\Microsoft.NET.Sdk.Functions.MSBuild\\bin\\Release" let generatorOutputPath = "src\\Microsoft.NET.Sdk.Functions.Generator\\bin\\Release" let packOutputPath = "src\\Microsoft.NET.Sdk.Functions\\bin\\Release" -let version = if isNull appVeyorBuildVersion then "1.0.0.3" else appVeyorBuildVersion +let buildVersion = (env "BUILD_VERSION") +let version = if isNull buildVersion then "1.0.0.3" else buildVersion Target "Clean" (fun _ -> if Directory.Exists "tmpBuild" |> not then Directory.CreateDirectory "tmpBuild" |> ignore - if Directory.Exists "deploy" |> not then Directory.CreateDirectory "deploy" |> ignore + if Directory.Exists "artifacts" |> not then Directory.CreateDirectory "artifacts" |> ignore CleanDir "tmpBuild" - CleanDir "deploy" + CleanDir "artifacts" ) Target "Build" (fun _ -> @@ -178,7 +179,7 @@ Target "SignNupkg" (fun _ -> Target "Publish" (fun _ -> !! (packOutputPath @@ "signed\\Microsoft.NET.Sdk.Functions.*.nupkg") - |> Seq.iter (MoveFile "deploy") + |> Seq.iter (MoveFile "artifacts") ) Dependencies