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

RNET-1158: Fix argument extraction for test client #3626

Merged
merged 2 commits into from
Jun 21, 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
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ dotnet_diagnostic.SA1637.severity = none

dotnet_diagnostic.SX1101.severity = warning


resharper_inconsistent_naming_highlighting = none


## Visual Studio generated .editorconfig file with C++ settings.
Expand Down
24 changes: 16 additions & 8 deletions Tests/Realm.Tests/Sync/SyncTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static SyncTestHelpers()
BaseUri = BaasUri ?? new Uri("http://localhost:12345"),
MetadataPersistenceMode = MetadataPersistenceMode.NotEncrypted,
#pragma warning disable CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id
BaseFilePath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"rt-sync-{System.Diagnostics.Process.GetCurrentProcess().Id}-{_appCounter++}")).FullName
BaseFilePath = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $"rt-sync-{Process.GetCurrentProcess().Id}-{_appCounter++}")).FullName
#pragma warning restore CA1837 // Use Environment.ProcessId instead of Process.GetCurrentProcess().Id
};

Expand Down Expand Up @@ -126,33 +126,41 @@ public static async Task<string[]> ExtractBaasSettingsAsync(string[] args)
return remainingArgs;
}

private class LogArgs
{
public string? RealmLogLevel { get; set; }

public string? RealmLogFile { get; set; }
}

public static (string[] RemainingArgs, IDisposable? Logger) SetLoggerFromArgs(string[] args)
{
var (extracted, remaining) = BaasClient.ExtractArguments(args, "realmloglevel", "realmlogfile");
var (extracted, remaining) = BaasClient.ExtractArguments<LogArgs>(args);

if (extracted.TryGetValue("realmloglevel", out var logLevelStr) && Enum.TryParse<LogLevel>(logLevelStr, out var logLevel))
if (!string.IsNullOrEmpty(extracted.RealmLogLevel))
{
var logLevel = (LogLevel)Enum.Parse(typeof(LogLevel), extracted.RealmLogLevel!);
TestHelpers.Output.WriteLine($"Setting log level to {logLevel}");

Logger.LogLevel = logLevel;
}

Logger.AsyncFileLogger? logger = null;
if (extracted.TryGetValue("realmlogfile", out var logFile))
if (!string.IsNullOrEmpty(extracted.RealmLogFile))
{
if (!Process.GetCurrentProcess().ProcessName.ToLower().Contains("testhost"))
{
TestHelpers.Output.WriteLine($"Setting sync logger to file: {logFile}");
TestHelpers.Output.WriteLine($"Setting sync logger to file: {extracted.RealmLogFile}");

// We're running in a test runner, so we need to use the sync logger
Logger.Default = Logger.File(logFile);
Logger.Default = Logger.File(extracted.RealmLogFile!);
}
else
{
TestHelpers.Output.WriteLine($"Setting async logger to file: {logFile}");
TestHelpers.Output.WriteLine($"Setting async logger to file: {extracted.RealmLogFile}");

// We're running standalone (likely on CI), so we use the async logger
Logger.Default = logger = new Logger.AsyncFileLogger(logFile);
Logger.Default = logger = new Logger.AsyncFileLogger(extracted.RealmLogFile!);
}
}

Expand Down
89 changes: 56 additions & 33 deletions Tools/DeployApps/BaasClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
#if !NETCOREAPP2_1_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif

namespace Baas
{
Expand Down Expand Up @@ -229,39 +229,57 @@ public static async Task<BaasClient> Atlas(Uri baseUri, string differentiator, T
return result;
}

private class BaasArgs
{
public string? BaasaasApiKey { get; set; }

public string? BaasUrl { get; set; }

public string? BaasCluster { get; set; }

public string? BaasApiKey { get; set; }

public string? BaasPrivateApiKey { get; set; }

public string? BaasProjectId { get; set; }

public string BaasDifferentiator { get; set; } = "local";

[MemberNotNullWhen(false, nameof(BaasCluster), nameof(BaasApiKey), nameof(BaasPrivateApiKey), nameof(BaasProjectId))]
public bool UseDocker => BaasCluster == null;
}

public static async Task<(BaasClient? Client, Uri? BaseUrl, string[] RemainingArgs)> CreateClientFromArgs(string[] args, TextWriter output)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}

var (extracted, remaining) = ExtractArguments(args, "baasaas-api-key", "baas-url", "baas-cluster",
"baas-api-key", "baas-private-api-key", "baas-projectid", "baas-differentiator");
var (extracted, remaining) = ExtractArguments<BaasArgs>(args);

var differentiator = extracted.GetValueOrDefault("baas-differentiator", "local");
var differentiator = extracted.BaasDifferentiator;

BaasClient client;
Uri baseUri;

if (extracted.TryGetValue("baasaas-api-key", out var baasaasApiKey) && !string.IsNullOrEmpty(baasaasApiKey))
if (!string.IsNullOrEmpty(extracted.BaasaasApiKey))
{
baseUri = await GetOrDeployContainer(baasaasApiKey, differentiator, output);
baseUri = await GetOrDeployContainer(extracted.BaasaasApiKey!, differentiator, output);
client = await Docker(baseUri, differentiator, output);
}
else
{
if (!extracted.TryGetValue("baasurl", out var baseUrl) || string.IsNullOrEmpty(baseUrl))
if (string.IsNullOrEmpty(extracted.BaasUrl))
{
return (null, null, remaining);
}

baseUri = new Uri(baseUrl);
var baasCluster = extracted.GetValueOrDefault("baascluster");
baseUri = new Uri(extracted.BaasUrl!);

client = string.IsNullOrEmpty(baasCluster)
client = extracted.UseDocker
? await Docker(baseUri, differentiator, output)
: await Atlas(baseUri, differentiator, output, baasCluster!, extracted["baasapikey"], extracted["baasprivateapikey"], extracted["baasprojectid"]);
: await Atlas(baseUri, differentiator, output, extracted.BaasCluster, extracted.BaasApiKey, extracted.BaasPrivateApiKey, extracted.BaasProjectId);
}

return (client, baseUri, remaining);
Expand All @@ -274,16 +292,16 @@ public static async Task TerminateBaasFromArgs(string[] args, TextWriter output)
throw new ArgumentNullException(nameof(args));
}

