Adding E2E test infrastructure and Cosmos E2E test (#100)

This commit is contained in:
Brett Samblanet 2021-02-03 15:38:28 -08:00 коммит произвёл GitHub
Родитель 451240bce6
Коммит 088ee4c28a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
27 изменённых файлов: 928 добавлений и 15 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -3,6 +3,8 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
Azure.Functions.Cli
# For local nuget packages
[Ll]ocal/

Просмотреть файл

@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtility", "test\TestUtility\TestUtility.csproj", "{C30B77A7-4085-422E-AADE-A4322989F5F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -67,6 +69,10 @@ Global
{BFB2832E-C3AB-4F09-B285-B24E535EC858}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFB2832E-C3AB-4F09-B285-B24E535EC858}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFB2832E-C3AB-4F09-B285-B24E535EC858}.Release|Any CPU.Build.0 = Release|Any CPU
{C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C30B77A7-4085-422E-AADE-A4322989F5F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C30B77A7-4085-422E-AADE-A4322989F5F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -78,6 +84,7 @@ Global
{5C38C5A3-83A5-4E2F-9B32-204C2225E890} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378}
{6B64FE04-BA90-49FC-893A-DF12EF15280C} = {4B0D77E7-FA83-4FDD-9E67-CC95EAD21348}
{BFB2832E-C3AB-4F09-B285-B24E535EC858} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378}
{C30B77A7-4085-422E-AADE-A4322989F5F8} = {FD7243E4-BF18-43F8-8744-BA1D17ACF378}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {497D2ED4-A13E-4BCA-8D29-F30CA7D0EA4A}

Просмотреть файл

@ -22,22 +22,27 @@ variables:
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core sdk'
displayName: 'Use .NET 5 sdk'
inputs:
packageType: sdk
version: 5.0.100
performMultiLevelLookup: true
- task: DotNetCoreCLI@2
displayName: 'Build projects'
inputs:
command: 'build'
arguments: '-c Release'
projects: '**/*.csproj'
- pwsh: ./setup-e2e-tests.ps1
displayName: "Setup E2E tests"
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: 'test/**/*.csproj'
projects: 'test/**/*Tests.csproj'
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
displayName: 'Sdk: ESRP CodeSigning - Authenticode'

34
run-e2e-tests.ps1 Normal file
Просмотреть файл

@ -0,0 +1,34 @@
function RunTest([string] $project, [string] $description,[bool] $skipBuild = $false, $filter = $null) {
Write-Host "Running test: $description" -ForegroundColor DarkCyan
Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan
Write-Host
$cmdargs = "test", "$project", "-v", "q", "-l", "trx", "-r",".\testResults"
if ($filter) {
$cmdargs += "--filter", "$filter"
}
& dotnet $cmdargs | Out-Host
$r = $?
Write-Host
Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan
Write-Host
return $r
}
$tests = @(
@{project ="$PSScriptRoot/test/E2ETests/E2ETests/E2ETests.csproj"; description="E2E integration tests"}
)
$success = $true
$testRunSucceeded = $true
foreach ($test in $tests){
$testRunSucceeded = RunTest $test.project $test.description $testRunSucceeded $test.filter
$success = $testRunSucceeded -and $success
}
if (-not $success) { exit 1 }

Просмотреть файл

@ -32,12 +32,12 @@
<None Include="$(OutputPath)Mono.Cecil.dll">
<Pack>true</Pack>
<PackagePath>tools\netstandard2.0</PackagePath>
</None>
</None>
<None Include="$(FunctionsGeneratorOutputPath)Microsoft.Azure.WebJobs.Extensions.FunctionMetadataLoader.dll">
<Pack>true</Pack>
<PackagePath>tools\netstandard2.0</PackagePath>
</None>
</None>
</ItemGroup>
@ -47,4 +47,10 @@
<PackageReference Include="System.Text.Json" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FunctionMetadataLoaderExtension\FunctionMetadataLoaderExtension.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
</Project>

