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

Launch artifacts extension #148

Merged
merged 7 commits into from
Oct 11, 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
15 changes: 13 additions & 2 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,24 @@ Requests are not repeated, only 1 attempt is allocated for all requests.
# Reporting Experience

If you want to redirect agent to send test results into some another way, there are several options:
- `Launch:Id` (UUID of existing launch) - agent will append test results into provided Launch ID. Launch should be *IN_PROGRESS* state, agent will not finish it. It's your responsibility to start and finish launch. Usefull for distributed test execution, where tests are running on different machines and you want to see consolidated report.
- `Launch:Id` (UUID of existing launch) - agent will append test results into provided Launch ID. Launch should be *IN_PROGRESS* state, agent will not finish it. It's your responsibility to start and finish launch. Useful for distributed test execution, where tests are running on different machines and you want to see consolidated report.
- `Launch:Rerun` (true/false/yes/no) - agent will try to add new tests into existing launch (compared by name) or adds new attempt/retry for existing tests.
- `Launch:RerunOf` (UUID of existing launch) - agent will try to add new tests into existing launch (by ID) or adds new attempt/retry for existing tests. Takes effect only if `Launch:Rerun` is `true`.

## Attachments
In additional of attaching artifacts during tests execution [dynamically](./Logging.md), it is possible to configure file attachments at launch level statically via files pattern. Set `Launch:Artifacts` configuration property to set of file patterns, and files will be automatically attached after tests execution.

Example:
```json
{
"launch": {
"artifacts": [ "*.log", "screenshots/*.png" ]
}
}
```

# Analytics

Each time when new launch is posted to RP server, reporting engine sends this fact to google analytics service. It doesn't collect sensetive information, just name and version of used engine/agent.
Each time when new launch is posted to RP server, reporting engine sends this fact to google analytics service. It doesn't collect sensitive information, just name and version of used engine/agent.

This behavior can be turned off through `Analytics:Enabled` configuration property.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using ReportPortal.Client.Abstractions.Requests;
using ReportPortal.Shared.Extensibility.ReportEvents;
using ReportPortal.Shared.Internal.Logging;
using ReportPortal.Shared.MimeTypes;
using System;
using System.IO;
using System.Threading.Tasks;

namespace ReportPortal.Shared.Extensibility.Embedded.LaunchArtifacts
{
public class LaunchArtifactsEventsObserver : IReportEventsObserver
{
private static readonly ITraceLogger _logger = TraceLogManager.Instance.GetLogger(typeof(LaunchArtifactsEventsObserver));

public string BaseDirectory { get; set; } = Environment.CurrentDirectory;

public void Initialize(IReportEventsSource reportEventsSource)
{
reportEventsSource.OnBeforeLaunchFinishing += ReportEventsSource_OnBeforeLaunchFinishing;
}

private void ReportEventsSource_OnBeforeLaunchFinishing(Reporter.ILaunchReporter launchReporter, ReportEvents.EventArgs.BeforeLaunchFinishingEventArgs args)
{
var artifactPaths = args.Configuration.GetValues<string>("Launch:Artifacts", null);

if (artifactPaths != null)
{
foreach (var filePattern in artifactPaths)
{
var artifacts = Directory.GetFiles(BaseDirectory, filePattern);

foreach (var artifact in artifacts)
{
var createLogItemRequest = new CreateLogItemRequest
{
LaunchUuid = launchReporter.Info.Uuid,
Time = DateTime.UtcNow,
Level = Client.Abstractions.Models.LogLevel.Trace,
Text = Path.GetFileName(artifact),
};

AttachFile(artifact, ref createLogItemRequest);

Task.Run(async () => await args.ClientService.LogItem.CreateAsync(createLogItemRequest)).GetAwaiter().GetResult();
}
}
}
}

private static void AttachFile(string filePath, ref CreateLogItemRequest request)
{
try
{
using (var fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var memoryStream = new MemoryStream())
{
fileStream.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();

request.Attach = new LogItemAttach
{
Name = Path.GetFileName(filePath),
MimeType = MimeTypeMap.GetMimeType(Path.GetExtension(filePath)),
Data = bytes
};
}
}
}
catch (Exception ex)
{
request.Text = $"{request.Text}\n> Couldn't read content of `{filePath}` file. \n{ex}";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using FluentAssertions;
using Moq;
using ReportPortal.Client.Abstractions.Requests;
using ReportPortal.Shared.Configuration;
using ReportPortal.Shared.Extensibility;
using ReportPortal.Shared.Extensibility.Embedded.LaunchArtifacts;
using ReportPortal.Shared.Tests.Helpers;
using System.IO;
using System.Threading;
using Xunit;

namespace ReportPortal.Shared.Tests.Extensibility.Embedded.LaunchArtifacts
{
public class LaunchArtifactsTest
{
private readonly IExtensionManager _extensionManager;

public LaunchArtifactsTest()
{
_extensionManager = new Shared.Extensibility.ExtensionManager();
_extensionManager.ReportEventObservers.Add(new LaunchArtifactsEventsObserver());
}

[Fact]
public void ShouldNotAttachArtifacts()
{
var client = new MockServiceBuilder().Build();

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest[]>(), default), Times.Never());
}

[Fact]
public void ShouldAttachSingleArtifactByPath()
{
File.WriteAllBytes("test_file_1.txt", new byte[] { 1, 2, 3 });

CreateLogItemRequest request = null;

var client = new MockServiceBuilder().Build();
client.Setup(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default))
.Callback<CreateLogItemRequest, CancellationToken>((a, b) => request = a);

var config = new ConfigurationBuilder().Build();
config.Properties["launch:artifacts"] = "test_file_1.txt";

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).WithConfiguration(config).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default), Times.Once);

