Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

re-enable e2e workflow #39

Merged
merged 16 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@ env:
# update e2e-docs.yml
jobs:
test:
if: false()
#if: |
# github.event_name != 'pull_request' ||
# (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
if: |
github.event_name != 'pull_request' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -70,4 +69,11 @@ jobs:
- run: ./build.sh test --test-suite=e2e
env:
E2E__ENDPOINT: "${{env.ELASTIC_APM_SERVER_URL}}"
E2E__AUTHORIZATION: "Authentication=ApiKey ${{env.ELASTIC_APM_API_KEY}}"
E2E__AUTHORIZATION: "Authorization=ApiKey ${{env.ELASTIC_APM_API_KEY}}"

Mpdreamz marked this conversation as resolved.
Show resolved Hide resolved
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-traces
path: .artifacts/playwright-traces/*-screenshot.jpeg
retention-days: 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*.user
*.userosscache
*.sln.docstates
.DS_Store

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.IO.Compression;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
using Microsoft.Playwright;
using Xunit;
Expand Down Expand Up @@ -29,14 +31,16 @@ public ApmUIBrowserContext(IConfigurationRoot configuration, string serviceName)
public IBrowser Browser { get; private set; } = null!;
public IPlaywright HeadlessTester { get; private set; } = null!;

private const string BootstrapTraceName = "test_bootstrap";

public async Task InitializeAsync()
{
var username = _configuration["E2E:BrowserEmail"]?.Trim() ?? string.Empty;
var password = _configuration["E2E:BrowserPassword"]?.Trim() ?? string.Empty;
Program.Main(["install", "chromium"]);
HeadlessTester = await Playwright.CreateAsync();
Browser = await HeadlessTester.Chromium.LaunchAsync();
var page = await OpenApmLandingPage("test_bootstrap");
var page = await OpenApmLandingPage(BootstrapTraceName);
try
{
await page.GetByRole(AriaRole.Textbox, new() { Name = "email" }).FillAsync(username);
Expand All @@ -47,11 +51,11 @@ public async Task InitializeAsync()

StorageState = await page.Context.StorageStateAsync();

await StopTrace(page);
await StopTrace(page, success: true);
}
catch (Exception e)
{
await StopTrace(page, "test_bootstrap");
await StopTrace(page, success: false);
Console.WriteLine(e);
throw;
}
Expand All @@ -68,7 +72,7 @@ await page.Context.Tracing.StartAsync(new()
Title = testName,
Screenshots = true,
Snapshots = true,
Sources = true
Sources = false
});

return page;
Expand All @@ -84,20 +88,21 @@ public async Task<IPage> OpenApmLandingPage(string testName)

public async Task WaitForServiceOnOverview(IPage page)
{
page.SetDefaultTimeout((float)TimeSpan.FromSeconds(30).TotalMilliseconds);

var servicesHeader = page.GetByRole(AriaRole.Heading, new() { Name = "Services" });
await servicesHeader.WaitForAsync(new() { State = WaitForSelectorState.Visible });
var timeout = (float)TimeSpan.FromSeconds(30).TotalMilliseconds;

page.SetDefaultTimeout((float)TimeSpan.FromSeconds(10).TotalMilliseconds);
var servicesHeader = page.GetByRole(AriaRole.Heading, new() { Name = "Services" });
await servicesHeader.WaitForAsync(new() { State = WaitForSelectorState.Visible, Timeout = timeout });

Exception? observed = null;

var refreshTimeout = (float)TimeSpan.FromSeconds(5).TotalMilliseconds;
for (var i = 0; i < 10; i++)
{
try
{
var serviceLink = page.GetByRole(AriaRole.Link, new() { Name = _serviceName });
await serviceLink.WaitForAsync(new() { State = WaitForSelectorState.Visible });
await serviceLink.WaitForAsync(new() { State = WaitForSelectorState.Visible, Timeout = refreshTimeout });
observed = null;
break;
}
Expand All @@ -108,30 +113,31 @@ public async Task WaitForServiceOnOverview(IPage page)
}
finally
{
page.SetDefaultTimeout((float)TimeSpan.FromSeconds(5).TotalMilliseconds);
page.SetDefaultTimeout(refreshTimeout);
}
}
if (observed != null)
throw observed; //TODO proper rethrow with stack

}

public async Task StopTrace(IPage page, string? testName = null)
private int _unnamedTests;
public async Task StopTrace(IPage page, bool success, [CallerMemberName] string? testName = null)
{

if (string.IsNullOrWhiteSpace(testName))
testName ??= $"unknown_test_{_unnamedTests++}";
//only dump trace zip of test name is provided.
if (success)
await page.Context.Tracing.StopAsync(new());
else
{
var root = DotNetRunApplication.GetSolutionRoot();
await page.Context.Tracing.StopAsync(new()
{
Path = Path.Combine(
Path.Combine(root.FullName, ".artifacts"),
"playwright-traces",
$"{testName}.zip"
)
});
var zip = Path.Combine(root.FullName, ".artifacts", "playwright-traces", $"{testName}.zip");
await page.Context.Tracing.StopAsync(new() { Path = zip });

using var archive = ZipFile.OpenRead(zip);
var entries = archive.Entries.Where(e => e.FullName.StartsWith("resources") && e.FullName.EndsWith(".jpeg")).ToList();
var lastScreenshot = entries.MaxBy(e => e.LastWriteTime);
lastScreenshot?.ExtractToFile(Path.Combine(root.FullName, ".artifacts", "playwright-traces", $"{testName}-screenshot.jpeg"));
}
await page.CloseAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Nullean.Xunit.Partitions.Sdk;
using Xunit.Sdk;

namespace Elastic.OpenTelemetry.EndToEndTests.DistributedFixture;

Expand All @@ -16,13 +18,28 @@ public class DistributedApplicationFixture : IPartitionLifetime

public string ServiceName { get; } = $"dotnet-e2e-{ShaForCurrentTicks()}";

public bool Started => AspNetApplication.ProcessId.HasValue;
public bool Started => AspNetApplication?.ProcessId.HasValue ?? false;

private readonly List<string> _output = new();

public int? MaxConcurrency => null;

public ApmUIBrowserContext ApmUI { get; private set; } = null!;
private ApmUIBrowserContext? _apmUI;
public ApmUIBrowserContext ApmUI
{
get => _apmUI ??
throw new NullReferenceException($"{nameof(DistributedApplicationFixture)} no yet initialized");
private set => _apmUI = value;
}

private AspNetCoreExampleApplication? _aspNetApplication;

public AspNetCoreExampleApplication AspNetApplication { get; private set; } = null!;
public AspNetCoreExampleApplication AspNetApplication
{
get => _aspNetApplication
?? throw new NullReferenceException($"{nameof(DistributedApplicationFixture)} no yet initialized");
private set => _aspNetApplication = value;
}

private static string ShaForCurrentTicks()
{
Expand All @@ -34,10 +51,31 @@ private static string ShaForCurrentTicks()
.Substring(0, 12);
}

public string FailureTestOutput()
{
var logLines = new List<string>();
if (_aspNetApplication?.ProcessId.HasValue ?? false)
AspNetApplication.IterateOverLog(s =>
{
Console.WriteLine(s);
logLines.Add(s);
});

var messages = string.Join(Environment.NewLine, _output.Concat(logLines));
return messages;

}

public async Task DisposeAsync()
{
AspNetApplication.Dispose();
await ApmUI.DisposeAsync();
_aspNetApplication?.Dispose();
await (_apmUI?.DisposeAsync() ?? Task.CompletedTask);
}

private void Log(string message)
{
Console.WriteLine(message);
_output.Add(message);
}

public async Task InitializeAsync()
Expand All @@ -47,18 +85,32 @@ public async Task InitializeAsync()
.AddUserSecrets<DotNetRunApplication>()
.Build();

Log("Created configuration");

AspNetApplication = new AspNetCoreExampleApplication(ServiceName, configuration);

Log("Started ASP.NET application");

ApmUI = new ApmUIBrowserContext(configuration, ServiceName);

Log("Started UI Browser context");

foreach (var trafficSimulator in _trafficSimulators)
await trafficSimulator.Start(this);

Log("Simulated traffic");

// TODO query OTEL_BSP_SCHEDULE_DELAY?
await Task.Delay(5000);

Log("Waited for OTEL_BSP_SCHEDULE_DELAY");

// Stateless refresh
//https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/index/IndexSettings.java#L286
await Task.Delay(TimeSpan.FromSeconds(15));

Log("Waited for Stateless refresh");

await ApmUI.InitializeAsync();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,25 @@ public abstract class DotNetRunApplication
{
private static readonly DirectoryInfo CurrentDirectory = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!;
private static readonly Regex ProcessIdMatch = new(@"^\s*Process Id (?<processid>\d+)");

public static readonly DirectoryInfo Root = GetSolutionRoot();
public static readonly DirectoryInfo LogDirectory = new(Path.Combine(Root.FullName, ".artifacts", "tests"));

private readonly LongRunningApplicationSubscription _app;
private readonly string _applicationName;
private readonly string _authorization;
private readonly string _endpoint;
private readonly string _serviceName;

public DotNetRunApplication(string serviceName, IConfiguration configuration, string applicationName)
protected DotNetRunApplication(string serviceName, IConfiguration configuration, string applicationName)
{
_serviceName = serviceName;
_applicationName = applicationName;
_endpoint = configuration["E2E:Endpoint"]?.Trim() ?? string.Empty;
_authorization = configuration["E2E:Authorization"]?.Trim() ?? string.Empty;

var args = CreateStartArgs();
_app = Proc.StartLongRunning(args, TimeSpan.FromSeconds(10));
_app = Proc.StartLongRunning(args, TimeSpan.FromSeconds(30));
}

public int? ProcessId { get; private set; }
Expand All @@ -48,8 +52,7 @@ public static DirectoryInfo GetSolutionRoot()

private LongRunningArguments CreateStartArgs()
{
var root = GetSolutionRoot();
var project = Path.Combine(root.FullName, "examples", _applicationName);
var project = Path.Combine(Root.FullName, "examples", _applicationName);

var arguments = new[] { "run", "--project", project };
var applicationArguments = GetArguments();
Expand All @@ -58,6 +61,7 @@ private LongRunningArguments CreateStartArgs()

return new("dotnet", arguments)
{

Environment = new Dictionary<string, string>
{
{ "OTEL_EXPORTER_OTLP_ENDPOINT", _endpoint },
Expand All @@ -67,6 +71,10 @@ private LongRunningArguments CreateStartArgs()
{ "OTEL_BSP_SCHEDULE_DELAY", "1000" },
{ "OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "5" },
{ "OTEL_RESOURCE_ATTRIBUTES", $"service.name={_serviceName},service.version=1.0,1,deployment.environment=e2e" },

{ "ELASTIC_OTEL_ENABLE_FILE_LOGGING", "1" },
{ "ELASTIC_OTEL_LOG_DIRECTORY", LogDirectory.FullName },
{ "ELASTIC_OTEL_LOG_LEVEL", "INFO" },
},
StartedConfirmationHandler = l =>
{
Expand All @@ -80,6 +88,28 @@ private LongRunningArguments CreateStartArgs()
return l.Line.StartsWith(" Application started.");
}
};


}

public void IterateOverLog(Action<string> write)
{
var logFile = DotNetRunApplication.LogDirectory
//TODO get last of this app specifically
//.GetFiles($"{_app.Process.Binary}_*.log")
.GetFiles($"*.log")
.MaxBy(f => f.CreationTimeUtc);

if (logFile == null)
write($"Could not locate log files in {DotNetRunApplication.LogDirectory}");
else
{
write($"Contents of: {logFile.FullName}");
using var sr = logFile.OpenText();
var s = string.Empty;
while ((s = sr.ReadLine()) != null)
write(s);
}
}

public virtual void Dispose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.41.1"/>
<PackageReference Include="Nullean.Xunit.Partitions" Version="0.4.3" />
<PackageReference Include="Nullean.Xunit.Partitions" Version="0.5.0" />
<PackageReference Include="Proc" Version="0.8.1"/>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0"/>
Expand Down
19 changes: 15 additions & 4 deletions tests/Elastic.OpenTelemetry.EndToEndTests/ServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,26 @@ public class EndToEndTests(ITestOutputHelper output, DistributedApplicationFixtu
[Fact]
public async Task LatencyShowsAGraph()
{
var timeout = (float)TimeSpan.FromSeconds(30).TotalMilliseconds;

// click on service in service overview page.
_page.SetDefaultTimeout((float)TimeSpan.FromSeconds(30).TotalMilliseconds);
var uri = new Uri(fixture.ApmUI.KibanaAppUri, $"/app/apm/services/{fixture.ServiceName}/overview").ToString();
await _page.GotoAsync(uri);
await Expect(_page.GetByRole(AriaRole.Heading, new() { Name = "Latency", Exact = true })).ToBeVisibleAsync();
await _page.GotoAsync(uri, new() { Timeout = timeout });
await Expect(_page.GetByRole(AriaRole.Heading, new() { Name = "Latency", Exact = true }))
.ToBeVisibleAsync(new() { Timeout = timeout });
}


public async Task InitializeAsync() => _page = await fixture.ApmUI.NewProfiledPage(_testName);

public async Task DisposeAsync() => await fixture.ApmUI.StopTrace(_page, PartitionContext.TestException == null ? null : _testName);
public async Task DisposeAsync()
{
var success = PartitionContext.TestException == null;
await fixture.ApmUI.StopTrace(_page, success, _testName);

if (!success)
return;

fixture.AspNetApplication.IterateOverLog(Output.WriteLine);
}
}
Loading
Loading