57
setup-e2e-tests.ps1 Normal file
Просмотреть файл

@ -0,0 +1,57 @@
# A function that checks exit codes and fails script if an error is found
function StopOnFailedExecution {
if ($LastExitCode)
{
exit $LastExitCode
}
}
Write-Host
Write-Host "---Core Tools download---"
$skipCliDownload = $false
if($args[0])
{
$skipCliDownload = $args[0]
Write-Host "Skipping CLI Download"
}
$currDir = Get-Location
$output = "$currDir\Azure.Functions.Cli.zip"
if(!$skipCliDownload)
{
Write-Host "Deleting Functions Core Tools if exists...."
Remove-Item -Force ./Azure.Functions.Cli.zip -ErrorAction Ignore
Remove-Item -Recurse -Force ./Azure.Functions.Cli -ErrorAction Ignore
if (-not (Test-Path env:CORE_TOOLS_URL))
{
$env:CORE_TOOLS_URL = "https://functionsclibuilds.blob.core.windows.net/builds/3/latest/Azure.Functions.Cli.win-x86.zip"
}
Write-Host "Downloading Functions Core Tools to $output"
Invoke-RestMethod -Uri 'https://functionsclibuilds.blob.core.windows.net/builds/3/latest/version.txt' -OutFile version.txt
Write-Host "Using Functions Core Tools version: $(Get-Content -Raw version.txt)"
Remove-Item version.txt
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($env:CORE_TOOLS_URL, $output)
$destinationPath = ".\Azure.Functions.Cli"
Write-Host "Extracting Functions Core Tools to $destinationPath"
Expand-Archive ".\Azure.Functions.Cli.zip" -DestinationPath $destinationPath
}
if (Test-Path $output)
{
Remove-Item $output
}
Write-Host "------"
.\tools\devpack.ps1 -E2E
.\tools\start-emulators.ps1
StopOnFailedExecution

Просмотреть файл

@ -1,9 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Context;
using Microsoft.Azure.Functions.Worker.Pipeline;
@ -64,12 +63,10 @@ namespace Microsoft.Azure.Functions.Worker
foreach (var binding in executionContext.OutputBindings)
{
dynamic d = binding.Value;
var rpcVal = d.GetValue();
var parameterBinding = new ParameterBinding
{
Name = binding.Key,
Data = RpcExtensions.ToRpc(rpcVal)
Data = binding.Value.ToRpc()
};
response.OutputData.Add(parameterBinding);
}

Просмотреть файл

@ -99,7 +99,7 @@ namespace Microsoft.Azure.Functions.Worker
else
{
// TODO: Is this correct? Passing a null body causes the entire
// response to become the body in functions. Need to investigate.
// response to become the body in functions. Need to investigate.
http.Body = string.Empty.ToRpc();
}