request.Should().NotBeNull();
request.Text.Should().Be("test_file_1.txt");

request.Attach.Should().NotBeNull();
request.Attach.Name.Should().Be("test_file_1.txt");
request.Attach.MimeType.Should().Be("text/plain");
request.Attach.Data.Should().BeEquivalentTo(new byte[] { 1, 2, 3 });
}

[Fact]
public void ShouldAttachSeveralArtifactsByPath()
{
File.Create("test_file_1.txt").Close();
File.Create("test_file_2.txt").Close();

var client = new MockServiceBuilder().Build();

var config = new ConfigurationBuilder().Build();
config.Properties["launch:artifacts"] = "test_file_1.txt;test_file_2.txt";

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).WithConfiguration(config).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default), Times.Exactly(2));
}

[Fact]
public void ShouldAttachSingleArtifactByPattern()
{
File.WriteAllBytes("test_file_1.txt", new byte[] { 1, 2, 3 });

CreateLogItemRequest request = null;

var client = new MockServiceBuilder().Build();
client.Setup(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default))
.Callback<CreateLogItemRequest, CancellationToken>((a, b) => request = a);

var config = new ConfigurationBuilder().Build();
config.Properties["launch:artifacts"] = "test_*_1.txt";

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).WithConfiguration(config).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default), Times.Once);
}

[Fact]
public void ShouldAttachSingleArtifactByDir()
{
Directory.CreateDirectory("a/b/c");
File.WriteAllBytes("a/b/c/abc.txt", new byte[] { 1, 2, 3 });

CreateLogItemRequest request = null;

var client = new MockServiceBuilder().Build();
client.Setup(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default))
.Callback<CreateLogItemRequest, CancellationToken>((a, b) => request = a);

var config = new ConfigurationBuilder().Build();
config.Properties["launch:artifacts"] = "a/b/c/*.txt";

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).WithConfiguration(config).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default), Times.Once);
}

[Fact]
public void ShouldAttachMessageWithException()
{
File.Create("test_file_open.txt"); // leaves it open

CreateLogItemRequest request = null;

var client = new MockServiceBuilder().Build();
client.Setup(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default))
.Callback<CreateLogItemRequest, CancellationToken>((a, b) => request = a);

var config = new ConfigurationBuilder().Build();
config.Properties["launch:artifacts"] = "test_file_open.txt";

var launchReporter = new LaunchReporterBuilder(client.Object).With(_extensionManager).WithConfiguration(config).Build(1, 0, 0);
launchReporter.Sync();

client.Verify(s => s.LogItem.CreateAsync(It.IsAny<CreateLogItemRequest>(), default), Times.Once);

request.Text.Should().Contain("Couldn't read");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void ShouldExploreExtensions()
manager.Explore(Environment.CurrentDirectory);

manager.ReportEventObservers.Count.Should()
.Be(2, "default and google analytic observers should be registered by default");
.Be(3, "normalizer, google analytic, launch artifacts - observers should be registered by default");

manager.CommandsListeners.Should().HaveCount(1);
}
Expand Down
Loading