var (extracted, _) = ExtractArguments(args, "baasaas-api-key", "baas-differentiator");
var (extracted, _) = ExtractArguments<BaasArgs>(args);

var differentiator = extracted.GetValueOrDefault("baas-differentiator", "local");
var differentiator = extracted.BaasDifferentiator;

if (!extracted.TryGetValue("baasaas-api-key", out var baaSaasApiKey) || string.IsNullOrEmpty(baaSaasApiKey))
if (string.IsNullOrEmpty(extracted.BaasaasApiKey))
{
throw new InvalidOperationException("Need a BaaSaas API key to terminate containers");
throw new InvalidOperationException("Need a Baasaas API key to terminate containers");
}

await StopContainer(baaSaasApiKey, differentiator, output);
await StopContainer(extracted.BaasaasApiKey!, differentiator, output);
}

public string GetSyncDatabaseName(string appType = AppConfigType.Default) => $"Sync_{Differentiator}_{appType}";
Expand Down Expand Up @@ -805,36 +823,41 @@ public static HttpContent GetJsonContent(object obj)
return content;
}

public static (Dictionary<string, string> Extracted, string[] RemainingArgs) ExtractArguments(string[] args, params string[] toExtract)
// Extract arguments and populate a type T with the extracted values. Only string readwrite properties
// will be considered.
public static (T Extracted, string[] RemainingArgs) ExtractArguments<T>(string[] args)
where T : new()
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}

var extracted = new Dictionary<string, string>();
var remainingArgs = new List<string>();
for (var i = 0; i < args.Length; i++)
{
if (!toExtract.Any(name => ExtractArg(i, name)))
{
remainingArgs.Add(args[i]);
}
}
var argsToExtract = typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(string) && p is { CanRead: true, CanWrite: true })
.Select(p => (ArgName: GetArgName(p.Name), PropertyInfo: p))
.ToArray();

return (extracted, remainingArgs.ToArray());
var extracted = new T();
return (extracted, args.Where(a => !argsToExtract.Any(kvp => ExtractArg(a, kvp))).ToArray());

bool ExtractArg(int index, string name)
bool ExtractArg(string arg, (string ArgName, PropertyInfo Info) prop)
{
var arg = args[index];
if (arg.StartsWith($"--{name}="))
if (arg.StartsWith($"--{prop.ArgName}="))
{
extracted[name] = arg.Replace($"--{name}=", string.Empty);
prop.Info.SetValue(extracted, arg.Replace($"--{prop.ArgName}=", string.Empty));
return true;
}

return false;
}

static string GetArgName(string propertyName)
{
return Regex.Replace(propertyName, "(?<!^)([A-Z][a-z]|(?<=[a-z])[A-Z0-9])", "-$1", RegexOptions.Compiled)
.Trim()
.ToLower();
}
}

public class BaasApp
Expand Down
Loading