Просмотреть файл

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Azure.Functions.Worker.E2EApp.Cosmos;
using Microsoft.Azure.Functions.Worker.Pipeline;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Worker.E2EApp
{
public static class CosmosFunction
{
[FunctionName("CosmosTrigger")]
public static void Run([CosmosDBTrigger(
databaseName: "%CosmosDb%",
collectionName: "%CosmosCollIn%",
ConnectionStringSetting = "CosmosConnection",
LeaseCollectionName = "leases",
CreateLeaseCollectionIfNotExists = true)] IReadOnlyList<MyDocument> input,
[CosmosDB(
databaseName: "%CosmosDb%",
collectionName: "%CosmosCollOut%",
ConnectionStringSetting = "CosmosConnection",
CreateIfNotExists = true)] OutputBinding<IEnumerable<object>> output,
FunctionExecutionContext context)
{
if (input != null && input.Count > 0)
{
foreach (var doc in input)
{
context.Logger.LogInformation($"id: {doc.Id}");
}
output.SetValue(input.Select(p => new { id = p.Id }));
}
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
namespace Microsoft.Azure.Functions.Worker.E2EApp.Cosmos
{
public class MyDocument
{
public string Id { get; set; }
public string Text { get; set; }
public int Number { get; set; }
public bool Boolean { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AzureFunctionsVersion>v3</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
<AssemblyName>Microsoft.Azure.Functions.Worker.E2EApp</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.E2EApp</RootNamespace>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.0.0-preview3" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="3.0.8" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.2" />
<PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="1.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\DotNetWorker\DotNetWorker.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,32 @@
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Configuration;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Azure.Functions.Worker.E2EApps.CosmosApp
{
class Program
{
static async Task Main(string[] args)
{
var host = new HostBuilder()
.ConfigureAppConfiguration(c =>
{
c.AddCommandLine(args);
})
.ConfigureFunctionsWorker((c, b) =>
{
b.UseFunctionExecutionMiddleware();
b.Services
.AddOptions<JsonSerializerOptions>()
.Configure(o => o.PropertyNameCaseInsensitive = true);
})
.Build();
await host.RunAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": true
}
}
}
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"CosmosConnection": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
"CosmosDb": "ItemDb",
"CosmosCollIn": "ItemCollectionIn",
"CosmosCollOut": "ItemCollectionOut"
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// 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.Configuration;
namespace Microsoft.Azure.Functions.Tests.E2ETests
{
public static class Constants
{
public static IConfiguration Configuration = TestUtility.GetTestConfiguration();
public static string FunctionsHostUrl = Configuration["FunctionAppUrl"] ?? "http://localhost:7071";
//Queue tests
public static class Queue
{
public static string StorageConnectionStringSetting = Configuration["AzureWebJobsStorage"];
public static string OutputBindingName = "test-output-node";
public static string InputBindingName = "test-input-node";
}
// CosmosDB tests
public static class CosmosDB
{
public static string EmulatorConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
public static string CosmosDBConnectionStringSetting = Configuration["AzureWebJobsCosmosDBConnectionString"] ?? EmulatorConnectionString;
public static string DbName = "ItemDb";
public static string InputCollectionName = "ItemCollectionIn";
public static string OutputCollectionName = "ItemCollectionOut";
public static string LeaseCollectionName = "leases";
}
// EventHubs
public static class EventHubs
{
public static string EventHubsConnectionStringSetting = Configuration["AzureWebJobsEventHubSender"];
public static class Json_Test
{
public static string OutputName = "test-output-object-node";
public static string InputName = "test-input-object-node";
}
public static class String_Test
{
public static string OutputName = "test-output-string-node";
public static string InputName = "test-input-string-node";
}
public static class Cardinality_One_Test
{
public static string InputName = "test-input-one-node";
public static string OutputName = "test-output-one-node";
}
}
// Xunit Fixtures and Collections
public const string FunctionAppCollectionName = "FunctionAppCollection";
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// 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.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Azure.Functions.Tests.E2ETests
{
[Collection(Constants.FunctionAppCollectionName)]
public class CosmosDBEndToEndTests : IDisposable
{
private readonly IDisposable _disposeLog;
private readonly FunctionAppFixture _fixture;
public CosmosDBEndToEndTests(FunctionAppFixture fixture, ITestOutputHelper testOutput)
{
_fixture = fixture;
_disposeLog = _fixture.TestLogs.UseTestLogger(testOutput);
}
[Fact]
public async Task CosmosDBTriggerAndOutput_Succeeds()
{
string expectedDocId = Guid.NewGuid().ToString();
try
{
//Trigger
await CosmosDBHelpers.CreateDocument(expectedDocId);
//Read
var documentId = await CosmosDBHelpers.ReadDocument(expectedDocId);
Assert.Equal(expectedDocId, documentId);
}
finally
{
//Clean up
await CosmosDBHelpers.DeleteTestDocuments(expectedDocId);
}
}
public void Dispose()
{
_disposeLog?.Dispose();
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<AssemblyName>Microsoft.Azure.Functions.Worker.E2ETests</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Worker.E2ETests</RootNamespace>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.DocumentDB.Core" Version="2.13.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="Moq" Version="4.14.7" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\TestUtility\TestUtility.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,48 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2ETests", "E2ETests.csproj", "{D8F06DF8-BA70-46B6-A4FC-FCC5A4B2C5ED}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Apps", "Apps", "{A5270BA7-7A26-41E0-B48E-26EBEF1A6AF2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtility", "..\..\TestUtility\TestUtility.csproj", "{1EC49D94-DFE9-4C4D-8D3E-E683AC930085}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetWorker", "..\..\..\src\DotNetWorker\DotNetWorker.csproj", "{30E9E94D-B936-4ECA-97F2-1B54824CE045}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2EApp", "..\E2EApps\E2EApp\E2EApp.csproj", "{D37818A3-3821-4F7F-B92B-41E4038C63CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D8F06DF8-BA70-46B6-A4FC-FCC5A4B2C5ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8F06DF8-BA70-46B6-A4FC-FCC5A4B2C5ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8F06DF8-BA70-46B6-A4FC-FCC5A4B2C5ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8F06DF8-BA70-46B6-A4FC-FCC5A4B2C5ED}.Release|Any CPU.Build.0 = Release|Any CPU
{1EC49D94-DFE9-4C4D-8D3E-E683AC930085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EC49D94-DFE9-4C4D-8D3E-E683AC930085}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EC49D94-DFE9-4C4D-8D3E-E683AC930085}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EC49D94-DFE9-4C4D-8D3E-E683AC930085}.Release|Any CPU.Build.0 = Release|Any CPU
{30E9E94D-B936-4ECA-97F2-1B54824CE045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30E9E94D-B936-4ECA-97F2-1B54824CE045}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30E9E94D-B936-4ECA-97F2-1B54824CE045}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30E9E94D-B936-4ECA-97F2-1B54824CE045}.Release|Any CPU.Build.0 = Release|Any CPU
{D37818A3-3821-4F7F-B92B-41E4038C63CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D37818A3-3821-4F7F-B92B-41E4038C63CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D37818A3-3821-4F7F-B92B-41E4038C63CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D37818A3-3821-4F7F-B92B-41E4038C63CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D37818A3-3821-4F7F-B92B-41E4038C63CC} = {A5270BA7-7A26-41E0-B48E-26EBEF1A6AF2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {89DAAC07-473A-4DEC-957E-62A8D7F2494B}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,71 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
namespace Microsoft.Azure.Functions.Tests.E2ETests
{
public static class FixtureHelpers
{
public static Process GetFuncHostProcess(bool enableAuth = false)
{
var funcProcess = new Process();
var rootDir = Path.GetFullPath(@"..\..\..\..\..\..");
var e2eAppBinPath = Path.Combine(rootDir, @"test\E2ETests\E2EApps\E2EApp\bin");
string e2eHostJson = Directory.GetFiles(e2eAppBinPath, "host.json", SearchOption.AllDirectories).FirstOrDefault();
if (e2eHostJson == null)
{
throw new InvalidOperationException($"Could not find a built worker app under '{e2eAppBinPath}'");
}
var e2eAppPath = Path.GetDirectoryName(e2eHostJson);
var cliPath = Path.Combine(rootDir, @"Azure.Functions.Cli\func.exe");
if (!File.Exists(cliPath))
{
throw new InvalidOperationException($"Could not find '{cliPath}'. Try running '{Path.Combine(rootDir, "setup-e2e-tests.ps1")}' to install it.");
}
funcProcess.StartInfo.UseShellExecute = false;
funcProcess.StartInfo.RedirectStandardError = true;
funcProcess.StartInfo.RedirectStandardOutput = true;
funcProcess.StartInfo.CreateNoWindow = true;
funcProcess.StartInfo.WorkingDirectory = e2eAppPath;
funcProcess.StartInfo.FileName = Path.Combine(rootDir, @"Azure.Functions.Cli\func.exe");
funcProcess.StartInfo.ArgumentList.Add("host");
funcProcess.StartInfo.ArgumentList.Add("start");
funcProcess.StartInfo.ArgumentList.Add("--csharp");
if (enableAuth)
{
funcProcess.StartInfo.ArgumentList.Add("--enableAuth");
}
return funcProcess;
}
public static void StartProcessWithLogging(Process funcProcess, ILogger logger)
{
funcProcess.ErrorDataReceived += (sender, e) => logger.LogError(e?.Data);
funcProcess.OutputDataReceived += (sender, e) => logger.LogInformation(e?.Data);
funcProcess.Start();
funcProcess.BeginErrorReadLine();
funcProcess.BeginOutputReadLine();
}
public static void KillExistingFuncHosts()
{
foreach (var func in Process.GetProcessesByName("func"))
{
func.Kill();
}
}
}
}

Просмотреть файл

@ -0,0 +1,88 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.E2ETests.Helpers;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.Azure.Functions.Tests.E2ETests
{
public class FunctionAppFixture : IAsyncLifetime
{
private readonly ILogger _logger;
private bool _disposed;
private Process _funcProcess;
public FunctionAppFixture(IMessageSink messageSink)
{
// initialize logging
ILoggerFactory loggerFactory = new LoggerFactory();
TestLogs = new TestLoggerProvider(messageSink);
loggerFactory.AddProvider(TestLogs);
_logger = loggerFactory.CreateLogger<FunctionAppFixture>();
}
public async Task InitializeAsync()
{
// start host via CLI if testing locally
if (Constants.FunctionsHostUrl.Contains("localhost"))
{
// kill existing func processes
_logger.LogInformation("Shutting down any running functions hosts..");
FixtureHelpers.KillExistingFuncHosts();
// start functions process
_logger.LogInformation($"Starting functions host for {Constants.FunctionAppCollectionName}...");
_funcProcess = FixtureHelpers.GetFuncHostProcess();
string workingDir = _funcProcess.StartInfo.WorkingDirectory;
_logger.LogInformation($" Working dir: '${workingDir}' Exists: '{Directory.Exists(workingDir)}'");
string fileName = _funcProcess.StartInfo.FileName;
_logger.LogInformation($" File name: '${fileName}' Exists: '{File.Exists(fileName)}'");
await CosmosDBHelpers.CreateDocumentCollections();
FixtureHelpers.StartProcessWithLogging(_funcProcess, _logger);
await TestUtility.RetryAsync(() =>
{
return Task.FromResult(TestLogs.CoreToolsLogs.Any(p => p.Contains("Host lock lease acquired by instance ID")));
});
}
}
internal TestLoggerProvider TestLogs { get; private set; }
public Task DisposeAsync()
{
if (!_disposed)
{
_logger.LogInformation("FunctionAppFixture disposing.");
if (_funcProcess != null)
{
_logger.LogInformation($"Shutting down functions host for {Constants.FunctionAppCollectionName}");
_funcProcess.Kill();
_funcProcess.Dispose();
}
}
_disposed = true;
return Task.CompletedTask;
}
}
[CollectionDefinition(Constants.FunctionAppCollectionName)]
public class FunctionAppCollection : ICollectionFixture<FunctionAppFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// 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.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
namespace Microsoft.Azure.Functions.Tests.E2ETests
{
public static class CosmosDBHelpers
{
private static DocumentClient _docDbClient;
private static Uri inputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName);
private static Uri outputCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName);
private static Uri leasesCollectionsUri = UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.LeaseCollectionName);
static CosmosDBHelpers()
{
var builder = new System.Data.Common.DbConnectionStringBuilder();
builder.ConnectionString = Constants.CosmosDB.CosmosDBConnectionStringSetting;
var serviceUri = new Uri(builder["AccountEndpoint"].ToString());
_docDbClient = new DocumentClient(serviceUri, builder["AccountKey"].ToString());
}
// keep
public async static Task CreateDocument(string docId)
{
Document documentToTest = new Document()
{
Id = docId
};
_ = await _docDbClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName), documentToTest);
}
// keep
public async static Task<string> ReadDocument(string docId)
{
var docUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId);
Document retrievedDocument = null;
await TestUtility.RetryAsync(async () =>
{
try
{
retrievedDocument = await _docDbClient.ReadDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) });
return true;
}
catch (DocumentClientException ex) when (ex.Error.Code == "NotFound")
{
return false;
}
}, pollingInterval: 500);
return retrievedDocument?.Id;
}
// keep
public async static Task DeleteTestDocuments(string docId)
{
var inputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.InputCollectionName, docId);
await DeleteDocument(inputDocUri);
var outputDocUri = UriFactory.CreateDocumentUri(Constants.CosmosDB.DbName, Constants.CosmosDB.OutputCollectionName, docId);
await DeleteDocument(outputDocUri);
}
private async static Task DeleteDocument(Uri docUri)
{
try
{
await _docDbClient.DeleteDocumentAsync(docUri);
}
catch (Exception)
{
//ignore
}
}
// keep
public async static Task CreateDocumentCollections()
{
Database db = await _docDbClient.CreateDatabaseIfNotExistsAsync(new Database { Id = Constants.CosmosDB.DbName });
Uri dbUri = UriFactory.CreateDatabaseUri(db.Id);
await CreateCollection(dbUri, Constants.CosmosDB.InputCollectionName);
await CreateCollection(dbUri, Constants.CosmosDB.OutputCollectionName);
await CreateCollection(dbUri, Constants.CosmosDB.LeaseCollectionName);
}
public async static Task DeleteDocumentCollections()
{
await DeleteCollection(inputCollectionsUri);
await DeleteCollection(outputCollectionsUri);
await DeleteCollection(leasesCollectionsUri);
}
private async static Task DeleteCollection(Uri collectionUri)
{
try
{
await _docDbClient.DeleteDocumentCollectionAsync(collectionUri);
}
catch (Exception)
{
//Ignore
}
}
private async static Task CreateCollection(Uri dbUri, string collectioName)
{
var pkd = new PartitionKeyDefinition();
pkd.Paths.Add("/id");
DocumentCollection collection = new DocumentCollection()
{
Id = collectioName,
PartitionKey = pkd
};
await _docDbClient.CreateDocumentCollectionIfNotExistsAsync(dbUri, collection,
new RequestOptions()
{
PartitionKey = new PartitionKey("/id"),
OfferThroughput = 400
});
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// 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.Linq;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.Azure.Functions.Worker.E2ETests.Helpers
{
internal class TestLoggerProvider : ILoggerProvider, ILogger
{
private readonly IMessageSink _messageSink;
private ITestOutputHelper _currentTestOutput;
IList<string> _logs = new List<string>();
public TestLoggerProvider(IMessageSink messageSink)
{
_messageSink = messageSink;
}
public IEnumerable<string> CoreToolsLogs => _logs.ToArray();
// This needs to be created/disposed per-test so we can associate logs
// with the specific running test.
public IDisposable UseTestLogger(ITestOutputHelper testOutput)
{
// reset these every test
_currentTestOutput = testOutput;
return new DisposableOutput(this);
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public ILogger CreateLogger(string categoryName)
{
return this;
}
public void Dispose()
{
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
string formattedString = formatter(state, exception);
_messageSink.OnMessage(new DiagnosticMessage(formattedString));
_logs.Add(formattedString);
_currentTestOutput?.WriteLine(formattedString);
}
private class DisposableOutput : IDisposable
{
private readonly TestLoggerProvider _xunitLogger;
public DisposableOutput(TestLoggerProvider xunitLogger)
{
_xunitLogger = xunitLogger;
}
public void Dispose()
{
_xunitLogger._currentTestOutput = null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,4 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"diagnosticMessages": true
}

Просмотреть файл

@ -0,0 +1,48 @@
// 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.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Azure.Functions.Tests
{
public static class TestUtility
{
public static IConfiguration GetTestConfiguration()
{
return new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddTestSettings()
.Build();
}
public static IConfigurationBuilder AddTestSettings(this IConfigurationBuilder builder)
{
string configPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".azurefunctions", "appsettings.tests.json");
return builder.AddJsonFile(configPath, true);
}
public static async Task RetryAsync(Func<Task<bool>> condition, int timeout = 60 * 1000, int pollingInterval = 2 * 1000, bool throwWhenDebugging = false, Func<string> userMessageCallback = null)
{
DateTime start = DateTime.Now;
while (!await condition())
{
await Task.Delay(pollingInterval);
bool shouldThrow = !Debugger.IsAttached || (Debugger.IsAttached && throwWhenDebugging);
if (shouldThrow && (DateTime.Now - start).TotalMilliseconds > timeout)
{
string error = "Condition not reached within timeout.";
if (userMessageCallback != null)
{
error += " " + userMessageCallback();
}
throw new ApplicationException(error);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>Microsoft.Azure.Functions.Tests.TestUtility</AssemblyName>
<RootNamespace>Microsoft.Azure.Functions.Tests.TestUtility</RootNamespace>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,8 +1,31 @@
# Packs the SDK locally, updates the Sample to use this package, and then builds the sample.
param(
[Parameter(Mandatory=$false)]
[Switch]
$E2E
)
# Packs the SDK locally, and (by default) updates the Sample to use this package, then builds.
# Specify --E2E to instead target the E2E test app.
$rootPath = Split-Path -Parent $PSScriptRoot
$project = "$rootPath\samples\FunctionApp\FunctionApp.csproj"
`dotnet pack $rootPath\sdk\sdk\Sdk.csproj -o $rootPath\local
`dotnet remove $rootPath\samples\FunctionApp\FunctionApp.csproj package Microsoft.Azure.Functions.Worker.Sdk
`dotnet add $rootPath\samples\FunctionApp\FunctionApp.csproj package Microsoft.Azure.Functions.Worker.Sdk -s $rootPath\local --prerelease
`dotnet build $rootPath\samples\FunctionApp\FunctionApp.csproj
if($E2E -eq $true)
{
$project = "$rootPath\test\E2ETests\E2EApps\E2EApp\E2EApp.csproj"
}
$localPack = "$rootPath\local"
Write-Host
Write-Host "---Updating project with local SDK pack---"
Write-Host "Packing SDK to $localPack"
& "dotnet" "pack" "$rootPath\sdk\sdk\Sdk.csproj" "-v" "q" "-o" "$localPack" "-nologo"
Write-Host
Write-Host "Updating SDK package reference in $project"
& "dotnet" "remove" $project "package" "Microsoft.Azure.Functions.Worker.Sdk"
& "dotnet" "add" $project "package" "Microsoft.Azure.Functions.Worker.Sdk" "-s" "$localPack" "--prerelease"
Write-Host
Write-Host "Building $project"
& "dotnet" "build" $project "-v" "q" "-nologo"
Write-Host "------"

28
tools/start-emulators.ps1 Normal file
Просмотреть файл

@ -0,0 +1,28 @@
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
Write-Host ""
Write-Host "---Starting CosmosDB emulator---"
$cosmosStatus = Get-CosmosDbEmulatorStatus
if ($cosmosStatus -ne "Running")
{
Start-CosmosDbEmulator -NoWait
Start-Sleep -Seconds 2
}
Write-Host "Cosmos status: $cosmosStatus"
Write-Host "------"
Write-Host ""
Write-Host "---Starting Storage emulator---"
& "${Env:ProgramFiles(x86)}\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe" "start"
Write-Host "------"
Write-Host ""
Write-Host "---Checking CosmosDB emulator status---"
while ($cosmosStatus -ne "Running")
{
$cosmosStatus = Get-CosmosDbEmulatorStatus
Start-Sleep -Seconds 2
}
Write-Host "Cosmos status: $cosmosStatus"
Write-Host "------"
Write-Host ""